Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion web-pages/product-site/data/deployments.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"verified": "2026-07-30",
"verified": "2026-08-04",
"deployments": [
{
"id": "vllm",
Expand Down Expand Up @@ -66,6 +66,86 @@
}
}
},
{
"id": "sensevoice-tensorrt",
"routes": {"zh": "/deploy/sensevoice-tensorrt.html", "en": "/en/deploy/sensevoice-tensorrt.html"},
"maturity": "production-verified",
"selector_rank": 15,
"workloads": ["batch", "private-api"],
"hardware": ["nvidia-gpu", "kubernetes"],
"priorities": ["throughput", "latency"],
"models": ["SenseVoiceSmall"],
"operating_systems": ["Linux"],
"interfaces": ["Triton gRPC/HTTP", "TensorRT plan", "Docker"],
"tested": {"funasr": "main@6408aaa9", "runtime": "TensorRT 10.0.1 / Triton 24.05", "verified": "2026-08-04"},
"commands": {
"install": [
"git clone https://github.com/modelscope/FunASR.git && cd FunASR && git checkout 6408aaa96d54f89db467346010eff3c7d1fc485a",
"pip install -e . \"onnx>=1.16\" soundfile \"tritonclient[grpc]\"",
"python - <<'PY'\nfrom funasr import AutoModel\n\nmodel = AutoModel(model=\"iic/SenseVoiceSmall\", device=\"cuda:0\")\nmodel.export(\n type=\"onnx\",\n quantize=False,\n device=\"cuda:0\",\n output_dir=\"./sensevoice_onnx\",\n max_seq_len=4096,\n)\nPY",
"curl -fL https://huggingface.co/FunAudioLLM/SenseVoiceSmall/resolve/main/chn_jpn_yue_eng_ko_spectok.bpe.model \\\n -o runtime/triton_gpu/model_repo_sense_voice_small/scoring/chn_jpn_yue_eng_ko_spectok.bpe.model \\\n&& echo \"aa87f86064c3730d799ddf7af3c04659151102cba548bce325cf06ba4da4e6a8 runtime/triton_gpu/model_repo_sense_voice_small/scoring/chn_jpn_yue_eng_ko_spectok.bpe.model\" | sha256sum -c -"
],
"launch": [
"python runtime/triton_gpu/scripts/build_sensevoice_tensorrt.py \\\n ./sensevoice_onnx/model.onnx \\\n runtime/triton_gpu/model_repo_sense_voice_small/encoder/1/model.plan \\\n --precision fp16 \\\n --min-batch 1 --opt-batch 8 --max-batch 16 \\\n --min-frames 1 --opt-frames 512 --max-frames 4096 \\\n --workspace-gb 8",
"cp runtime/triton_gpu/model_repo_sense_voice_small/encoder/config.pbtxt.tensorrt \\\n runtime/triton_gpu/model_repo_sense_voice_small/encoder/config.pbtxt",
"cd runtime/triton_gpu && tritonserver --model-repository ./model_repo_sense_voice_small \\\n --pinned-memory-pool-byte-size=512000000 \\\n --cuda-memory-pool-byte-size=0:1024000000"
],
"health": [
"curl -fsS http://localhost:8000/v2/health/ready",
"curl -fsS http://localhost:8000/v2/models/sensevoice/ready"
],
"smoke": [
"python - <<'PY'\nimport numpy as np\nimport soundfile as sf\nimport tritonclient.grpc as grpc\n\naudio, sample_rate = sf.read(\n \"runtime/triton_gpu/client/test_wavs/mid.wav\", dtype=\"float32\"\n)\nassert sample_rate == 16000\nvalues = {\n \"WAV\": audio[None, :],\n \"WAV_LENS\": np.array([[audio.size]], dtype=np.int32),\n \"LANGUAGE\": np.array([[0]], dtype=np.int32),\n \"TEXT_NORM\": np.array([[1]], dtype=np.int32),\n}\ninputs = []\nfor name, value in values.items():\n tensor = grpc.InferInput(name, value.shape, grpc.np_to_triton_dtype(value.dtype))\n tensor.set_data_from_numpy(value)\n inputs.append(tensor)\nresult = grpc.InferenceServerClient(\"localhost:8001\").infer(\"sensevoice\", inputs)\ntranscript = result.as_numpy(\"TRANSCRIPTS\")[0][0].decode()\nassert transcript.strip(), \"SenseVoice returned an empty transcript\"\nprint(transcript)\nPY"
]
},
"evidence": [
{"label": "TensorRT deployment guide", "url": "https://github.com/modelscope/FunASR/blob/main/runtime/triton_gpu/README.md"},
{"label": "validated TensorRT builder", "url": "https://github.com/modelscope/FunASR/blob/main/runtime/triton_gpu/scripts/build_sensevoice_tensorrt.py"},
{"label": "native TensorRT integration PR", "url": "https://github.com/modelscope/FunASR/pull/3463"},
{"label": "deployment request and validation record", "url": "https://github.com/modelscope/FunASR/issues/3453"}
],
"benchmarks": [
{
"model": "SenseVoiceSmall",
"runtime": "TensorRT 10.0.1 with Triton 24.05 model contract",
"hardware": "NVIDIA H100; device memory capacity and host CPU are not part of the recorded result",
"workload": "Native FP16 engine build, profile-bound execution, and PyTorch parity validation",
"audio": "Bundled Chinese example plus synthetic feature tensors at 30 and 64 frames",
"settings": "FP16; batch profile 1/8/16; post-LFR frame profile 1/512/4096; 8 GiB builder workspace",
"timing_scope": "Engine build wall time only; inference timings are correctness probes and not a throughput benchmark",
"result": "527,504,916 bytes; 113.9 s engine build; 100% CTC top-1 agreement; exact bundled-audio transcript",
"qualification": "Single-H100 compatibility and parity evidence. Build on the target GPU and TensorRT version, then load-test with production audio before capacity planning.",
"source": "https://github.com/modelscope/FunASR/blob/main/runtime/triton_gpu/README.md",
"verified": "2026-08-04"
}
],
"translations": {
"zh": {
"name": "SenseVoice TensorRT / Triton",
"summary": "把 SenseVoiceSmall 构建为原生 FP16 TensorRT engine,并通过 Triton 提供可批处理的 GPU 推理服务。",
"fit": ["固定 NVIDIA GPU 环境中的 SenseVoice 批量转写", "需要 Triton 动态批处理、健康检查和模型仓库", "愿意按生产音频分布定制 batch 与帧长 profile"],
"not_fit": ["CPU、macOS、Windows 或通用边缘设备", "需要跨 GPU 型号直接复制同一个 engine", "只有动态量化 model_quant.onnx 的部署"],
"selection_reason": "原生 TensorRT engine 和 Triton 调度适合固定 NVIDIA GPU 上以 SenseVoice 延迟与吞吐为核心的服务。",
"primary_limitation": "TensorRT plan 不保证跨 GPU 架构或 TensorRT 版本可移植,必须在目标环境重新构建并复测精度、显存和容量。",
"status_label": "生产验证",
"operations": ["在与生产相同的 GPU 架构和 TensorRT 版本上构建 engine", "按真实 batch、并发和音频时长重设优化 profile", "保存 ONNX、engine、构建日志、SHA-256 与 Triton 配置用于回滚"],
"security": ["Triton worker 仅绑定内网,由网关完成认证、TLS、限流和审计", "限制音频大小、时长、声道、采样率和并发", "只加载经过校验的 ONNX、TensorRT plan、分词器和模型仓库"],
"troubleshooting": ["构建前确认导出使用 quantize=False,不能使用 model_quant.onnx", "parser 报错时先运行 ONNX checker 并核对 SenseVoice tensor 名称和 dtype", "Triton 加载失败时确认 model.plan 与 config.pbtxt.tensorrt 位于同一 encoder 版本目录"]
},
"en": {
"name": "SenseVoice TensorRT / Triton",
"summary": "Build SenseVoiceSmall as a native FP16 TensorRT engine and serve batched GPU inference through Triton.",
"fit": ["SenseVoice batch transcription on a fixed NVIDIA GPU fleet", "Triton dynamic batching, health checks, and model repositories", "Optimization profiles tuned to production batch and audio distributions"],
"not_fit": ["CPU, macOS, Windows, or general edge targets", "Copying one engine across different GPU models", "Deployments that only have dynamically quantized model_quant.onnx"],
"selection_reason": "A native TensorRT engine and Triton scheduling fit SenseVoice services that prioritize latency and throughput on fixed NVIDIA GPUs.",
"primary_limitation": "A TensorRT plan is not portable across arbitrary GPU architectures or TensorRT versions; rebuild and revalidate accuracy, memory, and capacity on the target stack.",
"status_label": "Production verified",
"operations": ["Build on the same GPU architecture and TensorRT version used in production", "Retune optimization profiles for real batch, concurrency, and audio duration", "Retain ONNX, engine, build log, SHA-256, and Triton configuration for rollback"],
"security": ["Bind Triton workers to private networks and provide authentication, TLS, rate limits, and audit at the gateway", "Limit audio size, duration, channels, sample rate, and concurrency", "Load only verified ONNX, TensorRT plans, tokenizers, and model repositories"],
"troubleshooting": ["Confirm the export used quantize=False; model_quant.onnx is unsupported", "Run the ONNX checker and verify SenseVoice tensor names and dtypes before investigating parser errors", "When Triton cannot load the model, keep model.plan and config.pbtxt.tensorrt in the same encoder version directory"]
}
}
},
{
"id": "llama-cpp",
"routes": {"zh": "/deploy/llama-cpp.html", "en": "/en/deploy/llama-cpp.html"},
Expand Down
31 changes: 31 additions & 0 deletions web-pages/product-site/tests/browser/product-site.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,37 @@ test('selector, language peers, copy, and compatibility routes work', async ({ b
await context.close();
});

for (const viewport of [
{ name: 'mobile', width: 390, height: 844 },
{ name: 'desktop', width: 1440, height: 900 },
]) {
test(`SenseVoice TensorRT deployment is stable at ${viewport.name}`, async ({ page }, testInfo) => {
await page.setViewportSize(viewport);
await page.goto('/deploy/sensevoice-tensorrt.html');

await expect(page.locator('h1')).toHaveText('SenseVoice TensorRT / Triton');
await expect(page.locator('[data-section="commands"] .command-block')).toHaveCount(9);
await expect(page.locator('[data-section="smoke-test"] .command-block')).toHaveCount(1);
await expect(page.locator('a[href="https://github.com/modelscope/FunASR/pull/3463"]')).toBeVisible();
await expect(page.locator('a[href="/en/deploy/sensevoice-tensorrt.html"]')).toBeVisible();

const layout = await page.evaluate(() => ({
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
commandWidths: [...document.querySelectorAll<HTMLElement>('.command-block')].map((node) => ({
parent: node.parentElement?.getBoundingClientRect().width ?? 0,
width: node.getBoundingClientRect().width,
})),
}));
expect(layout.overflow).toBeLessThanOrEqual(1);
expect(layout.commandWidths.every(({ parent, width }) => width <= parent + 1)).toBe(true);

await page.screenshot({
path: testInfo.outputPath(`sensevoice-tensorrt-${viewport.name}.png`),
fullPage: true,
});
});
}

test('reduced motion disables smooth scrolling', async ({ page }) => {
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto('/en/');
Expand Down
52 changes: 52 additions & 0 deletions web-pages/product-site/tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
REGISTRY = SITE_ROOT / 'data' / 'deployments.json'
EXPECTED_IDS = {
'vllm',
'sensevoice-tensorrt',
'llama-cpp',
'audio-cpp',
'openai-api',
Expand Down Expand Up @@ -66,6 +67,57 @@ def test_audio_cpp_contract_tracks_merged_offline_runtime(valid_registry):
assert 'timestamp' in entry['translations']['en']['primary_limitation'].lower()


def test_sensevoice_tensorrt_contract_tracks_merged_native_runtime(valid_registry):
entry = next(
item for item in valid_registry['deployments']
if item['id'] == 'sensevoice-tensorrt'
)
vllm = next(item for item in valid_registry['deployments'] if item['id'] == 'vllm')
llama_cpp = next(item for item in valid_registry['deployments'] if item['id'] == 'llama-cpp')

assert entry['maturity'] == 'production-verified'
assert vllm['selector_rank'] < entry['selector_rank'] < llama_cpp['selector_rank']
assert entry['tested'] == {
'funasr': 'main@6408aaa9',
'runtime': 'TensorRT 10.0.1 / Triton 24.05',
'verified': '2026-08-04',
}
assert entry['models'] == ['SenseVoiceSmall']
assert entry['hardware'] == ['nvidia-gpu', 'kubernetes']
assert 'Triton gRPC/HTTP' in entry['interfaces']
assert any('git checkout 6408aaa9' in command for command in entry['commands']['install'])
assert any('quantize=False' in command for command in entry['commands']['install'])
assert any(
'chn_jpn_yue_eng_ko_spectok.bpe.model' in command
and 'aa87f86064c3730d799ddf7af3c04659151102cba548bce325cf06ba4da4e6a8' in command
for command in entry['commands']['install']
)
assert any(
'build_sensevoice_tensorrt.py' in command
and '--max-batch 16' in command
and '--max-frames 4096' in command
for command in entry['commands']['launch']
)
assert any(
command.startswith('cd runtime/triton_gpu && tritonserver')
for command in entry['commands']['launch']
)
assert any('/v2/health/ready' in command for command in entry['commands']['health'])
assert any('TRANSCRIPTS' in command for command in entry['commands']['smoke'])
assert any('/pull/3463' in item['url'] for item in entry['evidence'])
assert any(
'build_sensevoice_tensorrt.py' in item['url'] for item in entry['evidence']
)
assert any(
'527,504,916 bytes' in benchmark['result']
and '100% CTC top-1 agreement' in benchmark['result']
for benchmark in entry['benchmarks']
)
limitation = entry['translations']['en']['primary_limitation'].lower()
assert 'gpu architecture' in limitation
assert 'tensorrt version' in limitation


def test_production_entry_requires_evidence(valid_registry):
data = copy.deepcopy(valid_registry)
entry = next(item for item in data['deployments'] if item['maturity'] == 'production-verified')
Expand Down
Loading