读 ncnn 源码(XXXVII):`eliminate_orphaned_memorydata`——图优化的“垃圾回收”
读 ncnn 源码(XXXVII):eliminate_orphaned_memorydata——图优化的“垃圾回收”
在
ncnnoptimize的图优化篇章中,我们已经见证了多种“算子融合”和“算子消除” Pass。这些优化操作(如fuse_memorydata_binaryop)在将计算合并、烘焙参数的同时,往往会使原图中的某些层(如MemoryData)的输出不再被任何其他层所需要,从而使其成为“孤儿”节点。本篇,我们将剖析
eliminate_orphaned_memorydata这一优化 Pass,它扮演着图优化流水线中**“垃圾回收器”(Garbage Collector)**的角色,专门负责清理这些因上游优化而产生的冗余数据层。
TL;DR
- 目标: 识别并消除那些输出不再被任何有效层(non-fused)所消费的
MemoryData层。 - 动机: 这是一个清理(Cleanup)Pass。在
fuse_memorydata_binaryop,fuse_convolution_add等 Pass 中,MemoryData层(作为常量/偏置的提供者)的数据被“烘焙”到了消费者层(如BinaryOp,Convolution)的参数中。这导致原有的MemoryData -> Consumer连接断开,如果该MemoryData没有其他消费者,它就变成了图中的“孤儿”,在推理时空跑一次毫无意义。 - 机制: 遍历所有
MemoryData层(索引i)。对每一个MemoryData层,向前扫描其后的所有层(索引j > i),检查是否有任何一个有效层(type != "ncnnfused")在其bottoms列表中引用了该MemoryData层的top_blob。 - 行动: 如果遍历完所有后续层(
j == layer_count)都没有找到任何一个消费者,则证明该MemoryData层是“孤儿”,将其type标记为"ncnnfused"以便在推理时跳过。 - 效果: 确保了优化后的计算图不会遗留任何无用的
MemoryData节点,使得最终生成的.param文件更小、更干净,网络结构更简洁,减少了不必要的层调度和内存占用。
1. 优化的“副作用”:孤儿节点的产生
在 ncnnoptimize 执行图优化的过程中,许多融合 Pass 的核心是将一个“数据层”(如 MemoryData)或“计算层”(如 BatchNorm)的参数合并到另一个“计算层”(如 Convolution)中。
以 fuse_memorydata_binaryop 为例:
- 优化前:
MemoryData->BinaryOp(Add) - 优化中:
BinaryOp的op_type变为Add with Scalar,MemoryData的标量值被“烘焙”进BinaryOp的b参数中。BinaryOp与MemoryData的连接被断开。 - 优化后:
MemoryData层依然存在于layers列表中,但它的输出blob已经没有任何消费者了。它成了一个“孤儿” (Orphaned) 节点。
这些孤儿节点虽然不影响计算结果,但它们仍然是计算图的一部分,会在推理时被调度执行(加载数据到 Mat),造成不必要的开销。
2. eliminate_orphaned_memorydata 的职责:清理“垃圾”
eliminate_orphaned_memorydata Pass 的职责就是遍历整个计算图,找出所有这些“只生产、不消费”的 MemoryData 层,并将它们标记为无效,从而在推理时彻底跳过它们。
3. 源码解析:一次前向扫描 (Forward Scan)
该函数的实现逻辑非常直接:
1 | int NetOptimize::eliminate_orphaned_memorydata() |
关键点:
- 前向扫描 (
j = i + 1): 为什么只向前扫描?因为 ncnn 的计算图(在load_param后)是拓扑排序的,一个层的消费者必定位于其后。 - 检查
"ncnnfused": 必须跳过已经被融合的层。否则,一个MemoryData的消费者BinaryOp就算被融合了 (type == "ncnnfused"),它在bottoms列表中依然保留着对top_blob_index的引用,这会造成误判。eliminate_orphaned_memorydata只关心仍然有效的层是否在消费它。 - 标记清除: 简单地将
type设为"ncnnfused",与所有其他消除/融合 Pass 保持一致。
4. 结语
eliminate_orphaned_memorydata 是一个关键的清理(Cleanup)Pass。它本身并不直接创造性能收益,而是作为其他优化 Pass(如 fuse_memorydata_binaryop)的“搭档”出现,负责“打扫战场”,清除优化后遗留的冗余数据节点。
这体现了 ncnnoptimize 作为一个鲁棒的图优化工具,不仅要“能融合、会替换”,还要“善清理”。通过这种方式,ncnn 确保了图优化的流水线是完整的,最终输出的 .param 文件是最小、最干净的,网络结构是简洁高效的。





