FunASR 官方 checkpoint 原生 vLLM 验证实战指南:Fun-ASR-Nano 固定 revision 的服务化复现与边界
【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR
本文以 FunASR 仓库内《Official native vLLM validation》记录为骨架,完整拆解 Fun-ASR-Nano 官方FunASRForConditionalGenerationcheckpoint 在 vLLM 0.27.1 原生路径下的验证流程:从固定 revision 快照下载、完整性校验、离线启动到 OpenAI 兼容转写请求与并发探针。读者按文中步骤即可在一台已有 NVIDIA H100 环境中复现一次“官方权重 + 原生 vLLM”的功能性 smoke 验证,并理解该记录在精度、容量、流式与干净安装上的严格边界。
一、验证记录定位:官方原生路径与社区历史路径的区分
本次验证的对象是官方模型仓库FunAudioLLM/Fun-ASR-Nano-2512-vllm的不可变 revisiona4362c943d48951f98ca2a62181cc028970270c5,验证日期为 2026-09-07(Asia/Shanghai,对应原始 HTTP Date 头为 2026-09-06 UTC)。
需要首先澄清三点关键区别:
- 这是模型 revision,不是根目录 FunASR Python 包版本。验证内容不构成对 FunASR SDK 或任何包发布的验收。
- 走的是 vLLM 原生
FunASRForConditionalGeneration路径,不经 FunASRAutoModel,也不是 FunASR vLLM 推理引擎指南 描述的 split-engine(拆分引擎)解码路径。 - 与 2026-08-13 的社区历史验证相互独立。记录在 vllm_native_funasr_validation.md 中的社区
allendou/Fun-ASR-Nano-2512-vllm@e718b36ebenchmark 保持原样;本次官方运行不是旧数据的重新命名,也不表示官方 checkpoint 更快。
关于上游状态:vLLM 侧 [PR #54944](merge commite473e9036f979d546830aece9855027049faf0ba,2026-09-05 合并)更新的是 supported-model 文档与测试 registry 对官方 checkpoint 的引用,并未修改推理实现。2026-09-07 审计时 vLLM main 分支已使用官方引用,但 v0.28.0 发布线仍引用社区产物。已合并不等于已发布;本次验证既不覆盖 main 也不覆盖 v0.28.0,更不会覆盖上游测试 registry 独立的 Transformers 版本约束。
二、验证范围与实测环境
8 个已记录的 HTTP 请求全部返回 200:健康检查、模型发现(/v1/models)、中/英/日三种语言的单请求转写、中文热词请求,以及两个并发请求。全部 23 个文件均直接从官方固定 revision 下载,按 Hub Git/LFS 摘要逐一验证,复制到持久备份后再次哈希校验;全程没有替换为社区缓存文件,也没有修改上游模型代码。
| 组件 | 实测值 |
|---|---|
| Python | 3.12.3 |
| vLLM | 0.27.1+cu129 |
| Torch | 2.13.0+cu129 |
| Transformers | 5.15.0 |
| CUDA runtime / NVIDIA 驱动 | 12.9 / 550.127.08 |
| GPU | 单张 NVIDIA H100 80GB HBM3 |
| 音频依赖 | av 18.1.0、soundfile 0.14.0、scipy 1.18.0、soxr 1.1.0、NumPy 2.3.5 |
| Hub 工具 / HTTP 客户端 | huggingface_hub 1.27.0 / requests 2.34.2 |
| 服务配置 | FP32、eager、GPU memory utilization 0.40、实际 max model length 40,960 |
重要前提:这是既有环境验证,不是全新安装验证。验证过程中没有安装、升级或重新解析任何依赖;上表是环境观测结果,不是经全新安装验证的依赖 lockfile。因此不能推断一个不加约束的
pip install vllm或浮动 Hub 模型 ID 加载就能复现本次结果。完整的环境包清单、native 源文件摘要等已随验证过程写入 可复现性元数据。
三、准备固定的本地快照
3.1 环境变量与版本核对
以下命令把真实验证流程中的私有绝对路径替换为可移植变量。VLLM_PYTHON应指向一个已经按上表准备好的 Python 环境;示例中的.venv路径只是占位符,不是创建虚拟环境的命令。建议使用全新的隔离验证目录,并保留下载 manifest;允许使用已有 Hub 认证,但不要输出凭据。
export VLLM_PYTHON="$PWD/.venv/bin/python" export VALIDATION_DIR="$PWD/.official-native-validation" export MODEL_DIR="$VALIDATION_DIR/official-model/a4362c943d48951f98ca2a62181cc028970270c5"在与实测完全相同的 native-import 上下文(即直接 importvllm.model_executor.models.funasr)中核对版本,确保后续启动使用的就是验证过的环境:
"$VLLM_PYTHON" - <<'PY' import importlib.metadata as metadata import torch import vllm.model_executor.models.funasr for name, expected in {"vllm": "0.27.1+cu129", "torch": "2.13.0+cu129", "transformers": "5.15.0"}.items(): assert metadata.version(name) == expected, (name, metadata.version(name)) assert torch.version.cuda == "12.9" and torch.cuda.is_available() PY3.2 按不可变 revision 下载并校验全部 23 个文件
下面的脚本先通过HfApi().model_info(..., files_metadata=True)校验仓库 SHA 与文件数(必须等于 23),再以snapshot_download拉取到隔离目录。脚本不会执行下载内容中的convert_from_official.py转换脚本,也不会修改快照——原生路径直接消费完整的 safetensors 布局,无需运行时转换:
HF_HUB_DISABLE_TELEMETRY=1 HF_XET_CACHE="$VALIDATION_DIR/isolated-xet-cache" "$VLLM_PYTHON" - <<'PY' import hashlib import json import os from pathlib import Path from huggingface_hub import HfApi, snapshot_download model_id = "FunAudioLLM/Fun-ASR-Nano-2512-vllm" revision = "a4362c943d48951f98ca2a62181cc028970270c5" root = Path(os.environ["MODEL_DIR"]) info = HfApi().model_info(model_id, revision=revision, files_metadata=True) assert info.sha == revision and len(info.siblings) == 23 snapshot_download(model_id, revision=revision, local_dir=str(root), cache_dir=str(Path(os.environ["VALIDATION_DIR"]) / "isolated-hf-cache"), max_workers=4) files = [] for item in info.siblings: content = (root / item.rfilename).read_bytes() digest = hashlib.sha256(content).hexdigest() assert len(content) == item.size, item.rfilename if item.lfs: assert digest == item.lfs.sha256, item.rfilename else: assert hashlib.sha1(f"blob {len(content)}\0".encode() + content).hexdigest() == item.blob_id files.append({"path": item.rfilename, "size": len(content), "sha256": digest}) assert json.loads((root / "config.json").read_text())["architectures"] == ["FunASRForConditionalGeneration"] (root.parent / "download-manifest.json").write_text(json.dumps(files, indent=2) + "\n") PY其中 LFS 文件按其声明 SHA256 校验,非 LFS 文件按 Git blob 规则(blob <size>\0+ 内容)的 SHA1 与blob_id比对;config.json中的architectures必须是["FunASRForConditionalGeneration"],这是 vLLM 原生模型注册表识别该架构的前提。全部校验通过后,23 个文件的path/size/sha256被持久化到download-manifest.json。
3.3 Checkpoint 与示例音频摘要
示例音频均为单声道 48 kHz MP3。ffprobe 测得容器时长分别为:中文 5.616 s、英文 7.176 s、日文 7.224 s。服务端 usage 计数将其向上取整为 6/8/8 秒——取整值不是文件实测时长,引用时长时应以 ffprobe 值为准。
| 文件 | 字节数 | SHA256 |
|---|---|---|
example/en.mp3 | 57441 | f10378336a4e584f3f63799e62f99d5add3c2a401b51d3abe7d3a3a82f255ada |
example/ja.mp3 | 57837 | 496dbc43b289e1d0d0cb916df9737450bca56acd8aaca046a7a2472363b1be53 |
example/zh.mp3 | 44973 | 0e64de19e4ff9a02e682955c9112f32d2317cfdbb5bc2f3504664044c993f195 |
model.safetensors | 1970899072 | 96dfbec48282dd24d3334369a01e9e909f321ee39a1b0003c528c5379f68c1a6 |
注意:社区历史记录中的
model.safetensorsSHA256 与官方完全相同(均为96dfbec...),这恰恰说明权重哈希相同不等于运行记录可互换——可复现性还取决于 revision 内全部 23 个文件、依赖版本与请求字段。完整的 23 文件清单、Git/LFS 摘要、精确 HTTP 字段、原始响应摘要与未取整耗时都收录在 docs/benchmark/vllm_official_native_20260907.json 中。公开内容已省略私有主机路径、GPU 标识与凭据;原始/v1/models响应中的本地 root 保留在私有证据里,其摘要对应原始字节,不是脱敏后的替代响应。
四、离线启动 OpenAI 兼容服务(loopback)
启动前先确认 GPU 0 空闲、loopback 端口 57185 未被占用;如需换端口,必须同步修改后面所有请求中的端口(本次记录只使用了 57185)。在已准备好的 shell 中以前台方式启动:
CUDA_VISIBLE_DEVICES=0 PYTHONDONTWRITEBYTECODE=1 \ HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \ HF_HUB_DISABLE_IMPLICIT_TOKEN=1 HF_HUB_DISABLE_TELEMETRY=1 VLLM_NO_USAGE_STATS=1 \ VLLM_CACHE_ROOT="$VALIDATION_DIR/runtime-cache" TRITON_CACHE_DIR="$VALIDATION_DIR/triton-cache" \ "$VLLM_PYTHON" -B -m vllm.entrypoints.openai.api_server \ --model "$MODEL_DIR" --served-model-name fun-asr-nano-official-a4362c94 \ --host 127.0.0.1 --port 57185 --dtype float32 \ --gpu-memory-utilization 0.40 --enforce-eager启动参数要点:
--model "$MODEL_DIR"指向第 3 节校验过的本地快照,配合HF_HUB_OFFLINE=1/TRANSFORMERS_OFFLINE=1全程离线;没有使用--trust-remote-code,也没有让服务从浮动模型 ID 在线加载。--dtype float32:本次验证的确定性优先配置。从 FunASR 源码 funasr/models/fun_asr_nano/inference_vllm.py 中的_resolve_vllm_dtype()可以看到,Fun-ASR-Nano 的 Qwen3 decoder 在 FP16 下数值不稳定、可能出现退化重复输出,因此 FunASR 拆分路径会把 fp16 自动提升为 bfloat16(音频组件保持 fp16);不支持 BF16 的硬件则应显式使用fp32。原生验证直接采用float32,规避了该风险。--gpu-memory-utilization 0.40 --enforce-eager:历史记录(vllm_native_funasr_validation.md)曾指出 0.20 不足以支撑完整 40,960 token 模型长度(仅剩 1.70 GiB KV cache,而单条 40,960-token 请求需要 8.75 GiB)。0.40 是本次环境的具体取值,不是通用建议,应按目标 GPU 与负载重新评估。
本次实际记录中,模型下载已完成的前提下,服务从启动到/health返回 200 耗时 84.123246 s。这只是一次观测,不是启动性能保证。
在第二个 shell 中把VALIDATION_DIR、MODEL_DIR设为相同绝对路径,然后等待就绪:
curl --max-time 15 -fsS http://127.0.0.1:57185/health curl --max-time 15 -fsS http://127.0.0.1:57185/v1/models/v1/models响应必须包含 IDfun-asr-nano-official-a4362c94,且其本地 root 等于展开后的MODEL_DIR;实测同时检查了这两项。
五、实际转写请求与观察结果
实测 harness 使用 Python requests,multipart 音频 MIME 为audio/mpeg,连接/读取超时为 5/45 s,显式传入language=zh/language=en/language=ja与response_format=json,没有覆盖 temperature 或生成长度。以下等价 curl 保留了实际请求的全部字段(curl 的耗时未单独测量):
curl --max-time 45 -fsS http://127.0.0.1:57185/v1/audio/transcriptions -F "file=@$MODEL_DIR/example/zh.mp3;type=audio/mpeg" -F model=fun-asr-nano-official-a4362c94 -F language=zh -F response_format=json curl --max-time 45 -fsS http://127.0.0.1:57185/v1/audio/transcriptions -F "file=@$MODEL_DIR/example/en.mp3;type=audio/mpeg" -F model=fun-asr-nano-official-a4362c94 -F language=en -F response_format=json curl --max-time 45 -fsS http://127.0.0.1:57185/v1/audio/transcriptions -F "file=@$MODEL_DIR/example/ja.mp3;type=audio/mpeg" -F model=fun-asr-nano-official-a4362c94 -F language=ja -F response_format=json curl --max-time 45 -fsS http://127.0.0.1:57185/v1/audio/transcriptions -F "file=@$MODEL_DIR/example/zh.mp3;type=audio/mpeg" -F model=fun-asr-nano-official-a4362c94 -F language=zh -F 'hotwords=开放时间,开放时间,开放时间' -F response_format=json实际返回文本:
- 中文基线:开饭时间早上九点至下午五点。
- 英文:The tribal chieftain called for the boy, and presented him with fifty pieces of gold.
- 日文:うちの中学は弁当制で、持っていけない場合は、五十円の学校販売のパンを買う。
- 中文加
hotwords=开放时间,开放时间,开放时间:开放时间早上九点至下午五点。
关于热词的解读要非常克制:基线误识别被保留(“开饭时间”未被纠正),而把同一个热词重复三次后样本输出发生了改变。这只能证明请求参数确实进入了生成 prompt(与社区记录中“单个热词不改变输出、重复三次改变歧义短语”的观察一致),不是通用热词策略,更不是准确率保证。热词强度是需要针对代表性音频单独验证的策略项,不是确定性纠错。
各请求的客户端 wall time(含本地 HTTP 与解码,不含模型下载与启动;每项只有一次观测,不是延迟分布):
| 已记录请求 | HTTP | 客户端 wall time(秒) |
|---|---|---|
| GET /health | 200 | 0.001023 |
| GET /v1/models | 200 | 0.002583 |
| POST zh(第一个转写请求) | 200 | 0.889547 |
| POST en | 200 | 0.386346 |
| POST ja | 200 | 0.473937 |
| POST zh + hotwords | 200 | 0.190610 |
| Concurrent en | 200 | 0.799900 |
| Concurrent ja | 200 | 0.904910 |
需要特别说明:第一个中文请求没有经过转写预热——它是健康检查和模型发现之后的第一个转写请求;后续请求复用同一引擎。因此 0.889 s 是冷路径上的单次观测,与后续 0.190 s 之间没有可比性。
六、并发功能探针
四个顺序转写请求完成后,验证用双 worker 的ThreadPoolExecutor同时发出相同的英文与日文 multipart 请求。两者均返回 200,且文本与顺序请求完全一致;包含 executor 建立与等待两项完成在内的总 wall time 为0.9112209342420101 s(约 0.911 s)。
这只是一个两请求的功能并发探针:它不构成吞吐量、生产容量或准确率研究,也不是与社区历史 1.123 s 并发探针的性能对比(两次运行的环境、模型 revision 与 warm 状态均不同)。
七、源码级佐证:原生验证背后的 FunASR 实现细节
围绕本次验证涉及的请求字段与启动配置,FunASR 仓库内部有对应的源码与测试可以印证底层原理:
dtype 映射策略:
_resolve_vllm_dtype()位于 funasr/models/fun_asr_nano/inference_vllm.py,将fp16提升为bfloat16(音频 frontend/adaptor 保持 FP16)、bf16→bfloat16、fp32→float32;配套测试 tests/test_fun_asr_nano_vllm_dtype.py 断言了“音频组件保持 float16、vLLM 端提升为 bfloat16”的行为,并验证bf16/fp32等合法值原样传递。这也是原生验证采用--dtype float32的动机背景。热词进入生成 prompt 的机制:原生路径下
hotwords作为请求字段被送入生成 prompt,与 FunASR 拆分路径中文本 prompt 预编码(system/hotwords/language → embedding 拼接)是同一类设计目标,但实现路径不同。repetition penalty 的约束:
vllm_utils.py(funasr/models/fun_asr_nano/vllm_utils.py)中的resolve_repetition_penalty()解释了为什么 prompt-embeds 模式下任何非 1.0 的重复惩罚都会因“无 prompt token ID 可惩罚”而触发 CUDA scatter 索引越界崩溃。原生验证明确“不发送 temperature 或生成长度覆盖”,正是为了避免引入这类解码参数不一致。参考实现中的NEUTRAL_REPETITION_PENALTY = 1.0应作为生产封装的安全默认值。与拆分引擎的对比:拆分路径(
prepare_vllm_model_dir(),同样位于 inference_vllm.py)需要从model.pt提取llm.*权重并生成Qwen3-0.6B-vllm/model.safetensors;而原生路径的官方 checkpoint 本身就是完整的model.safetensors+tokenizer.json+merges.txt布局,因此验证脚本刻意不执行快照中的convert_from_official.py,两者不能混用(详见 vllm_guide.md 中“两条模型路径不可互换”的说明)。
八、Harness 边界与清理
本次验证还记录了一次有意义的工程细节:首次 harness 尝试在创建服务进程之前就被包清单 guard 拦截——准备阶段 import 原生 vLLM 会让 setuptools 的 vendored 包进入sys.path,而 serve 阶段使用了不同的导入上下文比较包清单,导致误报差异;统一导入上下文后差异为空。原始失败与修正过程均被保留;早期失败状态字段曾误写为“服务已启动”,但进程证据与修正记录确认当时服务并未启动。整个过程中没有修改任何依赖或模型代码,之后首次实际启动服务即完成了 8 请求 smoke。
清理验收结果:有时限的 harness 终止了自己创建的进程组并等待服务及子进程退出;服务退出码为 0、loopback 端口关闭、GPU 无计算进程残留、包与 native 源文件摘要不变;独立检查再次确认全部 23 个原始文件及备份摘要、8 个原始响应、模型身份与清理状态。手工复现后请停止前台服务,确认其 worker 与端口均已退出,不要终止无关的 GPU 任务。
九、部署边界与安全约束
本次验证的覆盖范围需要严格界定:
- 只覆盖 request/response 形式的
/v1/audio/transcriptions,没有验证/v1/realtime流式会话(FunASRForConditionalGeneration当前也不在 vLLM 的 Realtime Transcription 表内,需要实时流式识别时应走 FunASR 流式 SDK 或流式 ASR 服务,见 vllm_guide.md)。 - 长音频、说话人分离、时间戳准确率、其他 GPU、持续负载与生产容量均未测试;不能由本次原生服务推断 FunASR SDK 或包发布已通过验证。
- 该原生 HTTP API 不会自动获得 FunASR WebSocket 服务中的 VAD、partial 预览、会话状态或 SPK 处理能力。
安全上,worker 应保持绑定127.0.0.1;对外提供 API 前,应由网关统一完成认证、TLS、限流以及音频大小/时长限制,并隔离上传文件、落实保留策略——网关本身不在本次 smoke 范围内。规划对外边界时可参考 部署矩阵(其中明确标注了该原生路径“仅限既有环境功能 smoke、非干净安装或容量验证”的适用范围)与 OpenAI API 服务安全边界。
十、延伸阅读与复现资产
- 本记录的中文对照版:docs/vllm_official_native_validation_zh.md
- 完整可复现元数据(23 文件哈希、环境包版本、精确 HTTP 字段、未取整耗时):docs/benchmark/vllm_official_native_20260907.json
- 社区 checkpoint 历史验证(
allendou/Fun-ASR-Nano-2512-vllm@e718b36e,2026-08-13):docs/vllm_native_funasr_validation.md - FunASR vLLM 推理引擎指南(split-engine 安装、离线/流式 SDK、离线/流式服务):docs/vllm_guide.md
- 相关实现源码:inference_vllm.py、vllm_utils.py;相关测试:test_fun_asr_nano_vllm_dtype.py
最后提醒:本文给出的所有命令都以“已有符合上表的环境 + 固定 revision 本地快照”为前提;任何版本浮动、GPU 更换或生产化改造(批处理、网关、流式)都应重新走一遍独立验收,而不是把本次单点观测当作通用规格。
【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考