ONNX Runtime 中 Phi-2 模型的 ONNX 导出与优化实战:从 convert_to_onnx 脚本到 ORT 推理示例
2026/9/13 22:44:35 网站建设 项目流程

ONNX Runtime 中 Phi-2 模型的 ONNX 导出与优化实战:从 convert_to_onnx 脚本到 ORT 推理示例

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

本文基于 ONNX Runtime 仓库中 Phi-2 转换示例 展开,完整覆盖其前置条件、全部导出场景命令与运行示例,并结合 convert_to_onnx.py 与 inference_example.py 的源码,深入讲解每个导出参数背后的注意力算子选择、精度转换、INT4 量化与 CUDA Graph 优化原理,帮助读者掌握将 LLM 高效导出并跑通 ORT 推理的完整链路。

需要先说明的是:该示例在 README 首行和脚本入口 main() 中均已标记为Deprecated,官方建议使用 Olive recipes 替代。但作为理解 ONNX Runtime 针对 Phi-2 这类 2.7B 级 LLM 的“Dynamo 导出 + 图优化 + 量化 + IO Binding 推理”全流程的经典样本,它仍然是仓库内最完整的端到端参考实现。

一、前置条件与环境准备

README 列出的前置条件如下,均可在仓库中找到对应依据:

  1. 操作系统:必须使用 Linux。原因是导出依赖 PyTorch 的 TorchDynamo-based ONNX Exporter(torch.onnx.dynamo_export),README 的 Limitations 一节明确写到“TorchDynamo-based ONNX Exporter only supports Linux”。

  2. 依赖安装:在onnxruntime/python/tools/transformers目录下执行pip install -r requirements.txt。Phi-2 目录自身的 requirements.txt 内容精简,仅 3 项:

    onnx==1.22.0 transformers>=4.36.2 onnxscript>=0.1.0.dev20240126

    实际转换还依赖上级目录 transformers 工具集 及其余 PyTorch 环境。

  3. PyTorch 版本:导出 ONNX 要求 PyTorch 2.2.0 或更高版本(README 原话),需自行选择与 CUDA 11.8 / 12.1 匹配的安装包。

  4. ONNX Runtime 版本:README 给出安装onnxruntime-gpu==1.17.0(CUDA 11.8 默认构建)或 CPU 版onnxruntime==1.17.0的命令;CUDA 12.x 用户需按官方安装说明选择对应 wheel。

源码中还有一个值得注意的版本兼容细节:convert_to_onnx.py 会按 ORT 版本切换 INT4 量化器导入路径——ORT < 1.22.0 时从onnxruntime.quantization.matmul_4bits_quantizer导入MatMul4BitsQuantizer,否则从onnxruntime.quantization.matmul_nbits_quantizer导入MatMulNBitsQuantizer。也就是说在新版 ORT 中,该脚本实际调用的是通用 N-bits 量化器 matmul_nbits_quantizer.py。

二、两种运行方式:From Source 与 From Wheel

README 提供了两套等价的运行入口,区别在于代码位置:

方式一:从源码运行(From source)

# 默认 onnxruntime 包基于 CUDA 11.8 构建;CUDA 12.x 请参考官方安装文档 pip install onnxruntime-gpu==1.17.0 # CPU 场景用 onnxruntime==1.17.0 git clone git@github.com:microsoft/onnxruntime.git cd onnxruntime/onnxruntime/python/tools/transformers python -m models.phi2.convert_to_onnx -h

其能运行的关键在于 models/phi2/init.py:包初始化时通过sys.path.append把自身目录及上级transformers工具目录加入模块搜索路径,因此脚本内才能直接from benchmark_helper import Precisionfrom fusion_options import AttentionOpTypefrom onnx_model import OnnxModel(见 convert_to_onnx.py 第 15-17 行)。

方式二:从 wheel 运行(From wheel)

安装 ORT nightly 包后:

python -m onnxruntime.transformers.models.phi2.convert_to_onnx -h

后文所有命令均可按这两种入口互换模块前缀(models.phi2.onnxruntime.transformers.models.phi2.),README 中的每一组命令都成对给出了两套写法。

三、导出场景全景:7 类模型 × 4 种注意力算子

README 的核心内容是一张“场景-命令”清单。以下先完整继承原文所有命令,再结合源码解释每个场景实际产出的模型差异。

1. 为 NVIDIA GPU 导出 FP32 模型

