4.3 ONNX 导出与推理


4.3 ONNX 导出与推理

本节摘要:部署的最后一步减法:把检查点从「PyTorch 模型加完整训练栈」变成「ONNX 文件加轻量推理运行时」。本节先回答「什么时候值得」——目标环境装不动 torch、需要嵌进别的进程、边缘与固定运行时场景;然后走导出流程(torch.onnx.export 的写法示意,以官方文档为准);再用 onnxruntime 在 CPU 上跑通推理,并给出原生推理、ONNX 推理与 laya-serve 三种形态的延迟量级对照(示意值,须自测);最后划清边界:ONNX 文件只是检查点的工件,Router、批量、服务化这些外围能力不会随文件走,需要自己包一层。不是所有部署都该上 ONNX——多数场景 laya-serve 已经够用。

学习目标

  • 判断自己的场景值不值得上 ONNX(三个典型信号)。
  • 走通导出流程并在 onnxruntime 里完成一次 CPU 推理(写法示意)。
  • 说出三种推理形态的延迟量级关系(示意值口径)。
  • 划清 ONNX 工件与 laya 外围能力(Router、批量、服务)的边界。

一、什么时候值得上 ONNX

laya 的标准形态是「PyTorch 检查点」,运行它意味着装一套含 torch 的 Python 栈。大多数场景这不是问题,但三类信号出现时,ONNX 值得考虑:

信号 场景 ONNX 给的答案
目标环境装不动训练栈 嵌入式设备、极简容器、受限运行时 onnxruntime 依赖远轻于 torch
推理要嵌进别的进程 C# / Java / C++ 服务想进程内调用 onnxruntime 有多语言绑定
运行时要钉死 认证环境禁装新 Python 包族 一个模型文件加一个运行时库

反向的清醒剂也要备着:如果只是「一台 Linux 服务器跑决策服务」,laya-serve 加 Docker(4.1 与 4.2 节)就是更省事的答案,ONNX 只会多一道导出与对齐的工序。ONNX 是特殊场景的减法,不是默认的优化。

再列三个「看起来该上、其实不该」的反例:

反例场景 为什么不该 正确答案
想要更快的在线服务 速度差异不确定(同量级,示意) 先测 laya-serve 延迟是否真是瓶颈
团队只会 Python 但听说 ONNX 快 换运行时不解决代码组织问题 用 SDK,把工程做好
检查点还在频繁微调迭代 每版都要导出加验证,工序翻倍 等模型稳定(第 5 章收敛)再导出

二、导出流程

# export_onnx.py —— 检查点导出为 ONNX(写法示意,API 以 laya 与 torch 官方文档为准) import torch from laya import load # 载入目标检查点(此处以多语言检查点为例;名称以官方文档为准) model = load("laya-multilingual") model.eval() # 一组与真实调用同形状的样例输入,供追踪导出图 dummy_input = model.example_inputs() # 示意接口:以官方文档的导出说明为准 torch.onnx.export( model, # 待导出的模型 dummy_input, # 样例输入(决定图的形状) "laya-multilingual.onnx", # 输出文件 input_names=["input_ids", "attention_mask"], dynamic_axes={ # 序列长度与批量维度留动态 "input_ids": {0: "batch", 1: "seq"}, "attention_mask": {0: "batch", 1: "seq"}, }, opset_version=17, # 算子集版本以实测兼容为准(示意值) ) print("导出完成: laya-multilingual.onnx") ​

三个要领。样例输入要「像真的」:dynamic_axes 把批量与序列长度留成动态,但极端形状(超长文本)仍可能在图上不被算子支持,导出后要拿业务里最长最短的样本各验一遍。算子集版本是兼容性旋钮:报「不支持的算子」时先试调 opset,再查该检查点底座的官方导出说明。导出后先对答案:同一批样本在原生模型与 ONNX 上各跑一遍,数值应在浮点误差内一致——对不上就别用,这是硬门槛。

「对答案」可以固化成十行验证脚本,每次重新导出后必跑:

# verify_parity.py —— 原生与 ONNX 的数值一致性验证(写法示意) import numpy as np def parity_check(native_out, onnx_out, tol=1e-3): a = np.asarray(native_out, dtype=np.float32) b = np.asarray(onnx_out, dtype=np.float32) diff = float(np.max(np.abs(a - b))) # 逐元素最大绝对差 ok = diff <= tol print("最大偏差:", diff, "通过" if ok else "不通过(禁止上线该工件)") return ok # 用同一批样例输入分别过原生检查点与 onnxruntime, # 把两侧决策头输出喂给 parity_check;tol 为示意值,按精度要求收紧。 ​

