Ch15: Paddle-Lite——万能充电器
Part 3: 六大框架架构精讲 前置章节:Ch14: TNN——ncnn 的架构翻新版 后续章节:Ch16: MACE——深入骨髓的硬件适配
一句话定位
Paddle-Lite 是六大引擎中后端数量最多的一个。它不像 ncnn 那样专注于 ARM CPU 极致优化,也不像 MNN 那样用 Geometry Compiler 做框架级创新,而是用一套精心设计的”硬件抽象层”——NNAdapter HAL——让同一个模型能跑在 ARM CPU、GPU(OpenCL)、华为 NPU(Kirin)、高通 DSP(Hexagon)、联发科 APU、瑞芯微 NPU、寒武纪 MLU 等 13+ 种异构后端上。它就像一个万能充电器——你不需要为每种手机准备一根不同的充电线,一个充电器配上不同的转接头就能给所有设备充电。
GitHub 7K star,百度开源,约 3000 个源文件——PaddlePaddle 生态的端侧推理组件。
类比:万能充电器的工作方式
你出国旅行,带了手机、平板、笔记本电脑。每个国家的插座标准不同——中国是扁脚、美国是圆脚、英国是三角脚、欧洲又不一样。最笨的方式是为每个国家带一套专用充电器。聪明的方式是带一个万能充电器:一个变压器模块(核心)加上一组可拆卸的转接头(适配器)。
万能充电器的设计要点:
- 核心变压器对所有转接头来说都是一样的(统一的内部表示)
- 每个转接头只需要实现”怎么插入当地插座”(硬件特定的适配逻辑)
- 添加一个新国家的支持,只需要做一个新转接头,不需要改变压器(开放扩展)
- 充电器会自动检测当前插座的电压并适配(运行时自动选择)
Paddle-Lite 的 NNAdapter HAL 就是这个万能充电器的架构:
- 统一 IR(中间表示)= 变压器模块(和插座标准无关的核心)
- NNAdapter HAL = 转接头的标准接口(每种插座对应 8 个函数指针)
- 各厂商 driver = 具体的转接头(华为 NPU driver、高通 DSP driver 等)
static_kernel_pick_pass= 自动电压检测(根据硬件选最优内核)
类比边界:万能充电器的类比强调的是 Paddle-Lite 的”多后端适配”能力。但充电器是一个简单的单向流程(电源到设备),推理引擎有复杂的图优化、内存管理、异构调度等维度,这些超出了充电器类比的范围。
设计哲学精讲
哲学一:NNAdapter HAL——8 个函数指针统一异构硬件
NNAdapter 是 Paddle-Lite 最核心的设计创新。它定义了一个极简的 HAL(Hardware Abstraction Layer,硬件抽象层),让任何硬件后端只需要实现 8 个函数指针就能接入 Paddle-Lite。
// NNAdapter HAL 接口(简化)
typedef struct {
// 设备管理
int (*open_device)(const char* name, NNAdapterDevice** device);
int (*close_device)(NNAdapterDevice* device);
// 上下文管理
int (*create_context)(NNAdapterDevice* device,
const char* properties,
NNAdapterContext** context);
int (*destroy_context)(NNAdapterContext* context);
// 模型编译
int (*create_compilation)(NNAdapterModel* model,
NNAdapterContext* context,
NNAdapterCompilation** compilation);
int (*destroy_compilation)(NNAdapterCompilation* compilation);
// 推理执行
int (*create_execution)(NNAdapterCompilation* compilation,
NNAdapterExecution** execution);
int (*execute)(NNAdapterExecution* execution);
} NNAdapterHAL;
这 8 个函数覆盖了一个硬件后端的完整生命周期:打开/关闭设备、创建/销毁上下文、编译模型、执行推理。每个硬件厂商实现这 8 个函数,就完成了对 Paddle-Lite 的适配。
这种设计的灵感来自 Android 的 HAL 架构——Android 用 HAL 接口让不同厂商的硬件驱动可以无缝接入 Android 系统。Paddle-Lite 用同样的思路,让不同的 AI 加速器可以无缝接入 Paddle-Lite。
和 ORT 的 Execution Provider 对比:ORT 的 EP 接口有几十个方法(GetCapability、Compile、GetAllocator、OnRunStart 等等),实现一个 EP 是一个不小的工程。NNAdapter 的 8 个函数指针明显更轻量——代价是灵活性较低(EP 可以和 ORT 做更深度的集成,NNAdapter 的后端只能在”整图或子图”粒度上工作)。
NNAdapter 的工作流程:
1. Paddle-Lite 把模型图转换为 NNAdapter Model(标准中间表示)
2. NNAdapter Model 被传给硬件厂商的 HAL 实现
3. HAL 的 create_compilation 把 NNAdapter Model 编译为硬件特定的程序
4. HAL 的 execute 在硬件上执行推理
5. 如果某些 Op 硬件不支持,回退到 CPU 执行
目前通过 NNAdapter HAL 接入的硬件包括:华为 Kirin NPU(HiAI)、高通 Hexagon DSP、联发科 APU(NeuroPilot)、瑞芯微 NPU(RKNN)、晶晨 NPU、寒武纪 MLU、比特大陆 Sophon 等。这个列表还在不断增长——每增加一个新硬件,只需要实现 8 个函数。
哲学二:独立 IR——从 PaddlePaddle 模型到统一中间表示
和 ncnn/MNN/TNN 直接消费自己的模型格式不同,Paddle-Lite 有一个独立的中间表示(IR)。它的作用是在 PaddlePaddle 模型格式和各种后端之间建立一个缓冲层。
PaddlePaddle 模型 -> [转换] -> Paddle-Lite IR -> [优化] -> 优化后 IR -> [执行]
^
50+ 优化 pass
Paddle-Lite IR 由三个核心概念组成:
// IR 的核心概念
class OpLite { // 算子节点
std::string type_; // 算子类型(如 "conv2d")
OpInfo* op_info_; // 参数信息
};
class Scope { // 变量作用域
std::map<std::string, Variable*> vars_;
// 所有 Tensor 都通过 Scope 管理
};
class SSAGraph { // 计算图(静态单赋值形式)
std::list<mir::Node*> nodes_; // 节点列表
// mir = Model IR
};
独立 IR 的价值在于:所有的图优化 pass 都作用在 IR 上,而不是作用在 PaddlePaddle 的原始模型表示上。这让优化逻辑和模型格式解耦——如果未来 Paddle-Lite 要支持 ONNX 模型,只需要加一个”ONNX 到 Paddle-Lite IR”的前端转换器,所有已有的优化 pass 都可以直接复用。
哲学三:TensorLite——三维坐标描述 Tensor
Paddle-Lite 的 Tensor(TensorLite)有一个独特的设计:用三维坐标 (TargetType, PrecisionType, DataLayoutType) 描述 Tensor 的完整属性。
// TensorLite(简化)
class TensorLite {
TargetType target_; // 数据在哪种设备上
PrecisionType precision_; // 数据的精度
DataLayoutType layout_; // 数据的内存布局
DDim dims_; // 形状
void* raw_data_; // 数据指针
size_t memory_size_; // 内存大小
bool persistable_; // 是否是模型权重(不会被释放)
};
// 三维坐标的取值
enum class TargetType {
kHost, // CPU 通用
kARM, // ARM CPU(有 NEON 优化)
kOpenCL, // GPU (OpenCL)
kMetal, // GPU (Metal, iOS)
kNNAdapter, // NPU/DSP (通过 NNAdapter)
kFPGA, // FPGA
// ...
};
enum class PrecisionType {
kFloat, // fp32
kFP16, // fp16
kInt8, // int8
kInt32, // int32
kBool, // bool
// ...
};
enum class DataLayoutType {
kNCHW, // 标准布局
kNHWC, // TensorFlow 风格
kImageDefault, // OpenCL image2d 默认
kImageFolder, // OpenCL image2d 折叠
// ...
};
这个三维坐标系统的精妙之处在于:它把 Tensor 的属性从”隐式约定”变成了”显式声明”。在 ncnn 中,你看到一个 Mat,不知道它是 CPU 上的还是 GPU 上的(CPU 用 Mat,GPU 用 VkMat,但这是类型的区别,不是属性的区别)。在 Paddle-Lite 中,一个 TensorLite 通过 (kOpenCL, kFP16, kImageDefault) 精确描述”这是一个在 OpenCL 设备上、fp16 精度、image2d 默认布局的 Tensor”。
这个三维坐标在后面要讲的 static_kernel_pick_pass 和类型转换自动插入中扮演关键角色。
哲学四:MIR 50+ Pass——工业级的图优化管线
Paddle-Lite 拥有六大框架中最多的图优化 pass——超过 50 个。这些 pass 组成了一条完整的图优化管线,从模型加载到推理执行之间要经过大量的自动化处理。
优化管线(选取关键 pass):
前端处理:
lite_quant_dequant_fuse_pass # 量化/反量化融合
lite_conv_bn_fuse_pass # Conv+BN 融合
lite_conv_activation_fuse_pass # Conv+Activation 融合
lite_fc_fuse_pass # FC 层融合
中端优化:
static_kernel_pick_pass # 内核选择(核心 pass)
type_precision_cast_pass # 精度转换插入
type_layout_cast_pass # 布局转换插入
type_target_cast_pass # 设备转换插入
后端处理:
memory_optimize_pass # 内存优化
nnadapter_subgraph_partition_pass # NNAdapter 子图分区
这么多 pass 存在的原因是:Paddle-Lite 要支持 13+ 种后端,每种后端对数据精度、布局、算子融合的需求都不一样。比如 ARM CPU 偏好 NCHW + fp32、OpenCL GPU 偏好 ImageDefault + fp16、NPU 偏好 NHWC + int8。这些差异不能在一个 pass 里处理,需要分解为多个正交的 pass,每个 pass 只关注一个维度的适配。
哲学五:static_kernel_pick_pass——运行时内核选择
static_kernel_pick_pass 是 Paddle-Lite 最关键的优化 pass。它的作用是:为模型中的每个 Op 选择最优的内核实现。
一个 Op(比如 Conv2D)在 Paddle-Lite 中可能有多个内核实现:
Conv2D 的可用内核:
1. ARM + fp32 + NCHW -> arm_conv_compute (NEON 优化)
2. ARM + int8 + NCHW -> arm_conv_int8_compute (int8 量化)
3. ARM + fp16 + NCHW -> arm_conv_fp16_compute (fp16)
4. OpenCL + fp16 + ImageDefault -> opencl_conv_image_compute
5. OpenCL + fp32 + Buffer -> opencl_conv_buffer_compute
6. NNAdapter + any + any -> nnadapter_subgraph_compute
static_kernel_pick_pass 根据用户指定的目标后端和 Op 的输入/输出 Tensor 属性,选择最匹配的内核:
// static_kernel_pick_pass 的核心逻辑(概念简化)
void StaticKernelPickPass::Apply(SSAGraph* graph) {
for (auto& node : graph->mutable_nodes()) {
if (!node.IsStmt()) continue;
auto& stmt = node.AsStmt();
auto op_type = stmt.op_type();
// 获取所有可用内核
auto kernels = KernelRegistry::Global().Create(
op_type, target_, precision_, layout_);
// 按优先级排序
// 1. Target 匹配(设备类型): 权重最高
// 2. Precision 匹配(精度): 权重中等
// 3. Layout 匹配(布局): 权重较低
std::sort(kernels.begin(), kernels.end(),
[](const auto& a, const auto& b) {
return Score(a) > Score(b);
});
// 选择得分最高的内核
stmt.SetKernels(std::move(kernels));
}
}
选择内核后,可能出现”不匹配”的情况:比如前一层的输出是 (kARM, kFloat, kNCHW),但后一层选中的内核需要 (kOpenCL, kFP16, kImageDefault) 的输入。这时候需要自动插入类型转换节点。
哲学六:三类 Cast 自动插入——无缝的异构计算
Paddle-Lite 定义了三类自动插入的转换节点,分别处理 TensorLite 三维坐标中的每一个维度:
1. type_target_cast_pass: 设备转换
(kARM, fp32, NCHW) -> [io_copy] -> (kOpenCL, fp32, NCHW)
实际操作: CPU 内存到 GPU 内存(memcpy host-to-device)
2. type_precision_cast_pass: 精度转换
(kOpenCL, fp32, NCHW) -> [precision_cast] -> (kOpenCL, fp16, NCHW)
实际操作: fp32 到 fp16 数值转换
3. type_layout_cast_pass: 布局转换
(kOpenCL, fp16, NCHW) -> [layout_cast] -> (kOpenCL, fp16, ImageDefault)
实际操作: Buffer 到 Image2D 格式转换
这三类 cast 的自动插入机制让用户完全不需要关心异构计算的数据搬运问题。用户只需要说”我要用 OpenCL 后端”,Paddle-Lite 自动在需要的地方插入 CPU-GPU 数据传输、精度转换和布局转换。
自动插入示例:
用户模型(纯逻辑层面):
Input -> Conv -> ReLU -> Resize -> Conv -> Output
用户指定 OpenCL 后端后,Paddle-Lite 自动插入 cast:
Input(CPU)
-> [io_copy: CPU->GPU]
-> [layout_cast: Buffer->Image2D]
-> Conv(OpenCL, fp16, Image2D)
-> ReLU(OpenCL, fp16, Image2D)
-> [layout_cast: Image2D->Buffer] # Resize 不支持 Image2D
-> [io_copy: GPU->CPU]
-> Resize(CPU, fp32, NCHW) # 回退到 CPU 执行
-> [io_copy: CPU->GPU]
-> [layout_cast: Buffer->Image2D]
-> Conv(OpenCL, fp16, Image2D)
-> [layout_cast: Image2D->Buffer]
-> [io_copy: GPU->CPU]
-> Output(CPU)
这种自动化的代价是:如果回退到 CPU 的 Op 很多,频繁的 CPU-GPU 数据传输会严重拖慢推理速度。在实际使用中,需要确保模型中的大部分 Op 都有目标后端的内核实现,否则性能可能不如纯 CPU 推理。
核心数据结构:TensorLite
TensorLite 的完整属性:
class TensorLite {
// 三维坐标
TargetType target_; // kHost / kARM / kOpenCL / kNNAdapter / ...
PrecisionType precision_; // kFloat / kFP16 / kInt8 / ...
DataLayoutType layout_; // kNCHW / kNHWC / kImageDefault / ...
// 形状
DDim dims_; // {1, 3, 224, 224}
LoD lod_; // Level of Detail(PaddlePaddle 特有的序列信息)
// 内存
Buffer buffer_; // 底层内存缓冲
void* raw_data_; // 数据指针
size_t memory_size_; // 已分配的内存大小
size_t offset_; // 在 buffer 中的偏移
// 标记
bool persistable_; // 是否是模型权重
};
和其他框架的对比:
| 特性 | ncnn Mat | MNN Tensor | TNN Blob | ORT Tensor | PL TensorLite |
|---|---|---|---|---|---|
| 设备信息 | 无 | 内含 | BlobDesc | MemoryInfo | TargetType |
| 精度信息 | elemsize | halide_type | DataType | DataTypeImpl | PrecisionType |
| 布局信息 | elempack | DimensionType | DataFormat | 无 | DataLayoutType |
| 序列信息 | 无 | 无 | 无 | 无 | LoD |
TensorLite 是六大框架中信息最完整的 Tensor 类型——它用三个独立的枚举显式表达了设备、精度、布局三个维度的信息。这种显式表达让自动化的类型转换插入成为可能(因为系统可以精确地知道两个 Tensor 在哪个维度上不匹配,然后插入对应的 cast)。
LoD(Level of Detail)是 PaddlePaddle 特有的概念,用于表示变长序列数据。这在 NLP 任务中有用(不同句子有不同长度),但在其他框架中没有对应的概念。
推理流程
Paddle-Lite 的推理流程比其他框架多了一个”离线优化”阶段。
阶段零:模型优化(离线)
# 使用 opt 工具对模型做离线优化
paddle_lite_opt --model_file=model.pdmodel \
--param_file=model.pdiparams \
--optimize_out=optimized_model \
--valid_targets=arm
opt 工具执行所有 50+ 个优化 pass(包括 static_kernel_pick_pass 和三类 cast 插入),输出优化后的 .nb 文件(Naive Buffer 格式)。这个离线优化步骤是 Paddle-Lite 的特色——大量的优化工作在部署前完成,运行时的开销更小。
ncnn 也有类似的离线工具(ncnnoptimize),但它主要做算子融合,不涉及内核选择和类型转换插入。Paddle-Lite 的 opt 工具做的事情多得多。
阶段一:模型加载
MobileConfig config;
config.set_model_from_file("optimized_model.nb");
config.set_threads(4);
std::shared_ptr<PaddlePredictor> predictor =
CreatePaddlePredictor<MobileConfig>(config);
加载优化后的 .nb 文件比加载原始 PaddlePaddle 模型快得多——因为所有的图优化已经做过了,加载时只需要反序列化和创建内核实例。
阶段二:推理执行
// 获取输入 Tensor
auto input = predictor->GetInput(0);
input->Resize({1, 3, 224, 224});
auto* input_data = input->mutable_data<float>();
memcpy(input_data, data, sizeof(float) * 3 * 224 * 224);
// 执行推理
predictor->Run();
// 获取输出
auto output = predictor->GetOutput(0);
const float* output_data = output->data<float>();
关键差异:Paddle-Lite 与其他框架
vs ncnn/MNN/TNN:后端数量
Paddle-Lite 通过 NNAdapter HAL 支持 13+ 种后端,远超 ncnn(CPU+Vulkan)、MNN(CPU+OpenCL+Metal+Vulkan+CUDA)和 TNN(CPU+OpenCL+Metal)。如果你的部署目标包含特殊硬件(华为 NPU、瑞芯微 NPU、FPGA),Paddle-Lite 可能是唯一的选择。
vs ORT:HAL vs EP
ORT 的 EP 接口更复杂但更灵活(EP 可以参与图优化、内核选择等多个阶段)。Paddle-Lite 的 NNAdapter HAL 更简单(8 个函数指针),适合硬件厂商快速接入,但灵活性不如 EP。
vs MNN:pass 驱动 vs 编译器驱动
MNN 用 Geometry Compiler 在运行时自动处理非计算 Op 的合并;Paddle-Lite 用 50+ 个显式的 pass 在编译时(离线优化)处理图优化。MNN 的方式更自动化但有运行时开销,Paddle-Lite 的方式更可控但需要离线优化步骤。
适用场景与局限
最适合 Paddle-Lite 的场景
部署到异构硬件的场景。如果你的产品需要在华为手机(Kirin NPU)、高通手机(Hexagon DSP)、瑞芯微开发板(RKNN NPU)等多种硬件上部署同一个模型,Paddle-Lite 通过 NNAdapter HAL 提供了统一的部署方案。
PaddlePaddle 生态的用户。如果你用 PaddlePaddle 训练模型,Paddle-Lite 提供了最无缝的部署体验——模型格式直接兼容,不需要先转成 ONNX 再转成其他格式。
NPU 加速场景。在六大框架中,Paddle-Lite 的 NPU 后端覆盖面最广。如果你需要利用手机 NPU 的算力,Paddle-Lite 比 ncnn(不支持 NPU)和 MNN(NPU 支持有限)是更好的选择。
Paddle-Lite 的局限
纯 CPU 性能不如 ncnn。Paddle-Lite 的 ARM CPU 内核虽然也有 NEON 优化,但手写汇编的精细程度不如 ncnn。在纯 CPU 推理的基准测试中,ncnn 通常更快。
生态绑定。Paddle-Lite 和 PaddlePaddle 生态绑定较深。虽然也支持通过 ONNX 转入其他框架的模型,但原生体验还是 PaddlePaddle 模型最好。如果你用 PyTorch,需要先转 ONNX 再转 Paddle,多了一步。
离线优化步骤。需要用 opt 工具做离线优化才能获得最佳性能。其他框架(ncnn、MNN)可以直接加载模型运行,Paddle-Lite 多了一个部署环节。
代码复杂度中等偏高。约 3000 个文件、50+ 个 pass、三维类型系统——Paddle-Lite 的代码复杂度介于 MNN(2000 文件)和 ORT(8000 文件)之间。对初学者来说,理解内核选择和类型转换的交互逻辑有一定门槛。
不支持端侧 LLM 和训练。和 ncnn、TNN 一样,Paddle-Lite 是纯推理引擎,没有端侧 LLM 优化和端侧训练能力。MNN 在这方面领先。
代码结构
Paddle-Lite 约 3000 个文件,结构如下:
Paddle-Lite/lite/
├── api/ # 用户 API
│ ├── paddle_api.h # C++ API
│ └── python/ # Python 绑定
│
├── core/ # 核心抽象
│ ├── tensor.h / .cc # TensorLite
│ ├── kernel.h / .cc # KernelBase
│ ├── op_lite.h / .cc # OpLite
│ ├── scope.h / .cc # 变量作用域
│ ├── program.h / .cc # 程序表示
│ ├── context.h # 上下文(设备相关)
│ └── types.h # TargetType/PrecisionType/DataLayoutType
│
├── model_parser/ # 模型解析
│ ├── pb/ # PaddlePaddle protobuf 格式
│ ├── flatbuffers/ # FlatBuffers 格式
│ └── naive_buffer/ # NaiveBuffer 格式(.nb)
│
├── kernels/ # 内核实现
│ ├── host/ # 通用 CPU 内核
│ ├── arm/ # ARM NEON 内核
│ │ ├── conv_compute.h / .cc
│ │ ├── conv_depthwise.h / .cc
│ │ └── ...
│ ├── opencl/ # OpenCL 内核
│ │ ├── image2d/ # image2d 路径
│ │ └── buffer/ # buffer 路径
│ ├── metal/ # Metal 内核 (iOS)
│ └── x86/ # x86 内核
│
├── backends/ # NNAdapter 后端
│ └── nnadapter/
│ ├── nnadapter/ # NNAdapter 核心
│ │ ├── include/
│ │ │ └── nnadapter.h # HAL 接口定义
│ │ └── src/
│ │ ├── driver/ # 各厂商 HAL 实现
│ │ │ ├── huawei_kirin_npu/
│ │ │ ├── qualcomm_qnn/
│ │ │ ├── mediatek_apu/
│ │ │ ├── rockchip_npu/
│ │ │ ├── cambricon_mlu/
│ │ │ └── ...
│ │ └── runtime/ # NNAdapter 运行时
│ └── ...
│
├── operators/ # Op 定义
│ ├── conv_op.h / .cc
│ ├── relu_op.h / .cc
│ └── ...
│
└── core/optimizer/mir/ # MIR 图优化
├── pass.h # Pass 基类
├── pass_manager.h # Pass 管理器
├── fusion/ # 融合 pass
│ ├── conv_bn_fuse_pass.h / .cc
│ └── ...
├── static_kernel_pick_pass.h / .cc
├── type_precision_cast_pass.h / .cc
├── type_layout_cast_pass.h / .cc
├── type_target_cast_pass.h / .cc
└── memory_optimize_pass.h / .cc
关键阅读路径:
- 入门:
core/types.h(三维坐标定义)->core/tensor.h(TensorLite)->core/kernel.h(KernelBase) - 理解 NNAdapter:
backends/nnadapter/nnadapter/include/nnadapter.h(HAL 接口)-> 选一个 driver 实现阅读 - 理解内核选择:
core/optimizer/mir/static_kernel_pick_pass.cc - 理解类型转换:
core/optimizer/mir/type_target_cast_pass.cc(设备转换)->type_precision_cast_pass.cc(精度转换)->type_layout_cast_pass.cc(布局转换) - 理解 ARM 内核:
kernels/arm/conv_compute.cc
小结
Paddle-Lite 的设计哲学可以用一个词概括:适配性。它不追求在某一个后端上做到极致(那是 ncnn 在 ARM CPU 上做的事),而是追求让同一个模型能在尽可能多的硬件上跑。NNAdapter HAL 用 8 个函数指针定义了硬件接入的最小接口,TensorLite 的三维坐标精确描述了数据的设备/精度/布局属性,static_kernel_pick_pass 自动选择最优内核,三类 cast pass 自动插入数据转换节点。
它的核心创新在于把异构计算的复杂性封装在框架内部:用户不需要关心”数据从 CPU 搬到 GPU 需要什么操作”“fp32 到 fp16 的转换在哪里做”“NCHW 到 Image2D 的布局变换怎么处理”——这些全部由 Paddle-Lite 的 pass 管线自动完成。用户只需要指定目标后端,框架自动做完所有事情。
对于犀牛鸟竞赛的参赛者来说,Paddle-Lite 的 NNAdapter HAL 设计和三维坐标 TensorLite 值得学习——如果你需要为推理引擎加一个新的硬件后端,NNAdapter 的极简 HAL 思路比 ORT 的重量级 EP 接口更适合快速原型开发。而三类 cast pass 的自动插入机制,则是理解异构推理引擎如何管理跨设备数据流的绝佳案例。
下一章,也是 Part 3 的最后一章,我们将看到 MACE 如何用”深入骨髓的硬件适配”——OpenCL image2d 精细映射、Hexagon DSP 整图下推、功耗管理——在特定硬件上榨取最后一滴性能。
导读目录:README.md 上一章:Ch14: TNN——ncnn 的架构翻新版 下一章:Ch16: MACE——深入骨髓的硬件适配