python -m models.phi2.convert_to_onnx --fp32_gpu # From source python -m onnxruntime.transformers.models.phi2.convert_to_onnx --fp32_gpu # From wheel

2. 为 NVIDIA GPU 导出 FP16 模型

python -m models.phi2.convert_to_onnx --fp16_gpu python -m onnxruntime.transformers.models.phi2.convert_to_onnx --fp16_gpu

3. 为 NVIDIA GPU 导出 INT4 模型

python -m models.phi2.convert_to_onnx --int4_gpu python -m onnxruntime.transformers.models.phi2.convert_to_onnx --int4_gpu

4. 为 SM=80~89 的 GPU 导出 FP16 模型

python -m models.phi2.convert_to_onnx --fp16_gpu_sm8x python -m onnxruntime.transformers.models.phi2.convert_to_onnx --fp16_gpu_sm8x

5. 为 SM=80~89 的 GPU 导出 INT4 模型

python -m models.phi2.convert_to_onnx --int4_gpu_sm8x python -m onnxruntime.transformers.models.phi2.convert_to_onnx --int4_gpu_sm8x

6. 为 CPU 导出 FP32 模型

python -m models.phi2.convert_to_onnx --fp32_cpu python -m onnxruntime.transformers.models.phi2.convert_to_onnx --fp32_cpu

7. 为 CPU 导出 INT4 模型

python -m models.phi2.convert_to_onnx --int4_cpu python -m onnxruntime.transformers.models.phi2.convert_to_onnx --int4_cpu

8. 一次性导出全部场景

python -m models.phi2.convert_to_onnx --fp32_cpu --int4_cpu --fp32_gpu --fp16_gpu --int4_gpu --fp16_gpu_sm8x --int4_gpu_sm8x python -m onnxruntime.transformers.models.phi2.convert_to_onnx --fp32_cpu --int4_cpu --fp32_gpu --fp16_gpu --int4_gpu --fp16_gpu_sm8x --int4_gpu_sm8x

从源码结构看,每个场景参数都映射到 main() 中model_type_to_args字典的一个三元组:(AttentionOpType, Precision, 输出路径)

场景参数融合后注意力算子精度输出文件(默认目录phi2_onnx_models/
--fp32_cpuMultiHeadAttentionFP32phi2_decoder_fp32_cpu.onnx
--int4_cpuMultiHeadAttentionINT4phi2_decoder_int4_cpu.onnx
--fp32_gpuAttentionFP32phi2_decoder_fp32_gpu.onnx
--fp16_gpuAttentionFP16phi2_decoder_fp16_gpu.onnx
--int4_gpuAttentionINT4phi2_decoder_int4_gpu.onnx
--fp16_gpu_sm8xGroupQueryAttentionFP16phi2_decoder_fp16_gpu_sm8x.onnx
--int4_gpu_sm8xGroupQueryAttentionINT4phi2_decoder_int4_gpu_sm8x.onnx

四种注意力算子定义在 fusion_options.py 的AttentionOpType枚举中:AttentionMultiHeadAttentionGroupQueryAttentionPagedAttention。结合 FusionOptions 的注释可以推断其取舍逻辑:Attention算子将 Q/K/V 三个投影权重合并为一次 MatMul,在 GPU 上更快但仅支持自注意力,因此所有 GPU 场景默认使用它;MultiHeadAttention可同时处理自/交叉注意力,作为 CPU 场景(及非合并权重路径)的选择;GroupQueryAttention(GQA)是 Phi-2 架构(32 个 KV head 分组共享)对应的贡献算子,sm8x系列场景使用它,因为 CUDA EP 的 GQA 内核要求 SM 80~89 计算能力(Ampere 及以上)。源码中还有两个 README 未列出但脚本已支持的场景:--fp16_vllm/--int4_vllm,映射到PagedAttention算子、供 ORT VLLM 后端使用。

场景间不是串行的:main() 使用multiprocessing.Process为每个勾选的场景启动独立进程并发执行优化,因此“一次性导出全部”时各场景相互独立、同时落盘,这也是 README 提到每个优化进程约需 4.5GB 内存的原因。

四、导出流水线源码解析:Dynamo 导出、图优化与精度转换

整个转换由ConvertPhi2ToONNX类(convert_to_onnx.py)驱动,固定流程为:Dynamo 导出原始 ONNX → 按场景图优化 → 精度转换 / 量化 → 落盘

1. Dynamo 导出(dynamo_export)

  • 模型来自microsoft/phi-2AutoConfig/AutoModelForCausalLMtrust_remote_code=True,缓存目录由--cache_dir控制,默认./cache);
  • 构造 dummy 输入:batch_size=2sequence_length=8的随机input_ids,并通过prepare_inputs_for_generation生成attention_maskpast_key_values,保证导出的是带 KV cache 的 decoder 形态;
  • 关键调用是torch.onnx.dynamo_export(..., export_options=torch.onnx.ExportOptions(dynamic_shapes=True)),开启动态形状以支持变长序列;
  • 导出后执行onnx.checker.check_modelonnx.shape_inference.infer_shapes_path做合法性与形状推断。

