使用 TensorRT-Edge-LLM 0.9.0 在端侧测试 Qwen3-Omni

# 使用 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.requests[0].messages.push_back(std::move(user));

request.requests[0].imageBuffers.push_back(std::move(video));

request.requests[0].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.outputTexts[0] << 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.outputTexts[0]` 中返回 **一个** 字符串,并没有单独的

`video_result` / `audio_result` 字段。两种模态在一次 Thinker 解码中融合。

若希望结果更易读,可在 prompt 中要求模型按段落标注。

较好的回答形态示例:

```text

### Video

... 来自帧的场景描述 ...

### Audio

... 来自 PCM 的转写 / 摘要 ...

```

---

## 小结

1. **TensorRT-Edge-LLM 0.9.0** 是面向 Jetson 类 GPU 的端侧 LLM / VLM / Omni C++ 栈。

2. **Qwen3-Omni** 以多 engine 图部署:量化 → 导出 ONNX → `*_build` 生成 engine。

3. **一次 `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)* 用户指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大迫杰真帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值