Repository navigation
Releases: EthanPeng-2048/neuralnet.cpp
Release list
neuralnet.cpp v1.12.0
neuralnet.cpp v1.12.0 Release Notes
发布范围:
v1.11.0(tagv1.11.0,2026-10-23)→v1.12.0(tagv1.12.0)。
主题:RL 与可组合训练基础设施——具名多输出模型、schema 化 experience、
GAE/return/group normalization、REINFORCE/PPO/GRPO objectives、多模型角色容器与
可替换 rollout pipeline。
兼容性:无破坏性 API 或 CLI 变更。模型/断点文件格式仍为 v8;新增的
output_schema/output_packing是ModelSpec可选键,旧文件缺键时保持单输出语义。
授权条款变更:项目许可证从 MIT 切换为 LGPL-3.0,升级、链接或分发前请阅读 §4。
1. 主题与定位
v1.12.0 把此前散落在调用方的 policy-gradient 公共语义收进库层,同时保留算法和
环境边界:
- 库提供 schema、experience、objective、session 与 pipeline;
- 环境
reset/step、MCTS、棋规、自对局 worker、replay policy 仍由消费者实现; - 首版分布为 categorical,其他分布经
PolicyDistribution扩展; - 不新增引擎原语或 shader;RL 数值路径全部由现有 Tensor/Engine/DSL 组合。
设计背景与能力下界见 ../todo/31-rl-training-primitives.md,
调用指南见 ../usage/06-reinforcement-learning.md。
2. 新增能力
2.1 有序 schema 与具名多输出
TensorSchema/FieldHandle/NamedTensorSet/GradientBundle提供稳定字段顺序、
shape / precision / placement 校验和 setup 期名称解析。Layer::output_schema()、forward_named()、backward_named()为单输出层提供默认适配;
OutputBundleLayer将同一输入 fan-out 到具名分支并汇总输入梯度。Model::forward_named()/backward_named()让共享 trunk 的中间层继续使用单 Tensor,
最后一层可展开为多端口。RowBandPacking只作为旧单 Tensor ABI 的显式兼容适配器;named API 不依赖行号约定。ModelSpec可持久化可选output_schema/output_packing,缺键保持旧单输出行为。
2.2 Experience、GAE 与归一化
TensorRecord/ExperienceSchema/PolicyBatchView:标准字段可选项可按算法收紧,
自定义字段仍可由 consumer 通过 typed record 读取。GaeAdvantage:支持 done 截断、mask、terminal bootstrap;mask 外时间步不参与状态值
或 advantage 的跨步传递。MonteCarloReturn、MaskedNormalizer、GroupAdvantage:支持总体标准差、零方差与
GRPO 连续 group span。
2.3 Policy 与 objective 组件
PolicyDistribution接口与首版CategoricalDistribution:log-prob、entropy、sample、
analytic gradient。- 低层组件:
ReinforceObjective、ValueRegressionObjective、
EntropyBonusObjective、SampledKlObjective、ClippedPolicyObjective、
ClippedValueObjective。 - 标准 preset:
PpoObjective与GrpoObjective;preset 的组合损失/梯度与手工组件组合
等价。
2.4 多模型与 rollout 编排
TrainingGroupSession:具名 trainable/readonly role,共享同一ComputeEngine,
保持每个TrainingSession的 optimizer-before-model 生命周期。RLTrainingPipeline:固定rollout → buffer → sample → update顺序,batch 索引、
hooks、metrics、RNG/update/rollout 计数和 checkpoint provider 可替换。TrainingSession::train_named_step:与train_step共用 global/micro batch 累积、
归一化、裁剪和 optimizer update 语义,仅把最后反向替换为backward_named。
3. 发布审查修正
独立 Release 审查在合并前修复了以下路径,并加入回归:
- PPO off-policy 梯度:负 advantage 的裁剪两侧及容差带内部曾使用错误分支;现在
统一使用ratio * advantage的未被裁剪侧梯度,并覆盖负 advantage 的有限差分。 - GAE mask 语义:mask 外时间步的 value 不再泄漏到前一步 bootstrap;新增非零 value
的 masked-value 回归。 - Policy batch 形状校验:REINFORCE/entropy/KL/PPO 在进入引擎前置检查
(1,T),
阻止宽度不符时继续读写。 - 具名反向状态:失败的 forward 会立即失效旧的 backward 配对状态。
- 不可信 schema 解码:字段/名称计数在大分配前按上限和负载长度校验;字段名校验
改为线性重复检测,schema 指纹加入字符串长度边界。 - Pipeline 状态提交:模型 update 成功后,即使 post-update hook 失败,也会提交
update 计数与 on-policy buffer 清理,避免重试重复更新。
4. 兼容性与迁移
| 项目 | v1.11.0 → v1.12.0 |
|---|---|
| 已有 Model/Layer/Loss/Optimizer API | 不变;新增具名入口均为默认适配 |
已有单输出 Model::forward/backward |
不变 |
| 多输出旧 ABI | 使用 RowBandPacking;named API 直接用 forward_named/backward_named |
| 模型/断点格式 | v8 不变 |
ModelSpec |
新增可选 output_schema / output_packing;旧文件缺键 = 单输出 |
| CLI / GUI | 无新增或破坏性参数 |
nn.hpp |
聚合新增 RL 头;需要最短依赖时可改 include rl.hpp |
| 项目许可证 | MIT → LGPL-3.0;修改本库并分发时须继续使用 LGPL-3.0,应用链接本库的边界按 LGPL §4 处理 |
迁移旧 policy/value 双头模型时,将原有拼接输出拆为具名分支,并用
TensorSchema::create 固定端口顺序、shape 和 precision;只有仍消费单 Tensor 的调用方
才需要 RowBandPacking。
5. 验收基线
口径:
NN_ENABLE_NATIVE=OFF可移植基线;GPU = NVIDIA CMP 40HX / Vulkan SDK
1.4.350 / 驱动 616.92。全部构建目录均为全新构建。逐项记录见
../todo/25-release-requirements.md§9。
| # | 配置 | 构建 | 完整测试 |
|---|---|---|---|
| 1 | Windows clang 23.1 + MSVC STL / Release / Vulkan | 239/239 步 | 69/69:68 pass + 1 skip + 0 fail(604.58 s;skip=scan_bitwise_test 平台口径) |
| 2 | Windows clang 23.1 + MSVC STL / Debug / Vulkan | 239/239 步 | 69/69:68 pass + 1 skip + 0 fail(883.82 s;skip=scan_bitwise_test;rapt_test_f16 实际执行并 Passed) |
| 3 | Windows cl.exe 19.51 + MSVC STL / Debug / CPU-only | 全目标成功链接 | 67/67:46 pass + 21 skip + 0 fail(194.66 s) |
| 4 | WSL g++ 15.2 + libstdc++ / Release / CPU-only | 132/132 步 | 67/67:48 pass + 19 skip + 0 fail(41.05 s) |
| 5 | WSL clang 24.0 + libc++ / Release / CPU-only | 132/132 步 | 67/67:48 pass + 19 skip + 0 fail(37.09 s);ldd 确认 libc++ |
CPU-only 的 19 个 skip 均为 GPU/offload 依赖用例;MSVC 额外 2 个 skip 为已知的多配置
app 发现路径缺口。rapt_test_f16 在 Debug GPU 与 MSVC Debug 下均为实际执行并通过。
5.1 真实端到端训练
数据集 = datasets/tinystories_20k.nndataset(20,000 docs / 4,244,522 tokens /
66,321 个 seq64 窗口);GPT d_model=64 / heads=4 / layers=2 / d_ff=128 / seq_len=64,--batch-size 8 --accum-steps 2,20 update step,40HX:
| 配方 | loss 轨迹(step 5/10/15/20) | 结果 |
|---|---|---|
| f32 | 8.7752 → 8.3832 → 8.0131 → 7.7011 | 1.8 s,无 NaN/冻结 |
--f16 |
8.7207 → 8.3779 → 7.9881 → 7.6188 | 1.5 s,无 NaN/冻结 |
--t1_58 |
8.8803 → 8.6901 → 8.4593 → 8.1366 | 4.5 s,无 NaN/冻结 |
f32 模型 warm start 1 step:loss 7.6060;保存文件 SHA-256 由
A060F06A…02D2 变为 0211B037…27D06,证明参数确实移动。
5.2 门禁与 AOT
bench/doc_inventory.ps1:52/52 engine virtual,L2-VIOLATIONS: 0,
HANDWRITTEN-RESIDUE: 0,TERMINATING-CALLS: 0。bench/gui_cli_audit.py:七节 PASS,可行动问题 0(使用最终 Release 二进制)。bench/doc_align_audit.ps1/bench/audit_dangling.ps1:可行动问题 0。scan_exprs:库内 158 条融合表达式;RL PPO 梯度选择、value clip、GAE 与 reduction
路径均进入 AOT 闭合世界。
6. 提交索引
主要能力提交从 fb21e21(架构冻结)到 2eb21a2(named PPO → TrainingSession);
独立发布审查修复提交为 1cb1c2b(基于 d822089)。正式发布另含一个
release: v1.12.0 提交,仅同步版本号、文档计数、发布说明和验收记录。审查期间远端
main 已合并许可证变更 PR #22(11987ef / merge 736c2a1),本版本通过合并保留
该 MIT → LGPL-3.0 变更;代码与构建测试结果不受影响。
7. 已知限制
OutputBundleLayer当前是单输入 fan-out;任意多阶段 DAG 不在 v1.12 范围。ModelSpec保存 schema/packing 元数据,但不序列化任意自定义分支拓扑;具名模型仍由
应用代码或领域工厂重建。RLTrainingPipeline自身 checkpoint 只固定 RNG、update/rollout 计数;experience
buffer 与 replay policy 由CheckpointProvider的自定义 section 管理。- policy distribution 首版仅 categorical;连续动作与 multi-discrete 需实现
PolicyDistribution。 - 环境、MCTS、自对局、棋规与规则引擎不内置于库。
Release v1.11.0: TrainingSession修改
neuralnet.cpp v1.11.0
主题:TrainingSession 单步协议(train_step)与 global/micro batch 语义(docs/todo/30)
范围:v1.10.0 → v1.11.0 · 模型文件格式 v8 不变
⚠️ 破坏性变更
API 层
| 旧 | 新 |
|---|---|
session.train_state() / set_train_state() 删除 |
运行态移至 TextTrainer::train_state();Session 侧只读 session.state()(TrainingSessionState) |
| 手写 update 管线(zero_grad → fwd/loss/bwd → clip → step) | session.train_step(micro_batch, TrainingStepOptions, step_fn);失败后用 reset_accumulation() 丢弃半截 update |
TextTrainer::steps_per_epoch(batch_size) |
无参 steps_per_epoch()(返回 update 数);新增 micro_batch_size() |
TextSampleSet::steps_per_epoch(batch_size) |
参数语义改为 global batch,返回 update 数 |
新增:TrainingStepOutput / TrainingStepOptions / TrainingStepReport、TrainingConfig.grad_accum_steps(默认 1)、TextSampleSet::valid_target_count(indices)。
Batch 语义(同名参数、行为变化,升级必须核对脚本)
--batch-size:micro-batch → 理论 global batch(micro =batch_size / accum_steps,须整除,否则TextTrainer::createfail-fast)- step 计数统一为 optimizer update:
steps_per_epoch、global_step、lr 调度、--log-interval/--save-interval/--max-steps全部按 update 计 - 等效换算:旧
--batch-size 8 --accum-steps 4(有效 32)→ 新--batch-size 32 --accum-steps 4(micro 8)
✨ 新特性
TrainingSession::train_step()单步协议:接收已装配 micro-batch,经 Step 回调完成 forward/loss/backward;内部维护梯度累积,在 update 边界执行归一化、裁剪、optimizer.step()、zero_grad。不拥有数据集/epoch/lr 曲线/评估/断点策略(仍归TextTrainer)- 低层原语
zero_grad()/clip_grads()/step()保留(mnist_train仍用);新增reset_accumulation()与只读state()
🐛 修复
- v1.10.0 在
--accum-steps > 1时对每个 micro 梯度乘1/accum_steps("micro 均值之均值"),当 loss_mask 使各 micro 有效 token 数不同或末批不满时偏离理论目标函数。本版按gradient_units / normalization_units精确归一化(分母 = 全 global batch 有效 token 数),与"逐样本求和后一次归一"严格等价;默认accum=1路径不受影响 normalization_units == 0明确报错,不再执行无意义更新
🔄 兼容性
- 格式仍为 v8;训练状态段新增
batch_size_semantics/micro_batch_size键 - v1.10.0
.nnckpt可继续--resume:缺语义键按 legacy micro 语义解释,global_batch_size = legacy_batch_size × accum_steps,micro step 折算为 update step - 反向不成立:v1.10.0 二进制读到新断点会按 micro 语义解释
batch_size,回退旧二进制前请保留原文件
✅ 验收
| 项目 | 结果 |
|---|---|
| Windows clang 23.1 / Vulkan (40HX) Release | 构建 219/219(0 warning);59 测试 → 58 pass + 1 skip(540 s) |
| WSL g++ 15.2 / CPU-only Release | 构建 112/112;57 → 39 pass + 18 skip(GPU 依赖,预期) |
| 本机 clang 24.0 / libc++ / GPU | 219/219;57 pass + 2 fail(llvmpipe 软件光栅 f16 差异,40HX 真卡全绿) |
| 门禁审计 | gui_cli_audit.py / doc_inventory.ps1 / doc_align_audit.ps1 全 PASS,可行动 0 |
| GPU 端到端(tinystories_20k,GPT d64,global batch 8 = micro 4 × accum 2,20 step) | f32 8.7576→7.6022;--f16 8.7676→7.7230;--t1_58 8.9011→8.1010;无 NaN/冻结 |
| 断点续训 | 新格式 step20 续 2 步 7.6539→7.5228;legacy 断点 micro step 10 → update step 5 折算正确,续训 8.6738→8.5822 与 legacy 末值 8.7711 连续 |
| 新增回归 | global/micro 等价、累积边界(force_update / 尾批 / mask 不均)、legacy 断点折算(training_session_test 等扩充) |
📦 提交
bc37df3— TrainingSession 单步协议 + global/micro batch 语义 + 状态拆分- 另含发布提交
release: v1.11.0(tag 所指)
⚠️ 已知限制
TrainingStepReport.loss_sum设备侧回读为同步语义;异步回读由 Run Driver 经session.state()pending 槽自行安排- global batch 尾部不满时按剩余样本切 micro,最后 micro-step 走
force_update;归一化分母为实际有效 token 数 mnist_train仍走低层原语,迁移train_step留作后续
Release v1.10.0: 生命周期 v3 与训练工厂(P2b)
neuralnet.cpp v1.10.0 Release Notes
⚠️ 勘误(2026-10-23 追加,发布后标注;本版本不撤回)
- 梯度累积归一化缺陷:本版
TextTrainer在--accum-steps > 1时对每个 micro 梯度乘
1/accum_steps(“micro 均值之均值”);当 loss_mask 使各 micro 有效 token 数不同或末批不满时,
训练目标会偏离理论 global batch。默认--accum-steps 1路径不受影响。修复随 v1.11.0 发布
(按gradient_units / normalization_units精确归一化,见v1.11.0.md§3)。--batch-size/ step 计数语义在 v1.11.0 变更:batch_size从 micro-batch 改为 global batch
(micro = batch / accum,必须整除),--log-interval/--save-interval/--max-steps/ lr 调度
统一按 optimizer update 计。升级脚本请按v1.11.0.md§4.2 换算
(旧--batch-size 8 --accum-steps 4= 有效 batch 32 → 新--batch-size 32 --accum-steps 4)。- 断点兼容:v1.10.0 写出的
.nnckpt可在 v1.11.0 直接--resume(缺语义键按 legacy micro-batch
自动折算);反向(新断点回旧二进制)语义不同,回退前请保留原文件。- 本版本 tag 与产物保持不变(已发布版本不重打);新用户请直接使用 v1.11.0。
发布范围:
v1.9.0(795886f,2026-10-18)→v1.10.0(tagv1.10.0,2026-10-21;代码基线a9c6c6f+ 发布审查修复)。
主题:对象生命周期 v3(设备域模型) +TrainingSession训练生命周期容器 + 训练样本层与训练工厂TextTrainer+GpuBackend确定性回收。
兼容性:有破坏性变更(API 层)——Tensor不再保存引擎身份(engine_id()/bound()删除);ComputeEngine::id()、GpuBackend::instance()、NN_BIND_DEBUG删除;GPU 引擎默认拒绝 CPU 张量操作数;Dataset::load_text(bool)→load_text(DatasetSubset);DatasetIdentity.num_windows→num_samples;CLI 不再实现训练循环。模型文件格式 v8 与project VERSION 1.10.0不变。迁移见 §6。
版本关系:正式版汇总两个预发布候选——v1.10.0-rc0(生命周期 v3 +TrainingSessionP2a)与v1.10.0-rc0.1(P2b 训练工厂);rc0.1 之后未再引入新的功能变更,正式版收口独立发布审查发现的 P1-1 / P2-1 / P2-2(§2.5)并补齐发布验收与文档口径。
0. 亮点速览(TL;DR)
| 方向 | 变化 | 收益 |
|---|---|---|
| 生命周期 v3 | 删除 8 B 引擎身份字段(EngineId)与整套传播机制(stamp_ / adopt / bound / NN_BIND_DEBUG);设备域从存储派生(CPU → kHostDomain,GPU → backend 标识,空 → 0) |
减少一个概念:张量不需要知道"谁创建了我",只需要知道"现在在哪块设备上";机制点 101 → 42、净代码约 −120 行;sizeof(Tensor) 64 → 56 B(GPU 构建 88 → 80 B) |
| 入口检查 | 每个 public NVI 入口逐个操作数比较设备域(旧 bind_check_ 只比较操作数之间,单操作数入口可让外来 GPU 张量进入任意引擎) |
修复 D1:跨 backend 张量不再静默别名/UB,而是带 source_location + 形状 + 迁移提示的 Result 错误 |
| CPU/GPU 统一语义 | GPU 引擎拒绝 CPU 张量操作数(显式 import / to_matrix 是唯一跨域通道);删除 GPU impl 的 CPU 回退分支 |
推理/训练不再有"隐式上传"的语义分歧;CPU 与 GPU 走同一条域规则 |
GpuBackend 回收 |
shared() 改 weak 缓存 + mutex;删除 instance()(16 处调用点迁移) |
最后一个使用者释放后 Vulkan 资源确定性回收;再次 shared() 重建(不自动初始化设备) |
| 生命周期缺口 | activation offload 状态机 fail-fast + discard() 作废通道 + f16/t1_58 组合配置期拒绝;ExprSegment 改持 shared_ptr;GPU end_expr 无录制明确报错 |
推理/评估路径"未配对 backward"不再静默复用上一轮 slab(D4);录制段作用域内释放引擎句柄不再 UAF(D6) |
TrainingSession(P2a) |
新头 training_session.hpp:拥有 Model + Optimizer + TrainState,成员声明顺序保证 optimizer 先析构 |
D3 的结构性修复:Optimizer::params_/grads_ 的非拥有 TensorRef 永远有效,UAF 结构性地不可能发生 |
| 训练样本层(P2b) | 数据集存储单位 = doc;训练单位 = sample(seq_len 窗口);step = ceil(样本数 / batch_size) 是派生量 |
"样本"不再与"step"混用;数据集/断点身份记样本数,训练步数由 batch 派生 |
训练工厂 TextTrainer |
create(TrainingSession, Dataset, TextTrainConfig) → fit():切窗、epoch×step、累积/裁剪、lr 调度、评估、断点状态装配全在库层 |
训练入口从数百行手工接线降到"打开数据集 + 建 Session + create + fit";算法循环可测试、可被库外复用 |
| 配方单一真源 | nn::TextTrainConfig(库层)+ nn::make_text_train_config(session);CLI TrainConfig 继承之;lr_scheduler.hpp 上收库层 |
不再出现 CLI 与训练工厂两份默认值各自漂移;不一致在 TextTrainer::create 逐项 fail-fast |
1. 对象生命周期 v3:设备域模型
1.1 目标模型
Tensor 只拥有存储:
CPU 存储 → 域 = kHostDomain(进程常量;CPU 引擎无状态、共享宿主内存)
GPU 存储 → 域 = 其 GpuBuffer 的 backend 标识(buffer 持 shared_ptr<GpuBackend>
keepalive ⇒ 张量存活期内地址不被新 backend 复用)
空/无效 → 0(唯一豁免,交由各入口既有的无效/形状检查拒绝)
ComputeEngine 在派生类构造时写入本引擎域(非虚函数,虚表口径 52 方法不变);
每个 public 入口逐个操作数比较 t.device_domain_() == domain_。
import 是唯一跨域入口:CPU → GPU 上载;同域 = 别名/cast;GPU 异 backend 或
GPU → CPU(应走 to_matrix)= 明确错误。
1.2 入口检查与失败语义
| 场景 | v1.10.0 行为 |
|---|---|
| 张量活过引擎(含 GPU) | 安全(GPU 由存储 keepalive 保证) |
| 跨 CPU 引擎使用张量 | 允许(同一宿主域;CPU 引擎无状态) |
| GPU 张量进入不同 backend 的引擎 | Result 错误:跨 GPU backend,提示先 to_matrix 落宿主 |
| CPU 张量进入 GPU 引擎 op | Result 错误:提示先 engine.import(t) |
| GPU 张量进入 CPU 引擎 op | Result 错误:提示先 engine.to_matrix(t) |
跨 backend import |
Result 错误(提示先落 CPU) |
| 空张量参与运算 | 域检查跳过(0 豁免),由既有无效/形状检查拒绝 |
错误信息带 source_location、张量形状与两侧域值,不静默。
1.3 删除清单
include/neuralnet.cpp/core_engine_id.hpp与引擎租约头(core_engine_lease.hpp)整体删除;EngineId、ComputeEngine::id()、Tensor::engine_id()、Tensor::bound();ComputeEngine的stamp_/adopt出生绑定通道(及nn::dslfriend);NN_BIND_DEBUG行为开关与其 CI 第二轮 ctest(域检查现已是默认入口检查);- GPU impl 的 CPU 回退分支(
cast/copy_from/clone/copy_into/cast_into); GpuBackend::instance()(见 §2.1)。
新增 core_domain.hpp:using DomainId = std::uintptr_t;、inline constexpr DomainId kHostDomain = 1;、
ComputeEngine(DomainId) 保护构造与只读 domain()。
1.4 结构收益与数值不变量
| 项 | v1.9.0 | v1.10.0 |
|---|---|---|
sizeof(Tensor)(CPU / GPU 构建) |
64 B / 88 B | 56 B / 80 B |
| Tensor 拷贝 | 一次 8 B 身份复制 + 存储原子对 | 少一次 8 B 复制(原子行为不变) |
| 机制点(ID / stamp / adopt / bind / 调试开关) | 101 | 42(只留入口域检查调用点) |
| 净代码 | — | 约 −120 行 |
预期数值路径零改动:生命周期 v3 不改变任何计算表达式;CPU/GPU 字节锚必须逐位不变,
由 expr_fuse_test(融合 vs 未融合逐位一致、批量 vs 逐个 memcmp)、fused_gpu_test、
expr_fold_test 覆盖。scan_bitwise_test 仍以 Linux + 850M 为逐位权威口径。
2. GpuBackend 确定性回收与生命周期缺口收口
2.1 GpuBackend::shared() 弱缓存
static std::shared_ptr<GpuBackend> shared(); // weak 缓存 + mutex;全部释放后回收
static std::shared_ptr<GpuBackend> create(); // 独立实例(测试/多设备)
// instance() 已删除(16 处调用点迁移为持有 shared() 的 shared_ptr)行为:有活使用者时复用同一实例;最后一个使用者释放后 backend 与 Vulkan 资源按序析构;
下一次 shared() 重新创建(不自动初始化设备,调用方仍需 initialize())。
GpuBuffer 的 backend keepalive 保证"张量存活期内 backend 不析构、地址不复用"。
回归测试锁"全部释放 → 再 shared() 重建"。
2.2 activation offload 状态机(D4)
export_activations在offloaded_已为真时 fail-fast,不再静默return {};- forward 入口新增
discard()作废通道,推理/评估路径"未配对 backward"不会导入上一轮 slab; --activation-offload与--f16/--t1_58/--precision-*在配置期拒绝(slab 恒 f32,最短正确路径);- CPU 引擎的 offload 在 CLI / Session / 库三层 fail-fast(CPU offload 原语是 no-op,开启会破坏 backward)。
2.3 ExprSegment 录制段生命周期(D6)
ExprSegment 改持 std::shared_ptr<ComputeEngine>:作用域内释放最后一个外部引擎句柄不再 UAF;
GPU end_expr 在无录制时返回明确错误(跨线程 begin/end 从静默 no-op 变为可诊断错误)。
2.4 评审收口(rc0 之后)
- D1 收口:
GpuEngine::to_matrix拒绝异 backend 张量(v3 前会读外来VkBuffer= UB),
只接受本引擎域 GPU 张量与宿主域 CPU 张量; offload_restore补设备域检查;CPU offload 在库层 fail-fast;- 生命周期负例测试
lifetime_contract_test(CPU 可跑)随 P0 修复落地。
2.5 正式版前的独立发布审查修复(2026-10-21)
发布前对 v1.9.0..HEAD 做了独立代码审查(结论 P0 = 0 / P1 = 1 / P2 = 9),正式版收口了其中 3 项:
| 项 | 问题 | 修复 |
|---|---|---|
| P1-1 | TextTrainer::create 不校验"配方 seq_len ↔ 模型 seq_len":库外误配时 GPT 的 forward 用 input.rows()、位置编码/掩码却按模型 seq_len 构建(GPU 行 gather 无越界守卫 ⇒ 越界读),backward 又固定用 seq_len_(形状错配) |
create_impl 新增 fail-fast:模型带 ModelSpec 且 spec.seq_len 与配方不一致即拒绝;回归用例进 text_trainer_test [6] |
| P2-1 | --resume + --activation-offload 会绕过配置期拒绝:adopt 用断点状态段覆盖 cfg.precision 发生在组合校验之后,只能到首个 block 才由库层 fail-fast |
组合校验抽为 nn::cli::precision_offload_combo_error(cfg)(返回错误文案),argparse 结束与 adopt_resume_state 之后各校验一次;argparse 层 exit(1)、run_text_train 经 NN_FAIL 走 Result(保持三模块不终止进程) |
| P2-2 | 空 test 子集(合法文件:test 全为空 doc)不再打印 样本数: 0,与 rc0.1 承诺及 v1.9.0 输出锚不符 |
CLI 保持打印测试集概况行(含 assistant 掩码行)后跳过评估,训练继续 |
其余 P2(fit() 错误路径 begin_batch 无 RAII、hooks 错误路径未清空、offload 导出中途失败的状态残留、
基类 copy_into/cast_into 缺域检查、TrainState.global_step 注释语义等)记录为后续项,不阻塞本版发布。
3. TrainingSession(P2a)
3.1 用法
新头文件 include/neuralnet.cpp/training_session.hpp(nn.hpp / train.hpp 已转发):
nn::TrainingConfig cfg;
cfg.optimizer = "adamw"; cfg.lr = 1e-3f; cfg.weight_decay = 0.01f;
cfg.clip_grad_norm = 1.0f; // 0 = 不裁剪
auto session = NN_UNWRAP(nn::TrainingSession::create(std::move(model), cfg));
NN_UNWRAP(session.zero_grad());
auto logits = NN_UNWRAP(session.model().forward(x));
/* ... loss / backward(由调用方组合;Loss 不归 Session)... */
NN_UNWRAP(session.clip_grads());
NN_UNWRAP(session.step()); // optimizer.step() + step_count++TrainingConfig 字段 = optimizer / lr / weight_decay / precision / beta1 / beta2 /
clip_grad_norm / activation_offload / checkpoint_every;模型侧训练期开关在构造优化器前应用。
3.2 所有权不变式
model_声明在opt_之前 ⇒ optimizer 先于 Model 析构(TensorRef永不悬空);- Session 不可拷贝、可移动(Layer 对象在堆上,移动后
TensorRef仍有效); Model保持纯值对象——推理可直接用Model,不需要 Session;- Loss / 数据集 / epoch / CLI / 日志不归 Session(防 god object,见
../todo/29-session-architecture.md§4); Session反查引擎走session.engine()(=model().engine()),不持裸引擎引用。
3.3 自由工厂保留
create_optimizer(name, engine, params, grads, ...) 保留:调用方必须保证
"optimizer 在其参数所有者存活期内销毁";需要结构保证时用 TrainingSession。
其签名(unique_ptr / nullptr)本轮不收口,见 §9.3。
4. 训练样本层与训练工厂 TextTrainer(P2b)
4.1 样本概念(dataset_samples.hpp)
存储单位 = doc(.nndataset 里一行文本)
训练单位 = sample(一个 seq_len 滑动窗口,可跨 doc)
step = ceil(sample_count / batch_size) ← 派生量,不写进数据集格式
nn::DatasetSubset{Train, Test}:Dataset::load_text(subset)、doc_count(subset)、
text_doc(subset, index)(存储单位访问);nn::TextWindowConfig{seq_len, stride, pad_id}:窗口化策略(训练策略,不进文件格式);nn::TextSample:样本视图(tokens / doc_ids / loss_mask / valid_len / offset);nn::TextSampleSet::create(dataset, subset, cfg):一次完成切窗 + 元数据;
sample_count()、sample(i)、steps_per_epoch(batch_size)、make_batch(indices, out);nn::TextSampleBatch:input矩阵(seq_len, batch)+ batch-major 扁平
flat_targets / flat_mask / doc_ids+valid_tokens。
逐位约定(与旧 CLI 循环一致,dataset_samples_test 锁住):
x[t] = token_flow[offset+t],y[t] = token_flow[offset+t+1](目标 = 下一 token);- 位置参与 loss ⇔
t+1 < valid_len && loss_mask[offset+t+1](mask 按目标位置); - PAD 位置
x/y = pad_id、flat_mask = 0、doc_ids = 0;末批可不满。
4.2 训练工厂
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。
Release v1.8.2: f16 训练缺陷修复与提速
neuralnet.cpp v1.8.2 Release Notes
发布范围:
v1.8.1(830091f,2026-10-12)→v1.8.2。
主题:f16 训练缺陷修复(F1–F5,含"四字段全 f16 可训练") + f16 提速(编译期精度特化 / 批量派发 / 网格粒度变体 / F16C / f16 GEMM) + RAPT / CNN 的 f16 接线 +Layer::init参数精度自检。
兼容性:无破坏性 API 变更——引擎接口与 virtual 计数(52)不变、get_*_spirv()名称/签名不变、create_tensor签名保留;仅新增可选字段/函数与一处 init 期 fail-fast 收紧(见 §4 与 §6)。逐条核验见../todo/24-f16-training-defects.md(缺陷台账)与../todo/26-f16-speedup-plan.md(提速方案)。
0. 亮点速览(TL;DR)
| 方向 | 变化 | 收益 |
|---|---|---|
| f16 训练缺陷(F3) | GPU stable=f16 的 loss 冻结/inf 根因 = CE 的 (1,1) loss 归约按 stable 落盘溢出(Σ ≈ -log(vocab)·N 超 65504 → inf → NaN) |
two 条 CE 链(稀疏 + 稠密)的 (1,1) 输出恒 Precision::F32;stable=f16 曲线恢复可观测、120 步无 NaN |
| f16 全字段(F5) | 四字段全 f16 原不可训练(Adam 二阶矩 v≈(1-β2)g² 在 f16 下下溢为 0) |
优化器状态缩放(进 m/v 的梯度放大 S=4096,2 的幂无损):更新量数学不变、m/v 回到 f16 正常区 → 全 f16 现可训练 |
| 危险组合告警(F1) | optimizer=f16 无任何启动提示 |
nn::cli::dangerous_precision_warning 在进训练循环前打印一次警告(仅警告,不改退出码/行为) |
| f16 提速(P2-11/13) | --f16 曾比 f32 慢 11–17%;主因 = 运行期精度分派的"三元税"(#x 在热循环里每次读写都判精度) |
编译期精度特化(#s / #xxxx 真实签名变体)+ 网格粒度变体(#f/#ks/#kr × 精度签名):850M --f16 2005 vs f32 2038 ms(−1.6%)、40HX −3.2%,峰值显存 850M 606 vs 818 MiB(−26%) |
| 优化器派发(P2-12) | --f16 下 Adam 复合结构逐成员降级派发([ir-c][degrade] 70/步) |
#b 批量变体 + "scan 与运行期同源"修复 → 每步 0 降级、优化器派发 −94%、host 阶段 −34% |
| CPU f16(P2-8/S4) | 纯软件位操作的 f16 转换拖慢 CPU --f16 |
__F16C__ 硬件转换快路径(imm8 固定 RNE、与 MXCSR 无关;常量求值/可移植构建仍走软件路径):CPU f16 3.6 s → 1.9 s(不再慢于 f32) |
| RAPT / CNN f16(P2-3) | 两架构权重/梯度此前硬编码 Precision::F32,--f16 拿不到显存收益 |
RAPT 侧 27 处 dsl::compute* 补精度实参 + token_emb_/残差/rms_norm_ 输出按 param/compute;Conv2D w_/b_/grad 按 param;CnnConfig.precision 新增(默认全 F32 → 历史逐位不变) |
| 精度自检(P1-6 R2) | 层把权重硬编码 F32 时 --f16 静默失效(无告警、无日志) |
Layer::init 后校验"param 槽声明 ↔ 参数张量实际存储精度":当场抓到并修复 3 处静默降级(Conv2D w_/b_、RAPT token_emb_、ReLULinearAttention 未把 profile 转发自持的 4 个 Linear) |
1. f16 训练缺陷修复(24 F1–F5)
- F3 修 GPU
stable=f16loss 冻结:真因不是 loss 回读链,而是 CE 的(1,1)loss 归约输出按p_.stable落盘溢出(compute_loss.hpp的稠密与稀疏fused_forward_sparse_两条链)。修法 = 该(1,1)归约输出恒Precision::F32。实测(850M):seq256/b32由inf→9.0056 → 8.6922;stable=f16120 步无 NaN。回归用例见f16_precision_test/ce_fusion_test。 - F5 全 f16 可训练:不用训练循环 loss scaling,而是在优化器状态更新处缩放(
Adam::optimizer_state_scale_)——进入m/v的梯度放大S=4096(2 的幂 → f16 下无损),(1-β1)·S/(1-β2)·S²/eps·S同步换算。为什么有效:Adam 更新是尺度不变的lr·(S·m̂)/(√(S²·v̂)+S·eps) = lr·m̂/(√v̂+eps)⇒ 更新量数学不变,但m~g·S、v~g²·S²都回到 f16 正常区。边界:|g| ≳ 0.0625时v会溢出 f16 ⇒ 实践配--max-norm裁剪。 - F1 危险精度组合启动期告警:新增
include/neuralnet.cpp/cli/cli_precision_warning.hpp(nn::cli::dangerous_precision_warning),text_train/mnist_train在解析后、进训练循环前打印一次到 stderr;触发 =optimizer=f16(覆盖四字段全 f16)。仅警告:不退出、不加确认开关、退出码与训练行为不变。门禁src/cli_precision_warning_test.cpp(单测组合矩阵 + 子进程捕获 stderr)。 - F2 覆盖缺口:profile 组合矩阵补
{F16,F16,F16,F16}行;stable=f16/ 全 f16 形态进 GPU 分支做数值断言;CE 溢出回归。端到端全 f16 训练仍归大版本发布清单(25第 1 项,不进默认 ctest)。
2. f16 提速(26;台账 24 · F4)
验收锚 = 任意设备(含无 shaderFloat16 的 850M)--f16 step ≤ f32 且峰值显存更低。落地四件(另有一件 S3):
- P2-11 编译期精度特化(
#s= 全 f16 静态签名 /#xxxx= 扫描期真实观察到的签名):把"精度是运行期参数"(#x+ PCprec)在热路径换回"精度是编译期常量",只需 16 位存储、不依赖shaderFloat16。850M:--f162260 → 2055 ms/15 步(f32 2037,+10.6% → +0.9%)。 - P2-13 网格粒度变体(
#f/#ks/#kr叠加到精度签名):每个静态签名变体各发一份;部分和缓冲恒 f32(末段按 sig 落盘)。40HX(b64)该 wgrad f16 16.0 vs f32 25.1 ms/块;端到端 40HX 1013 vs 1027 ms(−1.4%)、850M(b16)2005 vs 2038 ms(−1.6%)。 - P2-12 优化器 f16 批量派发(
#b):对每个 extras 为空的静态签名变体各发一份批量变体;残差根因 = scan 与运行期不同源(已修)。实测(40HX):[ir-c][degrade]70 → 0 / 步、优化器派发 138.7 → 8.55 calls/步(−94%)、host 阶段 9.94 → 6.51 ms/步;端到端墙钟在 GPU-bound(忙率 95.5%)下不可测。 - P2-8 / S4 CPU F16C 转换:
detail::float_to_half_bits/half_bits_to_float增__F16C__快路径(_cvtss_sh/_cvtsh_ss,imm8 =_MM_FROUND_TO_NEAREST_INT→ 与 MXCSR 无关、恒 RNE);常量求值与NN_ENABLE_NATIVE=OFF可移植构建仍走软件位操作。precision_test增「软/硬逐位一致」段(23 条 f32→f16 + 15 条 f16→f32)。 - S3 f16 GEMM(storage-only、f32 累加):
-DNN_SHADER_F16=1编第二份 SPIR-V;小 N(N ≤ 8且transA=0)走matmul_q_gemv.comp。 - 已评估-不做(均附实测/上界证据,见
26§4):S0(step 内固定开销——前提被实测推翻)、S1(主导张量全 f16——实测净亏)、S2(f16x2 打包)、S5 / P2-10(native16 资格不再扩大——收益有界)。
3. RAPT / CNN 的 f16 接线(roadmap P2-3)
- RAPT:27 处
dsl::compute*补精度实参p_.compute(此前全部落默认 F32);两个残差分支由硬编码Precision::F32改p_.compute;token_emb_/grad_token_emb_改p_.param;rms_norm_forward_/backward_的输出改p_.compute(否则 dst/src 精度错配 →insert_rows断言)。 - Conv2D:
w_/b_/grad_w_/grad_b_改p_.param,4 处dsl::compute*补p_.compute。 - API:
CnnConfig增precision字段(默认PrecisionProfile{}= 全 F32),build_cnn_model(_from_spec)在add之前注入(新增可选形参)。 - 门禁:新增 4 条 ctest ——
cnn_test_f16/cnn_test_f16_gpu/rapt_test_f16/rapt_test_f16_gpu(断言 loss 有限且下降、梯度无 NaN;GPU 侧另验精度变体闭合世界与槽位)。
4. Layer::init 参数精度自检(P1-6 R2;行为收紧)
Layer::init(NVI)在 init_impl 之后统一执行 check_param_precision_()——层声明的 param 槽是存储精度时,逐个校验 parameters() 的张量实际存储精度与之相同(precision_bytes(p_.param) == 0 的非存储精度声明,如 T1_58,跳过)。
它防的是一类静默降级:层把权重/梯度硬编码 F32(或忘用 p_.param)→ --f16 下该层参数仍是 f32,训练照跑、数值不错,但"f16 省字节"在其上完全落空。当场抓到 3 处:Conv2D 的 w_/b_、RAPT 的 token_emb_、ReLULinearAttention 从未把 profile 转发给它自持的 4 个 Linear(RAPT 注意力投影权重在 --f16 下一直是 f32)——前两处在 P2-3 修掉,第三处由本自检报错后发现并修复。
迁移提示(写自定义层的人):若你的层声明了
PrecisionProfile.param为某种存储精度(f16/f32),但把参数张量硬编码成另一种,init现在会报错(此前静默通过)。修法 = 参数创建时用p_.param。指南见../development/write-a-custom-layer.md。
5. 工程化与文档
- ctest 47 条(34 个测试目标 + 13 个变体/附加):新增
cli_precision_warning_test与 §3 的 4 条 f16 变体;rapt_test补 GPU 可用性预检(CPU-only 构建下 f16_gpu 用例按退出码 77 = SKIP,不假红)。 - 文档审计回正:
bench/doc_align_audit.ps1的[A][B][C][D][E][F]可行动项归零——修若干处文档↔代码不一致(把 BitNet 的打包脚本改为带路径的外部引用、把文档里被当作 CLI flag 的 f32 档写回"默认、无 flag"、把 GQA 示意图中被误判成 CLI 参数的长箭头改掉),并给审计脚本补两处合法白名单(ternary_deploy.hpp/ternary_deploy_test等已删文件/测试的历史提及;cmake/ctest 自带选项)。 - 现状画像刷新(
18-roadmap.md§2 / §8.1、12):include/74 hpp / 41247 行、src/75 cpp / 26614 行、shaders/22 comp、docs/43 md;compute_vk_backend.hpp经 v1.8.1 拆分后 1055 行;引擎 52 virtual / Layer 直调 27 / 宿主桥 41。
6. 兼容性说明
- 无破坏性 API 变更:引擎 virtual 计数 52 不变;
get_*_spirv()名称/签名不变;create_tensor签名保留;Layer/Loss/Optimizer/Model公共入口无删除。 - 新增(可选):
nn::cli::dangerous_precision_warning;CnnConfig::precision(默认全 F32);build_cnn_model_from_spec(engine, spec, precision = PrecisionProfile{})可选形参;profile_all_f16()。 - 行为收紧(1 处):
Layer::init新增参数精度自检(§4)——库内层全部自洽;自定义层若声明param为存储精度却把参数硬编码为另一精度,会在init报错(此前静默降级)。 - 数值:默认(f32)路径与 v1.8.1 逐位一致(
param/compute默认 F32 不变);--f16的数值路径随新增特化/网格变体 shader 改变(同语义、不同 kernel),如出现旧--f16字节锚差异属预期。
7. 验收基线
| 项 | 结果 |
|---|---|
构建(Release,GPU 启用,build-gpu) |
全绿、零告警(-Werror) |
本机 ctest(Release + Vulkan 设备可用) |
47/47 通过(0 skip) |
bench/doc_align_audit.ps1 |
[A][B][D][E][F] 可行动 0 |
bench/doc_inventory.ps1 |
virtual methods 52/52、L2-VIOLATIONS: 0、宿主桥 41(仅披露)、HANDWRITTEN-RESIDUE: 0、exit 0 |
跨设备 --f16 ≤ f32(26 §5) |
850M −1.6%、40HX −3.2%、CPU 不再慢于 f32 |
8. 提交索引
| 主题 | 提交 |
|---|---|
| f16 缺陷定位与台账 24 | 886a1f3 / aab2c81 |
| f16 提速方案(26)+ P2-9/P2-10 立项 | 6c0d591 |
| S1s/S1t 编译期精度特化 | 2e200e8 |
| P2-13 网格粒度变体 | 1377900 |
| P2-12 优化器批量派发(+ 收口) | b75f2b9 / 5bb6a51 / b8465f9 |
| P2-3 RAPT/CNN f16 接线 | 0799a49 |
| P2-8/S4 CPU F16C 快路径 | 399eb5e |
| F5 全 f16 可训练 | 50cf907 |
| P1-6 R2 参数精度自检 | ea15796 |
| S0/S1/S2/S5 评估-不做 | e5fa160 / f5d0084 / 6f93587 |
| 合并(PR #19) | dfffbc5 |
| Release v1.8.2(版本号 + 本文件 + 索引同步) | 见本仓 git log |
完整变更:git log v1.8.1..v1.8.2 / git diff v1.8.1..v1.8.2
9. 仍 Open(下一批)
- roadmap P4(2026-10-14 立项):性能利用率 / 算子性能 / 显存开销的持续提升 + API 易用性与 Fold 表达式自动化扫描(注意力 fold 的 5 组合尚由
scan_exprs.cpp手写登记块覆盖)。 - P0-5:推理 top-k / top-p 采样 + 多轮对话闭环。
- P1-1 / P1-2 / P1-5:
text_train模块化、checkpoint 优化器状态(Adam m/v 入.nnckpt)、CI 冒烟扩 MSVC Debug + 权重变化断言。
完整台账见 ../todo/18-roadmap.md。
Release v1.8.1: 代码审查整改 + CI 真跑测试
neuralnet.cpp v1.8.1
发布范围:
v1.8.0(afcb3d0)→v1.8.1
主题:代码审查整改(RV-06 … RV-13) + CI 真跑测试与绑定不变量门禁 + 工程化(预设构建 / 格式与贡献指南 / 文档归档) + 编译提速(ccache × PCH)
兼容性:无破坏性 API 变更(引擎接口与 virtual 计数不变、get_*_spirv()名称/签名不变、create_tensor签名保留并新增try_create_tensor、Layer/Loss/Optimizer/Model 公共入口无删除;模型/数据集文件格式零破坏)
亮点速览
| 方向 | 变化 | 收益 |
|---|---|---|
| 算子分派(RV-07) | 同一个 ExprOp 原先散在约 9 处(CPU 引擎 3 switch + 语义校验 + 常量折叠 + GLSL emitter 6 处) |
收敛为唯一表 expr_op_table.hpp:新增一个逐元素算子只改表里一行(编译期断言强制一致) |
| VK 后端(RV-08) | compute_vk_backend.hpp 6397 行 + 42 个手写 get_*_spirv() |
getter → 单一注册表;成员体拆 4 片段;主文件 6397 → 1055 行 |
| 环境变量(RV-09) | 库内 14 处 getenv、≥6 份 helper |
收敛到 core_env.hpp 一处(探针门语义不变:门关恒 false 且不求值) |
| 绑定门禁(RV-06) | NN_BIND_DEBUG 靠人肉维护 |
升为 CI 默认门禁(Linux/Windows 各一轮);顺带修掉 matmul_q_impl 边界 cast 的 stamp 漏网 |
| CI | 只编不测、打包缺工具 | 配置加 -DNN_ENABLE_TESTS=ON + 测试步骤;打包补齐 dataset_gen/dataset_convert(入口 6 → 8) |
| 工程化 | 无预设 / 无格式配置 / 无贡献指南 | CMakePresets.json(6 preset + build/test/workflow)、.clang-format/.editorconfig/.gitattributes、CONTRIBUTING.md/CODE_OF_CONDUCT.md |
| 编译提速 | 用 PCH 的 TU 全部不可缓存 | 钉进 CCACHE_SLOPPINESS + -fno-pch-timestamp:同一 TU 30.2s → 0.11s(缓存命中) |
本批整改条目
- RV-06 绑定不变量 +
NN_BIND_DEBUG升为 CI 门禁(前置修matmul_q_impl的 stamp 漏网) - RV-07 算子元数据单一表(CPU 三处 switch +
expr_opt折叠 + GLSL emitter 六处 → 一张表) - RV-08 SPIR-V getter 单一注册表 +
GpuBackend物理拆分(纯移动) - RV-09 环境变量单一入口
core_env.hpp - RV-10 BitLinear 量化判据注释精确化(不改算法)
- RV-11
precision.hpp冻结「枚举值 ↔ 序列化 tag」映射 + 往返static_assert - RV-13
nn::Expression/nn::BoolExpression归位到 L1algebra_span.hpp - CI 修复 6 个 GPU 用例(
gpu_test/gpu_f16_test/gpt_offload_test/fused_gpu_test/ce_fusion_test+ 复用方expr_gpu_test)补齐「无设备 → 77 = ctest SKIP」口径;无卡 runner 上不再假红
验收基线
- 本机 Linux(clang 24,Release,CPU-only)
ctest40/40 - 融合 shader 字节锚:RV-07 前后两份源码各编
scan_exprs生成后diff -rq→ 805 个产物零差异 - 拆分等价性:主文件头 + 4 片段 + 尾 vs 拆分前文件逐行相同(纯移动)
- 40HX(Windows + clang++,Debug,GPU)全量构建通过;
ctest42/42、NN_BIND_DEBUG=1 ctest42/42 - CI 全绿:GitHub Actions(Linux + Windows × 普通/
NN_BIND_DEBUG)4 轮ctest各 42 个用例全通过 doc_inventory(L2 违规 0 / 手写残留 0 / virtual 52-52)与doc_align_auditexit 0;gui_cli_auditPASS
仍 Open(下一批)
RV-05(GPU 后端显式 shutdown()/引用计数)、RV-12(Loss 直构 IR spec,先设计)、RV-14(VK Descriptor 回收专项审计)。
完整变更:git log v1.8.0..v1.8.1 · 逐条台账见 docs/todo/00-code-review-remediation.md,详细说明见 docs/release-notes/v1.8.1.md
Release v1.8.0: BitNet And IR-C Optimize
neuralnet.cpp v1.8.0更新
新模型BitNet
- 增加了训练1.58bit模型的能力
- 增加了
BitLinear层 - 增加了
ReLU^2激活函数 - 增加了
AdamWQuant优化器
IR-C相关优化
本次更新后,API变化频率我尽可能降低,项目基本转入“加新模型训练推理能力,优化性能“的开发模式
Release v1.7.1: 添加tokenizer编码进度条
Release v1.7.0: Dataset类
neuralnet.cpp v1.7.0 Release Notes
发布范围:
v1.6.1(384c58b,2026-10-04)→v1.7.0;内容提交截至f7b6bf7(2026-10-06),其后仅含本发布文档与版本同步提交。
提交量:6 content commits,114 files changed,+6090 / −3150(含测试、文档与 Python 侧同步)。
主题:统一数据集——.nndataset/.nnvocab/ KVRecord v2 格式统一 + 只读类nn::Dataset+ 生成/转换工具dataset_gen/dataset_convert,text_train只吃数据集、模型格式 v6 内嵌词表 kvrec;同批交付错误处理宏族(NN_TRY/NN_CHECK/NN_EXIT/NN_FAIL);ctest 24 → 27。设计与逐条裁决见19-unified-dataset.md。
兼容性:有破坏性变更(CLI 参数、词表文件格式、.tokcache移除,见 §6 迁移指南);模型文件 v1–v5 零破坏(v5 及以下仍读其内嵌 JSON 词表);引擎 / Layer / Tensor 接口无变更(ComputeEnginevirtual 仍 49)。另含load_tokenizer_from_file签名变更(API 级,见 §6.3)。
亮点速览(TL;DR)
| 方向 | 变化 | 收益 |
|---|---|---|
| 数据格式 | 六条互不相通的读取路径(MNIST CSV×2 / txt 直读 / .tokcache / JSON 词表 / .nnpkg)收敛为 .nndataset(数据)+ .nnvocab(词表),底层统一 KVRecord v2 |
新接数据集 = 跑一次 dataset_gen,零新代码;词表/语料/掩码/缓存四个概念合一 |
| KVRecord v2 | 字段只记 key / 类型 / 文件绝对地址,值走数据块;Type::Record 层层嵌套;哨兵自举 v1/v2 分派 |
大值(词表 blob、doc 块)零拷贝寻址;数据块地址连续性校验 = 整体搬迁硬报错;模型 spec 头仍 v1 字节零变化 |
| 生成期语义 | tokenize、loss 掩码(all/assistant)、test 子集、来源 sha256、license 全部在生成期定好并嵌入数据集 |
text_train 删 4 个选项与全部数据加载代码;"数据集自带语义",不再训练期重扫 |
| 统一类 | 新 nn::Dataset(只读:嗅探标签分派 .nndataset/.nnvocab);load_text() 输出与旧 tokcache 产物逐位同构 |
下游"拼流 → 滑窗 → 训练"一行不改;§8 逐位一致有回归锁 |
| 模型格式 v6 | 内嵌词表改 .nnvocab kvrec(v5 及以下仍读内嵌 JSON,按版本分派) |
词表二进制自描述;旧模型零破坏、无需转换工具 |
| 错误处理 | 手写 auto r = f(); if (!r) … 三形态收敛为宏族 NN_TRY/NN_CHECK/NN_EXIT + NN_FAIL;全仓迁移 + 门禁基线 0 |
L2 层 −约 500 处样板;错误风格一处可查(core_errors.hpp) |
| 测试 | ctest 24 → 27:kvrec_test / nnvocab_test / dataset_test |
KVRecord v2、词表往返、数据集逐位一致全部进常驻口径 |
Release v1.6.1: text_train新选项
v1.6.1 — 旗舰开训前置
无破坏性变更:全部新选项可选,默认行为不变。
text_train 新选项
--max-steps <n>— 限步训练:跑 N 步即停止并保存模型,用于吞吐实测与分段续训(配合--resume);停止前强制完成当前梯度累积,不丢半截更新--loss-scope <all|assistant>— 对话 SFT 只对<|assistant|>…<|end_of_assistant|>段计算 loss(system/user 轮只作条件不作监督);训练/测试同口径,语料缺对话段直接 fail-fast--beta1/--beta2— Adam/AdamW 动量衰减可调(LLM 预训练惯例 β2=0.95)
数据脚本
scripts/download_code_dataset.py— 代码语料:The Stack(8 语言白名单)→ codeparrot 回退链scripts/download_math_dataset.py— 数学语料:OpenWebMath 回退链- 均为 streaming 拉取、每行一文档的项目预训练格式
同步与验收
- GUI 新增「loss 范围与限步」区与 β 输入框,GUI↔CLI 审计、文档对齐审计全绿
- 构建 73/73 · ctest 23/23 ·
gui_cli_auditPASS ·doc_align_auditexit 0