一个基本问题:当我们谈论移植的时候,我们聊的是 ONNX 还是自定义算子?

这篇笔记不讨论哪条路线“更先进”,只记录一个实际选型问题:模型离开训练框架以后,究竟应该交给通用推理引擎,还是把它改写成项目自己的 C++ 实现。 面试时我常问这个问题。我关心的是,对方能不能把“能跑”和“适合长期维护”分开看。很多项目一开始只是照着现成方案接进去,等到包体积、启动时间或某个算子性能出了问题,才发现当初其实已经做了取舍。 所以这里的“移植”,不只是换一个文件格式。它更像是在两套成本之间做选择:一套成本由运行时和通用性承担,另一套成本由自己的代码、测试和维护承担。 一、ONNX 到底做了一件什么事 先从模型离开训练框架后的处境说起。 我们在 PyTorch 里训练模型,享受的是动态图、Python 生态、随时 print 随时调试。但部署环境——一个可能没有 Python、没有 torch、甚至没有文件系统的进程——什么都没有。训练与部署之间需要一次"交接"。交接最朴素的方式是序列化:把权重存下来,把结构描述出来。问题在于,每家框架的序列化格式都是私有方言:PyTorch 有 TorchScript 和 .pt,TensorFlow 有 SavedModel 和 .pb,互不相认。而部署端的推理引擎又层出不穷:ONNX Runtime、TensorRT、OpenVINO、CoreML、DirectML、各家 NPU 的私有 SDK……如果每个框架都要为每种引擎各写一条导出通道,对接复杂度是 N × M 的;在中间垫一层公共格式,它就变成 N + M。 ONNX(Open Neural Network Exchange)就是这层公共格式。具体来说,它负责三件事: 定义了一套标准算子集(operator set,简称 opset):Conv、Gemm、BatchNormalization、Relu……每个算子的语义、输入输出、数值行为都有书面规范,并按版本演进(opset 13、17、18……)。 定义了一张计算图格式:用 protobuf 描述节点(NodeProto)、张量(ValueInfoProto)、权重(Initializer),以及它们之间的连接关系。一个 .onnx 文件本质上就是一段序列化后的 protobuf。 定义了类型与张量规范:数据类型、布局约定、广播规则。 可以把它看成模型领域的交换文件:训练框架负责把图和权重写出来,推理引擎负责把它读进去。双方不必直接适配彼此,只要共同遵守这份格式即可。它能普及,主要也是因为这个生态位置,而不是因为它在每一种设备上都能做到最好。 这里有一个几乎所有初学者都会混淆、但一问就露馅的点:ONNX 只是一个格式,它自己不会执行任何东西。真正让模型跑起来的是推理引擎,最常见的就是 ONNX Runtime(下文简称 ORT)。前者是协议,后者是实现,就像 JSON 和某个 JSON 解析器的关系。我们日常说"用 onnx 跑",省略掉的主语几乎都是 ONNX Runtime。 那么这条路线的优势和代价分别是什么? 先看它为什么常常是默认选项。实际项目里,通常看重的是三点:格式标准,工具链成熟,以及性能能够满足要求。 标准:一次导出,处处可跑(至少在纸面上)。跨框架、跨引擎、跨操作系统。 成熟:ORT 背后是大量工程师十几年打磨的图优化器、算子融合 Pass,以及接驳成熟的 kernel 库(CPU 上有 MLAS 一系,移动端常用 XNNPACK)。你几乎不可能在同等工时内手写出比它更稳、覆盖面更广的实现。 快得足够:对绝大多数 PC 端、服务端场景,ORT 的默认性能已经在业务容忍范围之内;而且它提供 Execution Provider(EP)机制——CUDA、TensorRT、DirectML、CoreML、QNN——同一个 .onnx 几乎零改动就能下发到 GPU 甚至 NPU。 再说代价。代价不在"能不能跑",而在你为这份通用性交出的东西: ...

September 2, 2026 · 2 min · Konpaku Youran

ONNX - 它到底快在哪,又慢在哪

ONNX - 它到底快在哪,又慢在哪 一、ONNX 是什么 ONNX(Open Neural Network Exchange)本质上是一种模型的中间表示格式(IR),类似于编译器里的 LLVM IR。 用 PyTorch 训练完一个模型之后,模型的计算逻辑是用 Python 描述的,跑推理的时候要经过 Python 解释器、PyTorch 的调度器、再到底层的 CUDA kernel。这条链路很长,开销不小。ONNX 做的事情是:把模型的计算图从 PyTorch 的世界里"导出"成一个独立的、与框架无关的静态计算图,然后交给专门的推理引擎(比如 ONNX Runtime)去执行。 打个比方:PyTorch 训练出来的模型像一份 Python 脚本,每次执行都要解释器逐行翻译;ONNX 导出后的模型像一份编译好的二进制文件,直接跑就行。 一个典型的 ONNX 文件(.onnx)里面存的是: 计算图的拓扑结构(哪些算子、怎么连接) 每个算子的类型和参数(Conv、MatMul、Relu 等) 模型的权重(以 protobuf 格式序列化) 输入输出的 shape 和数据类型 这里要注意一点:ONNX 本身只是一个格式规范,它不负责执行。真正跑推理的是 ONNX Runtime(简称 ORT)或者其他兼容 ONNX 的推理引擎(TensorRT、OpenVINO 等)。说"用 ONNX 加速",实际上是"用 ONNX Runtime 加速"。 二、ONNX Runtime 为什么能快 理解了 ONNX 是什么之后,关键问题来了:为什么换个引擎跑就能快? 2.1 去掉了 Python 开销 PyTorch 推理时,即使模型本身的计算是在 GPU 上跑的,Python 层面仍然有大量开销: ...