三、onnxruntime CPU 推理

# onnx_infer.py —— ONNX 工件的 CPU 推理(写法示意) import numpy as np import onnxruntime as ort session = ort.InferenceSession("laya-multilingual.onnx", providers=["CPUExecutionProvider"]) def infer(token_ids, attention_mask): outputs = session.run(None, { "input_ids": np.asarray(token_ids, dtype=np.int64), "attention_mask": np.asarray(attention_mask, dtype=np.int64), }) return outputs # 决策头的原始输出,softmax 与后处理由调用方完成 # 分词仍需检查点配套的分词器:ONNX 只搬走了计算图,文本到 token 的 # 前处理要沿用 laya 侧的配套组件(以官方文档为准),别自己另造一套。 ​

两个容易被低估的工序。前处理对齐:分词必须与导出模型配套,分词器不一致等于换了题面;后处理补齐:ONNX 输出的是决策头原始张量,softmax、按选项切片这些收尾逻辑要由包这一层的代码完成——也就是把第 1.2 节讲的返回形态自己「装修」出来。

四、延迟对照与结论

三种形态的量级关系(示意值,以自测为准):

形态 组成 延迟量级 备注
SDK 进程内(原生) torch 推理 基准(官方 README 口径 32.8 毫秒) 功能全
ONNX 进程内 onnxruntime 与原生同量级,部分环境更快(示意) 依赖轻
laya-serve HTTP 服务加网络往返 原生延迟加网络开销(毫秒级,示意) 跨系统

结论分三层。第一,ONNX 不等于更快:同硬件上 onnxruntime 与 torch 的差距通常在个位数百分比到可感知之间浮动,方向与幅度取决于算子与硬件(量级判断,勿当承诺);它的确定性收益是「依赖变轻、运行时可移植」。第二,要延迟数字就自己测:同一批样本、同一台机器,两种形态各计时三次取中位数——这个自测动作和第 3.2 节批量基准一样,是选型证据而不是仪式。第三,HTTP 的一跳开销通常是毫秒级(示意值),对大多数业务可忽略,别为了省这一跳放弃服务化的运维收益。

五、边界提醒:工件不等于引擎

ONNX 文件装走的是「一次前向的计算图」,laya 引擎的其余能力都不会随文件走:

laya 引擎的能力面 ONNX 工件带走的 ┌────────────────────┐ ┌────────────────┐ │ Router 与脚本检测 │ │ 检查点的前向图 │ │ 三检查点管理 │ │ (单检查点) │ │ predict_batch 批量 │ └────────────────┘ │ decide 封装与置信 │ 自己包一层: │ laya-serve 服务 │ 路由、分词对齐、 │ CLI 与 SDK │ softmax 收尾、批量 └────────────────────┘ ​

所以上 ONNX 的正确姿势是「为特殊运行时重造一个薄壳」,而不是「把 laya 换成 onnx 文件」:路由逻辑(第 2 章)要自己移植或简化成固定检查点,批量与置信门控(第 3 章与第 7 章)要在壳里重建。工作量可控,但要计入决策——当这个薄壳的维护成本超过「装一套 torch」时,答案就不该是 ONNX。

本节要点回顾

  • 三个上 ONNX 的信号:装不动训练栈、嵌进别的进程、运行时要钉死;普通服务器场景用 laya-serve 就够。
  • 导出要领:样例输入像真的、dynamic_axes 留动态、导出后原生与 ONNX 数值对答案。
  • 推理两工序:分词与导出模型配套对齐,softmax 与切片的后处理自己补。
  • 延迟结论:ONNX 的确定性收益是轻依赖与可移植,不是速度承诺;数字自测。
  • 边界:工件只含单检查点前向图,Router、批量、服务化要在薄壳里重建。

用对 laya 的完整链路——概念、检查点与路由、SDK 与 CLI、服务化与容器化、乃至 ONNX 工件——到本节全部走完。接下来换一个问题:当底座检查点的 zero-shot 表现不能满足业务(官方声明它接近随机猜),怎么用 RLCD 微调把它变成懂业务的决策器。下一章进入微调。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U