MOSS 转写与说话人分离

查看 / 编辑源文档 ↗

English

本文把第三方 OpenMOSS/MOSS-Transcribe-Diarize 模型接入 FunASR 部署生态。模型由 OpenMOSS 以 Apache-2.0 发布,不是 FunASR 自有模型。FunASR 为其公开的 Transformers、vLLM 与 SGLang Omni 接口提供适配器, 并保留 OpenMOSS 模型名称、许可证和上游 revision。

MOSS-Transcribe-Diarize 会联合生成转写、时间戳和 [S01] 等说话人标签,应用侧 不必再拼接外部 VAD、ASR 和 diarization 管线。这里描述的是部署形态,不表示模型 内部没有分块或分段。 这些是单次录音内的匿名说话人标签:[S01] 不能识别已知人物,不做已注册声纹 验证,也不保证与另一段录音中的 [S01] 是同一个人。

固定上游版本

本文按以下不可变版本验证:

  • 源码:OpenMOSS/MOSS-Transcribe-Diarize@cb765f2b0fe6f7a298aa2002e2281ae693d1f3c3
  • 模型:OpenMOSS-Team/MOSS-Transcribe-Diarize@e8681d68e7042738ffca8ac8212bc8fcb1131ab8
  • 许可证:固定上游源码和模型元数据中的 Apache-2.0
  • vLLM 发布版:v0.27.1@6e448d0ea9bf3d88d898b65449ca6dc2aec170ac
  • vLLM CUDA 12.9 x86_64 wheel SHA-256: bf0d52faa2a51e7a01c6856a7a8a2d1307fd0ff711415d34168a67ffac0fa47b

三者都要固定。模型依赖 trust_remote_code,生产服务不要执行浮动的模型 revision。

FunASR AutoModel 契约

本地后端应使用隔离的 Python 3.10+ 环境,并安装 Transformers 5.6 或更新版本。 MOSS 在一次生成中完成长音频转写与说话人分离,因此不要传 vad_modelspk_model。外部 VAD 会把长音频切开,并破坏跨分块的匿名标签一致性。

from funasr import AutoModel

model = AutoModel(
    model="OpenMOSS-Team/MOSS-Transcribe-Diarize",
    model_revision="e8681d68e7042738ffca8ac8212bc8fcb1131ab8",
    backend="hf",
    device="cuda:0",
    dtype="bf16",
    attn_implementation="sdpa",
    disable_update=True,
)

result = model.generate("audio.wav", max_new_tokens=5120)[0]
print(result["text"])
for segment in result["sentence_info"]:
    print(segment["start"], segment["end"], segment["spk"], segment["text"])

适配器在 raw_text 中保留原始标签生成,并返回 FunASR 通用字段:

  • text:移除 MOSS 控制标签后的可读转写;
  • timestamp:segment 级 [start_ms, end_ms]
  • sentence_info:包含 startendtextsentencespktimestamp
  • raw_text:用于审计的原始 [start][Sxx]text[end] 生成。

当解析器无法确认标签结构时,会保留 textraw_text,并返回空时间戳和分段, 不会静默编造说话人信息。

同一结果契约也可以包装已经启动的 vLLM 服务,并且不会下载本地权重:

from funasr import AutoModel

model = AutoModel(
    model="OpenMOSS-Team/MOSS-Transcribe-Diarize",
    backend="vllm",
    vllm_base_url="http://127.0.0.1:8898/v1",
    vllm_model="moss-transcribe-diarize",
    vllm_response_format="diarized_json",
    disable_update=True,
)
result = model.generate("audio.wav", max_completion_tokens=8192)[0]

vLLM 适配器发送官方文档中的 OpenAI-compatible multipart 请求,并把官方返回的 speaker segments 直接映射到 sentence_info。认证可通过 vllm_api_key 提供; 请放在 secret store,不要写入源码。

固定 vLLM revision 的生产结构化路径是 vllm_response_format="diarized_json"。为保持已有客户端兼容,默认值仍是 json, 此时 raw_text 保留原始标签生成;结构化模式下,raw_text 是 vLLM 返回的清理后 text,权威说话人信息位于 sentence_info

FunASR OpenAI 兼容服务

内置离线 HTTP 服务通过 /v1/audio/transcriptions 返回同一套标准化结果。 服务加载固定的 Transformers revision,不会外挂 VAD 或第二套说话人模型:

python -m pip install "transformers>=5.6,<6" fastapi uvicorn python-multipart
funasr-server --model moss-transcribe-diarize --device cuda:0 --port 8000