2. 图优化(optimize_phi2_onnx)

  • 构造FusionOptions("phi")set_attention_op_type(self.attn_op_type),再调用上层 optimizer.py 的optimize_model(..., model_type="phi", num_heads=..., hidden_size=..., opt_level=0, only_onnxruntime=False);Phi-2 的num_attention_headshidden_size直接取自 HuggingFace config;
  • 优化完成后通过get_fused_operator_statistics()+is_fully_optimized()判断是否融合到位并打日志(“Model is fully optimized.”);
  • FP32 场景到此直接存盘返回
  • FP16 / INT4 场景:先调用optimizer.convert_float_to_float16(...)做半精度转换,源码注释明确“最后三层 Attention 保持 FP32/bfloat16 以避免溢出”——具体是把Attention_29/30/31节点列入node_block_list阻断转换;当算子类型为GroupQueryAttention时,被阻断的节点改用 bfloat16(use_bfloat16_as_blocked_nodes_dtype=True);
  • INT4 场景:在半精度图之上再执行MatMulNBitsQuantizer(model=..., bits=4, block_size=self.block_size, is_symmetric=True, accuracy_level=self.accuracy_level),然后quant.process()并保存。block_size默认 16(--block_size参数,parse_arguments 的 help 指向量化器文档),--int4_accuracy_level控制MatMulNBits贡献算子的accuracy_level属性(算子说明见 ContribOperators.md 中com.microsoft.MatMulNBits一节)。INT4 权重的解码发生在 GPU 内核内,因此该格式主要面向 GPU EP。

模型统一以save_model_to_file(..., use_external_data_format=True)保存,权重写入外部数据文件。若目标.onnx已存在,脚本会先通过 erase_onnx_model 删除旧模型及其外部数据文件(除非显式传入--overwrite)。

3. CUDA Graph 适配(convert_to_use_cuda_graph)

--use_cuda_graph时必须同时使用--fp16_gpu_sm8x--int4_gpu_sm8x(源码中有 assert 强制),它会对 GQA 模型做图改造:新增seqlens_k(INT32,shape[batch_size])与total_sequence_length(INT32,shape[1])两个图输入,删除原本在GroupQueryAttention节点内部计算 seqlens 的Cast-Sub-ReduceSum-Cast子图与Cast-Gather-Shape子图,改由所有 GQA 节点直接消费这两个外部输入。这样做是把“依赖 attention_mask 的变长逻辑”外提为显式张量,使图结构在逐 token 解码时保持静态,从而可被 CUDA Graph 捕获复用。

五、用 ORT 运行推理示例与基准测试

README 给出的端到端运行示例(以 SM8x 场景为例):

python -m models.phi2.convert_to_onnx --fp16_gpu_sm8x --int4_gpu_sm8x --run_example python -m onnxruntime.transformers.models.phi2.convert_to_onnx --fp16_gpu_sm8x --int4_gpu_sm8x --run_example

README 注明“inference example currently supports all models running on CUDA”,这与源码一致:main() 对--run_example按场景分发——sm8x系列使用use_buffer_share=True, use_step=True;普通 GPU 场景(fp32_gpu/fp16_gpu/int4_gpu)使用packed_kv=True(KV 打包成(2, batch, heads, seq, head_dim)单张量)且默认 FP16 IO;而fp32_cpu/int4_cpu/vllm场景会直接抛出NotImplementedError("CPU/vllm inference example is not implemented yet.")