March 13, 2026 · 3 min · Konpaku Youran

GTCRN 轻量化的流式方案的演进思路

GTCRN 演进路径 记录 v1 → v2 → v3 → v3.1/v3.2 → v4 → v4.1 的改动和原因。 版本概览 版本 改动点 参数量 质量指标 内存 实时 v1 baseline 基线 139K DNSMOS 3.15 — × v2 transient 换损失函数 139K DNSMOS 3.15 — × v3 causal 因果化改造 145K DNSMOS 2.98 — √ v3.1 precision KD + QAT 压缩 41.6K PESQ 2.041 228 KB (INT8) √ v3.2 transient 宽度1.5× + 瞬态损失 ~83K PESQ ~2.15 ~355 KB (INT8) √ v4 network opt 架构精简 (4层GTConv) ~87K PESQ 2.147 683 KB (FP32) √ v4.1 int8 INT8 混合精度 C 推理 ~87K PESQ 2.037 464 KB √ 网络结构 (v1/v2 共用) 输入 spec (B, 513, T, 2) │ ├─ 可学习频带权重 (513,) │ ▼ ERB_48k.bm(): 513 → 219 │ 低频171保留,高频342→48 ERB band │ ▼ SFE_Lite: DWConv(1×5) → PWConv → BN │ ▼ ┌─ Encoder ─────────────────────────────┐ │ DSConv: 219→110 (stride=2) ← skip1 │ │ DSConv: 110→55 (stride=2) ← skip2 │ │ GTConvLite×6 (d=1,2,4,8,4,2) ← skip3-8 │ SubbandAttention │ └───────────────────────────────────────┘ │ ▼ DPGRNN_Enhanced × 2 │ intra: 双向GRU (频率轴) │ inter: 单向GRU (时间轴) │ ▼ ┌─ Decoder ─────────────────────────────┐ │ GTConvLite×6 + skip (逆序) │ │ DSDeconv: 55→110 + skip2 │ │ DSDeconv: 110→219 + skip1 │ └───────────────────────────────────────┘ │ ▼ ERB_48k.bs(): 219 → 513 │ ▼ CRM掩码 → 输出 GTConvLite 内部 x → DWConv(3×3, dilation) → PWConv → BN → PReLU → TRALite (时序注意力) → SEBlock (通道注意力) → + x (残差) DPGRNN 内部 x (B,C,T,F) → reshape (B*T, F, C) → Linear → 双向GRU (频率轴) → Linear → reshape + LayerNorm → reshape (B*F, T, C) → Linear → 单向GRU (时间轴) → Linear → reshape + LayerNorm → 输出 v1 → v2: 换损失函数 问题 v1 用的是标准 SpecRIMAGLoss,对所有帧一视同仁。但实际听感上,键盘敲击、鼠标点击这类突发噪音处理得不好。DNSMOS 是整段平均,掩盖了这个问题。 ...

February 13, 2026 · 7 min · Konpaku Youran

GTCRN轻量化方案

GTCRN-Light v3 技术说明书 0. 扼要(Executive Summary) GTCRN-Light v3(以下简称 v3)是在原生 GTRCN 基础上进行的等价轻量化实现:完整保留“ERB→SFE→Encoder(频轴两次 /2)→DPGRNN(intra→inter)→Decoder(镜像+跳连)→ERB⁻¹→复域 CRM”的主干数据流与功能语义,通过算子级设计收缩参数与 MACs,同时增强形状稳定性与工程可部署性。 核心收益: 结构等价:无语义重构、无路径删减;对齐原版的时/频建模顺序与接口。 计算瘦身:卷积 DW-Separable 化、RNN 低秩瓶颈、门控去 RNN 化、ERB 固定权重化。 工程稳态:严格的频轴上/下采样闭环(33→65→129),对齐安全,易于导出与部署。 1. 设计目标与边界(Design Goals & Constraints) 不改变 GTRCN 的任务假设与编解码语义:复域 CRM、ERB 子带、频轴二次下采样、DPGRNN(先 intra 后 inter)、镜像解码与跳连。 降低参数与 MACs,但不牺牲 DPGRNN 的双路径长程/跨频建模。 形状稳定:频轴整数对齐,杜绝奇偶差累积;跳连前天然同维。 部署友好:避免难以量化/导出的算子(极小化状态化 RNN、减少不必要的线性层)。 2. 与原生 GTRCN 保持一致的“架构不变量” 数据流: (B,F,T,2) → [|S|, Re, Im] → ERB(bm) → SFE → Encoder(freq /2 ×2) → DPGRNN(intra→inter) → Decoder → ERB(bs) → CRM × S(复域) 采样策略:ERB 后 F=129;编码两次在频轴 /2:129→65→33;解码反向:33→65→129(确保 33→65→129 的闭环)。 时/频耦合:瓶颈处严格遵循 intra-(per time, across F) → inter-(per freq, across T) 的双路径顺序。 输出语义:预测 CRM(实/虚) 并在复域与输入逐点相乘。 3. 轻量化的四大支柱(Pillars of Lightweighting) 3.1 卷积主干 DW-Separable 化 + 轻量 GT-ConvLite 动机:将 2D 卷积的通道耦合与空间(T/F)卷积解耦,保留感受野与局部子带建模能力的同时,将参数与 MACs 近似按 1/通道数 降低。 ...

February 13, 2026 · 3 min · Konpaku Youran