curl -fsS http://127.0.0.1:8000/v1/audio/transcriptions \
  -F file=@meeting.wav \
  -F model=moss-transcribe-diarize \
  -F response_format=verbose_json

响应包含 text、音频 duration,以及带 startendtext 和匿名 speaker 标签的 segments。请求不需要设置 spk=true;即使通用客户端传入 该字段,服务也只使用 MOSS 原生标签,不会启动第二套说话人流水线。

Open WebUI

Open WebUI 可将该服务配置为 OpenAI 兼容的语音转写提供方。在 Admin Panel > Settings > Audio 中选择 OpenAI STT engine,将 OpenAI API Base URL 设为 http://funasr:8000/v1(或可访问的主机 地址),模型选择 moss-transcribe-diarize,请求格式保持 multipart。Open WebUI 会把模型、可选语言和完整音频文件提交到 /v1/audio/transcriptions

MOSS 仍是离线长音频模型:这里提供的是 Open WebUI 文件转写,不是实时麦克风或 WebSocket 说话人分离路径。

GPU 容器可在仓库根目录使用 examples/openai_api/docker-compose.moss.yml 构建。Kubernetes 运维可构建同一 funasr-moss-api:local 镜像,再应用 examples/openai_api/kubernetes/funasr-moss-api.yaml;上线前应将本地镜像名 替换为内部镜像仓库的不可变 digest。

MOSS 是离线长音频模型,不接入 FunASR 的实时 WebSocket 服务;完整文件请使用 HTTP 接口。FunClip 通过同一套 sentence_info 契约生成带说话人信息的字幕与 片段。

内置服务路径已在一张 H100 80GB、Transformers 5.16.0.dev0、Torch 2.11.0+cu130 环境复现。固定 revision 的模型通过真实 HTTP 接口处理仓库自带 6.000 秒样例(ea03e1f473ad1618a03da3327a545369cb8f6f06cb0f4115535e5a866167d47e), 返回一个非空、时间单调且标记为 S01 的 segment,duration=6.0。这只是服务 契约 smoke test,不代表准确率、吞吐、并发或生产容量。

选择服务后端

