maximum_timeflow_3.0_release 全量跑分测试报告
maximum_timeflow_3.0_release 全量跑分测试报告
一、项目概述
| 项目 | 信息 |
|---|---|
| 项目仓库 | https://atomgit.com/Harmony_timeflow/maximum_timeflow.git |
| 分支 | main |
| 提交(HEAD) | 45fdd1c |
| 完整哈希 | 45fdd1caa17fbb99395ccfe2e0aae94949c8e2ac |
| 提交时间 | 2026-09-21 20:41:47 +0800 |
| 提交说明 | !56 merge main into main |
| 项目版本 | 0.1.0 |
| 被测模型 | 异常检测模型 128->32->16->1(3 层全连接),已 Ed25519 签名的 .htf 格式 |
二、跑分环境
| 项目 | 信息 |
|---|---|
| 操作系统 | WSL2 Ubuntu 24.04 LTS(内核 5.15.167.4-microsoft-standard-WSL2) |
| 宿主系统 | Windows 11 Pro 10.0.26200 |
| 处理器 | 12th Gen Intel Core i9-12900K(24 逻辑核) |
| 内存 | 物理 63.8 GB,WSL2 上限 50 GB(实测可用 49Gi) |
| 架构 | x86_64 |
| 编译器 | gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 |
| 构建工具 | cmake 3.28.3 + ninja 1.11.1 |
| 构建类型 | Release,优化级别 -O3 |
| 内核路径 | avx2+fma |
| 链接依赖 | OpenSSL 3.0.13 / zlib 1.3 / libjpeg-turbo 2.1.5 / libm |
| 分配审计 | -Wl,–wrap=malloc/calloc/realloc/free(GNU ld 2.42,已验证接通) |
| 单项迭代数 | 2000(项目默认值) |
| 跑分时间 | 2026-09-21T13:02:35Z |
| 单调时钟 | 可用 |
| 构建结果 | 0 警告 0 错误,28 个翻译单元 |
三、跑分总体结论
| 程序 | 结果 | 判定依据 | 退出码 |
|---|---|---|---|
| htf_bench_inference(宿主端) | PASS | 0 failure(s); all measured cases validated | 0 |
| htf_bench_constrained(受限端) | PASS | 0 failure(s); correctness verified, zero-allocation confirmed | 0 |
说明: 本项目的跑分程序不只是计时,还会校验测量结果——被测值与参考值不符、或分配审计在声称"零分配"的路径上发现分配,程序即退出非 0。两个程序均返回 0,且判定为 pass,即所有被测项均通过校验。
重要限定(工件中 device_figure=false): 本次跑分在 x86_64 宿主机上执行,所有时延数字均标注为 host-only,仅描述本机,不代表 riscv32 MCU 设备性能。项目自身明确禁止任何跨架构换算,本次也未做任何换算或外推。内存占用与分配次数为 portable(可移植),在任何目标上都成立。
四、htf_bench_inference 跑分明细(宿主端,2000 次迭代/项)
4.1 FP32 全连接层 GEMV(htf_dense_f32,激活=RELU)
内核路径: avx2+fma
| 层形状 | min(ns) | mean(ns) | p50(ns) | p95(ns) | max(ns) | n | ops/sec |
|---|---|---|---|---|---|---|---|
| 128x32 | 280 | 311.8 | 295.0 | 398.0 | 1049 | 2000 | 3207354 |
| 32x16 | 51 | 55.9 | 53.0 | 54.0 | 5875 | 2000 | 17873580 |
| 16x1 | 24 | 25.0 | 25.0 | 26.0 | 34 | 2000 | 40072933 |
| 1x1 | 18 | 19.3 | 19.0 | 20.0 | 38 | 2000 | 51912994 |
| 7x3 | 26 | 28.2 | 28.0 | 29.0 | 41 | 2000 | 35467910 |
| 64x64 | 222 | 239.8 | 226.0 | 246.0 | 9504 | 2000 | 4170385 |
| 255x129 | 2551 | 2762.4 | 2702.0 | 2940.0 | 26938 | 2000 | 362010 |
数据特征(源自工件原始数据,如实记录):
- 32x16: max=5875 ns 为极端离群值,把 mean(55.9) 拉到 p95(54) 之上。这是工件里的真实数据,图表中该点误差棒下界按 0 处理并已标注。
- 64x64: max=9504 ns 达 p95(246) 的 39 倍(首次调用/调度抖动所致),中位数与 p95 未受影响,稳态性能可信。
4.2 标量 vs 向量路径对比(形状 255x129)
| 路径 | min | mean | p50 | p95 | ops/sec |
|---|---|---|---|---|---|
| 强制标量 scalar | 10956 | 11115.3 | 11021.0 | 11191.0 | 89966 |
| 分发 avx2+fma | 2599 | 2791.8 | 2712.0 | 3227.0 | 358195 |
| 向量加速比 | 3.98x |
限定: 该加速比仅对本机 AVX2+FMA 核心有效;riscv32 目标无此路径,走标量代码。
4.3 INT8 定点 vs FP32 浮点(同一逻辑层 128x32)
| 表示 | min | mean | p50 | p95 | ops/sec |
|---|---|---|---|---|---|
| INT8 定点 int8_dense_act | 379 | 385.8 | 384.0 | 387.0 | 2592060 |
| FP32 浮点 dense_f32 | 293 | 306.7 | 300.0 | 322.0 | 3260441 |
| 时间比 mean(fp32)/mean(int8) | 0.80x |
可移植部分(字节数,与本机无关,任何目标都成立):
- 真实模型权重: 4624 B int8 vs 18496 B fp32
- INT8 节省: 13872 B saved (4.0x smaller)
说明: 本机 INT8 反而比 FP32 慢(比值 < 1),因为 AVX2+FMA 的 FP32 路径在该 CPU 上已高度优化;INT8 的真正收益在于 Flash 占用减少 4 倍、以及无需 FPU——这正是无浮点单元 MCU 上的决定性优势。
4.4 端到端引擎 htf_engine_run(已签名 .htf 模型,STRICT 验签)
引擎配置: native fp32 avx2+fma layers=3 in=128 out=1 arena=1152B
模型形状: in=128 out=1 layers=3
量化参数: in scale=0.0409732 zp=-6, out scale=0.00194258 zp=-128
| 路径 | min | mean | p50 | p95 | ops/sec |
|---|---|---|---|---|---|
| FP32 engine_run (3 layers, avx2+fma) | 339 | 398.1 | 352.0 | 444.0 | 2512023 |
| INT8 engine_run (in=128 out=1, packed int8) | 495 | 502.5 | 502.0 | 507.0 | 1990040 |
推理期堆分配(经 --wrap 精确计数):
- FP32: 0 over 2000 runs
- INT8: 0 over 2000 runs
- FP32 申请字节: 0 B requested
内存占用(可移植):
- arena 已分配: 1152 B
- 占 128 KB 预算: 1152 B = 1.1 KB of a 128 KB budget
4.5 受限平台适配性(可移植数字,非本机时延)
- arena 落在 128 KB 预算内: 1152 B of 131072 B budget (0.88%)
- 64 字节 arena 上限: refused at load (ok)
- INT8 模型加载: quantised=1, arena=1152 B
- LiteOS-M 子系统静态足迹: ROM 22.3 KB(其中 18.8 KB 为权重表),静态 RAM 约 1 KB + 2 KB 任务栈
4.6 分配审计自检
通过(计数通路已验证接通)
含义:审计在报告任何"零"之前先自检,证明计数钩子真的生效,因此"零分配"是实测结果,而非"没测到"伪装成的零。
五、htf_bench_constrained 跑分明细(受限端,2000 次迭代)
链接范围: 仅 platform/liteos_m 的 3 个翻译单元:htf_ai_lite.c / htf_ai_model.c / float_weights_output.c。不链接 OpenSSL、不链接宿主引擎、无文件 I/O(权重编译期内置)——即真正随固件出货到设备的那部分代码。
5.1 模型几何
| 项目 | 详情 |
|---|---|
| 模型 | 3 layers, in=128 out=1 |
| 第 0 层 | 128x32 w=0x55911f3306e0 b=yes |
| 第 1 层 | 32x16 w=0x55911f334760 b=yes |
| 第 2 层 | 16x1 w=0x55911f334fa0 b=yes |
| 静态缓冲校验 | widest=128, static buffer=128 (fits) |
| 权重表(Flash) | 4624 weights = 18496 B fp32 |
| 推理暂存 RAM | 1024 B = 1.0 KB (2 x 128 floats) |
5.2 数值正确性
| 测试项 | 结果 |
|---|---|
| 异常概率 | got 0.471768856, numpy golden 0.471768826, diff 2.98e-08 (tol 0.001) |
| softmax 常量回归检查 | not triggered |
| 黄金值来源 | tools/gen_liteos_golden.py 用 numpy 独立算出,与被测 C 代码无共享实现,故 C 的缺陷无法自我验证 |
5.3 堆分配审计(整生命周期,经 --wrap 精确计数)
| 测量项 | 详情 | 值 |
|---|---|---|
| 审计自检 | counting path verified | passed |
| 堆分配次数 | whole lifecycle | 0 malloc, 0 calloc, 0 realloc over init+2000 runs+deinit |
| 申请堆字节 | whole lifecycle | 0 B |
| 释放次数 | whole lifecycle | 0 |
| 判定 | allocation-free inference | CONFIRMED by measurement |
意义: init + 2000 次推理 + deinit 全程零堆分配,核心改用 2 块静态 BSS 缓冲。这决定了"128 KB 设备能否跑"——没有第 N 次调用因堆碎片而失败的风险。
5.4 推理时延(本机,非设备数字)
| 项目 | 详情 |
|---|---|
| 运行于 | host (NOT a device figure) |
| 测量项 | min | mean | p50 | p95 | max | ops/sec |
|---|---|---|---|---|---|---|
| 完整前向传播 128->1 (3 layers) | 1561 | 1599.1 | 1575.0 | 1676.0 | 9562 | 625363 |
- 单次推理时延: 1.599 us mean, 1.676 us p95
- device_figure: False(工件字段,编译期决定,不可用命令行伪造)
六、正确性与内存安全验证(配套测试套件,26 项全通过)
跑分程序自身会校验测量结果;此外还完整运行了项目的测试套件:
6.1 分组 A:单元测试 (unit) — 12 项全部 Passed
- test_cpu_features
- test_version
- test_status
- test_format
- test_arena
- test_kernels
- test_kernels_int8
- test_crypto
- test_model_loader
- test_engine
- test_liteos_real_model
- test_transformer
6.2 分组 B:黄金值测试 (golden) — 4 项全部 Passed
对 numpy 独立参考实现校验,覆盖引擎、模型加载器、LiteOS-M 真实模型、Transformer
6.3 分组 C:跑分冒烟 (benchmark) — 2 项全部 Passed
- bench_constrained_smoke
- bench_inference_smoke
6.4 分组 D:valgrind 内存检查 — 12 项全部 Passed(0 泄漏 0 非法访问)
- –leak-check=full --errors-for-leak-kinds=definite --track-origins=yes
- –error-exitcode=1,即发现泄漏会使测试变红而非静默通过
6.5 合计
ctest 26/26 通过,100% tests passed, 0 tests failed
七、交付内容清单
7.1 图表(7 张 PNG)
全部由 JSON 工件解析数据生成:
- 图1_FP32全连接层_各形状时延与吞吐.png — 7 种层形状的 min/mean/p95 与吞吐
- 图2_标量与AVX2向量路径对比.png — 加速比可视化
- 图3_INT8与FP32对比.png — 时延对比 + Flash 字节对比
- 图4_端到端引擎_FP32与INT8.png — 引擎时延 + 可移植数字表
- 图5_受限端完整前向传播时延分布.png — min/mean/p50/p95/max 分布
- 图6_可移植内存足迹与零分配结论.png — 内存足迹对数图 + 零分配判定
- 图7_数值正确性校验.png — 实测 vs numpy 黄金值
7.2 HTML 报告(2 份自包含报告)
CSS 与图表全内联,离线可看:
- 跑分报告_交互图表_plotly.html — 交互式,可悬停查看数值(约 16 MB)
- 跑分报告_轻量版_svg.html — 纯 SVG 零依赖,约 35 KB,便于分享
两份均由项目自带的 benchmark/report/report.py 渲染,含诚实性徽标:
- portable: 74 处
- host-only: 18 处
- device-measured: 0 处
并在 <head> 写入 provenance meta 标签:
- htf-bench-commits = 45fdd1c
- htf-bench-device-figures = false
7.3 数据/JSON 工件(唯一事实来源,报告与图表都由它生成)
- 20260921-x86_64-inference-2000.bench.json — 37 条记录(13 timing/16 metric/8 note)
- 20260921-x86_64-constrained-2000.bench.json — 20 条记录(1 timing/17 metric/2 note)
格式 htf-bench/1,命名遵循项目 benchmark/results/README.md 规范
7.4 数据/CSV(机器可读,12 列固定格式)
- inference.csv — 38 行
- constrained.csv — 24 行
列:kind, section, metric, detail, value, min_ns, mean_ns, p50_ns, p95_ns, max_ns, n, ops_per_sec
7.5 数据/文本输出(跑分程序 stdout 原文,便于逐项核对)
- htf_bench_inference_完整输出.txt
- htf_bench_constrained_完整输出.txt
八、复现方法
环境: WSL2 Ubuntu 24.04 LTS(本次新装,已配 50 GB 内存上限)
依赖: build-essential cmake ninja-build pkg-config libssl-dev zlib1g-dev libjpeg-dev python3-numpy python3-matplotlib python3-plotly valgrind
git clone https://atomgit.com/Harmony_timeflow/maximum_timeflow.git
cd maximum_timeflow
cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DHTF_ENABLE_OPENSSL=ON -DHTF_BUILD_BENCHMARKS=ON -DHTF_BUILD_TESTS=ON
ninja -C build htf_fixtures htf_bench_inference htf_bench_constrained
ninja -C build
./build/benchmark/htf_bench_inference --iters 2000 \
--model-fp32 build/tests/fixtures/real_model_fp32.htf \
--model-int8 build/tests/fixtures/real_model_int8.htf \
--json 结果目录/日期-x86_64-inference-2000.bench.json
./build/benchmark/htf_bench_constrained --iters 2000 \
--json 结果目录/日期-x86_64-constrained-2000.bench.json
python3 benchmark/report/report.py 结果目录/*.bench.json -o 报告.html
ctest --test-dir build --output-on-failure -j$(nproc)
注意: Windows 原生(MSVC)无法构建本项目,跑分程序硬依赖 GNU ld 的 -Wl,–wrap=malloc 实现分配审计,且测试框架用到 GCC/Clang 的 attribute((constructor))。必须用 GCC/Clang 工具链。
九、诚实性声明
- 所有数字均为本机实测,来自跑分程序 --json 产出的原始工件,未经手工编辑。
- 未做任何跨机器归一化、换算或外推;宿主机时延一律标注 host-only。
- device_figure=false:本次未在 riscv32 设备上实测,因此不提供任何"设备时延"。编造一个 MCU 数字是不诚实的——项目文档明确要求如此,本次严格遵守。
- 内存占用、Flash 字节数、堆分配次数为 portable 结论,在任何目标上都成立。
- 时延数字会随机器、温度、后台负载波动;同一份代码重跑数值会略有差异,但零分配、正确性校验、内存预算这类结论是确定性的,不随重跑变化。
十、JSON 工件完整数据
10.1 htf_bench_constrained JSON 工件(20260921-x86_64-constrained-2000.bench.json)
{
"format": "htf-bench/1",
"env": {
"program": "htf_bench_constrained",
"iterations": 2000,
"clock_available": true,
"device_figure": false,
"kernel_path": "n/a (LiteOS-M core, no runtime dispatch)",
"arch": "x86_64",
"model_fp32": null,
"model_int8": null,
"version": "0.1.0",
"git_hash": "45fdd1c",
"timestamp_utc": "2026-09-21T13:02:36Z"
},
"entries": [
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "model", "detail": "built-in weights, no file I/O", "value": "3 layers, in=128 out=1"},
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "layer 0", "detail": "shape / operands present", "value": "128x32 w=0x55911f3306e0 b=yes"},
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "layer 1", "detail": "shape / operands present", "value": "32x16 w=0x55911f334760 b=yes"},
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "layer 2", "detail": "shape / operands present", "value": "16x1 w=0x55911f334fa0 b=yes"},
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "static buffer sizing", "detail": "ping-pong scratch", "value": "widest=128, static buffer=128 (fits)"},
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "weight table", "detail": "in flash (.rodata)", "value": "4624 weights = 18496 B fp32"},
{"kind": "metric", "section": "Constrained target: model geometry (LiteOS-M core)", "metric": "inference scratch RAM", "detail": "static BSS, allocated once at link time", "value": "1024 B = 1.0 KB (2 x 128 floats)"},
{"kind": "metric", "section": "Constrained target: numerical correctness", "metric": "anomaly probability", "detail": "recorded input vector", "value": "got 0.471768856, numpy golden 0.471768826, diff 2.98e-08 (tol 0.001)"},
{"kind": "metric", "section": "Constrained target: numerical correctness", "metric": "softmax-constant regression", "detail": "output must not be 1.0", "value": "not triggered"},
{"kind": "metric", "section": "Constrained target: heap allocation audit", "metric": "audit self-test", "detail": "counting path verified", "value": "passed"},
{"kind": "metric", "section": "Constrained target: heap allocation audit", "metric": "heap allocations", "detail": "whole lifecycle", "value": "0 malloc, 0 calloc, 0 realloc over init+2000 runs+deinit"},
{"kind": "metric", "section": "Constrained target: heap allocation audit", "metric": "heap bytes requested", "detail": "whole lifecycle", "value": "0 B"},
{"kind": "metric", "section": "Constrained target: heap allocation audit", "metric": "heap frees", "detail": "whole lifecycle", "value": "0"},
{"kind": "metric", "section": "Constrained target: heap allocation audit", "metric": "VERDICT", "detail": "allocation-free inference", "value": "CONFIRMED by measurement"},
{"kind": "note", "section": "Constrained target: heap allocation audit", "text": "zero heap allocations across init, 2000 inferences and deinit. The core uses 2 static BSS buffers instead. This is what makes the run path safe on a device whose heap cannot be relied on: there is no allocation to fail on the Nth call, and no fragmentation to accumulate over a device's uptime."},
{"kind": "metric", "section": "Constrained target: inference timing (THIS machine)", "metric": "running on", "detail": "architecture", "value": "host (NOT a device figure)"},
{"kind": "note", "section": "Constrained target: inference timing (THIS machine)", "text": "these timings describe this host. They are not a prediction of MCU latency and must not be quoted as one. Cross-compile this file for the target and run it there for real device numbers."},
{"kind": "timing", "section": "Constrained target: inference timing (THIS machine)", "metric": "full forward pass", "detail": "128->1, 3 layers", "min_ns": 1561.0, "mean_ns": 1599.1, "p50_ns": 1575.0, "p95_ns": 1676.0, "max_ns": 9562.0, "n": 2000, "ops_per_sec": 625363.10},
{"kind": "metric", "section": "Constrained target: inference timing (THIS machine)", "metric": "per-inference latency", "detail": "this machine", "value": "1.599 us mean, 1.676 us p95"},
{"kind": "metric", "section": "Run verdict", "metric": "BENCH RESULT", "detail": "pass", "value": "0 failure(s); correctness verified, zero-allocation confirmed"}
]
}
10.2 htf_bench_inference JSON 工件(20260921-x86_64-inference-2000.bench.json)
{
"format": "htf-bench/1",
"env": {
"program": "htf_bench_inference",
"iterations": 2000,
"clock_available": true,
"device_figure": false,
"kernel_path": "avx2+fma",
"arch": "x86_64",
"model_fp32": "build/tests/fixtures/real_model_fp32.htf",
"model_int8": "build/tests/fixtures/real_model_int8.htf",
"version": "0.1.0",
"git_hash": "45fdd1c",
"timestamp_utc": "2026-09-21T13:02:35Z"
},
"entries": [
{"kind": "note", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "text": "kernel path in this process: avx2+fma"},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "128x32 avx2+fma", "min_ns": 280.0, "mean_ns": 311.8, "p50_ns": 295.0, "p95_ns": 398.0, "max_ns": 1049.0, "n": 2000, "ops_per_sec": 3207353.82},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "32x16 avx2+fma", "min_ns": 51.0, "mean_ns": 55.9, "p50_ns": 53.0, "p95_ns": 54.0, "max_ns": 5875.0, "n": 2000, "ops_per_sec": 17873580.17},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "16x1 avx2+fma", "min_ns": 24.0, "mean_ns": 25.0, "p50_ns": 25.0, "p95_ns": 26.0, "max_ns": 34.0, "n": 2000, "ops_per_sec": 40072932.74},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "1x1 avx2+fma", "min_ns": 18.0, "mean_ns": 19.3, "p50_ns": 19.0, "p95_ns": 20.0, "max_ns": 38.0, "n": 2000, "ops_per_sec": 51912993.82},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "7x3 avx2+fma", "min_ns": 26.0, "mean_ns": 28.2, "p50_ns": 28.0, "p95_ns": 29.0, "max_ns": 41.0, "n": 2000, "ops_per_sec": 35467910.41},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "64x64 avx2+fma", "min_ns": 222.0, "mean_ns": 239.8, "p50_ns": 226.0, "p95_ns": 246.0, "max_ns": 9504.0, "n": 2000, "ops_per_sec": 4170385.26},
{"kind": "timing", "section": "FP32 dense GEMV (htf_dense_f32), activation=RELU", "metric": "dense_f32 relu", "detail": "255x129 avx2+fma", "min_ns": 2551.0, "mean_ns": 2762.4, "p50_ns": 2702.0, "p95_ns": 2940.0, "max_ns": 26938.0, "n": 2000, "ops_per_sec": 362010.41},
{"kind": "timing", "section": "Scalar vs vector path, same shape", "metric": "dense_f32 (forced scalar)", "detail": "255x129 scalar", "min_ns": 10956.0, "mean_ns": 11115.3, "p50_ns": 11021.0, "p95_ns": 11191.0, "max_ns": 31353.0, "n": 2000, "ops_per_sec": 89965.69},
{"kind": "timing", "section": "Scalar vs vector path, same shape", "metric": "dense_f32 (dispatched)", "detail": "255x129 avx2+fma", "min_ns": 2599.0, "mean_ns": 2791.8, "p50_ns": 2712.0, "p95_ns": 3227.0, "max_ns": 16521.0, "n": 2000, "ops_per_sec": 358194.61},
{"kind": "metric", "section": "Scalar vs vector path, same shape", "metric": "vector speedup", "detail": "mean(scalar)/mean(vector)", "value": "3.98x"},
{"kind": "note", "section": "Scalar vs vector path, same shape", "text": "speedup is specific to THIS machine's AVX2+FMA core; a riscv32 target has no such path and runs the scalar code."},
{"kind": "timing", "section": "INT8 packed vs FP32, same logical layer", "metric": "int8_dense_act", "detail": "128x32 int8 relu", "min_ns": 379.0, "mean_ns": 385.8, "p50_ns": 384.0, "p95_ns": 387.0, "max_ns": 630.0, "n": 2000, "ops_per_sec": 2592060.26},
{"kind": "timing", "section": "INT8 packed vs FP32, same logical layer", "metric": "dense_f32", "detail": "128x32 fp32 relu", "min_ns": 293.0, "mean_ns": 306.7, "p50_ns": 300.0, "p95_ns": 322.0, "max_ns": 5499.0, "n": 2000, "ops_per_sec": 3260440.75},
{"kind": "metric", "section": "INT8 packed vs FP32, same logical layer", "metric": "int8 vs fp32 time", "detail": "mean(fp32)/mean(int8), this host", "value": "0.80x"},
{"kind": "metric", "section": "INT8 packed vs FP32, same logical layer", "metric": "real model weights", "detail": "128->32->16->1, per layer summed", "value": "4624 B int8 vs 18496 B fp32"},
{"kind": "metric", "section": "INT8 packed vs FP32, same logical layer", "metric": "flash saved by INT8", "detail": "same model, weights only", "value": "13872 B saved (4.0x smaller)"},
{"kind": "note", "section": "INT8 packed vs FP32, same logical layer", "text": "byte figures are properties of the representation and hold on any target; the time ratio above is specific to this host."},
{"kind": "metric", "section": "End-to-end engine on the real model (FP32 path)", "metric": "engine", "detail": "active configuration", "value": "native fp32 avx2+fma layers=3 in=128 out=1 arena=1152B"},
{"kind": "metric", "section": "End-to-end engine on the real model (FP32 path)", "metric": "model shape", "detail": "signed .htf", "value": "in=128 out=1 layers=3"},
{"kind": "metric", "section": "End-to-end engine on the real model (FP32 path)", "metric": "heap allocations during inference", "detail": "malloc+calloc+realloc", "value": "0 over 2000 runs"},
{"kind": "metric", "section": "End-to-end engine on the real model (FP32 path)", "metric": "heap bytes during inference", "detail": "sum of requested sizes", "value": "0 B requested"},
{"kind": "note", "section": "End-to-end engine on the real model (FP32 path)", "text": "zero allocations measured over 2000 inferences: the arena is sized once at load and reused. This is what makes the run path safe on a device whose heap cannot be relied on."},
{"kind": "timing", "section": "End-to-end engine on the real model (FP32 path)", "metric": "engine_run fp32", "detail": "3 layers, avx2+fma", "min_ns": 339.0, "mean_ns": 398.1, "p50_ns": 352.0, "p95_ns": 444.0, "max_ns": 18633.0, "n": 2000, "ops_per_sec": 2512023.17},
{"kind": "metric", "section": "End-to-end engine on the real model (FP32 path)", "metric": "arena allocated", "detail": "tensor scratch, sized at load", "value": "1152 B"},
{"kind": "metric", "section": "End-to-end engine on the real model (FP32 path)", "metric": "arena as share of 128 KB", "detail": "typical LiteOS-M RAM", "value": "1152 B = 1.1 KB of a 128 KB budget"},
{"kind": "note", "section": "End-to-end engine on the real model (FP32 path)", "text": "arena_bytes is a property of the model shape and the arena planner, not of this machine; it is the number to compare against a device's RAM budget."},
{"kind": "metric", "section": "End-to-end engine on the real model (INT8 path)", "metric": "quantisation params", "detail": "calibrated at pack time", "value": "in scale=0.0409732 zp=-6, out scale=0.00194258 zp=-128"},
{"kind": "metric", "section": "End-to-end engine on the real model (INT8 path)", "metric": "heap allocations during INT8 inference", "detail": "malloc+calloc+realloc", "value": "0 over 2000 runs"},
{"kind": "timing", "section": "End-to-end engine on the real model (INT8 path)", "metric": "engine_run int8", "detail": "in=128 out=1, packed int8", "min_ns": 495.0, "mean_ns": 502.5, "p50_ns": 502.0, "p95_ns": 507.0, "max_ns": 527.0, "n": 2000, "ops_per_sec": 1990039.85},
{"kind": "note", "section": "End-to-end engine on the real model (INT8 path)", "text": "INT8 reuses the same arena as the FP32 path: codes are 1 byte per element against 4 for float, so no second allocation is needed."},
{"kind": "note", "section": "Constrained-platform fit (portable numbers, not host timing)", "text": "no emulator is available in this environment (qemu absent), so NO device latency is reported here. Claiming one would be invented. Cross-compile benchmark/htf_bench_constrained for the target and run it there for real device timings; htf_bench_inference needs OpenSSL and the host engine and does not run on the device."},
{"kind": "metric", "section": "Constrained-platform fit (portable numbers, not host timing)", "metric": "arena within 128 KB budget", "detail": "hard cap enforced at load", "value": "1152 B of 131072 B budget (0.88%)"},
{"kind": "metric", "section": "Constrained-platform fit (portable numbers, not host timing)", "metric": "64 B arena cap", "detail": "expected refusal", "value": "refused at load (ok)"},
{"kind": "metric", "section": "Constrained-platform fit (portable numbers, not host timing)", "metric": "INT8 model loaded", "detail": "packed weights, dequant-free run", "value": "quantised=1, arena=1152 B"},
{"kind": "note", "section": "Constrained-platform fit (portable numbers, not host timing)", "text": "for the LiteOS-M subsystem the measured static footprint is ROM 22.3 KB (18.8 KB of it the weight table) and static RAM ~1 KB plus a 2 KB task stack; see docs/ohos_subsystem.md and the ohos_subsystem gate, which re-measures it with the product's own flags."},
{"kind": "metric", "section": "Run verdict", "metric": "BENCH RESULT", "detail": "pass", "value": "0 failure(s); all measured cases validated"}
]
}
十一、CSV 数据完整记录
11.1 constrained.csv(24 行)
| kind | section | metric | detail | value | min_ns | mean_ns | p50_ns | p95_ns | max_ns | n | ops_per_sec |
|---|---|---|---|---|---|---|---|---|---|---|---|
| metric | Constrained target: model geometry (LiteOS-M core) | model | built-in weights, no file I/O | 3 layers, in=128 out=1 | |||||||
| metric | Constrained target: model geometry (LiteOS-M core) | layer 0 | shape / operands present | 128x32 w=0x563215e0f6e0 b=yes | |||||||
| metric | Constrained target: model geometry (LiteOS-M core) | layer 1 | shape / operands present | 32x16 w=0x563215e13760 b=yes | |||||||
| metric | Constrained target: model geometry (LiteOS-M core) | layer 2 | shape / operands present | 16x1 w=0x563215e13fa0 b=yes | |||||||
| metric | Constrained target: model geometry (LiteOS-M core) | static buffer sizing | ping-pong scratch | widest=128, static buffer=128 (fits) | |||||||
| metric | Constrained target: model geometry (LiteOS-M core) | weight table | in flash (.rodata) | 4624 weights = 18496 B fp32 | |||||||
| metric | Constrained target: model geometry (LiteOS-M core) | inference scratch RAM | static BSS, allocated once at link time | 1024 B = 1.0 KB (2 x 128 floats) | |||||||
| metric | Constrained target: numerical correctness | anomaly probability | recorded input vector | got 0.471768856, numpy golden 0.471768826, diff 2.98e-08 (tol 0.001) | |||||||
| metric | Constrained target: numerical correctness | softmax-constant regression | output must not be 1.0 | not triggered | |||||||
| metric | Constrained target: heap allocation audit | audit self-test | counting path verified | passed | |||||||
| metric | Constrained target: heap allocation audit | heap allocations | whole lifecycle | 0 malloc, 0 calloc, 0 realloc over init+2000 runs+deinit | |||||||
| metric | Constrained target: heap allocation audit | heap bytes requested | whole lifecycle | 0 B | |||||||
| metric | Constrained target: heap allocation audit | heap frees | whole lifecycle | 0 | |||||||
| metric | Constrained target: heap allocation audit | VERDICT | allocation-free inference | CONFIRMED by measurement | |||||||
| note | Constrained target: heap allocation audit | zero heap allocations across init, 2000 inferences and deinit. The core uses 2 static BSS buffers instead. This is what makes the run path safe on a device whose heap cannot be relied on: there is no allocation to fail on the Nth call, and no fragmentation to accumulate over a device’s uptime. | |||||||||
| metric | Constrained target: inference timing (THIS machine) | running on | architecture | host (NOT a device figure) | |||||||
| note | Constrained target: inference timing (THIS machine) | these timings describe this host. They are not a prediction of MCU latency and must not be quoted as one. Cross-compile this file for the target and run it there for real device numbers. | |||||||||
| timing | Constrained target: inference timing (THIS machine) | full forward pass | 128->1, 3 layers | 1563.0 | 1607.5 | 1577.0 | 1647.0 | 21589.0 | 2000 | 622066.95 | |
| metric | Constrained target: inference timing (THIS machine) | per-inference latency | this machine | 1.608 us mean, 1.647 us p95 | |||||||
| metric | Run verdict | BENCH RESULT | pass | 0 failure(s); correctness verified, zero-allocation confirmed |
11.2 inference.csv(38 行)
| kind | section | metric | detail | value | min_ns | mean_ns | p50_ns | p95_ns | max_ns | n | ops_per_sec |
|---|---|---|---|---|---|---|---|---|---|---|---|
| note | FP32 dense GEMV (htf_dense_f32), activation=RELU | kernel path in this process: avx2+fma | |||||||||
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 128x32 avx2+fma | 299.0 | 320.8 | 305.0 | 404.0 | 1481.0 | 2000 | 3117051.52 | |
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 32x16 avx2+fma | 49.0 | 50.4 | 50.0 | 51.0 | 59.0 | 2000 | 19835956.64 | |
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 16x1 avx2+fma | 24.0 | 25.0 | 25.0 | 26.0 | 94.0 | 2000 | 39959241.57 | |
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 1x1 avx2+fma | 18.0 | 19.7 | 20.0 | 20.0 | 21.0 | 2000 | 50873756.77 | |
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 7x3 avx2+fma | 26.0 | 27.9 | 28.0 | 29.0 | 43.0 | 2000 | 35889245.79 | |
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 64x64 avx2+fma | 221.0 | 226.8 | 225.0 | 244.0 | 383.0 | 2000 | 4410094.71 | |
| timing | FP32 dense GEMV (htf_dense_f32), activation=RELU | dense_f32 relu | 255x129 avx2+fma | 2621.0 | 2799.3 | 2768.0 | 2917.0 | 16182.0 | 2000 | 357236.25 | |
| timing | Scalar vs vector path, same shape | dense_f32 (forced scalar) | 255x129 scalar | 10969.0 | 11215.6 | 11040.0 | 11597.0 | 69482.0 | 2000 | 89161.14 | |
| timing | Scalar vs vector path, same shape | dense_f32 (dispatched) | 255x129 avx2+fma | 2556.0 | 2759.9 | 2714.0 | 2838.0 | 22340.0 | 2000 | 362327.44 | |
| metric | Scalar vs vector path, same shape | vector speedup | mean(scalar)/mean(vector) | 4.06x | |||||||
| note | Scalar vs vector path, same shape | speedup is specific to THIS machine’s AVX2+FMA core; a riscv32 target has no such path and runs the scalar code. | |||||||||
| timing | INT8 packed vs FP32, same logical layer | int8_dense_act | 128x32 int8 relu | 383.0 | 392.0 | 388.0 | 390.0 | 8753.0 | 2000 | 2550773.14 | |
| timing | INT8 packed vs FP32, same logical layer | dense_f32 | 128x32 fp32 relu | 292.0 | 296.2 | 295.0 | 310.0 | 323.0 | 2000 | 3375892.08 | |
| metric | INT8 packed vs FP32, same logical layer | int8 vs fp32 time | mean(fp32)/mean(int8), this host | 0.76x | |||||||
| metric | INT8 packed vs FP32, same logical layer | real model weights | 128->32->16->1, per layer summed | 4624 B int8 vs 18496 B fp32 | |||||||
| metric | INT8 packed vs FP32, same logical layer | flash saved by INT8 | same model, weights only | 13872 B saved (4.0x smaller) | |||||||
| note | INT8 packed vs FP32, same logical layer | byte figures are properties of the representation and hold on any target; the time ratio above is specific to this host. | |||||||||
| metric | End-to-end engine on the real model (FP32 path) | engine | active configuration | native fp32 avx2+fma layers=3 in=128 out=1 arena=1152B | |||||||
| metric | End-to-end engine on the real model (FP32 path) | model shape | signed .htf | in=128 out=1 layers=3 | |||||||
| metric | End-to-end engine on the real model (FP32 path) | heap allocations during inference | malloc+calloc+realloc | 0 over 2000 runs | |||||||
| metric | End-to-end engine on the real model (FP32 path) | heap bytes during inference | sum of requested sizes | 0 B requested | |||||||
| note | End-to-end engine on the real model (FP32 path) | zero allocations measured over 2000 inferences: the arena is sized once at load and reused. This is what makes the run path safe on a device whose heap cannot be relied on. | |||||||||
| timing | End-to-end engine on the real model (FP32 path) | engine_run fp32 | 3 layers, avx2+fma | 337.0 | 353.8 | 342.0 | 354.0 | 10652.0 | 2000 | 2826495.57 | |
| metric | End-to-end engine on the real model (FP32 path) | arena allocated | tensor scratch, sized at load | 1152 B | |||||||
| metric | End-to-end engine on the real model (FP32 path) | arena as share of 128 KB | typical LiteOS-M RAM | 1152 B = 1.1 KB of a 128 KB budget | |||||||
| note | End-to-end engine on the real model (FP32 path) | arena_bytes is a property of the model shape and the arena planner, not of this machine; it is the number to compare against a device’s RAM budget. | |||||||||
| metric | End-to-end engine on the real model (INT8 path) | quantisation params | calibrated at pack time | in scale=0.0409732 zp=-6, out scale=0.00194258 zp=-128 | |||||||
| metric | End-to-end engine on the real model (INT8 path) | heap allocations during INT8 inference | malloc+calloc+realloc | 0 over 2000 runs | |||||||
| timing | End-to-end engine on the real model (INT8 path) | engine_run int8 | in=128 out=1, packed int8 | 498.0 | 517.6 | 511.0 | 517.0 | 13265.0 | 2000 | 1931919.17 | |
| note | End-to-end engine on the real model (INT8 path) | INT8 reuses the same arena as the FP32 path: codes are 1 byte per element against 4 for float, so no second allocation is needed. | |||||||||
| note | Constrained-platform fit (portable numbers, not host timing) | no emulator is available in this environment (qemu absent), so NO device latency is reported here. Claiming one would be invented. Cross-compile benchmark/htf_bench_constrained for the target and run it there for real device timings; htf_bench_inference needs OpenSSL and the host engine and does not run on the device. | |||||||||
| metric | Constrained-platform fit (portable numbers, not host timing) | arena within 128 KB budget | hard cap enforced at load | 1152 B of 131072 B budget (0.88%) | |||||||
| metric | Constrained-platform fit (portable numbers, not host timing) | 64 B arena cap | expected refusal | refused at load (ok) | |||||||
| metric | Constrained-platform fit (portable numbers, not host timing) | INT8 model loaded | packed weights, dequant-free run | quantised=1, arena=1152 B | |||||||
| note | Constrained-platform fit (portable numbers, not host timing) | for the LiteOS-M subsystem the measured static footprint is ROM 22.3 KB (18.8 KB of it the weight table) and static RAM ~1 KB plus a 2 KB task stack; see docs/ohos_subsystem.md and the ohos_subsystem gate, which re-measures it with the product’s own flags. | |||||||||
| metric | Run verdict | BENCH RESULT | pass | 0 failure(s); all measured cases validated |
十二、HTML 报告说明
12.1 交互式图表报告(benchmark_report_interactive_charts_plotly.html)
- 基于 Plotly.js v2.24.1 渲染交互式图表
- 支持悬停查看数值、缩放、平移等交互操作
- 文件大小约 16 MB(包含内联的 Plotly.js 库)
- 报告包含以下章节:
- Provenance(来源信息)
- Portable facts(可移植事实)
- Timing(时序数据)
- Comparisons and derived figures(比较与派生数据)
12.2 轻量版 SVG 报告(benchmark_report_lite_version_svg.html)
- 纯 SVG 图表,零外部依赖
- 文件大小约 35 KB,便于分享
- 包含与交互式报告相同的数据内容
- 图表以 SVG 形式内联,无需 JavaScript 渲染
两份 HTML 报告均包含以下诚实性标识:
- portable(绿色徽标):表示该数据为可移植数据,在任何目标平台上成立
- host-only(灰色徽标):表示该数据仅描述测试主机,不代表目标设备性能
- device-measured(深绿色徽标):表示该数据来自目标设备实测(本次为 0 处)
- 点赞
- 收藏
- 关注作者
评论(0)