使用 TensorRT-Edge-LLM 0.9.0 在端侧测试 Qwen3-Omni
本文介绍如何用 NVIDIA TensorRT-Edge-LLM(https://github.com/NVIDIA/TensorRT-Edge-LLM.git)
**v0.9.0** 部署 **Qwen3-Omni-30B-A3B-Instruct**,并在一组按时间排序的 RGB 帧与原始 PCM 音频上发起一次多模态 `handleRequest`------与我们在 Jetson Thor 上验证的小型 C++ 样例一致。
1. 什么是 TensorRT-Edge-LLM?
**TensorRT Edge-LLM** 是 NVIDIA 面向嵌入式平台(Jetson、DRIVE、DGX Spark)的
高性能 **C++** 大模型推理运行时,覆盖 LLM、VLM 与 Omni 等模型。
-
仓库:https://github.com/NVIDIA/TensorRT-Edge-LLM(https://github.com/NVIDIA/TensorRT-Edge-LLM.git)
-
**本文使用的版本:`0.9.0`**
```bash
git clone https://github.com/NVIDIA/TensorRT-Edge-LLM.git
cd TensorRT-Edge-LLM
git checkout v0.9.0 # 或仓库中对应的 0.9.0 release tag
```
为什么适合端侧
-
**TensorRT + CUDA kernel**,追求低延迟
-
**量化**(如 NVFP4),便于把大模型装进设备显存
-
**面向量产的 C++ 运行时**(部署阶段可不依赖 Python)
-
端到端链路:Hugging Face 权重 → ONNX → TensorRT engine → C++ API
三阶段流水线
```
Hugging Face 模型
→ 量化 / 导出(Python 工具链)
→ ONNX 图 + tokenizer / chat template
→ engine 构建(llm_build / visual_build / audio_build)
→ TensorRT engines
→ C++ 运行时(LLMInferenceRuntime::handleRequest)
```
支持的模型族包括 Qwen、Llama、InternVL、Phi、Gemma、Nemotron,以及 Qwen3-Omni
等 Omni 变体。完整矩阵与 JetPack / DriveOS 要求见项目文档。
2. Qwen3-Omni-30B-A3B-Instruct:从 HF 到 ONNX 再到 engine
2.1 模型概览
Qwen3-Omni-30B-A3B-Instruct(https://huggingface.co/Qwen/Qwen3-Omni-30B-A3B-Instruct)
是统一的多模态 MoE 模型,可接收 **文本 + 音频 + 图像/视频**,并输出 **文本**(可选地通过 Talker 路径输出语音)。
在 TensorRT-Edge-LLM 中,Omni 拆成 **六个 engine**:
| Engine | 作用 |
|--------|------|
| **Thinker** | 文本解码器------生成助手 token |
| **Talker** | Codec token 解码器(语音路径) |
| **CodePredictor** | Codec 上的小型预测头 |
| **Audio encoder** | Whisper 风格 mel → 音频嵌入 |
| **Visual encoder** | Qwen3-VL 风格 ViT → 视觉嵌入 |
| **Code2Wav** | Codec token → 24 kHz PCM(TTS) |
若只做 **文本输出** 的多模态测试(描述视频 + 总结音频),通常只需要
**Thinker + audio encoder + visual encoder**。仅在需要语音输出时,才需要
Talker / CodePredictor / Code2Wav。
2.2 环境变量
```bash
export WORKSPACE_DIR=$HOME/tensorrt-edgellm-workspace
mkdir -p $WORKSPACE_DIR
export OMNI_MODEL=Qwen3-Omni-30B-A3B-Instruct
export HF_ROOT=Qwen/$OMNI_MODEL # 或本地 HF 快照目录
export ONNX=WORKSPACE_DIR/OMNI_MODEL/onnx
export ENG=WORKSPACE_DIR/OMNI_MODEL/engines
export QUANT_ROOT=WORKSPACE_DIR/OMNI_MODEL/nvfp4
```
请先安装并编译 **TensorRT-Edge-LLM 0.9.0**(见该 tag 对应的
Installation Guide(https://github.com/NVIDIA/TensorRT-Edge-LLM))。
2.3 量化(NVFP4 Thinker + Talker)
```bash
tensorrt-edgellm-quantize qwen3-omni \
--model_dir $HF_ROOT \
--output_dir $QUANT_ROOT \
--talker_num_audio 150 \
--talker_num_image 150 \
--talker_num_text 200
```
输出:
```
$QUANT_ROOT/
├── thinker/ # NVFP4 文本 MoE(Thinker)
└── talker/ # NVFP4 Talker 主干
```
视觉 / 音频 / code2wav 编码器通常保持 FP16,并从原始 HF root 导出。
2.4 导出 ONNX
```bash
mkdir -p $ONNX
Thinker
tensorrt-edgellm-export QUANT_ROOT/thinker ONNX/thinker
Talker(并从完整 HF root 提取 projection sidecar)
tensorrt-edgellm-export \
--talker-sidecar-from $HF_ROOT \
QUANT_ROOT/talker ONNX/talker
Visual + audio(以及可选的 TTS 组件)
tensorrt-edgellm-export \
--components visual,audio,code2wav,code_predictor \
HF_ROOT ONNX/multimodal
```
期望目录结构(简化):
```
$ONNX/
├── thinker/llm/ # model.onnx、config.json、tokenizer、chat template
├── talker/llm/ # 另含 hidden_projection / text_projection
└── multimodal/
├── audio/
├── visual/
├── code2wav/
└── code_predictor/
```
2.5 构建 TensorRT engine
```bash
以下 profile 与 thirdpart/engine/*/config.json 中的 builder_config 一致。
Thinker
./build/examples/llm/llm_build \
--onnxDir $ONNX/thinker/llm \
--engineDir $ENG/thinker \
--maxBatchSize 2 \
--maxInputLen 131072 \
--maxKVCacheCapacity 135168
Talker(仅文本多模态时可省略)
./build/examples/llm/llm_build \
--onnxDir $ONNX/talker/llm \
--engineDir $ENG/talker \
--maxBatchSize 2 \
--maxInputLen 131072 \
--maxKVCacheCapacity 135168
Audio encoder(maxTimeSteps=46875 约对应 16 kHz 下 ~5 分钟音频)
./build/examples/multimodal/audio_build \
--onnxDir $ONNX/multimodal/audio \
--engineDir $ENG/multimodal/audio \
--minTimeSteps 100 \
--maxTimeSteps 46875 \
--minCodeLen 1 \
--optCodeLen 300 \
--maxCodeLen 2000
Visual encoder
./build/examples/multimodal/visual_build \
--onnxDir $ONNX/multimodal/visual \
--engineDir $ENG/multimodal/visual \
--minImageTokens 4 \
--maxImageTokens 1024 \
--maxImageTokensPerImage 512
```
这些构建参数与 `thirdpart/engine` 中已发布的 engine 一致。若需更小显存占用,可自行下调。
运行时较常用的目录布局:
```
$ENG/
├── thinker/ # llm.engine、tokenizer、processed_chat_template.json
└── multimodal/
├── audio/ # audio_encoder.engine
└── visual/ # visual.engine
```
3. 用图片序列 + PCM 做推理(Edge-LLM 0.9.0)
本节对应基于 **TensorRT-Edge-LLM 0.9.0** 的小型 C++ 样例(`avi_inference`):
在一次 `LLMInferenceRuntime::handleRequest` 中同时送入:
-
按时间排序的 RGB 帧(作为短视频),
-
16 kHz 单声道 float32 PCM,
-
文本 prompt。
3.1 输入
| 模态 | 示例 | 说明 |
|------|------|------|
| 视频帧 | `order_food/0.png ... 5.png` | 分辨率一致,按时间顺序 |
| 音频 | `sess-....f32le` | 原始单声道 **f32le**,16 kHz(不是 WAV 容器) |
| Prompt | `what did you see and hear?` | 也可要求输出带 `Video:` / `Audio:` 分段 |
PCM 体量自检示例:`30 s × 16000 × 4 bytes = 1 920 000` bytes。
3.2 构造请求(C++ 示意)
```cpp
#include "runtime/llmInferenceRuntime.h"
#include "runtime/imageUtils.h"
#include "runtime/audioUtils.h"
#include "runtime/audioLoader.h"
#include "common/trtUtils.h"
using namespace trt_edgellm::rt;
// 1) 视频:帧堆叠 → ImageData T,H,W,3 uint8 RGB
auto video = imageUtils::loadVideoFromFrames(framePaths, /*fps=*/0.2);
// 2) 音频:host 侧单声道 FP32 @ 16 kHz
audioUtils::AudioData audio;
audio.pcm = std::make_shared<audio::AudioPCM>();
audio.pcm->samples = /* load f32le */;
audio.pcm->sampleRate = 16000;
audio.pcm->numChannels = 1;
audio.sampleRate = 16000;
// 3) 一条 user 消息:视觉 + 音频 + 文本
LLMGenerationRequest request;
request.applyChatTemplate = true;
request.addGenerationPrompt = true;
request.generateAudio = false; // 只要文本回答
request.maxGenerateLength = 512;
request.requests.resize(1);
Message user;
user.role = "user";
// 重要:为 Qwen3-Omni MRoPE 包上 start/end 标记
user.contents.push_back({"text", "<|vision_start|>"});
user.contents.push_back({"video", "frames"});
user.contents.push_back({"text", "<|vision_end|>"});
user.contents.push_back({"text", "<|audio_start|>"});
user.contents.push_back({"audio", ""});
user.contents.push_back({"text", "<|audio_end|>"});
user.contents.push_back({"text", "what did you see and hear?"});
request.requests0.messages.push_back(std::move(user));
request.requests0.imageBuffers.push_back(std::move(video));
request.requests0.audioBuffers.push_back(std::move(audio));
// 4) Runtime
cudaStream_t stream;
cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking);
auto plugin = trt_edgellm::loadEdgellmPluginLib();
LLMInferenceRuntime runtime(
"/path/to/engine/thinker",
"/path/to/engine/multimodal",
/*loraMap=*/{},
stream);
LLMGenerationResponse response;
runtime.handleRequest(request, response, stream, /*returnHiddenStates=*/false);
std::cout << response.outputTexts0 << std::endl;
```
**输入约定**
-
帧必须是 **uint8 RGB**;均值/方差归一化在 ViT runner 内部完成。
-
Qwen3-Omni 音频输入为 **16 kHz 单声道原始 FP32 PCM**;mel 由 audio runner 内部提取。
-
消息里 `video` / `audio` 类型条目须与 `imageBuffers` / `audioBuffers` **一一对应**。
-
若 `processed_chat_template.json` 只插入裸的 `<|video_pad|>` / `<|audio_pad|>`,
请自行注入 `<|vision_start|>...<|vision_end|>` 与 `<|audio_start|>...<|audio_end|>`
(Omni 的 MRoPE 需要这些包裹符)。
3.3 实用限制:视觉 token 预算
视觉 engine 有 max HW / image-token 上限。高分辨率长帧序列可能超限
(例如 6×720p → ViT token 过多)。**不重建 engine** 时的缓解办法:
-
对帧做子采样(如 `--max-frames 4`),或
-
在上游先缩小分辨率,
使总视觉 token 落在 engine 限制内。
3.4 命令行示例
```bash
export EDGELLM_PLUGIN_PATH=/path/to/tensorrt-edge-llm-build/libNvInfer_edgellm_plugin.so.1.0
export LD_LIBRARY_PATH=/path/to/tensorrt-edge-llm-build:$LD_LIBRARY_PATH
./avi_inference \
--frames-dir /path/to/frames \
--pcm /path/to/audio.f32le \
--thinker /path/to/engine/thinker \
--multimodal /path/to/engine/multimodal \
--prompt "请严格分两段回答,标题为 Video 与 Audio。Video:描述画面。Audio:转写你听到的内容。" \
--fps 0.2 \
--max-frames 4 \
--max-new-tokens 512 \
--output-dir ./outputs
```
3.5 返回结果是什么
API 在 `response.outputTexts0` 中返回 **一个** 字符串,并没有单独的
`video_result` / `audio_result` 字段。两种模态在一次 Thinker 解码中融合。
若希望结果更易读,可在 prompt 中要求模型按段落标注。
较好的回答形态示例:
```text
Video
... 来自帧的场景描述 ...
Audio
... 来自 PCM 的转写 / 摘要 ...
```
小结
-
**TensorRT-Edge-LLM 0.9.0** 是面向 Jetson 类 GPU 的端侧 LLM / VLM / Omni C++ 栈。
-
**Qwen3-Omni** 以多 engine 图部署:量化 → 导出 ONNX → `*_build` 生成 engine。
-
**一次 `handleRequest`** 即可同时消费帧序列 + PCM + 文本并返回联合回答------
很适合端侧"你看到了什么、听到了什么?"类应用。
参考
-
NVIDIA/TensorRT-Edge-LLM(https://github.com/NVIDIA/TensorRT-Edge-LLM.git)(**v0.9.0**)
-
Qwen3-Omni-30B-A3B-Instruct(https://huggingface.co/Qwen/Qwen3-Omni-30B-A3B-Instruct)
-
TensorRT-Edge-LLM 0.9.0 文档:*Omni (Audio + Vision + Speech I/O)* 用户指南