Repository navigation
Release v1.9.0: 可续训模型与引擎所有权 v2
neuralnet.cpp v1.9.0 Release Notes
发布范围:
v1.8.2(3966247,2026-10-14)→v1.9.0(2026-10-18)。
主题:可续训模型文件 v8 + 引擎所有权 v2(含GpuBackend泄漏修复) + 推理采样与多轮对话 + Vulkan 后端结构重整 / 扫描原语提速 + GUI、CLI 与 Windows 工程化收口。
兼容性:有破坏性变更——模型文件写版本升到 v8;CpuEngine/GpuEngine改为工厂创建;Tensor::engine()/Tensor::engine_alive()删除;.nnckpt续训会拒绝显式训练配方旗标。迁移见 §6。
0. 亮点速览(TL;DR)
| 方向 | 变化 | 收益 |
|---|---|---|
| 断点续训(v8) | .nnckpt 新增训练状态段:计数器、完整优化器状态(含 Adam t)、精度四槽、lr 曲线形状、batch/accum/max-norm/stride、数据集内容身份 |
重启后不是“只载权重再冷启动”,而是真正接着训练;TDR / 进程重启的 loss 与动量轨迹可连续 |
| 文件完整性 | 文件尾新增 [sha256 32B]["NNEND"],load_model / load_train_state 在解析参数前校验 |
截断或损坏的模型不再静默载入错误权重 |
| 角色语义 | .nnckpt 的状态段是唯一权威;.nnmodel 走 warm start,支持 dense 与三值模型 |
--resume .nnmodel 成为 SFT / QAT 入口;.nnckpt 不会再把 f16 断点静默恢复成 f32 |
| 引擎所有权 v2 | Tensor 只保存 8 B EngineId;计算对象持 shared_ptr<ComputeEngine>;GpuBuffer 持 backend keepalive |
消除 GpuBackend 故意泄漏、check-then-use 与 buffer 晚于 backend 析构的 UAF;RV-05 正式关闭 |
| 推理可用性 | top-k / top-p / 重复惩罚 / 随机种子;CLI / GUI 多轮对话;history 按整轮截断到 seq_len |
生成结果可复现,终端、脚本和 GUI 都能保持多轮上下文 |
| GPU 性能 | RAPT 逐头 1/rms 收集改设备侧拼装;scan_prefix 行归属 + 4 路展开 + dk ≤ 32 窄共享变体 |
RAPT 3-step 配对实测 888.24 → 510.24 ms(−42.6%);scan_prefix f32/dk32 单次约 5.9× |
| 工程化 | text_train 1800+ 行拆为 config/loop/recovery/checkpoint;GUI 参数声明式表;Windows clang++/MSVC 栈与测试路径修复 |
训练入口可测试、GUI 参数不漂移;Windows ctest 与四编译器矩阵全绿 |
1. 可续训模型文件(v8)
1.1 训练状态段
.nnckpt 在模型参数与词表之后追加 role 门控状态段:
[state_len u64][TrainState kvrec][优化器状态张量条目 × N]
[sha256 32B]["NNEND" 5B]
状态段保存四类信息:
- 计数器:
global_step、epoch、epoch 内步号、当前 lr; - 优化器状态:Adam m/v 与
t、SGD momentum、Muon、AdamWQuant 的 f16 状态与逐行尺度; - 训练配方:精度 profile 四槽、
weight_quant、lr 调度配方与曲线形状、batch / accum / max-norm / stride; - 数据集身份:
.nndataset的 sha256、tokens/docs/windows、steps_per_epoch、loss scope。
--resume 读取 .nnckpt 时,状态段是唯一权威。除保存路径、保存间隔、日志间隔、--gpu、激活卸载、--max-steps 等不改变训练配方的选项外,再显式给出形状 / 优化器 / 精度 / 计数器旗标会直接报错;需要限制本次追加跑多少步时使用 --max-steps。
数据集 path 是“位置”,内容身份才是“身份”:路径可由参数覆盖,但 sha256 或网格身份不一致会硬报错,不提供绕过开关。换数据集属于新任务,应从 .nnmodel warm start。
1.2 文件角色与三值续训
.nnmodel是推理角色,参数区仍按参数级kind保存;三值层在盘上使用 base-3 打包形态。load_model(..., WeightLoadIntent::Inference)保持只读部署语义;WeightLoadIntent::Trainable会在宿主侧按文件τ反量化成 f32 latent,供三值 SFT / QAT 继续训练。- 反量化使用量化器的不动点:逐行 latent 的 absmean 仍等于文件
τ,用同一τ重新量化可复现同一组 trit,避免 warm start 第一步就发生尺度跳变。 mnist_train的 role 语义仍只接受.nnckpt;text_train同时接受.nnckpt续训与.nnmodelwarm start。
1.3 兼容性
v1.9.0 可读 v5 / v6 / v7 / v8,写出一律为 v8。v1.8.x 及更旧程序无法读取 v8 新文件;需要旧程序读取时保留原文件或继续使用旧版本二进制。
2. 引擎所有权 v2 与 GPU 生命周期
2.1 新模型
Tensor不再持有引擎指针,只保存 8 B、进程内单调递增且不复用的EngineId;Tensor 拷贝与析构不增加原子计数。Layer/Model/Optimizer/ExprGraph/ExprBlock持std::shared_ptr<ComputeEngine>;只要借用方存活,引擎就存活。CpuEngine/GpuEngine构造函数私有,统一经create()返回shared_ptr;栈上构造成为编译错误。GpuBackend提供shared()(进程共享)与create()(独立实例);GpuBuffer持 backend keepalive,GPU Tensor 可安全晚于GpuEngine析构。reduce_partial_改为 backend 自有的裸VkBuffer,切断backend → GpuTensor → GpuBuffer → backend引用环,同时修正 backend 析构中 scratch 晚于 memory pool 释放的顺序隐患。- GPU f16 “先算 f32、再 cast 回 f16”回退路径的 7 个出口(gather / rearrange / transpose / im2col / col2im / matmul / batched matmul)统一在进入公共
cast前经adopt补EngineId;NN_BIND_DEBUG=1下不再把库内中转误报为未绑定输入。
2.2 RV-05 关闭
原先 GpuBackend::instance() 用进程级 new 故意泄漏 Vulkan device / instance / pipeline / memory pool。v2 改为引用计数所有权后:
- 最后一个强引用释放时执行
vkDeviceWaitIdle、释放延迟 buffer 与池资源、最后析构VulkanDevice; GpuBuffer不再在析构时回查可能已死亡的全局单例;layer_lifetime_test覆盖 Tensor 晚于引擎析构、Layer / Model / Optimizer 保活、跨引擎拒绝,以及NN_GPU_LIFETIME_FULL=1下的完整 GPU 释放路径。
GpuBackend::instance() 保留为兼容入口,但新代码应使用 shared();生命周期契约见 ../development/08-object-lifetimes.md 与 ../development/09-engine-ownership.md。
3. 推理采样与多轮对话
- 新增
SamplingConfig:temperature/top_k/top_p/repeat_penalty/repeat_window/seed。 - 采样顺序固定为:重复惩罚 → 温度 → top-k → top-p → 数值稳定 softmax → 采样;默认配置与旧 temperature 路径逐位等价。
text_infer新增--top-k、--top-p、--repeat-penalty、--repeat-window、--seed。- 多轮 prompt 的唯一实现是
Tokenizer::chat_prompt:使用训练词表的对话标记,超seq_len时按“一问一答”整轮丢弃,system 块与末轮保留。 - CLI 新增
--turn <role>:<text>、--reply-out <path>、--system <text>;--interactive改为在进程内累积历史。无状态--turn协议供脚本与 GUI 每轮回传历史。 - GUI「GPT 推理」页新增对话记录、发送多轮、清空对话与系统提示;回复经
--reply-out临时文件取回,不再解析日志。
4. GPU 后端与性能
4.1 Vulkan 后端结构
- GPU 存储从代数层下沉到
backend/compute_gpu_storage.hpp,切断 L1 对 Vulkan 后端的反向依赖。 - 原语 pipeline 收敛为单一目录与布局表;融合 kernel 的 9 张并行元数据表合并为
FusedKernel。 - descriptor set 的错误分支统一经
release_desc_set_now(),batch 模式不再双重释放。 - backend 拆分后的结构、所有权和扩展步骤见
../development/07-vulkan-backend-map.md。
4.2 RAPT 与扫描原语
- RAPT 的逐头
1/rms缓存改为设备侧cast_into + insert_rows,消除每头一次 host 往返;3-step 配对实测 741.81 → 521.68 ms(−29.7%),to_matrix由 768 次降为 0。 scan_prefix_outer专用路径改为行归属、R 行块 shared 缓存、4 路展开,并新增dk ≤ 32窄共享变体;累加次序未变。scan_prefix配对 A/B:dk=32 f32 10.87–10.94 → 1.81–1.87 ms(约 5.9×)、dk=32 f16 11.86 → 3.07 ms、dk=64 f32 22.33–22.41 → 7.44–7.50 ms。- 两项合流的 RAPT 配对实测 888.24 → 510.24 ms(−42.6%)。
layer_bench新增scan_prefix/scan_suffix、--dk与位级哈希--hash;scan_bitwise_test用 CPU 参考逐位验证 GPU 扫描 kernel。
5. DSL、训练入口、GUI 与工程化
- Fold / DSL:fold 结构由定义方自登记;新增
dsl::compute/compute_reduce无形状重载与形状推断,门禁dsl_shape_infer_test。 - 训练入口:
src/text_train.cpp由约 1830 行缩到约 396 行;解析、循环、恢复、断点分别落到cli/text_train_{config,loop,recovery,checkpoint}.hpp,非解析模块不再调用std::exit。断点保存失败现在会让进程以非零码退出。 - GUI:六个训练 Tab 的参数改为声明式
Field表,补上字段表 ↔ Controller 闭包审计;画板一笔一画布项、空帧跳过与延迟光栅化,使 2000 个 motion 事件的合成审计由 37.9 ms 降到 16.6 ms,画布项由 2058 降到 59。 - CI / Windows:新增 MSVC Debug CPU-only job,并对齐 GitHub
windows-latest的 VS 2026 镜像(Visual Studio 18 2026生成器);Windows clang++ 补-Wl,/STACK:8388608,MSVC 补/bigobj;CLI 子进程测试使用绝对路径与 cmd 安全引号;scan_bitwise_test在 Windows 与非优化 Debug 构建按 77 skip,逐位权威锚保留在 Linux Release + GTX 850M。
6. 破坏性变更与迁移
| 变更 | 影响 | 迁移 |
|---|---|---|
| 模型文件写 v8 | v1.9.0 可读 v5–v8;v1.8.x 不能读 v8 | 部署侧一起升级;需要旧程序读取时保留 v7 原文件 |
.nnckpt 状态段为唯一权威 |
续训时显式传 --f16 / --t1_58 / 形状 / 优化器 / --epochs 等会报错 |
从断点加载配方;本次追加时长用 --max-steps |
| 数据集身份硬校验 | 换数据集后不能续接旧 .nnckpt |
新任务用 .nnmodel warm start |
| 引擎必须经工厂创建 | nn::CpuEngine engine;、nn::GpuEngine engine; 编译失败 |
auto cpu = nn::CpuEngine::create();;GPU 用 GpuBackend::shared() + GpuEngine::create(backend) |
Tensor::engine() / engine_alive() 删除 |
从 Tensor 反取引擎的代码编译失败 | 显式传入引擎;跨引擎诊断用 Tensor::engine_id() |
三值 .nnmodel 可续训 |
默认推理载入仍是只读;训练载入会反量化成 f32 latent | 训练侧传 WeightLoadIntent::Trainable;要逐位复现 warm start 时继续给原精度 profile |
7. 验收基线
| 项 | 结果 |
|---|---|
| Linux Release + Vulkan(GTX 850M) | ctest 55/55 通过,零告警;含 scan_bitwise_test / rapt_test_f16 |
Linux Release + Vulkan / NN_BIND_DEBUG=1 |
55/55 通过(CPU-only 同口径 53/53) |
| Linux Debug + Vulkan | 54/54 通过(scan_bitwise_test 按非优化构建 77 skip;rapt_test_f16 按既有 C11 口径排除) |
| Windows 102 / clang 23.1 + MSVC STL / Release / 40HX | 55/55 通过;NN_BIND_DEBUG=1 同为 55/55(1 条 Windows 扫描逐位口径 skip) |
| Windows 102 / cl.exe 19.51 + MSVC STL / Debug CPU-only | 52/52 通过(GPU 用例 skip;C11 排除) |
| WSL 102 / clang 24 + libc++ / CPU-only | 53/53 通过(GPU 用例 77 skip) |
| WSL 102 / g++ 15.2 + libstdc++ / CPU-only | 53/53 通过(GPU 用例 77 skip) |
| 文档门禁 | doc_align_audit.ps1、doc_inventory.ps1、gui_cli_audit.py 均可行动项 0、exit 0 |
端到端训练:datasets/tinystories_200mb.nndataset,GPT d_model=64 / heads=4 / layers=2 / d_ff=128 / seq_len=128 / batch=8,850M GPU,20 step:
| 配方 | loss 起 → 止 | 结果 |
|---|---|---|
| f32 | 8.7601 → 7.6061 | 健康下降,无 NaN |
--f16 |
8.7734 → 7.5770 | 健康下降,无 NaN |
--t1_58 |
8.8839 → 8.0488 | 健康下降,无 NaN |
复现命令与更多口径见 ../todo/25-release-requirements.md §4。
8. 提交索引
| 主题 | 提交 |
|---|---|
| Fold 自登记 + DSL 无形状重载 + 形状推断 | c7e1d32 |
| 推理采样与 CLI 参数 | ebed110 |
| RAPT host 往返消除 / RV-15 | d2b7984 / 23adc49 |
scan_prefix 优化 + benchmark + 逐位门禁 |
a28aef7 / ac58a1e / 8867d78 |
| GUI 绘制节流 + 声明式字段表 | 382ef9d / 0550ec8 |
text_train 退出码与模块化 |
2a86090 / cb98b66 |
| v8 状态段 + 优化器状态 + 训练接线 + GUI 收口 | bca2bdc / 11d521f / d230675 / 1fb455e / e59a078 |
| 三值模型续训(Trainable 反量化) | 6f9ca5d |
| 多轮对话闭环 | 09de432 |
| Vulkan 后端结构重整 | fc10b93 / 59de9e7 / 789f2ce / aff6484 / 96198d1 |
引擎所有权 v2 + GpuBackend 泄漏修复 |
9d86156 / 02ed686 / 724d441 |
| Windows 可移植性与发布前复验 | 6011b4e / f42a185 / f9e842d |
| Release v1.9.0 | 见本仓 git log |
完整变更:git log v1.8.2..v1.9.0 / git diff v1.8.2..v1.9.0
9. 仍 Open
- RV-12:Loss 直接构造 IR spec 与
FoldAnchor的时间耦合,仍按“先设计、后动手”跟踪。 - 实验 / 后续性能项:真 f16 长跑矩阵、RAPT GQA in-kernel、FFN
ReLU²官方对拍与 HF 权重转换器。
完整台账见 ../todo/18-roadmap.md 与 ../todo/00-code-review-remediation.md。