[实践记录]Jetson orin Nx部署量化Qwen3实录
一、最终目录关系
整个流程会产生三类模型目录:
Qwen3-0.6B/
├── config.json
├── model.safetensors
├── tokenizer.json
└── ...
这是从 Hugging Face 下载的原始模型,也是权重转换和量化的起点。
Qwen3-0.6B-trtllm-checkpoint/
├── config.json
└── rank0.safetensors
这是 TensorRT-LLM 的中间 checkpoint,不能直接用于推理。
Qwen3-0.6B-engine/
├── config.json
└── rank0.engine
这是最终运行时加载的 TensorRT engine。
完整关系:
Hugging Face 原始模型
Qwen3-0.6B
↓
convert_checkpoint.py
↓
TensorRT-LLM checkpoint
↓
trtllm-build
↓
TensorRT engine
↓
实际推理
二、下载 Qwen3-0.6B
创建模型目录:
mkdir -p ~/tesnorrt_model
cd ~/tesnorrt_model
使用 Git LFS 下载:
git lfs install
git clone https://huggingface.co/Qwen/Qwen3-0.6B
下载完成后检查:
ls ~/tesnorrt_model/Qwen3-0.6B
应当至少包含:
config.json
model.safetensors
tokenizer.json
tokenizer_config.json
三、准备 Qwen3 转换环境
当前使用的是:
TensorRT-LLM 0.12.0-jetson
Jetson Orin NX
CUDA 12.6
TensorRT 10.3
Python 3.10
激活 TensorRT-LLM 环境:
source ~/venvs/trtllm/bin/activate
Qwen3 需要较新的 Transformers 支持,因此安装:
pip install "transformers==4.51.3"
验证:
python3 - <<'PY'
import transformers
from transformers.models.qwen3 import Qwen3ForCausalLM
print("transformers:", transformers.__version__)
print("Qwen3 support: OK")
PY
为了确保 Python 使用修改后的 TensorRT-LLM 源码,而不是虚拟环境中旧的 wheel,设置:
export PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH
验证路径:
python3 - <<'PY'
import tensorrt_llm
import tensorrt_llm.models.qwen3.model as qwen3_model
print(tensorrt_llm.__file__)
print(qwen3_model.__file__)
PY
路径应指向:
/home/nx-aiq11/TensorRT-LLM/tensorrt_llm/
而不是:
/home/nx-aiq11/venvs/trtllm/lib/python3.10/site-packages/
四、为 TensorRT-LLM 0.12 适配 Qwen3
TensorRT-LLM 0.12 原生支持 Qwen2,但不支持 Qwen3,因此基于原来的 Qwen 实现创建:
tensorrt_llm/models/qwen3/
├── __init__.py
├── config.py
├── model.py
└── convert.py
同时创建:
examples/qwen3/convert_checkpoint.py
主要适配内容如下。
1. 独立 head_dim
Qwen3-0.6B 的配置是:
hidden_size = 1024
num_attention_heads = 16
num_key_value_heads = 8
head_dim = 128
不能使用:
hidden_size // num_attention_heads
因为:
1024 / 16 = 64
但模型实际的 head_dim 是 128。
因此配置必须优先读取:
head_size = hf_config.head_dim
2. GQA
Qwen3-0.6B 使用:
Q heads = 16
KV heads = 8
head_dim = 128
所以 QKV 投影宽度为:
Q = 16 × 128 = 2048
K = 8 × 128 = 1024
V = 8 × 128 = 1024
QKV = 4096
正确的 QKV 权重 shape:
(4096, 1024)
而不是普通 MHA 假设下的:
16 × 3 × 128 = 6144
3. Q/K RMSNorm
Qwen3 在 Q、K 投影后增加:
q_norm
k_norm
在 TensorRT-LLM 中启用:
qk_layernorm=True
layernorm_type=LayerNormType.RmsNorm
eps=config.norm_epsilon
HF 权重:
model.layers.N.self_attn.q_norm.weight
model.layers.N.self_attn.k_norm.weight
映射到:
transformer.layers.N.attention.q_layernorm.weight
transformer.layers.N.attention.k_layernorm.weight
它们的 shape 均为:
(128,)
4. 模型注册
创建独立模型类:
QWen3ForCausalLM
checkpoint 中写入:
"architecture": "QWen3ForCausalLM",
"qwen_type": "qwen3"
并将该 architecture 注册到 TensorRT-LLM 模型映射中。
5. Attention 支持 GQA + Q/K Norm
旧版 Attention 的 Q/K Norm 分支默认 Q、K、V head 数相同,会尝试:
[num_tokens, 4096]
→
[num_tokens, 16, 3, 128]
导致体积不一致。
修改后按照真实尺寸拆分:
Q: [num_tokens, 2048]
K: [num_tokens, 1024]
V: [num_tokens, 1024]
然后分别 reshape:
Q: [num_tokens, 16, 128]
K: [num_tokens, 8, 128]
执行 Q/K RMSNorm 后,再拼回:
[num_tokens, 4096]
五、转换 FP16 TensorRT-LLM checkpoint
删除旧输出:
rm -rf ~/tesnorrt_model/Qwen3-0.6B-trtllm-checkpoint
运行转换:
cd ~/TensorRT-LLM/examples/qwen3
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
python3 convert_checkpoint.py \
--model_dir ~/tesnorrt_model/Qwen3-0.6B \
--output_dir ~/tesnorrt_model/Qwen3-0.6B-trtllm-checkpoint \
--dtype float16
成功日志:
Weights loaded.
Total time of converting checkpoints: ...
六、验证 FP16 checkpoint
检查配置:
python3 - <<'PY'
import json
path = "/home/nx-aiq11/tesnorrt_model/Qwen3-0.6B-trtllm-checkpoint/config.json"
with open(path, "r", encoding="utf-8") as f:
cfg = json.load(f)
for key in [
"architecture",
"qwen_type",
"hidden_size",
"num_attention_heads",
"num_key_value_heads",
"head_size",
]:
print(f"{key}: {cfg.get(key)}")
PY
正确结果:
architecture: QWen3ForCausalLM
qwen_type: qwen3
hidden_size: 1024
num_attention_heads: 16
num_key_value_heads: 8
head_size: 128
检查权重:
python3 - <<'PY'
from safetensors.torch import load_file
path = "/home/nx-aiq11/tesnorrt_model/Qwen3-0.6B-trtllm-checkpoint/rank0.safetensors"
weights = load_file(path)
keys = [
"transformer.layers.0.attention.qkv.weight",
"transformer.layers.0.attention.q_layernorm.weight",
"transformer.layers.0.attention.k_layernorm.weight",
]
for key in keys:
print(key, tuple(weights[key].shape))
PY
正确结果:
qkv.weight (4096, 1024)
q_layernorm.weight (128,)
k_layernorm.weight (128,)
七、构建 FP16 TensorRT engine
删除旧 engine:
rm -rf ~/tesnorrt_model/Qwen3-0.6B-engine
构建:
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
trtllm-build \
--checkpoint_dir ~/tesnorrt_model/Qwen3-0.6B-trtllm-checkpoint \
--output_dir ~/tesnorrt_model/Qwen3-0.6B-engine \
--gemm_plugin float16
成功后检查:
ls -lh ~/tesnorrt_model/Qwen3-0.6B-engine
应生成:
config.json
rank0.engine
八、测试 FP16 engine
Qwen3 是对话模型,必须应用 chat template,不能只使用裸文本测试。
可以先构造 Prompt:
PROMPT=$(python3 - <<'PY'
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(
"/home/nx-aiq11/tesnorrt_model/Qwen3-0.6B"
)
messages = [
{
"role": "user",
"content": "你好,请用一句话介绍一下你自己。"
}
]
print(tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
))
PY
)
运行:
cd ~/TensorRT-LLM/examples/qwen3
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
python3 ../run.py \
--input_text "$PROMPT" \
--max_output_len 100 \
--tokenizer_dir ~/tesnorrt_model/Qwen3-0.6B \
--engine_dir ~/tesnorrt_model/Qwen3-0.6B-engine
FP16 能正常输出后,说明:
模型结构适配成功
权重转换成功
TensorRT engine 构建成功
Qwen3 推理成功
九、在 RTX 4090 主机上进行 Weight-Only 量化
由于 Jetson NX 的 ARM64 环境中 ModelOpt、PyTorch distributed 和部分依赖安装困难,因此将量化任务放到 x86 RTX 4090 主机上完成。
重要原则:
4090 负责生成量化 checkpoint
NX 负责构建最终 TensorRT engine
不要在 4090 上构建 engine 后直接复制到 NX。
两台主机必须使用相同版本的 Qwen3 backport 代码,建议比较:
sha256sum \
tensorrt_llm/models/qwen3/config.py \
tensorrt_llm/models/qwen3/model.py \
tensorrt_llm/models/qwen3/convert.py \
tensorrt_llm/models/qwen3/__init__.py \
examples/qwen3/convert_checkpoint.py
十、生成 W8A16 checkpoint
在 4090 主机上运行:
cd ~/TensorRT-LLM
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
python3 examples/qwen3/convert_checkpoint.py \
--model_dir /home/scjs/trt_models/Qwen3-0.6B \
--output_dir /home/scjs/trt_models/Qwen3-0.6B-trtllm-w8a16 \
--dtype float16 \
--use_weight_only \
--weight_only_precision int8
含义:
W8 = 权重 INT8
A16 = 激活 FP16
十一、生成 W4A16 checkpoint
在 4090 主机上运行:
cd ~/TensorRT-LLM
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
python3 examples/qwen3/convert_checkpoint.py \
--model_dir /home/scjs/trt_models/Qwen3-0.6B \
--output_dir /home/scjs/trt_models/Qwen3-0.6B-trtllm-w4a16 \
--dtype float16 \
--use_weight_only \
--weight_only_precision int4
含义:
W4 = 权重 INT4
A16 = 激活 FP16
量化配置中应出现:
"quantization": {
"quant_algo": "W4A16",
"kv_cache_quant_algo": null
}
十二、将量化 checkpoint 拷贝回 NX
例如使用 SCP:
scp -r \
/home/scjs/trt_models/Qwen3-0.6B-trtllm-w4a16 \
nx-aiq11@<NX_IP>:/home/nx-aiq11/tesnorrt_model/
W8A16 同理。
最终 NX 上目录:
/home/nx-aiq11/tesnorrt_model/Qwen3-0.6B-trtllm-w8a16
/home/nx-aiq11/tesnorrt_model/Qwen3-0.6B-trtllm-w4a16
十三、在 NX 上构建 W8A16 engine
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
trtllm-build \
--checkpoint_dir ~/tesnorrt_model/Qwen3-0.6B-trtllm-w8a16 \
--output_dir ~/tesnorrt_model/Qwen3-0.6B-engine-w8a16 \
--gemm_plugin float16
虽然权重是 INT8,但这里仍使用:
--gemm_plugin float16
因为 W8A16 中激活和计算仍然是 FP16。
十四、在 NX 上构建 W4A16 engine
PYTHONPATH=$HOME/TensorRT-LLM:$PYTHONPATH \
trtllm-build \
--checkpoint_dir ~/tesnorrt_model/Qwen3-0.6B-trtllm-w4a16 \
--output_dir ~/tesnorrt_model/Qwen3-0.6B-engine-w4a16 \
--gemm_plugin float16
同样:
权重:INT4
激活:FP16
GEMM 计算:FP16
十五、量化结果验证
最初量化模型曾出现重复输出:
我是你吗?我是你吗?我是你吗?……
后来通过固定以下条件重新测试:
相同 tokenizer
相同 Qwen3 chat template
相同 input_ids
相同 decoding 参数
固定参数:
max_output_len = 64
temperature = 1.0
top_k = 1
top_p = 0.0
random_seed = 0
三种模型使用的 input_ids 完全一致。
最终测试结果:
FP16:
我是AI助手,专注于帮助用户解决问题和提供支持。
W8A16:
我是AI助手,专注于帮助用户解决问题和提供支持。
W4A16:
能够输出正常、连贯文本,但相比 FP16/W8A16 有一定质量退化。
同时验证量化确实生效:
W8A16:
torch.int8 weight
+ per_channel_scale
W4A16:
packed INT4 weight,以 torch.int8 保存
+ per_channel_scale
量化模型实际实例化为:
attention.qkv → WeightOnlyQuantLinear
attention.dense → WeightOnlyQuantRowLinear
mlp.gate → WeightOnlyQuantLinear
mlp.fc → WeightOnlyQuantLinear
mlp.proj → WeightOnlyQuantRowLinear
因此最终结论:
FP16 正常
W8A16 正常
W4A16 可用,但存在一定精度退化
十六、最终模型目录
原始模型:
~/tesnorrt_model/Qwen3-0.6B
FP16 checkpoint:
~/tesnorrt_model/Qwen3-0.6B-trtllm-checkpoint
FP16 engine:
~/tesnorrt_model/Qwen3-0.6B-engine
W8A16 checkpoint:
~/tesnorrt_model/Qwen3-0.6B-trtllm-w8a16
W8A16 engine:
~/tesnorrt_model/Qwen3-0.6B-engine-w8a16
W4A16 checkpoint:
~/tesnorrt_model/Qwen3-0.6B-trtllm-w4a16
W4A16 engine:
~/tesnorrt_model/Qwen3-0.6B-engine-w4a16
十七、完整流程总结
1. 从 Hugging Face 下载 Qwen3-0.6B
↓
2. 在 TensorRT-LLM 0.12 中 backport Qwen3
↓
3. 适配 head_dim、GQA、q_norm/k_norm
↓
4. 转换 FP16 TRT-LLM checkpoint
↓
5. 验证 config 和权重 shape
↓
6. 在 NX 上构建 FP16 engine
↓
7. 使用 Qwen3 chat template 验证 FP16 推理
↓
8. 在 RTX 4090 上生成 W8A16/W4A16 checkpoint
↓
9. 将量化 checkpoint 复制回 NX
↓
10. 在 NX 上分别构建 W8A16/W4A16 engine
↓
11. 使用相同 input_ids 和解码参数进行 A/B 测试
↓
12. 确认 W8A16 正常、W4A16 可用
核心原则:
原始 safetensors 是转换和量化起点
TRT-LLM checkpoint 是中间产物
TensorRT engine 才是真正运行的模型
量化在 4090 上完成
最终 engine 在 NX 上重新构建

浙公网安备 33010602011771号