VibeVoice 的 vLLM server 怎么起:start_server.py 逐段读

2026-08-18

手里有一个 ASR 模型,想让它变成一个别人能用 HTTP 调的服务——这件事的麻烦从来不在”起个服务”,而在于:模型目录里缺哪些文件、tokenizer 要不要额外生成、vLLM 怎么认得这个架构、多卡时是把一个模型切开还是跑多份。VibeVoice 仓库把这些拼在了一个脚本里:vllm_plugin/scripts/start_server.py

它的 docstring 自述是”一键部署脚本”,处理五件事:装系统依赖、装 VibeVoice 包、从 Hugging Face 下模型、生成 tokenizer 文件、启动 vLLM server。下面按这五步走一遍,重点看哪些是你能从命令行调的、哪些是脚本写死的。

前置条件:这个脚本假定自己跑在容器里

先说最容易踩的一点:这个脚本不是给你在自己机器上裸跑的

看代码就知道。install_system_deps() 直接调 apt-get updateapt-get install -y ffmpeg libsndfile1_install_nginx() 先用 which nginx 探测、探不到再走 apt-get install -y nginxinstall_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-getwhichnginx 在 Windows 上都不存在。另外上面那条命令里的 $(pwd) 是 POSIX shell 的当前目录替换,PowerShell 不认这个写法,需要换成对应的当前路径表达(这属于通用的 shell 差异,不是 VibeVoice 仓库的官方说明)。仓库里我们没有找到关于 Windows 原生运行该脚本的说明。

Python 版本方面,pyproject.toml 写的是 requires-python = ">=3.10"

五步流程:main() 里发生了什么

main()argparse 收参数,然后依次执行。逐步说:

第一步,系统依赖。装 ffmpeglibsndfile1。加 --skip-deps 可以整段跳过。

第二步,装包pip install -e /app[vllm]-e 是可编辑安装,意味着容器里改代码不用重装。

第三步,下模型。调 huggingface_hubsnapshot_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.jsonmerges.txttokenizer.jsontokenizer_config.json,然后打补丁——往 added_tokens_decoder 里补 QWEN25_EXTENDED_TOKENSVIBEVOICE_AUDIO_TOKENS(后者是 <|AUDIO|><|audio_bos|><|audio_eos|>),把 chat_template 改成能把 part['type'] == 'audio''audio_url' 渲染成 <|AUDIO|>,再把 model_max_length 设成 131072,最后生成 added_tokens.jsonspecial_tokens_map.json。加 --skip-tokenizer 可以跳过这一步——如果模型目录里已经有这几个文件的话。

第五步,起服务。看 --dp 的值分两条路,下一节说。

你能调的参数,和你调不了的那串

命令行这一层,main() 里定义了这些:

参数作用(照脚本的 help 文本)默认值
--model / -mHugging Face model IDmicrosoft/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 注册给 AutoTokenizerslow_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 torchtorch.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/modelsfor attempt in range(600)time.sleep(1),代码注释标的是”up to 10 minutes”;但循环走完仍失败时,for...else 分支只打印一行失败信息,没有退出。这时 nginx 已经在往这个后端转发了。这是代码写明的行为,请自行判断在你的场景里意味着什么。

请求体大小有上限。 生成的 nginx 配置里 client_max_body_size 200mproxy_read_timeoutproxy_send_timeout 都是 600s——这些是仓库当前代码里的取值,随版本可能变动。ASR 请求里音频是 base64 塞进 JSON 的(见 vllm_plugin/tests/test_api.py),这个上限就不只是理论值了。

没有鉴权。 我们在 start_server.py 里没有找到任何与 API key、鉴权或访问控制相关的参数或配置;脚本也没有对 nginx 前端做来源限制。要放到公网上,得自己在外面加一层——这属于通用运维做法,不是该项目的官方内容。

关停行为:DP 路径注册了 SIGTERMSIGINT 处理,会把信号转给 nginx 与所有 worker;主循环里任何一个 worker 或 nginx 退出,都会触发整体关停。

怎么确认起对了

三个动作,从浅到深:

  1. 看日志。文档给的是 docker logs -f vibevoice-vllm。脚本每一步都会打一段 ==== 包起来的标题,卡在哪一步一眼能看出来。
  2. /v1/models。这正是脚本自己用来判断后端就绪的地址;DP 模式下别只看前端端口,按 frontend_port + 100 + rank 的规则把每个后端端口都打一遍,否则少起一个副本你也看不出来。
  3. 发一次真实请求。仓库带了脚本,文档里的用法是 docker exec -it vibevoice-vllm python3 vllm_plugin/tests/test_api.py /app/audio.wav,另外还有一个带自动恢复逻辑的 test_api_auto_recover.pytest_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 生成内容时主动披露。

安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。