Mini-Infer (25): 动态形状的基石 — OptimizationProfile 设计与实现

1. 核心理念:Min, Opt, Max 三位一体

对于任何一个动态输入(比如 input_0),我们不再只给它一个模糊的 -1,而是要求用户提供三个形状:

  1. Min Shape: 允许的最小尺寸。
    • 作用:边界检查。小于此尺寸的输入将被拒绝。
  2. Opt Shape (Optimal): 最常用的尺寸。
    • 作用核心优化的依据。引擎会针对这个尺寸选择最优的算法(Kernel Selection)和执行调度策略。
  3. Max Shape: 允许的最大尺寸。
    • 作用内存分配的依据MemoryPlanner 会根据 Max Shape 来计算网络中所有中间 Tensor 的最大可能尺寸,并据此分配显存。这样,在运行期间只要输入不超过 Max,就永远不需要重新分配显存

2. 代码实现:ShapeRangeOptimizationProfile

A. 形状范围 (ShapeRange)

这是 Profile 的基本单元。它封装了三个形状,并提供了校验逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ShapeRange {
core::Shape min, opt, max;

// 校验逻辑:必须满足 min <= opt <= max
bool is_valid() const {
if (min.ndim() != opt.ndim() || opt.ndim() != max.ndim()) return false;
for (int i = 0; i < min.ndim(); ++i) {
// 跳过动态维度(-1),检查具体维度
if (min[i] > opt[i] || opt[i] > max[i]) return false;
}
return true;
}

// 运行时检查:当前输入 shape 是否落在这个范围内?
bool contains(const core::Shape& shape) const {
// ... 逐维检查 min[i] <= shape[i] <= max[i] ...
}
};

B. 配置文件管理器 (OptimizationProfile)

这个类管理整个网络所有输入的范围设置。它就像是引擎的“合同”,用户必须遵守。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class OptimizationProfile {
public:
// 用户接口:设置输入的范围
// 示例: set_shape_range("data", {1,3,224,224}, {8,3,224,224}, {16,3,224,224})
core::Status set_shape_range(const std::string& input_name, ...);

// 运行时校验接口
bool is_valid_for(const std::map<std::string, core::Shape>& shapes) const;

// 构建时接口:获取用于优化的 shape
std::map<std::string, core::Shape> get_optimal_shapes() const;

private:
std::map<std::string, ShapeRange> shape_ranges_;
};

3. 如何集成到 Engine 工作流中?

OptimizationProfile 是连接 Build Time (编译期)Run Time (运行期) 的桥梁。

编译期 (Engine::build)

当我们在编译网络时,我们会查询 profile->get_optimal_shapes()

  • Kernel 选择器 会用这组形状去测试不同的卷积算法,选出最快的一个。
  • Memory Planner 会查询 max 形状(虽然代码中展示的是 opt,但通常显存分配要看 max),计算出整个推理过程所需的最大显存块,并预先申请。

运行期 (Engine::forward)

当用户调用 forward 时:

  1. 校验:调用 profile->is_valid_for(input_shapes)。如果输入超出了 [min, max] 范围,直接报错,防止显存越界。
  2. 推理:调用我们在 Blog 24 中实现的 ShapeInferenceEngine,计算当前具体的输出形状。
  3. 执行:使用预分配好的(足够大的)显存执行计算。

4. 为什么这很重要?

在实际生产环境中,稳定性 (Stability) 压倒一切。

如果没有 OptimizationProfile

  • 用户输入变大 -> 触发 realloc -> 耗时增加,甚至导致系统其他进程因 OOM 崩溃。
  • 用户输入变小 -> 显存没有释放 -> 浪费资源。

有了 OptimizationProfile

  • 显存零波动:无论输入怎么变(只要在范围内),显存占用是恒定的(Max Profile 决定)。
  • 性能可预期:我们在最常用的尺寸(Opt Profile)上做了极致优化。

5. 总结

OptimizationProfileMini-Infer 支持动态形状的最后一块基石。它体现了 “Bounded Dynamism” (有界动态) 的设计思想——给予用户灵活性,但这种灵活性必须被限制在系统可控的范围内。