摘要:本文把前 7 天学到的 TFLM 知识整理成一份可执行的工程计划,指导如何让一颗新的 MCU、DSP、NPU 或 AI accelerator 稳定运行 TFLM 并逐步获得可量化的性能收益。核心原则是「先跑通 reference,再接入优化,最后用模型和 benchmark 证明收益」。文章按 8 个阶段展开:从需求与 baseline、平台 bring-up、reference 模型闭环、NN library 边界,到第一个 optimized kernel、扩展热点 op、内存与稳定性,再到 CI 与版本维护,并为每个阶段给出目标、工作项、交付物和完成条件,最后补充角色分工、风险应对、验收标准与最小可执行路线图。
轻松学习 TFLM Day 8:AI 芯片接入 TFLM 的工程计划
Day 1 到 Day 7,我们依次学习了 TFLM 的运行时、调用链、内存、kernel、后端目录、模型 op 分析,以及把 .tflite 模型接入示例。
今天把这些知识整理成一份可以执行的工程计划:
如何让一颗新的 MCU、DSP、NPU 或 AI accelerator 稳定地运行 TFLM,并逐步获得可量化的性能收益?
核心原则只有一句话:
先跑通 reference,再接入优化,最后用模型和 benchmark 证明收益。
1. 项目目标和边界
1.1 最终目标
完成后,目标平台应能够:
1.2 不属于第一阶段的内容
以下内容不要作为项目第一步:
- 一开始就支持所有 TFLM op。
- 一开始就实现完整 graph compiler。
- 一开始就重写 TFLM reference kernel。
- 没有 baseline 就声称硬件加速有效。
- 只测试一个输入就宣布模型兼容。
TFLM 的 reference kernel 是正确性基线。芯片优化应放在目标平台 kernel 子目录中,而不是破坏共享的 reference 实现。
2. 推荐的总体架构
用户应用
|
v
TFLM MicroInterpreter
|
v
MicroMutableOpResolver
|
v
your_chip optimized kernels
|
v
your_chip NN library / driver
|
v
NPU / DSP / AI accelerator
建议把工程拆成三个独立模块:
| 平台适配 | 工具链、启动、日志、计时、系统初始化、链接脚本。 |
| TFLM 后端 | conv.cc、fully_connected.cc 等薄 wrapper。 |
| 芯片软件栈 | NN library、driver、firmware、DMA、cache 和硬件命令。 |
NN library 不必完全遵循 TFLM 的代码风格,也可以独立发布和测试。TFLM wrapper 只负责 tensor、shape、量化参数和状态码之间的转换。
3. 项目阶段总览
阶段 0 需求和基线
|
阶段 1 平台 bring-up
|
阶段 2 reference 模型闭环
|
阶段 3 芯片 NN library 边界
|
阶段 4 第一个优化 kernel
|
阶段 5 扩展热点 op
|
阶段 6 性能、内存和稳定性
|
阶段 7 CI、发布和版本维护
每个阶段都必须有“完成条件”。没有通过当前阶段的验证,就不要把问题带到下一阶段。
4. 阶段 0:需求、模型和 baseline
目标
确定芯片、软件环境、目标模型和可复现的 reference 数据。
工作项
交付物
requirements.md
models/
model_a.tflite
model_b.tflite
baseline/
input_data.*
reference_output.*
benchmark_definition.md
完成条件
[ ] 每个目标模型都有明确输入和输出
[ ] 每个模型的 op 集合已经确认
[ ] host reference 输出可以重复得到
[ ] latency 和内存的测量方法已经确定
不要只选择一个“最容易跑”的模型。至少应包含一个小模型用于 bring-up,以及一个能代表实际业务负载的模型用于性能评估。
5. 阶段 1:平台 bring-up
TFLM 官方建议先不接硬件优化,使用 reference kernel 建立平台运行环境。
目标
让 TFLM 在目标芯片上完成最小示例运行。
主要工作
5.1 工具链和链接
确认:
- 编译器支持 C++17。
- include 路径和宏定义正确。
- 链接脚本能放下代码、只读模型和 tensor arena。
- C/C++ ABI 和浮点 ABI 设置一致。
- 栈、全局区、堆策略符合芯片 SDK。
5.2 平台文件
准备目标平台版本的:
tensorflow/lite/micro/debug_log.cc
tensorflow/lite/micro/micro_time.cc
tensorflow/lite/micro/system_setup.cc
它们分别负责日志输出、时间测量和系统初始化。实现可以放在平台自己的目录,只要最终链接时能提供对应接口。
5.3 项目裁剪
可以使用项目生成脚本创建只包含所需示例的源码树:
python3 tensorflow/lite/micro/tools/project_generation/create_tflm_tree.py \\
-e hello_world \\
-e person_detection \\
/tmp/tflm-tree
然后用芯片自己的构建系统编译 TFLM 静态库,例如:
libtensorflow-microlite.a
交付物
tensorflow/lite/micro/your_chip/
README.md
debug_log.cc
micro_time.cc
system_setup.cc
platform_build/
toolchain file
linker script
startup code
完成条件
[ ] 编译通过
[ ] 日志可以输出
[ ] 计时接口返回有效值
[ ] 系统初始化完成
[ ] 目标板能运行最小程序
6. 阶段 2:reference 模型闭环
目标
在不使用芯片 optimized kernel 的情况下,让一个真实模型完成正确推理。
推荐顺序
hello_world
-> 一个小型 int8 模型
-> person_detection 或 micro_speech
-> 实际业务模型
工作项
典型代码结构
const tflite::Model* model = tflite::GetModel(g_model_data);
TFLITE_CHECK_EQ(model->version(), TFLITE_SCHEMA_VERSION);
using Resolver = tflite::MicroMutableOpResolver<3>;
Resolver resolver;
resolver.AddConv2D();
resolver.AddFullyConnected();
resolver.AddSoftmax();
uint8_t tensor_arena[128 * 1024];
tflite::MicroInterpreter interpreter(
model, resolver, tensor_arena, sizeof(tensor_arena));
TF_LITE_ENSURE_STATUS(interpreter.AllocateTensors());
TF_LITE_ENSURE_STATUS(interpreter.Invoke());
下面是一个完整的 main.cc 示例,展示从模型数据加载、输入预处理、调用 AllocateTensors 和 Invoke,到输出反量化并与 host reference 比较的完整流程:
// examples/your_chip_model/main.cc
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/micro/system_setup.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "model_data.h" // 由 .tflite 转换生成的模型数组
#include "input_adapter.h" // 输入预处理:原始数据 -> int8 量化输入
#include "output_adapter.h" // 输出解释:int8 输出 -> 反量化浮点值
// 静态 tensor arena:大小需根据模型实际需求调整
constexpr int kTensorArenaSize = 128 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
// 从 host 端保存的 reference 输出(浮点)中读取期望值
extern const float kReferenceOutput[]; // 定义在 baseline/reference_output.cc
extern const int kReferenceOutputSize;
// 反量化:int8 原始值 -> 浮点真实值
float Dequantize(int8_t value, float scale, int32_t zero_point) {
return (static_cast<float>(value) – zero_point) * scale;
}
// 比较目标板输出与 host reference 输出
bool CompareWithReference(const float* actual, int size,
float tolerance = 0.01f) {
for (int i = 0; i < size; ++i) {
float diff = actual[i] – kReferenceOutput[i];
if (diff < –tolerance || diff > tolerance) {
MicroPrintf("Mismatch at [%d]: actual=%f ref=%f", i, actual[i],
kReferenceOutput[i]);
return false;
}
}
return true;
}
int main() {
// 1. 平台初始化:日志、计时、系统时钟等
tflite::InitializeTarget();
// 2. 加载模型:从 C 数组解析 FlatBuffer
const tflite::Model* model = tflite::GetModel(g_model_data);
TFLITE_CHECK_EQ(model->version(), TFLITE_SCHEMA_VERSION);
// 3. 创建最小 resolver,只注册模型实际用到的 op
using Resolver = tflite::MicroMutableOpResolver<3>;
Resolver resolver;
resolver.AddConv2D();
resolver.AddFullyConnected();
resolver.AddSoftmax();
// 4. 构建解释器,绑定静态 arena
tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
kTensorArenaSize);
// 5. 分配 tensor 内存;失败通常意味着 arena 太小
TfLiteStatus allocate_status = interpreter.AllocateTensors();
if (allocate_status != kTfLiteOk) {
MicroPrintf("AllocateTensors failed: arena too small?");
return –1;
}
// 6. 获取输入 tensor,进行预处理和量化
TfLiteTensor* input = interpreter.input(0);
TFLITE_CHECK_EQ(input->type, kTfLiteInt8);
// 从 input_adapter 读取原始数据并量化为 int8
const float* raw_input = GetRawInput(); // 原始浮点输入
const float input_scale = input->params.scale;
const int32_t input_zero_point = input->params.zero_point;
for (int i = 0; i < input->dims->data[1] * input->dims->data[2] *
input->dims->data[3];
++i) {
// 量化公式:q = round(raw / scale) + zero_point
float scaled = raw_input[i] / input_scale;
int32_t quantized = static_cast<int32_t>(scaled + input_zero_point);
input->data.int8[i] = static_cast<int8_t>(quantized);
}
// 7. 执行推理
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
MicroPrintf("Invoke failed");
return –1;
}
// 8. 获取输出 tensor,反量化并与 host reference 比较
TfLiteTensor* output = interpreter.output(0);
TFLITE_CHECK_EQ(output->type, kTfLiteInt8);
const float output_scale = output->params.scale;
const int32_t output_zero_point = output->params.zero_point;
const int output_size = output->dims->data[1] * output->dims->data[2] *
output->dims->data[3];
// 反量化到浮点数组
float actual_output[64]; // 大小需根据模型输出调整
for (int i = 0; i < output_size; ++i) {
actual_output[i] = Dequantize(output->data.int8[i], output_scale,
output_zero_point);
}
// 9. 与 host reference 比较,验证正确性
if (!CompareWithReference(actual_output, output_size)) {
MicroPrintf("Output mismatch with host reference");
return –1;
}
MicroPrintf("Inference OK: output matches reference");
return 0;
}
关键点说明:
- 模型加载:g_model_data 由 model_data.cc 提供,是 .tflite 转换后的 C 数组;GetModel 解析 FlatBuffer 并校验 schema 版本。
- 输入预处理:原始浮点输入通过量化公式 q = round(raw / scale) + zero_point 转成 int8,写入输入 tensor。
- 内存分配:AllocateTensors 在静态 tensor_arena 上完成所有 tensor 分配,失败通常意味着 arena 太小,需要增大或改用 RecordingMicroAllocator 分析。
- 推理执行:Invoke 驱动整个图执行;返回非 kTfLiteOk 时先检查模型、输入和 arena,不要归因于“芯片还没加速”。
- 输出验证:int8 输出通过 Dequantize 还原为浮点,再与 host 端保存的 kReferenceOutput 逐元素比较,误差在容差内才算闭环成功。
交付物
examples/your_chip_model/
model_data.cc
model_data.h
main.cc
input_adapter.cc
output_adapter.cc
README.md
完成条件
[ ] target board 上 AllocateTensors 成功
[ ] target board 上 Invoke 成功
[ ] 输出在明确误差范围内
[ ] 连续运行不会破坏 arena 或状态
[ ] 记录了 peak arena 使用量
如果这一阶段失败,先修复模型、输入、arena 或平台问题,不要把失败归因于“芯片还没有加速”。
7. 阶段 3:建立 NN library 边界
目标
让硬件实现与 TFLM 解耦,形成可独立测试的 NN library API。
推荐目录
your_nnlib/
include/
your_nnlib.h
src/
conv_int8.c
depthwise_conv_int8.c
fully_connected_int8.c
tests/
conv_test.c
fully_connected_test.c
tensorflow/lite/micro/kernels/your_chip/
README.md
your_chip_common.h
your_chip_common.cc
conv.cc
fully_connected.cc
NN library API 应明确
- 支持的数据类型。
- tensor layout。
- padding 和 stride 表示法。
- activation 范围。
- per-tensor/per-channel quantization。
- 输入输出 alignment 要求。
- scratch 大小和生命周期。
- 同步或异步执行方式。
- 错误码和超时行为。
例如 wrapper 不应猜测硬件库的量化约定,而应明确转换:
TFLM zero_point / multiplier / shift
-> your_nnlib quantization parameters
完成条件
[ ] NN library 可脱离 TFLM 单独编译
[ ] NN library 有独立输入输出测试
[ ] API 能表达目标 kernel 的限制
[ ] TFLM wrapper 不需要暴露硬件寄存器细节
8. 阶段 4:实现第一个 optimized kernel
目标
选择一个高收益、边界清晰的 op,完成 reference 到硬件实现的替换。
优先选择:
CONV_2D
DEPTHWISE_CONV_2D
FULLY_CONNECTED
选择依据应是 benchmark,而不是算子名称的流行程度。
wrapper 的职责
TFLM TfLiteEvalTensor
|
├── 检查 type / shape / quantization
├── 计算或读取 OpData
├── 申请和取得 scratch
├── 转换 padding / stride / activation
├── 调用 your_nnlib
└── 将错误码转成 TfLiteStatus
Prepare 负责检查能力和申请资源,Eval 或 Invoke 负责提交计算。不要在每次推理中重复做可以提前完成的参数计算。
下面是一个完整的 CONV_2D wrapper 示例,展示 Prepare 和 Eval 的骨架、OpData 结构定义、量化参数转换,以及调用 your_nnlib 的接口:
// tensorflow/lite/micro/kernels/your_chip/conv.cc
#include "tensorflow/lite/micro/kernels/your_chip/your_chip_common.h"
#include "your_nnlib.h" // 芯片 NN library 头文件
namespace tflite {
// OpData:在 Prepare 阶段计算并持久化,避免每次推理重复计算
struct OpData {
// 量化参数(TFLM 格式)
int32_t output_multiplier;
int output_shift;
int32_t output_zero_point;
int32_t input_zero_point;
int32_t filter_zero_point;
// 转换后的 your_nnlib 量化参数
your_nnlib_quant_params nn_quant;
// 卷积参数
int32_t padding_h;
int32_t padding_w;
int32_t stride_h;
int32_t stride_w;
// scratch buffer 索引
int scratch_index;
};
// 将 TFLM 量化参数转换为 your_nnlib 格式
static void ConvertQuantParams(const TfLiteAffineQuantization* quant,
OpData* data) {
// 读取 TFLM 的 multiplier / shift / zero_point
data->output_multiplier = quant->multiplier->data[0];
data->output_shift = quant->shift->data[0];
data->output_zero_point = quant->zero_point->data[0];
// 转换为 your_nnlib 的量化参数结构
data->nn_quant.multiplier = data->output_multiplier;
data->nn_quant.shift = data->output_shift;
data->nn_quant.zero_point = data->output_zero_point;
}
// Prepare:检查能力、计算 OpData、申请 scratch
TfLiteStatus ConvPrepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 3); // input, filter, bias
// 检查是否支持 int8(本示例只支持 int8)
const TfLiteEvalTensor* input =
tflite::micro::GetEvalInput(context, node, 0);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteInt8);
// 分配并初始化 OpData(persistent,跨 Invoke 保留)
OpData* data = reinterpret_cast<OpData*>(
context->AllocatePersistentBuffer(context, sizeof(OpData)));
node->user_data = data;
// 读取卷积参数(padding / stride)
auto* params = reinterpret_cast<TfLiteConvParams*>(node->builtin_data);
data->stride_h = params->stride_height;
data->stride_w = params->stride_width;
data->padding_h = params->padding == kTfLitePaddingSame ? 1 : 0;
// 转换量化参数
const auto* quant = reinterpret_cast<TfLiteAffineQuantization*>(
input->quantization.params);
ConvertQuantParams(quant, data);
// 申请 scratch buffer(例如用于 DMA 对齐的中间缓冲)
TF_LITE_ENSURE_STATUS(context->RequestScratchBufferInArena(
context, input->dims->data[1] * input->dims->data[2] * 4,
&data->scratch_index));
return kTfLiteOk;
}
// Eval:提交计算到 your_nnlib
TfLiteStatus ConvEval(TfLiteContext* context, TfLiteNode* node) {
OpData* data = reinterpret_cast<OpData*>(node->user_data);
// 获取输入输出 tensor
const TfLiteEvalTensor* input =
tflite::micro::GetEvalInput(context, node, 0);
const TfLiteEvalTensor* filter =
tflite::micro::GetEvalInput(context, node, 1);
TfLiteEvalTensor* output =
tflite::micro::GetEvalOutput(context, node, 0);
// 获取 scratch buffer
void* scratch = context->GetScratchBuffer(context, data->scratch_index);
// 构造 your_nnlib 的输入描述
your_nnlib_conv_params nn_params;
nn_params.input = static_cast<const int8_t*>(input->data.data);
nn_params.filter = static_cast<const int8_t*>(filter->data.data);
nn_params.output = static_cast<int8_t*>(output->data.data);
nn_params.quant = data->nn_quant;
nn_params.stride_h = data->stride_h;
nn_params.stride_w = data->stride_w;
nn_params.padding_h = data->padding_h;
nn_params.padding_w = data->padding_w;
nn_params.scratch = scratch;
// 调用芯片 NN library,并转换错误码
int ret = your_nnlib_conv2d_int8(&nn_params);
if (ret != YOUR_NNLIB_OK) {
return kTfLiteError; // 错误码转成 TfLiteStatus
}
return kTfLiteOk;
}
// 注册到 TFLM kernel 注册表
TFLMRegistration Register_CONV_2D() {
return tflite::micro::RegisterOp(ConvInit, ConvPrepare, ConvEval);
}
} // namespace tflite
关键点说明:
- Prepare 只做一次:检查 type/shape、计算 OpData、申请 scratch,避免在每次 Eval 中重复计算量化参数。
- OpData 使用 AllocatePersistentBuffer 分配,跨多次 Invoke 保留。
- 量化参数在 Prepare 中从 TFLM 格式转换为 your_nnlib 格式,Eval 直接使用。
- Eval 只负责把 tensor 指针、量化参数和卷积参数打包传给 your_nnlib,并把错误码转成 TfLiteStatus。
- 不支持的组合(如 float32)在 Prepare 中通过 TF_LITE_ENSURE_EQ 明确拒绝,避免静默产生错误结果。
fallback 策略
对不支持的组合必须明确处理:
支持 -> 调用 optimized kernel
不支持 -> 调用 reference kernel 或返回错误
不要静默产生错误结果。常见限制包括:
- 只支持 int8,不支持 float32。
- 只支持特定 stride、padding 或 dilation。
- 只支持 per-tensor quantization。
- 只支持特定 channel 对齐。
- DMA buffer 需要特定 alignment。
下面是完整的 fallback 决策流程:
#mermaid-svg-HVD7dsufoOHD2OA7{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HVD7dsufoOHD2OA7 .error-icon{fill:#552222;}#mermaid-svg-HVD7dsufoOHD2OA7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HVD7dsufoOHD2OA7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HVD7dsufoOHD2OA7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HVD7dsufoOHD2OA7 .marker.cross{stroke:#333333;}#mermaid-svg-HVD7dsufoOHD2OA7 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HVD7dsufoOHD2OA7 p{margin:0;}#mermaid-svg-HVD7dsufoOHD2OA7 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 .cluster-label text{fill:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 .cluster-label span{color:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 .cluster-label span p{background-color:transparent;}#mermaid-svg-HVD7dsufoOHD2OA7 .label text,#mermaid-svg-HVD7dsufoOHD2OA7 span{fill:#333;color:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 .node rect,#mermaid-svg-HVD7dsufoOHD2OA7 .node circle,#mermaid-svg-HVD7dsufoOHD2OA7 .node ellipse,#mermaid-svg-HVD7dsufoOHD2OA7 .node polygon,#mermaid-svg-HVD7dsufoOHD2OA7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HVD7dsufoOHD2OA7 .rough-node .label text,#mermaid-svg-HVD7dsufoOHD2OA7 .node .label text,#mermaid-svg-HVD7dsufoOHD2OA7 .image-shape .label,#mermaid-svg-HVD7dsufoOHD2OA7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-HVD7dsufoOHD2OA7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HVD7dsufoOHD2OA7 .rough-node .label,#mermaid-svg-HVD7dsufoOHD2OA7 .node .label,#mermaid-svg-HVD7dsufoOHD2OA7 .image-shape .label,#mermaid-svg-HVD7dsufoOHD2OA7 .icon-shape .label{text-align:center;}#mermaid-svg-HVD7dsufoOHD2OA7 .node.clickable{cursor:pointer;}#mermaid-svg-HVD7dsufoOHD2OA7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HVD7dsufoOHD2OA7 .arrowheadPath{fill:#333333;}#mermaid-svg-HVD7dsufoOHD2OA7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HVD7dsufoOHD2OA7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HVD7dsufoOHD2OA7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HVD7dsufoOHD2OA7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HVD7dsufoOHD2OA7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HVD7dsufoOHD2OA7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HVD7dsufoOHD2OA7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HVD7dsufoOHD2OA7 .cluster text{fill:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 .cluster span{color:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HVD7dsufoOHD2OA7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HVD7dsufoOHD2OA7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-HVD7dsufoOHD2OA7 .icon-shape,#mermaid-svg-HVD7dsufoOHD2OA7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HVD7dsufoOHD2OA7 .icon-shape p,#mermaid-svg-HVD7dsufoOHD2OA7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HVD7dsufoOHD2OA7 .icon-shape .label rect,#mermaid-svg-HVD7dsufoOHD2OA7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HVD7dsufoOHD2OA7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HVD7dsufoOHD2OA7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HVD7dsufoOHD2OA7 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
支持
支持
成功
失败
不支持
不支持
kernel 被调用(Eval/Invoke)
检查 type / shape / 量化参数
检查 stride / padding / dilation / channel 对齐
调用 optimized kernel(your_nnlib)
执行结果
返回 kTfLiteOk
返回 kTfLiteError
调用 reference kernel
决策要点:
- 在 Prepare 阶段就完成能力检查,把不支持的组合尽早拒绝,避免在每次 Eval 中重复判断。
- 只有所有条件都满足时才走 optimized 路径;任何一项不满足都回退到 reference kernel,保证正确性优先。
- 硬件执行失败(如 DMA 超时、命令提交错误)不能静默吞掉,必须转成 kTfLiteError 让上层可见。
下面是常见不支持组合在 optimized 与 reference 两条路径下的行为对比:
| float32 输入 | Prepare 阶段通过 TF_LITE_ENSURE_EQ 直接拒绝,不进入 Eval | 正常执行浮点 reference kernel,输出正确结果 | optimized 返回 kTfLiteError 并打印日志;reference 正常返回 kTfLiteOk |
| 特定 stride(如 stride=3) | Prepare 检查 stride 不在支持集合内,回退到 reference | 支持任意 stride,按参数正常计算 | optimized 不报错,静默回退到 reference;reference 返回 kTfLiteOk |
| per-channel quantization | Prepare 检测到 per-channel 量化参数,回退到 reference | 支持 per-channel scale/zero_point,逐通道反量化 | optimized 不报错,静默回退到 reference;reference 返回 kTfLiteOk |
| 特定 dilation(如 dilation=2) | Prepare 检查 dilation 超出支持范围,回退到 reference | 支持任意 dilation,按参数正常计算 | optimized 不报错,静默回退到 reference;reference 返回 kTfLiteOk |
| channel 数不满足对齐要求 | Prepare 检查 channel 对齐,不满足则回退到 reference | 无对齐要求,任意 channel 均可计算 | optimized 不报错,静默回退到 reference;reference 返回 kTfLiteOk |
| DMA buffer 未对齐 | Eval 阶段检查地址 alignment,不满足则回退到 reference | 无 DMA 依赖,普通内存访问即可 | optimized 不报错,静默回退到 reference;reference 返回 kTfLiteOk |
| 硬件执行失败(DMA 超时/命令错误) | Eval 调用 your_nnlib 返回非 YOUR_NNLIB_OK | 不涉及硬件调用,不存在该失败场景 | optimized 返回 kTfLiteError 并打印错误码;reference 不适用 |
对比要点:
- 能力检查类(float32、stride、dilation、per-channel、channel 对齐)在 Prepare 阶段完成,能提前判断就提前判断,避免每次 Eval 重复检查。
- 运行时失败类(DMA 超时、命令提交错误)只能在 Eval 阶段发现,必须转成 kTfLiteError 让上层可见,不能静默吞掉。
- 回退到 reference 是「正确性优先」的默认策略:只要 optimized 不支持,就保证 reference 能给出正确结果;只有硬件真正执行失败时才返回错误。
完成条件
[ ] 单算子测试通过
[ ] 不支持的 case 有明确行为
[ ] reference 和 optimized 输出一致或误差有定义
[ ] kernel 不引入不必要的数据复制
[ ] benchmark 证明有性能收益
9. 阶段 5:扩展热点 op
目标
用 profiling 结果决定下一批优化,而不是一次性实现全部 kernel。
推荐工作循环:
跑整模型 profiler
-> 找出热点 op
-> 选择一个 kernel
-> 加测试
-> 加 optimized wrapper
-> 重新跑整模型
-> 记录收益
每个 kernel 单独提交、单独测量,便于定位回归。
评估表
| 单次 latency | 记录 | 记录 | 降低 |
| 整模型 latency | 记录 | 记录 | 降低 |
| peak arena | 记录 | 记录 | 不恶化或可解释 |
| 固件代码体积 | 记录 | 记录 | 在预算内 |
| 功耗 | 记录 | 记录 | 在预算内 |
| 输出误差 | 基线 | 对比 | 满足精度要求 |
如果某个 kernel 加速很快,但引入大量 scratch 或复制导致整模型变慢,就不能只看该 kernel 的局部数字。
10. 阶段 6:内存、并发和稳定性
10.1 Tensor arena
用 RecordingMicroInterpreter 或 RecordingMicroAllocator 记录:
- head 使用量。
- tail 使用量。
- temporary 使用量。
- 每个 op 的 persistent allocation。
- scratch buffer 总量。
10.2 NPU/DSP 内存
明确区分:
TFLM tensor arena
芯片本地 SRAM
DMA 可访问内存
NPU command buffer
driver 工作区
cacheable / non-cacheable 区域
尽量让硬件直接使用 TFLM tensor buffer,避免 input/output copy;但如果硬件有 alignment、cache 或地址空间要求,必须在接口层明确处理。
10.3 异步执行
如果 accelerator 是异步的,必须定义:
- command 提交接口。
- 完成通知或轮询接口。
- timeout。
- 错误恢复。
- Invoke() 返回前的同步保证。
- 多次 Invoke() 的状态清理。
TFLM 的 kernel Invoke 返回后,相关 output tensor 必须已经满足应用读取约定。
完成条件
[ ] 连续 Invoke 稳定
[ ] 复位后可以再次运行
[ ] 超时和硬件错误可观察
[ ] cache/DMA 一致性有测试
[ ] 峰值内存没有超出芯片预算
11. 阶段 7:CI、发布和版本维护
CI 最小矩阵
host reference kernel tests
host optimized kernel tests
目标平台编译
目标平台 smoke test
代表模型推理测试
benchmark / size profiling
仓库已有的构建和测试入口包括:
bazel test //tensorflow/lite/micro/...
make -f tensorflow/lite/micro/tools/make/Makefile \\
TARGET=your_chip \\
OPTIMIZED_KERNEL_DIR=your_chip \\
microlite
实际 target 名称需要根据平台构建文件加入的位置调整。
优化后端建议提供:
tensorflow/lite/micro/tools/make/targets/your_chip_makefile.inc
tensorflow/lite/micro/tools/make/ext_libs/your_chip_nnlib.inc
tensorflow/lite/micro/tools/make/ext_libs/your_chip_nnlib_download.sh
如果使用 Bazel,也应提供对应的 BUILD target 和外部依赖声明。
版本策略
记录以下版本:
- TFLM commit 或 release。
- schema 版本。
- 芯片 SDK 版本。
- NN library 版本。
- compiler/toolchain 版本。
- firmware/driver 版本。
- 模型转换工具版本。
TFLM、模型转换器和 NN library 任一升级,都应重新跑模型兼容性测试。
12. 角色分工建议
| 平台工程师 | 工具链、启动、链接、日志、计时、内存布局。 |
| TFLM 工程师 | resolver、模型接入、kernel wrapper、reference 对齐。 |
| NN library 工程师 | 算法实现、SIMD/NPU API、scratch 和性能。 |
| driver/firmware 工程师 | 命令提交、DMA、cache、同步、错误恢复。 |
| 模型工程师 | 模型转换、量化、输入预处理、输出解释。 |
| QA/性能工程师 | 正确性、回归、benchmark、size 和功耗。 |
一个 kernel 的 owner 应同时拥有:接口约定、测试数据、benchmark 和失败 case,而不是只维护一份 .cc 文件。
13. 风险与应对
| 模型 op 未覆盖 | Missing registration | 先分析 operator_codes,再补 resolver。 |
| arena 不够 | AllocateTensors() 失败 | recording allocator 分析 head/tail/scratch。 |
| 量化不一致 | 输出偏差大 | 对照 scale、zero point、multiplier、shift。 |
| optimized 不稳定 | 只在某些 shape 失败 | 建立 shape/type/padding 参数矩阵测试。 |
| 加速没有收益 | 单 op 快但整模型不快 | 测量 copy、同步和调度开销。 |
| DMA/cache 错误 | 偶发错误或重复运行失败 | 增加 alignment、flush/invalidate 和压力测试。 |
| SDK 升级破坏构建 | CI 编译失败 | 固定版本并保存构建 manifest。 |
| 只验证 demo | 业务模型失败 | 尽早加入真实代表模型。 |
14. 工程验收标准
功能验收
[ ] 至少一个 float 或 int8 模型可以运行
[ ] 目标模型所有 op 都有 registration
[ ] AllocateTensors 和 Invoke 稳定成功
[ ] 输出与 reference 在约定误差内
[ ] custom op 有明确 options 解析
性能验收
[ ] 有 reference baseline
[ ] 有 optimized latency
[ ] 有整模型收益数据
[ ] 有 peak arena 数据
[ ] 有代码体积和功耗数据
[ ] benchmark 命令可复现
工程验收
[ ] 平台代码和 TFLM 后端边界清晰
[ ] NN library 可独立测试
[ ] 不支持的输入组合有明确行为
[ ] CI 能阻止功能和性能回归
[ ] 版本、工具链和模型来源有记录
15. 最小可执行路线图
如果团队资源有限,可以按以下顺序落地:
第 1 周:工具链、链接脚本、日志、计时、hello_world
第 2 周:一个真实模型 reference 推理和输出比对
第 3 周:NN library API、独立测试、性能基线
第 4 周:CONV_2D 或 FULLY_CONNECTED 第一个 optimized kernel
第 5 周:整模型 benchmark、arena 和错误处理
第 6 周:第二个热点 kernel、CI 和版本文档
周数只是组织方式,不能替代实际验收条件。模型复杂度、芯片 SDK 完整度和 NPU driver 状态都可能改变周期。
16. 今天的精髓
需求和模型
-> reference bring-up
-> 平台适配
-> 模型正确性闭环
-> NN library 边界
-> 第一个 optimized kernel
-> profiling 驱动扩展
-> CI 和版本维护
一句话总结:
AI 芯片接入 TFLM 不是把几个算子文件复制进工程,而是建立一条可验证的链路:平台能启动,模型能正确运行,硬件实现有清晰边界,性能收益可重复测量,版本升级不会悄悄破坏结果。
17. 相关源码和文档
- tensorflow/lite/micro/docs/new_platform_support.md
- tensorflow/lite/micro/docs/optimized_kernel_implementations.md
- tensorflow/lite/micro/examples/hello_world/hello_world_test.cc
- tensorflow/lite/micro/examples/hello_world/BUILD
- tensorflow/lite/micro/micro_interpreter.cc
- tensorflow/lite/micro/micro_mutable_op_resolver.h
- tensorflow/lite/micro/kernels/cmsis_nn/
- tensorflow/lite/micro/tools/project_generation/create_tflm_tree.py
- tensorflow/lite/micro/tools/make/targets/
- tensorflow/lite/micro/tools/make/ext_libs/
网硕互联帮助中心



评论前必须登录!
注册