共计 2876 个字符,预计需要花费 8 分钟才能阅读完成。
为什么选择 ONNX?
ONNX(Open Neural Network Exchange)是 AI 模型跨平台部署的桥梁。它允许将训练好的模型从 PyTorch/TensorFlow 等框架导出为统一格式,在 C# 等非 Python 环境中高效运行。对.NET 开发者而言,这意味着可以直接在生产环境使用 Python 训练的先进模型,无需复杂的语言桥接。

模型转换:TensorFlow/PyTorch → ONNX
- TensorFlow 转换:
- 优势:
tf2onnx工具链成熟,对 CNN 类模型支持完善 - 劣势:动态计算图模型(如 RNN)可能需手动调整 opset 版本
-
C#兼容性:需注意
Placeholder节点命名是否符合 C# 变量命名规范 -
PyTorch 转换:
- 优势:原生支持
torch.onnx.export,转换过程更直观 - 劣势:自定义算子需注册符号函数
- C# 兼容性:推荐使用固定输入尺寸(避免动态轴引发运行时异常)
转换建议:使用
onnxruntime官方模型库(如 resnet50)作为入门测试基准
实战四步曲
1. 环境搭建
# 在 NuGet 包管理器控制台
Install-Package Microsoft.ML.OnnxRuntime -Version 1.15.0
Install-Package Microsoft.ML.OnnxRuntime.Gpu # 如需 GPU 支持
2. 模型加载(含健壮性处理)
try
{
// 使用 using 确保资源释放
using var session = new InferenceSession("model.onnx",
SessionOptions.MakeSessionOptionWithCudaProvider(0)); // GPU 设备 0
// 检查输入节点维度
var inputMeta = session.InputMetadata;
var expectedShape = inputMeta["input"].Dimensions; // 假设输入节点名为 "input"
if(expectedShape[0] != 1)
throw new ArgumentException("仅支持 batch_size= 1 的推理");
}
catch (OnnxRuntimeException ex)
{
// 典型错误:模型版本不兼容 / 缺少 CUDA 依赖
Console.WriteLine($"ONNX 加载失败:{ex.Message}");
}
3. 预处理 Pipeline
// 图像→Tensor 标准化流程
static float[] PreprocessImage(Image<Rgb24> image, int targetWidth, int targetHeight)
{
// 1. 等比例缩放 + 中心裁剪
image.Mutate(x => x
.Resize(new ResizeOptions {Size = new Size(targetWidth, targetHeight),
Mode = ResizeMode.Pad
})
.Crop(new Rectangle((image.Width - targetWidth)/2,
(image.Height - targetHeight)/2,
targetWidth,
targetHeight
)));
// 2. 像素值归一化[0,255]→[0,1]
var tensor = new DenseTensor<float>(new[] {1, 3, targetHeight, targetWidth});
for (int y = 0; y < targetHeight; y++)
for (int x = 0; x < targetWidth; x++)
for (int c = 0; c < 3; c++)
tensor[0, c, y, x] = image[x, y].GetComponent(c) / 255f;
return tensor.Buffer.ToArray();}
4. 推理与后处理
// 线程安全推理(推荐单例 Session+ 多线程 Run)lock (_inferenceLock)
{
using var inputs = new List<NamedOnnxValue>
{NamedOnnxValue.CreateFromTensor("input", inputTensor)
};
using var results = session.Run(inputs);
var output = results.First().AsTensor<float>();
// 获取 Top- 5 分类结果
var scores = output.Buffer.Span;
var top5 = scores.ToArray()
.Select((score, idx) => new {Label = idx, Score = score})
.OrderByDescending(x => x.Score)
.Take(5);
}
性能调优秘籍
Session 配置黄金法则
var options = new SessionOptions
{
GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL,
ExecutionMode = ExecutionMode.ORT_PARALLEL,
IntraOpNumThreads = Environment.ProcessorCount / 2, // 避免 CPU 过载
InterOpNumThreads = 1 // 大多数模型不需要跨 op 并行
};
GPU 加速实测数据(ResNet50@RTX3090)
| 批次大小 | CPU 耗时(ms) | GPU 耗时(ms) | 加速比 |
|---|---|---|---|
| 1 | 45 | 6 | 7.5x |
| 8 | 320 | 28 | 11.4x |
| 16 | 638 | 51 | 12.5x |
注:小模型(如 MobileNet)在 CPU 上可能更高效
避坑指南
版本兼容性
- 使用
onnxruntime1.8+ 时,建议 opset 版本≥11 - 遇到
Unsupported ONNX opset version时,用python -m tf2onnx.convert --opset 13重导模型
内存泄漏排查
- 用
GC.GetTotalMemory(true)监控推理前后内存变化 - 确保所有
IDisposable对象(如InferenceSession、DisposableNamedOnnxValue)被正确释放
量化模型妙用
# 生成 INT8 量化模型(体积缩小 4 倍,CPU 推理提速 2 - 3 倍)python -m onnxruntime.quantization.preprocess \
--input model.onnx --output model.quant.onnx
进阶思考
- 动态批处理:可通过
IEnumerable<NamedOnnxValue>收集多个请求,当达到optimal_batch_size时一次性推理 - 可变输入尺寸:使用
ResizableArray作为输入缓冲,配合OnnxRuntime.Extension中的动态形状支持
结语
通过 ONNX Runtime,我们成功在 C# 中实现了 ResNet50 图像分类的完整流程。相比直接调用 Python 服务,本地推理的延迟降低了一个数量级。下一步可以尝试将模型封装为 gRPC 微服务,或探索 ONNX 的模型压缩工具链。
正文完
