Qwen3-0.6B ONNX(KV-Cache)模型部署

本项目演示 Qwen3-0.6B ONNX 模型通过 onnxruntime 加载并进行自回归推理的完整实现。


模型下载&部署源码获取

shell 复制代码
pip install modelscope==1.37.1
modelscope download --model KeanuX/Qwen3-0.6B-ONNX --local_dir ./

模型目录结构

复制代码
Qwen3-0.6B-ONNX/
├── model.onnx                # 原始 ONNX 模型(~1.22 GB,fp16)
├── model_no_isnan.onnx       # 移除 IsNaN 算子后的 ONNX 模型(已优化,推理用)
├── config.json               # 模型架构配置
├── generation_config.json    # 生成参数默认配置
├── tokenizer.json            # Tokenizer 词表 & 编码规则
├── tokenizer_config.json     # Tokenizer 行为配置
├── special_tokens_map.json   # 特殊 token 映射
├── added_tokens.json         # 附加 token 定义
├── vocab.json                # 词表
├── merges.txt                # BPE merge 规则
└── chat_template.jinja       # Chat 模板(用于构建 prompt)

ONNX 模型:

文件 说明
model.onnx optimum 导出的原始图,attention 层 Softmax 后含 IsNaN + Where 节点
model_no_isnan.onnx 已移除 IsNaN/Where 冗余算子,直接以 Softmax → MatMul 连接,推理时使用

Tokenizer 文件: 可直接传给 transformers.AutoTokenizer.from_pretrained(...) 加载。


部署算法实现

推理引擎由 llm_loader.py 中的 QwenLoader 类实现。

初始化

python 复制代码
model = QwenLoader(model_path, logger, device="cuda", system_prompt="你是智能助手小智")

初始化过程:

  1. 通过 AutoTokenizer.from_pretrained 加载同目录下的 tokenizer
  2. 通过 onnxruntime.InferenceSession 加载 model_no_isnan.onnx,支持 CPU/CUDA 两种执行后端
  3. 自动解析模型的 IO 规格:输入/输出名称列表、层数 num_layers、head 数 num_heads、head 维度 head_dim

自回归生成流程

generate(user_text, max_new_tokens, enable_thinking) 分为三个阶段:

第一阶段:Prompt 编码 & Prefill

将用户输入包装为 system + user 格式的 chat template,通过 tokenizer 编码得到 input_ids(shape 1 × seq_len)。连同 attention_maskposition_ids、以及各层全零的 past_key_values 送入模型,一次性计算完整 prompt 的 logits,并获得首轮的 present KV-Cache。

复制代码
输入: input_ids(1, seq_len) + attention_mask + position_ids + past KV (zeros)
输出: logits(1, seq_len, vocab_size) + present KV × 28 层

logits[0, -1, :] 的 argmax 作为第一个生成 token。

第二阶段:自回归 Decode

循环执行,每步:

  1. 以当前 token 构造 input_ids(shape 1 × 1),attention_mask 随已生成长度逐步扩展,position_ids 为当前绝对位置

  2. 同时传入上一轮输出的 present KV-Cache 作为本轮 past_key_values

  3. 模型输出当前步的 logits 和更新后的 KV-Cache

  4. argmax 选取下一 token,继续循环,直到命中 EOS 或达到 max_new_tokens

    循环:
    input: input_ids(1,1) + attention_mask + position_ids + past KV
    output: logits(1,1,vocab_size) + present KV
    token = argmax(logits) → 更新 past KV ← present KV

第三阶段:Decode & 后处理
  • 将生成的 token id 序列通过 tokenizer 解码
  • 解析 </think> 特殊 token(id=151668),将输出分离为 thinking content 和最终回答

输入/输出规格

