Official native vLLM functional validation (2026-09-07)

View / edit source ↗

中文

Verified 2026-09-07, Asia/Shanghai (raw HTTP Date headers: 2026-09-06 UTC). This is a separate functional serving record for the official FunAudioLLM/Fun-ASR-Nano-2512-vllm snapshot, revision a4362c943d48951f98ca2a62181cc028970270c5. It is a model revision, not a root FunASR package version. This run used vLLM's native FunASRForConditionalGeneration, not FunASR AutoModel or the split-engine decoder path.

The 2026-08-13 historical community validation and its community allendou/Fun-ASR-Nano-2512-vllm@e718b36e benchmark remain unchanged. The official run is not a relabeling of those timings or evidence of a speed improvement.

Tested scope and environment

Eight recorded HTTP requests returned 200: health, model discovery, three single-language transcriptions, Chinese hotwords, and two concurrent requests. All 23 files were downloaded directly from the pinned official repository, verified against Hub Git/LFS digests, copied to a durable backup, and rehashed. The test did not substitute community cache files or modify upstream model code.

Component Observed value
Python 3.12.3
vLLM 0.27.1+cu129
Torch 2.13.0+cu129
Transformers 5.15.0
CUDA runtime / NVIDIA driver 12.9 / 550.127.08
GPU One NVIDIA H100 80GB HBM3
Audio libraries av 18.1.0, soundfile 0.14.0, scipy 1.18.0, soxr 1.1.0, NumPy 2.3.5
Hub tooling / HTTP client huggingface_hub 1.27.0 / requests 2.34.2
Server FP32, eager, GPU memory utilization 0.40, resolved max model length 40,960

This validated an existing environment, not a clean installation. No packages were installed, upgraded or re-resolved. These observed versions are not a newly validated dependency lockfile. In particular, do not infer that an unconstrained pip install vllm or a floating Hub loader reproduces this run.

vLLM #54944 merged at e473e9036f979d546830aece9855027049faf0ba on 2026-09-05. It updates the supported-model documentation and test registry to the official checkpoint, not the inference implementation. At the 2026-09-07 audit, main used the official reference but v0.28.0 still referenced the community artifact. Merged does not mean released. This run validates neither main nor v0.28.0, and does not override the upstream test registry's separate Transformers version constraint.

Prepare a pinned local snapshot

The following commands are a portable transcription of the tested preparation and launch procedure: private absolute paths are replaced by variables. Point VLLM_PYTHON at an already provisioned environment matching the table; the .venv path below is a placeholder, not a venv creation command. Use a new isolated validation directory and keep its download manifest. Existing Hub authentication may be used without displaying credentials.

export VLLM_PYTHON="$PWD/.venv/bin/python"
export VALIDATION_DIR="$PWD/.official-native-validation"
export MODEL_DIR="$VALIDATION_DIR/official-model/a4362c943d48951f98ca2a62181cc028970270c5"

Check versions in the same native-import context used during validation:

"$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()
PY

Download all 23 files at the immutable revision and verify the official Hub metadata before serving. The script does not execute the downloaded conversion script and does not modify the snapshot:

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

Checkpoint and sample hashes

All examples are mono 48 kHz MP3. Container durations measured by ffprobe were zh 5.616 s, en 7.176 s, ja 7.224 s. Server usage counters rounded those to 6/8/8 seconds; those rounded values are not the measured file durations.

File Bytes SHA256
example/en.mp3 57441 f10378336a4e584f3f63799e62f99d5add3c2a401b51d3abe7d3a3a82f255ada
example/ja.mp3 57837 496dbc43b289e1d0d0cb916df9737450bca56acd8aaca046a7a2472363b1be53
example/zh.mp3 44973 0e64de19e4ff9a02e682955c9112f32d2317cfdbb5bc2f3504664044c993f195
model.safetensors 1970899072 96dfbec48282dd24d3334369a01e9e909f321ee39a1b0003c528c5379f68c1a6

