先把任务缩到能审计的一份小模型
这份完整工程从生成16×16灰度圆/方轮廓,到40轮训练、标准PTQ、128个合成留出样本,再到S3构建和模型执行自检。circle=0、square=1,不识别现实照片,也不初始化相机、LCD、SD、麦克风、无线或执行器。
本站实际执行了桌面训练/PTQ与指定SDK构建;补充S3 QEMU参考自检失败,目标推理正确性尚未证明。本文提供完整实验与诊断方法,不能把CPU留出集100%写成实板或相机准确率。实体板只有先通过model->test,才可继续128样本;所有物理表仍空。
参考:[1] ESP-DL3.3.8固定manifest与IDF范围[2] 固定Model API:test、run与输入输出[3] 固定模型执行/参考比较实现[6] ESP-PPQ1.3.11固定训练后量化入口与目标
固定板、PSRAM与运行库,不混用Tensor接口
仅ESP32-S3-EYE-MB_V2.2,8MiB Flash/DIO80、8MiB OPI PSRAM80、CPU240MHz、main栈16KiB,原生Micro-USB Serial/JTAG19/20;无USB-UART桥。只电脑USB供电,不接电池或外设,断电检查板修订和排线。
IDF5.5.5/b774170…,ESP-DL3.3.8/77a8a624…、dl_fft0.8.0及原dependencies.lock。3.3.8指数是ExponentInfo,必须get(),并检查per-channel;不能将C++对象直接传printf变参。启动检查PSRAM已初始化且容量8MiB;之前关闭PSRAM的探索固件在QEMU模型构造处StoreProhibited,已保留,不能因为模型小就认定预编译分配路径无需PSRAM。
# ---- main/idf_component.yml ----
dependencies:
idf: "==5.5.5"
espressif/esp-dl: "==3.3.8"
espressif/dl_fft: "==0.8.0"参考:[1] ESP-DL3.3.8固定manifest与IDF范围[2] 固定Model API:test、run与输入输出[4] 固定TensorBase:ExponentInfo及内存能力[8] 固定S3-EYE指南:MB修订和原生USB
数据分割:测试集不参与校准或选模型
生成512训练、128验证、128测试,类别各半;每个样本有固定独立种子,全部768像素SHA互异。位置、半径、线宽、背景和噪声不按标签设置。合成分布内部留出不代表现实分布覆盖。dataset-manifest.json同时保文件与像素SHA。
训练40轮,以验证accuracy优先、再按验证loss选checkpoint;最后才评估test。64个PTQ校准ID严格从train选,calibration-ids.json可逐项核对。test-0000仅作为导出器的参考输入/输出,不是校准样本;不能拿测试预测改阈值再称独立测试。
| 数据 | 数量与唯一用途 |
|---|---|
| train | 512:梯度训练,64个校准样本只从这里选 |
| validation | 128:40轮中选择checkpoint |
| test | 128:最后评估并全部保留预测;test-0000另作导出参考 |
922参数网络与int8输入映射
网络为Conv1→8/ReLU/AvgPool2/Conv8→8/ReLU/AvgPool2/Flatten/Linear128→2,共922参数。导出图含Conv、AveragePool、Transpose、Flatten、Gemm与融合激活,须核固定目标算子和形状;PyTorch能跑不等于S3已正确执行。
ESP-PPQ1.3.11采用S3的8bit对称、2幂指数与ROUND_HALF_UP。训练浮点输入pixel/255;导出为INT8 NHWC1×16×16×1、exponent−7,固件用整数映射四舍五入并把128饱和到127。直接uint8转int8会把高像素绕成负数。tests/pixels_host.c直接包含生产头,检查全部256输入。
// ---- main/pixels.h ----
/* SPDX-License-Identifier: MIT */
#pragma once
#include <stdint.h>
/* Fixed exported input exponent -7: real=pixel/255, q=round_half_up(real*128).
* Inputs are nonnegative. Saturate128 to127, never wrap into a negative int8. */
static inline int8_t v05_quantize_pixel(uint8_t pixel) {
unsigned q=((unsigned)pixel*128U+127U)/255U;
return(int8_t)(q>127U?127U:q);
}参考:[4] 固定TensorBase:ExponentInfo及内存能力[6] ESP-PPQ1.3.11固定训练后量化入口与目标[7] ESP-DL固定支持算子与量化规范
真实桌面结果与可获取的全部数据
本轮CPU浮点及CPU量化图test矩阵都为行truth/列prediction [[64,0],[0,64]],128条prediction改变0。所有logits、标签和预测都保留,不只给成功截图;这是有限合成留出集,未做置信度校准或域外拒识。
下载包含768PNG、manifest、64校准ID、40轮history、浮点权重、ONNX、转换配置/信息及128预测;当前模型7296B、SHA256 c495b5de7df02046c6596ae84d5c7c832e88c45a0657165fe1fb3eef7da594ec。另一个16通道诊断候选有自己的数据和产物,不替换本下载的原8通道模型。
先构建固定输入,保存同次可恢复档案
解压后激活IDF5.5.5,在工程根使用新目录:IDF_PY_BUILD_JOBS=2 idf.py -B build-v05 -D IDF_TARGET=esp32s3 -D SDKCONFIG=build-v05/sdkconfig build。三个输入逐SHA门禁同时核模型、128×256像素和128标签;不删锁或跳过门禁。模型已在app RODATA内,资源汇总不要再重复加7296B。
NVS0x9000/0x6000、phy0xf000/0x1000、factory0x10000/0x300000,无OTA。保存源码、锁、实际配置、compile_commands、完整log、ELF/map、app/bootloader/分区bin与flasher_args。刷前按E09私密备份原板完整8MiB Flash/布局/NVS和SHA,不能自动erase或把app当完整Flash写0x0。
# ---- main/CMakeLists.txt ----
include(${CMAKE_CURRENT_LIST_DIR}/inputs.cmake)
foreach(item shape-s3.espdl heldout-pixels.bin heldout-labels.bin)
file(SHA256 "${CMAKE_CURRENT_LIST_DIR}/${item}" actual_sha)
if(NOT actual_sha STREQUAL "${V05_SHA_${item}}")
message(FATAL_ERROR "V05 input identity mismatch: ${item}")
endif()
endforeach()
idf_component_register(SRCS "main.cpp" INCLUDE_DIRS "." REQUIRES esp-dl esp_timer heap esp_psram
EMBED_FILES "shape-s3.espdl" "heldout-pixels.bin" "heldout-labels.bin")
target_compile_definitions(${COMPONENT_LIB} PRIVATE V05_MODEL_SHA256="${V05_SHA_shape-s3.espdl}")参考:[1] ESP-DL3.3.8固定manifest与IDF范围[2] 固定Model API:test、run与输入输出[8] 固定S3-EYE指南:MB修订和原生USB
参考自检先于128样本,失败就停止
主程序先查二进制长度、PSRAM容量,再构造模型并记录LOAD。model->test对标准导出参考进行目标执行比较;失败会释放模型、打印STOP并退出,零条CASE不能写作128次成功。之后才核INT8输入/输出、NHWC形状、输入指数−7与输出两项,并拒绝per-channel输入/输出。
每条CASE打印test-ID、truth/pred、两项raw logit、共同exponent和围model->run的us;打印发生在计时结束后。等值固定选circle只是tie规则,未做softmax,raw logit或差值不是校准概率。MEM分别列internal/PSRAM的free与largest;free不等于最大连续块,单轮released近似恢复也不证明长期无泄漏。
// ---- main/main.cpp ----
/* SPDX-License-Identifier: MIT */
#include <cstdio>
#include <cinttypes>
#include <cstring>
#include "dl_model_base.hpp"
#include "esp_heap_caps.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "esp_psram.h"
#include "pixels.h"
extern const uint8_t model_start[] asm("_binary_shape_s3_espdl_start");
extern const uint8_t pixels_start[] asm("_binary_heldout_pixels_bin_start");
extern const uint8_t pixels_end[] asm("_binary_heldout_pixels_bin_end");
extern const uint8_t labels_start[] asm("_binary_heldout_labels_bin_start");
extern const uint8_t labels_end[] asm("_binary_heldout_labels_bin_end");
static void memory(const char *phase) {
printf("MEM,%s,internal_free=%u,internal_largest=%u\n",phase,
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
printf("PSRAM,%s,free=%u,largest=%u\n",phase,
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
}
extern "C" void app_main(void) {
printf("V05,SYNTHETIC_HELDOUT_ONLY,idf=%s\nMODEL_SHA256,%s\n",esp_get_idf_version(),V05_MODEL_SHA256);
if((size_t)(pixels_end-pixels_start)!=128U*256U||(size_t)(labels_end-labels_start)!=128U){printf("STOP,case_lengths\n");return;}
if(!esp_psram_is_initialized()||esp_psram_get_size()!=8U*1024U*1024U){printf("STOP,requires_8MiB_PSRAM\n");return;}
memory("before_model");const int64_t load_start=esp_timer_get_time();
auto *model=new dl::Model((const char*)model_start,fbs::MODEL_LOCATION_IN_FLASH_RODATA);
printf("LOAD,us=%" PRId64 "\n",esp_timer_get_time()-load_start);memory("loaded");
/* Runs the standard exporter-stored reference, not a probability threshold. */
const esp_err_t test=model->test();printf("EXPORT_REFERENCE_TEST,%s\n",esp_err_to_name(test));
if(test!=ESP_OK){delete model;printf("STOP,reference_mismatch\n");return;}
auto *input=model->get_input();auto *output=model->get_output();
if(!input||!output||input->dtype!=dl::DATA_TYPE_INT8||output->dtype!=dl::DATA_TYPE_INT8||
input->shape!=std::vector<int>({1,16,16,1})||(input->exponent.is_per_channel()||input->exponent.get()!=-7)||output->exponent.is_per_channel()||output->get_size()!=2){
delete model;printf("STOP,export_contract\n");return;
}
unsigned confusion[2][2]={{0,0},{0,0}};
for(unsigned n=0;n<128;n++) {
if(labels_start[n]>1){printf("STOP,label_range\n");delete model;return;}
auto *destination=(int8_t*)input->data;
for(unsigned i=0;i<256;i++)destination[i]=v05_quantize_pixel(pixels_start[n*256+i]);
const int64_t began=esp_timer_get_time();model->run();const int64_t elapsed=esp_timer_get_time()-began;
const auto *logits=(const int8_t*)output->data;const unsigned prediction=logits[1]>logits[0]?1:0;
confusion[labels_start[n]][prediction]++;
printf("CASE,test-%04u,truth=%u,pred=%u,raw0=%d,raw1=%d,exponent=%d,us=%" PRId64 "\n",n,(unsigned)labels_start[n],prediction,(int)logits[0],(int)logits[1],output->exponent.get(),elapsed);
}
printf("SYNTHETIC_CONFUSION,circle=%u/%u,square=%u/%u\n",confusion[0][0],confusion[0][1],confusion[1][0],confusion[1][1]);
memory("after_128");delete model;memory("released");
printf("END,no_camera_no_GPIO_no_real_object_accuracy\n");
}参考:[2] 固定Model API:test、run与输入输出[3] 固定模型执行/参考比较实现[4] 固定TensorBase:ExponentInfo及内存能力
QEMU失败如实保留,诊断不能替代目标通过
固定QEMU9.2.2补充配置必须保持stdin打开、8MiB RAM并设置ssi_psram.is_octal=true;默认Quad不是OPI。恢复正确OPI后,原8通道模型在runtime3.2.0和3.3.8均参考28/执行−128,零条CASE。模型能装载、宿主退出0或固件能链接都不是目标结果正确。
保持原权重/校准/输入额外导出六层参考后,首次差异在首层Conv+ReLU NHWC[0,0,0,3]:参考0、模拟127。FbsLoader未对齐警告实际会启用param_copy,不能仅凭警告宣布根因;guest_errors也没有可指认该卷积未实现PIE的证据。另一个16通道2994参数候选虽真实训练/构建,参考77/模拟127仍失败。原因尚未确定,不能断言QEMU、转换器或实体S3有已证实缺陷。
参考:[3] 固定模型执行/参考比较实现[4] 固定TensorBase:ExponentInfo及内存能力[5] 固定FbsLoader:未对齐参数拷贝[9] 官方QEMU固定S3扩展实现;不能由失败推断未实现PIE
从零重训:独立venv、完整脚本、新目录
本轮macOS arm64/Python3.12.13、CPU单线程,无CUDA;固定torch2.8.0、esp-ppq1.3.11、onnx1.17.0、onnxruntime1.22.1、numpy1.26.4、protobuf4.25.8、Pillow12.3.0与flatbuffers25.12.19。用tools/requirements.txt安装到独立venv,不改IDF的Python。
运行python tools/train_export.py --out NEW_TRAINING,再python tools/prepare_inputs.py --training NEW_TRAINING --out NEW_PROJECT;也可--training reference复用本轮数据。两out均必须新目录。准备器核768图/像素身份、跨分割重复、train-only校准、test顺序、模型SHA与输入格式,再复制精确manifest/锁和源,按实际训练结果刷新versions.json模型SHA/字节数及训练结果SHA,生成独立工程。
固定种子与确定性算法不能保证另一平台的浮点/序列化SHA相同,须保自己的结果/版本/算子图并重新过目标自检。最初裸Tensor导出真实失败,修为inputs=[tensor]后完整重训;原失败和后续诊断均保留,不将日志里的探索模型混为当前公开模型。
实板验收与恢复:保存失败,门禁不让步
确认板修订、USB端口、备份与同次flasher_args后才自行idf.py -B build-v05 -p YOUR_NATIVE_USB_PORT flash monitor;原生USB会重枚举,macOS/Linux/Windows分别核实际端口。必要时按住BOOT、点RESET、松BOOT,再找端口,Ctrl+]退出monitor。本文没有执行实体烧录或推理。
预期顺序V05/MODEL_SHA、MEM/PSRAM、LOAD、EXPORT_REFERENCE_TEST;实体ESP_OK后才应出现128 CASE、SYNTHETIC_CONFUSION和END。若自检失败,保存完整log、模型/配置/ELF,停止分类与动作,再核输入/算子/版本。PSRAM失败不靠关检查绕过,panic用同次ELF。恢复采用同板原完整备份/原布局及匹配eFuse状态,不只写旧app到新factory偏移。
records表由真实板填写,包含全部失败与资源条件。QEMU us/heap不进入实体表;没有本站相机泛化、实体准确率、时延/heap、电流、能耗或续航结果。后续V10分拣还必须独立收集现实数据、验证低分拒绝与动作恢复,不能直接继承本页CPU100%。
参考:[2] 固定Model API:test、run与输入输出[3] 固定模型执行/参考比较实现[8] 固定S3-EYE指南:MB修订和原生USB
资料来源与版本说明
固定Model/Tensor/FbsLoader/算子与PPQ接口核对;完整源、训练/PTQ/失败和同次构建证据分别留档,不把CPU图或链接成功当S3执行通过。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。