docs: publish verified native Transformers adoption guides

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-09 11:31:36 +00:00
parent 57d219d55b
commit 3923534bf1
23 changed files with 728 additions and 8 deletions
+1 -1
View File
@@ -357,7 +357,7 @@ recordings with the same evaluation scope.
- **MOSS-Transcribe-Diarize** brings long-form ASR, timestamps, and anonymous speaker labels to FunASR services, Docker, Kubernetes, vLLM/SGLang workflows, and FunClip. [Deploy MOSS ->](./docs/moss_transcribe_diarize.md)
- **FunASR 1.4.15** adds tested NumPy 2 compatibility and fixes streaming KWS/VAD boundaries and checkpoint ranking. Install with `python -m pip install -U "funasr==1.4.15"`. [Release and verification scope ->](https://github.com/modelscope/FunASR/releases/tag/v1.4.15)
- **Production deployment** now includes faster, more resilient realtime serving and verified llama.cpp archives for ten Linux, macOS, and Windows targets. [GPU services ->](./docs/vllm_guide.md) · [CPU/edge packages ->](https://www.funasr.com/en/deploy/llama-cpp.html)
- **Native Transformers:** Fun-ASR-Nano [merged upstream](https://github.com/huggingface/transformers/pull/46180). Use the official `-hf` checkpoint and pinned source; stable 5.16.1 does not include it. [Installation and inference ->](./docs/transformers_native.md)
> See [GitHub Releases](https://github.com/modelscope/FunASR/releases) for the complete changelog and downloadable assets.
+1 -1
View File
@@ -105,7 +105,7 @@ CER/WER をそろえて比較してください。オフラインのスループ
- **MOSS-Transcribe-Diarize** を FunASR service、Docker、Kubernetes、vLLM/SGLang workflow、FunClip に統合し、長時間 ASR、timestamp、匿名 speaker label を一度に処理できます。[MOSS をデプロイ ->](./docs/moss_transcribe_diarize.md)
- **FunASR 1.4.15** はテスト済みの NumPy 2 互換性を追加し、ストリーミング KWS/VAD の境界処理と checkpoint の順位付けを修正します。`python -m pip install -U "funasr==1.4.15"`。[リリースと検証範囲 ->](https://github.com/modelscope/FunASR/releases/tag/v1.4.15)
- **Production deployment** に、より高速で安定した realtime serving と、Linux、macOS、Windows の 10 target 向け llama.cpp package を追加しました。[GPU service ->](./docs/vllm_guide.md) · [CPU / edge package ->](https://www.funasr.com/en/deploy/llama-cpp.html) · [v0.2.6 binaries ->](https://github.com/modelscope/FunASR/releases/tag/runtime-llamacpp-v0.2.6)
- **ネイティブ Transformers:** Fun-ASR-Nano が[上流にマージ](https://github.com/huggingface/transformers/pull/46180)されました。公式 `-hf` checkpoint と固定ソースを使用します。安定版 5.16.1 には未収録です。[導入ガイド(英語) ->](./docs/transformers_native.md)
> 完全な変更履歴と download asset は [GitHub Releases](https://github.com/modelscope/FunASR/releases) を参照してください。
+1 -1
View File
@@ -104,7 +104,7 @@ checkpoint/revision, 오디오 집합, 하드웨어, 배치, 워밍업, 측정
- **MOSS-Transcribe-Diarize**를 FunASR service, Docker, Kubernetes, vLLM/SGLang workflow, FunClip에 통합해 긴 오디오 ASR, timestamp, 익명 speaker label을 한 번에 처리합니다. [MOSS 배포 ->](./docs/moss_transcribe_diarize.md)
- **FunASR 1.4.15**는 테스트를 거친 NumPy 2 호환성을 추가하고 스트리밍 KWS/VAD 경계 처리와 checkpoint 순위 산정을 수정합니다. `python -m pip install -U "funasr==1.4.15"`. [릴리스 및 검증 범위 ->](https://github.com/modelscope/FunASR/releases/tag/v1.4.15)
- **Production deployment**에 더 빠르고 안정적인 realtime serving과 Linux, macOS, Windows 10개 target용 llama.cpp package를 추가했습니다. [GPU service ->](./docs/vllm_guide.md) · [CPU / edge package ->](https://www.funasr.com/en/deploy/llama-cpp.html) · [v0.2.6 binaries ->](https://github.com/modelscope/FunASR/releases/tag/runtime-llamacpp-v0.2.6)
- **네이티브 Transformers:** Fun-ASR-Nano가 [업스트림에 병합](https://github.com/huggingface/transformers/pull/46180)되었습니다. 공식 `-hf` checkpoint와 고정 소스를 사용하세요. 안정 버전 5.16.1에는 아직 포함되지 않았습니다. [설치 가이드(영문) ->](./docs/transformers_native.md)
> 전체 변경 기록과 download asset은 [GitHub Releases](https://github.com/modelscope/FunASR/releases)에서 확인할 수 있습니다.
+1 -1
View File
@@ -150,7 +150,7 @@ checkpoint/revision、音频集、硬件、批量大小、预热、计时范围
- **MOSS-Transcribe-Diarize** 已接入 FunASR 服务、Docker、Kubernetes、vLLM/SGLang 工作流和 FunClip,一次完成长音频转写、时间戳与匿名说话人标注。[部署 MOSS ->](./docs/moss_transcribe_diarize_zh.md)
- **FunASR 1.4.15** 新增经过测试的 NumPy 2 兼容支持,修复流式 KWS/VAD 边界处理和 checkpoint 排序。升级命令:`python -m pip install -U "funasr==1.4.15"`。[发布说明与验证范围 ->](https://github.com/modelscope/FunASR/releases/tag/v1.4.15)
- **工业部署** 新增更快、更稳定的实时服务,以及覆盖 Linux、macOS、Windows 十种目标的 llama.cpp 预编译包。[GPU 服务 ->](./docs/vllm_guide_zh.md) · [CPU/端侧包 ->](https://www.funasr.com/deploy/llama-cpp.html)
- **原生 Transformers:** Fun-ASR-Nano [已合入上游](https://github.com/huggingface/transformers/pull/46180)。使用官方 `-hf` checkpoint 与固定源码;稳定版 5.16.1 尚未包含。[安装与推理 ->](./docs/transformers_native_zh.md)
> 完整改动记录和可下载资产请查看 [GitHub Releases](https://github.com/modelscope/FunASR/releases)。
+1
View File
@@ -12,6 +12,7 @@
| Install and check the Python environment | [Installation](installation/installation.md) / [中文](installation/installation_zh.md) |
| Choose a model, language, or timestamp capability | [Model selection](model_selection.md) / [中文](model_selection_zh.md) |
| Run the first transcription | [Quickstart](tutorial/README.md) / [中文](tutorial/README_zh.md) / [CLI](cli.md) |
| Use native Hugging Face Transformers APIs | [Fun-ASR-Nano native guide](transformers_native.md) / [中文](transformers_native_zh.md) |
| Configure AutoModel, generate, and streaming cache | [Python SDK](python_api.md) / [中文](python_api_zh.md) |
| Move from Whisper or a cloud API | [Migration](migration_from_whisper.md) / [中文](migration_from_whisper_zh.md) |
+1
View File
@@ -6,6 +6,7 @@ Use this page to choose the shortest deployment path for a product, demo, benchm
| Path | Best for | Start here | Operational notes |
|---|---|---|---|
| Native Transformers | Python applications using Hugging Face processors and generation | [Native Fun-ASR-Nano guide](./transformers_native.md) | Official `-hf` checkpoint and pinned source; stable 5.16.1 lacks support at the 2026-09-09 check. Python inference, not an HTTP or realtime server. |
| Colab notebook | Browser smoke tests, first evaluation, shareable demos | [Colab quickstart](../examples/colab/) | No local setup; first run downloads model files, GPU runtime is faster. |
| Python API | Notebooks, offline jobs, first model evaluation | [README quick start](../README.md#quick-start) | Lowest ceremony; caller owns batching, retries, and files. |
| OpenAI-compatible API | Private speech API, agents, Dify/LangChain/AutoGen-style clients | [OpenAI API example](../examples/openai_api/) | Easiest integration for apps that already support OpenAI audio APIs. |
+1
View File
@@ -6,6 +6,7 @@
| 路径 | 适合场景 | 从这里开始 | 运维提示 |
|---|---|---|---|
| 原生 Transformers | 已使用 Hugging Face 处理器与生成接口的 Python 应用 | [原生 Fun-ASR-Nano 指南](./transformers_native_zh.md) | 官方 `-hf` checkpoint 与固定源码;2026-09-09 核验的稳定版 5.16.1 尚未包含。是 Python 推理入口,不是 HTTP 或实时服务器。 |
| Colab Notebook | 浏览器 smoke test、首次评估、可分享 demo | [Colab 快速体验](../examples/colab/README_zh.md) | 不需要本地环境;首次运行会下载模型,GPU runtime 更快。 |
| Python API | Notebook、离线任务、首次模型评测 | [README 快速开始](../README_zh.md#快速开始) | 最简单;调用方自己负责批处理、重试和文件管理。 |
| OpenAI 兼容 API | 私有语音 API、Agent、Dify/LangChain/AutoGen 风格客户端 | [OpenAI API 示例](../examples/openai_api/README_zh.md) | 已支持 OpenAI audio API 的应用最容易接入。 |
+208
View File
@@ -0,0 +1,208 @@
# Fun-ASR-Nano with native Transformers
[简体中文](./transformers_native_zh.md) | English
Use this path when your Python application already works with Hugging Face
processors and generation APIs. It runs Fun-ASR-Nano through native Transformers
classes, without the FunASR toolkit or checkpoint-provided remote Python code.
For a managed HTTP service, start with the [deployment matrix](./deployment_matrix.md);
this guide does not add an OpenAI endpoint, request queue or realtime server.
## What was merged, and what was released?
On **2026-09-09**, [Transformers PR #46180](https://github.com/huggingface/transformers/pull/46180)
was merged as `fc501343edfccdc840eb8594a6cafa8185c2de53`.
At verification time that day, the latest stable **5.16.1 did not contain this
model**: both its release tag and actual wheel were inspected. A merge is not
a stable release. The source build identifies itself as `5.17.0.dev0`; that is
not a promised release version or date. Pin the tested source below, or check
that a later released package really contains `fun_asr_nano` before migrating.
## Choose the checkpoint and the interface together
| Path | Checkpoint | Entry point |
| --- | --- | --- |
| FunASR toolkit | `FunAudioLLM/Fun-ASR-Nano-2512` | `funasr.AutoModel`; separate documented split-engine path |
| Native Transformers | `FunAudioLLM/Fun-ASR-Nano-2512-hf` | `AutoProcessor` + `AutoModelForSpeechSeq2Seq` |
| Native vLLM | `FunAudioLLM/Fun-ASR-Nano-2512-vllm` | [Official native vLLM guide](./vllm_official_native_validation.md) |
| Native C++ | Converted GGUF files for the chosen runtime | [llama.cpp guide](../runtime/llama.cpp/README.md) |
These are different artifacts and API contracts. Renaming a model ID or passing
`backend="vllm"` does not convert weights. Native Transformers support does not
make the `-hf` checkpoint a vLLM, GGUF or FunASR HTTP-server checkpoint.
The official [native HF checkpoint](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf/tree/d93b302ee7fd505e1b3576120fc142fc6f7820e1)
is public and ungated at revision `d93b302ee7fd505e1b3576120fc142fc6f7820e1`.
Its configuration maps to `FunAsrNanoForConditionalGeneration`. Its feature
extractor is embedded in `processor_config.json`; a separate
`preprocessor_config.json` is not required for this snapshot.
## Independent CPU environment
Use a new directory for the virtual environment; do not upgrade an existing
FunASR or vLLM service environment in place. The following is **Linux x86-64,
Python 3.12, CPU-only**, not a CUDA or macOS installation recipe.
```bash
python3.12 -m venv .venv-funasr-native
. .venv-funasr-native/bin/activate
python -m pip install --index-url https://download.pytorch.org/whl/cpu \
'torch==2.10.0+cpu' 'torchaudio==2.10.0+cpu'
python -m pip install 'numpy==1.26.4' 'librosa==0.11.0' 'soundfile==0.13.1' \
'huggingface-hub==1.30.0' 'tokenizers==0.23.2' \
'https://github.com/huggingface/transformers/archive/fc501343edfccdc840eb8594a6cafa8185c2de53.zip'
python -m pip check
```
The verification environment installed a wheel built from the exact source
commit above. Matching **torch and torchaudio** are required for this native
feature extractor: it calls `torchaudio.compliance.kaldi.fbank`. The toolkit's
optional audio dependencies do not imply that torchaudio is optional here.
Older prepared environments failed dependency gates; do not disable those gates
or mix incompatible Hub/tokenizers versions to get an import to succeed.
This is a tested environment, not a complete lock of every transitive dependency;
record `python -m pip freeze`, platform details and model revision in your own run.
## Check preprocessing without loading weights
This downloads the public configuration, tokenizer and chat template, **not
model weights**. One second of synthetic silence checks the entry point; it is
not a speech-recognition quality test.
<!-- native-example: processor -->
```python
import numpy as np
from transformers import AutoProcessor
model_id = "FunAudioLLM/Fun-ASR-Nano-2512-hf"
revision = "d93b302ee7fd505e1b3576120fc142fc6f7820e1"
processor = AutoProcessor.from_pretrained(
model_id, revision=revision, trust_remote_code=False, token=False
)
inputs = processor.apply_transcription_request(
audio=np.zeros(16000, dtype=np.float32),
language="en",
processor_kwargs={
"return_tensors": "pt",
"audio_kwargs": {"sampling_rate": 16000},
},
)
print({name: tuple(value.shape) for name, value in inputs.items()})
```
In the verified environment the text tensors have shape `(1, 42)`, audio features
`(1, 17, 560)`, and feature mask `(1, 17)`. There are 17 audio placeholder tokens
and 17 valid feature frames. These shapes depend on input length and template;
do not hard-code them for real recordings.
## Transcribe one recording
Prepare a non-empty **mono 16 kHz WAV** named `audio.wav` in your working directory.
The code deliberately rejects a different sample rate or channel layout rather
than silently assigning 16 kHz to arbitrary samples. Resample/downmix with an
explicit policy before this step and preserve the original recording for review.
The first model load downloads the weights unless the pinned snapshot is already
cached. It needs substantially more memory and time than preprocessing. This
example uses CPU float32 explicitly; GPU placement, mixed precision, attention
backends and service concurrency need separate validation.
<!-- native-example: transcribe -->
```python
import soundfile as sf
import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor
model_id = "FunAudioLLM/Fun-ASR-Nano-2512-hf"
revision = "d93b302ee7fd505e1b3576120fc142fc6f7820e1"
audio, sample_rate = sf.read("audio.wav", dtype="float32")
if sample_rate != 16000 or audio.ndim != 1 or audio.size == 0:
raise ValueError("Use a non-empty mono 16 kHz WAV file")
processor = AutoProcessor.from_pretrained(
model_id, revision=revision, trust_remote_code=False, token=False
)
model = AutoModelForSpeechSeq2Seq.from_pretrained(
model_id, revision=revision, trust_remote_code=False, token=False,
dtype=torch.float32,
).to("cpu").eval()
inputs = processor.apply_transcription_request(
audio=audio, language="zh",
processor_kwargs={
"return_tensors": "pt",
"audio_kwargs": {"sampling_rate": sample_rate},
},
)
with torch.inference_mode():
generated = model.generate(**inputs, max_new_tokens=128, do_sample=False)
new_tokens = generated[:, inputs.input_ids.shape[1]:]
print(processor.batch_decode(new_tokens, skip_special_tokens=True)[0])
```
`generated` includes the input prompt. Slice off `inputs.input_ids.shape[1]`
before decoding so prompt framing is not mistaken for recognized speech.
`max_new_tokens=128` bounds this short-file example; hitting the limit can
truncate output. A longer limit is not proof of complete coverage.
Use `language="en"` or `language="ja"` for the corresponding target language;
language selection is a transcription instruction, not a promise of translation.
## Context, hotwords and batches
`apply_transcription_request` accepts `prompt` for context and `keywords` for
hotwords. These are native processor arguments, not the toolkit's `hotword`
parameter or a vLLM HTTP field. The source API accepts one language for the batch
or a language list matching its length; per-recording prompt and nested keyword
lists must also match the batch. Keep the decoded results in input order.
Do not infer quality gains from a template accepting a keyword. Evaluate names,
numbers and unusual terms against authorized, representative recordings.
Batch padding, memory use and generated-text correctness must be tested together
before turning a single-file example into a production batch worker.
## Evidence and limits
The documented single-recording recipe was also executed on the official Chinese
sample, followed by an English single recording, a Chinese keyword request and a
Chinese/English batch. All returned non-empty text and EOS before the 128-token
limit. These are functional checks on two short public samples, not a CER/WER or
capacity evaluation. CPU: Intel Xeon Platinum 8480+, float32, four Torch threads;
matching torch/torchaudio 2.10.0+cpu and librosa 0.11.0. Model weights were loaded
from a previously downloaded official cache whose SHA/size were rechecked.
Samples came from the original model's
[fixed example directory](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512/tree/272c57b82523ada6fd87095e955f8e29100979ab/example),
not from an invented -hf example directory. The mono 48 kHz Chinese MP3 was
explicitly resampled with librosa's default soxr_hq and written as float32 16 kHz
WAV before running the unmodified recipe. Its raw text was
`开饭时间早上九点至下午五点。`; adding the candidate keyword `开放时间` did not force that
spelling into the output. A keyword is a hint, not an enforced vocabulary.
Synthetic processor tests exercised language aliases, context/keyword lists and
two-length batches. The empty-list case still raises an upstream `IndexError`
before its intended validation; reject empty batches in your application.
The guide's explicit non-empty waveform check avoids that entry path. Successful
cases do not erase this observed limitation. Initial resampling also exposed the
old librosa 0.10.1 dependency on removed `pkg_resources`; the final environment
uses 0.11.0 and was retested.
The 2026-09-09 isolated CPU verification passed package dependency checks,
native configuration/class resolution and synthetic preprocessing with the
pinned official revision. Installed configuration, modeling, processing and
feature-extraction files were byte-identical to the merged upstream source.
A preprocessing pass is **not an accuracy or capacity result**. It does not
establish word timestamps, diarization, realtime behavior, GPU compatibility,
or performance equivalence with vLLM.
For anonymous speakers and segment timestamps, see the third-party OpenMOSS
[MOSS guide](./moss_transcribe_diarize.md); for other choices see the
[Model Zoo](../model_zoo/readme.md). Do not infer timestamp or identity support
from a model returning text. Preserve input hashes, versions, raw responses and
an authorized human review when assessing an application. Never attach private
recordings or credentials to a public issue.
## Sources and next steps
- [Merged implementation and usage documentation](https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/docs/source/en/model_doc/fun_asr_nano.md).
- [Native processor](https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/src/transformers/models/fun_asr_nano/processing_fun_asr_nano.py) and [audio feature extractor](https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/src/transformers/models/fun_asr_nano/feature_extraction_fun_asr_nano.py).
- [Why the checkpoint suffix matters](https://www.funasr.com/en/blog/fun-asr-nano-transformers.html), an application-oriented walkthrough.
- [Fun-ASR model project](https://github.com/QwenAudio/Fun-ASR) and [FunASR toolkit](https://github.com/modelscope/FunASR).
+181
View File
@@ -0,0 +1,181 @@
# 用原生 Transformers 接入 Fun-ASR-Nano
简体中文 | [English](./transformers_native.md)
如果 Python 应用已经使用 Hugging Face 的处理器和生成接口,可以选择这条路径。
它通过 Transformers 原生类运行 Fun-ASR-Nano,不依赖 FunASR 工具包或 checkpoint
中的远程 Python 代码。需要 HTTP 服务时先看[部署矩阵](./deployment_matrix_zh.md):
本指南不会自动提供 OpenAI 接口、请求队列或实时服务器。
## 合入主线,不等于已经发布
**2026-09-09**,[Transformers PR #46180](https://github.com/huggingface/transformers/pull/46180)
合入提交 `fc501343edfccdc840eb8594a6cafa8185c2de53`。
当天核验时,最新稳定版 **5.16.1 不包含这个模型**,其 tag 和实际 wheel 均已检查。
源码构建显示 `5.17.0.dev0`,不代表下一稳定版的版本或发布日期承诺。
请固定下方经过验证的源码提交;改用后续稳定版前,实际确认安装包包含 `fun_asr_nano`。
## 模型格式和接口一起选
| 路径 | Checkpoint | 入口 |
| --- | --- | --- |
| FunASR 工具包 | `FunAudioLLM/Fun-ASR-Nano-2512` | `funasr.AutoModel`;拆分引擎另见对应文档 |
| 原生 Transformers | `FunAudioLLM/Fun-ASR-Nano-2512-hf` | `AutoProcessor` + `AutoModelForSpeechSeq2Seq` |
| 原生 vLLM | `FunAudioLLM/Fun-ASR-Nano-2512-vllm` | [官方原生 vLLM 指南](./vllm_official_native_validation_zh.md) |
| 原生 C++ | 对应运行时转换后的 GGUF 文件 | [llama.cpp 指南](../runtime/llama.cpp/README.md) |
这些产物及 API 契约不同。改模型 ID 的名字或传 `backend="vllm"` 不会转换权重;
原生 Transformers 支持不意味着 `-hf` 可以交给 vLLM、GGUF 运行时或 FunASR HTTP 服务。
官方[原生 HF checkpoint](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf/tree/d93b302ee7fd505e1b3576120fc142fc6f7820e1)
在 revision `d93b302ee7fd505e1b3576120fc142fc6f7820e1` 公开且无需申请访问。
配置映射到 `FunAsrNanoForConditionalGeneration`,特征提取器嵌在
`processor_config.json` 中。此快照不需要另一个 `preprocessor_config.json`。
## 准备独立的 CPU 环境
使用新目录创建虚拟环境,不要直接升级正在运行的 FunASR/vLLM 服务环境。
下面对应 **Linux x86-64、Python 3.12、仅 CPU**,不是 CUDA 或 macOS 安装方案。
```bash
python3.12 -m venv .venv-funasr-native
. .venv-funasr-native/bin/activate
python -m pip install --index-url https://download.pytorch.org/whl/cpu \
'torch==2.10.0+cpu' 'torchaudio==2.10.0+cpu'
python -m pip install 'numpy==1.26.4' 'librosa==0.11.0' 'soundfile==0.13.1' \
'huggingface-hub==1.30.0' 'tokenizers==0.23.2' \
'https://github.com/huggingface/transformers/archive/fc501343edfccdc840eb8594a6cafa8185c2de53.zip'
python -m pip check
```
验证环境安装的是从上述精确提交构建的 wheel。原生特征提取器调用
`torchaudio.compliance.kaldi.fbank`,因此需要匹配的 **torch 和 torchaudio**。
FunASR 工具包的可选音频依赖策略不能套用到这里。旧环境曾被过旧的 Hub/tokenizers
依赖挡住;不要关闭依赖检查或混搭版本来强行导入。
这是经过验证的环境,不是所有传递依赖的完整锁文件;自己的运行还应记录
`python -m pip freeze`、平台信息和模型 revision。
## 先验证预处理,不加载权重
这一步仅下载公开的配置、tokenizer 和聊天模板,**不下载模型权重**。
一秒合成静音用来检查入口,不是识别质量测试。
<!-- native-example: processor -->
```python
import numpy as np
from transformers import AutoProcessor
model_id = "FunAudioLLM/Fun-ASR-Nano-2512-hf"
revision = "d93b302ee7fd505e1b3576120fc142fc6f7820e1"
processor = AutoProcessor.from_pretrained(
model_id, revision=revision, trust_remote_code=False, token=False
)
inputs = processor.apply_transcription_request(
audio=np.zeros(16000, dtype=np.float32),
language="en",
processor_kwargs={
"return_tensors": "pt",
"audio_kwargs": {"sampling_rate": 16000},
},
)
print({name: tuple(value.shape) for name, value in inputs.items()})
```
验证环境中文字张量形状是 `(1, 42)`,音频特征是 `(1, 17, 560)`,
特征 mask 是 `(1, 17)`;17 个音频占位 token 与 17 帧有效特征对应。
这些形状随输入时长和模板变化,不应在真实应用里写死。
## 转写一份录音
在工作目录准备非空、**单声道 16 kHz WAV** 文件 `audio.wav`。
代码明确拒绝其他采样率或声道布局,而不是把任意采样直接标成 16 kHz。
重采样和混音请使用明确的策略,在这一步之前完成,并保留原录音便于回听。
首次加载模型会下载权重,已有固定 revision 缓存时复用缓存。
其内存和耗时明显高于仅预处理。下面显式使用 CPU float32;
GPU 放置、混合精度、attention 后端和服务并发需要分别验证。
<!-- native-example: transcribe -->
```python
import soundfile as sf
import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor
model_id = "FunAudioLLM/Fun-ASR-Nano-2512-hf"
revision = "d93b302ee7fd505e1b3576120fc142fc6f7820e1"
audio, sample_rate = sf.read("audio.wav", dtype="float32")
if sample_rate != 16000 or audio.ndim != 1 or audio.size == 0:
raise ValueError("Use a non-empty mono 16 kHz WAV file")
processor = AutoProcessor.from_pretrained(
model_id, revision=revision, trust_remote_code=False, token=False
)
model = AutoModelForSpeechSeq2Seq.from_pretrained(
model_id, revision=revision, trust_remote_code=False, token=False,
dtype=torch.float32,
).to("cpu").eval()
inputs = processor.apply_transcription_request(
audio=audio, language="zh",
processor_kwargs={
"return_tensors": "pt",
"audio_kwargs": {"sampling_rate": sample_rate},
},
)
with torch.inference_mode():
generated = model.generate(**inputs, max_new_tokens=128, do_sample=False)
new_tokens = generated[:, inputs.input_ids.shape[1]:]
print(processor.batch_decode(new_tokens, skip_special_tokens=True)[0])
```
`generated` 包含输入 prompt。解码前去掉 `inputs.input_ids.shape[1]`,
避免把提示模板当作识别结果。`max_new_tokens=128` 限定这份短文件示例的生成上限;
触顶可能截断输出,调大也不保证整段覆盖。对应语言可改为 `language="en"` 或
`language="ja"`;这里是转写语言指令,不是翻译能力承诺。
## 上下文、热词和批处理
`apply_transcription_request` 使用 `prompt` 传上下文、`keywords` 传热词,
不是工具包的 `hotword` 参数或 vLLM HTTP 字段。源码 API 接受全批统一语言,
或与批大小一致的语言列表;逐录音的 prompt 和嵌套关键词列表也必须与批大小匹配。
结果应保持输入顺序。
模板接收了关键词,不等于识别更准确。应在获授权且有代表性的录音中检查专名、
数字和生僻词。把单文件示例变成生产批处理之前,应一起测试 padding、内存和生成文本,
而不是只检查请求张量的形状。
## 验证范围与边界
本指南的单录音代码还实际运行了官方中文样本,随后测试了英文单录音、中文关键词请求及
中英混合批量。各请求均返回非空文本,并在 128 token 上限之前生成 EOS。
这只是两份公开短样本的功能检查,不是 CER/WER 或容量评测。
CPU 为 Intel Xeon Platinum 8480+,float32、Torch 四线程;torch/torchaudio
2.10.0+cpu、librosa 0.11.0。模型来自先前下载的官方缓存,本轮重新检查了 SHA 和文件大小。
样本来自原始模型的[固定 example 目录](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512/tree/272c57b82523ada6fd87095e955f8e29100979ab/example),
不是假定存在的 -hf 示例目录。中文源文件是单声道 48 kHz MP3,先用 librosa 默认的
soxr_hq 明确重采样,再写成 float32 16 kHz WAV,运行未改写的指南代码。
原始文本为 `开饭时间早上九点至下午五点。`;传入候选关键词 `开放时间` 也没有强制产生该词形。
热词是提示,不是强制词表约束。
合成处理器测试覆盖了语言别名、上下文/关键词列表和不同长度的批量输入。
空列表仍会在上游预期校验前抛 `IndexError`,应用应提前拒绝空批量;指南已显式拒绝空波形,
不经过这一路径。成功用例不能抹去这个限制。最初的重采样也暴露出 librosa 0.10.1
依赖已移除的 `pkg_resources`;最终环境使用 0.11.0 并重新验证。
2026-09-09 的独立 CPU 验证通过依赖检查、原生配置/类解析和固定官方 revision 的合成预处理。
已安装的配置、模型、处理器、特征提取器源码逐字节匹配上游合并提交。
预处理通过**不代表准确率或容量结论**,也不证明字级时间戳、说话人分离、实时行为、
GPU 兼容性或与 vLLM 的性能一致。
需要匿名说话人与段级时间戳时,参考第三方 OpenMOSS 的
[MOSS 指南](./moss_transcribe_diarize_zh.md);其他路径见
[Model Zoo](../model_zoo/readme_zh.md)。不能从“返回了文本”推导时间戳或身份识别能力。
应用验收应保留输入散列、固定版本、原始输出和获授权的人工回听记录;
不要把私有录音或凭据附到公开 issue 中。
## 来源与下一步
- [合并后的实现与官方用法](https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/docs/source/en/model_doc/fun_asr_nano.md)。
- [原生处理器](https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/src/transformers/models/fun_asr_nano/processing_fun_asr_nano.py)与[音频特征提取器](https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/src/transformers/models/fun_asr_nano/feature_extraction_fun_asr_nano.py)。
- [为什么 checkpoint 后缀重要](https://www.funasr.com/blog/fun-asr-nano-transformers.html):面向应用的专题解读。
- [Fun-ASR 模型项目](https://github.com/QwenAudio/Fun-ASR)与[FunASR 工具包](https://github.com/modelscope/FunASR)。
+10
View File
@@ -21,6 +21,16 @@ the [deployment matrix](../docs/deployment_matrix.md) for serving options.
## Model Usage
### Native Transformers checkpoint
Use [FunAudioLLM/Fun-ASR-Nano-2512-hf](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf)
with `AutoProcessor` and `AutoModelForSpeechSeq2Seq`, not the original toolkit,
native vLLM or GGUF loader. [Installation and inference](../docs/transformers_native.md)
pin the merged source and official revision. As checked on 2026-09-09, stable
Transformers 5.16.1 does not contain this native model. Matching torchaudio is required.
### FunASR toolkit
Start with the [installation guide](../docs/installation/installation.md) and
[Python tutorial](../docs/tutorial/README.md). Use an explicit hub and record the
resolved checkpoint/revision, FunASR version, device and inference options.
+10
View File
@@ -20,6 +20,16 @@
## 模型用法
### 原生 Transformers checkpoint
[FunAudioLLM/Fun-ASR-Nano-2512-hf](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf)
使用 `AutoProcessor` 和 `AutoModelForSpeechSeq2Seq`,不是原始工具包、原生 vLLM 或
GGUF 的加载路径。[安装与推理指南](../docs/transformers_native_zh.md)固定了上游提交和官方
模型 revision。2026-09-09 核验的稳定版 Transformers 5.16.1 尚未包含该原生模型,
并且这条路径需要匹配的 torchaudio。
### FunASR 工具包
先完成[安装](../docs/installation/installation_zh.md)和
[Python 教程](../docs/tutorial/README_zh.md)。显式选择 hub,并记录实际下载的
checkpoint/revision、FunASR 版本、设备和推理选项。
@@ -3,6 +3,9 @@
"source": "https://www.funasr.com/ public static corpus",
"captured": "2026-07-26",
"files": {
"blog/fun-asr-nano-transformers.html": "e9834f5d62f3d172abcbb9d9157e19bc0fed8ff0f68bc4e7030dd7b1e65fb0ff",
"en/blog/fun-asr-nano-transformers.html": "67fe99f396c537915828978c7b2a755360ed8bcb24b3eb082a743f41744e102f",
"img/fun-asr-nano-native-waveform.png": "930517464ffb47bc7f9051dbdcb5422e1b3daa5c6abaa455a8bf072359ae0f0b",
"7ec404429c825fe9a9a030731bbca986.txt": "e0e4c945481cb03f52426d8420c3407ea18cd3b46cb2482590bd50e5aab117d2",
"blog/cantonese-speech-recognition.html": "f4a674f2b8f15963a386d586bb79d62f9f75c20d704ca2eafba21a800f52d4df",
"blog/chinese-speech-recognition.html": "a78eeb4e5ea38ce95ad05ebc54eceb0185d8dddd2286769b5ba1b7dd00e8b615",
@@ -24,7 +27,7 @@
"blog/funclip-v2-1-0-video-clipping-release.html": "88f6c44e5332d1746c4db0fc97d755ef82f12e46d8f1c1c0ad9351152ff9dfc1",
"blog/funclip-v2-2-0-moss-speaker-clipping.html": "0d50747d3992301fb3062ad2cdf903bd427bec3db1f1e4fab1f06de207928bc9",
"blog/generate-subtitles-srt-vtt-from-audio-video.html": "f1133235673d441cc654310581f59a319f182314ab7c0b0824af87da9f4a0591",
"blog/index.html": "f966d5ad5c713a4180df05ff84216fd5ad65f831c57968b9feb7e3f4712bc136",
"blog/index.html": "bd04951ed11b340a6786c7b38a25beace6554f7269274c2d25d4228ebaaf2a0e",
"blog/japanese-speech-recognition.html": "399f5ce84e68ac00854bdf70b52b1cde6795efca7c800fd6492035a37a7c1b68",
"blog/lightweight-speech-recognition-cpu.html": "d6270066222ed5baef0df108e6a4155f4a87bb5482169f23f218794d371f3281",
"blog/meeting-transcript-acceptance.html": "3e35d1f283c7005977ad8cbd079e38c51e14b51f04f50cedb5636e2d4baf38bd",
@@ -66,7 +69,7 @@
"en/blog/funclip-v2-1-0-video-clipping-release.html": "229baf59adf2c3290541d9b3c8a6243992406ba84b712714e9d14599205cb1d4",
"en/blog/funclip-v2-2-0-moss-speaker-clipping.html": "644159267f0fa4e7aab9d0eda98f68cd2b6365a9985038b639c626a5ea5ea7e8",
"en/blog/generate-subtitles-srt-vtt-from-audio-video.html": "1530d4b9e94820a0b60d801e8d1c19b7aeb031b2f2b76c092657392e47706c6d",
"en/blog/index.html": "363b5b77b4702ed1f5d7f62fa2eeadb7e4bfa1a56b86ca9c22527d6c7666bec7",
"en/blog/index.html": "fe18072fb67da6b9cc274fa26b8dabc5854c426a9a38210bb2317219e8123900",
"en/blog/japanese-speech-recognition.html": "c476adcc2be1ed7c19e2345cc91b8ee6a0e04efd794b9b6476dd5d9b3a2c8b04",
"en/blog/lightweight-speech-recognition-cpu.html": "0e02c0e0853b12613d32c250f593c05c2052f18231207b63ee01d6e6600a86f7",
"en/blog/meeting-transcript-acceptance.html": "3183682d2cae0ce18f68e3bd33e22b9effb4de699f6a4d821938cc03dd6835bf",
@@ -85,7 +88,7 @@
"en/ecosystem.html": "f02ee516440dfc3757165df801fbf93b403b2ef1fdbee1a2dee26cd2a3ef9fa3",
"en/index.html": "d86effc3bf4d218eabd465cbbe026f62c98dd50110ddd58251d9d271a64882a2",
"en/llama-cpp.html": "dbfbd125ebbf07f7d923c91b122818f8b09484ce54c1e80eba66a89d22dfb5b4",
"en/models.html": "87ef70a357dcd430703c290eff4319d2b4ffefcde2f854d91bc250bbd29b9fa2",
"en/models.html": "5802ec4902b145d2ea398e172a07680cbef61188a3dc611f4d313db1dc12b1d8",
"en/quickstart.html": "7453cd7d582079eb4082373bce6e5de26492458b73c41617bbb1bd4248bda6ac",
"en/vs-whisper.html": "4833b93195957210436f791a9d8ebc726f0af93b809d740a5cee1e53de0ac390",
"favicon.ico": "e5edd9f12088fa920b848485c62c72a093f27bfba7b6a46b4e7b63c34eb5126a",
@@ -110,7 +113,7 @@
"lang-redirect.js": "e1189080a8e81eb76cf39e0538d0378b6631f5aa87e4679c0fd4fd34d1f6c197",
"llama-cpp.html": "892b5c05e3375bcc7cd0af343b629f069d8ec4f372e6470cd830cdf2181a11d5",
"logo.png": "ac0ff97f2b0203c3fad4ac5e90231d93e36d98cc8e90d383483de150b0dd0728",
"models.html": "01209193cd48363f00789061fbfad0a2b387582e2507ae5e875687773ef52fbc",
"models.html": "1e16794d389275a1ecca05bcc218a9c89311582aebcec7e62ec4b1ecd4379d40",
"quickstart.html": "a543bf07c2cfc9a69ef3831f15c9e9d3e571a826a73cf7e4bdc8e9eb7b910254",
"robots.txt": "1305b0f680b3e9f73b01301dc474423230dffab85731a379a694f30c73c07f2d",
"sitemap.xml": "1fc88be1d337d7fac6acf0dd61e6b3416f6808f5d6e8fc0006fb9b15cba6e039",
@@ -15,6 +15,7 @@
{"id": "reference", "zh": "协议与参考", "en": "Protocols & reference"}
],
"pages": [
{"slug": "native-transformers", "group": "models", "zh": "Transformers 原生 Fun-ASR-Nano", "en": "Native Transformers Fun-ASR-Nano", "source_zh": "docs/transformers_native_zh.md", "source_en": "docs/transformers_native.md"},
{"slug": "installation", "group": "start", "zh": "安装与环境验证", "en": "Installation & environment checks", "source_zh": "docs/installation/installation_zh.md", "source_en": "docs/installation/installation.md"},
{"slug": "quickstart", "group": "start", "zh": "第一次转写", "en": "Your first transcription", "source_zh": "docs/tutorial/README_zh.md", "source_en": "docs/tutorial/README.md"},
{"slug": "python-api", "group": "start", "zh": "Python SDK: AutoModel", "en": "Python SDK: AutoModel", "source_zh": "docs/python_api_zh.md", "source_en": "docs/python_api.md"},
+1
View File
@@ -14,6 +14,7 @@ from documentation import load_catalogue, render_source
ORIGIN = 'https://www.funasr.com'
ALIASES = {
'native-transformers': 'native-transformers.html',
'quickstart': 'tutorial.html',
'training': 'training.html',
'model-registration': 'model-registration.html',
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="zh"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Fun-ASR-Nano + Transformers:先选对权重,再接入应用 | FunASR</title>
<meta name="description" content="原生 Transformers 支持已合并:分清 -hf、原始权重、vLLM 和 GGUF,理解音频预处理、生成结果与应用验收,附固定版本接入指南。">
<meta property="og:title" content="Fun-ASR-Nano + Transformers:先选对权重,再接入应用"><meta property="og:description" content="原生 Transformers 支持已合并:分清 -hf、原始权重、vLLM 和 GGUF,理解音频预处理、生成结果与应用验收,附固定版本接入指南。"><meta property="og:type" content="article"><meta property="og:url" content="https://www.funasr.com/blog/fun-asr-nano-transformers.html"><meta property="og:image" content="https://www.funasr.com/img/fun-asr-nano-native-waveform.png">
<link rel="canonical" href="https://www.funasr.com/blog/fun-asr-nano-transformers.html"><link rel="alternate" hreflang="en" href="https://www.funasr.com/en/blog/fun-asr-nano-transformers.html">
<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Fun-ASR-Nano + Transformers:先选对权重,再接入应用","datePublished":"2026-09-09","dateModified":"2026-09-09","author":{"@type":"Organization","name":"FunASR"},"mainEntityOfPage":{"@type":"WebPage","@id":"https://www.funasr.com/blog/fun-asr-nano-transformers.html"}}</script>
<style>body{margin:0;font-family:system-ui,sans-serif;color:#202827;line-height:1.8;background:#fff}*{box-sizing:border-box}a{color:#126a59}a:hover{text-decoration:underline}.container{max-width:920px;margin:auto;padding:0 24px}.nav{border-bottom:1px solid #dce3df;padding:14px 0}.nav .container{display:flex;align-items:center;gap:20px}.nav-links{margin-left:auto;display:flex;gap:16px}.nav-logo{font-weight:700}article{padding:48px 0 64px}h1{font-size:2rem;line-height:1.3;letter-spacing:0}h2{font-size:1.4rem;margin-top:36px;letter-spacing:0}.lead{font-size:1.1rem}.meta,figcaption{color:#5e6864;font-size:.875rem}figure{margin:24px 0}img{display:block;max-width:100%;height:auto}pre{overflow:auto;padding:18px;background:#f3f6f5;border:1px solid #dce3df;border-radius:6px;line-height:1.6}code{font-family:ui-monospace,monospace}p code,li code{overflow-wrap:anywhere}.table-wrap{overflow-x:auto}table{width:100%;min-width:640px;border-collapse:collapse}th,td{padding:10px;text-align:left;vertical-align:top;border-bottom:1px solid #dce3df}th{background:#f3f6f5}footer{border-top:1px solid #dce3df;padding:24px}@media(max-width:700px){h1{font-size:1.6rem}.nav-links{display:none}.container{padding:0 20px}table{font-size:.9rem}th,td{padding:7px}}</style>
</head><body>
<nav class="nav"><div class="container"><a class="nav-logo" href="/">FunASR</a><div class="nav-links"><a href="/deploy/">部署</a><a href="/blog/">技术博客</a></div><a href="/en/blog/fun-asr-nano-transformers.html">EN</a><a href="/go/github">GitHub</a></div></nav>
<article><div class="container">
<h1>Fun-ASR-Nano + Transformers:先选对权重,再接入应用</h1>
<p class="meta">2026-09-09 · 生态技术与应用 · 阅读约 7 分钟</p>
<p class="lead">在已有 Hugging Face 应用里接入语音识别,最容易踩的坑往往不是模型大小,而是把“同一模型家族”当成“同一种权重和接口”。Fun-ASR-Nano 已进入 Transformers 主线。这篇文章从 checkpoint、处理器到生成结果,解释如何把这次集成接到自己的应用中。</p>
<section data-native-section="release"><h2>先确认安装包,不只看合并状态</h2>
<p><a href="https://github.com/huggingface/transformers/pull/46180">PR #46180</a> 于 2026-09-09 合并。当天核验的稳定版 <code>5.16.1</code> 还没有原生 <code>fun_asr_nano</code> 文件;源码包显示的 <code>5.17.0.dev0</code> 也不是下一稳定版发布承诺。请从<a href="/docs/native-transformers.html">固定版本接入指南</a>安装明确的源码提交,不要只执行一次无版本约束的升级就假定已经兼容。</p>
<p>这种区别影响排障方向:未知模型类型可能是安装包未包含实现,不一定是 checkpoint 损坏;模型能下载也不代表所选运行时认识它。</p></section>
<section data-native-section="formats"><h2>同一模型家族,四条不同路径</h2>
<div class="table-wrap"><table><thead><tr><th>应用已有的接口</th><th>选择什么</th><th>不要混淆</th></tr></thead><tbody>
<tr><td>FunASR AutoModel</td><td><code>Fun-ASR-Nano-2512</code></td><td>原始工具包路径;拆分引擎看对应文档。</td></tr>
<tr><td>Transformers processor / generate</td><td><code>Fun-ASR-Nano-2512-hf</code></td><td>本篇的原生接口,不是 vLLM 的转换包。</td></tr>
<tr><td>原生 vLLM HTTP 服务</td><td><code>Fun-ASR-Nano-2512-vllm</code></td><td>使用独立运行时、模型格式及服务参数。</td></tr>
<tr><td>C++ / GGML 运行时</td><td>与运行时匹配的 GGUF</td><td>不是把 Transformers 目录改后缀。</td></tr>
</tbody></table></div>
<p>这几个官方模型仓库位于 <code>FunAudioLLM</code> 下。原生 Transformers 路径加载 <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf">官方 -hf checkpoint</a>,模型和处理器代码来自固定的 Transformers 安装,不需要执行 checkpoint 的远程 Python 代码。它减少了一种接入依赖,并不自动提供鉴权、队列或服务监控。</p>
<p>如果目标是多客户端并发请求,直接查看<a href="/deploy/vllm.html">原生 vLLM 部署页</a>;如果目标是离线 C++ 运行,查看<a href="/deploy/llama-cpp.html">llama.cpp 部署页</a>。原生 Python 接口是一个可组合入口,不是所有服务的替换品。</p></section>
<section data-native-section="mechanics"><h2>从声音到文本,中间实际经过什么?</h2>
<ol><li><strong>音频样本。</strong>采样率与声道是数据含义的一部分。48 kHz 的数组不能只改参数标成 16 kHz;那会改变模型看到的时间尺度。指南先要求明确的单声道 16 kHz WAV。</li>
<li><strong>音频特征。</strong>原生特征提取器用 torchaudio 的 Kaldi fbank,再做 LFR 堆叠与降采样。这里仍需要匹配版本的 torchaudio;不能套用 FunASR 工具包的可选依赖结论。</li>
<li><strong>转写请求。</strong><code>apply_transcription_request</code> 把音频、语言、上下文和关键词整理成模型的聊天模板,并对齐音频占位 token 与特征。</li>
<li><strong>生成和解码。</strong><code>AutoModelForSpeechSeq2Seq</code> 返回 token。去掉输入 prompt 的长度后再解码,才能避免把模板混进转写文本。</li></ol>
<figure><img src="/img/fun-asr-nano-native-waveform.png" width="1800" height="510" alt="用于功能检查的官方中文样本波形,横轴为秒,纵轴为振幅"><figcaption>官方中文样本原始波形:约 5.62 秒、单声道 48 kHz。运行指南前应明确重采样为 16 kHz。波形图不是准确率或性能结果。</figcaption></figure>
<p>先用合成静音验证配置和张量形状,可以把依赖、模板错误与权重加载问题分开。但预处理没有调用生成模型,不能据此宣布“识别通过”。</p></section>
<section data-native-section="context"><h2>应用上下文应该放在哪里?</h2>
<p>业务词表通过原生 <code>keywords</code> 参数传入,相关背景通过 <code>prompt</code> 传入。它们不是工具包的 <code>hotword</code> 参数,也不是 HTTP 层字段。给两份录音分别传上下文时,语言、prompt 和嵌套关键词列表应与录音数量一致。输出顺序应回到同一份输入清单。</p>
<p>例如客服录音里的人名、产品名,可以作为候选关键词;但“模板中出现了关键词”和“模型在噪声中识别对了这个词”是两项测试。不要把拼写纠正或摘要改写后的文本冒充原始 ASR 输出。</p>
<p>当前固定快照默认左侧 padding;批量示例仍可显式设置 padding 以便阅读。生成返回值要按整批输入张量的宽度去除 prompt,不是各条 attention mask 有效长度。空录音和空列表应在应用边界提前拒绝。</p></section>
<section data-native-section="observations"><h2>一次真实短录音检查说明了什么?</h2>
<p>在固定源码、官方 -hf revision 和 CPU float32 环境中,我们运行了中文单录音、英文单录音、中英混合批量,以及一个带关键词的中文请求。四个请求均返回文本并在上限前生成 EOS;批量结果顺序正确。这是两份公开短音频的功能检查,不是准确率排行榜。</p>
<div class="table-wrap"><table><thead><tr><th>同一份中文音频</th><th>原始输出</th></tr></thead><tbody>
<tr><td>不传关键词</td><td>开饭时间早上九点至下午五点。</td></tr>
<tr><td>候选关键词:开放时间</td><td>开饭时间:早上九点至下午五点。</td></tr>
</tbody></table></div>
<p>这个例子没有显示关键词强制生效,不能包装成“加热词就会识别对”。API 参数被接收、输出发生变化和业务词写对,是不同结论。保留原文,再结合人工参考检查;不要用提示词替代验收。环境、样本来源与重采样口径见<a href="/docs/native-transformers.html">验证记录</a>。</p></section>
<section data-native-section="acceptance"><h2>接入成功之后,还要验收什么?</h2>
<ul><li><strong>范围。</strong>这条 -hf 路径用于生成式转写,不包含原生 CTC 分支;不能把结果文本当作字级时间戳或说话人分离输出。</li>
<li><strong>完整性。</strong>短文件示例的生成上限是 128 个新 token。触顶可能截断;增加上限不会自动证明长录音没有漏句。</li>
<li><strong>资源。</strong>独立 CPU 示例不是 CUDA、服务并发或 vLLM 性能测试。分别记录下载、模型加载和生成,不把首次下载算成模型推理,也不把一次短录音当作容量规划。</li>
<li><strong>质量与隐私。</strong>在授权录音中回听关键数字、否定词和末尾,保留固定版本与原始结果;公开反馈不要携带客户录音、token 或身份信息。</li></ul>
<p>需要段级时间戳和匿名说话人时,可比较 OpenMOSS 的第三方 <a href="/docs/moss-transcribe-diarize.html">MOSS 统一转写与分离路径</a>。匿名标签不是人物身份,模型选型仍应从应用真正需要的输出开始。</p></section>
<section data-native-section="next"><h2>从哪里开始</h2>
<p>先打开<a href="/docs/native-transformers.html">原生 Transformers 安装与推理指南</a>,完成独立 CPU 环境、无权重预处理和短录音生成,再决定是否需要批量或服务化。模型页和 <a href="/docs/model-zoo.html">Model Zoo</a>保留格式入口;<a href="/blog/meeting-transcript-acceptance.html">会议录音验收清单</a>帮助把结果交付到应用。</p>
<p>源码依据:<a href="https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/docs/source/en/model_doc/fun_asr_nano.md">固定提交的官方模型文档</a>、<a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf/blob/d93b302ee7fd505e1b3576120fc142fc6f7820e1/README.md">固定 revision 的模型卡</a>。模型项目与贡献入口见 <a href="https://github.com/QwenAudio/Fun-ASR">Fun-ASR</a> 和 <a href="https://github.com/modelscope/FunASR">FunASR</a>。</p></section>
</div></article><footer><div class="container">FunASR · 应用实践与技术解读</div></footer>
</body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Fun-ASR-Nano + Transformers: choose the checkpoint before the API | FunASR</title>
<meta name="description" content="Native Transformers support has merged. Separate -hf, original, vLLM and GGUF artifacts, understand preprocessing and generation, and follow a pinned application guide.">
<meta property="og:title" content="Fun-ASR-Nano + Transformers: choose the checkpoint before the API"><meta property="og:description" content="Native Transformers support has merged. Separate -hf, original, vLLM and GGUF artifacts, understand preprocessing and generation, and follow a pinned application guide."><meta property="og:type" content="article"><meta property="og:url" content="https://www.funasr.com/en/blog/fun-asr-nano-transformers.html"><meta property="og:image" content="https://www.funasr.com/img/fun-asr-nano-native-waveform.png">
<link rel="canonical" href="https://www.funasr.com/en/blog/fun-asr-nano-transformers.html"><link rel="alternate" hreflang="zh" href="https://www.funasr.com/blog/fun-asr-nano-transformers.html">
<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Fun-ASR-Nano + Transformers: choose the checkpoint before the API","datePublished":"2026-09-09","dateModified":"2026-09-09","author":{"@type":"Organization","name":"FunASR"},"mainEntityOfPage":{"@type":"WebPage","@id":"https://www.funasr.com/en/blog/fun-asr-nano-transformers.html"}}</script>
<style>body{margin:0;font-family:system-ui,sans-serif;color:#202827;line-height:1.8;background:#fff}*{box-sizing:border-box}a{color:#126a59}a:hover{text-decoration:underline}.container{max-width:920px;margin:auto;padding:0 24px}.nav{border-bottom:1px solid #dce3df;padding:14px 0}.nav .container{display:flex;align-items:center;gap:20px}.nav-links{margin-left:auto;display:flex;gap:16px}.nav-logo{font-weight:700}article{padding:48px 0 64px}h1{font-size:2rem;line-height:1.3;letter-spacing:0}h2{font-size:1.4rem;margin-top:36px;letter-spacing:0}.lead{font-size:1.1rem}.meta,figcaption{color:#5e6864;font-size:.875rem}figure{margin:24px 0}img{display:block;max-width:100%;height:auto}pre{overflow:auto;padding:18px;background:#f3f6f5;border:1px solid #dce3df;border-radius:6px;line-height:1.6}code{font-family:ui-monospace,monospace}p code,li code{overflow-wrap:anywhere}.table-wrap{overflow-x:auto}table{width:100%;min-width:640px;border-collapse:collapse}th,td{padding:10px;text-align:left;vertical-align:top;border-bottom:1px solid #dce3df}th{background:#f3f6f5}footer{border-top:1px solid #dce3df;padding:24px}@media(max-width:700px){h1{font-size:1.6rem}.nav-links{display:none}.container{padding:0 20px}table{font-size:.9rem}th,td{padding:7px}}</style>
</head><body>
<nav class="nav"><div class="container"><a class="nav-logo" href="/en/">FunASR</a><div class="nav-links"><a href="/en/deploy/">Deploy</a><a href="/en/blog/">Blog</a></div><a href="/blog/fun-asr-nano-transformers.html">中文</a><a href="/go/github">GitHub</a></div></nav>
<article><div class="container">
<h1>Fun-ASR-Nano + Transformers: choose the checkpoint before the API</h1>
<p class="meta">2026-09-09 · Ecosystem engineering · 7-minute read</p>
<p class="lead">When adding speech recognition to a Hugging Face application, a common failure is treating one model family as one interchangeable checkpoint format. Fun-ASR-Nano is now on the Transformers main branch. Here is how to connect the new native path to an application, from model selection to decoding the generated result.</p>
<section data-native-section="release"><h2>Check the package, not just the merge</h2>
<p><a href="https://github.com/huggingface/transformers/pull/46180">PR #46180</a> merged on 2026-09-09. The stable <code>5.16.1</code> package checked that day did not contain native <code>fun_asr_nano</code> files. The source build's <code>5.17.0.dev0</code> label is not a promise of the next stable version or release date. Use the explicit source commit in the <a href="/en/docs/native-transformers.html">pinned setup guide</a>; an unversioned upgrade alone is not evidence of compatibility.</p>
<p>This distinction changes troubleshooting: an unknown model type may mean that the installed package lacks the implementation, not that the checkpoint is corrupt. A successful model download does not prove that the selected runtime can load it.</p></section>
<section data-native-section="formats"><h2>One family, four different paths</h2>
<div class="table-wrap"><table><thead><tr><th>Your application interface</th><th>Artifact</th><th>Important distinction</th></tr></thead><tbody>
<tr><td>FunASR AutoModel</td><td><code>Fun-ASR-Nano-2512</code></td><td>Original toolkit path; split-engine has its own guide.</td></tr>
<tr><td>Transformers processor / generate</td><td><code>Fun-ASR-Nano-2512-hf</code></td><td>The native path described here, not a vLLM conversion.</td></tr>
<tr><td>Native vLLM HTTP service</td><td><code>Fun-ASR-Nano-2512-vllm</code></td><td>Separate runtime, checkpoint format and service options.</td></tr>
<tr><td>C++ / GGML runtime</td><td>Matching converted GGUF files</td><td>Not a renamed Transformers directory.</td></tr>
</tbody></table></div>
<p>The official model repositories are under <code>FunAudioLLM</code>. Native Transformers loads the <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf">official -hf checkpoint</a>, while the model and processor code come from the pinned Transformers installation. It does not need to execute checkpoint-provided remote Python code. That removes one integration dependency; it does not supply authentication, queues or service monitoring.</p>
<p>For concurrent clients, inspect the <a href="/en/deploy/vllm.html">native vLLM deployment</a>. For offline C++, inspect <a href="/en/deploy/llama-cpp.html">llama.cpp deployment</a>. A composable Python interface is not a replacement for every serving stack.</p></section>
<section data-native-section="mechanics"><h2>What happens between audio and text?</h2>
<ol><li><strong>Audio samples.</strong> Sample rate and channel layout are part of the data's meaning. Labeling a 48 kHz array as 16 kHz changes the time scale seen by the model. The guide requires an explicitly prepared mono 16 kHz WAV.</li>
<li><strong>Audio features.</strong> The native extractor computes Kaldi fbank through torchaudio, then applies low-frame-rate stacking and subsampling. Matching torchaudio is required here, regardless of the toolkit's optional-dependency policy.</li>
<li><strong>The transcription request.</strong> <code>apply_transcription_request</code> prepares the checkpoint's chat template from audio, language, context and keywords, aligning audio placeholder tokens with features.</li>
<li><strong>Generation and decoding.</strong> <code>AutoModelForSpeechSeq2Seq</code> returns tokens. Remove the input prompt width before decoding to avoid mixing template framing into recognized speech.</li></ol>
<figure><img src="/img/fun-asr-nano-native-waveform.png" width="1800" height="510" alt="Official Chinese functional-test sample waveform, time in seconds and amplitude"><figcaption>The official Chinese sample: approximately 5.62 seconds, mono, originally 48 kHz. Explicitly resample it to 16 kHz before the guide's input step. A waveform is not an accuracy or performance result.</figcaption></figure>
<p>A synthetic-silence preprocessing check separates dependency and template failures from weight loading. It does not run the generation model, so it cannot establish successful speech recognition.</p></section>
<section data-native-section="context"><h2>Where does application context belong?</h2>
<p>Pass a vocabulary through native <code>keywords</code> and relevant background through <code>prompt</code>. These are not the toolkit's <code>hotword</code> argument or HTTP fields. For separate recording contexts, language, prompt and nested keyword lists must match the number of audio inputs. Map results back to the same input manifest in order.</p>
<p>Customer-service recordings may benefit from candidate product or person names. But “the template includes this keyword” and “the recognizer recovered it in noise” are different tests. Do not present a spelling correction or summary rewrite as raw ASR output.</p>
<p>The pinned checkpoint defaults to left padding; explicit padding still makes a batch example easier to inspect. Remove the full input tensor width from generated sequences, not each row's valid attention-mask length. Reject empty recordings and empty batches at your application boundary.</p></section>
<section data-native-section="observations"><h2>What did the short-recording check establish?</h2>
<p>With pinned source, official -hf revision and CPU float32, we ran a Chinese recording, an English recording, a mixed Chinese/English batch and a Chinese keyword request. All four requests returned text and EOS before the limit, with the batch in input order. These are functional checks on two public short recordings, not an accuracy ranking.</p>
<div class="table-wrap"><table><thead><tr><th>The same Chinese audio</th><th>Raw output</th></tr></thead><tbody>
<tr><td>No keywords</td><td>开饭时间早上九点至下午五点。</td></tr>
<tr><td>Candidate keyword: 开放时间</td><td>开饭时间:早上九点至下午五点。</td></tr>
</tbody></table></div>
<p>The hint did not force the requested spelling. Accepting a parameter, changing the output and recovering the intended business term are different conclusions. Keep the raw result and check it against a human reference; prompts do not replace acceptance testing. See the <a href="/en/docs/native-transformers.html">verification record</a> for environment, sample provenance and resampling details.</p></section>
<section data-native-section="acceptance"><h2>What remains after the interface works?</h2>
<ul><li><strong>Output scope.</strong> This -hf path is generation-based transcription and omits the native CTC branch. Text is not a word-timestamp or speaker-diarization result.</li>
<li><strong>Completeness.</strong> The short-file example allows 128 new tokens. Reaching the limit can truncate output; raising it does not prove complete long-recording coverage.</li>
<li><strong>Resources.</strong> A CPU example is not a CUDA, concurrent-serving or vLLM performance test. Measure download, model loading and generation separately. One short file is not capacity planning.</li>
<li><strong>Quality and privacy.</strong> Review important numbers, negation and the recording's end in authorized audio. Preserve fixed versions and raw output. Never attach customer recordings, tokens or identity data to a public report.</li></ul>
<p>For segment timestamps and anonymous speakers, compare the third-party OpenMOSS <a href="/en/docs/moss-transcribe-diarize.html">MOSS unified transcription and diarization path</a>. Anonymous labels are not known-person identities. Choose the output the application actually needs.</p></section>
<section data-native-section="next"><h2>Start with a reproducible recording</h2>
<p>Follow the <a href="/en/docs/native-transformers.html">native Transformers installation and inference guide</a>: an independent CPU environment, preprocessing without weights, then a short recording. Only then extend to batching or serving. The <a href="/en/docs/model-zoo.html">Model Zoo</a> preserves format-specific entry points; the <a href="/en/blog/meeting-transcript-acceptance.html">meeting acceptance checklist</a> covers downstream delivery.</p>
<p>Primary sources: <a href="https://github.com/huggingface/transformers/blob/fc501343edfccdc840eb8594a6cafa8185c2de53/docs/source/en/model_doc/fun_asr_nano.md">official model documentation at the merged commit</a> and the <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf/blob/d93b302ee7fd505e1b3576120fc142fc6f7820e1/README.md">pinned model card</a>. Explore the model and contribute through <a href="https://github.com/QwenAudio/Fun-ASR">Fun-ASR</a> and <a href="https://github.com/modelscope/FunASR">FunASR</a>.</p></section>
</div></article><footer><div class="container">FunASR · Applications and technical guides</div></footer>
</body></html>
File diff suppressed because one or more lines are too long
@@ -89,6 +89,7 @@ footer a{color:#60a5fa}
<div class="model-card">
<div class="model-name">Fun-ASR-Nano <span class="badge badge-orange">⭐ Flagship / default</span></div>
<div class="model-meta">800M params &middot; LLM-based (SenseVoice encoder + Qwen3-0.6B) &middot; <a href="/go/fun-asr">GitHub</a> &middot; <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512">HuggingFace</a></div>
<p data-native-transformers>Native Transformers support is merged upstream. Use the <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf">official -hf checkpoint</a>, distinct from original, -vllm and GGUF artifacts. The <a href="/en/docs/native-transformers.html">pinned installation and inference guide</a> explains why stable 5.16.1 is not sufficient.</p>
<div class="model-desc">Next-generation LLM-based ASR model. Combines SenseVoice's audio encoder with Qwen3-0.6B language model for superior context understanding. Supports vLLM acceleration for high-throughput batch inference and real-time streaming. The released <code>model.pt</code> checkpoint does not provide reliable checkpoint-native character timestamps (<a href="https://github.com/QwenAudio/Fun-ASR/issues/106" target="_blank" rel="noopener">issue #106</a>).</div>
<div class="model-features">
<span class="badge badge-green">vLLM accelerated</span>
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

@@ -89,6 +89,7 @@ footer a{color:#60a5fa}
<div class="model-card">
<div class="model-name">Fun-ASR-Nano <span class="badge badge-orange">⭐ 旗舰 / 默认推荐</span></div>
<div class="model-meta">800M 参数 &middot; LLM 架构 (SenseVoice 编码器 + Qwen3-0.6B) &middot; <a href="/go/fun-asr">GitHub</a> &middot; <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512">HuggingFace</a></div>
<p data-native-transformers>原生 Transformers 路径已合入上游:使用 <a href="https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-hf">官方 -hf checkpoint</a>,不要与原始权重、-vllm 或 GGUF 混用。<a href="/docs/native-transformers.html">固定版本的安装与推理指南</a>说明 5.16.1 稳定版尚未包含的边界。</p>
<div class="model-desc">新一代 LLM 架构 ASR 模型。融合 SenseVoice 音频编码器与 Qwen3-0.6B 语言模型,具备更强的上下文理解能力。支持 vLLM 加速的高吞吐批量推理与实时流式服务。发布的 <code>model.pt</code> checkpoint 不提供可靠的原生字级时间戳(<a href="https://github.com/QwenAudio/Fun-ASR/issues/106" target="_blank" rel="noopener">issue #106</a>)。</div>
<div class="model-features">
<span class="badge badge-green">vLLM 加速</span>
@@ -0,0 +1,51 @@
import { expect, test } from '@playwright/test';
for (const prefix of ['', 'en/']) {
for (const width of [390, 1440]) {
test(`native Transformers article and guide ${prefix || 'zh'} ${width}`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
const route = `/${prefix}blog/fun-asr-nano-transformers.html`;
await page.goto(`/${prefix}blog/`);
await page.locator(`a.post-card[href="${route}"]`).click();
await expect(page).toHaveURL(new RegExp(route.replaceAll('.', '\\.')));
const layout = await page.evaluate(() => ({
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
headingTop: document.querySelector('article h1')!.getBoundingClientRect().top,
navBottom: document.querySelector('nav')!.getBoundingClientRect().bottom,
}));
expect(layout.overflow).toBeLessThanOrEqual(1);
expect(layout.headingTop).toBeGreaterThanOrEqual(layout.navBottom);
await page.screenshot({ path: testInfo.outputPath('article-top.png') });
const waveform = page.locator('article img');
await waveform.scrollIntoViewIfNeeded();
await expect(waveform).toBeVisible();
expect(await waveform.evaluate((node: HTMLImageElement) => node.complete && node.naturalWidth === 1800)).toBeTruthy();
await page.screenshot({ path: testInfo.outputPath('waveform.png') });
const table = page.locator('[data-native-section="formats"] table');
await table.scrollIntoViewIfNeeded();
const scroll = await table.evaluate((tableNode) => {
const node = tableNode.closest('.table-wrap')!;
node.scrollLeft = node.scrollWidth;
const rect = node.getBoundingClientRect();
return { left: rect.left, right: rect.right, scroll: node.scrollLeft,
width: node.clientWidth, content: node.scrollWidth };
});
expect(scroll.left).toBeGreaterThanOrEqual(0);
expect(scroll.right).toBeLessThanOrEqual(width);
if (width === 390) expect(scroll.scroll).toBeGreaterThan(0);
await page.locator(`article a[href="/${prefix}docs/native-transformers.html"]`).first().click();
await expect(page.locator('[data-source-link]')).toHaveAttribute(
'href', new RegExp(`/docs/transformers_native${prefix ? '' : '_zh'}\\.md$`),
);
await expect(page.locator('.docs-article')).toContainText('torchaudio==2.10.0+cpu');
expect(await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)).toBeLessThanOrEqual(1);
await page.screenshot({ path: testInfo.outputPath('guide-top.png') });
await page.goto(`/${prefix}models.html`);
await page.locator(`a[href="/${prefix}docs/native-transformers.html"]`).click();
await expect(page).toHaveURL(new RegExp('/docs/native-transformers\\.html$'));
await page.goto(`/__pages/${prefix ? '' : 'zh/'}native-transformers.html`);
await expect(page.locator('.docs-article')).toContainText('FunAudioLLM/Fun-ASR-Nano-2512-hf');
expect(await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)).toBeLessThanOrEqual(1);
});
}
}
@@ -0,0 +1,134 @@
"""Native Transformers adoption documentation, without model downloads."""
import ast
import json
import re
import sys
from pathlib import Path
import pytest
from bs4 import BeautifulSoup
SITE = Path(__file__).resolve().parents[1]
ROOT = SITE.parents[1]
sys.path.insert(0, str(SITE))
from build import build
from export_docs import export_documentation
SLUG = "native-transformers"
BLOG = "fun-asr-nano-transformers.html"
HEAD = "fc501343edfccdc840eb8594a6cafa8185c2de53"
REVISION = "d93b302ee7fd505e1b3576120fc142fc6f7820e1"
MODEL = "FunAudioLLM/Fun-ASR-Nano-2512-hf"
def guide(suffix=""):
path = ROOT / f"docs/transformers_native{suffix}.md"
assert path.is_file(), "Missing native Transformers guide"
return path.read_text()
def example(text, name):
pattern = rf"<!-- native-example: {name} -->\s*\x60\x60\x60python\n(.*?)\x60\x60\x60"
matches = re.findall(pattern, text, re.S)
assert len(matches) == 1, f"Missing unique {name} example"
return matches[0]
def test_catalogue_and_pages_export_own_both_native_guides():
data = json.loads((SITE / "data/documentation.json").read_text())
entries = [p for p in data["pages"] if p["slug"] == SLUG]
assert len(entries) == 1
assert entries[0]["source_en"] == "docs/transformers_native.md"
assert entries[0]["source_zh"] == "docs/transformers_native_zh.md"
from export_docs import ALIASES
assert ALIASES[SLUG] == "native-transformers.html"
@pytest.mark.parametrize("suffix", ["", "_zh"])
def test_native_guide_pins_format_runtime_and_audio_dependency(suffix):
text = guide(suffix)
for expected in (MODEL, REVISION, HEAD, "torchaudio==2.10.0+cpu",
"torch==2.10.0+cpu", "5.16.1", "2026-09-09",
"vllm", "GGUF", "46180"):
assert expected in text
assert re.search(r"not.*stable|stable.*not|稳定版.*不|不.*稳定版", text, re.I)
assert re.search(r"not.*(?:capacity|accuracy)|不.*(?:容量|准确率)", text, re.I)
@pytest.mark.parametrize("name", ["processor", "transcribe"])
def test_bilingual_recipes_are_identical_and_use_fixed_native_checkpoint(name):
en = example(guide(), name)
zh = example(guide("_zh"), name)
assert en == zh
tree = ast.parse(en)
constants = {node.value for node in ast.walk(tree) if isinstance(node, ast.Constant) and isinstance(node.value, str)}
assert MODEL in constants and REVISION in constants
calls = [node for node in ast.walk(tree) if isinstance(node, ast.Call)]
loaders = [node for node in calls if isinstance(node.func, ast.Attribute) and node.func.attr == "from_pretrained"]
assert loaders
for node in loaders:
names = {kw.arg for kw in node.keywords}
assert {"revision", "trust_remote_code"} <= names
assert next(kw.value for kw in node.keywords if kw.arg == "trust_remote_code").value is False
assert "apply_transcription_request" in en
if name == "processor":
assert not any(isinstance(node.func, ast.Attribute) and node.func.attr == "generate" for node in calls)
else:
assert "inference_mode" in en and "max_new_tokens" in en
assert "generated[:, inputs.input_ids.shape[1]:]" in en
assert "batch_decode" in en
assert "16000" in en and "ndim" in en
@pytest.mark.parametrize("name,heading", [
("README.md", "What's new"), ("README_zh.md", "最新动态"),
("README_ja.md", "最新情報"), ("README_ko.md", "최신 소식"),
])
def test_readme_news_stays_three_items_with_native_guide(name, heading):
text = (ROOT / name).read_text()
section = text.split("## " + heading, 1)[1].split("\n## ", 1)[0]
bullets = re.findall(r"^- .*", section, re.M)
assert len(bullets) == 3
assert any("Transformers" in row and "transformers_native" in row for row in bullets)
assert any("1.4.15" in row for row in bullets)
assert any("MOSS" in row for row in bullets)
@pytest.mark.parametrize("suffix", ["", "_zh"])
def test_model_zoo_and_deployment_matrix_link_native_format(suffix):
zoo = (ROOT / f"model_zoo/readme{suffix}.md").read_text()
matrix = (ROOT / f"docs/deployment_matrix{suffix}.md").read_text()
assert MODEL in zoo and f"transformers_native{suffix}.md" in zoo
assert f"transformers_native{suffix}.md" in matrix
@pytest.fixture(scope="module")
def rendered(tmp_path_factory):
output = tmp_path_factory.mktemp("native-transformers-site")
build(output)
pages = tmp_path_factory.mktemp("native-transformers-pages")
export_documentation(output, pages)
return output, pages
@pytest.mark.parametrize("prefix,suffix,pages_prefix", [("", "_zh", "zh/"), ("en/", "", "")])
def test_rendered_blog_models_guide_and_pages_are_connected(rendered, prefix, suffix, pages_prefix):
output, pages = rendered
route = f"/{prefix}docs/{SLUG}.html"
article_path = output / prefix / "blog" / BLOG
assert article_path.is_file()
article = BeautifulSoup(article_path.read_text(), "html.parser")
assert article.select_one(f'article a[href="{route}"]')
assert article.select_one('link[rel="canonical"]')["href"] == f"https://www.funasr.com/{prefix}blog/{BLOG}"
body = article.select_one("article").get_text(" ", strip=True)
for term in ["Transformers", "-hf", "vLLM", "GGUF", "2026-09-09"]:
assert term in body
index = BeautifulSoup((output / prefix / "blog/index.html").read_text(), "html.parser")
assert len(index.select(f'a.post-card[href="/{prefix}blog/{BLOG}"]')) == 1
models = BeautifulSoup((output / prefix / "models.html").read_text(), "html.parser")
assert models.select_one(f'a[href="{route}"]')
native = BeautifulSoup((output / prefix / "docs" / f"{SLUG}.html").read_text(), "html.parser")
assert native.select_one("[data-source-link]")["href"].endswith(f"/docs/transformers_native{suffix}.md")
exported = BeautifulSoup((pages / pages_prefix / "native-transformers.html").read_text(), "html.parser")
assert MODEL in exported.get_text()
assert exported.select_one('link[rel="canonical"]')["href"] == "https://www.funasr.com" + route