The reproducibility metadata contains all 23 file sizes, SHA256 and upstream Git/LFS digests, observed package versions, exact HTTP fields, original response hashes and unrounded timings. Private host paths, GPU identifiers and credentials are not published. In particular, the raw /v1/models response's local root is retained in private evidence; its digest refers to the original bytes, not a redacted replacement.

Launch offline on loopback

First ensure GPU 0 is idle and loopback port 57185 is free. Choose another free port consistently in every command if necessary; only 57185 was used in this record. Run in the foreground from the prepared 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

The engine loaded the verified local snapshot with Hub and Transformers offline. No --trust-remote-code or floating model ID loader was used. Startup to healthy took 84.123246 s; download was already complete. This startup observation is not a startup performance guarantee.

In a second shell, set VALIDATION_DIR and MODEL_DIR to the same absolute paths as above, then wait for readiness:

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 must contain ID fun-asr-nano-official-a4362c94 and a root equal to your resolved MODEL_DIR. The recorded run checked both.

Actual transcription requests

The harness used Python requests with multipart audio MIME audio/mpeg, connect/read timeouts of 5/45 s, explicit language=zh, language=en and language=ja, and response_format=json. It sent no temperature or generation-length override. These equivalent curl requests preserve those fields; their timings have not been separately measured:

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

Observed texts:

  • Chinese baseline: 开饭时间早上九点至下午五点。
  • English: The tribal chieftain called for the boy, and presented him with fifty pieces of gold.
  • Japanese: うちの中学は弁当制で、持っていけない場合は、五十円の学校販売のパンを買う。
  • Chinese with hotwords=开放时间,开放时间,开放时间: 开放时间早上九点至下午五点。

The baseline error is preserved. Repeating this hotword changed this sample's output; it is not a general hotword policy or an accuracy guarantee.

Recorded request HTTP Client wall seconds
GET /health 200 0.001023
GET /v1/models 200 0.002583
POST zh (first) 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

The first Chinese call was not warmed: it was the first transcription after health and model discovery. Later calls reused that engine. Times include local HTTP and decoding, exclude model download and startup, and are single observations, not latency distributions.

After the four sequential transcription calls, a two-worker ThreadPoolExecutor sent the same English and Japanese multipart requests. Both returned 200 with the same texts. The combined wall time was 0.9112209342420101 s (0.911 s), including executor setup and waiting for both results. This is only a two-request functional concurrency probe, not throughput, production capacity, an accuracy study or a comparison with the historical 1.123 s community probe.

Harness boundary and cleanup

The first harness attempt failed a package-inventory guard before server spawn. Preparation imported native vLLM, which exposed setuptools vendored distributions on sys.path; the original serve phase compared a different import context. Matching that import produced an empty package difference. The original failure and correction were preserved. An early failure status flag incorrectly said the server had started; process evidence and the correction establish that it had not. No dependency or model-code changes were needed. The first actual server launch then completed the eight-request smoke.

The bounded harness terminated its own process group and waited for the server and children. Server exit code was 0, the loopback port closed, and no GPU compute processes remained. Package and native source hashes were unchanged. A separate check confirmed all 23 original and backup file hashes, eight raw responses, model identity and cleanup. When reproducing manually, stop the foreground server after the probes and verify its workers and port are gone; do not terminate unrelated GPU jobs.

Deployment boundaries

This is request/response /v1/audio/transcriptions, not a validated /v1/realtime streaming session. Long audio, speaker diarization, timestamp accuracy, other GPUs, sustained load and production capacity were not tested. Do not infer a FunASR SDK or package-release validation from this native server.

Keep the worker on 127.0.0.1. Before exposing an API, use a gateway for authentication, TLS, rate limits and audio size/duration limits, with isolated uploads and a retention policy. That gateway was not part of this smoke. See the deployment matrix and security boundaries.