路径 环境 响应契约 适用场景
vLLM CUDA 12(cu129)或 CUDA 13(cu130 response_format=diarized_json 返回 OpenAI-compatible 说话人 segments;response_format=json 保留原始 [start][Sxx]text[end] 文本 已运行 vLLM,或需要其调度能力
SGLang Omni 当前上游指南为 CUDA 13 response_format=verbose_json 返回解析后的 segments API 需要直接返回结构化说话人分段
Transformers PyTorch 进程 Python 对象和原始标签文本 评测、排障或自定义预处理
moss-transcribe.cpp / LocalAI C++17、ggml、GGUF;CPU 或 ggml GPU 后端 LocalAI 的 OpenAI-compatible 转写接口 不希望在推理机安装 Python/PyTorch,或需要量化 GGUF 和边缘部署

两个 HTTP 后端都使用 /v1/audio/transcriptions,但官方文档中的响应格式不能混为 一谈。固定的 vLLM revision 支持 diarized_json;承诺 segments 前仍要验证实际 部署的 exact server revision。

vLLM

创建隔离环境并安装已验证的 vLLM 发布版:

uv venv --python 3.12 .venv-moss
curl -fL \
  https://github.com/vllm-project/vllm/releases/download/v0.27.1/vllm-0.27.1%2Bcu129-cp38-abi3-manylinux_2_28_x86_64.whl \
  -o vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl
echo "bf0d52faa2a51e7a01c6856a7a8a2d1307fd0ff711415d34168a67ffac0fa47b  vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl" \
  | sha256sum -c -
uv pip install --python .venv-moss/bin/python --torch-backend=auto \
  "vllm[audio] @ file://$PWD/vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl"

必须安装 audio extra。只安装 plain vllm 时服务可以启动,但因为没有音频解码器, 请求会返回 HTTP 400(Invalid or unsupported audio file)。

使用不可变模型 revision 启动:

CUDA_VISIBLE_DEVICES=0 .venv-moss/bin/vllm serve \
  OpenMOSS-Team/MOSS-Transcribe-Diarize \
  --revision e8681d68e7042738ffca8ac8212bc8fcb1131ab8 \
  --served-model-name moss-transcribe-diarize \
  --trust-remote-code \
  --host 127.0.0.1 \
  --port 8898

提交音频并验证官方结构化说话人响应:

curl -fsS http://127.0.0.1:8898/v1/audio/transcriptions \
  -F file=@audio.wav \
  -F model=moss-transcribe-diarize \
  -F response_format=diarized_json \
  -F max_completion_tokens=8192 \
  -F temperature=0 \
  | tee moss-transcription.json

python - <<'PY'
import json

with open("moss-transcription.json", encoding="utf-8") as stream:
    payload = json.load(stream)
text = payload.get("text", "")
segments = payload.get("segments", [])
assert text.strip(), payload
assert segments, payload
assert all(
    isinstance(item.get("speaker"), str)
    and item.get("text")
    and item.get("start") <= item.get("end")
    for item in segments
), payload
print(text, sorted({item["speaker"] for item in segments}))
PY

如需审计模型原始紧凑标签生成,请把请求改为 response_format=json,并验证返回 text 中包含 [S01]

长音频需要按业务最长会议验证 max_completion_tokens。FunASR vLLM 适配器同时接受 OpenAI-compatible 名称 max_completion_tokens 和兼容名称 max_new_tokens;两者同时 出现时以前者为准。增大上限也会影响显存和尾延迟。

已复现的 vLLM 契约

结构化响应验证使用 vLLM 0.27.1、Torch 2.13.0+cu129 和一张 H100 80GB。 15.1685 秒双说话人样例 (43dccc068506439cb633b382b6b98185baa837363d08cc5f7152ca89b0fdc3c8) 返回两个 diarized_json segments,标签为 S01S02;同一请求通过 FunASR 适配器后,返回两个时间单调且说话人一致的 sentence_info

同一环境还验证了 FunASR #3539 的两段真实中文长音频:

  • 309.600 秒样例 (6561ee553c8f762aac4ebd65439d3414820761b547fa3a2edcea43b86a2abc02) 使用默认 5120 上限返回 158 个 segments,最后时间戳为 309.41 秒,7.459 秒完成;
  • 379.664 秒样例 (779899a3ce937dd7352b4db1ea53e3f6aa2cfef7109de0249082223c936f9372) 使用默认 5120 上限时生成在 354.98 秒处截断,diarized_json 因标签不完整返回 HTTP 400;设置 max_completion_tokens=8192 后返回 224 个 segments,最后时间戳 覆盖到 378.55 秒,10.516 秒完成。

这两段样例中,MOSS 修正了报告者指出的多处专有词和近音词,也正确保持了“转述基金会 声明”的语义连续性;但模型输出仍包含较短的独立分段,不能据此宣称字幕切分问题已解决。 这些数字只证明 exact input 的完整性边界,不是精度、吞吐或生产容量基准。

较早的 raw-response 验证使用 vLLM 0.23.1rc1.dev949+g68b4a1d58、Torch 2.11.0+cu129 和一张 H100 80GB,结果如下:

  • 仓库内 6.000 秒样例 (ea03e1f473ad1618a03da3327a545369cb8f6f06cb0f4115535e5a866167d47e) 返回 HTTP 200 和 [0.96][S01]... [5.94]
  • A + 0.8 秒静音 + B + 0.8 秒静音 + A 的拼接样例 (dbb32bcfed2e8226bedf64248a9f4a44685b293a4696d18fb4cfa701b04db912) 返回 HTTP 200、S01 -> S02 -> S01,时间戳覆盖到 19.08 秒。

这证明固定版本对这些输入满足 API 和说话人返回契约,不是 diarization 精度、重叠语音、 吞吐或生产容量结论。

旧 nightly commit 68b4a1d582818e67adc903bf1b8fc5a5447da2fa 早于 vLLM #48543:它支持 response_format=json,但 diarized_json 会返回 HTTP 400。应升级到已验证发布版, 不要静默重试一次成本较高的转写请求。

FunASR 适配器还分别使用 backend="hf"backend="vllm",按模型 revision e8681d68e7042738ffca8ac8212bc8fcb1131ab8 做了真实测试。15.1685 秒的双说话人 样例(43dccc068506439cb633b382b6b98185baa837363d08cc5f7152ca89b0fdc3c8)通过 统一 AutoModel 结果契约返回两个单调时间分段和 S01S02。测试完成后已停止 临时 vLLM worker;这只是契约冒烟测试,不是精度基准。

moss-transcribe.cpp 与 LocalAI

localai-org/moss-transcribe.cpp 是 LocalAI 团队维护的第三方 C++17/ggml 重写,许可证为 MIT;原始 OpenMOSS 模型 权重仍保持 Apache-2.0。它不是 FunASR AutoModel 后端,也不能与上面的 Python 适配器混用。

在需要 GGUF 量化、CPU 或 ggml CUDA/Metal/Vulkan/HIP 后端时,可以选择这条路径。 LocalAI master@a7cc5873ef5b7c909fc9ff7d349d51738ba9bb05 已包含 moss-transcribe-cpp 后端及 Hugging Face importer;后端固定 moss-transcribe.cpp@190a569c13b4b247450f2fb3b2a431244e84833e,importer 可识别 huggingface://mudler/moss-transcribe.cpp-gguf 并默认选择 Q5_K。部署前应按 LocalAI 文档验证所选 backend package、GGUF SHA-256、实际硬件后端及 /v1/audio/transcriptions 响应,不要把上游 README 的性能数字当作本机基准。

SGLang Omni

按固定上游 SGLang Omni 安装指南准备 CUDA 13 环境,然后下载不可变模型快照并从本地目录启动:

git clone https://github.com/sgl-project/sglang-omni.git
git -C sglang-omni checkout 3f819f9cdae3d4eeec22f73306c9067a1ec2542e

该源码 pin 已把 max_new_tokens 传入 transcription 生成请求。最初的 #914 merge 早于这个请求字段,因此虽然它的 H100 benchmark 仍可作为上游证据,却不能 支撑下方长音频命令。

hf download OpenMOSS-Team/MOSS-Transcribe-Diarize \
  --revision e8681d68e7042738ffca8ac8212bc8fcb1131ab8 \
  --local-dir .models/moss-transcribe-diarize

sgl-omni serve \
  --model-path .models/moss-transcribe-diarize \
  --port 8898 \
  --max-running-requests 16 \
  --cuda-graph-max-bs 16 \
  --mem-fraction-static 0.80

请求结构化分段:

curl -fsS http://127.0.0.1:8898/v1/audio/transcriptions \
  -F file=@audio.wav \
  -F model=OpenMOSS-Team/MOSS-Transcribe-Diarize \
  -F response_format=verbose_json

验证每个 segment 都有起止时间与非空文本。SGLang Omni 当前的 verbose_json 合同把说话人编号保留为 segments[].text[Sxx] 前缀, 并没有单独的 speaker 字段。接入字幕、会议纪要或分析系统前,应解析并校验此前缀。

FunASR 适配器会完成该校验,并把 SGLang 官方 segments 映射为与 HF、vLLM 一致的 sentence_info 合同:

from funasr import AutoModel

model = AutoModel(
    model="OpenMOSS-Team/MOSS-Transcribe-Diarize",
    backend="sglang",
    sglang_base_url="http://127.0.0.1:8898/v1",
    sglang_model="OpenMOSS-Team/MOSS-Transcribe-Diarize",
    max_new_tokens=65536,
    disable_update=True,
)
result = model.generate(input="audio.wav", max_new_tokens=65536)[0]
for segment in result["sentence_info"]:
    print(segment["start"], segment["end"], segment["spk"], segment["text"])

不要传入 vad_modelspk_model:MOSS 在一次生成中联合完成分段和匿名说话人 归属,外部分段会破坏长轮次中的说话人一致性。适配器把上游带标签原文保存在 raw_text,只从标准化 segment 中移除已经校验的 [Sxx] 前缀;如果 SGLang 没有返回此前缀,则明确失败,不会伪造说话人标签。

原生 runtime 已通过 SGLang Omni #914 合并。其单张 H100 Seed-TTS EN benchmark 完成 1088/1088 条请求且无请求失败。WER 是在移除 timestamp 与 speaker markup 后针对单说话人英文片段计算的,因此不评估 diarization 或 timestamp 准确率,也不是生产容量承诺。

上线前验证

不要只用短单人音频,应使用真实多人长音频:

  • 检查长轮次及说话人再次出现时的身份一致性;
  • 覆盖重叠语音、串音、音乐、噪声和长静音;
  • 验证时间戳单调递增并覆盖完整音频;
  • 记录 GPU、CUDA、Torch、后端 commit、模型 revision、音频时长、生成上限、 墙钟延迟和峰值显存;
  • 在 API 网关完成认证、TLS、请求限制和数据留存策略,模型 worker 只绑定内网。

向 FunASR 报告问题时,请明确这是 OpenMOSS 第三方路径,并附上后端与上游 exact revision。模型架构或权重问题应反馈给上游;部署中心的契约或文档问题可以在 FunASR 提交。