project —— 成熟模型两种部署方式的实质四维对比(多对象检测大模型)
本项目是 nrfunc 库能力的端到端实跑 demo:用真实 MNIST 训练一个「单图多对象检测」大模型(13.77M 参数),然后走两条部署路线做实质性的四维(体积/内存/CPU/精度)对比。
训练(Python)── 模型 ── ... ── 成熟模型(13.77M)
│
┌─────────────────────────┴─────────────────────────┐
│ │
路线 A(传统) 路线 B(函数化)
成熟模型直接部署 函数化 → 生成物 → 部署
│ │
model.bin(全稠密) functional.bin(fc2 区域函数)
│ │
└────────────── Rust 独立二进制对比 ──────────────┘
│
comparison.log(四维对比)
任务:MNIST 单图多对象检测(真实数据)
- 数据:官方 MNIST,每张 28×28 画布随机放 1~2 个数字(各缩放到 8px),各落到 2×2 网格的不同格。
- 输出:YOLO 式网格头,每格预测
[objectness(1) + 类别 one-hot(10) + bbox 偏移(4)],共2×2×15 = 60维。 - 模型:共享骨架
784 → 4096 → 2048 → 1024,网格头 → 60。13.77M 参数,适配本机(RTX 5070 12G)可训练的大模型。
两条路线
| 路线 | 部署方式 | 产物 | 说明 |
|---|---|---|---|
| A 传统 | 成熟模型直接部署 | model.bin |
全部层稠密权重(int8 量化) |
| B 函数化 | 函数化 → 生成物 → 部署 | functional.bin |
fc2 层用区域函数(int8 量化),其余层稠密 |
两侧都极限压制:原型与优化物都量化到 int8(而非刻意让优化物迁就原型保持 f32), 在各自极限下对比谁更优。量化粒度实测 per-array 单 scale 优于 per-row(掉点 -0.08pt vs +0.03pt,见 train.py 量化评估)。
函数化对象是 fc2(4096→2048 宽层)——这是本项目最有价值的发现:
| 层 | 函数化掉点 | 结论 |
|---|---|---|
| fc1(首层,784→4096) | 30~80pt(灾难) | 首层是特征提取层,函数化必崩,auto_alloc 正确判 skip |
| fc2(中间层,4096→2048) | ≈0pt | 宽层冗余足,函数化几乎无损,是正收益层 |
复现
# 1. 训练 + 函数化 + 导出产物(Python)
cd z:/Projects/AIG/nrfunc/examples/project
& z:\Projects\AIG\.venv\Scripts\python.exe -X utf8 train.py
# 2. Rust 部署对比(读产物,出比较日志)
cd deploy
cargo build --release
.\target\release\nrfunc-deploy.exe ..\artifacts
产物
| 文件 | 内容 |
|---|---|
artifacts/model.bin |
成熟模型完整权重(路线 A,二进制) |
artifacts/functional.bin |
函数化生成物(路线 B:fc2 区域函数 + 其余稠密,二进制 NRFN 格式) |
artifacts/stats.json |
Python 侧训练/函数化统计(配置,非生成物) |
artifacts/comparison.log |
Rust 独立二进制产出的四维对比日志 |
生成物采用二进制(禁用 JSON):
functional.bin的 fc2 函数参数走nrfunc.io.to_bytes(NRFN 自描述字节,int8 量化);model.bin全距密权重 int8 量化(per-array 单 scale)。stats.json是配置/统计,非生成物,保留 JSON。
本轮实测结论(大模型,都 int8 极限压制,见 comparison.log)
| 维度 | 路线 A(int8 稠密) | 路线 B(int8 函数化) | 对比 |
|---|---|---|---|
| fc2 层内存 | 67,125,248 B | 6,325,256 B | 省 90.6% |
| 全模型内存 | 110,125,536 B | 49,325,544 B | 省 55.2% |
| 体积(二进制) | 13.77 MB | 6.17 MB | 省 55.1% |
| 精度(Python 真实掉点) | 74.76% cls acc | 74.76% | 掉 -0.005pt(无损) |
| CPU 单样本 | 6,962 µs | 3,219 µs | 快 2.16x |
| CPU 批量(64) | 385,295 µs | 136,865 µs | 快 2.82x |
| GPU 单样本 | 557 µs | 387 µs | 快 1.44x |
| GPU 批量(64) | 6,146 µs | 4,342 µs | 快 1.42x |
- 两路线输出 100% objectness 一致、99.22% 分类 argmax 一致;函数化 K=1 r=128
(
auto_allocsignal='auto' 探测出的全局低秩形态)几乎无损,掉点 -0.005pt - 两侧都 int8 后:体积省 55.1%(13.77MB→6.17MB),CPU 快 2.16~2.82x,GPU 快 1.42~1.44x; K=1 全局低秩(K·r=128≪n=2048)共享计算收益在 CPU/GPU 都兑现(GPU 数值自检 1.2e-13)
关键洞察
- 模型够大,函数化收益才足:小 MLP(128×257 层)函数化全模型只省 11.6%,换成大模型 fc2(2048×4097 宽层)后省 55.2%,CPU 从"不省"翻成"快 2.2~2.8 倍"。
- 函数化层选择:首层(特征提取)函数化必崩,中间宽层(冗余足)才是正收益对象,
auto_alloc的逐层自适应 +eval_fn真实掉点 <3pt 红线是可靠保障。 - 编译语言才兑现 CPU 提速:numpy 参考实现测不出函数前向提速(Python 循环 + gather 反而慢),Rust 下才干净兑现 2x+。
- GPU 端也能兑现:配 cuBLAS(共享 GEMM)+ 自定义 gather kernel,函数化 fc2 这层省算力(K·r=128≪n=2048)超过 H2D/D2H 边角传输,整网 GPU 前向快 1.42~1.44x。
- 两侧极限压制(int8)后优化物仍全面占优:体积省 55.1%、CPU 快 2.8x、GPU 快 1.44x,量化粒度 per-array 单 scale 掉点 -0.08pt(比 per-row 更好)。
设计要点
- 训练/函数化用 Python(
train.py,auto_alloc signal='auto' 自适应 + 真实掉点 <3pt 红线) - 生成物用二进制(禁用 JSON):
model.bin(稠密 int8)+functional.bin(fc2 走to_bytesNRFN 格式 int8) - 部署用 Rust 独立二进制(
deploy/src/main.rs,零依赖 nrfunc_rust 库 + cudarc/cuBLAS,自带 register-blocking 稠密 GEMM + 8 路展开点积 + GPU 前向,手写二进制解析器读 .bin 含 int8 反量化) - Rust 自带确定性随机生成同一批测试输入,两路线输出直接对比
二进制格式(小端,跨语言自描述)
model.bin :n_layers(4B) + 每层 [n_out(4B) n_in(4B) bits(1B) + 权重]
bits=32 直存 f32;bits=8/4 为 打包 int8/int4 字节 + scale(1×f32 在段尾)
(层序 fc1/fc2/fc3/head,per-array 对称量化)
functional.bin :fc1/fc3/head 三层的 [同上 int8] + func_blob_len(8B) + NRFN 块
NRFN 块(bits=8):magic'NRFN'(4B) version=2(1B) order(1B) bits(1B) K(4B) n(4B) D(4B) r(4B)
+ signal_index(1B) op_version(1B) ← v2 头共 25B
+ means 打包 + scale(段尾) [+ components 打包 + scale + coeffs 打包 + scale if order=1]
+ assign(n×i32)