From 47b9fb464bb34b050a3b3eeafbe5670b19c85c53 Mon Sep 17 00:00:00 2001 From: zhifu gao Date: Sun, 6 Sep 2026 22:19:45 +0000 Subject: [PATCH] docs: document official native vLLM validation and refine deployment limitations Signed-off-by: zhifu gao --- .../vllm_official_native_20260907.json | 624 ++++++++++++++++++ docs/deployment_matrix.md | 2 +- docs/deployment_matrix_zh.md | 2 +- docs/index.rst | 4 +- docs/vllm_official_native_validation.md | 250 +++++++ docs/vllm_official_native_validation_zh.md | 231 +++++++ web-pages/product-site/assets/css/site.css | 9 + web-pages/product-site/data/deployments.json | 159 ++++- .../product-site/data/documentation.json | 3 +- .../product-site/templates/deploy-detail.html | 4 +- .../tests/browser/product-site.spec.ts | 42 ++ .../product-site/tests/test_documentation.py | 35 + .../tests/test_official_vllm_docs.py | 94 +++ web-pages/product-site/tests/test_registry.py | 37 +- 14 files changed, 1443 insertions(+), 53 deletions(-) create mode 100644 docs/benchmark/vllm_official_native_20260907.json create mode 100644 docs/vllm_official_native_validation.md create mode 100644 docs/vllm_official_native_validation_zh.md create mode 100644 web-pages/product-site/tests/test_official_vllm_docs.py diff --git a/docs/benchmark/vllm_official_native_20260907.json b/docs/benchmark/vllm_official_native_20260907.json new file mode 100644 index 000000000..158aee4b0 --- /dev/null +++ b/docs/benchmark/vllm_official_native_20260907.json @@ -0,0 +1,624 @@ +{ + "schema_version": 1, + "verified": "2026-09-07", + "time_zone": "Asia/Shanghai (HTTP Date headers are UTC on 2026-09-06)", + "model": "FunAudioLLM/Fun-ASR-Nano-2512-vllm", + "revision": "a4362c943d48951f98ca2a62181cc028970270c5", + "files": [ + { + "path": ".gitattributes", + "size": 2161, + "git_blob": "0bb08f0d1369b2604a2de018bd9468aa0f7bb527", + "lfs_sha256": null, + "sha256": "c6391bc5513b1407e95e1b5567f11dd7f71616ee294f85b133d397afe72d5e2d" + }, + { + "path": "LICENSE", + "size": 11358, + "git_blob": "d645695673349e3947e8e5ae42332d0ac3164cd7", + "lfs_sha256": null, + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" + }, + { + "path": "MODEL_PROVENANCE.json", + "size": 1501, + "git_blob": "7d9d3b3eaa59d42b22be085a8fb7e4d0776b3eac", + "lfs_sha256": null, + "sha256": "eba0b6a9b2a9baf53392160df38bc1d99299eb5fced854803e475303c54a1f60" + }, + { + "path": "README.md", + "size": 3584, + "git_blob": "cae88395a68155067dfe0d8c28d03b591e233e31", + "lfs_sha256": null, + "sha256": "c6cfac1332d8fdf7a63db6c148a8898b7eab63b128ef9682b95ada07ef50ae18" + }, + { + "path": "README_zh.md", + "size": 3330, + "git_blob": "1120b18a3831e64df88ee6fd88d73ed791a5a5b6", + "lfs_sha256": null, + "sha256": "30d462f6914cf0c83df6ee9da0a55c9ec917d3fdf5c90325dde5efb74ed095fa" + }, + { + "path": "config.json", + "size": 1395, + "git_blob": "9502ac38fe453e076402a673987a181060a1402e", + "lfs_sha256": null, + "sha256": "5e72ae4baf304d65bfdf0e779be19817a71f7d65441b096f1dcff377c63ed0e5" + }, + { + "path": "config.yaml", + "size": 3140, + "git_blob": "1fb7aa3cff4496048cc80d1906c82e0ca5572ac1", + "lfs_sha256": null, + "sha256": "daed38ea6484f5650fb32cbd9069b9aa13880acaf2bcb1f0bf4be2712837917c" + }, + { + "path": "configuration.json", + "size": 398, + "git_blob": "2529b01f4be6622337b88ae0a24cb92c6e28dc70", + "lfs_sha256": null, + "sha256": "b64a3a55d35bcbe2cf4d31f2d3ef25a423d3ba2ebff203298c27fa055f3c7612" + }, + { + "path": "convert_from_official.py", + "size": 2156, + "git_blob": "f560dbb5748e06bb44822ac4168d1bd8bce45afa", + "lfs_sha256": null, + "sha256": "1b2242ae6da997d4d3d70310cc5e1226c83ca82988555be82a03464442ce4b50" + }, + { + "path": "example/en.mp3", + "size": 57441, + "git_blob": "325005e60db535f24f5b3d9504b209704031bdb4", + "lfs_sha256": null, + "sha256": "f10378336a4e584f3f63799e62f99d5add3c2a401b51d3abe7d3a3a82f255ada" + }, + { + "path": "example/ja.mp3", + "size": 57837, + "git_blob": "b3056dd92c8a0210eb8f786662a4528e31b44942", + "lfs_sha256": null, + "sha256": "496dbc43b289e1d0d0cb916df9737450bca56acd8aaca046a7a2472363b1be53" + }, + { + "path": "example/ko.mp3", + "size": 27909, + "git_blob": "3e67ab481230742d41e27adb63f5091cec39a7db", + "lfs_sha256": null, + "sha256": "8612f62db8319a6cb4ab4b1d2039bfc32f174f89611889ddafdeb5c0a6070b5f" + }, + { + "path": "example/yue.mp3", + "size": 31246, + "git_blob": "a67e8f862132e61d867854dd5e6e4d28af57b55a", + "lfs_sha256": null, + "sha256": "5098eebc13530a66e4eac1f30d3246e65c9cfc4e096665f9d395aca8eff0d181" + }, + { + "path": "example/zh.mp3", + "size": 44973, + "git_blob": "1ae2c89b29112ee5e23bcebc353ea6687c38e6bd", + "lfs_sha256": null, + "sha256": "0e64de19e4ff9a02e682955c9112f32d2317cfdbb5bc2f3504664044c993f195" + }, + { + "path": "generation_config.json", + "size": 239, + "git_blob": "20a8a9156fc8c3f25295ca067f61fdf120d517c5", + "lfs_sha256": null, + "sha256": "2325da0f15bb848e018c5ae071b7943332e9f871d6b60e2ed22ca97d4cb993d2" + }, + { + "path": "merges.txt", + "size": 1671853, + "git_blob": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7", + "lfs_sha256": null, + "sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5" + }, + { + "path": "model.safetensors", + "size": 1970899072, + "git_blob": "7e1ee59c4a9517d8b9f4dec52a8555fc5367c184", + "lfs_sha256": "96dfbec48282dd24d3334369a01e9e909f321ee39a1b0003c528c5379f68c1a6", + "sha256": "96dfbec48282dd24d3334369a01e9e909f321ee39a1b0003c528c5379f68c1a6" + }, + { + "path": "multilingual.tiktoken", + "size": 907395, + "git_blob": "ac47fe685d24565030965a94c0d98e00e0331eaa", + "lfs_sha256": null, + "sha256": "747979631e813193436aabcff7c1c235d37de8097b71c563ec8b63b7a515c718" + }, + { + "path": "preprocessor_config.json", + "size": 553, + "git_blob": "b8db96b12744ebd1c9da7e70c42ae71a8ecbd9ac", + "lfs_sha256": null, + "sha256": "007aac6d52b5ecf5e75052330489138a6a8940d68cf3e9723c502e8093ab1b38" + }, + { + "path": "tests/test_conversion.py", + "size": 993, + "git_blob": "f1a1e2095b6a81a49b28b424ec98cc1b9480a231", + "lfs_sha256": null, + "sha256": "262eda8e83e17b5f11c3e6e3b050327ad488688d8a4883da183bcb04332c4713" + }, + { + "path": "tokenizer.json", + "size": 11422654, + "git_blob": "cd71f61a15a522601badb3dc960d800d9cb3766c", + "lfs_sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4", + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer_config.json", + "size": 9965, + "git_blob": "4a31bcc54d80aaa3c6602b70979fc874957bc8e1", + "lfs_sha256": null, + "sha256": "906ee1fc8a1ffbeb04417b1fd61ea8b04debe1ae7bf6e70f7b4475712144870d" + }, + { + "path": "vocab.json", + "size": 2776833, + "git_blob": "4783fe10ac3adce15ac8f358ef5462739852c569", + "lfs_sha256": null, + "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910" + } + ], + "audio": { + "zh": { + "path": "example/zh.mp3", + "sha256": "0e64de19e4ff9a02e682955c9112f32d2317cfdbb5bc2f3504664044c993f195", + "duration_seconds": 5.616, + "sample_rate": 48000, + "channels": 1 + }, + "en": { + "path": "example/en.mp3", + "sha256": "f10378336a4e584f3f63799e62f99d5add3c2a401b51d3abe7d3a3a82f255ada", + "duration_seconds": 7.176, + "sample_rate": 48000, + "channels": 1 + }, + "ja": { + "path": "example/ja.mp3", + "sha256": "496dbc43b289e1d0d0cb916df9737450bca56acd8aaca046a7a2472363b1be53", + "duration_seconds": 7.224, + "sample_rate": 48000, + "channels": 1 + } + }, + "environment": { + "python": "3.12.3", + "cuda_runtime": "12.9", + "gpu": "NVIDIA H100 80GB HBM3", + "driver": "550.127.08", + "packages": { + "z3-solver": "4.15.4.0", + "nvidia-nccl-cu12": "2.29.7", + "torchvision": "0.28.0+cu129", + "nvidia-cusparse-cu12": "12.5.10.65", + "loguru": "0.7.3", + "ninja": "1.13.0", + "requests": "2.34.2", + "mdurl": "0.1.2", + "dnspython": "2.8.0", + "tokenizers": "0.22.2", + "rpds-py": "2026.6.3", + "attrs": "26.1.0", + "soxr": "1.1.0", + "vllm": "0.27.1+cu129", + "typer": "0.27.1", + "detect-installer": "0.1.0", + "email-validator": "2.3.0", + "numpy": "2.3.5", + "setproctitle": "1.3.7", + "openai": "2.54.0", + "truststore": "0.10.4", + "fastapi-cli": "0.0.32", + "nvidia-cublas-cu12": "12.9.1.4", + "opentelemetry-exporter-otlp": "1.44.0", + "jsonschema-specifications": "2025.9.1", + "opentelemetry-exporter-otlp-proto-common": "1.44.0", + "prometheus_client": "0.26.0", + "partial-json-parser": "0.2.1.1.post7", + "astor": "0.8.1", + "aiohappyeyeballs": "2.7.1", + "depyf": "0.20.0", + "sympy": "1.14.0", + "nvidia-cuda-runtime-cu12": "12.9.79", + "nvidia-cutlass-dsl-libs-base": "4.6.0", + "anthropic": "0.121.0", + "opentelemetry-sdk": "1.44.0", + "nvidia-cutlass-dsl-libs-core": "4.6.0", + "humming-kernels": "0.1.10", + "rich": "15.0.0", + "pybase64": "1.5.0", + "ijson": "3.5.1", + "apache-tvm-ffi": "0.1.11", + "PyYAML": "6.0.3", + "pyzmq": "27.1.0", + "frozenlist": "1.8.0", + "multidict": "6.7.1", + "jmespath": "1.1.0", + "opentelemetry-semantic-conventions-ai": "0.5.1", + "nvidia-ml-py": "13.610.43", + "nvidia-cuda-nvdisasm": "13.3.73", + "docstring_parser": "0.18.0", + "lm-format-enforcer": "0.11.3", + "mcp": "2.0.0", + "urllib3": "2.7.0", + "huggingface_hub": "1.27.0", + "torchcodec": "0.15.0+cu129", + "anyio": "4.14.2", + "nvidia-cutlass-dsl-libs-cu12": "4.6.0", + "typing_extensions": "4.16.0", + "setuptools": "80.10.2", + "cffi": "2.1.1", + "mcp-types": "2.0.0", + "aiosignal": "1.4.0", + "jiter": "0.16.0", + "python-dotenv": "1.2.2", + "fastar": "0.11.0", + "cuda-toolkit": "12.9.1", + "six": "1.17.0", + "tqdm": "4.70.0", + "sniffio": "1.3.1", + "cuda-bindings": "12.9.7", + "sentry-sdk": "2.67.1", + "MarkupSafe": "3.0.3", + "nvidia-cudnn-frontend": "1.27.0", + "PyJWT": "2.13.0", + "llguidance": "1.7.6", + "fastsafetensors": "0.3.3", + "nccl4py": "0.4.1", + "referencing": "0.37.0", + "nvidia-cusparselt-cu12": "0.8.1", + "numba": "0.65.0", + "lark": "1.2.2", + "rignore": "0.8.1", + "fastapi-cloud-cli": "0.23.0", + "propcache": "0.5.2", + "pip": "24.0", + "torch": "2.13.0+cu129", + "cuda-core": "1.1.1", + "certifi": "2026.7.22", + "psutil": "7.2.2", + "pycparser": "3.0", + "triton": "3.7.1", + "starlette": "1.6.0", + "cryptography": "50.0.0", + "nvidia-curand-cu12": "10.3.10.19", + "python-multipart": "0.0.32", + "tabulate": "0.10.0", + "Pygments": "2.20.0", + "outlines_core": "0.2.14", + "cuda-python": "12.9.7", + "pillow": "12.3.0", + "shellingham": "1.5.4", + "httptools": "0.8.0", + "pynvvideocodec": "2.0.4", + "transformers": "5.15.0", + "msgspec": "0.21.1", + "httpx2": "2.10.0", + "dill": "0.4.1", + "nvidia-cuda-crt": "13.3.73", + "packaging": "26.0", + "regex": "2026.7.19", + "nvidia-nvshmem-cu12": "3.4.5", + "uvicorn": "0.52.1", + "cuda-tile": "1.5.0", + "nvidia-cuda-nvrtc": "13.3.33", + "pydantic-extra-types": "2.11.1", + "ml_dtypes": "0.5.4", + "prometheus-fastapi-instrumentator": "8.1.0", + "opentelemetry-api": "1.44.0", + "pydantic": "2.13.4", + "Jinja2": "3.1.6", + "pycountry": "26.2.16", + "filelock": "3.32.2", + "opencv-python-headless": "5.0.0.93", + "tokenspeed-triton": "3.8.10.post20260721", + "sse-starlette": "3.4.8", + "torchaudio": "2.11.0+cu129", + "nvidia-cutlass-dsl-libs-cu13": "4.6.0", + "protobuf": "6.33.6", + "cbor2": "6.1.4", + "pydantic_core": "2.46.4", + "flashinfer-python": "0.6.16.post3", + "googleapis-common-protos": "1.75.1", + "nvidia-nvvm": "13.3.73", + "hf-xet": "1.6.0", + "nvidia-cudnn-cu12": "9.20.0.48", + "nvidia-nvjitlink-cu12": "12.9.86", + "yarl": "1.24.5", + "nvidia-cuda-cccl": "13.3.3.4.1", + "charset-normalizer": "3.5.0", + "interegular": "0.3.3", + "h11": "0.16.0", + "xgrammar": "0.2.3", + "nvidia-cutlass-dsl": "4.6.0", + "quack-kernels": "0.6.1", + "nvidia-cuda-nvcc": "13.3.73", + "opentelemetry-proto": "1.44.0", + "httpcore": "1.0.9", + "annotated-types": "0.8.0", + "nvidia-cusolver-cu12": "11.7.5.82", + "rich-toolkit": "0.20.3", + "mistral_common": "1.11.7", + "nvtx": "0.2.15", + "distro": "1.9.0", + "tokenspeed-mla": "0.1.8", + "supervisor": "4.3.0", + "nvidia-nvtx-cu12": "12.9.79", + "grpcio": "1.83.0", + "nvidia-cufft-cu12": "11.4.1.4", + "model-hosting-container-standards": "0.1.16", + "av": "18.1.0", + "pydantic-settings": "2.15.0", + "uvloop": "0.22.1", + "httpx": "0.28.1", + "nvidia-cufile-cu12": "1.14.1.1", + "opentelemetry-semantic-conventions": "0.65b0", + "opentelemetry-exporter-otlp-proto-grpc": "1.44.0", + "typing-inspection": "0.4.4", + "markdown-it-py": "4.2.0", + "tiktoken": "0.13.0", + "python-json-logger": "4.1.0", + "nvidia-cuda-runtime": "13.3.29", + "websockets": "17.0.1", + "httpcore2": "2.10.0", + "torch_c_dlpack_ext": "0.1.5", + "opentelemetry-exporter-otlp-proto-http": "1.44.0", + "cloudpickle": "3.1.2", + "safetensors": "0.8.0", + "tilelang": "0.1.12", + "compressed-tensors": "0.17.0", + "nvidia-cuda-cupti-cu12": "12.9.79", + "soundfile": "0.14.0", + "einops": "0.8.2", + "fastapi": "0.136.3", + "cuda-pathfinder": "1.6.0", + "aiohttp": "3.14.3", + "networkx": "3.6.1", + "fsspec": "2026.7.0", + "watchfiles": "1.2.0", + "llvmlite": "0.47.0", + "annotated-doc": "0.0.5", + "pyelftools": "0.33", + "py-cpuinfo": "9.0.0", + "openai-harmony": "0.0.8", + "mpmath": "1.3.0", + "blake3": "1.0.9", + "sentencepiece": "0.2.2", + "click": "8.4.2", + "nvidia-cuda-nvrtc-cu12": "12.9.86", + "scipy": "1.18.0", + "jsonschema": "4.26.0", + "cachetools": "7.1.7", + "idna": "3.18", + "more-itertools": "10.8.0", + "importlib_metadata": "8.7.1", + "jaraco.text": "4.0.0", + "wheel": "0.46.3", + "tomli": "2.4.0", + "platformdirs": "4.4.0", + "zipp": "3.23.0", + "jaraco.functools": "4.4.0", + "jaraco.context": "6.1.0", + "backports.tarfile": "1.2.0", + "autocommand": "2.2.2" + }, + "native_source": "vllm/model_executor/models/funasr.py", + "native_source_sha256": "0cc059845327614a82259c0fe7bd5a1f868565e7e011c113b24c18a47f79c5c7" + }, + "serving": { + "model_directory": "$MODEL_DIR", + "served_model": "fun-asr-nano-official-a4362c94", + "host": "127.0.0.1", + "port": 57185, + "dtype": "float32", + "gpu_memory_utilization": 0.4, + "enforce_eager": true, + "resolved_max_model_len": 40960 + }, + "commands": { + "install": [ + "export VLLM_PYTHON=\"$PWD/.venv/bin/python\"\nexport VALIDATION_DIR=\"$PWD/.official-native-validation\"\nexport MODEL_DIR=\"$VALIDATION_DIR/official-model/a4362c943d48951f98ca2a62181cc028970270c5\"", + "\"$VLLM_PYTHON\" - <<'PY'\nimport importlib.metadata as metadata\nimport torch\nimport vllm.model_executor.models.funasr\n\nfor name, expected in {\"vllm\": \"0.27.1+cu129\", \"torch\": \"2.13.0+cu129\",\n \"transformers\": \"5.15.0\"}.items():\n assert metadata.version(name) == expected, (name, metadata.version(name))\nassert torch.version.cuda == \"12.9\" and torch.cuda.is_available()\nPY", + "HF_HUB_DISABLE_TELEMETRY=1 HF_XET_CACHE=\"$VALIDATION_DIR/isolated-xet-cache\" \"$VLLM_PYTHON\" - <<'PY'\nimport hashlib\nimport json\nimport os\nfrom pathlib import Path\nfrom huggingface_hub import HfApi, snapshot_download\n\nmodel_id = \"FunAudioLLM/Fun-ASR-Nano-2512-vllm\"\nrevision = \"a4362c943d48951f98ca2a62181cc028970270c5\"\nroot = Path(os.environ[\"MODEL_DIR\"])\ninfo = HfApi().model_info(model_id, revision=revision, files_metadata=True)\nassert info.sha == revision and len(info.siblings) == 23\nsnapshot_download(model_id, revision=revision, local_dir=str(root),\n cache_dir=str(Path(os.environ[\"VALIDATION_DIR\"]) / \"isolated-hf-cache\"),\n max_workers=4)\nfiles = []\nfor item in info.siblings:\n content = (root / item.rfilename).read_bytes()\n digest = hashlib.sha256(content).hexdigest()\n assert len(content) == item.size, item.rfilename\n if item.lfs:\n assert digest == item.lfs.sha256, item.rfilename\n else:\n assert hashlib.sha1(f\"blob {len(content)}\\0\".encode() + content).hexdigest() == item.blob_id\n files.append({\"path\": item.rfilename, \"size\": len(content), \"sha256\": digest})\nassert json.loads((root / \"config.json\").read_text())[\"architectures\"] == [\"FunASRForConditionalGeneration\"]\n(root.parent / \"download-manifest.json\").write_text(json.dumps(files, indent=2) + \"\\n\")\nPY" + ], + "launch": [ + "CUDA_VISIBLE_DEVICES=0 PYTHONDONTWRITEBYTECODE=1 \\\nHF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \\\nHF_HUB_DISABLE_IMPLICIT_TOKEN=1 HF_HUB_DISABLE_TELEMETRY=1 VLLM_NO_USAGE_STATS=1 \\\nVLLM_CACHE_ROOT=\"$VALIDATION_DIR/runtime-cache\" TRITON_CACHE_DIR=\"$VALIDATION_DIR/triton-cache\" \\\n\"$VLLM_PYTHON\" -B -m vllm.entrypoints.openai.api_server \\\n --model \"$MODEL_DIR\" --served-model-name fun-asr-nano-official-a4362c94 \\\n --host 127.0.0.1 --port 57185 --dtype float32 \\\n --gpu-memory-utilization 0.40 --enforce-eager" + ], + "health": [ + "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" + ], + "smoke": [ + "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" + ] + }, + "http": { + "seconds_to_health": 84.12324620783329, + "requests": [ + { + "method": "GET", + "path": "/health", + "request_fields": null, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:28 GMT", + "server": "uvicorn", + "content-length": "0" + }, + "seconds": 0.001022782176733017, + "response_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "body_file": "health.body", + "raw_body_utf8": "" + }, + { + "method": "GET", + "path": "/v1/models", + "request_fields": null, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:28 GMT", + "server": "uvicorn", + "content-length": "571", + "content-type": "application/json" + }, + "seconds": 0.0025825612246990204, + "response_sha256": "8b4d500ad70f80251bc5d7535f3b037fb2001ba13a44a1d1e0bfa82918a6f9b7", + "body_file": "models.body" + }, + { + "method": "POST", + "path": "/v1/audio/transcriptions", + "request_fields": { + "model": "fun-asr-nano-official-a4362c94", + "language": "zh", + "response_format": "json" + }, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:28 GMT", + "server": "uvicorn", + "content-length": "93", + "content-type": "application/json" + }, + "seconds": 0.8895468302071095, + "response_sha256": "a81ea2be2bf7b823ecf880d8c66c67ce6b1c6d12542cd4529008a07e2c40c22e", + "body_file": "transcription-zh.body", + "text": "开饭时间早上九点至下午五点。", + "raw_body_utf8": "{\"text\":\"开饭时间早上九点至下午五点。\",\"usage\":{\"type\":\"duration\",\"seconds\":6}}" + }, + { + "method": "POST", + "path": "/v1/audio/transcriptions", + "request_fields": { + "model": "fun-asr-nano-official-a4362c94", + "language": "en", + "response_format": "json" + }, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:29 GMT", + "server": "uvicorn", + "content-length": "136", + "content-type": "application/json" + }, + "seconds": 0.3863455504179001, + "response_sha256": "4530a812ce88e903ee6196469cef1ffba991d035e6272b145cb487edf12a8bcb", + "body_file": "transcription-en.body", + "text": "The tribal chieftain called for the boy, and presented him with fifty pieces of gold.", + "raw_body_utf8": "{\"text\":\"The tribal chieftain called for the boy, and presented him with fifty pieces of gold.\",\"usage\":{\"type\":\"duration\",\"seconds\":8}}" + }, + { + "method": "POST", + "path": "/v1/audio/transcriptions", + "request_fields": { + "model": "fun-asr-nano-official-a4362c94", + "language": "ja", + "response_format": "json" + }, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:29 GMT", + "server": "uvicorn", + "content-length": "162", + "content-type": "application/json" + }, + "seconds": 0.47393732890486717, + "response_sha256": "d5c3f6c5ccbc5d026cbfcd00839b2ec8b5bc2a296fda877595c8ed27f831fd6c", + "body_file": "transcription-ja.body", + "text": "うちの中学は弁当制で、持っていけない場合は、五十円の学校販売のパンを買う。", + "raw_body_utf8": "{\"text\":\"うちの中学は弁当制で、持っていけない場合は、五十円の学校販売のパンを買う。\",\"usage\":{\"type\":\"duration\",\"seconds\":8}}" + }, + { + "method": "POST", + "path": "/v1/audio/transcriptions", + "request_fields": { + "model": "fun-asr-nano-official-a4362c94", + "language": "zh", + "response_format": "json", + "hotwords": "开放时间,开放时间,开放时间" + }, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:30 GMT", + "server": "uvicorn", + "content-length": "93", + "content-type": "application/json" + }, + "seconds": 0.19060998782515526, + "response_sha256": "972701daeae831b0fe97b4ace846c3cee997cdaab7c31d79e89e2c3dd0856a94", + "body_file": "transcription-zh-hotwords.body", + "text": "开放时间早上九点至下午五点。", + "raw_body_utf8": "{\"text\":\"开放时间早上九点至下午五点。\",\"usage\":{\"type\":\"duration\",\"seconds\":6}}" + }, + { + "method": "POST", + "path": "/v1/audio/transcriptions", + "request_fields": { + "model": "fun-asr-nano-official-a4362c94", + "language": "en", + "response_format": "json" + }, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:30 GMT", + "server": "uvicorn", + "content-length": "136", + "content-type": "application/json" + }, + "seconds": 0.7999004796147346, + "response_sha256": "4530a812ce88e903ee6196469cef1ffba991d035e6272b145cb487edf12a8bcb", + "body_file": "concurrent-en.body", + "text": "The tribal chieftain called for the boy, and presented him with fifty pieces of gold.", + "raw_body_utf8": "{\"text\":\"The tribal chieftain called for the boy, and presented him with fifty pieces of gold.\",\"usage\":{\"type\":\"duration\",\"seconds\":8}}" + }, + { + "method": "POST", + "path": "/v1/audio/transcriptions", + "request_fields": { + "model": "fun-asr-nano-official-a4362c94", + "language": "ja", + "response_format": "json" + }, + "status": 200, + "headers": { + "date": "Sun, 06 Sep 2026 21:05:30 GMT", + "server": "uvicorn", + "content-length": "162", + "content-type": "application/json" + }, + "seconds": 0.9049098119139671, + "response_sha256": "d5c3f6c5ccbc5d026cbfcd00839b2ec8b5bc2a296fda877595c8ed27f831fd6c", + "body_file": "concurrent-ja.body", + "text": "うちの中学は弁当制で、持っていけない場合は、五十円の学校販売のパンを買う。", + "raw_body_utf8": "{\"text\":\"うちの中学は弁当制で、持っていけない場合は、五十円の学校販売のパンを買う。\",\"usage\":{\"type\":\"duration\",\"seconds\":8}}" + } + ], + "two_request_wall_seconds": 0.9112209342420101 + }, + "clean_install_validated": false, + "first_chinese_request_warmed": false, + "guard_failure_before_server_spawn": true, + "guard_correction": "Match native vLLM import context before package inventory comparison; no dependency changes; first actual server launch succeeded.", + "cleanup": { + "all_owned_exited": true, + "server_exit_code": 0, + "loopback_port_closed": true, + "no_gpu_compute_processes": true, + "packages_unchanged": true, + "native_source_unchanged": true + }, + "redactions": "Private host, user paths, GPU UUID and PIDs omitted. HTTP response_sha256 values hash original raw bytes; models.body contains a private local root and is retained privately, not embedded. Public commands substitute portable directory variables for original absolute paths; they are not an independently tested clean installer.", + "limitations": "Short functional compatibility probe only. First sequential Chinese call is not warmed. No accuracy, production capacity, or historical-benchmark equivalence claim." +} diff --git a/docs/deployment_matrix.md b/docs/deployment_matrix.md index 0f70fb7b1..38891caf2 100644 --- a/docs/deployment_matrix.md +++ b/docs/deployment_matrix.md @@ -14,7 +14,7 @@ Use this page to choose the shortest deployment path for a product, demo, benchm | Kubernetes API | Internal speech API for cluster services | [Kubernetes template](../examples/openai_api/kubernetes/) | Starts as private `ClusterIP`; add auth, TLS, network policy, and GPU scheduling before broader exposure. | | Runtime WebSocket service | Live captions, meetings, call-center streams | [Runtime service docs](../runtime/readme.md) | Use when partial results, endpointing, or long-lived audio streams matter. | | ONNX/C++ runtime | High-concurrency CPU services or embedded realtime ASR | [ONNX runtime docs](../runtime/onnxruntime/readme.md) | Keep this path when latency/concurrency is already proven; add text post-processing for fixed business terms before moving to GPU LLMs. | -| vLLM acceleration | Higher-throughput LLM-based ASR with Fun-ASR-Nano | [vLLM guide](./vllm_guide.md) | Use for LLM decoder throughput; does not apply to non-autoregressive Paraformer. | +| vLLM acceleration | Native file transcription or split-engine LLM decoding with Fun-ASR-Nano | [Official native validation](./vllm_official_native_validation.md); [split-engine guide](./vllm_guide.md) | Native: official a4362c94 local snapshot, vLLM 0.27.1, existing-environment functional smoke only, not clean-install or capacity validation. Does not apply to non-autoregressive Paraformer. | | MOSS-Transcribe-Diarize | Long-form multi-speaker transcription with timestamps and speaker labels | [Third-party MOSS deployment guide](./moss_transcribe_diarize.md) | OpenMOSS Apache-2.0 model integrated with FunASR `AutoModel`; choose local HF (`backend="hf"`), vLLM (`backend="vllm"`), or SGLang Omni (`backend="sglang"`). The model remains published and maintained by OpenMOSS. | | MCP server | Claude/Cursor/desktop agent speech tools | [MCP example](../examples/mcp_server/) | Good when the ASR result should be exposed as a local tool. | | Subtitle generator | SRT/VTT from long audio or video | [Subtitle example](../examples/subtitle/) | Use verbose segments and speaker labels when readability matters. | diff --git a/docs/deployment_matrix_zh.md b/docs/deployment_matrix_zh.md index a9970f0e5..cc134e1de 100644 --- a/docs/deployment_matrix_zh.md +++ b/docs/deployment_matrix_zh.md @@ -14,7 +14,7 @@ | Kubernetes API | 集群内私有语音 API | [Kubernetes 模板](../examples/openai_api/kubernetes/README_zh.md) | 默认私有 `ClusterIP`;对外开放前补齐鉴权、TLS、网络策略和 GPU 调度。 | | Runtime WebSocket 服务 | 实时字幕、会议、客服流式音频 | [Runtime 服务文档](../runtime/readme_cn.md) | 需要中间结果、断句或长连接音频流时选择。 | | ONNX/C++ Runtime | 高并发 CPU 服务或嵌入式实时 ASR | [ONNX Runtime 文档](../runtime/onnxruntime/readme.md) | 如果延迟和并发已经验证,不要轻易替换;固定业务词优先做文本后处理。 | -| vLLM 加速 | Fun-ASR-Nano 等 LLM-based ASR 高吞吐 | [vLLM 指南](./vllm_guide.md) | 适合 LLM 解码吞吐;不适用于非自回归 Paraformer。 | +| vLLM 加速 | Fun-ASR-Nano 原生文件转写或 split-engine LLM 解码 | [官方原生验证](./vllm_official_native_validation_zh.md);[split-engine 指南](./vllm_guide_zh.md) | 原生路径固定官方 a4362c94 本地快照、vLLM 0.27.1,仅为既有环境功能验证,不是全新安装或容量验证;不适用于非自回归 Paraformer。 | | MOSS-Transcribe-Diarize | 长音频多人转写、时间戳和说话人标签 | [第三方 MOSS 部署指南](./moss_transcribe_diarize.md) | OpenMOSS Apache-2.0 模型,已接入 FunASR `AutoModel`;可选本地 HF(`backend="hf"`)、vLLM(`backend="vllm"`)或 SGLang Omni(`backend="sglang"`)。模型仍由 OpenMOSS 发布和维护。 | | MCP 服务 | Claude/Cursor/桌面 Agent 语音工具 | [MCP 示例](../examples/mcp_server/) | 适合把 ASR 结果暴露成一个本地工具。 | | 字幕生成 | 从长音频或视频生成 SRT/VTT | [字幕示例](../examples/subtitle/) | 需要可读性时使用 verbose segments 和说话人标签。 | diff --git a/docs/index.rst b/docs/index.rst index d13b8a2cb..95ddcb99e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,7 +45,9 @@ entry to these guides. Source Markdown remains in this repository. ./moss_transcribe_diarize_zh.md vllm_guide vllm_guide_zh - vllm_native_funasr_validation + vllm_official_native_validation + vllm_official_native_validation_zh + Historical community vLLM validation (2026-08-13) .. toctree:: :maxdepth: 1 diff --git a/docs/vllm_official_native_validation.md b/docs/vllm_official_native_validation.md new file mode 100644 index 000000000..d9eb1fe6d --- /dev/null +++ b/docs/vllm_official_native_validation.md @@ -0,0 +1,250 @@ +# Official native vLLM validation: Fun-ASR-Nano + +[中文](vllm_official_native_validation_zh.md) + +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](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-vllm/tree/a4362c943d48951f98ca2a62181cc028970270c5), +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](vllm_guide.md). + +The [2026-08-13 historical community validation](vllm_native_funasr_validation.md) +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](https://github.com/vllm-project/vllm/pull/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. + +```sh +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: + +```sh +"$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: + +```sh +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](benchmark/vllm_official_native_20260907.json) +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: + +```sh +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: + +```sh +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: + +```sh +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](deployment_matrix.md) and +[security boundaries](../examples/openai_api/SECURITY.md). + diff --git a/docs/vllm_official_native_validation_zh.md b/docs/vllm_official_native_validation_zh.md new file mode 100644 index 000000000..546e45c06 --- /dev/null +++ b/docs/vllm_official_native_validation_zh.md @@ -0,0 +1,231 @@ +# 官方 checkpoint 原生 vLLM 验证记录:Fun-ASR-Nano + +[English](vllm_official_native_validation.md) + +验证日期:**2026-09-07,Asia/Shanghai**,原始 HTTP Date 头对应 2026-09-06 UTC。 +本记录独立验证官方 +[FunAudioLLM/Fun-ASR-Nano-2512-vllm 快照](https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-vllm/tree/a4362c943d48951f98ca2a62181cc028970270c5), +不可变 revision 为 `a4362c943d48951f98ca2a62181cc028970270c5`。 +这是**模型 revision,不是根目录 FunASR Python 包版本**。本次使用 vLLM 原生 +`FunASRForConditionalGeneration`,不经过 FunASR AutoModel,也不是 +[split-engine 解码器路径](vllm_guide_zh.md)。 + +[2026-08-13 社区 checkpoint 历史记录](vllm_native_funasr_validation.md) +及其 `allendou/Fun-ASR-Nano-2512-vllm@e718b36e` benchmark 保持原样。 +本次不是给旧数据换模型名称,也不说明官方模型更快。 + +## 验证范围与环境 + +8 个已记录 HTTP 请求均返回 200:健康检查、模型列表、中英日单请求转写、 +中文热词请求,以及两个并发请求。全部 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 可复现本次结果。 + +[vLLM #54944](https://github.com/vllm-project/vllm/pull/54944) 于 2026-09-05 +合并,merge commit 为 `e473e9036f979d546830aece9855027049faf0ba`。 +它更新 supported-model 文档及测试 registry 的官方 checkpoint 引用, +没有修改推理实现。2026-09-07 审计时 main 已使用官方引用, +但 v0.28.0 仍引用社区产物。**已合并不等于已发布**。 +本次也没有验证 main 或 v0.28.0,更不覆盖上游测试 registry 独立的 +Transformers 版本约束。 + +## 准备固定的本地快照 + +以下命令将实际验证流程中的私有绝对路径替换成可移植变量。 +请把 `VLLM_PYTHON` 指向已准备好且符合上表的环境; +示例 `.venv` 路径不代表本次新建过虚拟环境。 +使用全新的隔离验证目录并保留下载 manifest。 +可使用已有 Hub 认证,不要输出凭据。 + +```sh +export VLLM_PYTHON="$PWD/.venv/bin/python" +export VALIDATION_DIR="$PWD/.official-native-validation" +export MODEL_DIR="$VALIDATION_DIR/official-model/a4362c943d48951f98ca2a62181cc028970270c5" +``` + +在与实测相同的 native-import 上下文检查版本: + +```sh +"$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 +``` + +按不可变 revision 下载全部 23 个文件,先校验 Hub 元数据再启动。 +此脚本不执行下载内容中的转换脚本,也不修改快照: + +```sh +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 与音频摘要 + +音频均为单声道 48 kHz MP3。ffprobe 测得容器时长分别为 +**中文 5.616 s、英文 7.176 s、日文 7.224 s**。 +服务 usage 计数将其向上取整为 6/8/8 秒;取整值不是文件实测时长。 + +| 文件 | 字节数 | SHA256 | +| --- | --- | --- | +| `example/en.mp3` | 57441 | `f10378336a4e584f3f63799e62f99d5add3c2a401b51d3abe7d3a3a82f255ada` | +| `example/ja.mp3` | 57837 | `496dbc43b289e1d0d0cb916df9737450bca56acd8aaca046a7a2472363b1be53` | +| `example/zh.mp3` | 44973 | `0e64de19e4ff9a02e682955c9112f32d2317cfdbb5bc2f3504664044c993f195` | +| `model.safetensors` | 1970899072 | `96dfbec48282dd24d3334369a01e9e909f321ee39a1b0003c528c5379f68c1a6` | + +[可复现元数据](benchmark/vllm_official_native_20260907.json) 包含全部 +23 个文件的大小、SHA256、上游 Git/LFS 摘要、包版本、精确 HTTP 字段、 +原始响应摘要及未取整耗时。公开内容省略私有主机路径、GPU 标识和凭据。 +其中原始 `/v1/models` 响应的本地 root 保留在私有证据中; +其摘要对应原始字节,不是脱敏后的替代响应。 + +## 离线启动与 loopback + +先确认 GPU 0 空闲、loopback 端口 57185 未被占用。如需换端口, +须同步更改所有请求;本记录只使用了 57185。在准备环境的 shell 中以前台启动: + +```sh +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 +``` + +实际服务加载经过验证的**本地快照**,Hub 与 Transformers 均为离线模式。 +没有使用 `--trust-remote-code`,也没有让服务从浮动模型 ID 加载。 +下载完成后,启动至健康状态耗时 84.123246 s;这不是启动性能保证。 + +在第二个 shell 中将 `VALIDATION_DIR` 和 `MODEL_DIR` 设为相同绝对路径, +然后等待健康检查成功: + +```sh +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` 应包含 ID `fun-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 耗时: + +```sh +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=开放时间,开放时间,开放时间`:开放时间早上九点至下午五点。 + +保留基线误识别结果。重复热词只证明此样本输出发生改变, +不能直接推广为通用热词策略或准确率保证。 + +| 已记录请求 | HTTP | 客户端 wall time(秒) | +| --- | --- | --- | +| 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 | + +首次中文请求**未经转写预热**:它是健康检查和模型列表之后的第一个转写请求。 +后续请求复用同一引擎。时间包含本地 HTTP 与解码,不含模型下载和服务启动; +每项只有一次观测,不是延迟分布。 + +四个顺序转写请求之后,双 worker 的 `ThreadPoolExecutor` 同时发出相同的 +英文和日文 multipart 请求,均返回 200,文本与顺序请求一致。 +两请求总 wall time 为 **0.9112209342420101 s(0.911 s)**, +包含 executor 建立与等待两项完成。这只是**两个请求的并发功能探针**, +不是吞吐量、生产容量或准确率研究,也不是与社区历史 1.123 s 探针的性能对比。 + +## Harness 边界与清理 + +首次 harness 尝试在**创建服务进程之前**被包清单 guard 拦截。 +准备阶段导入 native vLLM,使 setuptools 的 vendored 包进入 `sys.path`, +原始 serve 阶段却使用不同导入上下文比较包清单。 +统一导入上下文后差异为空,原始失败和修正均已保留。 +早期失败状态字段曾误写为服务已启动,进程证据与修正记录确认当时没有启动。 +没有修改依赖或模型代码;之后首次实际启动服务即完成 8 请求 smoke。 + +有时限的 harness 终止了自己创建的进程组,并等待服务及子进程退出。 +服务退出码为 0、端口关闭、GPU 无计算进程,包与 native 源文件摘要不变。 +独立检查再次确认全部 23 个原始文件及备份摘要、8 个原始响应、模型身份和清理状态。 +手工复现后请停止前台服务,确认其 worker 与端口均已退出,不要终止无关 GPU 任务。 + +## 部署边界 + +这里只覆盖 request/response `/v1/audio/transcriptions`,没有验证 +`/v1/realtime` 实时流式会话。长音频、说话人分离、时间戳准确率、 +其他 GPU、持续负载与生产容量均不在范围内。 +不能由原生服务推导 FunASR SDK 或包发布已通过验证。 + +worker 保持绑定 `127.0.0.1`。对外提供 API 前,由网关完成认证、 +TLS、限流、音频大小/时长限制,隔离上传文件并落实保留策略。 +网关不在本次 smoke 范围内。参见[部署矩阵](deployment_matrix_zh.md)和 +[服务安全边界](../examples/openai_api/SECURITY_zh.md)。 + diff --git a/web-pages/product-site/assets/css/site.css b/web-pages/product-site/assets/css/site.css index d322a787d..b6d50ce2f 100644 --- a/web-pages/product-site/assets/css/site.css +++ b/web-pages/product-site/assets/css/site.css @@ -1024,6 +1024,7 @@ td a { .limitation-callout h2 { max-width: 940px; margin: 0; + overflow-wrap: anywhere; font-size: 28px; line-height: 1.35; } @@ -1033,6 +1034,14 @@ td a { color: var(--muted); } +.limitation-summary { + max-width: 78ch; + margin: 14px 0 0; + overflow-wrap: anywhere; + font-size: 16px; + line-height: 1.65; +} + .evidence-list { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 28px; diff --git a/web-pages/product-site/data/deployments.json b/web-pages/product-site/data/deployments.json index b13feb45d..480f5bd52 100644 --- a/web-pages/product-site/data/deployments.json +++ b/web-pages/product-site/data/deployments.json @@ -191,40 +191,89 @@ "workloads": ["batch", "private-api"], "hardware": ["nvidia-gpu", "kubernetes"], "priorities": ["throughput", "latency"], - "models": ["Fun-ASR-Nano-2512 (community vLLM conversion)"], + "models": [ + "FunAudioLLM/Fun-ASR-Nano-2512-vllm (official checkpoint)" + ], "operating_systems": ["Linux"], "interfaces": ["OpenAI-compatible HTTP"], - "tested": {"funasr": "Fun-ASR-Nano-2512 conversion@e718b36e", "runtime": "vLLM 0.27.1+cu129 / Torch 2.13.0+cu129", "verified": "2026-08-13"}, + "tested": { + "funasr": "Official model revision a4362c943d48951f98ca2a62181cc028970270c5; not a FunASR package version", + "runtime": "vLLM 0.27.1+cu129 / Torch 2.13.0+cu129 / Transformers 5.15.0 / CUDA 12.9 / Python 3.12.3", + "verified": "2026-09-07" + }, "commands": { "install": [ - "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 venv --python 3.12 .venv && uv pip install --python .venv/bin/python --torch-backend=auto \"vllm[audio] @ file://$PWD/vllm-0.27.1+cu129-cp38-abi3-manylinux_2_28_x86_64.whl\"" + "export VLLM_PYTHON=\"$PWD/.venv/bin/python\"\nexport VALIDATION_DIR=\"$PWD/.official-native-validation\"\nexport MODEL_DIR=\"$VALIDATION_DIR/official-model/a4362c943d48951f98ca2a62181cc028970270c5\"", + "\"$VLLM_PYTHON\" - <<'PY'\nimport importlib.metadata as metadata\nimport torch\nimport vllm.model_executor.models.funasr\n\nfor name, expected in {\"vllm\": \"0.27.1+cu129\", \"torch\": \"2.13.0+cu129\",\n \"transformers\": \"5.15.0\"}.items():\n assert metadata.version(name) == expected, (name, metadata.version(name))\nassert torch.version.cuda == \"12.9\" and torch.cuda.is_available()\nPY", + "HF_HUB_DISABLE_TELEMETRY=1 HF_XET_CACHE=\"$VALIDATION_DIR/isolated-xet-cache\" \"$VLLM_PYTHON\" - <<'PY'\nimport hashlib\nimport json\nimport os\nfrom pathlib import Path\nfrom huggingface_hub import HfApi, snapshot_download\n\nmodel_id = \"FunAudioLLM/Fun-ASR-Nano-2512-vllm\"\nrevision = \"a4362c943d48951f98ca2a62181cc028970270c5\"\nroot = Path(os.environ[\"MODEL_DIR\"])\ninfo = HfApi().model_info(model_id, revision=revision, files_metadata=True)\nassert info.sha == revision and len(info.siblings) == 23\nsnapshot_download(model_id, revision=revision, local_dir=str(root),\n cache_dir=str(Path(os.environ[\"VALIDATION_DIR\"]) / \"isolated-hf-cache\"),\n max_workers=4)\nfiles = []\nfor item in info.siblings:\n content = (root / item.rfilename).read_bytes()\n digest = hashlib.sha256(content).hexdigest()\n assert len(content) == item.size, item.rfilename\n if item.lfs:\n assert digest == item.lfs.sha256, item.rfilename\n else:\n assert hashlib.sha1(f\"blob {len(content)}\\0\".encode() + content).hexdigest() == item.blob_id\n files.append({\"path\": item.rfilename, \"size\": len(content), \"sha256\": digest})\nassert json.loads((root / \"config.json\").read_text())[\"architectures\"] == [\"FunASRForConditionalGeneration\"]\n(root.parent / \"download-manifest.json\").write_text(json.dumps(files, indent=2) + \"\\n\")\nPY" ], "launch": [ - "CUDA_VISIBLE_DEVICES=0 .venv/bin/vllm serve allendou/Fun-ASR-Nano-2512-vllm --revision e718b36e2578203ec893e9b488239225f8d668e2 --served-model-name fun-asr-nano --host 127.0.0.1 --port 8899 --dtype float32 --gpu-memory-utilization 0.40 --enforce-eager" + "CUDA_VISIBLE_DEVICES=0 PYTHONDONTWRITEBYTECODE=1 \\\nHF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \\\nHF_HUB_DISABLE_IMPLICIT_TOKEN=1 HF_HUB_DISABLE_TELEMETRY=1 VLLM_NO_USAGE_STATS=1 \\\nVLLM_CACHE_ROOT=\"$VALIDATION_DIR/runtime-cache\" TRITON_CACHE_DIR=\"$VALIDATION_DIR/triton-cache\" \\\n\"$VLLM_PYTHON\" -B -m vllm.entrypoints.openai.api_server \\\n --model \"$MODEL_DIR\" --served-model-name fun-asr-nano-official-a4362c94 \\\n --host 127.0.0.1 --port 57185 --dtype float32 \\\n --gpu-memory-utilization 0.40 --enforce-eager" ], "health": [ - "curl -fsS http://127.0.0.1:8899/health", - "curl -fsS http://127.0.0.1:8899/v1/models" + "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" ], "smoke": [ - "curl -fL https://huggingface.co/allendou/Fun-ASR-Nano-2512-vllm/resolve/e718b36e2578203ec893e9b488239225f8d668e2/example/zh.mp3 -o zh.mp3 && echo \"0e64de19e4ff9a02e682955c9112f32d2317cfdbb5bc2f3504664044c993f195 zh.mp3\" | sha256sum -c -", - "curl -fsS http://127.0.0.1:8899/v1/audio/transcriptions -F file=@zh.mp3 -F model=fun-asr-nano -F language=zh -F response_format=json", - "curl -fsS http://127.0.0.1:8899/v1/audio/transcriptions -F file=@zh.mp3 -F model=fun-asr-nano -F language=zh -F 'hotwords=开放时间,开放时间,开放时间' -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 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" ] }, "evidence": [ - {"label": "H100 native FunASR validation record", "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_native_funasr_validation.md"}, - {"label": "official checkpoint registered upstream (merged PR #54944)", "url": "https://github.com/vllm-project/vllm/pull/54944"}, - {"label": "official checkpoint at a4362c94 (separate from historical tests below)", "url": "https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-vllm/tree/a4362c943d48951f98ca2a62181cc028970270c5"}, - {"label": "vLLM 0.27.1 release and CUDA 12.9 wheel", "url": "https://github.com/vllm-project/vllm/releases/tag/v0.27.1"}, - {"label": "native FunASR model integration", "url": "https://github.com/vllm-project/vllm/pull/33247"}, - {"label": "native FunASR hotword support", "url": "https://github.com/vllm-project/vllm/pull/39674"}, - {"label": "FunASR initialization fix", "url": "https://github.com/vllm-project/vllm/pull/44215"}, - {"label": "validated community conversion at e718b36e", "url": "https://huggingface.co/allendou/Fun-ASR-Nano-2512-vllm/tree/e718b36e2578203ec893e9b488239225f8d668e2"}, - {"label": "official FunASR split-engine guide", "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_guide.md"}, - {"label": "FunASR deployment matrix", "url": "https://github.com/modelscope/FunASR/blob/main/docs/deployment_matrix.md"} + { + "label": "2026-09-07 official checkpoint native HTTP functional validation", + "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_official_native_validation.md" + }, + { + "label": "2026-09-07 官方 checkpoint 原生 HTTP 功能验证(中文)", + "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_official_native_validation_zh.md" + }, + { + "label": "23-file manifest and 8 HTTP response digests (private paths redacted)", + "url": "https://github.com/modelscope/FunASR/blob/main/docs/benchmark/vllm_official_native_20260907.json" + }, + { + "label": "2026-08-13 historical community checkpoint validation (unchanged)", + "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_native_funasr_validation.md" + }, + { + "label": "Official reference in upstream docs/test registry; merged main, not a release claim (#54944)", + "url": "https://github.com/vllm-project/vllm/pull/54944" + }, + { + "label": "Official checkpoint at immutable a4362c94 (validated separately on 2026-09-07)", + "url": "https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-vllm/tree/a4362c943d48951f98ca2a62181cc028970270c5" + }, + { + "label": "vLLM 0.27.1 release and CUDA 12.9 wheel", + "url": "https://github.com/vllm-project/vllm/releases/tag/v0.27.1" + }, + { + "label": "native FunASR model integration", + "url": "https://github.com/vllm-project/vllm/pull/33247" + }, + { + "label": "native FunASR hotword support", + "url": "https://github.com/vllm-project/vllm/pull/39674" + }, + { + "label": "FunASR initialization fix", + "url": "https://github.com/vllm-project/vllm/pull/44215" + }, + { + "label": "validated community conversion at e718b36e", + "url": "https://huggingface.co/allendou/Fun-ASR-Nano-2512-vllm/tree/e718b36e2578203ec893e9b488239225f8d668e2" + }, + { + "label": "official FunASR split-engine guide", + "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_guide.md" + }, + { + "label": "FunASR deployment matrix", + "url": "https://github.com/modelscope/FunASR/blob/main/docs/deployment_matrix.md" + } ], "benchmarks": [ { @@ -244,27 +293,67 @@ "translations": { "zh": { "name": "vLLM 原生 FunASR 服务", - "summary": "通过 vLLM 内置的 FunASR 架构,在 NVIDIA GPU 上提供兼容 OpenAI 的多语言转写与热词接口。", - "fit": ["需要标准 /v1/audio/transcriptions 接口", "Fun-ASR-Nano 批量或并发转写", "已有 NVIDIA GPU 和 Linux 服务环境"], - "not_fit": ["未经重新验证就直接替换下方固定版本 checkpoint", "纯 CPU 或边缘设备", "尚未完成显存、并发与业务音频压测的公网服务"], + "summary": "使用官方 FunAudioLLM/Fun-ASR-Nano-2512-vllm 固定快照,在 vLLM 0.27.1 上提供中英日转写与热词接口;已通过单张 H100 的独立功能验证。", + "fit": [ + "需要标准 /v1/audio/transcriptions 接口", + "Fun-ASR-Nano 批量或并发转写", + "已有 NVIDIA GPU 和 Linux 服务环境" + ], + "not_fit": [ + "未经重新验证就直接替换下方固定版本 checkpoint", + "纯 CPU 或边缘设备", + "尚未完成显存、并发与业务音频压测的公网服务" + ], "selection_reason": "vLLM 已原生注册 FunASR 架构,并提供 OpenAI 兼容转写、显存调度、并发和热词参数。", - "primary_limitation": "下方历史实测与命令使用社区转换 checkpoint。官方 FunAudioLLM/Fun-ASR-Nano-2512-vllm 已被 vLLM 上游登记(PR #54944);这不等于本页旧测试已覆盖新权重,切换时须重新验证。历史数据仅覆盖单张 H100,目标 GPU、音频、语言、热词与流量均须复测。", + "primary_limitation": "当前命令对应官方 a4362c943d48951f98ca2a62181cc028970270c5,2026-09-07 在既有 vLLM 0.27.1+cu129 / Torch 2.13.0+cu129 / Transformers 5.15.0 / CUDA 12.9 环境验证,不是全新安装验证。准备步骤只检查版本和下载本地快照,不重新安装依赖。8 个 HTTP 请求均为功能验证;首次中文未预热,两请求 0.911 s 不代表容量。下方 2026-08-13 benchmark 仍是社区 checkpoint 的历史数据,没有重命名或与新结果比较。#54944 仅更新 main 的文档/测试 registry;审计时 v0.28.0 仍引用社区产物,不代表官方引用已发布。", "status_label": "社区验证", - "operations": ["固定 vLLM wheel、Torch、CUDA、模型 revision 与音频依赖", "等待 /health 就绪并完成预热后再采集容量数据", "记录队列等待、最终结果延迟、显存和热词命中率"], - "security": ["在 API 网关实现认证、TLS、限流和音频大小/时长限制", "worker 仅绑定内网地址,不直接暴露公网", "校验 wheel、模型 revision 和示例音频摘要,隔离模型缓存与上传临时目录"], - "troubleshooting": ["音频返回 400 时确认安装了 vllm[audio]", "启动时 KV cache 不足则提高显存利用率或在业务允许时降低 max-model-len", "非英文请求必须显式传 language;热词用逗号分隔并以真实业务样本验证偏置强度"] + "operations": [ + "固定官方模型 revision 与本地文件摘要,以及 vLLM 0.27.1+cu129、Torch 2.13.0+cu129、Transformers 5.15.0、CUDA 12.9;不要将模型 revision 当成 FunASR 包版本", + "等待 /health 就绪并完成预热后再采集容量数据", + "记录队列等待、最终结果延迟、显存和热词命中率" + ], + "security": [ + "在 API 网关实现认证、TLS、限流和音频大小/时长限制", + "worker 仅绑定 127.0.0.1;网关未在本次 smoke 中验证,不直接暴露公网", + "校验 wheel、模型 revision 和示例音频摘要,隔离模型缓存与上传临时目录" + ], + "troubleshooting": [ + "音频返回 400 时确认安装了 vllm[audio]", + "启动时 KV cache 不足则提高显存利用率或在业务允许时降低 max-model-len", + "非英文请求必须显式传 language;热词用逗号分隔并以真实业务样本验证偏置强度" + ] }, "en": { "name": "Native FunASR on vLLM", - "summary": "Use vLLM's built-in FunASR architecture for multilingual and hotword-aware transcription through an OpenAI-compatible API on NVIDIA GPUs.", - "fit": ["A standard /v1/audio/transcriptions endpoint", "Concurrent or batch Fun-ASR-Nano transcription", "An NVIDIA GPU Linux serving environment"], - "not_fit": ["Replacing the pinned checkpoint below without revalidation", "CPU-only or edge devices", "Internet-facing service before memory, concurrency, and workload tests"], + "summary": "Serve the pinned official FunAudioLLM/Fun-ASR-Nano-2512-vllm snapshot on vLLM 0.27.1; Chinese, English, Japanese and hotwords passed a separate single-H100 functional smoke.", + "fit": [ + "A standard /v1/audio/transcriptions endpoint", + "Concurrent or batch Fun-ASR-Nano transcription", + "An NVIDIA GPU Linux serving environment" + ], + "not_fit": [ + "Replacing the pinned checkpoint below without revalidation", + "CPU-only or edge devices", + "Internet-facing service before memory, concurrency, and workload tests" + ], "selection_reason": "vLLM natively registers the FunASR architecture and provides OpenAI-compatible transcription, memory scheduling, concurrency, and hotword parameters.", - "primary_limitation": "The historical tests and commands below use a community-converted checkpoint. The official FunAudioLLM/Fun-ASR-Nano-2512-vllm checkpoint is now registered upstream in vLLM PR #54944; this does not extend the old test evidence to new weights. Revalidate any switch and the target GPU, audio, languages, hotwords, and traffic; historical measurements cover one H100 only.", + "primary_limitation": "Current commands use official FunAudioLLM/Fun-ASR-Nano-2512-vllm@a4362c943d48951f98ca2a62181cc028970270c5, tested 2026-09-07 in an existing environment: vLLM 0.27.1+cu129 / Torch 2.13.0+cu129 / Transformers 5.15.0 / CUDA 12.9. This is not a clean installation validation; preparation only checks versions and downloads a local snapshot. Eight HTTP requests establish functional compatibility only; first Chinese was not warmed, and the 0.911 s two-request probe is not capacity evidence. The 2026-08-13 benchmark below remains a historical community-converted checkpoint result, neither relabeled nor compared with the new run. #54944 updates main docs/test registry only; v0.28.0 still referenced the community artifact at audit time, not a claim of released official references.", "status_label": "Community verified", - "operations": ["Pin the vLLM wheel, Torch, CUDA, model revision, and audio dependencies", "Wait for /health and warm the model before measuring capacity", "Measure queue time, final-result latency, memory, and hotword hit rate"], - "security": ["Put authentication, TLS, rate limits, and audio size/duration limits at the gateway", "Bind workers to a private address instead of exposing them directly", "Verify wheel, model revision, and sample digests; isolate model cache and temporary uploads"], - "troubleshooting": ["For audio HTTP 400 responses, confirm vllm[audio] is installed", "If startup reports insufficient KV cache, raise GPU utilization or lower max-model-len only when the workload permits", "Pass language explicitly for non-English audio; comma-separate hotwords and validate bias strength on production samples"] + "operations": [ + "Pin the official model revision and local file digests plus vLLM 0.27.1+cu129, Torch 2.13.0+cu129, Transformers 5.15.0 and CUDA 12.9; a model revision is not a FunASR package version", + "Wait for /health and warm the model before measuring capacity", + "Measure queue time, final-result latency, memory, and hotword hit rate" + ], + "security": [ + "Put authentication, TLS, rate limits, and audio size/duration limits at the gateway", + "Bind the worker to 127.0.0.1; the gateway was not validated by this smoke, and the worker must not be exposed directly", + "Verify wheel, model revision, and sample digests; isolate model cache and temporary uploads" + ], + "troubleshooting": [ + "For audio HTTP 400 responses, confirm vllm[audio] is installed", + "If startup reports insufficient KV cache, raise GPU utilization or lower max-model-len only when the workload permits", + "Pass language explicitly for non-English audio; comma-separate hotwords and validate bias strength on production samples" + ] } } }, diff --git a/web-pages/product-site/data/documentation.json b/web-pages/product-site/data/documentation.json index e9be41718..55a44fd71 100644 --- a/web-pages/product-site/data/documentation.json +++ b/web-pages/product-site/data/documentation.json @@ -25,7 +25,8 @@ {"slug": "kubernetes", "group": "deploy", "zh": "Kubernetes 部署", "en": "Kubernetes deployment", "source_zh": "examples/openai_api/kubernetes/README_zh.md", "source_en": "examples/openai_api/kubernetes/README.md"}, {"slug": "deployment-matrix", "group": "deploy", "zh": "运行时与部署矩阵", "en": "Runtime & deployment matrix", "source_zh": "docs/deployment_matrix_zh.md", "source_en": "docs/deployment_matrix.md"}, {"slug": "vllm", "group": "deploy", "zh": "vLLM split-engine 推理", "en": "vLLM split-engine inference", "source_zh": "docs/vllm_guide_zh.md", "source_en": "docs/vllm_guide.md"}, - {"slug": "native-vllm", "group": "deploy", "zh": "原生 vLLM 验证记录", "en": "Native vLLM validation record", "source_zh": "docs/vllm_native_funasr_validation.md", "source_en": "docs/vllm_native_funasr_validation.md"}, + {"slug": "official-native-vllm", "group": "deploy", "zh": "官方原生 vLLM 功能验证(2026-09-07)", "en": "Official native vLLM functional validation (2026-09-07)", "source_zh": "docs/vllm_official_native_validation_zh.md", "source_en": "docs/vllm_official_native_validation.md"}, + {"slug": "native-vllm", "group": "deploy", "zh": "社区原生 vLLM 历史记录(2026-08-13)", "en": "Historical community native vLLM record (2026-08-13)", "source_zh": "docs/vllm_native_funasr_validation.md", "source_en": "docs/vllm_native_funasr_validation.md"}, {"slug": "troubleshooting", "group": "operate", "zh": "安装与推理故障排查", "en": "Troubleshooting", "source_zh": "docs/troubleshooting_zh.md", "source_en": "docs/troubleshooting.md"}, {"slug": "security", "group": "operate", "zh": "服务安全边界", "en": "Service security boundaries", "source_zh": "examples/openai_api/SECURITY_zh.md", "source_en": "examples/openai_api/SECURITY.md"}, {"slug": "benchmark-method", "group": "operate", "zh": "可复现性能评测", "en": "Reproducible performance tests", "source_zh": "docs/benchmark/rtf_reproducibility.md", "source_en": "docs/benchmark/rtf_reproducibility.md"}, diff --git a/web-pages/product-site/templates/deploy-detail.html b/web-pages/product-site/templates/deploy-detail.html index 4c741713f..e7f29e188 100644 --- a/web-pages/product-site/templates/deploy-detail.html +++ b/web-pages/product-site/templates/deploy-detail.html @@ -173,8 +173,8 @@
-

{{ '已知限制' if language == 'zh' else 'Known limitation' }}

-

{{ copy.primary_limitation }}

+

{{ '已知限制' if language == 'zh' else 'Known limitations' }}

+

{{ copy.primary_limitation }}

{{ '公开基准仅用于复现起点,不替代目标业务负载测试。' if language == 'zh' else 'Public benchmarks are reproduction starting points, not substitutes for target-workload testing.' }}

diff --git a/web-pages/product-site/tests/browser/product-site.spec.ts b/web-pages/product-site/tests/browser/product-site.spec.ts index 9f6313a4b..6025424c9 100644 --- a/web-pages/product-site/tests/browser/product-site.spec.ts +++ b/web-pages/product-site/tests/browser/product-site.spec.ts @@ -362,6 +362,48 @@ for (const viewport of [ }); } +for (const language of ['zh', 'en']) { + test(`vLLM limitation preserves full revision and wraps on mobile (${language})`, async ({ page }, testInfo) => { + const revision = 'a4362c943d48951f98ca2a62181cc028970270c5'; + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto(`${language === 'en' ? '/en' : ''}/deploy/vllm.html`); + await page.evaluate(() => document.fonts.ready); + + const heading = page.locator('.limitation-callout h2'); + await expect(heading).toHaveText(language === 'en' ? 'Known limitations' : '已知限制'); + const summary = page.locator('.limitation-callout p.limitation-summary'); + await expect(summary).toContainText(revision); + await summary.scrollIntoViewIfNeeded(); + const layout = await summary.evaluate((node, revision) => { + const text = node.firstChild!; + const start = text.textContent!.indexOf(revision); + const range = document.createRange(); + range.setStart(text, start); + range.setEnd(text, start + revision.length); + const bounds = node.getBoundingClientRect(); + return { + overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth, + summaryOverflow: node.scrollWidth - node.clientWidth, + summaryLeft: bounds.left, + summaryRight: bounds.right, + fontSize: Number.parseFloat(getComputedStyle(node).fontSize), + revisionRects: [...range.getClientRects()].map(rect => ({ left: rect.left, right: rect.right, top: rect.top })), + }; + }, revision); + await testInfo.attach('limitation-layout', { body: JSON.stringify(layout), contentType: 'application/json' }); + await page.screenshot({ path: testInfo.outputPath(`vllm-limitation-${language}-mobile.png`), fullPage: true }); + await page.locator('.limitation-callout').screenshot({ path: testInfo.outputPath(`vllm-limitation-${language}-callout.png`) }); + + expect(layout.overflow).toBeLessThanOrEqual(1); + expect(layout.summaryOverflow).toBeLessThanOrEqual(1); + expect(layout.fontSize).toBeLessThanOrEqual(18); + expect(new Set(layout.revisionRects.map(rect => Math.round(rect.top))).size).toBeGreaterThan(1); + expect(layout.revisionRects.every(rect => + rect.left >= layout.summaryLeft - 1 && rect.right <= layout.summaryRight + 1, + )).toBe(true); + }); +} + test('reduced motion disables smooth scrolling', async ({ page }) => { await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto('/en/'); diff --git a/web-pages/product-site/tests/test_documentation.py b/web-pages/product-site/tests/test_documentation.py index 76647e68c..141f70c50 100644 --- a/web-pages/product-site/tests/test_documentation.py +++ b/web-pages/product-site/tests/test_documentation.py @@ -31,6 +31,41 @@ def test_bilingual_docs_have_navigation_and_real_source_content(output): assert moss.select_one('[data-source-link]')['href'].endswith('.md') +def test_official_native_record_has_local_language_links_and_search(output): + for language, prefix, suffix in (('zh', '', '_zh'), ('en', 'en/', '')): + route = '/' + prefix + 'docs/official-native-vllm.html' + page = BeautifulSoup((output / route.lstrip('/')).read_text(), 'html.parser') + assert page.select_one('[data-source-link]')['href'].endswith( + f'/docs/vllm_official_native_validation{suffix}.md') + links = {link['href'] for link in page.select('.docs-article a[href]')} + assert '/' + prefix + 'docs/native-vllm.html' in links + assert '/' + prefix + 'docs/deployment-matrix.html' in links + assert any('/docs/benchmark/vllm_official_native_20260907.json' in link for link in links) + rows = json.loads((output / f'search-{language}.json').read_text()) + assert any(row['url'] == route and '2026-09-07' in row['title'] for row in rows) + deploy = BeautifulSoup((output / prefix / 'deploy/vllm.html').read_text(), 'html.parser') + commands = deploy.select_one('[data-section="commands"]').get_text() + assert 'FunAudioLLM/Fun-ASR-Nano-2512-vllm' in commands + assert 'allendou/' not in commands + + +def test_deployment_limitations_use_short_heading_and_preserve_full_body(output): + entries = json.loads((SITE_ROOT / 'data/deployments.json').read_text())['deployments'] + for language in ('zh', 'en'): + for entry in entries: + route = entry['routes'][language].lstrip('/') + page = BeautifulSoup((output / route).read_text(), 'html.parser') + callout = page.select_one('[data-section="limitations"] .limitation-callout') + heading = callout.select_one('h2') + expected = entry['translations'][language]['primary_limitation'] + assert heading.get_text() == ('已知限制' if language == 'zh' else 'Known limitations') + assert heading.get_text() != expected + summary = callout.select_one('p.limitation-summary') + assert summary is not None, (language, entry['id']) + assert summary.get_text() == expected, (language, entry['id']) + assert not callout.select('.eyebrow') + + def test_search_index_is_bilingual_and_links_to_emitted_pages(output): for language in ('zh', 'en'): rows = json.loads((output / f'search-{language}.json').read_text()) diff --git a/web-pages/product-site/tests/test_official_vllm_docs.py b/web-pages/product-site/tests/test_official_vllm_docs.py new file mode 100644 index 000000000..6ffef575d --- /dev/null +++ b/web-pages/product-site/tests/test_official_vllm_docs.py @@ -0,0 +1,94 @@ +"""Model-free contracts for the separate official native serving record.""" + +import hashlib +import json +from pathlib import Path +import ast +import subprocess + +import pytest + +SITE = Path(__file__).resolve().parents[1] +ROOT = SITE.parents[1] +REVISION = 'a4362c943d48951f98ca2a62181cc028970270c5' +MANIFEST = ROOT / 'docs/benchmark/vllm_official_native_20260907.json' + + +def test_dated_community_record_is_unchanged(): + path = ROOT / 'docs/vllm_native_funasr_validation.md' + assert hashlib.sha256(path.read_bytes()).hexdigest() == ( + 'e8f348b0f473769e1ece224d881edcd03ce897e99bcd17918068ab97fbeb1583') + + +def test_official_catalogue_has_two_sources_and_keeps_historical_route(): + pages = json.loads((SITE / 'data/documentation.json').read_text())['pages'] + rows = [row for row in pages if row['slug'] == 'official-native-vllm'] + assert len(rows) == 1 + official = rows[0] + assert official['group'] == 'deploy' + for language, suffix in (('en', ''), ('zh', '_zh')): + path = f'docs/vllm_official_native_validation{suffix}.md' + assert official[f'source_{language}'] == path + assert Path(path).stem in (ROOT / 'docs/index.rst').read_text() + historical = next(row for row in pages if row['slug'] == 'native-vllm') + assert 'historical' in historical['en'].lower() + assert '历史' in historical['zh'] + assert historical['source_en'] == historical['source_zh'] == 'docs/vllm_native_funasr_validation.md' + + +@pytest.mark.parametrize('suffix', ('', '_zh')) +def test_official_record_has_reproducibility_and_scoped_evidence(suffix): + path = ROOT / f'docs/vllm_official_native_validation{suffix}.md' + assert path.is_file(), 'Missing separate official source record' + text = path.read_text() + for marker in (REVISION, 'FunAudioLLM/Fun-ASR-Nano-2512-vllm', + '0.27.1+cu129', '2.13.0+cu129', '5.15.0', '12.9', + 'HF_HUB_OFFLINE=1', 'TRANSFORMERS_OFFLINE=1', + 'snapshot_download', '--model "$MODEL_DIR"', + 'language=zh', 'language=en', 'language=ja', + '0.911', '5.616', '7.176', '7.224', + 'vllm_native_funasr_validation.md', + 'benchmark/vllm_official_native_20260907.json', + '54944', 'v0.28.0', '2026-08-13'): + assert marker in text + for private in ('/cpfs_speech/', '/Users/', 'ind-gpu8'): + assert private not in text + + +def test_official_manifest_preserves_exact_files_and_http_scope(): + assert MANIFEST.is_file(), 'Missing public redacted reproducibility metadata' + record = json.loads(MANIFEST.read_text()) + assert record['revision'] == REVISION + files = {row['path']: row for row in record['files']} + assert len(files) == len(record['files']) == 23 + assert files['model.safetensors']['sha256'] == '96dfbec48282dd24d3334369a01e9e909f321ee39a1b0003c528c5379f68c1a6' + requests = record['http']['requests'] + assert len(requests) == 8 and all(row['status'] == 200 for row in requests) + for row in requests: + if row['body_file'] != 'models.body': + assert hashlib.sha256(row['raw_body_utf8'].encode()).hexdigest() == row['response_sha256'] + assert {row['request_fields']['language'] for row in requests if row['request_fields']} == {'zh', 'en', 'ja'} + assert record['http']['two_request_wall_seconds'] == 0.9112209342420101 + assert record['first_chinese_request_warmed'] is False + assert record['clean_install_validated'] is False + assert record['guard_failure_before_server_spawn'] is True + assert record['cleanup']['all_owned_exited'] is True + for private in ('/cpfs_speech/', '/Users/', 'ind-gpu8', '/tmp/funasr-'): + assert private not in MANIFEST.read_text() + + +def test_portable_commands_match_both_sources_and_parse_without_execution(): + entry = next(row for row in json.loads((SITE / 'data/deployments.json').read_text())['deployments'] + if row['id'] == 'vllm') + record = json.loads(MANIFEST.read_text()) + assert entry['commands'] == record['commands'] + for commands in entry['commands'].values(): + for command in commands: + result = subprocess.run(['bash', '-n'], input=command, text=True, + capture_output=True, timeout=10) + assert result.returncode == 0, result.stderr + if "<<'PY'\n" in command: + ast.parse(command.split("<<'PY'\n", 1)[1].rsplit('\nPY', 1)[0]) + for suffix in ('', '_zh'): + text = (ROOT / f'docs/vllm_official_native_validation{suffix}.md').read_text() + assert command in text diff --git a/web-pages/product-site/tests/test_registry.py b/web-pages/product-site/tests/test_registry.py index b622e8643..3ee979b27 100644 --- a/web-pages/product-site/tests/test_registry.py +++ b/web-pages/product-site/tests/test_registry.py @@ -51,31 +51,41 @@ def test_vllm_contract_tracks_native_funasr_release_and_h100_validation(valid_re assert entry['maturity'] == 'community-verified' assert entry['tested'] == { - 'funasr': 'Fun-ASR-Nano-2512 conversion@e718b36e', - 'runtime': 'vLLM 0.27.1+cu129 / Torch 2.13.0+cu129', - 'verified': '2026-08-13', + 'funasr': 'Official model revision a4362c943d48951f98ca2a62181cc028970270c5; not a FunASR package version', + 'runtime': 'vLLM 0.27.1+cu129 / Torch 2.13.0+cu129 / Transformers 5.15.0 / CUDA 12.9 / Python 3.12.3', + 'verified': '2026-09-07', } - assert entry['models'] == ['Fun-ASR-Nano-2512 (community vLLM conversion)'] + assert entry['models'] == ['FunAudioLLM/Fun-ASR-Nano-2512-vllm (official checkpoint)'] install = '\n'.join(entry['commands']['install']) - assert 'vllm[audio]' in install - assert 'vllm-0.27.1%2Bcu129-cp38-abi3-manylinux_2_28_x86_64.whl' in install - assert 'bf0d52faa2a51e7a01c6856a7a8a2d1307fd0ff711415d34168a67ffac0fa47b' in install + for marker in ('snapshot_download', 'FunAudioLLM/Fun-ASR-Nano-2512-vllm', + 'a4362c943d48951f98ca2a62181cc028970270c5', '5.15.0', + '2.13.0+cu129', '0.27.1+cu129', 'local_dir=', 'cache_dir='): + assert marker in install + assert 'pip install' not in install + assert 'uv venv' not in install launch = '\n'.join(entry['commands']['launch']) for marker in ( - 'vllm serve allendou/Fun-ASR-Nano-2512-vllm', - '--revision e718b36e2578203ec893e9b488239225f8d668e2', - '--served-model-name fun-asr-nano', + '-m vllm.entrypoints.openai.api_server', + '--model "$MODEL_DIR"', + 'HF_HUB_OFFLINE=1', 'TRANSFORMERS_OFFLINE=1', + '--host 127.0.0.1', '--port 57185', + '--served-model-name fun-asr-nano-official-a4362c94', '--dtype float32', '--gpu-memory-utilization 0.40', + '--enforce-eager', ): assert marker in launch + assert 'allendou' not in launch smoke = '\n'.join(entry['commands']['smoke']) assert '/v1/audio/transcriptions' in smoke - assert 'language=zh' in smoke + for language in ('zh', 'en', 'ja'): + assert f'language={language}' in smoke assert 'hotwords=开放时间,开放时间,开放时间' in smoke evidence_urls = {item['url'] for item in entry['evidence']} for url in ( 'https://github.com/modelscope/FunASR/blob/main/docs/vllm_native_funasr_validation.md', + 'https://github.com/modelscope/FunASR/blob/main/docs/vllm_official_native_validation.md', + 'https://github.com/modelscope/FunASR/blob/main/docs/vllm_official_native_validation_zh.md', 'https://github.com/vllm-project/vllm/releases/tag/v0.27.1', 'https://github.com/vllm-project/vllm/pull/33247', 'https://github.com/vllm-project/vllm/pull/39674', @@ -94,7 +104,10 @@ def test_vllm_contract_tracks_native_funasr_release_and_h100_validation(valid_re limitation = entry['translations']['en']['primary_limitation'].lower() assert 'community-converted checkpoint' in limitation assert 'official funaudiollm/fun-asr-nano-2512-vllm' in limitation - assert 'does not extend the old test evidence' in limitation + assert 'existing environment' in limitation + assert 'not a clean installation' in limitation + assert '2026-08-13' in limitation + assert 'v0.28.0' in limitation assert 'https://github.com/vllm-project/vllm/pull/54944' in evidence_urls