本文档专为 AI 编码助手编写:用最少 token 建立项目心智模型,快速开始开发。 人类向的完整文档在
docs/(索引见 §11)。改代码前必读 §5 铁律与 §10 高频坑。 本文只记录当前状态;演进记录、已修复勘误、被否决方案与性能 A/B 过程统一归档在docs/history/history.md(索引见 §11)。当前能力清单见 §12。
从零实现的 C++26 神经网络库:CPU / Vulkan 双后端,支持 MLP / ViT / GPT 训练推理 + BPE 分词器,可选 f16 混合精度。
include/neuralnet.cpp/ 是 header-only 库(统一聚合入口 nn.hpp;也可按需 include engine/layer/loss/optimizer/model/train.hpp 薄转发头),src/ 是可执行入口,shaders/ 是 GPU 原语 shader。
# 构建(默认 Release:-O3 -funroll-loops -march=native,Clang/GCC 另加 -fno-exceptions -Wall -Wextra -Wpedantic -Werror)
cmake -B build -G Ninja && cmake --build build
# 测试(默认关闭;开启后测试程序在 build/test/,并注册 ctest)
cmake -B build -G Ninja -DNN_ENABLE_TESTS=ON && cmake --build build && ctest --test-dir build- 子仓单测已下沉(
extern/{base,threadpool,kvrecord,dataset,tokenizer}):error_macro_test(NN_UNWRAP/NN_TRY 宏族)、precision_type_test(f16 位级转换/舍入)、kvrec_test(KVRecord v1/v2)、tokenizer_test/tokenizer_consistency_test、nnvocab_test、dataset_test(纯格式段)已迁至各子仓tests/,主仓 ctest 不再包含;分词器×数据集端到端集成链路在 tokenizer 子仓dataset_integration_test。子仓独立构建 + ctest(各子仓 README「构建与测试」);子仓测试门NN_BUILD_TESTS(顶层工程默认 ON、嵌入默认 OFF,与主仓NN_ENABLE_TESTS互不干扰)。threadpool 子仓另有新写的threadpool_test/parallel_test(主仓此前无直接覆盖);base 子仓另有新写的sha256_test/file_test/assert_test。 - 链接报
permission denied(build/下 exe 正在运行被占用 → 构建中止,易误判为"未改动"):追加-- -k 0让 ninja 跳过失败目标继续构建其余目标;被占用目标仍需关掉进程后重链。 - 编译器与标准库:CMakeLists 不指定(编译器由 CMake 默认探测或调用方
CXX=.../-DCMAKE_CXX_COMPILER决定,标准库跟随编译器默认);CI 主矩阵显式指定 clang++(Linux/Windows,避免 GCC 独有警告在-Werror下失败),另有一个 MSVC Debug 冒烟 job(msvc-debug:Windows + Visual Studio 18 2026 生成器 = cl.exe、NN_ENABLE_GPU=OFF纯 CPU、Debug + ctest(除已知 C11 外全跑);P1-5,2026-10-18 起)。标准库编译/链接两侧必须一致(否则链接期大量std::__cxx11::*未定义)。CMake 3.30+;已验证四组「编译器 × 标准库」各自全新构建 + ctest:clang 24 + libc++(本机 Linux)、g++ 15.2 + libstdc++(WSL,CPU-only)、clang 23 + MSVC STL 与 MSVC cl 19.51 + MSVC STL(Windows/102)——MSVC 侧必须带/Zc:preprocessor(已写进CMakeLists.txt的目录级与接口选项):cl.exe 默认的传统预处理器把__VA_ARGS__代入嵌套宏时按"单个实参"处理,NN_UNWRAP(x, msg)会静默退化成逗号表达式(条件恒真、检查永不触发),NN_EXIT(x, code, msg)则直接C3861;打开该开关后两者都按标准展开(详见docs/history/history.md的构建系统条目)。MSVC Debug 另有两条实测必需的开关:/bigobj(text_train.cpp触发 C1128)与/STACK:8388608(9 个测试默认 1 MB 栈触发0xC00000FD),均已写进CMakeLists.txt;v1.10.0 发布复验(2026-10-21,本机 VS 2026 / cl 19.51 Debug CPU-only)实测 57 注册 / 37 pass + 20 skip / 0 failed,CI 口径(-E "^rapt_test_f16$")56 注册 / 36 pass + 20 skip / 0 failed(20 skip = 17 GPU 依赖 + 1 Vulkan 探针 + 2 个 app 发现类用例:cli_precision_warning_test/chat_prompt_test在 VS 多配置下 exe 位于<build>/Debug/找不到 ⇒ 77 skip,所以这 2 个在 MSVC Debug 下实际不执行;旧记的 CI 51/51、完整 52/52 已随新增用例过时)。 - Windows clang++ 栈口径:GNU-like 驱动的 clang++ 在 Windows 上不是 CMake 的
MSVC分支,但链接仍走link.exe、默认栈同样 1 MB;CMakeLists.txt另加-Wl,/STACK:8388608(102 实测t1_58_test/bitnet_struct_test/model_roles_test否则 SegFault)。 - clang + libc++ 的口径差异:
-Werror下 libc++ 会对"记录类型但从未使用"的局部变量报-Wunused-variable(同一代码在 libstdc++ 下不报)——clean 构建四种组合时会先撞到,属真实死变量,删掉即可(先例:extern/kvrecord/tests/kvrec_test.cpp的std::string ls;)。 - 构建选项:
NN_ENABLE_NATIVE(默认 ON,开启-march=native,分发/CI 用OFF生成可移植基线);NN_ENABLE_TESTS(默认 OFF);NN_ENABLE_PCH(默认 ON,见下);NN_ENABLE_PROBES(默认 OFF,见下条)。 - 预设构建(
CMakePresets.json,2026-10-11 落地):cmake --preset <名>+cmake --build --preset <名>+ctest --preset <名>;cmake --list-presets看全部。六个 preset =release(Release+native,无测试)/dev(+NN_ENABLE_TESTS)/debug(-O0 -g)/portable(NN_ENABLE_NATIVE=OFF,发布/CI 口径)/cpu(NN_ENABLE_GPU=OFF,纯 CPU 最短路径)/probes(+NN_ENABLE_PROBES)。binaryDir=build/<preset名>;另有caller无关的build/test/workflowpreset(cmake --workflow --preset dev一条命令走完)。preset 只描述本仓的构建口径,不改变 CMakeLists 的默认值——不带 preset 的cmake -B build -G Ninja行为与以前逐字相同。 - 探针统一编译门
NN_ENABLE_PROBES(默认 OFF,2026-10-10):凡"在热路径采样/打印/下载/扫描"的诊断代码一律走这个门——库内nn_dbg_scan(NN_F16_DEBUG逐中间量扫描)、NN_PREC_TRACE(变体分派与边界 cast 形状归因note_temp_)、nn::dsl::env_flag/ComputeEngine::prec_env_flag(门关时不求值 getenv、恒 false)、text_train的NN_NAN_TRACE/NN_MEM_STATS,以及探针可执行目标mem_probe/f16_cpu_probe/gpu_stability_probe(门关时连目标都不创建)。门经nn_core的 INTERFACE 定义传播(NN_ENABLE_PROBES=1)→ 所有 TU 与嵌入消费方取值一致(同头两种语义 = ODR 隐患);core_config.hpp把它折成NN_PROBES_ENABLED(0/1)。默认构建里探针不参与编译:没有额外分支、没有字符串、没有 host 下载路径,相关环境变量一律无效——探针只在排障时-DNN_ENABLE_PROBES=ON重新配置,不允许"设了环境变量就悄悄拖慢正常训练/推理"。例外(不进本门):① 初始化期读一次并缓存的行为开关——NN_VULKAN_DEVICE/NN_VULKAN_NO_16BIT_ALU/NN_VULKAN_NO_TIMELINE(设备与特性逃生阀)、NN_POOL_BLOCK_MB/NN_POOL_LADDER_MAX_MB/NN_NO_EARLY_REAP/NN_CPU_PAUSE/NN_GPU_PROFILE(池/调度 A/B),新增同类开关必须"读一次 + 缓存";②f16_writeback_probe是注册在 ctest 的回归用例(断言 in-place f16 写回 + 8 步 Adam 参数必须移动),刻意保留在默认测试门禁里。凡是"覆盖率/归因"类验收(如NN_PREC_TRACE的 V1 覆盖实测)必须在探针构建下复现。 - 作为库被消费(嵌入 3rd_party / FetchContent):门面目标
neuralnet::nn——消费方add_subdirectory后target_link_libraries(app PRIVATE neuralnet::nn)+#include <neuralnet.cpp/nn.hpp>即可(C++26/线程/NN_HAS_VULKAN/SPIR-V 嵌入头目录/Vulkan 链接/shader 生成顺序全部经接口传递)。嵌入时NN_BUILD_APPS默认 OFF(不编本仓 app/工具;顶层工程默认 ON);NN_ENABLE_GPU=AUTO|ON|OFF一行控制 GPU(OFF=纯 CPU 最快路径,ON=缺 Vulkan/glslc 配置期失败);自研dsl::compute的融合注册nn_enable_gpu_fusion(<target> MAIN <main源>)(仅 GPU 启用时有定义;函数体在调用方作用域求值,库内路径经NN_LIB_SOURCE_DIR/NN_LIB_BINARY_DIRCACHE 变量解析,函数内不可用CMAKE_SOURCE_DIR)。样例examples/downstream/(独立工程),指南docs/usage/05-consume-as-library.md。⚠ 两条实测约束:①Vulkan::Vulkan是 imported 目标不可跨目录直接引用(消费方直接链接报 target not found),一律经neuralnet::nn传递;② 库头文件需要-Wno-pass-failed/-fexperimental-library(Clang)与/utf-8(MSVC),已作为接口选项随门面传递——漏传时消费方开-Werror会挂在#pragma clang loop警告上。 - 编译提速(默认已开启,无需额外配置):本工程 header-only,单 TU 17.5s 的大头是重复 parse
nn.hpp(含 44 份原语 SPIR-V 嵌入头 + AOT 融合注册表)。已落地五件事:①NN_ENABLE_PCH——nn.hpp预编译一次,由编译条件相同的目标REUSE_FROM nn_pch共享(本机 GPU+tests 构建实测 48 个候选目标、其中 2 个自带 PCH);②CMAKE_CXX_SCAN_FOR_MODULES OFF——本项目零 C++ 模块,省掉每 TU 一次clang-scan-deps;③scan_exprs的 glslc 并行(NN_SCAN_JOBS=<n>覆盖,=1回串行)+fused_registry.hpp内容稳定写入(内容不变则不动 mtime,靠自定义命令的restat掐断下游全量重编);④ 编译器缓存探测扩到 ccache/sccache(-DNN_COMPILER_LAUNCHER=<path>可指定);⑤ ccache × 共享 PCH 的兼容(2026-10-11)——ccache 手册要求 Clang 侧sloppiness=pch_defines,time_macros+-include-pch+-fno-pch-timestamp三项齐备才肯缓存"用了 PCH 的 TU";前两项由构建系统用cmake -E env钉进RULE_LAUNCH_COMPILE前缀(不依赖用户全局 ccache 配置),第三项(cc1 开关,必须-Xclang形式)自动加到 PCH 生成方与全部使用方。修复前每一个用 PCH 的 TU 都记 "Could not use precompiled header" → 42+ 个目标的编译全部不可缓存(缓存形同虚设却看不出来);修复后同一个 TU:30.2s → 0.11s(Release,命中缓存)。顺带删掉RULE_LAUNCH_LINK(本项目不做 LTO,ccache 包链接只有开销)。实测(Debug/本机):全量净构建 125.5s→72.7s、改单个源文件 43.9s→10.4s;Release+tests+GPU 全量冷构建 13m26s / 8 核(单个 TU ≈30s、PCH ≈54s;-ftime-trace归因:后端 29.1s vs 前端 4.0s —— PCH 之后的大头已是 LLVM 优化/代码生成,不是头解析;口径与复现见docs/development/cmake-integration.md§8)。⚠ 两条不变量:(a) 收集器目标(scan_exprs、*_nnfusion_collect)不套 PCH——前者在源文件内、include 之前#define NN_EXPR_SCAN,PCH 会先于该宏展开nn.hpp→ DSL 收集钩子失效(注册表空);(b) 新写"生成物参与 PCH"的场景必须显式add_dependencies(<pch_target>, <生成边>)——__has_include在 PCH 里一次性求值,早于生成完成就整块落空且因无 depfile 记录而永不重试。 - Vulkan 可选:CMake 自动探测 Vulkan + glslc,找到则定义
NN_HAS_VULKAN启用 GPU,否则纯 CPU。支持多 Vulkan 设备选择(--gpu参数,见cli/cli_gpu_option.hpp)。 - 本机 GPU 测试(NVIDIA GeForce GTX 850M,Maxwell / 驱动 580.178.04 / Vulkan):① 设备节点
/dev/nvidia*在受限沙箱内不可见 → GPU 测试必须在非沙箱(提权)环境执行,否则只会落到 llvmpipe(软件光栅,慢且 f16 行为不同);② 显式选卡NN_VULKAN_DEVICE=850M(等价 CLI--gpu=850M),不写也会被自动选中(打分:独显 3 > 核显 2 > llvmpipe 1),显式写只是免歧义;③ Maxwell 无shaderFloat16(f16 存储可用、f16 ALU 走回退)→ f16 分派变体(V1/V2)的权威覆盖仍以 40HX(ethan@192.168.1.102)为准,850M 适合跑"接线/闭合世界/三值正确性"这类冒烟与回归。 - 本项目不支持 CUDA:后端仅 CPU / Vulkan,CLI 无
--cuda参数;文档勿声称支持 CUDA。 - 应用入口:
build/{mnist_train,mnist_infer,text_train,text_infer,tokenizer_train,tokenizer_infer,dataset_gen,dataset_convert},另有layer_bench(性能)与测试类可执行文件。gui.py是 Python GUI,train_pkg.py打包.nnpkg训练包,cli_controllers.py提供 CLI 控制逻辑。
| 任务 | 文件 |
|---|---|
| 加/改神经网络层(Linear/Attention/Norm/激活…) | compute_layer.hpp(聚合头)+ compute_layer_{base,mlp,bitlinear,conv,softmax,attention,feedforward,transformer,gpt,rapt}.hpp(bitlinear = 三值权重 BitLinear) |
| 加/改位置编码(Learned/Sinusoidal/RoPE/ALiBi/无) | compute_position_encoding.hpp(PositionEncoder 基类 + 子类 + 按注入点分开的两个工厂)+ compute_layer_attention.hpp 的 AttnScoreMask 族(掩码语义) |
| 加/改损失函数 | compute_loss.hpp |
| 加/改优化器(SGD/Adam/AdamW/AdamWQuant/Muon) | compute_optimizer.hpp(AdamWQuant = 量化状态 AdamW:状态 f16 + 按行有界尺度,4 B/权重 vs 8 B)。名单唯一真源 = 该文件的 kOptimizerTable(RV-04,2026-10-11):CLI 帮助/校验(nn::find_optimizer / nn::optimizer_names)、scan_exprs dry-run、mem_probe 状态账、GUI 下拉(运行时从 CLI --help 派生 + bench/gui_cli_audit.py 第 [4] 节断言)全部查表——新增优化器只需表 1 行 + create_optimizer 1 个分支) |
| 加/改引擎原语(CPU 实现) | compute_engine.hpp(接口)+ compute_cpu_engine.hpp |
| 加/改引擎原语(GPU 实现) | compute_gpu_engine.hpp + backend/compute_vk_backend.hpp + backend/compute_vk_device.hpp + shaders/*.comp。GpuBackend 成员体已按主题拆成 4 个片段(backend/compute_vk_backend_{destroy,frame,pipelines,ops}.inc,由主文件在 class 体内按序 #include;纯移动,片段不可单独编译,改动按其主题选文件);原语 SPIR-V getter 由 NN_SPV_GETTER_LIST 单一注册表展开(新增 shader = 文件头加探测块 + 列表加 token,见 RV-08) |
状态扫描原语(RLA/RAPT 的 scan_prefix_outer) |
shaders/scan_prefix_outer.comp(行归属 + 4 路展开,专用路径 dk ≤ 64)+ 同一份源码以 -DNN_SCAN_DK_MAX=32 编出的窄共享变体(每 SM 驻留 6 个 WG 而不是 1 个;C++ 按 dk ≤ 32 选、缺则回落宽变体)。scan_suffix_outer 刻意保持原结构(它是 D 的 dk²×seq 列式散布读主导的流量/争用受限 kernel,加占用率反而更慢) |
算子级配对 A/B(layer_bench) |
src/layer_bench.cpp:--op matmul,matmul_q,matmul_q_bw,scan_prefix,scan_suffix…(--f16 走 f16 变体)+ --dk <n>(扫描原语 head 维)+ --hash(计时之后打印输出张量的位级 FNV-1a 64 —— 逐位不变的 kernel 改写必须哈希相同;放计时前会污染 ms) |
| 张量/设备抽象 | compute_tensor.hpp |
| 按需 include(不走聚合头) | engine.hpp / layer.hpp / loss.hpp / optimizer.hpp / model.hpp / train.hpp = 薄转发头(nn.hpp 仍是唯一聚合头;只求好记,不省 parse) |
| 混合精度 / f16 类型系统 / 三值(T1_58) | precision.hpp(Precision 枚举含 T1_58、precision_rank()、nn::f16、PrecisionProfile);层能力声明 PrecisionSet/PrecisionSupport + Layer::precision_support()/layer_name()(compute_layer_base.hpp);三值打包布局 ternary_pack.hpp;打包 GEMM 原语 trit_pack/matmul_q(_t)(compute_engine.hpp + CPU/GPU 实现 + shaders/{trit_pack,matmul_q,matmul_q_gemv}.comp) |
| 矩阵/代数层(CPU 存储与手写内核) | algebra_matrix.hpp / algebra_span.hpp / algebra_ops.hpp(Expression/BoolExpression 概念在 expr_dsl.hpp) |
| 表达式 DSL / 融合 IR | expr_dsl.hpp / expr_spec.hpp / expr_opt.hpp / expr_registry.hpp / expr_graph.hpp(IR-C 图融合 = 现行能力:expr_graph.hpp + begin_expr/end_expr + dsl::start_expr/end_expr + GPU 图执行;P1 链/P2 写穿多输出,已接入 Adam/AdamW step) |
| 后端代码生成(IR-D emitter 抽象) | expr_emitter.hpp(注册表)+ expr_glsl_gen.hpp(GlslEmitter) |
| 模型容器/规格/序列化 | model_container.hpp / model_spec.hpp / model_serialization.hpp / model_keyvalue_record.hpp |
| 训练生命周期容器(推荐入口) | training_session.hpp:TrainingSession 拥有 Model + Optimizer + TrainingSessionState(optimizer 先于 Model 析构),train_step 处理单 Tensor 输出,train_named_step 处理 GradientBundle/具名多输出;两者共享累积/归一化/update 语义。不拥有数据/epoch/lr 曲线/断点策略(docs/todo/30) |
| RL 与可组合训练 | rl.hpp 薄聚合;tensor_schema.hpp/named_tensor_set.hpp、compute_layer_bundle.hpp、rl_experience.hpp、rl_policy.hpp、training_group_session.hpp、rl_pipeline.hpp。指南 docs/usage/06-reinforcement-learning.md |
| 训练工厂(数据集 → global batch → 循环) | text_trainer.hpp:nn::TextTrainer(create(session, dataset, TextTrainConfig) → fit();global→micro batch 切分、shuffle/lr 调度/非阻塞 loss 回读/测试评估/TrainState 与断点状态装配;Step 回调承载文本 sparse-CE)+ text_train_config.hpp(nn::TextTrainConfig = 训练配方唯一真源)+ lr_scheduler.hpp(update-step 级 lr) |
| MNIST / GPT / CNN / RLA / 分词器 模型工厂 | domain_mnist.hpp / domain_gpt.hpp / domain_cnn.hpp / domain_rla.hpp / domain_tokenizer{,_base,_bpe,_charbpe}.hpp |
| 训练/推理 CLI 入口 | src/mnist_train.cpp 等;公共 CLI 逻辑在 include/neuralnet.cpp/cli/(cli_help.hpp = 各 --help 的统一排版器:宽度感知对齐、统一版式;改帮助/加选项前先读其文件头的审计约定)。text_train 已模块化(P1-1)且训练循环已上收库层(P2b):cli/text_train_config.hpp(TrainConfig : nn::TextTrainConfig + --help + argv 解析 = CLI 参数解析层,唯一允许 std::exit)、cli/text_train_loop.hpp(只装 CLI hooks:梯度日志/探针/.nnckpt 写盘 → 调 TextTrainer::fit())、cli/text_train_recovery.hpp(--resume)、cli/text_train_checkpoint.hpp(.nnckpt/.nnmodel 产出 + 退出码语义);src/text_train.cpp 只留接线与唯一出口 main,三个模块不得终止进程(经 Result 上抛;门禁 bench/doc_inventory.ps1 第 [6] 节) |
推理采样(top-k / top-p / 重复惩罚 / 种子)+ 多轮对话(--turn/--reply-out/--system) |
采样:compute_layer_base.hpp(SamplingConfig + 共用 sample_next_token_,host 侧采样、不进 AOT 闭合世界)+ compute_layer_{gpt,rapt}.hpp 的 generate(配置重载;旧 temperature 重载 = 全默认语义)+ src/text_infer.cpp 的 --top-k/--top-p/--repeat-penalty/--repeat-window/--seed。多轮:prompt 构建唯一真源 = domain_tokenizer_base.hpp 的 Tokenizer::chat_prompt(ChatTurn + 对话标记模板 + 按整轮截断到 seq_len);CLI 侧 --interactive(进程内累积)与 --turn(历史由调用方自带,一次一问)+ --reply-out(回复落盘,GUI 取回复不解析日志);GUI 侧 cli_controllers.py 的 ChatSession + gui.py 的「GPT 推理」页多轮面板;门禁 chat_prompt_test + bench/gui_cli_audit.py 第 [7] 节 |
统一数据集(.nndataset/.nnvocab/KVRecord v2) |
dataset.hpp(nn::Dataset 只读类 + 生成期公共件;存储单位 = doc,DatasetSubset{Train,Test})/ dataset_samples.hpp(训练单位 = 样本:TextSample/TextSampleSet——切窗 + batch 装配;steps_per_epoch(global_batch_size) 是 optimizer update 数的派生量)/ dataset_format.hpp(文件标签/词表 blob/.nnvocab 编解码)/ model_keyvalue_record.hpp(KVRecord v1+v2);生成/转换 CLI 在 src/dataset_gen.cpp / src/dataset_convert.cpp(设计与裁决见 docs/todo/19-unified-dataset.md) |
训练 step 剖析器(NN_PROFILE=1 门控) |
core_profile.hpp(聚合器:帧栈 inclusive/exclusive 双口径、stage::op 聚合、NN_PROFILE_TRACE Chrome trace、device_add 设备侧回填(含 per-key 归因表)、interned/interned_detail 动态名驻留);插桩点:compute_engine.hpp 42 个 NVI 入口 + expr_dsl.hpp 三入口(CPU 模板路径不经引擎,须单独计时)+ 两引擎 public-virtual override + text_train phase + Model/GPT/RAPT/Transformer 逐层逐块 scope + compute_vk_backend.hpp timestamp query(设备侧 kernel 表 / per-key 归因 / GPU busy%) |
| 构建期工具(AOT 融合) | tools/scan_exprs.cpp(单步 = 收集 + 生成)/ tools/fused_generate.hpp(生成阶段,原独立工具 gen_fused;另有 tools/decode_fused.py 调试用)。库外使用者:CMake 函数 nn_enable_gpu_fusion(<target> MAIN <含 main 的源>)(定义在 CMakeLists.txt,样例 examples/fusion_custom_layer*) |
批量改写 / 一致性审计(改多处时用,均带 -DryRun) |
tools/edit_ranges.ps1(行区间删除:四重断言 + 花括号平衡护栏)/ tools/test_refactor.ps1(删定义块 / 插 include / 正则替换)/ tools/apply_nn_try.ps1(auto X = f(); if (!X) … → NN_TRY/NN_TRY_CHECK,L2 层,带 -DryRun 计数)/ tools/doc_rename.ps1(文档词法改名)/ bench/doc_align_audit.ps1(文档↔代码对齐审计:文件/符号/CLI/数字/测试名)/ bench/doc_inventory.ps1(引擎接口盘点 + 第 [4] 节 L2+ 分层审计:Matrix/Matrix 型 I/O 动词零命中门禁,铁律 #12 + 第 [5] 节手写 return std::unexpected 残留门禁,基线 0) |
| 与 PyTorch 对拍 | compare_with_torch/(model.py / text_train.py / text_infer.py) |
GUI ↔ CLI 一致性(改 gui.py/cli_controllers.py/任一 CLI 选项后跑) |
bench/gui_cli_audit.py(七节:GUI→CLI flag 差集 + 反向缺失、幽灵选项=帮助声明 vs 解析分支、gui.py 的 self.X 赋值静态校验、优化器名单、声明式字段表 ↔ Controller 闭包(P2-6)、断点续训禁列(P1-2)、多轮对话闭环(P0-5 尾项);退出码 0 = 无偏差。复现:python bench/gui_cli_audit.py) |
| 加/改 GUI 参数行(P2-6 声明式表) | gui.py 各 Tab 的 fields() —— 一个 Field = 一行 UI + 一条 collect_args 语义(Field/collect_fields/ParamsMixin 在文件中部);表表达不了的条件语义(engine→--gpu、精度预设→--f16/--t1_58、输入方式→input)留在该 Tab 的 collect_args 后处理里,并用 _v()/ui_only=True 声明给门禁 |
| 参与/贡献入口(给非作者:构建/测试/规范/PR/CI 口径) | CONTRIBUTING.md + CODE_OF_CONDUCT.md;预设构建 CMakePresets.json(cmake --preset dev/portable/cpu/probes) |
| 可引用的实测结论(结论/证据/重跑命令/边界) | research/(索引 research/README.md;一次研究一个子目录)。代码/设计稿引用这些结论时按四段式引用 |
| 代码风格(机器可读) | .clang-format(目标风格,用 git clang-format 只格式化自己的改动行;不要整仓重排)+ .editorconfig + .gitattributes(全仓 LF);规则文本见 docs/todo/10-development-standards.md §8 |
graph TB
subgraph "L5 用户入口层"
A["mnist_train/infer"]
B["text_train/infer"]
C["tokenizer_train/infer"]
D["gui.py"]
end
subgraph "L4 领域构建层"
E["domain_mnist.hpp"]
F["domain_gpt.hpp"]
G["domain_tokenizer*.hpp"]
F2["domain_cnn / domain_rla.hpp"]
end
subgraph "L3 实现层"
H["model_container.hpp"]
I["model_spec.hpp"]
J["model_serialization.hpp"]
end
subgraph "L2 计算层(引擎化)"
K["compute_engine.hpp"]
L["compute_cpu_engine.hpp"]
M["compute_gpu_engine.hpp"]
N["compute_layer*.hpp<br/>(含三值 BitLinear)"]
O["compute_loss.hpp"]
P["compute_optimizer.hpp"]
P2["precision.hpp<br/>F16/F32 + T1_58(三值)"]
end
subgraph "L1 代数层"
Q["algebra_matrix.hpp"]
R["algebra_span.hpp"]
S["algebra_ops.hpp"]
end
subgraph "L0 硬件层"
V["core_config.hpp"]
W["core_threadpool.hpp"]
X["core_errors.hpp"]
end
D -->|subprocess| A & B & C
A & B & C --> E & F & G & F2
E & F & G & F2 --> H & I & J
H & I & J --> K & N & O & P
K --> L & M
N & O & P --> Q
P2 -. "精度配置(含 param=T1_58 → BitLinear)".-> K
P2 -. "层能力校验(init 期,按槽)".-> N
Q --> R & S
Q --> V & W & X
引擎化(Engine-Based):Layer 的 forward/backward 只写一次,通过 ComputeEngine& 参数自动适配 CPU/GPU。
算法与原语分离:
- Engine 只提供 op-level 原语(
matmul/add/exp/reduce…) - Layer 用原语组合表达算法(
ReLU = max(x,0)) - Engine 不知道 "ReLU" 是什么
数据流:
Matrix → engine.from_matrix → Tensor[GPU] → forward/loss/optimizer 全程在 GPU → 仅 evaluate 时 to_matrix 回 CPU
多精度:Precision(F16/F32 为存储/舍入精度;BF16/F64 是保留值,使用即报错)贯穿张量存储与算子输出;PrecisionProfile{param/compute/stable/optimizer} 做模型级配置。语义锚点见 precision.hpp 头注释与 docs/development/05-mixed-precision.md。
三值权重(T1_58,1.58-bit):Precision::T1_58 是离散化而不是舍入——它把权重按逐行 absmean 尺度投影到 {-1,0,+1}、需要额外尺度 γ,且不进提升序(precision_rank() 无定义,max_precision 对它 fail-fast)。P1 里它不是存储精度(precision_bytes = 0;create_tensor/from_matrix/dsl::compute 一律拒绝拿它建张量/当输出),只作为 PrecisionProfile.param 的一句声明 + 层能力集合的取值。落地形态:每个 Layer 按槽声明自己支持的精度(PrecisionSupport{param,compute,stable,optimizer},默认 {f16,f32}×四槽 → 既有层一行不改),Layer::init(NVI 首步)做唯一校验,不兼容组合 = 初始化期报错(层名 + 槽位 + 取值 + 允许集合);set_precision_profile 在已 init 时 fail-fast("profile init 后不可变")。谁声明什么:BitLinear(三值线性层:量化 forward + STE backward,latent 恒 f32)param={T1_58}、其余层默认。CLI 只有 --precision-param t1_58(无别名;compute/stable/optimizer 收它即解析期报错),ModelSpec.weight_quant 记住"该模型用 BitLinear"(缺键 = None → 旧文件零破坏)。接线范围(P1 = MLP,P1.5 = GPT):GPT 的 4 个投影 / fc1_/fc2_ / 未 tie 的 lm_head_ 全部走 make_linear_layer 工厂(成员类型 std::unique_ptr<Layer>,与 make_norm_layer 同一先例),开关判据唯一 effective_weight_quant()(precision.param == T1_58 ⇔ weight_quant == T1_58),profile 由 split_ternary_profile() 分流(param=T1_58 只给 BitLinear;复合层自身 / norm / 位置编码拿 param 归一化为 f32 的那份;未以三值模式构造却收到 T1_58 → fail-fast,不静默降级);RAPT / ViT 明确拒绝(--arch rapt --precision-param t1_58 前置报错;ViT 在 PatchEmbedding 能力校验处报错)。GPU 侧结构由 scan_exprs 的模型 pass 登记(含两条三值 GPT 用例;结构数 107)。完整设计/架构图/验收见 docs/todo/21-quantized-weights.md §4.10 与 docs/todo/22-bitnet-2b4t-architecture.md §3.4。
三值精度预设(CLI --t1_58,2026-10-10 落地):profile_t1_58() = {param=T1_58, compute=F16, stable=F32, optimizer=F32} —— 定义上就是 profile_f16() 把 param 换成三值(另三槽在同一函数里派生 → 不会与 --f16 各自漂移),即"其他默认 f16、模型参数 1.58";CLI --t1_58、GUI 两个训练页的"精度预设"下拉各一项,三条命令等价:--t1_58 == --f16 --precision-param t1_58 == --precision-param t1_58 --precision-compute f16。实测验收见 docs/todo/21-quantized-weights.md §4.10。
三值性能优化(2026-10-11 落地,docs 21 §4.12)——改三值相关代码前必读:
① 写版本(write epoch):Tensor 的存储对象(MatrixT / GpuBuffer)持单调
write_epoch,ComputeEngine 的每个写入口首行放 WriteEpochGuard(返回即 +1,
失败也 +1);DSL 在 CPU 上绕过引擎直写 dst(eval_into_tensor_cpu)必须经
TensorAccess::mark_written 补记 —— 新增写入口/新的直写路径时漏掉 Guard 就会让
BitLinear 的量化缓存静默用旧权重(src/write_epoch_test.cpp 逐入口断言 + 只读路径
不推进 + 别名可见)。BitLinear 只在 latent_w_.write_epoch() 变化时重算 τ/wq
(quantize_calls() 可观测),覆盖梯度累积 / 逐 token 生成 / 激活重计算三种重复调用。
② 打包权重(P2):trit_pack / matmul_q / matmul_q_t 是 op-level 引擎原语
(不进 DSL 闭合世界、不需 scan 登记),布局 = 2 bit/权重 + 4 权重/字节 + 码值 = 三值 + 2
(与 microsoft/BitNet gpu/pack_weight.py 一致;不做其 CUDA interleave),容器是 f32 张量
当原始字(布局/编解码唯一来源 ternary_pack.hpp;base-3 1.6 bpw 只是 .nnmodel
的盘上编码,读盘后一律转回 2-bit 容器)。引擎是否具备打包路径是引擎私有事实
(GPU = 两条 pipeline 都在;对外只经 describe().ternary_gemm 披露,见铁律 13)。
③ 打包是唯一的权重缓冲形态(训练与解码同一条路):BitLinear 恒用打包缓冲,
不查引擎能力(不支持的引擎在 trit_pack/matmul_q 处 fail-fast);
set_ternary_pack_enabled() 仅作 A/B 与回退的强制开关。分块版 matmul_q.comp 的两条不变量:网格随分块常量(BM=BN=64)、
轴向 x↔输出列 N / y↔输出行 M(与 matmul_tiled/batched_matmul 同约定)——
写反只会在 M≠N 的形状上错值(方形小形状恰好正确,是 WIP 卡住一轮的根因)。
N ≤ 8 且 transA=0 走 matmul_q_gemv.comp(subgroup = 4 行、subgroup 内沿打包字
分片、subgroupAdd 每 (行,列) 一次 → 无 barrier/shared;host 侧 grid.x = ceil(M/(4·(256/subgroup_size))) 是布局契约)。CPU 侧 matmul_q_impl 走行块解码
(RB=64)+ 稠密微内核(multiply_to/transpose_multiply_to)——逐元素直算比稠密慢
~70×,禁止回退到那种写法。动这块前先量:layer_bench --op matmul,matmul_q,matmul_q_bw --f16 --inner N 的配对 A/B + 端到端(训练/解码/显存/CPU)口径见 docs 21 §4.14。
④ 模型文件角色(v8,已取代原 NNQT 独立格式,见 docs 23):一份格式、两种角色 ——
*.nnmodel(推理:三值层写打包形态,盘上 base-3 1.6 bpw + τ)与 *.nnckpt
(训练断点:写 f32 latent)。文件头 = magic|version=8|精度|role 1B|spec;参数区是
参数级 kind 条目,由拥有该参数的层经 Layer::export_param / import_param 决定
形态(参数形态协议);spec 之后是 role 门控的训练状态段([state_len][TrainState kvrec] [优化器状态张量条目],Model 角色写 0 长度;内容 = 计数器 / 优化器 t / 训练配方含精度
四槽 / 数据集身份,include/neuralnet.cpp/train_state.hpp),文件尾是 整文件校验和
[sha256 32B]["NNEND" 5B](P2-1:verify_model_integrity 在 load_model / load_train_state
里先行校验,截断/损坏明确报错)。载入 .nnmodel 后三值层的载入意图(nn::WeightLoadIntent,
P1-7,2026-10-16)决定形态:Inference(默认)= 部署只读态(只有打包字 + τ,无 latent ⇒
backward() 返回明确错误;parameters() 不再 abort);Trainable = 按文件 τ 反量化成 f32
latent(序列化层宿主侧逐行解码,ternary_pack.hpp 的 latent_row_scale/dequant_row),层再用文件
τ 播种量化缓存 ⇒ 与文件里的模型首次 forward 逐位一致且可反向/可更新 —— 这是三值模型的
SFT / QAT 续训入口(训练侧 CLI 一律传 Trainable)。CLI:--save(默认 *.nnmodel)写推理角色、
--save-interval 写 *.nnckpt;--resume 的 role 语义(P1-2 第三刀,2026-10-16):
.nnckpt ⇒ 断点续训,形状/配方/计数器一律由状态段决定,显式给出 A/B 类旗标即报错
(禁列表 nn::cli::kForbiddenResumeFlags;仍允许 --save/--save-interval/--log-interval/
--max-steps/--gpu/--grad-log/--checkpoint-every/--activation-offload/--flush-interval),
数据集身份(sha256 + 网格三元组)不一致硬报错(无逃生阀,出口 = 从 .nnmodel 开新一轮);
.nnmodel ⇒ warm start(SFT 入口,CLI 全权、优化器冷启动;P1-7 起三值 .nnmodel 同样放行——
原"必须拒绝"门禁与 check_resume_target 已随 P1-7 删除,不再存在被拒组合);
v5/v6/v7 老 .bin/.nnmodel 仍可读(v7 无状态段 ⇒ 冷启动语义)。
断点状态段 = train_state.hpp 的 TrainState(含精度四槽与 lr 曲线形状)+ Optimizer::save_state/ load_state(含 Adam 的 t);读写 API = CheckpointState / peek_train_state / load_train_state。
text_infer / mnist_infer 用 peek_any_model_spec + load_any_model + peek_model_role。
Layer::sub_layers() 已删除(层树枚举没有消费者);参数下标转发走受保护的
param_children()(仅供参数协议,不是对外结构契约)。
⑤ 梯度惰性分配:Linear / BitLinear 的梯度只在 param_gradients() /
backward() 首次触及时建(grad_buffers_allocated() 可见);新增层若也用
ensure_grad_() 模式,注意返回 void 的函数里用 NN_UNWRAP 而不是 NN_FAIL
(后者是 return std::unexpected 语义,-Wreturn-mismatch 直接编译失败)。
| 类别 | 原语 |
|---|---|
| 矩阵级 | matmul/batched_matmul/matmul_with_bias/transpose/add_inplace/accumulate/scale_inplace/zero |
| 归约级 | row_reduce_sum、col_reduce_sum/max、grouped_reduce_sum/max(沿行方向按固定长度 R 分组归约,池化/多头等场景免逐通道循环) |
| 数据操作 | slice_rows/insert_rows/gather_rows/scatter_add_rows/rearrange_3d/im2col/col2im/clone/copy_from/cast(im2col/col2im:卷积/池化窗口展开与伴随散射,纯数据搬运) |
| 扫描级 | scan_prefix_outer/scan_suffix_outer/outer_col(RLA/RAPT,dk≤64,见 docs/development/06 §扫描原语) |
| 表达式 | eval_expr/eval_expr_into/eval_expr_reduce(AOT 融合 shader 入口) |
| 批次/内存 | begin_batch/end_batch/flush_batch/release_idle_pool_blocks/pool_stats |
| offload | create_offload_buffer/offload_save/offload_restore(activation offload) |
引擎的 virtual 方法计数以审计输出为准:
bench/doc_inventory.ps1的virtual methods行为 52 declarations / 52 unique(两条线合并后的实测值:dev 侧库级清理把 6 个能力谓词supports_native_data_move/supports_native_f16_reduce/supports_expr_precision_variant/supports_ternary_gemm/supports_int8_dot/int8_dot_accelerated从基类删除(49/49; 能力改由describe()披露),main 侧 IR-C 恢复新增begin_expr/end_expr+ P2 多输出eval_expr_multi_into_impl(+3);trit_pack_impl/matmul_q_impl/grad_buffers_allocated保留;另:grep -c "\bvirtual\b"原始计 54 处 = 52 方法 + 析构 1 + 注释里的词 1); 逐元素/广播等计算类原语一律经表达式 DSL 求值,不经 Layer 直调(dsl::row_reduce_max是 DSL 叶子,不是引擎算子)。CRLF/scan_exprsAOT 键规则见 §7。
- 先理解 L0-L1(基础数据结构):
Matrix、Tensor、Scalar - 再理解 L2 核心:
ComputeEngine接口 + 一个简单 Layer(如Linear) - 然后理解 L3:
Model容器如何组合 Layer - 最后理解 L4-L5:具体模型实现和训练流程
- 禁止 throw/try/catch:编译期
-fno-exceptions强制。错误一律Result<T> = std::expected<T, Error>(core_errors.hpp)。手写错误检查一律改用宏族(三种语义,完整定义与约束见core_errors.hpp宏注释):传播NN_TRY(decl, expr)/NN_TRY_MSG(decl, "语境", expr)/NN_TRY_CHECK(x)(L2 层已收敛,展开后与手写auto r = expr; if (!r) return std::unexpected(r.error());逐字等价);解包+终止NN_UNWRAP(expr[, "语境"])(别名NN_UNWRAP_1,失败打印后 abort,也接受 bool 条件);解包+退出码NN_EXIT(expr, code[, "语境"])(CLI/参数错误用它保住退出码);错误源头语法糖NN_FAIL("msg")。终止策略只有 abort/exit —— 本族任何路径都不出现throw。 前置条件校验(core_assert.hpp,RV-01,2026-10-11):NN_ASSERT_ALWAYS(cond, msg)/NN_FATAL(msg)—— 语义与构建类型无关(NDEBUG 下同样打印file:line后经detail::fail_abort终止)。判定口径:凡断言后仍会使用被校验对象(解引用/索引/除零/继续按错误形状算)的点位,一律用这两个宏;NN_ASSERT是"可选诊断"(NDEBUG 展开为空),只留给"失败也不影响后续正确性"的检查(当前全仓已无NN_ASSERT(...)调用点)。负例回归src/assert_always_test.cpp。 - 禁止 new/delete/裸指针所有权:
std::vector/std::unique_ptr/std::span。 - 分层职责单一:Matrix(L1)不写神经网络算法;Layer(L2)不写底层计算;原语 shader 永不含算法(ReLU/Softmax/Attention 等一律来自 Layer 或 DSL)。
- 不穿透接口:上层不访问下层内部数据结构(
.data()等),改一个模块只改一个头文件。 - 布局全局统一 batch-major:序列展平列序
i = b*seq + t(batch 在列方向)。position-major(i = t*batch + b)会造成跨样本串扰,任何情况下不得混用。所有注意力/序列相关测试必须覆盖 batch>1(batch=1 时两种布局重合,测不出)。 - GPU 命令录制生命周期:录制期(
begin_batch→end_batch之间)引用的所有张量必须存活到end_batch()之后;GPU buffer 销毁走pending_destroys_延迟队列。 - AOT 闭合世界:GPU 表达式 shader 全部构建期生成,运行时按
expr_spec_key精确匹配,未命中硬报错,无 eager、无运行时编译。 - 确定性:任何"依赖容器迭代顺序"的决策点(BPE 平局打破、ID 分配等)必须显式排序/按 key 打破平局;并行化后结果必须与单线程逐字节一致。
- 大词表禁止物化 one-hot:用
CrossEntropyLoss::forward_sparse(整数标签 + loss_mask)。 //注释中禁止出现\反斜杠(尤其行尾):GCC 行拼接会把下一行并入当前注释——轻则-Wcomment -Werror编译失败,重则下一行代码被静默吞掉。- 张量存储不可绕过引擎(M1,docs/todo/17 §4.1):一切 Tensor 的创建/读写/reshape 必须经 ComputeEngine(
create_tensor/from_matrix/to_matrix/reshape等);存储访问器(cpu_matrix/cpu_shared/gpu_tensor/gpu_shared)与静态直构工厂(Tensor::from_matrix/cpu/...)已私有,绕过 = 编译错误。库内豁免仅两条:引擎 friend(ComputeEngine/CpuEngine/GpuEngine)与detail::TensorAccess(DSL 求值器专用通道,库外禁用,违反靠 grep 审计抓)。 - L2+ 计算路径禁用 Matrix(M4,docs/todo/17 §4.6 + §3 D11):Layer/Loss/Optimizer/Model 头文件里不得出现
Matrix/MatrixT类型,也不得调用 Matrix 型 I/O 动词(from_matrix/to_matrix/copy_from)。层自算的辅助数据(索引/位置编码/掩码/斜率/编码表等——没有引擎侧生成原语的那类)一律走 span 宿主桥detail::upload_span/download_span/download_vector(内部是 M3 的批量write/read,见compute_engine.hpp尾部)或create_tensor+InitSpec/zero就地建张量;数据集、预训练权重、对拍与落盘等大批量 I/O 仍在 I/O 层用from_matrix/to_matrix。审计口径:pwsh -File bench/doc_inventory.ps1第 [4] 节,验收 =L2-VIOLATIONS: 0(宿主桥用量只披露不判违规)。 - 引擎能力体系(docs/todo/23 §2.3):能力谓词是具体引擎的私有实现细节(私有成员 / 编译期常量),不得出现在基类
ComputeEngine,也不得被 Layer/L4/L5/CLI/测试调用;基类 NVI 入口不做能力分支(精度策略下沉到各引擎的*_impl)。能力信息只经ComputeEngine::describe()返回的EngineInfo披露(text()是稳定一行摘要),且禁止用于选路——选路必须走引擎内部谓词。热路径政策:由引擎类别决定的(如 CPU 引擎没有原生 f16 搬运/归约)用编译期标签消分支;由设备决定的在 init 期解析成常量成员;每结构可变的(有无该表达式的融合变体)不当能力用,由实现内部一次解析。 - 参数形态协议(v7,docs/todo/23 §2.2):模型文件的每个参数条目带参数级
kind(ParamKind:Dense / TernaryLatent / TernaryPacked2 / TernaryPacked3 / 预留 I8Packed4),由拥有该参数的层经Layer::export_param(index, ModelRole)/import_param(index, entry)决定落盘/载入形态;index与parameters()同序(复合层经受保护的param_children()按顺序转发)。禁止在序列化层dynamic_cast具体层类型;禁止恢复层树枚举型公开 API(sub_layers()已删)。形态不被接受必须 fail-fast(层名 + 期望 + 实际),不得静默降级。
Matrix: 行主序 (rows, cols),data_[row*cols + col]
神经网络张量: 列主序 batch-major
输入 (feature_dim, batch_size) 每列一个样本
权重 (out_features, in_features)
多头 Q/K/V: (H*d_k, batch*seq) → rearrange_3d → (batch*H*d_k, seq)
GPT 序列展平: 列序 i = b*seq + t(batch-major,全局唯一约定)
- Layer 内用
nn::dsl(expr_dsl.hpp)写普通数学表达式;CPU 编译期模板直接求值(内联+SIMD),GPU 折叠成ExprSpec(扁平 IR,expr_spec.hpp)→ 按 key 查预编译融合 shader。 - 主要入口:
dsl::compute(engine, expr, rows, cols)(一行表达式,最常用——输出形状可省,推导口径见下条 bullet);把结果写进既有张量(原地更新,零分配)用dsl::compute_into(engine, expr, dst);归约语义用dsl::compute_reduce。跨表达式融合(IR-C)为现行能力、已接入训练路径:engine.begin_expr()/end_expr()+ExprSegmentRAII 守卫 +expr_graph.hpp图融合分析(dsl::start_expr/end_expr= 单表达式跨行语法糖,不是跨表达式)。融合两条规则:P1 链(单输出;两节点均逐元素/无归约/同形状/同精度,tail 恰一个消费者且被 Linear 视图引用,matmul 头链可用)与 P2 分量(纯逐元素 F32 同形状节点按依赖边连通成组,多消费者/菱形不再是边界,写穿全部成员输出 =ExprSpec.extras(≤4);预算超限分裂多 kernel);kernel 按确定性稳定拓扑序执行。GPU 真录制(execute_fused_graph,复合 kernel 直接写回 tail 占位存储);CPU 普通运行 no-op(数值不变),NN_EXPR_SCAN下end_expr跑同一融合分析并登记复合 spec(闭合世界两端一致)。已接入Adam::step/AdamW::step(每张量 1 个多输出 kernel)。P3 跨链批量派发:execute_fused_graph把同结构、同参数值、互不依赖的 kernel 按组键(结构 key + consts/rparams/vp 值序列;贪心按列表序收集、单组 CAP 32)合成一次批量派发——纯逐元素结构(含 extras 复合)生成期追加key#b批量变体(实例表 + buffer device address,逐实例尾槽 (count,cols)),后端run_fused_gpu_batch;NN_IRC_NO_BATCH=1关闭批量(A/B 逃生阀)、NN_IRC_TRACE=1打[ir-c][batch]/[ir-c][batch-degrade],#b未登记/无 BDA = 逐 kernel 降级回退(可观测,非错误)。⚠ 闭合世界与精度的交互:融合分组依赖精度(P2 要全 F32、P1 要同精度),混精度 profile 会产出 f32 扫描期未登记的复合结构——不做构建期精度组合枚举,而是运行期通用降级:复合 kernel 未登记(或写穿前置条件不满足)时拆回成员逐个派发(成员都是已登记的个体结构;NN_IRC_TRACE=1可见[ir-c][degrade]),未登记的单节点结构仍是硬报错。历史与取舍见docs/history/history.md「IR-C 定位修正与恢复立项」条。设计原则(用户裁定):不为单一操作加专用原语——Adam 多状态更新、CE online-softmax 等一律走通用机制(图融合扩展 / fold 通用折叠)。分组归约(MaxPool 窗口等)用dsl::grouped_reduce_sum/max(tensor, R)视图(R 走运行期视图参数vp、不进 key——同一结构不同 R 共享一个 shader);alpha 缩放写成dsl::matmul(..., batch) * dsl::rparam(alpha)尾链(rparam 值不进 key)。 dsl::compute/dsl::compute_reduce可省输出形状:输出网格由表达式推导(expr_dsl.hpp的GridInfer:张量叶/形状保持视图按广播规则合并;归约视图与归约指令贡献输入整网格;grouped_reduce(t,R)→(rows/R, cols);matmul 段 →(batch·M, N),GQA 的 batch/ratio 行块同to_spec的 k 口径;标量、网格索引叶、稀疏与批视图中性)。冲突 / 不可推导 / 全无贡献者 = 明确报错并提示改用显式形式(绝不猜形状)。回归src/dsl_shape_infer_test.cpp,细则见docs/development/cpp-api-manual.md§6。- fold 段(P-C1/C2):
ExprSpec.fold = FoldSpec(分块状态归约:键域逐块 body + 行标量态跨块进位 +vecacc行向量态块累加 + 向量域 finalize)——通用折叠表达式机制,不含任何注意力专属语义(与注意力无关的通用样例 rowmax/rowsum/softmax_denom/logsumexp 在expr_fold.hpp;make_fold_logsumexp的 finalize 为多指令链lse = m + log(l),校验按 def-before-use 链放行)。fold 值构造走FoldAnchor自登记(与FusedAnchor同机制,见下"收集"条:通用样例由expr_fold.hpp末尾登记、make_fold_logsumexp由消费方compute_loss.hpp登记);注意力 forward 直调engine.eval_expr(make_fold_attn_o(...))(不经 DSL 钩子,其全部组合由定义方compute_layer_attention.hpp的attn_fold_anchor自登记——P4-4 ① 起scan_exprs不再有 fold 登记块);该构造与掩码种类AttnMaskKind(Plain/Causal/CausalDoc)定义在compute_layer_attention.hpp(按 AOT 原则"表达式文本只出现在 Layer"归位)——位置偏置是正交的第二入参bool score_bias(由PositionEncoder::has_score_bias()给出),掩码清单是 X 宏单一真源(NN_ATTN_MASK_KIND_LIST同时展开枚举与登记表)→ 5 个掩码/偏置组合 × {MHA,GQA} 共 10 条结构在静态初始化期登记,新增掩码/组合只改定义方、不改scan_exprs(漏登记 = GPU 闭合世界硬报错的最后一个已知风险点由此消除);FoldSpec.tri_skip(行界整块跳过)进 key(codegen 分歧点);EXPR_FOLD_BLOCK=128/EXPR_FOLD_ROWS_PER_WG=2为 CPU/GPU 共享常量(改则两侧同改)。fold v1 生成器含两项通用优化:归约输入内联(被归约独占的临时量逐块重算、不物化e[n][128])与循环剥离预取软件流水(消串行延迟链;累加序不变 = CPU/GPU 逐位同序)。fold v2 生成器(双域)含三项恒等优化(字节锚不动):tri_skip 空块continue整块跳过(此前只钳 valid、块内屏障照跑;空块的 m/α/l/O 更新全为恒等元,可达态论证见生成器注释)、元素链按读集载入/按写集存回(消死加载,出口 shared 逐位一致)、归约 gather/shuffle 收在 subgroup0(sred 本只取 lane0,其余 subgroup 结果全被丢弃)。同窗 A/B 注意力 fwd fold −5%;相位消融探针(临时、已删):vecacc ≈57%、mm ≈15%、掩码链+归约+屏障 ≈28%——vecacc 展宽 = P2-2 已实施(FP 求和序变更 = 移 GPU 锚,已获批):veclen 不进 key → 运行期 uniform 双路径(0 < vs ≤ 256 = j 分片跨线程:c=tid%vs、片地板划分精确覆盖 [0,valid)、块循环后每行一次槽归约(复用 spart、o不跨线程);vs > 256 = 现行 dd 步进 + 4 累加器),热路径用标量累加器 ob0..ob3 恒寄存器——动态下标o[4][4]数组落 local/scratch 是流量雷区(首版数组累加 A/B 反慢 26%,教训见 history「P2-2」条),尾段按固定累加器序展开零动态下标;同窗 A/B fold 3838 → 2656 µs(−30.8%)、E2E step 95.4 → 90.6 ms(−5.0%);GPU 字节锚随之移动(新值见 §12 锚行,CPU 锚不动)。详见expr_fold.hpp与expr_spec.hpp的 FoldSpec 注释。 - 构建期单步(CMake 自动编排,改 Layer 内联表达式后重跑构建即可)——
scan_exprs同进程收集 + 生成:- 收集:只收集结构(= 唯一需要构建期枚举的东西)。两个来源互补、不冗余(
NN_SCAN_DUMP_SOURCES=1可逐条打印来源):FusedAnchor<Expr>在静态初始化期按表达式类型自登记(编译期可达,dry-run 跑不到的分支也覆盖);fold 结构走FoldAnchor同机制自登记、由定义方挂锚(通用样例与 3 个 v1 样例在expr_fold.hpp末尾,注意力组合在compute_layer_attention.hpp的attn_fold_anchor——掩码清单 X 宏NN_ATTN_MASK_KIND_LIST同时展开枚举与登记表,新增掩码/组合不改scan_exprs;P4-4 ①,2026-10-15);per-layer dry-run + 模型 pass 执行补齐运行期配置决定的变体(视图种类/项数在类型层面推不出,符号实例Expr{}与真实实例会折叠出不同 key);matmul trans 族与测试覆盖仍由scan_exprs显式登记。只跑 f32 一遍——实测 f16 遍对结构贡献为 0(跳过它结构数不变),该两遍已删除。实测分工(126 条;另有样例口径:nn_enable_gpu_fusion收集器 = 库内 126 + 样例自定义 2 = 128):per-layer+显式 58、模型 pass +32 = dry-run 侧 90、锚点 92(新增 36),重叠 56 → 锚点独有 36、dry-run 独有 34(项目 4/5 后:输入槽去重令锚点符号结构与真实张量结构 key 分叉、且 Softmax fwd/bwd 补入 dry-run)。所以任何一方单独删掉都会丢结构——dry-run/model pass 是结构的主要来源,删不掉(唯一例外 = fold:dry-run 一条 fold 都产不出,14 条 fold 全部来自自登记)。⚠ 输入槽去重的覆盖约定:SpecBuilder::add_input对"同张量+同视图"去重,但锚点符号实例(占位张量)不参与去重 → 含重复同张量输入的表达式必须有 dry-run/模型 pass 覆盖,否则运行期去重后的新 key 未登记 = 闭合世界硬报错(实测Softmax::backward中招后补 dry-run;见docs/history/history.md「项目 4/5」条)。 - 生成:
tools/fused_generate.hpp(原独立工具gen_fused)对每个结构发三份:V0 全 f32(键 =key)/ V1 运行期精度分派(键 =key#x)/ V2 native16(键 =key#a,按expr_prec_sig_native16的结构谓词判定)→ 经emitter_registry选后端(默认"glsl"=GlslEmitter)生成 GLSL → glslc → 内联 SPIR-V →build/generated/fused_registry.hpp。不经.bin中间序列化(write_registry/read_registry已删除)。
- 收集:只收集结构(= 唯一需要构建期枚举的东西)。两个来源互补、不冗余(
- 精度签名不再是构建期集合("签名从身份降级为参数"):V1 的 GLSL 对每个输入/输出各声明 f32 +
float16_t双视图,加载/存储处按 push constantuint prec(bit i = 输入 i 为 f16,bit16 = 输出 f16)走 uniform 分支,一份 shader 覆盖任意签名。因此构建期不需要枚举签名,也不存在回填清单(tools/prec_backfill.txt已删除,NN_PREC_TRACE的"补变体"流程随之失效)。⚠ 精度路径仍有三档、不是"永不 miss":key#a(native16)→key#x(V1 运行期分派)→ 基类边界 cast;当某个结构的#x生成被跳过([skip] 分派变体…)、NN_SCAN_NO_DISPATCH=1、或设备无 16bit 存储时,运行期会 miss 并落到边界 cast([prec][miss]仍会触发,它是通用诊断)。V2 谓词含常量值判据而常量值不进 key:运行时选#a前会用真实实例的 spec 重跑expr_prec_sig_native16,否则"生成时那一个实例的常量"会被套用到同结构但常量不同的实例上(微值在 f16 下 flush-to-0 = 静默错值)。变体选择事件(V1/V2)一律打NN_PREC_TRACE日志(V0 默认路径不打——f32 热路径会淹没输出;V1 分支曾漏打,导致"V1 从未被使用"的错误判断),且只在确认命中后打、只由真正 dispatch 的入口打(supports_expr_precision_variant只是查询,它打日志会让计数翻倍:实测修复前 12040 行 = 6020 次真实分派 × 2)。实测 V1 覆盖(NN_PREC_TRACE=1 f16_precision_test):6022 条命中日志 / 24 条 distinct 结构 = 20 条 V1(#x)+ 4 条 V2(#a),V1 覆盖全部四个生成器族(元素 9 / 归约 6 / matmul 4 / fold 1),且该测试用CHECK_NEAR容差断言;miss 为 0。 - 新增调用点无需改动其他文件(实测):在 Layer 头里新写一个
dsl::compute(其结构不在任何 dry-run / 模型 pass 覆盖内)→ 锚点独有 +1、结构 +1、注册表新增key/key#x/key#a三条,其他文件零改动。 - 编译期精度特化(
key#s/key#xxxx,2026-10-13)——改 f16 变体选路前必读:#x的"一个 shader 覆盖任意签名"在热循环里每次读写都要带((prec >> i & 1u) ? float(bNh[…]) : bN[…])三元,实测这一项就是 f16 变慢的主因(850M:CE 融合注意力 146.1→120.0 ms,同机 f32 基线 117.2)。因此生成期追加两类静态签名变体(与#x数值逐字等价,只需 16 位存储、不需要shaderFloat16):key#s= "全输入 f16 + 输出 f16"(无条件发射,兜底)与key#xxxx= 扫描期真实观察到的签名(ExprRegistry::variants;scan_exprs的 dry-run 与模型 pass 各跑 f32 + f16 两遍,scan_register(dsl::compute*)与 CPU 引擎eval_expr*入口(fold / 直接求值不经 dsl::compute,必须在引擎入口补记)按真实张量精度 + 真实 P 登记)。运行期查找序 =#a(原生 f16)→#xxxx→#s→#x→ 基类边界 cast;未枚举到的签名照旧落#x,所以特化集合只是优化、不是身份(ExprRegistry::variants是"优化清单",specs才是结构身份)。⚠ 网格粒度变体也可叠加在特化签名上(2026-10-14):#f(细 tile)与#ks/#kr(K 切分)现在对每个静态签名变体各发一份(键形如key#10003#ks),网格饥饿形状(大 batch 的 wgrad)在 f16 下同样拿到 K 切分——40HX 实测该结构 f16 16.0 ms/块 < f32 25.1 ms/块(此前 f16 掉回 64-tile = 79 ms/块)。两条不变量:①#ks的输出恒 f32(写的是部分和缓冲,后端按 f32 分配)→ 生成器把输出声明钉成float,注入 sig 只决定 A/B 读取类型;② 后缀可叠加 ⇒ 后端"#f/#ks/#kr无需 16 位存储"的豁免只对纯 V0 键成立(判据 = 去掉网格尾后缀后仍含#即需要 16 位存储)。运行期派发哨兵#x不叠加网格变体(它多一个precPC 槽、绑定翻倍)。批量变体同理(2026-10-14,P2-12 已完成):#b对每个 extras 为空的静态签名变体各发一份(键形如key#0002#b;extras 的元素类型不在ExprPrecSig编码里 → 带 extras 的 f16 结构仍逐 kernel 降级)。⚠ P2-12 的残差(Adam 复合 kernel 在--f16下逐成员降级、绕开批量队列)根因是 "scan 与运行期不同源",不是派发器——两条不变量:① scan 的优化器 dry-run 必须把当前 pass 的 precision profile 透传给create_optimizer(文本路径text_train本来就在传;漏传 = f16 pass 仍按默认 f32 profile 定upd_prec/写目标精度 ⇒ 融合出的复合结构与运行期不同 ⇒ 运行期命中不到 ⇒ 降级);② 复合 kernel(≥2 成员)的运行期精度签名必须在 scan 的 CPUend_expr补记(=add_variant(spec, expr_prec_sig_of(输入张量, tail 输出精度)))——单节点结构有scan_register/eval_expr*入口,融合结构只在 GPUend_expr执行时才算出签名 ⇒ 否则 f16 下复合结构只剩 V0/#s/#x三种载体,拿不到#xxxx特化与其#b批量变体。两条坑:① 运行期组键必须带精度身份——批量变体的元素类型是编译期定死的(f16 槽走float16_t的 buffer reference),同组混签名会把 f16 缓冲按 f32 读;② 取实例缓冲必须按t.precision()选槽位(gpu_tensor()的默认模板实参是 F32,f16 张量会命中"无 P 精度存储"断言)。 - 库外使用者:
nn_enable_gpu_fusion(<target> MAIN <含 main 的源>)(CMakeLists.txt):使用者在自己的层头里写dsl::compute+ 在 CMake 加这一行 → 自动生成该目标专属的fused_registry.hpp并加进它的 include path。机制 = 收集器由库内收集逻辑(tools/scan_exprs.cpp:锚点 + dry-run + 模型 pass + 显式登记)加上本目标的全部 TU(-DNN_EXPR_SCAN编译,让使用者的调用点自登记)组成。⚠ 必须保留库内收集逻辑:99 个结构里只有 17 个来自编译期锚点、82 个来自 dry-run/模型 pass,"纯锚点收集器"会漏库自身的结构。⚠ include 顺序有语义:${NNF_OUT_DIR}必须排在${CMAKE_BINARY_DIR}/generated之前(后者含库自身注册表与手写原语 shader 嵌入头),否则__has_include("fused_registry.hpp")会解析到库的那一份、使用者的自定义结构全部闭合世界报错。样例见examples/fusion_custom_layer*(库内目标形态;函数本身定义在本仓CMakeLists.txt的if(Vulkan_FOUND)内,且仓库无install()/export——真正树外工程要复用它需先做 install/export,见docs/todo/18-roadmap.mdE2)。 - 端到端验收(库外 + f16 分派):
build/fusion_custom_layer_example.exe实测两种自研算子形态:①逐元素exp(|x|·α)/(√|y|+α);②归一化形态rsqrt(col_reduce_sum(x)·inv + ε)(= 库内 LayerNorm/RMSNorm 的std_inv同形,raxis=1、viewkinds=[0,8],属库里"只有执行 Layer 才拿得到"的那一类)。两者都只用锚点(自定义层没有 dry-run)→ 都命中并跑通 ⇒nn_enable_gpu_fusion对常见自定义层可靠。实测值:CPU f320.6712035(差 0);GPU f32(V0)0.6712036(差 5.96e-08);GPU f16(V1 运行期精度分派)0.6708984(差 3.05e-04,f16 舍入内);归一化形态 GPU f161.2246094。含 rparams ⇒ native16 谓词不通过 ⇒ f16 命中必然是key#x;注册表若漏了它就是闭合世界硬报错,"跑通"本身即断言。残余不确定性:库内确有 25 条结构只有执行 Layer/模型才拿得到(锚点的符号实例Expr{}与运行期实例折叠结果不同);实测的两种自定义形态不在其中,但无法先验排除。若使用者的自定义层遇到闭合世界报错,下一步是给nn_enable_gpu_fusion加COLLECT <file>钩子(由使用者提供"跑一遍自己的层"的收集函数,等价于库内的模型 pass)。 - 库是 header-only,多 TU 必须可链接:头里的类外定义一律要
inline(曾漏GpuBuffer::~GpuBuffer→ ≥2 个 TU 的程序链接期 duplicate symbol,examples/fusion_custom_layer*抓到)。 - IR-D emitter 抽象(
expr_emitter.hpp):把后端代码生成从 GLSL 专用抽象为 emitter 接口(一份 canonical IR → 多后端代码),--list-backends可列出注册后端。目前仅glsl后端注册(无CpuEmitter/CudaEmitter实现)。 - 手写原语 shader 在
shaders/*.comp(matmul、matmul_tiled、matmul_gemv、batched_matmul、reduce、elementwise_v2、transpose、gather、scatter_add、rearrange_3d、im2col、col2im、group_reduce、scan_prefix_outer、scan_suffix_outer、scan_prefix_outer_gen、scan_suffix_outer_gen(gen = 通用 dk 版,dk>64 后端走它)、outer_col、cast),构建期 glslc 编译并嵌入 C++ 头文件;多数 shader 另用-DNN_SHADER_F16=1编一份 f16 变体(NN_ETYPE=float16_t,f32 分支留在#else)。 - IR 优化 pass(canonicalize/CSE/寄存器分配)见
expr_opt.hpp,设计文档docs/todo/03-ir-optimization.md(IR-C 图融合的当前形态见 §7;历史取舍记录归档在docs/history/history.md)。
推荐(P2b,数据集直接接入训练工厂)——训练配方 + 样本切分 + 循环全在库层:
#include <neuralnet.cpp/nn.hpp>
#include <neuralnet.cpp/text_trainer.hpp> // 训练工厂(L5;不在 nn.hpp 聚合头内)
auto engine = nn::CpuEngine::create(); // 或 nn::GpuEngine::create(gpu_backend)
auto model = NN_UNWRAP(nn::build_gpt_model(*engine, vocab, d_model, seq, heads, d_ff, layers));
nn::TrainingConfig scfg; scfg.optimizer = "adamw"; scfg.lr = 1e-4f;
scfg.clip_grad_norm = 1.0f; scfg.grad_accum_steps = 4;
auto session = NN_UNWRAP(nn::TrainingSession::create(std::move(model), scfg));
auto dataset = NN_UNWRAP(nn::Dataset::open("train.nndataset"));
nn::TextTrainConfig tcfg = nn::make_text_train_config(session); // 优化器/lr/精度同源
tcfg.seq_len = seq; tcfg.batch_size = 32; tcfg.epochs = 2; // batch = global batch
auto trainer = NN_UNWRAP(nn::TextTrainer::create(std::move(session), dataset, tcfg));
auto report = NN_UNWRAP(trainer.fit()); // epoch×step / 累积 / 裁剪 / 评估配方一致性:
TextTrainConfig与TrainingConfig的重叠字段(optimizer/lr/ weight_decay/beta1/beta2/precision/max_norm)必须相同,TextTrainer::create会逐项 校验并报错;nn::make_text_train_config(session)是从 Session 派生的推荐起步。
nn::TextTrainer 内部:Dataset → TextSampleSet(样本 = seq_len 窗口)→ 理论 global batch
→ 按 batch_size / accum_steps 拆 micro-batch → TrainingSession::train_step。step 指一次
optimizer update,steps_per_epoch = ceil(样本数 / global_batch_size);TrainState 由
TextTrainer 装配和持有。Sample 的 loss_mask 是 token 是否参与 loss 的权威来源。
低层手写(自定义任务/推理)——只需 Model + TrainingSession:
// 每个 micro-batch:Step 回调只描述任务语义,Session 管累积/更新。
auto x = NN_UNWRAP(engine->from_matrix(batch)); // Matrix → Tensor(I/O 层)
nn::TrainingStepOptions opts;
opts.lr = current_lr;
opts.clip_grad_norm = 1.0f;
opts.normalization_units = global_valid_tokens; // 整个 update 的归一化单位
auto report = NN_UNWRAP(session.train_step(
x, opts,
[&](nn::Model& model, nn::ComputeEngine& engine, const nn::Tensor& input)
-> nn::Result<nn::TrainingStepOutput> {
auto logits = model.forward(input);
if (!logits) return std::unexpected(logits.error());
std::size_t valid = 0;
auto loss_sum = ce_loss.forward_sparse_sum(engine, *logits, labels, loss_mask,
vocab, valid, &*logits);
if (!loss_sum) return std::unexpected(loss_sum.error());
auto grad = ce_loss.backward();
if (!grad) return std::unexpected(grad.error());
return nn::TrainingStepOutput{std::move(*grad), valid,
std::move(*loss_sum), valid};
}));
// report.parameters_updated=false = 仍在校验累积;true = 本次完成 optimizer update注意签名细节:
Layer::forward/backward/zero_grad/forward_recompute自 M6 段 C 起不带 engine 参数(engine 在init(engine)时绑定到层内engine_;方法体里用函数首行的局部ComputeEngine& engine = engine_ref(),DSL 入口照旧收 engine 形参)。Model::forward/backward/zero_grad、Loss::backward、Optimizer::step/zero_grad本来就不带 engine;仍需显式传 engine 的:Loss::forward/forward_sparse、engine.from_matrix、以及PositionEncoder/RotaryEmbedding这类辅助对象(apply/backward)与层内私有 helper。手工构造的层必须先layer.init(engine)(Model::add会自动调)——未绑定即 fail-fast 并打印调用点 file:line。
- 模型工厂:
nn::build_mnist_mlp_model(engine)/build_mnist_transformer_model(engine)/build_gpt_model(...)/build_gpt_model_from_spec(spec)。 - 逐层构建:
model.add<nn::Linear>(784,256); model.add<nn::ReLU>(); model.add<nn::Linear>(256,10);——add<T>(args...)是模板方法(无链式 API),返回Result<void>,须检查错误;层构造后自动init(engine)。 - 推荐容器:
nn::TrainingSession(training_session.hpp)拥有Model+Optimizer,结构性避免"优化器晚于参数所有者析构"的 UAF;任务 Step 回调负责 forward/loss/backward,Session 内置累积/裁剪/update。 - 样本/数据集:存储单位 = doc(
Dataset);训练单位 = 样本(TextSampleSet,文本 = 滑动窗口);batch_size是理论 global batch,micro = batch_size / accum_steps,step 按 update 计。mnist_train仍是 CSV 自读 + Session 循环(tabular 数据集为阶段四预留)。 - 序列化:
save_model/load_model/peek_model_spec(model_serialization.hpp,v4 自描述格式);.nnpkg训练包见docs/usage/04-train-package.md。 - GPT/RAPT 高级特性(
GPTModel/RAPTModel,compute_layer.hpp尾部):梯度检查点(checkpoint_every_)、activation offload、文档感知掩码(set_doc_ids)、batch flush 粒度。RAPT 与 GPT 同档支持前三者(RAPTModel::set_checkpoint_every/set_activation_offload/set_flush_interval),offload 走共用实现ActivationOffloader(compute_layer_base.hpp)。
Scalar = float;BLOCK_SIZE = 64(matmul 分块,b_block 栈预算 64KB);PARALLEL_THRESHOLD = 524288(自适应并行阈值,由nn::parallel_for_blocks/parallel_for_samples门控)。- 不使用
-ffast-math(保 NaN/Inf,训练稳定性)。
- 布局混用:position-major vs batch-major → 跨样本串扰、loss 平台期。改序列代码先确认列序约定。
- GPU 双存储影子一致性:CPU/GPU 双存储是分布式状态机问题;GPU-resident 路径已禁用,走 staging(每次算子往返 PCIe)。
- 录制期 use-after-free:局部张量在
end_batch()前析构 →VK_ERROR_DEVICE_LOST。 - TDR/设备死亡:
VK_ERROR_DEVICE_LOST不可重试(存 checkpoint 退出);VK_TIMEOUT可减半 batch 重试。 - 缓存 key 冲突:naive 位域/组合 key 会撞车,用强哈希(FNV-1a)或双字段。
- 零拷贝 reshape 的 CPU/GPU 语义差异:CPU 分支 reshape 需复制数据,GPU 保持零拷贝。
- 内存爆炸:"所有出现位置"类索引随输入线性膨胀、大词表 one-hot 物化都会把内存吃到几十 GB 级。先做内存预算(历史量级见
docs/todo/08-pitfalls-and-lessons.md)。 - gradcheck 必须先 forward 填充
input_cache_再 backward,否则空缓存崩溃。
总入口
docs/README.md:规定每个子目录写给谁看。一句话分册定位——introduction/usage/development写给调用者;todo/与history/history.md写给项目开发者。docs/development/的读者是"库使用者 / 扩展者"(在库之上写代码),不是改库的人; 项目内部的设计 / 路线图 / 盘点 / 规范 / 踩坑 / 故障 / 整改全在docs/todo/。 2026-10-11 的结构整理记录(哪些迁走 / 并入 history / 删除)见docs/README.md§3。
| 文档 | 何时读 |
|---|---|
README.md |
文档总索引与分册定位(含 2026-10-11 整理记录) |
history/history.md |
全仓唯一的历史状态归档:演进记录 / 已修复勘误 / 被否决方案 / 性能 A/B 过程。代码注释与其他文档只记录当前状态;查"为什么当初这么改、旧数字是多少"只来这里。原 development/15(ComputeEngine Refresh 设计)与 16(P0 盘点)、已删的 13(重构清单)均归档在本文 |
release-notes/v*.md |
面向使用者的版本变更说明(破坏性 API 变更 + 迁移指南 + 验收基线)。当前为 release-notes/v1.12.0.md(v1.11.0 → RL 基础设施:schema/具名多输出、GAE、REINFORCE/PPO/GRPO、多模型 Session、rollout pipeline;无破坏性 API,模型格式 v8 不变)。上一版 v1.11.0.md = TrainingSession 单步协议与 global/micro batch 语义;预发布候选记录保留在 v1.10.0-rc0.md / v1.10.0-rc0.1.md |
| 文档 | 何时读 |
|---|---|
introduction/01-architecture.md |
需要完整分层/数据流/模块详解时(含快速理解指南和理解路线图) |
introduction/02-performance.md |
性能优化(自适应并行、缓存分块、GPU) |
introduction/03-algorithm-reference.md |
每个 Layer/Loss/Optimizer 的数学与原语分解 |
introduction/04-innovative-designs.md |
创新设计全景 |
| 文档 | 何时读 |
|---|---|
development/README.md |
本分册定位与索引(明确"写给调用者") |
development/cpp-api-manual.md |
全功能 C++ API 手册(库使用者首选):错误宏 / 引擎 / 张量 / 精度 / DSL / 层与 Model / 模型工厂 / 损失 / 优化器 / 训练循环 / 模型文件 / 数据集 / 分词器 / 生成 / 剖析,每节带可粘贴代码片段;§0 有"我要做什么 → 看哪节"表 |
development/cmake-integration.md |
接入与构建:add_subdirectory/FetchContent、门面目标 neuralnet::nn 传递内容、全部构建开关(NN_ENABLE_GPU/NN_BUILD_APPS/NN_ENABLE_TESTS/NN_ENABLE_PCH/NN_ENABLE_NATIVE/NN_ENABLE_PROBES/NN_COMPILER_LAUNCHER)、GPU 语义、nn_enable_gpu_fusion(target MAIN <main源> [OUT_DIR ...])、本仓构建/测试、跨平台、故障排查 |
development/write-a-custom-layer.md |
写自定义层的指南(P1-6):完整示例(实测过数值梯度检查 + 训练收敛)+ 七条接入契约 + 精度槽声明 + 可复制的梯度自检片段 + GPU 检查清单 + 常见编译错误对照。背景实测见 development/20 |
development/01-compute-engine-development.md |
扩展引擎原语:接口详解、CPU/GPU 实现模式、添加新原语的完整步骤 |
development/05-mixed-precision.md |
多精度语义契约(写层 / 选 profile 必读):Precision 类型系统、类型化存储、分派、PrecisionProfile(param/compute/stable/optimizer)、实测结论与已知数值限制(§12) |
development/06-rapt-algorithm.md |
线性注意力(RLA → RAPT → RLA-2):直观理解 + 数学定义 + 扫描原语工程落地 + 训练显存开关 |
development/20-custom-layer-ergonomics.md |
写自定义层的实测报告:库外使用者视角的接入契约、7 个摩擦点(是 write-a-custom-layer.md 的背景证据,不是操作指南) |
索引与定位见 docs/todo/README.md。逐篇:
| 文档 | 何时读 |
|---|---|
todo/00-code-review-remediation.md |
2026-10-11 代码审查整改立项台账:审查发现逐条立项(现象/证据/根因/方案/验收/优先级)。接手整改前先读本文 |
todo/02-operator-fusion.md |
算子融合全篇(当前形态与设计约束):IR 扩展(归约语义)→ 表达式录制 → matmul 参与 IR 融合 → 跨 kernel 自动融合(一期 M1-M7 + 二期 S1-S5/S7;分期实施流水与否决方案见 docs/history/history.md) |
todo/03-ir-optimization.md |
IR 优化(现行 IR-A/B/C/D;IR-C 图融合为现行能力——当前形态与融合规则见 AGENTS §7,历史取舍归档在 docs/history/history.md) |
todo/04-memory-optimization.md |
显存优化:L1 激活重计算(梯度检查点)/ L2 内存池整块归还(均已实施);L3 远期 |
todo/08-pitfalls-and-lessons.md |
踩坑警示录,改代码前读 |
todo/10-development-standards.md |
C++ 编码规范全文 |
todo/12-compute-engine-inventory.md |
引擎接口盘点(复现:bench/doc_inventory.ps1):52 个 virtual 方法、Layer 直调 32 个(基础设施/数据搬运/状态扫描/fold 执行(自登记)/IR-C 录制段开合/三值打包/张量形变/精度落盘/RL 行带适配)、两套 CPU 求值机制(DSL 模板 + IR 解释器)、ctest 69 个测试(无 Vulkan 树 67);演进记录见 docs/history/history.md |
todo/14-f16-stable-gpu-loss-frozen.md |
故障报告(2026-09-26;2026-10-13 ✅ 已修/根因订正):GPU stable=f16 训练 loss 打印冻结(权重不冻结)——真因 = CE 的 (1,1) loss 归约按 stable=f16 落盘溢出,非回读链;触发矩阵、测试覆盖缺口、证据与复现 |
todo/17-unified-tensor-engine.md |
统一 Tensor/ComputeEngine/MemoryPool 底层架构总纲(2026-09-30 裁定;M1-M6 全部已实施 2026-09-30,仅 M7 未立项):访问不变量(Tensor 存储私有、一切经引擎,已立为铁律 #11)、InitSpec 声明式初始化、批量 read/write、Matrix 降级为宿主 I/O 载体(L2+ 禁用 = 铁律 #12)、内存池契约统一、import 跨设备拉取、Layer 删每调用 engine 形参(M6 段 C,Loss/DSL/辅助对象保留形参见 §8);吸收原 15 未实施的 P2-P6(改期 M1-M7)。立项前先读本文件 + §5 分期 + §8 |
todo/18-roadmap.md |
未来方向路线图 + 未完成项裁定台账(2026-10-01,13 号清单的承接者):RL 主线 ✅(2026-10-23)+ P0-P4 分期(P4 = 持续优化方向,2026-10-14 立项)、13 号逐条裁定(做/条件触发/不做)、触发式立项条件、非目标、门禁与验收口径。查"某件事该不该做、做到什么算完成、避免重复立项"时读本文;执行排序不在本文(见 todo/27-execution-plan.md) |
todo/27-execution-plan.md |
执行计划(优先级象限,2026-10-15):RL-0…RL-2b 已完成并从执行表移出;其余象限是后续 backlog。想知道"下一个先做哪个"读它;裁定去 18 号,本文只排顺序、不产生新裁定 |
todo/31-rl-training-primitives.md |
RL 与可组合训练基础设施(2026-10-23 ✅ 已实施):issue #23 是能力下界;公共语义 = NamedTensorSet/OutputSchema、schema 化 experience、policy-objective 组件、通用 TrainingGroupSession 与可替换编排;实现/门禁见 §11,使用指南见 usage/06-reinforcement-learning.md |
todo/19-unified-dataset.md |
统一数据集格式与加载(.nndataset/.nnvocab/KVRecord v2,2026-10-05 设计定稿、阶段一~三已实施):裁决记录、KVRecord v2 规范、文件布局、nn::Dataset/dataset_gen/dataset_convert/模型 v6 分期落地与兼容迁移。接数据集相关任务前读本文 |
todo/21-quantized-weights.md |
三值(1.58-bit)量化权重与 BitLinear(2026-10-09 设计定稿 → 2026-10-10 P1 已实施,分支 dev/t1_58):Precision::T1_58 + Layer::precision_support()(按槽声明、init 期校验)+ BitLinear(量化 forward / STE backward);含 Lumina-Engine TQ1_0 参考实现拆解、§4.9 实现与设计的差异、§8 架构图、§10 提交清单。已裁定:尺度 = absmean + per-row(absmax 只剩 0.13~1.29 bit 信息,实测见 research/ternary_scale/)、打包 = TQ1_0 编码 + 行级块、命名 T1_58 / CLI 无别名 / 层名 BitLinear、D3 = (a)+(b)、D5 = ③(P1 只覆盖 MLP;GPT 侧由 P1.5 补齐,RAPT/ViT 仍拒绝);仍待定 D6(P2 行步长/对齐)。前置证据 research/ternary_ste/;验收 src/t1_58_test.cpp(+--gpu 变体) |
todo/22-bitnet-2b4t-architecture.md |
BitNet b1.58 2B4T 的结构对齐(2026-10-10 立项 + S1/S2/S3 三步已实施并提交,分支 dev/t1_58):三项结构差距 = SubLN / GQA / tied embedding,均为可选开关、默认关**。已裁定:SubLN 封装在子层内部(AttentionBase/FeedForward 的可选 unique_ptr<Layer>,不是 NormType 新值、不是 block 的第三个 norm)、类型跟随 norm_type;GQA 头映射 = kv = h/(n_head/n_head_kv)(llama.cpp FA 与 HF repeat_kv 互证)+ 显式展开 K/V 头(前向 gather_rows / 反向 scatter_add_rows,fold 与 AOT 注册表零改动);tied embedding 在 GPTModel 内一个开关(梯度累加进 grad_token_emb_);不加 CLI 预设(GUI 留待以后);ModelSpec 追加 subln/n_head_kv/tie_embeddings 三键(缺键 = 旧语义,不升 MODEL_VERSION)。验收 src/bitnet_struct_test.cpp(+--gpu):GQA 退化与 MHA 逐位一致、GQA↔按块映射复制成 MHA 前向差 0、tied 梯度累加差 0;102 上全量 ctest 30/30。实施记录见本文 §10。待定:FFN 激活 ReLU²(需确认官方 hidden_act;llama.cpp 对 gated squared relu GGML_ABORT,无对拍)、GQA in-kernel 版(P2)、HF 权重转换器。结构侧与 21 号(权重侧 T1_58/BitLinear)是两条正交的腿** |
todo/23-library-design-cleanup-and-model-file-roles.md |
库级违规设计清理(2026-10-11 已实施):模型文件角色 + 参数形态协议 + 引擎能力体系(范围 = 整个库,不只三值)。三轮裁决要点:① 参数级 kind(Dense / TernaryLatent / TernaryPacked2 / TernaryPacked3 / 预留 int8);② 不引入可训练/不可训练状态;③ 能力谓词 = 具体引擎私有、不得进基类、不作对外标签、只作内部分发(设备不支持走引擎内降级/报错),能力信息走统一对外查询通道(describe());④ 文件角色拆开:.nnmodel(推理,.bin 接替者)与 .nnckpt(含训练参数,--save-interval 写它);⑤ 热路径审查(基类能力分支 + eval_expr 每次求值双份归因 → 编译期标签 / init 期常量 / 引擎内就地回退);⑥ sub_layers() 与 NNQT 独立格式、--export-deploy 等标签全部删除(BitLinear 的 f16 稠密 A/B 开关保留)。§3 待裁决已定:EngineInfo 结构体 + text()、接口名 describe();.nnmodel 不留 latent;旧 .bin 只读不写、不提供转换工具。落地验收见 21 §4.15 与 src/model_roles_test.cpp |
| 文档 | 何时读 |
|---|---|
usage/01-quickstart-model.md |
构建模型 API 教程 |
usage/02-quickstart-train-infer.md |
训练/推理 CLI + C++ API + GUI |
usage/03-compute-engine-usage.md |
计算引擎使用指南:张量操作、矩阵运算、表达式融合 |
usage/04-train-package.md |
.nnpkg 训练包 |
usage/05-consume-as-library.md |
把库嵌进自己工程(add_subdirectory/FetchContent):neuralnet::nn 门面、NN_ENABLE_GPU/NN_BUILD_APPS 开关、nn_enable_gpu_fusion、故障排查 |
usage/06-reinforcement-learning.md |
RL 与可组合训练基础设施:named output/schema、experience/GAE、PolicyDistribution、REINFORCE/PPO/GRPO、TrainingGroupSession、可替换 rollout pipeline、AOT 与测试口径 |
本节只列当前事实;演进记录、已修复勘误、被否决方案与性能 A/B 过程见
docs/history/history.md。
- GPU
stable=f16训练 loss 打印冻结(✅ 2026-10-13 已修,原判断已订正):真因不是 loss 回读链,而是 CE 的(1,1)loss 归约输出按stable=f16落盘溢出(Σ ≈ -log(vocab)·N超 65504 → inf → NaN;旧报告里的4.0137 ≈ 65504/num_valid是饱和表现)。修法 =compute_loss.hpp稀疏(fused_forward_sparse_)与稠密两条链的(1,1)reduce 输出恒Precision::F32。实测(850M):seq256/b32由inf→9.0056→8.6922;stable=f16120 步无 NaN。回归门禁见docs/todo/14-f16-stable-gpu-loss-frozen.md与 24 号 §3.1。推荐路径仍是--f16(stable/optimizer=f32)。 - f16 两套计划(2026-10-18):Plan A = 标准
--f16(参考 PyTorch AMP)——--f16(param/compute=f16,stable/optimizer=f32)是推荐默认,补动态 loss scaling / master weights;Plan B = 全流程真 f16——四字段全 f16,支持 f16 计算的设备全程零 f16↔f32 转换,不支持时只由引擎在算子内部隐式转换(Layer/DSL/CLI 零手写 cast)。F5 后四字段全 f16 已可训练,但 CE 标量仍是 F32 补丁、matmul/归约仍有 f32 累加、native16 未全覆盖,不等于真 f16。细则见docs/todo/26-f16-speedup-plan.md§8.A/§8.B。 - 不存在的 CLI 参数:
--tdr-retry/--max-tdr-retries与mnist_train --osc-guard/--osc-window/--osc-threshold(均已移除:前两者从未实现,后三者属"帮助声明了、解析分支不存在"的幽灵选项,已从帮助/控制器/GUI 三层清除,详见docs/history/history.md「GUI / CLI 参数一致性清理」)。 text_train --model zipt启动即中止(未修) → 已随 ZiPT 整体移除而消失:AttnZip/ZiPT 已从主线移除(--model zipt现在直接报"未知模型架构"并给出迁移提示),代码保留在legacy/zipt分支。恢复前提(两条同时满足):① 算法层——压缩向量必须因果(压缩器只能看当前位置之前的内容);② 代码层——实现质量对齐 RAPT/GPT(支持forward_recompute梯度检查点与 activation offload、CLI/序列化/测试齐备)。裁决依据见docs/history/history.md「ZiPT 移除」条。已移除的 API:ZiPTModel/ZiPTBlock/CrossAttention、build_zipt_model*/make_zipt_spec/ZiPTConfig/ZIPT_MEMORY_TOKENS、ModelSpec::is_zipt()与memory_tokens/window字段、CLI--model zipt/--window/--memory-tokens、zipt_test;ModelType的 6 号枚举保留为Reserved_ZiPT占位,用于对旧.bin(type=6)给出明确错误。
只列"能力名 + 关键不变量",不含数字/过程——每项的实测收益、A/B 数字、锚值变化与取舍全部归档在
docs/history/history.md,需要"为什么这么改 / 旧数字"时再去查。设计稿在docs/todo/。
- 表达式 DSL + AOT 融合(
expr_dsl.hpp/expr_spec.hpp/expr_graph.hpp):Layer 用dsl::compute*写数学,CPU 模板路径直接求值、GPU 折成ExprSpec按expr_spec_key查预编译 shader(闭合世界,未命中硬报错)。 - IR-C 图融合(
expr_graph.hpp+begin_expr/end_expr+dsl::start_expr/end_expr):P1 链 / P2 写穿多输出,已接入Adam::step/AdamW::step;P3 跨链批量派发(NN_IRC_NO_BATCH=1A/B、NN_IRC_TRACE=1观测)。混精度下的未登记复合结构逐成员降级派发(可观测,非错误),未登记单节点仍硬报错。全 f32 逐形态 GPU 对拍(fused_gpu_test)与图融合端到端(expr_fuse_test,多输出逐位 err=0)为回归锁。 - 三值权重
T1_58/BitLinear(precision.hpp+compute_layer_bitlinear.hpp):离散化不是舍入、不进提升序、P1 里不是存储精度(只作param槽声明 + 层能力集合取值);BitLinear= 打包权重 +matmul_q(训练/解码同一条路)。CLI--precision-param t1_58与预设--t1_58均存在(词法唯一t1_58;预设是"整份 profile 快捷方式")。MLP 与 GPT 已接线;RAPT / ViT 明确拒绝。续训入口(P1-7):三值.nnmodel用WeightLoadIntent::Trainable按文件 τ 反量化成 f32 latent(L = trit·τ·K/n= 量化器不动点)⇒ SFT / QAT 可续训;推理侧仍走默认Inference部署只读态。结构开关(默认关):SubLN / GQA(n_head_kv,块映射)/ tied embedding / ReLU²,见docs/todo/22。 - BitNet b1.58 2B4T 结构开关:SubLN(子层内部,非
NormType新值)/ GQA(n_head_kv,块映射kv=h/(n_head/n_head_kv),训练路径ExprViewKind::HeadGroup不展开 K/V)/ tied embedding(head 复用token_emb_,dW累加)/ ReLU²(ReLU2GLU)。ModelSpec追加subln/n_head_kv/tie_embeddings(缺键 = 旧语义,不升版本)。见docs/todo/22。 - 多精度:
Precision/PrecisionProfile{param,compute,stable,optimizer};f16 走基类 NVI 边界 cast + 引擎 in-kernel f16 变体 + op-level f16 GEMM(-DNN_SHADER_F16=1编第二份 SPIR-V);--f16={param:F16, compute:F16, stable:F32, optimizer:F32}。语义契约docs/development/05。 - 推理采样截断(
SamplingConfig+sample_next_token_,CPU 侧 host 采样):temperature / top-k / top-p(核采样,保留集内重新归一化)/ 重复惩罚(可选repeat_window)/ 固定种子(has_seed);顺序固定 = 重复惩罚 → 温度 → top-k → top-p → softmax → 采样/贪心,全部关闭时与旧路径逐位一致(sampling_test锁:下标 + 概率向量 + RNG 消耗序);text_infer暴露--top-k/--top-p/--repeat-penalty/--repeat-window/--seed(同 seed 同 prompt ⇒ 同输出)。 - 多轮对话闭环(P0-5 尾项,2026-10-16):prompt 构建唯一真源 =
Tokenizer::chat_prompt(BOS<|system|>…模板 + 末轮 user 补<|assistant|>+ 超seq_len时按一问一答整轮丢弃,system 块与末轮恒保留);"上下文不丢"的可验证形式 = 第 N 轮的 prompt 以第 N-1 轮为逐 token 前缀。三条使用路径:--interactive(进程内累积,助手轮用生成的原 token 回灌)/--turn <role>:<text>(历史由调用方自带,末条必须 user;合法形状 = 交替、user 起头)+--reply-out <path>(回复落盘 → GUI 取回复不解析日志)/ GUI「GPT 推理」页的「💬 发送(多轮)」+「🧹 清空对话」(ChatSession持有历史,无状态协议:每轮把整段历史交回 CLI ⇒ 每轮都能改采样参数)。门禁chat_prompt_test(模板/前缀不变式/截断/纯文本回退 + 真实 CLI 子进程端到端)+bench/gui_cli_audit.py第 [7] 节(Python 侧参数面)。 - 训练性能:matmul 细 tile + K 切分二次归约(
#ks/#kr,逃生阀NN_MM_NO_KSPLIT/NN_MM_KSPLIT_S)、录制窗口排空隐藏(copy_fromfresh 上传,逃生阀NN_COPY_FROM_DRAIN=1)、SpecBuilder输入槽去重、CE online-softmax 单趟 fold(P4)、RAPT 逐头 1/rms 缓存设备侧拼装(rms_norm_forward_,不再逐头 GPU↔CPU 往返;P4-2a,端到端 −29.7%)。⚠ 去重的覆盖约定:含重复同张量输入的表达式必须有 dry-run/模型 pass 覆盖,否则运行期去重后的新 key 未登记 = 闭合世界硬报错(Softmax::backward中招先例见 history)。⚠ RAPT 的scan_prefix已不是最大单项(P4-2b 已修,见下条);下一步 e2e 的瓶颈是 host 侧 staging 等待(copy_from≈66% 墙钟)。 - 状态扫描原语(
scan_prefix_outer/scan_suffix_outer,RLA/RAPT):1 个 workgroup/头、256 线程。前缀扫描专用路径(dk ≤ 64)是行归属结构(线程 a 独占 A/B 第 a 行 ⇒ per-t 只 3 次 barrier、1 次全局往返;内层循环 4 路展开只提 ILP、不改累加次序)⇒ 与 CPU 实现逐位一致(门禁scan_bitwise_test,61 用例 CPU↔GPU 逐位比对)。dk ≤ 32 走窄共享变体(-DNN_SCAN_DK_MAX=32,共享 8.5KB ⇒ 每 SM 6 个 WG)。实测(850M / RAPT d128·h4 形状):单次 7.4–7.9 → 1.64 ms、端到端 step −42.6%(同会话配对)。⚠ 改这块前先读docs/history/history.md「P4-2 (b)」条(消融数据、suffix 为何不改、layer_bench --op scan_* --hash的用法与"shader 迭代只需重建layer_bench"的捷径)。 - 扫描逐位门禁的平台口径:
scan_bitwise_test的逐位锚以 Linux Release + 850M 为权威;Windows(40HX)与 Debug(-O0)的 CPU/GPU FMA 路径不同,按 77 skip;数值正确性仍由gpu_test的容差用例覆盖。 - 训练 step 剖析器(
NN_PROFILE=1门控,core_profile.hpp):阶段/原语/设备 kernel/busy%/per-key 归因 +NN_PROFILE_TRACEChrome trace;默认关闭零开销。 - 训练显存 / 稳定性:梯度检查点
set_checkpoint_every、activation offloadset_activation_offload(两者可混用)、文档掩码set_doc_ids、batch flushset_flush_interval、防 NaN 跳步。引擎侧 API =create_offload_buffer/offload_save/offload_restore(不存在set_offload_enabled)。 - 归一化:
--norm auto|layernorm|rmsnorm|batchnorm(全架构通用)×--norm-place auto|none|conv|head|both|final(位置);auto= 按架构默认(MLP/ViT=LayerNorm、CNN=BatchNorm@conv、ViT=LayerNorm@final)。BatchNorm双态(set_training转发)+extra_state()序列化。旧文件缺norm_place键 →none(旧布局)。 - 位置编码:
PositionEncoder多态基类(Learned/Sinusoidal/RoPE/ALiBi/None),注入点分三组、所有权按注入点划分(模型持嵌入侧、注意力层自持注意力侧);ALiBi 偏置是掩码之后的独立一步(不融进掩码表达式)。 - 注意力单 fold kernel:
FoldSpec分块流式(S 不物化),掩码AttnMaskKind× 位置偏置 两正交维度。 - CNN 全引擎化:
im2col/col2im+rearrange_3d,无 PCIe 往返。 - 分词器:保序并行
encode(set_encode_threads,任意并行度逐字节一致)+ 零堆分配合并 +encode_docs_parallel。 - 统一数据集:KVRecord v2 /
.nnvocab(旧 JSON 不直读,dataset_convert vocab迁移)/.nndataset(nn::Dataset只读类,词表/掩码/test 子集随数据集);P2b 起样本概念显式——存储单位 = doc,训练单位 = sample(dataset_samples.hpp的TextSampleSet,文本 = 滑动窗口;step是ceil(样本数/batch)派生量),数据集可经text_trainer.hpp直接接入训练工厂。text_train位置参数是<dataset.nndataset>(--vocab/--test-file/--loss-scope/--no-cache已移除)。见docs/todo/19。 - 模型文件角色(v8):一份格式两种角色——
*.nnmodel(推理,三值层写打包形态 base-3 1.6 bpw)与*.nnckpt(训练断点,写 f32 latent + 训练状态段);参数级kind(参数形态协议)+ 载入意图WeightLoadIntent(Inference部署只读 /Trainable反量化 warm start,P1-7);--resume两种角色都收(.nnckpt= 断点续训、.nnmodel= warm start)。见docs/todo/23。 - 错误处理宏族:传播
NN_TRY/NN_TRY_MSG/NN_TRY_CHECK、解包+终止NN_UNWRAP(expr[, "语境"])(语义 = unwrap,不是 assert;旧名NN_CHECK/NN_EASY_CHECK保留为等价别名)、解包+退出码NN_EXIT(expr, code[, "语境"])、错误源NN_FAIL("msg")。手写形态已全量迁宏(残留 0,门禁doc_inventory.ps1§5 基线 0)。 - RL 与可组合训练(2026-10-23,RL-0…RL-2b ✅):
TensorSchema/NamedTensorSet/GradientBundle;Model::forward_named/backward_named+OutputBundleLayer,RowBandPacking仅作旧单 Tensor ABI 适配,ModelSpec 可往返 schema/packing;ExperienceSchema/TensorRecord/PolicyBatchView+ GAE/return/group transform;可替换PolicyDistribution(首版 categorical)与 REINFORCE/PPO/GRPO preset;TrainingGroupSession管具名 trainable/readonly role;RLTrainingPipeline固定rollout→buffer→sample→update并可恢复 seed/计数。新增 RL 内联表达式必须纳入scan_exprs闭合世界。详见docs/usage/06-reinforcement-learning.md与docs/todo/31-rl-training-primitives.md。 - CPU 性能:DSL 模板向量化/并行、
dsl::compute_into零分配原地更新、分块 GEMM(BLOCK_SIZE=64)。 - Vulkan 多设备选择:
--gpu/NN_VULKAN_DEVICE(cli/cli_gpu_option.hpp)。
gpu_stability_probe(机器/stdlib 相关;历史值与变更先例见 docs/history/history.md):
- init 锚(
--init-hash):Debugmnist_mlp a22e807ee05ec3ac/cnn 2d7ecb570d91fd37/mnst_transformer 27f50137087d09b5/gpt daec9e935f9b4c68/rapt d31c2e46bda6f3b5;Release3e982d018c00d94a/d8c8457f0053c4f9/70c4c085052268b2/f8c2d892f5834239/7002b986b84f679f。 - 字节锚(
--steps 20):Debug CPU8809f07142e29472/ GPU dev2c69a3378c7dcc037;Release CPUf360f9bdbbfbe12c/ GPU dev24103194cb9e3e230。 - 默认结构变更移动锚(新增参数经 InitSpec seed 混流整体推移);数值算法变更(求和/归约序)移动字节锚 —— 处置 = 重测 + 更新本节 + history 记录新旧值。
二期范围 = S1-S5、S7(S6 自动窗口不接线;见 docs/todo/02-operator-fusion.md,分期流水见 docs/history/history.md)。
- 运行时值禁进表达式常量池(进
expr_spec_key会破坏闭合世界):scale折进 Q(forwardscale_inplace+ backward 补乘)、inv_num_valid后置scale_inplace。 - BatchCol 视图要求
(1, BH*seq)(doc_ids 按 (b,h) 块重复),(1, batch*seq)会越界。 - RowGather 主输入行数≠网格行数(loss_vec 在 (1,N) 读 (C,N) logits),校验只查 cols。
- key 只描述结构(= 自登记锚点的前提;契约回归测试
src/expr_fused_key_test.cpp,并入expr_cpu_test):常量池值、MatmulSpec.transA/transB、分组归约 R 三者不进expr_spec_key——值走 push constantc<i>,转置走mm_trans(shader 内两条加载路径 + uniform 分支),R 走视图参数vp(gr_r<i>()运行期循环)。精度签名同样不进 key:key#x= 运行期精度分派结构变体(双视图 + PCprec),key#a= native16 结构变体,两者都不带签名 hex。反之属于结构、必须进 key:AttnScoreMask::mask_kind()、FoldSpec.tri_skip。生成阶段emit_spec的 ±inf 常量必须用numeric_limits。 - matmul + 列归约可用:
generate_glsl_reduce列分支按元素分解 batch(batch = row/m_per,遍历全部rows = batch*m_per行);覆盖 =expr_cpu_test::col_max(matmul)(batch=2 独立标量参考)+expr_gpu_testcol 对拍(广播/向量/batch=2)。 - IR 扩展点:MatmulSpec.batch(不进 key,dispatch z)、MatmulSpec.transA/transB(不进 key,PC
mm_trans)、分组归约 R(不进 key,PCvp)、Row/Col/Batch 操作数(6/7/8)、RowGather(9)/BatchMod(10)/BatchCol(11);注意力 forward = 单 fold kernel(FoldSpec)构造期定型,bwd = 掩码 → 位置偏置(独立一步)→ softmax 的 R/X 表达式 + 3 个batched_matmul;CE 稠密denom = col_sum(exp(logits-cb(col_max))),稀疏 grad/loss_vec 用 Row+RowGather。
⚠
row()的批内语义依赖MatmulSpec.batch:dsl::row()在 IR 里是"批内行号",其分解来自同一 ExprSpec 里的 matmul 段。把原本融在 matmul 表达式里的项(如 ALiBi 的col − row)拆成独立一步后,新 spec 没有 matmul → batch 退化为 1 →row()变全局行号 → 静默错值。独立成步的表达式若需要"批内位置",请用(rows,1)行表 +dsl::row_broadcast(AlibiPositionEncoder::apply_score_bias即此写法)。含占位eval()的节点(RowIdxLeaf/ColIdxLeaf/RowGatherRef/BatchModRef/BatchColRef)真实语义只由 IR 解释器提供,dsl::compute_into的cpu_preparable_v白名单不会放它们进模板路径。
变更本文件时务必同步 git 状态:
CMakeLists.txt的project(... VERSION ...)可能滞后于 git tag,以 git tag 为准。