VibeVoice 的 vLLM server 怎么起:start_server.py 逐段读
手里有一个 ASR 模型,想让它变成一个别人能用 HTTP 调的服务——这件事的麻烦从来不在”起个服务”,而在于:模型目录里缺哪些文件、tokenizer 要不要额外生成、vLLM 怎么认得这个架构、多卡时是把一个模型切开还是跑多份。VibeVoice 仓库把这些拼在了一个脚本里:vllm_plugin/scripts/start_server.py。
它的 docstring 自述是”一键部署脚本”,处理五件事:装系统依赖、装 VibeVoice 包、从 Hugging Face 下模型、生成 tokenizer 文件、启动 vLLM server。下面按这五步走一遍,重点看哪些是你能从命令行调的、哪些是脚本写死的。
前置条件:这个脚本假定自己跑在容器里
先说最容易踩的一点:这个脚本不是给你在自己机器上裸跑的。
看代码就知道。install_system_deps() 直接调 apt-get update 和 apt-get install -y ffmpeg libsndfile1;_install_nginx() 先用 which nginx 探测、探不到再走 apt-get install -y nginx;install_vibevoice() 执行的是 pip install -e /app[vllm],路径 /app 是写死的。这几条都是 Debian/Ubuntu 系 Linux 的命令与路径,脚本里没有任何分支去处理别的系统。
仓库文档 docs/vibevoice-vllm-asr.md 给出的启动方式也印证了这一点,它把脚本放进官方 vLLM 镜像里执行:
docker run -d --gpus all --name vibevoice-vllm \
--ipc=host \
-p 8000:8000 \
-e VIBEVOICE_FFMPEG_MAX_CONCURRENCY=64 \
-e PYTORCH_ALLOC_CONF=expandable_segments:True \
-v $(pwd):/app \
-w /app \
--entrypoint bash \
vllm/vllm-openai:v0.14.1 \
-c "python3 /app/vllm_plugin/scripts/start_server.py"
这段是原样抄自该文档的,其中的镜像 tag 与两个环境变量取值是仓库文档当前写的示例值,会随仓库更新变动。注意 -v $(pwd):/app:宿主机目录必须挂到容器的 /app,因为脚本装包时写死了 /app,后面拼 vllm serve 命令时也写死了 --allowed-local-media-path /app。挂到别处,这两处都对不上。
Windows 侧:脚本本身跑在容器内的 Linux 环境里,所以你要装的是能给容器分配 GPU 的 Docker 环境,而不是在 PowerShell 里直接 python start_server.py——apt-get、which、nginx 在 Windows 上都不存在。另外上面那条命令里的 $(pwd) 是 POSIX shell 的当前目录替换,PowerShell 不认这个写法,需要换成对应的当前路径表达(这属于通用的 shell 差异,不是 VibeVoice 仓库的官方说明)。仓库里我们没有找到关于 Windows 原生运行该脚本的说明。
Python 版本方面,pyproject.toml 写的是 requires-python = ">=3.10"。
五步流程:main() 里发生了什么
main() 用 argparse 收参数,然后依次执行。逐步说:
第一步,系统依赖。装 ffmpeg 与 libsndfile1。加 --skip-deps 可以整段跳过。
第二步,装包。pip install -e /app[vllm],-e 是可编辑安装,意味着容器里改代码不用重装。
第三步,下模型。调 huggingface_hub 的 snapshot_download(model_id),用默认缓存目录,返回的本地路径后面一路传下去。默认 model id 是 microsoft/VibeVoice-ASR,用 --model / -m 改。注意这是 ASR 模型,跟 VibeVoice 家族里的其它模型不是同一个。
第四步,生成 tokenizer 文件。执行 python -m vllm_plugin.tools.generate_tokenizer_files --output <模型路径>,也就是直接写进上一步下载的模型目录。这一步值得多看两眼,因为它不是”复制几个文件”那么简单:vllm_plugin/tools/generate_tokenizer_files.py 会从 Qwen2.5 基座拉 vocab.json、merges.txt、tokenizer.json、tokenizer_config.json,然后打补丁——往 added_tokens_decoder 里补 QWEN25_EXTENDED_TOKENS 与 VIBEVOICE_AUDIO_TOKENS(后者是 <|AUDIO|>、<|audio_bos|>、<|audio_eos|>),把 chat_template 改成能把 part['type'] == 'audio' 或 'audio_url' 渲染成 <|AUDIO|>,再把 model_max_length 设成 131072,最后生成 added_tokens.json 与 special_tokens_map.json。加 --skip-tokenizer 可以跳过这一步——如果模型目录里已经有这几个文件的话。
第五步,起服务。看 --dp 的值分两条路,下一节说。
你能调的参数,和你调不了的那串
命令行这一层,main() 里定义了这些:
| 参数 | 作用(照脚本的 help 文本) | 默认值 |
|---|---|---|
--model / -m | Hugging Face model ID | microsoft/VibeVoice-ASR |
--port / -p | 服务端口 | 8000 |
--skip-deps | 跳过安装系统依赖 | 关 |
--skip-tokenizer | 跳过生成 tokenizer 文件 | 关 |
--tp / --tensor-parallel-size | 把一个模型切到 N 张卡上 | 1 |
--dp / --data-parallel-size | 跑 N 份独立副本做负载均衡 | 1 |
--max-num-seqs | 每批最大序列数 | 64 |
--max-model-len | 最大上下文长度 | 65536 |
--gpu-memory-utilization | 显存占用比例 | 0.8 |
这些默认值是仓库当前代码里的取值,随版本可能变动;它们是脚本传给 vLLM 的入参,不是”你用起来会怎样”的保证。
真正容易被忽略的是 _build_vllm_cmd()。这个函数拼出最终的 vllm serve 命令,里面有一批选项是写死的、命令行改不了的:--served-model-name vibevoice、--trust-remote-code、--dtype bfloat16、--no-enable-prefix-caching、--enable-chunked-prefill、--chat-template-content-format openai、--allowed-local-media-path /app。上表那几个参数只是被格式化后塞进这条命令的尾巴。所以想改 dtype 或者关掉 chunked prefill,得改脚本,或者干脆不用这个脚本、自己写 vllm serve。
--served-model-name vibevoice 尤其要记住:它决定了客户端请求体里 model 字段该填什么。
vLLM 是怎么认识 VibeVoice 的
这部分不在 start_server.py 里,但脚本能跑通全靠它。pyproject.toml 声明了一个 entry point:
[project.entry-points."vllm.general_plugins"]
vibevoice = "vllm_plugin:register_vibevoice"
于是第二步的 pip install -e 一装完,vLLM 启动时就会通过这个 entry point 调到 vllm_plugin/__init__.py 里的 register_vibevoice()。该函数的注释自述它注册了四样东西:把 VibeVoiceConfig 注册给 AutoConfig、把 VibeVoiceASRTextTokenizerFast 注册给 AutoTokenizer(slow_tokenizer_class 给的是 Qwen2Tokenizer)、把 Qwen2AudioProcessor 注册给 AutoProcessor,最后向 vLLM 的 ModelRegistry 注册模型类 VibeVoiceForCausalLM,架构名注册了两个:"VibeVoice" 与 "VibeVoiceForASRTraining"。
代码注释写明”这个名字必须和 config.json 里的 architectures 对得上”。翻一下 microsoft/VibeVoice-ASR 的 config,architectures 正是 ["VibeVoiceForASRTraining"]——两处对上了。同一函数里还有一段注释说明 tokenizer 的映射关系(speech_start_id 对 <|object_ref_start|>、speech_pad_id 对 <|box_start|>、speech_end_id 对 <|object_ref_end|>),并强调这个对齐会影响 ASR 结果——这是注释的原话,具体影响多大仓库里没有量化说明,我们也没有跑过。
边界:几处读代码就能看见的坑
[vllm] 这个 extra 在 pyproject.toml 里查不到。 脚本装的是 /app[vllm],但 pyproject.toml 的 [project.optional-dependencies] 里只定义了 streamingtts 一项。两处白纸黑字放在一起就是这样,仓库里没有解释,我们也不替它推断后果。
多卡的两条路差别很大。 --dp 大于 1 时走 start_dp_server():先 import torch 数 torch.cuda.device_count(),用 assert 卡住”可用卡数 ≥ dp × tp”,然后给每个 worker 分配 CUDA_VISIBLE_DEVICES、分配后端端口(规则是 frontend_port + 100 + rank)、用 subprocess.Popen 各起一个 dp=1 的 vLLM 进程,前面挂一个 nginx 做反向代理。nginx 配置由 _write_nginx_config() 生成,写到 /tmp/nginx_vllm.conf,用 least_conn 策略,worker_processes 默认按后端数的两倍算。--dp 为 1 时则走 start_vllm_server(),末尾是 os.execvp("vllm", vllm_cmd)——直接用 vLLM 进程顶替掉当前 Python 进程,所以这条路上既没有 nginx,也没有下面说的健康等待。
健康检查失败不会让脚本停下。 DP 路径起完 worker 后会逐个轮询 http://127.0.0.1:<port>/v1/models,for attempt in range(600) 配 time.sleep(1),代码注释标的是”up to 10 minutes”;但循环走完仍失败时,for...else 分支只打印一行失败信息,没有退出。这时 nginx 已经在往这个后端转发了。这是代码写明的行为,请自行判断在你的场景里意味着什么。
请求体大小有上限。 生成的 nginx 配置里 client_max_body_size 200m,proxy_read_timeout 与 proxy_send_timeout 都是 600s——这些是仓库当前代码里的取值,随版本可能变动。ASR 请求里音频是 base64 塞进 JSON 的(见 vllm_plugin/tests/test_api.py),这个上限就不只是理论值了。
没有鉴权。 我们在 start_server.py 里没有找到任何与 API key、鉴权或访问控制相关的参数或配置;脚本也没有对 nginx 前端做来源限制。要放到公网上,得自己在外面加一层——这属于通用运维做法,不是该项目的官方内容。
关停行为:DP 路径注册了 SIGTERM 与 SIGINT 处理,会把信号转给 nginx 与所有 worker;主循环里任何一个 worker 或 nginx 退出,都会触发整体关停。
怎么确认起对了
三个动作,从浅到深:
- 看日志。文档给的是
docker logs -f vibevoice-vllm。脚本每一步都会打一段====包起来的标题,卡在哪一步一眼能看出来。 - 打
/v1/models。这正是脚本自己用来判断后端就绪的地址;DP 模式下别只看前端端口,按frontend_port + 100 + rank的规则把每个后端端口都打一遍,否则少起一个副本你也看不出来。 - 发一次真实请求。仓库带了脚本,文档里的用法是
docker exec -it vibevoice-vllm python3 vllm_plugin/tests/test_api.py /app/audio.wav,另外还有一个带自动恢复逻辑的test_api_auto_recover.py。test_api.py里请求打的是{base_url}/v1/chat/completions,payload 的"model"字段填的是"vibevoice",音频以{"type": "audio_url", "audio_url": {"url": data_url}}的形式传,data_url是 base64 编码后的 data URI。文档提醒音频/视频文件必须放在挂载进去的目录(容器里的/app)。
如果第 3 步报 model 找不到,先回头看 --served-model-name 那一项;如果报音频解码失败,文档的 troubleshooting 一节让你先确认 FFmpeg 装上了。
以上命令均原样抄自仓库文档与脚本,属于按仓库文档中的参数语义组合的示例,未经实测,以仓库最新内容与 --help 的实际输出为准。该项目持续更新,文中涉及的参数名、默认值与文件路径随版本变动。
本文依据 github.com/microsoft/VibeVoice 仓库与 Hugging Face 模型卡于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有下载权重、没有跑过推理、也没有做过训练,
因此不涉及显存占用、推理速度、识别准确率与音质的任何描述,也不与其它模型做比较或排名。
该项目持续更新,文中涉及的模块路径、配置字段与接口写法随版本变动,请以仓库最新内容为准。
仓库 README 的风险与限制一节写明:该模型仅供研究与开发用途, 未经进一步测试与开发不建议用于商业或真实场景,并特别提示了合成语音被用于伪造与虚假信息的风险。 使用合成语音时应遵守所在司法辖区的法律法规,并在分享 AI 生成内容时主动披露。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。