Skip to content

Releases: EthanPeng-2048/neuralnet.cpp

neuralnet.cpp v1.12.0

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 23 Oct 16:30

neuralnet.cpp v1.12.0 Release Notes

发布范围:v1.11.0(tag v1.11.0,2026-10-23)→ v1.12.0(tag v1.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 审查在合并前修复了以下路径,并加入回归:

  1. PPO off-policy 梯度:负 advantage 的裁剪两侧及容差带内部曾使用错误分支;现在
    统一使用 ratio * advantage 的未被裁剪侧梯度,并覆盖负 advantage 的有限差分。
  2. GAE mask 语义:mask 外时间步的 value 不再泄漏到前一步 bootstrap;新增非零 value
    的 masked-value 回归。
  3. Policy batch 形状校验:REINFORCE/entropy/KL/PPO 在进入引擎前置检查 (1,T),
    阻止宽度不符时继续读写。
  4. 具名反向状态:失败的 forward 会立即失效旧的 backward 配对状态。
  5. 不可信 schema 解码:字段/名称计数在大分配前按上限和负载长度校验;字段名校验
    改为线性重复检测,schema 指纹加入字符串长度边界。
  6. 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修改

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 22 Oct 23:11

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::create fail-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)

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 21 Oct 13:45

neuralnet.cpp v1.10.0 Release Notes

⚠️ 勘误(2026-10-23 追加,发布后标注;本版本不撤回)

  1. 梯度累积归一化缺陷:本版 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)。
  2. --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)。
  3. 断点兼容:v1.10.0 写出的 .nnckpt 可在 v1.11.0 直接 --resume(缺语义键按 legacy micro-batch
    自动折算);反向(新断点回旧二进制)语义不同,回退前请保留原文件。
  4. 本版本 tag 与产物保持不变(已发布版本不重打);新用户请直接使用 v1.11.0。

发布范围:v1.9.0(795886f,2026-10-18)→ v1.10.0(tag v1.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 + TrainingSession P2a)与 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::dsl friend);
  • 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 所有权不变式

  1. model_ 声明在 opt_ 之前 ⇒ optimizer 先于 Model 析构(TensorRef 永不悬空);
  2. Session 不可拷贝、可移动(Layer 对象在堆上,移动后 TensorRef 仍有效);
  3. Model 保持纯值对象——推理可直接用 Model,不需要 Session;
  4. Loss / 数据集 / epoch / CLI / 日志不归 Session(防 god object,见 ../todo/29-session-architecture.md §4);
  5. 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 训练工厂

Read more

Release v1.9.0: 可续训模型与引擎所有权 v2

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 18 Oct 14:07

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 续训与 .nnmodel warm 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 训练缺陷修复与提速

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 14 Oct 15:39

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=f16 loss 冻结:真因不是 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=f16 120 步无 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 + PC prec)在热路径换回"精度是编译期常量",只需 16 位存储、不依赖 shaderFloat16。850M:--f16 2260 → 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 真跑测试

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 12 Oct 15:52

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 归位到 L1 algebra_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)ctest 40/40
  • 融合 shader 字节锚:RV-07 前后两份源码各编 scan_exprs 生成后 diff -rq → 805 个产物零差异
  • 拆分等价性:主文件头 + 4 片段 + 尾 vs 拆分前文件逐行相同(纯移动)
  • 40HX(Windows + clang++,Debug,GPU)全量构建通过;ctest 42/42、NN_BIND_DEBUG=1 ctest 42/42
  • CI 全绿:GitHub Actions(Linux + Windows × 普通/NN_BIND_DEBUG)4 轮 ctest 各 42 个用例全通过
  • doc_inventory(L2 违规 0 / 手写残留 0 / virtual 52-52)与 doc_align_audit exit 0;gui_cli_audit PASS

仍 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

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 11 Oct 12:07

neuralnet.cpp v1.8.0更新

新模型BitNet

  • 增加了训练1.58bit模型的能力
  • 增加了BitLinear层
  • 增加了ReLU^2激活函数
  • 增加了AdamWQuant优化器

IR-C相关优化


本次更新后,API变化频率我尽可能降低,项目基本转入“加新模型训练推理能力,优化性能“的开发模式

Release v1.7.1: 添加tokenizer编码进度条

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 05 Oct 14:11

如标题所示

Release v1.7.0: Dataset类

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 05 Oct 12:57

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 接口无变更(ComputeEngine virtual 仍 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新选项

Choose a tag to compare

@EthanPeng-2048 EthanPeng-2048 released this 04 Oct 14:39

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_audit PASS · doc_align_audit exit 0