类型 名称 Shape 说明
输入 input_ids (1, seq_len) token id 序列
输入 attention_mask (1, seq_len) 注意力掩码
输入 position_ids (1, seq_len) 位置编码索引
输入 past_key_values.{i}.key (1, num_heads, past_len, head_dim) 第 i 层 key 缓存
输入 past_key_values.{i}.value (1, num_heads, past_len, head_dim) 第 i 层 value 缓存
输出 logits (1, seq_len, vocab_size) 预测 logits
输出 present.{i}.key (1, num_heads, total_len, head_dim) 更新后的 key 缓存
输出 present.{i}.value (1, num_heads, total_len, head_dim) 更新后的 value 缓存
  • 层数 i = 0 ... 27(共 28 层)
  • num_heads = 8(GQA,key/value heads),head_dim = 128
  • 输入共 59 个,输出共 57 个

运行测试

test_run.py 加载模型并执行一次推理:

bash 复制代码
python test_run.py
python 复制代码
from model_classes.llm_loader import QwenLoader

model_loader = QwenLoader("./hf_models/Qwen3-0.6B-ONNX/", logger, device="cuda")
thk_con, cus_con = model_loader.generate("你是谁", max_new_tokens=256, enable_thinking=False)

日志输出到 ./logs/run.log


性能测试

model.onnxmodel_no_isnan.onnx 在 CPU / CUDA 两种后端下进行基准对比(prompt 长度 29 tokens,decode 128 步,warmup 2 轮后取 3 轮均值)。

CPU 结果

指标 model.onnx model_no_isnan.onnx 差异
Prefill 耗时 64.32 ms 64.20 ms -0.18%
Prefill 吞吐 450.89 tok/s 451.72 tok/s +0.18%
Decode 每步耗时 52.99 ms 50.95 ms -3.85%
Decode 吞吐 18.87 tok/s 19.63 tok/s +4.00%

CUDA 结果

指标 model.onnx model_no_isnan.onnx 差异
Prefill 耗时 8.23 ms 8.23 ms +0.02%
Prefill 吞吐 3522.76 tok/s 3522.15 tok/s -0.02%
Decode 每步耗时 8.78 ms 8.62 ms -1.87%
Decode 吞吐 113.88 tok/s 116.05 tok/s +1.91%

分析:

  • CPU 下 Decode 提升 ~4%,每步节省 ~2ms;Prefill 几乎无变化。
  • CUDA 下 GPU 算力更强,单步延迟远低于 CPU (~8.6ms vs ~51ms),但相对提升幅度缩小到 ~2%,因为 IsNaN/Where 在 GPU 上的计算成本比例更低。
  • 两种后端下 model_no_isnan.onnx 均有正向收益,无精度损失。

模型配置参数

参数
架构 Qwen3ForCausalLM
层数 28
隐藏维度 1024
中间层维度 3072
Attention Heads 16 (Q) / 8 (KV, GQA)
Head Dim 128
词表大小 151,936
激活函数 SiLU
Norm RMSNorm (eps=1e-6)
RoPE theta 1,000,000
最大位置 40,960
精度 float16
相关推荐
带刺的坐椅17 小时前
从 Claude Code 隐私争议,看 SolonCode 的设计选择
ai·llm·agent·claudecode·soloncode·codingplan
MomentYY1 天前
Temperature:AI 的“脑洞旋钮”
前端·llm·ai编程
Darling噜啦啦1 天前
上下文工程实战:从 Prompt 到 Harness 的三次 AI 工程化浪潮
llm·ai编程
Hyyy2 天前
Function Calling / Tool Use的原理和实现模式
前端·llm·ai编程
智泊AI2 天前
Loop Engineering 为什么会出现?一个 Loop 的组成部分有哪些?
llm
凌奕2 天前
别用文档约束你的 Agent:聊聊 Agent 开发流程的思想
llm·github·agent
Java之美3 天前
vLLM 是怎么工作的?
llm
JouYY3 天前
聊一下多 Agent 编排架构的应用实践
架构·llm·agent
To_OC5 天前
数据集划分不是随便切:手把手切分大众点评情感数据集
人工智能·llm·agent
weiwei228445 天前
神经网络模型导出及开放标准格式ONNX
pytorch·onnx