
Qwen-Audio-Chat WebDemo 部署实战在 AutoDL 上搭建 Gradio 音频对话 Web 界面【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/datawhalechina/self-llm本篇指南完整复现 Datawhale《开源大模型食用指南》中 Qwen-Audio-Chat WebDemo 的部署全流程从 AutoDL 租用 24G 显存显卡、配置 pip 环境、通过 ModelScope 下载 20GB 模型权重到逐段解析基于 Gradio 构建的多模态聊天机器人代码最终通过端口映射在本地浏览器中与模型进行「上传音频 / 录音 / 文本」的多轮对话。读完本文你将掌握一套可直接复用的音频大模型 Web 应用部署方案并理解其中对话历史、音频时间戳切片等核心实现原理。Qwen-Audio 简介Qwen-Audio是阿里云研发的大规模音频语言模型Large Audio Language ModelLALM。它以多种音频包括说话人语音、自然音、音乐、歌声和文本作为输入并以文本作为输出属于典型的多模态音频 文本对话模型。输入音频文件如flac、wav、mp3等 文本指令输出文本回答模型规模以 Qwen-Audio-Chat 对话版为例模型权重约 20GB需要 24G 显存级别的显卡如 RTX 3090方可流畅推理。在本仓库中该模型的部署教程位于 models/Qwen-Audio 目录下共两篇互为补充01-Qwen-Audio-chat FastApi.md面向 API 服务化调用场景02-Qwen-Audio-chat WebDemo.md面向交互式 Web 界面场景本文主体。环境准备租用 GPU 机器并安装依赖租用 AutoDL 实例本教程基于 AutoDL 云平台完成推荐租用一张3090 等 24G 显存的显卡机器。创建实例时选择镜像如下图所示依次选择PyTorch → 2.0.0 → 3.8(ubuntu20.04) → 11.8CUDA 11.3 版本以上的均可。实例启动后打开服务器自带的JupyterLab在其中的终端Terminal里完成接下来的环境配置、模型下载和运行演示。pip 换源与依赖安装为了加速库安装先升级 pip 并将 pypi 源更换为清华源然后按顺序安装模型运行所需的依赖包# 升级pip python -m pip install --upgrade pip # 更换 pypi 源加速库的安装 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip install modelscope1.9.5 pip install accelerate pip install tiktoken pip install einops pip install transformers_stream_generator0.0.4 pip install scipy pip install torchvision pip install pillow pip install tensorboard pip install matplotlib pip install transformers4.32.0 pip install gradio3.39.0 pip install nest_asyncio各依赖包在本次部署中的作用简要说明如下依赖包版本要求作用modelscope1.9.5ModelScope 模型下载与模型仓库 APItransformers4.32.0模型加载、分词器加载与对话推理accelerate最新支持device_map自动设备分配与显存优化加载gradio3.39.0构建 Web 聊天界面chatBot.py 的核心 UI 框架tiktoken最新Qwen 系列模型分词器依赖einops最新模型结构中张量维度重排操作transformers_stream_generator0.0.4流式生成支持scipy/torchvision/pillow/tensorboard/matplotlib最新多模态与训练可视化通用依赖nest_asyncio最新在 Jupyter/嵌套事件循环环境中兼容 asyncio从代码结构看chatBot.py中还导入了pydubfrom pydub import AudioSegment用于按时间戳切分音频片段而原教程的依赖清单并未包含该库。因此实际运行前建议补装pip install pydub且pydub在底层依赖ffmpeg完成格式解码若环境缺失还需先安装 ffmpeg例如 Ubuntu 下执行apt-get install ffmpeg。模型下载使用 ModelScope 拉取 Qwen-Audio-Chat模型权重通过modelscope中的snapshot_download函数下载。第一个参数为模型名称参数cache_dir为模型的下载路径。在/root/autodl-tmp路径下新建download.py文件并输入以下内容粘贴代码后记得保存文件然后运行python /root/autodl-tmp/download.py执行下载import torch from modelscope import snapshot_download, AutoModel, AutoTokenizer from modelscope import GenerationConfig model_dir snapshot_download(qwen/Qwen-Audio-Chat, cache_dir/root/autodl-tmp, revisionmaster)模型大小约20 GB受网络状况影响下载大概需要10~20 分钟下载完成后模型权重会被保存在/root/autodl-tmp/qwen/Qwen-Audio-Chat目录下该路径正是后续chatBot.py中DEFAULT_CKPT_PATH指向的默认加载路径revisionmaster表示拉取主分支上的最新权重。代码准备chatBot.py 完整实现与逐段解析在/root/autodl-tmp路径下新建chatBot.py文件并输入以下内容代码出自本仓库 02-Qwen-Audio-chat WebDemo.md保留了完整注释# Copyright (c) Alibaba Cloud. # # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. A simple web interactive chat demo based on gradio. from argparse import ArgumentParser from pathlib import Path import copy import gradio as gr import os import re import secrets import tempfile from transformers import AutoModelForCausalLM, AutoTokenizer from transformers.generation import GenerationConfig from pydub import AudioSegment # DEFAULT_CKPT_PATH Qwen/Qwen-Audio-Chat DEFAULT_CKPT_PATH /root/autodl-tmp/qwen/Qwen-Audio-Chat def _get_args(): parser ArgumentParser() parser.add_argument(-c, --checkpoint-path, typestr, defaultDEFAULT_CKPT_PATH, helpCheckpoint name or path, default to %(default)r) parser.add_argument(--cpu-only, actionstore_true, helpRun demo with CPU only) parser.add_argument(--share, actionstore_true, defaultFalse, helpCreate a publicly shareable link for the interface.) parser.add_argument(--inbrowser, actionstore_true, defaultFalse, helpAutomatically launch the interface in a new tab on the default browser.) parser.add_argument(--server-port, typeint, default8000, helpDemo server port.) parser.add_argument(--server-name, typestr, default127.0.0.1, helpDemo server name.) args parser.parse_args() return args def _load_model_tokenizer(args): tokenizer AutoTokenizer.from_pretrained( args.checkpoint_path, trust_remote_codeTrue, resume_downloadTrue, ) if args.cpu_only: device_map cpu else: device_map cuda model AutoModelForCausalLM.from_pretrained( args.checkpoint_path, device_mapdevice_map, trust_remote_codeTrue, resume_downloadTrue, ).eval() model.generation_config GenerationConfig.from_pretrained( args.checkpoint_path, trust_remote_codeTrue, resume_downloadTrue, ) return model, tokenizer def _parse_text(text): lines text.split(\n) lines [line for line in lines if line ! ] count 0 for i, line in enumerate(lines): if in line: count 1 items line.split() if count % 2 1: lines[i] fprecode classlanguage-{items[-1]} else: lines[i] fbr/code/pre else: if i 0: if count % 2 1: line line.replace(, r\) line line.replace(, lt;) line line.replace(, gt;) line line.replace( , nbsp;) line line.replace(*, ast;) line line.replace(_, lowbar;) line line.replace(-, #45;) line line.replace(., #46;) line line.replace(!, #33;) line line.replace((, #40;) line line.replace(), #41;) line line.replace($, #36;) lines[i] br line text .join(lines) return text def _launch_demo(args, model, tokenizer): uploaded_file_dir os.environ.get(GRADIO_TEMP_DIR) or str( Path(tempfile.gettempdir()) / gradio ) def predict(_chatbot, task_history): query task_history[-1][0] print(User: _parse_text(query)) history_cp copy.deepcopy(task_history) full_response history_filter [] audio_idx 1 pre global last_audio for i, (q, a) in enumerate(history_cp): if isinstance(q, (tuple, list)): last_audio q[0] q fAudio {audio_idx}: audio{q[0]}/audio pre q \n audio_idx 1 else: pre q history_filter.append((pre, a)) pre history, message history_filter[:-1], history_filter[-1][0] response, history model.chat(tokenizer, message, historyhistory) ts_pattern r\|\d{1,2}\.\d\| all_time_stamps re.findall(ts_pattern, response) print(response) if (len(all_time_stamps) 0) and (len(all_time_stamps) % 2 0) and last_audio: ts_float [ float(t.replace(|,).replace(|,)) for t in all_time_stamps] ts_float_pair [ts_float[i:i 2] for i in range(0,len(all_time_stamps),2)] # 读取音频文件 format os.path.splitext(last_audio)[-1].replace(.,) audio_file AudioSegment.from_file(last_audio, formatformat) chat_response_t response.replace(|, ).replace(|, ) chat_response chat_response_t temp_dir secrets.token_hex(20) temp_dir Path(uploaded_file_dir) / temp_dir temp_dir.mkdir(exist_okTrue, parentsTrue) # 截取音频文件 for pair in ts_float_pair: audio_clip audio_file[pair[0] * 1000: pair[1] * 1000] # 保存音频文件 name ftmp{secrets.token_hex(5)}.{format} filename temp_dir / name audio_clip.export(filename, formatformat) _chatbot[-1] (_parse_text(query), chat_response) _chatbot.append((None, (str(filename),))) else: _chatbot[-1] (_parse_text(query), response) full_response _parse_text(response) task_history[-1] (query, full_response) print(Qwen-Audio-Chat: _parse_text(full_response)) return _chatbot def regenerate(_chatbot, task_history): if not task_history: return _chatbot item task_history[-1] if item[1] is None: return _chatbot task_history[-1] (item[0], None) chatbot_item _chatbot.pop(-1) if chatbot_item[0] is None: _chatbot[-1] (_chatbot[-1][0], None) else: _chatbot.append((chatbot_item[0], None)) return predict(_chatbot, task_history) def add_text(history, task_history, text): history history [(_parse_text(text), None)] task_history task_history [(text, None)] return history, task_history, def add_file(history, task_history, file): history history [((file.name,), None)] task_history task_history [((file.name,), None)] return history, task_history def add_mic(history, task_history, file): if file is None: return history, task_history os.rename(file, file .wav) print(add_mic file:, file) print(add_mic history:, history) print(add_mic task_history:, task_history) # history history [((file.name,), None)] # task_history task_history [((file.name,), None)] task_history task_history [((file .wav,), None)] history history [((file .wav,), None)] print(task_history, task_history) return history, task_history def reset_user_input(): return gr.update(value) def reset_state(task_history): task_history.clear() return [] with gr.Blocks() as demo: gr.Markdown(\ p aligncenterimg srchttps://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-Audio/logo.jpg styleheight: 80px/p) ## todo gr.Markdown(centerfont size8Qwen-Audio-Chat Bot/center) gr.Markdown( \ centerfont size3This WebUI is based on Qwen-Audio-Chat, developed by Alibaba Cloud. \ (本WebUI基于Qwen-Audio-Chat打造实现聊天机器人功能。)/center) gr.Markdown(\ centerfont size4Qwen-Audio a hrefhttps://modelscope.cn/models/qwen/Qwen-Audio/summary /a | a hrefhttps://huggingface.co/Qwen/Qwen-Audio/anbsp Qwen-Audio-Chat a hrefhttps://modelscope.cn/models/qwen/Qwen-Audio-Chat/summary /a | a hrefhttps://huggingface.co/Qwen/Qwen-Audio-Chat/anbsp nbspa hrefhttps://github.com/QwenLM/Qwen-AudioGithub/a/center) chatbot gr.Chatbot(labelQwen-Audio-Chat, elem_classescontrol-height, height750) query gr.Textbox(lines2, labelInput) task_history gr.State([]) mic gr.Audio(sourcemicrophone, typefilepath) with gr.Row(): empty_bin gr.Button( Clear History (清除历史)) submit_btn gr.Button( Submit (发送)) regen_btn gr.Button(️ Regenerate (重试)) addfile_btn gr.UploadButton( Upload (上传文件), file_types[audio]) mic.change(add_mic, [chatbot, task_history, mic], [chatbot, task_history]) submit_btn.click(add_text, [chatbot, task_history, query], [chatbot, task_history]).then( predict, [chatbot, task_history], [chatbot], show_progressTrue ) submit_btn.click(reset_user_input, [], [query]) empty_bin.click(reset_state, [task_history], [chatbot], show_progressTrue) regen_btn.click(regenerate, [chatbot, task_history], [chatbot], show_progressTrue) addfile_btn.upload(add_file, [chatbot, task_history, addfile_btn], [chatbot, task_history], show_progressTrue) gr.Markdown(\ font size2Note: This demo is governed by the original license of Qwen-Audio. \ We strongly advise users not to knowingly generate or allow others to knowingly generate harmful content, \ including hate speech, violence, pornography, deception, etc. \ (注本演示受Qwen-Audio的许可协议限制。我们强烈建议用户不应传播及不应允许他人传播以下内容\ 包括但不限于仇恨言论、暴力、色情、欺诈相关的有害信息。)) demo.queue().launch( shareargs.share, inbrowserargs.inbrowser, server_portargs.server_port, server_nameargs.server_name, file_directories[/tmp/] ) def main(): args _get_args() model, tokenizer _load_model_tokenizer(args) _launch_demo(args, model, tokenizer) if __name__ __main__: main()启动参数解析_get_args程序入口main()首先调用_get_args()解析命令行参数全部参数均有合理默认值可直接无参数运行参数默认值说明-c, --checkpoint-path/root/autodl-tmp/qwen/Qwen-Audio-Chat模型权重路径若你更换了下载目录需同步修改--cpu-onlyFalse是否仅用 CPU 推理显存不足时的兜底选项速度较慢--shareFalse是否生成 Gradio 公共分享链接需外网--inbrowserFalse启动后是否自动在默认浏览器打开页面--server-port8000Gradio 服务监听端口--server-name127.0.0.1Gradio 服务监听地址仅本机可访问模型与分词器加载_load_model_tokenizertokenizer AutoTokenizer.from_pretrained(args.checkpoint_path, trust_remote_codeTrue, resume_downloadTrue)trust_remote_codeTrueQwen-Audio 的模型结构与分词逻辑通过仓库内自定义代码实现必须开启该选项device_map默认cuda配合accelerate将模型加载到 GPU传入--cpu-only时切换为cpu加载后立即.eval()切换到推理模式并从权重目录读取GenerationConfig包含do_sample、top_p、temperature等生成策略配置作为默认生成配置resume_downloadTrue下载中断后支持断点续传。Markdown 渲染_parse_text_parse_text负责把模型输出的原始文本安全地渲染为聊天界面中的 HTML/Markdown处理 代码块成对出现的代码块被转换为precode classlanguage-xxx结构以便高亮展示对代码块内部的特殊字符如、、*、_、$等逐一转义为 HTML 实体避免被浏览器误解析为标签或格式符号普通文本行之间插入br实现换行。这样做的目的是防止模型输出被当作 HTML/脚本执行同时保证代码类回答在界面上可读。对话核心predict与音频时间戳切片predict是整个 Demo 的灵魂函数其工作流程如下历史重构遍历task_history凡是(q, a)中的q为元组/列表即音频条目时将其格式化为Audio N: audio文件路径/audio拼接到上下文遇到文本条目时把累积内容作为一个历史轮次存入history_filter。最终history_filter[:-1]作为多轮对话历史最后一条累积文本作为当前message模型推理调用model.chat(tokenizer, message, historyhistory)得到response与更新后的history时间戳检测与音频切片用正则r\|\d{1,2}\.\d\|匹配模型输出中的时间戳标记如|0.5|。Qwen-Audio-Chat 在回答与音频相关的问题时会在文本中标注时间点切片播放当时间戳数量为偶数且存在last_audio最近一次上传/录制的音频时将时间戳两两配对为[start, end]使用pydub.AudioSegment读取原音频并按毫秒区间pair[0]*1000 : pair[1]*1000截取片段导出到GRADIO_TEMP_DIR下的临时目录并在聊天记录中以可播放音频的形式追加展示结果渲染将回答文本经_parse_text处理后写回task_history返回更新后的_chatbot供界面刷新。通过这一机制用户不仅能看到文本答案还能直接听到模型定位到的对应音频片段这正是音频对话模型的特色体验。多轮交互辅助函数regenerate将最后一条助手回复置空并重新调用predict实现「重试」功能add_text把文本框内容以(text, None)形式追加到历史并清空输入框add_file处理「上传文件」按钮把音频文件路径(file.name,)加入历史add_mic处理「录音」输入将麦克风录制的临时文件重命名为.wav后加入历史reset_state清空task_history即清空全部对话记忆。Gradio 界面构建与事件绑定界面使用gr.Blocks()组件化构建gr.Chatbot聊天面板高 750px展示对话气泡与音频卡片gr.Textbox文本输入框gr.Audio(sourcemicrophone)麦克风录音组件四个按钮清除历史Clear History、发送Submit、重试Regenerate、上传音频文件Upload仅接受audio类型文件事件链submit_btn.click(add_text).then(predict)先追加文本再触发推理同时重置输入框mic.change、regen_btn.click、empty_bin.click、addfile_btn.upload分别绑定对应处理函数demo.queue()启用请求队列避免并发冲突launch()中file_directories[/tmp/]暴露临时音频目录使切分出的音频片段可被浏览器直接下载播放。运行 WebDemo端口映射与本地访问代码保存完成后在 AutoDL 终端中运行以下命令原教程给出的启动命令streamlit run /root/autodl-tmp/chatBot.py --server.address 127.0.0.1 --server.port 6006需要特别说明的是chatBot.py实际是基于Gradio构建的应用代码中import gradio as gr并使用gr.Blocks并非 Streamlit 应用因此更规范的启动方式是直接执行python /root/autodl-tmp/chatBot.py此时 Gradio 服务默认监听127.0.0.1:8000如需修改端口可追加参数例如python /root/autodl-tmp/chatBot.py --server-port 6006服务启动后按照 AutoDL 控制台的指引将实例的对应端口本教程约定映射到本地6006端口映射到本地然后在浏览器中打开链接http://localhost:6006/即可看到如下所示的聊天界面页面顶部展示了 Qwen-Audio-Chat Bot 标题与模型来源信息中部为 750px 高的聊天面板下方依次是文本输入框、麦克风录音组件以及「清除历史 / 发送 / 重试 / 上传音频」四个操作按钮底部附有模型许可协议与内容合规提示。补充音频-文本输入格式解析来自 FastApi 教程WebDemo 底层依赖的model.chat接口其音频输入的组织方式在仓库的 01-Qwen-Audio-chat FastApi.md 中有更直白的展示。核心是使用分词器的from_list_format方法把「音频路径 文本指令」组装成对话查询query tokenizer.from_list_format([ {audio: temp_file_path}, # 音频文件路径 {text: what does the person say?}, # 文本指令 ]) response, history model.chat(tokenizer, queryquery, historyNone)列表中的每个元素对应一种模态输入{audio: 路径}注入音频{text: 指令}注入文本FastApi 教程还提供了/test-audio/与/audio/两个接口用于服务化调用并配套了一段测试音频如1272-128104-0000.flac用于快速验证「人物语音识别」类问题如果读者希望将本 WebDemo 改造成 RESTful API 服务可直接参考该篇文档中的 FastAPI 实现包括torch_gc()显存回收、uvicorn多 worker 部署等细节。使用要点与注意事项显存需求模型权重约 20GB推理态建议使用 24G 显存显卡如 RTX 3090显存紧张时可尝试--cpu-only参数但推理速度会明显下降输入方式支持三种音频输入路径——上传本地音频文件、麦克风实时录音、以及在文本框中仅输入文字纯文本对话同样可用音频时间戳当回答内容包含成对的时间戳标记形如|0.5|时界面会自动切出对应音频片段供逐段试听这是 Qwen-Audio-Chat 对音频进行细粒度定位回答的典型表现端口与访问Gradio 服务默认仅监听127.0.0.1远程访问必须通过 AutoDL 端口映射如需生成公网临时链接可追加--share参数依赖外网连通性多轮记忆task_historygr.State保存完整对话上下文点击「Clear History」可随时清空重新开始独立会话内容合规页面底部已内置许可协议提示使用时应遵守 Qwen-Audio 原始许可协议不得生成或传播仇恨言论、暴力、色情、欺诈等有害内容。至此一个可交互的 Qwen-Audio-Chat 音频对话 Web 应用便部署完成。你可以上传任意语音、音乐或自然音片段尝试「这段音频里说了什么」「这是男声还是女声」「识别一下背景音乐」等多模态问答体验音频大模型的能力边界。【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/datawhalechina/self-llm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考