云计算百科
云计算领域专业知识百科平台

轻松学习TFLM_day8

摘要:本文把前 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 最终目标

完成后,目标平台应能够:

  • 编译 TFLM 静态库和目标平台应用。
  • 加载至少一个 .tflite 模型。
  • 使用静态 tensor_arena 完成 AllocateTensors()。
  • 调用 Invoke() 获得正确输出。
  • 通过 UART、RTT 或平台日志观察运行状态。
  • 测量单算子和整模型 latency。
  • 按需将热点 op 替换为芯片优化实现。
  • 在 reference 与 optimized 之间进行正确性和性能对比。
  • 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 数据。

    工作项

  • 确定目标芯片型号、核心、时钟和内存布局。
  • 确认 C++17 工具链、SDK、IDE 和链接器版本。
  • 选择一到三个代表性 .tflite 模型。
  • 用 Day 6 的工具找出每个模型的唯一 op。
  • 记录模型输入、输出、类型、shape 和量化参数。
  • 在 host 上保存 reference 输出和测试输入。
  • 定义 latency、arena、代码体积和功耗的测量方法。
  • 交付物

    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
    -> 实际业务模型

    工作项

  • 把 .tflite 转成 C 数组,或由构建系统生成数组。
  • 将模型 .cc 和 .h 加入目标工程。
  • 根据模型 op 创建最小 resolver。
  • 分配静态 tensor arena。
  • 检查模型 schema 版本。
  • 检查输入 tensor 的 type、shape 和 bytes。
  • 进行输入预处理和量化。
  • 调用 AllocateTensors()。
  • 调用 Invoke()。
  • 对输出进行反量化并和 host reference 比较。
  • 典型代码结构

    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 两条路径下的行为对比:

    不支持组合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 单独提交、单独测量,便于定位回归。

    评估表

    指标referenceoptimized目标
    单次 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/
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 轻松学习TFLM_day8
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!