Mini-Infer (7): 高性能“内核注册表” (A TensorRT-Style Kernel Registry)
Mini-Infer (7): 高性能“内核注册表” (A TensorRT-Style Kernel Registry)
1. 架构目标:从“静态分发”到“动态注册”
我们的新目标是:
- 解耦:
GEMMKernel(调度器)不应该“知道”任何具体的实现(如avx2_gemm_impl)。 - 可扩展:添加一个新的
AVX512内核,应该不需要修改任何现有的GEMMKernel代码。 - 高性能:系统必须能自动检测硬件能力,并优先选择最快的可用内核(例如,
cuBLAS>AVX2>CPU)。
为了实现这一点,我们将构建一个“内核电话簿”(Registry),每个内核实现(AVX2、CUDA…)都会在启动时自动将其“电话号码”(函数指针)和“能力”注册到这个“电话簿”中。
2. 核心设计:KernelRegistryBase (kernel_registry.h)
这是我们的“电话簿”模板。它是一个通用的 C++ 模板类,可以为任何类型的内核(GEMM, im2col…)管理一个实现列表。
1 | // mini_infer/kernels/kernel_registry.h |
这个设计的核心是 get_best_kernel()。Linear 算子不再需要知道 AVX2 的存在。它只需要在初始化时调用一次 GEMMRegistry::instance().get_best_kernel(),就能自动获得一个(可能是 AVX2 或 cuBLAS)指向最快 GEMM 函数的指针。
3. C++ 魔法:AutoRegister 与“静态初始化”
我们如何“填充”这个“电话簿”?我们不希望在 main() 函数中写一长串 register_kernel(...)。
我们使用与 OperatorFactory(Blog 3)完全相同的技巧:自动注册。
1 | // mini_infer/kernels/kernel_registry.h (接上文) |
如何使用它? 假设我们在 gemm_avx2.cpp 中编写了一个 AVX2 优化的 GEMM。我们只需在该文件的全局命名空间中添加这一行:
1 | // in: mini_infer/kernels/gemm_avx2.cpp |
在程序启动时(main 执行之前),_gemm_avx2_register 变量的构造函数会自动被调用,从而将 gemm_nt_avx2_impl 自动注册到 GEMMRegistry 中!
4. 解决“终极问题”:KernelRegistryInitializer 与链接器
AutoRegister 有一个致命的弱点:静态库(Static Libraries)。
如果 gemm_avx2.cpp 被编译进一个静态库(如 libmini_infer_kernels.a),而我们的主程序 Engine 没有显式调用 gemm_avx2.cpp 中的任何函数,那么链接器(Linker)会认为 gemm_avx2.cpp 整个文件都是“无用的”。
链接器会“智能地”丢弃这个文件,导致 _gemm_avx2_register 这个全局变量根本不存在于最终的可执行文件中。自动注册**“无声地”失败**了。
KernelRegistryInitializer 就是我们的解决方案。
1 | // mini_infer/kernels/kernel_registry_init.h |
这个 initialize() 函数必须在 Engine 构造时(或 main 启动时)被调用一次。
通过“显式”调用 cpu::register_gemm_kernels(),我们“欺骗”了链接器。链接器现在认为 gemm_cpu.cpp 是“有用的”,它被迫将该文件链接到最终的程序中,AutoRegister 的全局变量得以“存活”,自动注册成功!
总结与展望
我们彻底重构了 Kernel 层。我们从一个“硬编码”的 switch 语句,升级到了一个媲美 TensorRT 的、基于优先级和硬件能力的动态内核注册表。
这个新架构为 Mini-Infer 的终极性能铺平了道路。