推理实现全部在 inference_example.py 的ORTGenerator类中,它是学习 LLM 上 ORT IO Binding 的最佳范例:

  • 会话创建(create_session):device_id >= 0时用("CUDAExecutionProvider", {"device_id": ..., "enable_cuda_graph": self.use_cuda_graph}),否则回退 CPU EP;并从microsoft/phi-2加载 tokenizer(pad_token="[PAD]")。Phi-2 结构参数被硬编码为num_heads=32head_size=80num_layers=32max_sequence_length=2048、词表 logits 宽度 51200;
  • IO Binding(apply_io_binding):所有输入以buffer_ptr=v.data_ptr()绑定 GPU 张量地址,输出绑定到预分配缓冲;use_buffer_share=True时,present_*输出直接绑定到对应的past_*输入缓冲(name.replace("present", "past")),实现 KV cache 原地更新,免去每步分配;
  • 逐 token 解码循环(generate_impl):io_binding.synchronize_inputs()sess.run_with_iobinding(io_binding, self.ro)synchronize_outputs();采样采用贪心argmax,处理 EOS 与 ragged batching(提前结束的样本填充 EOS token),每步把logits缓冲清零复用;
  • CUDA Graph 的启用时序:prompt 阶段通过ro.add_run_config_entry("gpu_graph_id", "-1")关闭 graph,解码阶段切回注解 id;配合use_traced_inputs(要求 CUDA Graph + 静态输入 + buffer share + 非 packed KV),输入/输出指针固定在static_inputs_map中,每步仅用cudaMemcpy拷贝新 token(见 cuda_memcpy);
  • 默认 demo 与 benchmarkrun_phi2内置一个代码补全 prompt(print_prime函数),max_length=210;加--run_benchmark后,先 warm-up 再对batch_size ∈ {1,2,4,8} × sequence_length ∈ {16,512}、固定生成 32 个 token 的组合计时,打印 Prompt latency 与平均 Token latency。

六、脚本完整参数速查

除 README 覆盖的场景开关外,parse_arguments 还暴露了若干调优参数:

参数类型 / 默认值作用
--fp32_cpu/--int4_cpu/--fp32_gpu/--fp16_gpu/--int4_gpu/--fp16_gpu_sm8x/--int4_gpu_sm8xflag选择导出场景(见上表)
--fp16_vllm/--int4_vllmflag导出 PagedAttention 模型供 ORT VLLM 使用(README 未列出)
--use_cuda_graphflag对 sm8x 模型做 CUDA Graph 图改造(需与 sm8x 场景联用)
--overwriteflag覆盖已存在的 ONNX 模型文件
--cache_dirstr,默认./cachePyTorch 模型缓存目录
--device_idint,默认0PyTorch 模型所用 GPU 编号;同时决定推理示例的 EP 选择
--run_exampleflag转换后运行 ORT 推理示例
--run_benchmarkflag运行基准测试(batch 1/2/4/8 × seq 16/512)
--skip_exportflag跳过 Dynamo 导出(复用已有phi2_original.onnx,仅做优化/运行)
--output_dirstr,默认phi2_onnx_models模型输出目录
--block_sizeint,默认16INT4 量化的 block size
--int4_accuracy_levelintMatMulNBits算子的 accuracy_level 属性

七、限制与注意事项

综合 README 的 Limitations 一节与源码行为,使用本示例时需注意:

  1. 仅限 Linux:Dynamo 导出器不支持其他平台;
  2. 内存占用大:README 给出实测参考——Dynamo 导出约消耗 11.6GB 内存,每个优化进程约 4.5GB;由于多场景是并发进程执行,“all at once” 模式的峰值内存需求应按并发进程数叠加评估;
  3. sm8x 场景对 GPU 有硬要求GroupQueryAttention内核面向 CUDA 架构 SM=80~89,老架构(如 SM=75 的 Turing)请改用普通--fp16_gpu/--int4_gpu场景;
  4. 示例覆盖范围:推理示例只实现 CUDA 路径,CPU / vllm 场景会显式抛NotImplementedError;INT4 场景的推理示例仅覆盖 GPU;
  5. 示例已弃用:脚本一运行就会发出DeprecationWarning指向 Olive recipes,生产环境建议以 Olive 工作流为准,本示例更适合阅读源码、理解导出与优化链路。

八、关键文件索引

  • 示例 README(本文主体来源):onnxruntime/python/tools/transformers/models/phi2/README.md
  • 转换入口与全部 CLI 参数:convert_to_onnx.py
  • ORT 推理与基准测试实现:inference_example.py
  • 场景依赖:requirements.txt
  • 注意力算子类型与融合选项:fusion_options.py
  • 图优化器主体:optimizer.py
  • INT4 量化器实现:matmul_nbits_quantizer.py
  • MatMulNBits贡献算子说明:ContribOperators.md

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询