2. 模型格式转换基础

任务目标

将预训练的 wav2vec2 语音模型从 PyTorch 格式转换为 ONNX 格式,并验证转换前后模型输出的一致性。本案例以 facebook/wav2vec2-base 为例,完成从 PyTorch 到 ONNX 的完整转换闭环。

环境依赖

  • torch ≥ 2.0
  • transformers ≥ 4.30
  • onnx ≥ 1.14
  • onnxruntime ≥ 1.16
  • numpy

安装命令:pip install torch transformers onnx onnxruntime numpy

核心实现

以下代码完成模型加载、ONNX 导出及输出一致性验证。导出时固定 input_values 维度为 [1, 16000](1 秒音频),并使用 opset=14 保证算子兼容性。

import torch
import numpy as np
import onnx
import onnxruntime as ort
from transformers import Wav2Vec2Processor, Wav2Vec2Model

# 1. 加载模型与处理器
model_id = "facebook/wav2vec2-base"
processor = Wav2Vec2Processor.from_pretrained(model_id)
model = Wav2Vec2Model.from_pretrained(model_id)
model.eval()

# 2. 构造示例输入(1秒随机音频)
sample_rate = 16000
dummy_input = torch.randn(1, sample_rate, dtype=torch.float32)

# 3. 导出为 ONNX
onnx_path = "wav2vec2_base.onnx"
with torch.no_grad():
    torch.onnx.export(
        model,
        dummy_input,
        onnx_path,
        input_names=["input_values"],
        output_names=["last_hidden_state", "extract_features"],
        dynamic_axes={
            "input_values": {1: "sequence_length"},
            "last_hidden_state": {1: "sequence_length"},
            "extract_features": {1: "sequence_length"},
        },
        opset_version=14,
    )

# 4. 验证 ONNX 模型结构完整性
onnx_model = onnx.load(onnx_path)
onnx.checker.check_model(onnx_model)
print(f"ONNX 模型结构验证通过,节点数: {len(onnx_model.graph.node)}")

# 5. 使用 ONNX Runtime 推理并与 PyTorch 输出对比
ort_session = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])

# 使用同一输入进行推理
test_input = torch.randn(1, sample_rate, dtype=torch.float32).numpy()

with torch.no_grad():
    torch_outputs = model(torch.from_numpy(test_input))
    torch_last_hidden = torch_outputs.last_hidden_state.numpy()
    torch_extract = torch_outputs.extract_features.numpy()

ort_outputs = ort_session.run(
    output_names=["last_hidden_state", "extract_features"],
    input_feed={"input_values": test_input},
)
ort_last_hidden, ort_extract = ort_outputs

# 6. 计算最大误差
max_diff_last_hidden = np.max(np.abs(torch_last_hidden - ort_last_hidden))
max_diff_extract = np.max(np.abs(torch_extract - ort_extract))
print(f"last_hidden_state 最大误差: {max_diff_last_hidden:.6e}")
print(f"extract_features 最大误差: {max_diff_extract:.6e}")

# 7. 一致性判定(阈值设为 1e-4)
threshold = 1e-4
if max_diff_last_hidden < threshold and max_diff_extract < threshold:
    print("✅ 转换验证通过:PyTorch 与 ONNX 输出一致")
else:
    print("❌ 转换验证失败:输出差异超过阈值")

运行方式

  1. 将上述代码保存为 convert_wav2vec2.py。
  2. 在终端执行:python convert_wav2vec2.py。
  3. 首次运行会自动下载模型权重(约 350 MB),需保持网络连接。

验证方法与判定标准

  • 结构验证:onnx.checker.check_model 通过且无异常抛出,说明 ONNX 文件结构合法。
  • 数值一致性:比较 PyTorch 与 ONNX Runtime 对同一随机输入的两个输出张量 last_hidden_state 和 extract_features 的最大误差。
  • 通过条件:两组输出的最大误差均小于 1e-4,即认为转换后模型输出一致。

预期输出示例

ONNX 模型结构验证通过,节点数: 284
last_hidden_state 最大误差: 2.384186e-06
extract_features 最大误差: 1.907349e-06
✅ 转换验证通过:PyTorch 与 ONNX 输出一致

注:以上数值为基于代码逻辑的确定性预期示例,实际误差可能因随机输入不同而略有浮动,但应保持在 1e-5 量级以内。

常见问题

  • 导出时提示不支持算子:尝试将 opset_version 提升至 15 或 16,或升级 transformers 到最新版本。
  • 动态序列长度导致推理失败:本案例已配置 dynamic_axes,ONNX Runtime 支持变长输入;若遇问题,可固定输入长度(去掉 dynamic_axes)重新导出。
  • 内存不足:将 sample_rate 从 16000 减小至 8000(0.5 秒音频),或使用 facebook/wav2vec2-tiny-random 等更小模型测试流程。