From 51d7256a3eb6bbc6aeac953ca854746ac0ff3523 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 3 Nov 2026 07:01:06 +0800 Subject: [PATCH 01/16] =?UTF-8?q?submodule:=20engine=20=E2=86=92=20d2dd5d1?= =?UTF-8?q?=EF=BC=88kernel=5Fcache=20=E5=AE=8F=E6=97=8F=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine b/engine index 03e8670..d2dd5d1 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit 03e8670629925e5ec354bc4628add492e34a9424 +Subproject commit d2dd5d191d616c98e5ab5e6e7bebbd9f8a053f64 From b28bfd7881ca2980e80383145e16738985d1aa2e Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Tue, 3 Nov 2026 07:42:37 +0800 Subject: [PATCH 02/16] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E4=B8=BB?= =?UTF-8?q?=E4=BB=93=E4=B8=8E=20compute=20=E5=AD=90=E4=BB=93=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E7=9A=84=E4=BB=A3=E7=A0=81=E2=80=94=E2=80=94=E5=88=A0?= =?UTF-8?q?=20shaders/=EF=BC=8823=20=E4=B8=AA=20.comp=20=E4=B8=8E=20engine?= =?UTF-8?q?=20=E5=AE=8C=E5=85=A8=E7=9B=B8=E5=90=8C=E4=B8=94=E6=97=A0?= =?UTF-8?q?=E5=BC=95=E7=94=A8=EF=BC=8Crc0=20=E8=BF=81=E7=A7=BB=E6=AE=8B?= =?UTF-8?q?=E7=95=99=EF=BC=89+=208=20=E4=B8=AA=E5=BC=95=E6=93=8E=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=A4=B4=EF=BC=88expr=5Fdsl/expr=5Ffold/expr=5Fregist?= =?UTF-8?q?ry/ternary=5Fpack/int8=5Fpack/kernel=5Fcache/kernelgen=5Fapi/nn?= =?UTF-8?q?=5Fapi=EF=BC=89=E6=94=B9=E7=94=B1=20engine/engine/include=20?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=EF=BC=88include=20=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=85=9C=E5=BA=95=EF=BC=8C=E6=B6=88=E8=B4=B9=E4=BE=A7=E5=A4=B4?= =?UTF-8?q?=E4=BB=8D=E5=9C=A8=E4=B8=BB=E4=BB=93=EF=BC=89=EF=BC=9Bdoc=20?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 8 +- include/neuralnet.cpp/expr_dsl.hpp | 1990 ----------------------- include/neuralnet.cpp/expr_fold.hpp | 207 --- include/neuralnet.cpp/expr_registry.hpp | 89 - include/neuralnet.cpp/int8_pack.hpp | 83 - include/neuralnet.cpp/kernel_cache.hpp | 459 ------ include/neuralnet.cpp/kernelgen_api.h | 60 - include/neuralnet.cpp/nn_api.h | 102 -- include/neuralnet.cpp/ternary_pack.hpp | 206 --- research/rla/01-what-is-rla.md | 2 +- shaders/batched_matmul.comp | 322 ---- shaders/cast.comp | 53 - shaders/col2im.comp | 89 - shaders/elementwise_v2.comp | 284 ---- shaders/gather.comp | 108 -- shaders/group_reduce.comp | 70 - shaders/im2col.comp | 78 - shaders/matmul.comp | 92 -- shaders/matmul_gemv.comp | 312 ---- shaders/matmul_q.comp | 236 --- shaders/matmul_q_gemv.comp | 204 --- shaders/matmul_tiled.comp | 417 ----- shaders/nn_f16_rne.glsl | 30 - shaders/outer_col.comp | 48 - shaders/rearrange_3d.comp | 73 - shaders/reduce.comp | 242 --- shaders/scan_prefix_outer.comp | 216 --- shaders/scan_prefix_outer_gen.comp | 125 -- shaders/scan_suffix_outer.comp | 88 - shaders/scan_suffix_outer_gen.comp | 85 - shaders/scatter_add.comp | 124 -- shaders/transpose.comp | 117 -- shaders/trit_pack.comp | 53 - 33 files changed, 8 insertions(+), 6664 deletions(-) delete mode 100644 include/neuralnet.cpp/expr_dsl.hpp delete mode 100644 include/neuralnet.cpp/expr_fold.hpp delete mode 100644 include/neuralnet.cpp/expr_registry.hpp delete mode 100644 include/neuralnet.cpp/int8_pack.hpp delete mode 100644 include/neuralnet.cpp/kernel_cache.hpp delete mode 100644 include/neuralnet.cpp/kernelgen_api.h delete mode 100644 include/neuralnet.cpp/nn_api.h delete mode 100644 include/neuralnet.cpp/ternary_pack.hpp delete mode 100644 shaders/batched_matmul.comp delete mode 100644 shaders/cast.comp delete mode 100644 shaders/col2im.comp delete mode 100644 shaders/elementwise_v2.comp delete mode 100644 shaders/gather.comp delete mode 100644 shaders/group_reduce.comp delete mode 100644 shaders/im2col.comp delete mode 100644 shaders/matmul.comp delete mode 100644 shaders/matmul_gemv.comp delete mode 100644 shaders/matmul_q.comp delete mode 100644 shaders/matmul_q_gemv.comp delete mode 100644 shaders/matmul_tiled.comp delete mode 100644 shaders/nn_f16_rne.glsl delete mode 100644 shaders/outer_col.comp delete mode 100644 shaders/rearrange_3d.comp delete mode 100644 shaders/reduce.comp delete mode 100644 shaders/scan_prefix_outer.comp delete mode 100644 shaders/scan_prefix_outer_gen.comp delete mode 100644 shaders/scan_suffix_outer.comp delete mode 100644 shaders/scan_suffix_outer_gen.comp delete mode 100644 shaders/scatter_add.comp delete mode 100644 shaders/transpose.comp delete mode 100644 shaders/trit_pack.comp diff --git a/CMakeLists.txt b/CMakeLists.txt index a941b0f..d832481 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,10 +338,16 @@ add_library(nn_core INTERFACE) # 主仓库已删除与抽象层重复的接口/值类型头(algebra_* / compute_engine / # compute_tensor / core_* / expr_spec 等),同名头一律解析到抽象层 —— 与 # libnn-cpu.so / libnn-gpu.so(LGPL)按同一份头编译,跨界 ABI 一致。 +# engine/engine/include(LGPL 实现目录)放**最后**兜底:主仓库不再维护 +# 与引擎重复的功能头副本(expr_dsl / expr_fold / expr_registry / ternary_pack / +# int8_pack / kernel_cache / kernelgen_api / nn_api,2026-11 v1.16.0 后去重), +# 消费侧头(层/模型/训练/数据集/分词器)仍在主仓库 include/ 优先命中。 target_include_directories(nn_core INTERFACE $ $ - $) + $ + $ + $) target_link_libraries(nn_core INTERFACE Threads::Threads) if(NN_STACKTRACE_LIB) target_link_libraries(nn_core INTERFACE ${NN_STACKTRACE_LIB}) diff --git a/include/neuralnet.cpp/expr_dsl.hpp b/include/neuralnet.cpp/expr_dsl.hpp deleted file mode 100644 index 287ff61..0000000 --- a/include/neuralnet.cpp/expr_dsl.hpp +++ /dev/null @@ -1,1990 +0,0 @@ -#pragma once - -// ═══════════════════════════════════════════════════════════════════════════ -// expr_dsl.hpp — 统一表达式 DSL(编译期模板,唯一前端) -// -// 目标:所有逐元素计算都用"普通数学写法"写出,编译期融合: -// auto e = leaf(q) * row_mod(cos, dk) + rotate_half(leaf(q), dk) * row_mod(sin, dk); -// engine.compute(e, rows, cols); -// -// 单一事实来源 = 这一整段表达式本身(编译期类型)。两个消费方共用: -// 1. CPU:直接把表达式当作编译期 AST,逐元素求值 → 编译器内联 + SIMD 融合, -// 等价手写 for 循环(零开销抽象)。 -// 2. GPU(AOT,无运行时生成):to_expr_spec(expr) 在编译期把同一表达式折叠 -// 成扁平 ExprSpec → 与预生成融合 shader 比对 → dispatch。 -// -// AOT 注册表约定:代码里用到的表达式集合 = 构建期 AOT 生成的 shader 集合 -// (预热加速器)。运行时未命中默认走运行期 JIT 现编兜底;NN_STRICT_CLOSED_WORLD=1 -// 恢复硬报错(把"难以察觉的漂移"变成"可选的立即暴露")。 -// -// 设计要点: -// - 算子复用 nn::ops(唯一的算子来源,op_id() 统一映射到 ExprOp)。 -// - 叶子持有 Tensor(零拷贝 shared_ptr);视图(RotateHalf/RowMod)是索引 -// 映射叶子,不物化中间张量。 -// - 运算符重载/概念约束:普通 + - * / 与比较、select 均照常书写。 -// ═══════════════════════════════════════════════════════════════════════════ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include // NN_PROBES_ENABLED(探针编译门) -#include // nn::parallel_for_samples -#include "core_domain.hpp" -#include "core_env.hpp" // RV-09:环境变量读取唯一入口 - -namespace nn::dsl -{ -// 环境变量开关(RV-09:实现收敛到 core_env.hpp;本函数保留 dsl 门面名, -// 供 layer/engine 侧的诊断开关调用)。 -// ⚠ 探针门:默认构建(未 -DNN_ENABLE_PROBES=ON)里恒返回 false 且**不求值 getenv** -// → 调用点被编译期折叠,"NN_F16_DEBUG" 之类的诊断开关不会在正常训练里生效。 -inline bool env_flag(const char* name) -{ return ::nn::detail::probe_flag_raw(name); } -} // namespace nn::dsl -#include -#include - -#include "compute_tensor.hpp" -#include "compute_engine.hpp" -#include -#include "expr_spec.hpp" -#include "expr_registry.hpp" -#include "algebra_ops.hpp" // nn::ops(唯一算子来源,含 op_id()) -#include "algebra_matrix.hpp" // Matrix - -// ── 表达式概念 nn::Expression / nn::BoolExpression(RV-13:定义已移到 L1 的 -// algebra_span.hpp——它描述的是 L1 原语的契约;本头经 algebra_matrix.hpp -// → algebra_span.hpp 传递可见)──────────────────────────────────────── - -namespace nn::dsl -{ - -// M1(docs/internal/17 §4.1):Tensor 存储访问已私有化,DSL 求值器经 -// 引擎域内通道 detail::TensorAccess 创建/读写张量(friend 关系,库外禁用)。 -using TensorAccess = ::nn::detail::TensorAccess; - -// ── DSL 逐元素模板路径的并行门控 ──────────────────────────────────────── -// 低于全局 nn::PARALLEL_THRESHOLD(524288)。全局值按"裸逐元素 add 的内存 -// 带宽回本点"标定;而本路径折叠的是多算子融合表达式(每元素 2~6 次算术, -// 还可能含 exp/tanh/sqrt),per-element 成本更高,回本点更低。 -// LayerNorm/RMSNorm 的典型形状 768×512 = 393216 低于全局门控 → 若按全局 -// 门控判定会整段串行,故本路径需要更低的专属门控。 -// 131072 元素 × ~2ns ≈ 0.26ms,远超并行区启动开销(实测 ~100µs)。 -inline constexpr std::size_t kDslParallelThreshold = 131072; - -// ══════════════════════════════════════════════════════════════════════════ -// 内部求值/折叠构建器:把编译期表达式折叠成扁平 ExprSpec + 输入张量列表 -// ══════════════════════════════════════════════════════════════════════════ -struct SpecBuilder -{ - ExprSpec spec; - std::vector inputs; - - ExprOperand add_const(Scalar v) - { - spec.consts.push_back(v); - return expr::cst(static_cast(spec.consts.size() - 1)); - } - // 运行时标量参数(RParam):值作为运行时数据存入 spec.rparams,不进 - // expr_spec_key;同结构不同值共享一个融合 shader(glsl_gen 把 rparams - // 作为 push constant 读取,dispatch 时按实际 spec 填充)。用于优化器的 - // lr/eps/β、偏差修正系数等每步会变、但结构固定的标量。 - ExprOperand add_rparam(Scalar v) - { - spec.rparams.push_back(v); - return expr::rval(static_cast(spec.rparams.size() - 1)); - } - // ── 中心化输入登记(含去重)───────────────────────────────────────── - // 同一张量(same_storage:同设备/精度/存储/形状)+ 同一视图 → 复用已有 - // 槽,不新增绑定。收益(GPU/IR 路径): - // ① 归约视图只算一遍——RowReduceMax 这类视图在归约生成器里各占一个 - // 全矩阵 pass,重复槽 = 白读一遍 S(反传 W 重算的 softmax 实测 - // 4 槽 → 2 槽、行 max pass 减半,见 docs/history 本条); - // ② 输入下标一致后 CSE 才能把重复子链合并(两次 leaf(S)-rowmax(S) - // 此前因操作数下标不同而永不合并); - // ③ 绑定/描述符更少。 - // **仅对有效张量判同**:锚点符号实例(FusedAnchor 的占位张量,无存储) - // 一律不合并——锚点侧结构由 dry-run/模型 pass 的真实张量路径覆盖, - // 两侧走同一 SpecBuilder,保证扫描期登记与运行期 key 一致。 - // RowGather 这类"登记后改写视图"的调用方必须传 dedup=false: - // 若复用已有槽再覆写 views[i],会污染原输入的视图。 - ExprOperand add_input(const Tensor& t, const ExprView& v, bool dedup = true) - { - if (dedup) - { - for (std::size_t i = 0; i < inputs.size(); ++i) - if (spec.views[i] == v && inputs[i].same_storage(t)) - return expr::input(static_cast(i)); - } - spec.views.push_back(v); - inputs.push_back(t); - return expr::input(static_cast(inputs.size() - 1)); - } - ExprOperand add_input_linear(const Tensor& t, bool dedup = true) - { return add_input(t, expr::linear(), dedup); } - ExprOperand add_input_rotate(const Tensor& t, std::uint32_t block) - { return add_input(t, expr::rotate_half(block, /*negate_first_half=*/true)); } - ExprOperand add_input_rowmod(const Tensor& t, std::uint32_t mod) - { return add_input(t, expr::row_mod(mod)); } - ExprOperand add_input_rowaccess(const Tensor& t, std::uint32_t offset, std::uint32_t mod) - { return add_input(t, expr::row_access(offset, mod)); } - // 分组归约视图输入:输出 (G,N)、输入 (G*R,N),out[r][c] = Σ/max over - // t(row,col)`(见 - // ExprViewKind::GroupedReduce* 注释)。 - ExprOperand add_input_grouped_reduce(const Tensor& t, std::uint32_t R, bool is_max) - { - ExprView v; - v.kind = static_cast(is_max ? ExprViewKind::GroupedReduceMax - : ExprViewKind::GroupedReduceSum); - v.param = R; - return add_input(t, v); - } - // 归约视图输入:该输入被归约为每行/每列一个标量((rows,1)/(1,cols)), - // 求值期自动广播——参与算术时按当前 (r,c) 读取对应标量。 - ExprOperand add_reduce_input(const Tensor& t, ExprViewKind kind) - { - ExprView v; - v.kind = static_cast(kind); - return add_input(t, v); - } - // 归约指令:对某表达式结果(操作数 a)归约 → 隐式"每行/每列一个标量" - // 的归约向量,后续指令经 Reduce 操作数按行/列广播访问。 - ExprOperand add_reduce_instr(ExprOp op, ExprOperand a) - { - ExprInstr in; - in.op = static_cast(op); - in.dst = static_cast(spec.num_regs++); - in.a = a; - spec.instrs.push_back(in); - return expr::reduce(in.dst); - } - // 广播视图:输入本身已是 (rows,1)/(1,cols) 小向量,按行/列广播 - ExprOperand add_input_rowbroadcast(const Tensor& t) - { return add_input(t, expr::row_broadcast()); } - ExprOperand add_input_colbroadcast(const Tensor& t) - { return add_input(t, expr::col_broadcast()); } - ExprOperand add_instr(ExprOp op, ExprOperand a, ExprOperand b = {}, ExprOperand c = {}) - { - ExprInstr in; - in.op = static_cast(op); - in.dst = static_cast(spec.num_regs++); - in.a = a; - in.b = b; - in.c = c; - spec.instrs.push_back(in); - return expr::reg(in.dst); - } -}; - -// ══════════════════════════════════════════════════════════════════════════ -// 编译期求值的"输出列数感知"分发:eval_with_cols -// -// nn::Expression 的契约是 eval(i):只有一个扁平下标,**不知道输出列数**。 -// 大部分叶子是列数无关的(Linear/RotateHalf/RowMod/RowAccess 都能从自身 -// 张量推出列数),但有一类视图必须知道"输出列数"才能把扁平下标还原成 -// (row, col):RowBroadcast(输入 (rows,1),读第 i/cols 个标量)。这类节点 -// 额外提供 eval(i, cols);复合节点把 cols 透传给子节点: -// eval_with_cols(child, i, cols):子节点有 2 参 eval 就用它,否则退回 eval(i)。 -// -// 只有真正需要"全行/全列"信息(归约视图/归约指令/matmul/网格索引)的表达式 -// 才走引擎解释器(由 has_reduction_v 分流);纯索引映射(含广播视图)留在 -// 模板求值路径 → 编译器内联 + 与其它逐元素路径共用同一并行门控。 -// ══════════════════════════════════════════════════════════════════════════ -template -concept ColsAwareEval = requires (const T& t, std::size_t i, std::size_t cols) { - { t.eval(i, cols) } -> std::convertible_to; -}; - -template -[[nodiscard]] Scalar eval_with_cols(const E& e, std::size_t i, std::size_t cols) -{ - if constexpr (ColsAwareEval) return static_cast(e.eval(i, cols)); - else return static_cast(e.eval(i)); -} - -// ══════════════════════════════════════════════════════════════════════════ -// eval_into_span — 把逐元素表达式写进既有 span(eval_cpu / compute_into 共用) -// -// 与逐元素原语(algebra_matrix 的 apply/transform/原地运算)**同构**: -// - n < kDslParallelThreshold:串行计数循环 + NN_VECTORIZE_PRAGMA -// - n ≥ kDslParallelThreshold:自建分块并行,块内同样是**带向量化提示的裸 -// 指针计数循环**(不用 nn::for_each(iota):其分块内层在池里,无法为该 -// 循环单独加向量化提示——池被多种读写模式复用,不能全局 assume_safety; -// 实测大张量下慢 1.3–1.5 倍) -// NN_VECTORIZE_PRAGMA(clang `loop vectorize(assume_safety)` / GCC ivdep)是 -// 关键:内层循环读写的是多个"来源未知"的指针(目标 + 各叶子),编译器无法 -// 证明互不别名;不给提示时不向量化,实测逐元素 add 慢 3 倍以上。 -// 语义安全性:本循环是纯 map —— 每个输出元素只依赖**同一下标**的输入, -// 先读完再写回。无论目标是否与某输入同 buffer,向量化后的"载入同下标向量 -// →计算→存回"与串行逐一读改写逐字节一致(无跨下标依赖)。 -// -// 门控说明:这里用 kDslParallelThreshold(131072)而非全局 PARALLEL_THRESHOLD -// (524288)。全局值是按"裸 add 的内存带宽回本点"标的;而本路径折叠的是 -// 多算子融合表达式(每元素 2~6 次算术 + 可能的 exp/tanh),per-element 成本 -// 更高,回本点更低。典型 LayerNorm/RMSNorm 形状 768×512=393216 正好低于 -// 524288——按全局门控会**整段串行**(归一化层将只用 ~1 核),故本路径 -// 取更低的专属门控。 -// ══════════════════════════════════════════════════════════════════════════ -template inline void eval_into_span(const E& e, Span d_span, std::size_t cols) noexcept -{ - Scalar* d = d_span.data(); - const std::size_t n = d_span.size(); - if (n == 0) return; - if (n < kDslParallelThreshold) - { - NN_VECTORIZE_PRAGMA - for (std::size_t i = 0; i < n; ++i) - d[i] = eval_with_cols(e, i, cols); - return; - } - - const std::size_t hw = static_cast(std::thread::hardware_concurrency()); - // 分块数:以 ~64K 元素/块为目标,上限 hw*4 以保证负载均衡。 - // 65536 的取值依据(层级别实测裁决):更小的分块(4096/16384)在隔离 - // kernel 上有收益但层级别反而变差(layernorm 2.8→3.2ms、rmsnorm - // 1.7→1.9ms)——隔离内核的收益不会传递到层;再调整必须先有层级别证据。 - // 并行区固定开销实测 ~100µs(32 线程)/ ~3~16µs(≤16 线程)。 - constexpr std::size_t TARGET_CHUNK = std::size_t{1} << 16; - const std::size_t n_chunks = - std::clamp(n / TARGET_CHUNK, std::size_t{1}, std::max(hw, 1) * 4); - const std::size_t base = n / n_chunks; - const std::size_t rem = n % n_chunks; - nn::parallel_for_samples(n_chunks, - [&e, d, cols, base, rem](std::size_t c) noexcept - { - const std::size_t off = c * base + std::min(c, rem); - const std::size_t len = base + (c < rem ? 1 : 0); - Scalar* dc = d + off; - NN_VECTORIZE_PRAGMA - for (std::size_t k = 0; k < len; ++k) - dc[k] = eval_with_cols(e, off + k, cols); - }); -} -// ══════════════════════════════════════════════════════════════════════════ -// CpuViewCache — 叶子节点的 CPU 数据视图缓存 -// -// 逐元素模板求值会**按元素**读取叶子(e.eval(i))。若每个元素都重新 -// t.cpu_matrix().span(),会引入固定开销:variant 槽检查 + span 构造 —— -// 实测使模板路径比等价手写循环慢 1.5–4 倍(原地原语只做一次指针遍历)。 -// 因此在叶子构造时缓存一次数据视图(ConstSpan 只是 (指针, 长度),16 字节)。 -// -// 非 CPU 张量(GPU)**不**触发 cpu_matrix():GPU 路径只走 to_spec 折叠, -// 从不调用 eval;此时缓存留空,eval 走兜底(理论上不会到达该分支)。 -// ══════════════════════════════════════════════════════════════════════════ -struct CpuViewCache -{ - // CPU 张量的数据指针与列数(构造时缓存一次)。非 CPU 张量(GPU)留空: - // GPU 路径只走 to_spec 折叠,从不调用 eval。 - // 用裸指针而非 ConstSpan:at() 必须**无分支**,否则编译器无法向量化 - // 内层循环(实测带 `empty() ? ... : ...` 兜底分支时原地 add 慢 5 倍)。 - const Scalar* data = nullptr; - std::size_t cols = 0; - - // f16 源:§7.2 定义下 f16 的逐元素语义 = "f32 参考计算 + 输出舍入",故 - // 求值前把 f16 叶子**一次性**抬到 f32 副本,at() 仍是无分支的 f32 读。 - // (把 f16 转换塞进 at() 会给 f32 热路径引入每元素分支 → 实测同类分支 - // 使模板路径慢 3-5 倍,不可接受;这里用一次 O(n) 转换换零热路径代价。) - std::shared_ptr f32_view; - - void cache_cpu_view(const Tensor& t) - { - if (!t.is_cpu()) return; - // 符号张量(默认构造:未绑定、无存储)——AOT 自登记需要"从表达式类型 - // 折叠出结构",那条路径只看 to_spec 折出的视图/指令,不会 eval;此处 - // 直接返回,避免在无存储张量上取 CPU 视图(原会 NN_ASSERT 硬失败)。 - if (!t.valid()) return; - if (t.precision() == Precision::F16) - { - auto m = std::make_shared(t.rows(), t.cols()); - const auto s = TensorAccess::cpu_matrix(t).span(); - auto d = m->span(); - for (std::size_t i = 0; i < s.size(); ++i) - d[i] = static_cast(s[i]); // 升 cast:精确无损 - f32_view = std::move(m); - data = f32_view->span().data(); - cols = t.cols(); - return; - } - const auto& m = TensorAccess::cpu_matrix(t); - data = m.span().data(); - cols = t.cols(); - } - [[nodiscard]] Scalar at(std::size_t i) const - { - // RV-01:失败后 `data[i]` 是空指针解引用 → 无条件终止 - NN_ASSERT_ALWAYS(data != nullptr, - "DSL 叶子 eval 仅适用于 CPU 张量(GPU 路径走 to_spec 折叠)"); - return data[i]; - } -}; - -// ══════════════════════════════════════════════════════════════════════════ -// 叶子节点(同时满足 nn::Expression:可 eval(i);以及可 to_spec(SpecBuilder&)) -// ══════════════════════════════════════════════════════════════════════════ - -// 标量常量 -struct ConstLeaf -{ - Scalar value; - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return value; } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_const(value); } -}; - -// 运行时标量参数叶子(RParam):常量与运行时的折中。值参与算术但**不进 -// expr_spec_key**(非表达式结构),glsl_gen 把 rparams 作为 push constant -// 读取;CPU 求值用 spec.rparams[idx]。适配"每步会变的标量"(优化器超参)。 -struct RParamLeaf -{ - Scalar value; - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return value; } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_rparam(value); } -}; - -// 线性叶子:直接读取 Tensor 数据(row-major 扁平) -struct TensorRef : CpuViewCache -{ - Tensor t; - - TensorRef(Tensor tt) : t(std::move(tt)) { cache_cpu_view(t); } - // 符号实例(AOT 自登记 `FusedAnchor`):`t` 默认构造(未绑定、无存储, - // cache_cpu_view 对 !valid() 直接返回)——结构只由类型决定 - TensorRef() = default; - [[nodiscard]] Scalar eval(std::size_t i) const { return at(i); } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_input_linear(t); } -}; - -// 视图:RotateHalf —— 按 block 分块,块内前后半行交换 + 前半取负(LLaMA rotate_half) -struct RotateHalfRef : CpuViewCache -{ - Tensor t; - std::uint32_t block = 1; // 运行时视图参数(不进 key);默认值供符号实例 - - RotateHalfRef(Tensor tt, std::uint32_t blk) : t(std::move(tt)), block(blk) - { cache_cpu_view(t); } - // 符号实例(AOT 自登记):结构由类型决定,视图参数取中性值(不进 key) - RotateHalfRef() = default; - - [[nodiscard]] Scalar eval(std::size_t i) const - { - const std::size_t c = i % cols, r = i / cols; - const std::size_t blk = block; - const std::size_t rl = r % blk; - const std::size_t rr = (r / blk) * blk + ((rl < blk / 2) ? (rl + blk / 2) : (rl - blk / 2)); - const Scalar v = at(rr * cols + c); - return (rl < blk / 2) ? -v : v; - } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_input_rotate(t, block); } -}; - -// 视图:RowMod —— 行取模广播(频率表平铺到多行块) -struct RowModRef : CpuViewCache -{ - Tensor t; - std::uint32_t mod = 1; // 运行时视图参数(不进 key);默认值供符号实例 - - RowModRef(Tensor tt, std::uint32_t m) : t(std::move(tt)), mod(m) { cache_cpu_view(t); } - // 符号实例(AOT 自登记):结构由类型决定,视图参数取中性值(不进 key) - RowModRef() = default; - - [[nodiscard]] Scalar eval(std::size_t i) const - { - const std::size_t c = i % cols, r = i / cols; - return at((r % mod) * cols + c); - } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_input_rowmod(t, mod); } -}; - -// 视图:RowAccess —— 行偏移+取模访问:data[(offset + r % mod)*cols + c] -// 用于 SwiGLU 等"同一 (2·d_ff) 输入按半个偏移读取"的行切分: -// gate = row_access(in, offset=0, mod=d_ff) → in[r % d_ff] -// up = row_access(in, offset=d_ff, mod=d_ff) → in[d_ff + r % d_ff] -// offset/mod 均为运行时形状数据(共享一个融合 shader,运行时经 vp 槽填充)。 -struct RowAccessRef : CpuViewCache -{ - Tensor t; - std::uint32_t offset = 0; // 运行时视图参数(不进 key);默认值供符号实例 - std::uint32_t mod = 1; // 同上 - - RowAccessRef(Tensor tt, std::uint32_t off, std::uint32_t m) - : t(std::move(tt)), offset(off), mod(m) - { cache_cpu_view(t); } - // 符号实例(AOT 自登记):结构由类型决定,视图参数取中性值(不进 key) - RowAccessRef() = default; - - [[nodiscard]] Scalar eval(std::size_t i) const - { - const std::size_t c = i % cols, r = i / cols; - return at((offset + (r % mod)) * cols + c); - } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_input_rowaccess(t, offset, mod); } -}; - -// 视图:GroupedReduce —— 沿行按固定长度 R 分组归约(segmented reduce) -// 输出网格 (G, N),输入 (G*R, N)(G = 输入行数 / R;列数两侧相同): -// out[g][c] = Σ_{t` 默认构造符号实例) -// 的前提——运行期 bool 会让两种视图同类型、结构不可枚举。 -// r_len(R)是运行期视图参数,不进 expr_spec_key(见 expr_spec.hpp)。 -template struct GroupedReduceRef : CpuViewCache -{ - Tensor t; - std::uint32_t r_len = 1; // 组长度 R(运行期视图参数,不进 expr_spec_key) - - GroupedReduceRef(Tensor tt, std::uint32_t R) : t(std::move(tt)), r_len(R) { cache_cpu_view(t); } - // 符号实例(AOT 自登记):结构由类型决定,R 取中性值(不进 key) - GroupedReduceRef() = default; - - // 输入 cols = 输出 cols(分组只沿行方向)→ 缓存 cols 即可分解 (g, c) - [[nodiscard]] Scalar eval(std::size_t i) const - { - const std::size_t g = i / cols, c = i % cols; - Scalar acc = IsMax ? std::numeric_limits::lowest() : Scalar{0}; - const std::size_t base = g * static_cast(r_len); - for (std::uint32_t tt = 0; tt < r_len; ++tt) - { - const Scalar v = at((base + tt) * cols + c); - if (IsMax) - { - if (v > acc) acc = v; - } - else - { - acc += v; - } - } - return acc; - } - // 2 参版本:cols 语义与 1 参相同(输入/输出同列),转发即可 - [[nodiscard]] Scalar eval(std::size_t i, std::size_t) const { return eval(i); } - ExprOperand to_spec(SpecBuilder& b) const - { return b.add_input_grouped_reduce(t, r_len, IsMax); } -}; - -using GroupedReduceSumRef = GroupedReduceRef; -using GroupedReduceMaxRef = GroupedReduceRef; - -// ══════════════════════════════════════════════════════════════════════════ -// 归约叶子:把"按行/按列归约出标量向量"接入表达式 -// -// 两类(对应 expr_spec.hpp 的归约视图与归约指令两种机制): -// 1. ReduceViewRef:对输入 Tensor 直接归约 → 归约**视图**。 -// 求值期代表一个 (rows,1)/(1,cols) 的广播向量,参与算术时自动广播。 -// 2. ReduceRef:对子表达式结果归约 → 归约**指令**, -// 用于 exp(x) 求和这类"表达式内部归约"(见 DslExpr 概念定义之后)。 -// -// 含归约的表达式**不实例化**模板 CPU 求值路径(has_reduction_v 把 dsl::compute -// 分流到引擎 eval_expr,由引擎实现归约视图/指令语义),故 eval 仅需满足 -// nn::Expression 概念约束,返回占位值。 -// ══════════════════════════════════════════════════════════════════════════ -template struct ReduceViewRef -{ - // 归约轴与算子:行归约 → 向量长度 rows;列归约 → 长度 cols - static constexpr bool reduces_rows = - (Kind == ExprViewKind::RowReduceSum || Kind == ExprViewKind::RowReduceMax); - static constexpr bool reduces_max = - (Kind == ExprViewKind::RowReduceMax || Kind == ExprViewKind::ColReduceMax); - - Tensor t; - - // ── CPU 模板路径预绑定(引擎内部,Layer 无感知)───────────────────── - // 由 cpu_prepare 按输出槽算出行/列归约向量并绑定指针;随后 eval(i, cols) - // 按 (r, c) 读取该向量。槽内沿归约轴升序累加 → 与解释器的 view_reduce - // 预计算逐字节一致(铁律 8:并行只是把独立槽分给不同线程)。 - // GPU/scan 不使用本状态(to_spec 只读 t 与 Kind)。 - mutable std::vector vec_{}; - mutable const Scalar* v_ = nullptr; - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - // CPU 模板路径(2 参):归约视图按行/列广播 - [[nodiscard]] Scalar eval(std::size_t i, std::size_t cols) const noexcept - { - if (v_ == nullptr) return Scalar{0}; - return reduces_rows ? v_[i / cols] : v_[i % cols]; - } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_reduce_input(t, Kind); } -}; - -using RowReduceSumRef = ReduceViewRef; -using RowReduceMaxRef = ReduceViewRef; -using ColReduceSumRef = ReduceViewRef; -using ColReduceMaxRef = ReduceViewRef; - -// ══════════════════════════════════════════════════════════════════════════ -// matmul 叶子(算子融合二期:matmul 参与 IR 融合) -// -// matmul(A, B) 折叠成 ExprSpec 的**前置 matmul 段**(MatmulSpec): -// 逐元素链经 Matmul 操作数按 (r,c) 读取 C = op(A,B),中间结果不物化。 -// A/B 在折叠时登记为输入(Linear 视图),槽位记录进 MatmulSpec.a_input/b_input -// (折叠顺序决定槽位,MatmulRef::to_spec 记录实际槽位,不假设 0/1)。 -// k(求和维度)是形状参数:不进 expr_spec_key,同结构不同 K 共享融合 shader。 -// -// 模板 CPU 求值路径不实例化(has_reduction_v=true 把 dsl::compute -// 分流到引擎 eval_expr,由引擎做 matmul 预计算 + 逐元素链),eval 仅满足 -// nn::Expression 概念约束,返回占位值。 -// ══════════════════════════════════════════════════════════════════════════ -struct MatmulRef -{ - Tensor a, b; - bool transA = false; - bool transB = false; - std::uint32_t batch = 1; // 批量数(形状参数,不进 key) - // GQA 头分组比(docs/internal/22 §3.2 P2):>1 时该操作数只存 - // batch/ratio 份行块(多个 query 头共享一个 KV 头)。1 = 恒等。 - std::uint32_t a_kv_ratio = 1; - std::uint32_t b_kv_ratio = 1; - - // ── CPU 模板求值路径的预绑定状态(引擎内部,Layer 无感知)─────────── - // C 由引擎通用 matmul 原语物化一次并绑定指针;随后尾链(如 +bias)在 - // 编译期模板路径内联求值 → 编译器融合 + 向量化,不经 ExprSpec 解释器。 - // 详见本节末尾 "CPU 预绑定" 一节的说明。GPU/scan 不使用本状态。 - mutable Tensor c_cache_{}; - mutable const Scalar* c_data_ = nullptr; - - [[nodiscard]] Result prepare_cpu(ComputeEngine& eng) const - { - // GQA(ratio>1):CPU 预绑定路径用通用 batched_matmul,无头映射语义 - // → 直接拒绝,让上层回退到解释器(eval_expr 的 MatmulSpec 分支已支持 - // 头映射)。预绑定只是优化,回退不引入正确性风险。 - if (a_kv_ratio > 1 || b_kv_ratio > 1) - NN_FAIL("dsl matmul prepare: kv_ratio > 1 走解释器路径(CPU 预绑定不支持头映射)"); - if (c_data_ != nullptr) return {}; // 幂等:同一棵树重复求值只物化一次 - // ── 精度安全(§7.2 f32 参考)──────────────────────────────────── - // C 固定以 P=F32 物化,而操作数可能带 **f16 存储**(profile_f16 的 - // Linear 权重/激活):内层 GEMM 的 f32 路径按 f32 存储直接读 - // (cpu_matrix() 对 f16 存储返回空指针 → Release 读空 UB、 - // Debug 断言)。故先把非 f32 操作数抬到 f32 再物化,C 按 f32 绑定。 - const Tensor* pa = &a; - const Tensor* pb = &b; - Tensor ca, cb; - if (a.precision() != Precision::F32) - { - auto r = eng.cast(a, Precision::F32); - NN_TRY_CHECK(r); - ca = std::move(*r); - pa = &ca; - } - if (b.precision() != Precision::F32) - { - auto r = eng.cast(b, Precision::F32); - NN_TRY_CHECK(r); - cb = std::move(*r); - pb = &cb; - } - constexpr Precision P = Precision::F32; - Result c = (batch > 1) - ? eng.batched_matmul(*pa, *pb, batch, transA, transB, Scalar{1}, P) - : eng.matmul(*pa, *pb, transA, transB, P); - NN_TRY_CHECK(c); - if (!c->is_cpu()) NN_FAIL("dsl matmul prepare: result not on CPU"); - if (c->precision() != Precision::F32) NN_FAIL("dsl matmul prepare: result not f32"); - if (env_flag("NN_F16_DEBUG")) - { - const auto sp = TensorAccess::cpu_matrix(*c).span(); - double mx = 0.0; - bool bad = false; - for (auto v : sp) - { - if (!std::isfinite(v)) bad = true; - else mx = std::max(mx, std::fabs(static_cast(v))); - } - if (bad || mx > 10.0) - std::fprintf(stderr, - "[dbg][prepare] C=(%zux%zu) A=(%zux%zu)%s B=(%zux%zu)%s " - "max(C)=%.6g%s <<< 异常\n", - c->rows(), c->cols(), pa->rows(), pa->cols(), - pa->precision() == Precision::F16 ? "f16" : "f32", pb->rows(), - pb->cols(), pb->precision() == Precision::F16 ? "f16" : "f32", mx, - bad ? " NONFINITE" : ""); - } - c_cache_ = std::move(*c); - c_data_ = TensorAccess::cpu_matrix(c_cache_).span().data(); - return {}; - } - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - // CPU 模板路径(2 参):读预绑定的 C(形状与输出网格一致) - [[nodiscard]] Scalar eval(std::size_t i, std::size_t) const noexcept - { return c_data_ != nullptr ? c_data_[i] : Scalar{0}; } - ExprOperand to_spec(SpecBuilder& sb) const - { - // 显式固定登记顺序(A 先 B 后),保证跨编译器确定(同 Binary 约定)。 - // 下标取 add_input_linear 的**返回值**(不能用 inputs.size() 预判)。 - // A/B **不去重**(dedup=false):matmul 的 vec4 快路径以 a_slot!=b_slot - // 为启用条件(见 expr_glsl_gen vec4_ok)——若 A、B 是同一张量(如 - // Muon 的 X·Xᵀ)被合并成单槽,快路径整体关闭、标量回退,得不偿失。 - // 两槽绑同一张量语义等价(a/b 索引公式各按 transA/transB 独立计算)。 - const std::uint8_t ai = sb.add_input_linear(a, /*dedup=*/false).idx; - const std::uint8_t bi = sb.add_input_linear(b, /*dedup=*/false).idx; - // k = 求和维度:transA=0 → A.cols()(A 存储 (batch*M, K)); - // transA=1 → A.rows()/batch(A 存储 (batch*K, M),每批 (K, M)) - const std::uint32_t k = static_cast( - transA ? (a.rows() / (batch / (a_kv_ratio ? a_kv_ratio : 1))) : a.cols()); - sb.spec.matmul = MatmulSpec{ai, - bi, - transA ? std::uint8_t{1} : std::uint8_t{0}, - transB ? std::uint8_t{1} : std::uint8_t{0}, - k, - batch}; - // GQA(ratio>1):把该操作数的**视图**标成 HeadGroup 并记下头分组比 - // —— param 是运行期形状数据(不进 key),任意 n_rep 共用一份 shader。 - if (a_kv_ratio > 1) - { - sb.spec.views[ai].kind = static_cast(ExprViewKind::HeadGroup); - sb.spec.views[ai].param = a_kv_ratio; - } - if (b_kv_ratio > 1) - { - sb.spec.views[bi].kind = static_cast(ExprViewKind::HeadGroup); - sb.spec.views[bi].param = b_kv_ratio; - } - return expr::matmul_op(); - } -}; - -// ══════════════════════════════════════════════════════════════════════════ -// 网格索引叶子:行号/列号/批次下标作为标量参与算术 -// -// row() = 当前输出元素在 batch 内的行号 -// col() = 当前输出列号 -// batch() = 当前批次下标 -// 用于位置相关掩码/偏置(causal select(Col > Row, -inf, 0)、ALiBi 等)。 -// 走引擎 eval_expr(索引由引擎按网格推导),eval 仅满足概念约束。 -// ══════════════════════════════════════════════════════════════════════════ -struct RowIdxLeaf -{ - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - ExprOperand to_spec(SpecBuilder&) const { return expr::row(); } -}; -struct ColIdxLeaf -{ - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - ExprOperand to_spec(SpecBuilder&) const { return expr::col(); } -}; -struct BatchIdxLeaf -{ - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - ExprOperand to_spec(SpecBuilder&) const { return expr::batch(); } -}; - -// ══════════════════════════════════════════════════════════════════════════ -// 视图叶子:标签行收集 / 按批次索引 -// row_gather(logits, labels):读取 logits[label[c]][c](稀疏 CE loss 收集); -// labels 登记为 ColBroadcast 视图((1,cols)),RowGather 视图的 param -// 指向 labels 输入槽。 -// batch_mod(t, modulo):读取 t[batch % modulo](ALiBi 按头斜率等)。 -// ══════════════════════════════════════════════════════════════════════════ -struct RowGatherRef -{ - Tensor t; // 主输入(rows, cols),如 logits (classes, total) - Tensor labels; // 标签 (1, cols) 浮点打包 - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - ExprOperand to_spec(SpecBuilder& sb) const - { - // 登记后要改写视图为 RowGather → 必须独占新槽(dedup=false): - // 若复用已有 Linear 槽再覆写 views[i],会污染原输入的视图。 - const ExprOperand ti = sb.add_input_linear(t, /*dedup=*/false); - const std::uint8_t li = sb.add_input_colbroadcast(labels).idx; - sb.spec.views[ti.idx] = expr::row_gather(li); // RowGather 视图,param=标签槽 - return ti; - } -}; - -struct BatchModRef -{ - Tensor t; - std::uint32_t modulo = 1; // 运行时视图参数(不进 key);默认值供符号实例 - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - ExprOperand to_spec(SpecBuilder& sb) const - { - // 登记 BatchMod 视图(输入 (1,mod) 小向量,按 batch 取模索引) - return sb.add_input(t, expr::batch_mod(modulo)); - } -}; - -// 按 (batch, col) 切片:doc_ids (1, batch*seq) → data[batch*seq + col] -struct BatchColRef -{ - Tensor t; - std::uint32_t per_batch_cols = 1; // 运行时视图参数(不进 key);默认值供符号实例 - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - ExprOperand to_spec(SpecBuilder& sb) const - { return sb.add_input(t, expr::batch_col(per_batch_cols)); } -}; - -// 广播视图叶子:输入 (rows,1)/(1,cols) 小向量,按行/列广播参与算术。 -// -// 两者都是**纯索引映射**(不物化、无跨元素依赖),因此留在模板求值路径, -// 不经引擎解释器(见 has_reduction_v = false): -// - 列广播:输入 (1,cols),输出列数 = 输入列数 → 1 参 eval(i) 即可正确求值。 -// - 行广播:输入 (rows,1),读第 i/cols 个标量 —— 需要"输出列数",输入自身 -// 推不出,故 1 参 eval(i)(无 cols)无法给出正确值,返回占位值;行广播 -// 一律经 2 参 eval(i, cols)(eval_with_cols 自动选择)求值。 -template struct BroadcastRef : CpuViewCache -{ - Tensor t; - - BroadcastRef(Tensor tt) : t(std::move(tt)) { cache_cpu_view(t); } - // 符号实例(AOT 自登记):结构由类型(Kind)与默认张量决定 - BroadcastRef() = default; - - [[nodiscard]] Scalar eval(std::size_t i) const - { - if constexpr (Kind == ExprViewKind::ColBroadcast) - return at(i % cols); // 输入 (1,cols) → cols 即输出列数 - else return Scalar{0}; // 行广播需输出列数,见 eval(i, out_cols) - } - [[nodiscard]] Scalar eval(std::size_t i, std::size_t out_cols) const - { - if constexpr (Kind == ExprViewKind::ColBroadcast) return at(i % cols); - else return at(i / out_cols); // 输入 (rows,1):读第 r 个标量 - } - ExprOperand to_spec(SpecBuilder& b) const - { - if constexpr (Kind == ExprViewKind::RowBroadcast) return b.add_input_rowbroadcast(t); - else return b.add_input_colbroadcast(t); - } -}; - -using RowBroadcastRef = BroadcastRef; -using ColBroadcastRef = BroadcastRef; - -// ══════════════════════════════════════════════════════════════════════════ -// DSL 表达式概念(比 nn::Expression 更严格:额外要求可折叠成 ExprSpec) -// -// 用于让 DSL 运算符以更严的约束参与重载解析,从而在 namespace nn(Layer) -// 里**直接写内联数学表达式**时重载唯一确定:只有可折叠节点(带 to_spec) -// 参与 + - * / 与比较,非折叠的 nn::Expression(Span/ConstSpan 等)不成为 -// 候选,误用在编译期即暴露。 -// ══════════════════════════════════════════════════════════════════════════ -template -concept DslExpr = nn::Expression && requires (SpecBuilder& b, const T& t) { - { t.to_spec(b) } -> std::convertible_to; -}; - -// ══════════════════════════════════════════════════════════════════════════ -// 节点模板(每个都同时支持 CPU 求值 eval(i) 与 GPU 折叠 to_spec) -// ══════════════════════════════════════════════════════════════════════════ - -template struct Unary -{ - C child; - [[nodiscard]] auto eval(std::size_t i) const { return Op::apply(child.eval(i)); } - [[nodiscard]] auto eval(std::size_t i, std::size_t cols) const - { return Op::apply(eval_with_cols(child, i, cols)); } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_instr(Op::op_id(), child.to_spec(b)); } -}; - -// 比较算子(ops::Gt 等)使 eval 返回 bool → 该 Binary 是 BoolExpression, -// 供 select() 使用;to_spec 仍折叠成单条比较指令(输出 0.0/1.0 到寄存器)。 -template struct Binary -{ - L l; - R r; - [[nodiscard]] auto eval(std::size_t i) const { return Op::apply(l.eval(i), r.eval(i)); } - [[nodiscard]] auto eval(std::size_t i, std::size_t cols) const - { return Op::apply(eval_with_cols(l, i, cols), eval_with_cols(r, i, cols)); } - ExprOperand to_spec(SpecBuilder& b) const - { - // 显式固定操作数折叠顺序(l 先 r 后):C++ 函数实参求值顺序未指定, - // 直接 b.add_instr(.., l.to_spec(b), r.to_spec(b)) 会让 views/inputs 的 - // 登记顺序随编译器(MSVC 左到右 / Clang 右到左)漂移 → expr_spec_key - // 跨编译器不稳定,破坏 AOT 匹配。先求值到局部变量以固定顺序。 - const ExprOperand lo = l.to_spec(b); - const ExprOperand ro = r.to_spec(b); - return b.add_instr(Op::op_id(), lo, ro); - } -}; - -// select(cond, then, else) —— cond 为 BoolExpression,then/else 为值表达式 -template struct Select -{ - C cond; - T then_e; - E else_e; - [[nodiscard]] Scalar eval(std::size_t i) const - { return cond.eval(i) ? then_e.eval(i) : else_e.eval(i); } - [[nodiscard]] Scalar eval(std::size_t i, std::size_t cols) const - { - return (eval_with_cols(cond, i, cols) != Scalar{0}) ? eval_with_cols(then_e, i, cols) - : eval_with_cols(else_e, i, cols); - } - ExprOperand to_spec(SpecBuilder& b) const - { - // 同 Binary:显式固定折叠顺序(cond → then → else),保证跨编译器确定。 - const ExprOperand co = cond.to_spec(b); - const ExprOperand to = then_e.to_spec(b); - const ExprOperand eo = else_e.to_spec(b); - return b.add_instr(ExprOp::Select, co, to, eo); - } -}; - -// 归约节点:对子表达式结果做归约(归约**指令**)→ 隐式标量向量(Reduce 操作数)。 -// row_reduce_sum(exp(x)) 这类"表达式内部归约";对输入 Tensor 直接归约请用 -// ReduceViewRef(归约视图,GPU 融合更友好)。 -template struct ReduceRef -{ - // 归约轴与算子:行归约 → 向量长度 rows;列归约 → 长度 cols - static constexpr bool reduces_rows = (Rop == ExprOp::RowSum || Rop == ExprOp::RowMax); - static constexpr bool reduces_max = (Rop == ExprOp::RowMax || Rop == ExprOp::ColMax); - - E child; - - // ── CPU 模板路径预绑定(引擎内部,Layer 无感知)───────────────────── - // 两阶段:先递归备好子表达式(其内部若还有归约节点也一并备好),再按输出槽 - // 对子表达式求值并归约到 vec_。槽内沿归约轴升序累加 → 与解释器 - // replay_prefix 的累加顺序逐字节一致(铁律 8)。 - mutable std::vector vec_{}; - mutable const Scalar* v_ = nullptr; - - [[nodiscard]] constexpr Scalar eval(std::size_t) const noexcept { return Scalar{0}; } - // CPU 模板路径(2 参):归约指令按行/列广播 - [[nodiscard]] Scalar eval(std::size_t i, std::size_t cols) const noexcept - { - if (v_ == nullptr) return Scalar{0}; - return reduces_rows ? v_[i / cols] : v_[i % cols]; - } - ExprOperand to_spec(SpecBuilder& b) const { return b.add_reduce_instr(Rop, child.to_spec(b)); } -}; - -// ══════════════════════════════════════════════════════════════════════════ -// has_reduction_v:表达式树是否**必须**走引擎解释器 -// -// 用于 dsl::compute 的 CPU 路径分流:需要"全行/全列"信息、或需要引擎按网格 -// 推导的表达式(归约视图/归约指令/matmul/网格索引/标签收集)无法用逐元素 -// 模板求值表达 → 折叠成 ExprSpec 走引擎 eval_expr(CPU 扩展语义处理); -// 其余表达式(含广播视图这类纯索引映射)保持编译期模板求值(内联 + 并行)。 -// 该标志只影响 CPU 分支;GPU/scan 一律走 to_expr_spec,与本标志无关。 -// ══════════════════════════════════════════════════════════════════════════ -template inline constexpr bool has_reduction_v = false; - -// 归约叶子需走 eval_expr(模板求值无法表达全行/全列归约) -template inline constexpr bool has_reduction_v> = true; -// 广播视图**不需要**走 eval_expr:它是纯索引映射(无跨元素依赖),2 参 -// eval(i, cols) 即可在模板求值路径上正确求值(见 BroadcastRef 与 -// eval_with_cols)→ 留在编译期模板路径(内联 + 并行)。 -template inline constexpr bool has_reduction_v> = false; -// 分组归约视图:每输出元素独立归约 R 行、元素间零依赖 → 非归约(留 -// 编译期模板路径;fail-safe 显式声明,防默认值漂移把它推进解释器) -template <> inline constexpr bool has_reduction_v = false; -template <> inline constexpr bool has_reduction_v = false; -// matmul 叶子同样需走 eval_expr(matmul 预计算 + 逐元素链,引擎实现) -template <> inline constexpr bool has_reduction_v = true; -// 索引/收集叶子:行/列/批次下标与标签收集由引擎按网格推导 -template <> inline constexpr bool has_reduction_v = true; -template <> inline constexpr bool has_reduction_v = true; -template <> inline constexpr bool has_reduction_v = true; -template <> inline constexpr bool has_reduction_v = true; -template <> inline constexpr bool has_reduction_v = true; -template <> inline constexpr bool has_reduction_v = true; -template -inline constexpr bool has_reduction_v> = true; -template -inline constexpr bool has_reduction_v> = has_reduction_v; -template -inline constexpr bool has_reduction_v> = has_reduction_v || has_reduction_v; -template -inline constexpr bool has_reduction_v> = - has_reduction_v || has_reduction_v || has_reduction_v; - -// ══════════════════════════════════════════════════════════════════════════ -// 叶子构造(普通写法入口) -// ══════════════════════════════════════════════════════════════════════════ -[[nodiscard]] inline TensorRef leaf(Tensor t) -{ return TensorRef{std::move(t)}; } - -// 运行时标量参数(RParam):值不限、结构固定 → 同结构共享融合 shader。 -// 用于优化器超参(lr/eps/β)、偏差修正系数等每步会变但结构不变的标量。 -[[nodiscard]] inline constexpr RParamLeaf rparam(Scalar v) -{ return RParamLeaf{v}; } - -// matmul 叶子:C = op(A,B),折叠为 ExprSpec 的前置 matmul 段。 -// 逐元素链(如 +bias、激活)自动与 matmul 融合成一个 kernel(GPU AOT)。 -// batch:A/B 按 batch 垂直切分(batched_matmul 同布局),形状参数。 -[[nodiscard]] inline MatmulRef matmul(Tensor a, Tensor b, bool transA = false, bool transB = false, - std::uint32_t batch = 1) -{ return MatmulRef{std::move(a), std::move(b), transA, transB, batch}; } - -// GQA 变体:a_kv_ratio / b_kv_ratio > 1 时该操作数只存 batch/ratio 份行块 -// (多个连续 query 头共享一个 KV 头;块映射见 docs/internal/22 §3.2)。 -[[nodiscard]] inline MatmulRef matmul_kv(Tensor a, Tensor b, bool transA, bool transB, - std::uint32_t batch, std::uint32_t a_kv_ratio, - std::uint32_t b_kv_ratio) -{ - MatmulRef m{std::move(a), std::move(b), transA, transB, batch}; - m.a_kv_ratio = a_kv_ratio; - m.b_kv_ratio = b_kv_ratio; - return m; -} - -// 网格索引叶子:行号/列号/批次下标 -[[nodiscard]] inline RowIdxLeaf row() -{ return {}; } -[[nodiscard]] inline ColIdxLeaf col() -{ return {}; } -[[nodiscard]] inline BatchIdxLeaf batch() -{ return {}; } - -// 视图叶子:标签行收集(稀疏 CE loss)/ 按批次取模索引(ALiBi 斜率)/ -// 按 (batch,col) 切片(doc_ids) -[[nodiscard]] inline RowGatherRef row_gather(Tensor t, Tensor labels) -{ return RowGatherRef{std::move(t), std::move(labels)}; } -[[nodiscard]] inline BatchModRef batch_mod(Tensor t, std::uint32_t modulo) -{ return BatchModRef{std::move(t), modulo}; } -[[nodiscard]] inline BatchColRef batch_col(Tensor t, std::uint32_t per_batch_cols) -{ return BatchColRef{std::move(t), per_batch_cols}; } - -[[nodiscard]] inline RotateHalfRef rotate_half(Tensor t, std::uint32_t block) -{ return RotateHalfRef{std::move(t), block}; } - -[[nodiscard]] inline RowModRef row_mod(Tensor t, std::uint32_t mod) -{ return RowModRef{std::move(t), mod}; } - -// 行偏移+取模访问(SwiGLU 半切分等):data[(offset + r % mod)*cols + c] -[[nodiscard]] inline RowAccessRef row_access(Tensor t, std::uint32_t offset, std::uint32_t mod) -{ return RowAccessRef{std::move(t), offset, mod}; } - -// 分组归约(MaxPool 窗口归约等):输入 (G*R, N) → 输出 (G, N) -[[nodiscard]] inline GroupedReduceSumRef grouped_reduce_sum(Tensor t, std::uint32_t R) -{ return GroupedReduceSumRef{std::move(t), R}; } -[[nodiscard]] inline GroupedReduceMaxRef grouped_reduce_max(Tensor t, std::uint32_t R) -{ return GroupedReduceMaxRef{std::move(t), R}; } - -// ══════════════════════════════════════════════════════════════════════════ -// 归约自由函数:对输入 Tensor 直接归约 → 归约**视图**(GPU 融合更友好); -// 对表达式结果归约 → 归约**指令**(重载按参数类型自动选择,见 ReduceRef)。 -// 返回的归约叶子参与算术时自动按行/按列广播。 -// ══════════════════════════════════════════════════════════════════════════ -[[nodiscard]] inline RowReduceSumRef row_reduce_sum(Tensor t) -{ return {std::move(t)}; } -[[nodiscard]] inline RowReduceMaxRef row_reduce_max(Tensor t) -{ return {std::move(t)}; } -[[nodiscard]] inline ColReduceSumRef col_reduce_sum(Tensor t) -{ return {std::move(t)}; } -[[nodiscard]] inline ColReduceMaxRef col_reduce_max(Tensor t) -{ return {std::move(t)}; } - -template [[nodiscard]] auto row_reduce_sum(const E& e) -{ return ReduceRef{e}; } -template [[nodiscard]] auto row_reduce_max(const E& e) -{ return ReduceRef{e}; } -template [[nodiscard]] auto col_reduce_sum(const E& e) -{ return ReduceRef{e}; } -template [[nodiscard]] auto col_reduce_max(const E& e) -{ return ReduceRef{e}; } - -// 广播视图自由函数:gamma/beta (F,1) 按行广播、std_inv (1,B) 按列广播 -[[nodiscard]] inline RowBroadcastRef row_broadcast(Tensor t) -{ return {std::move(t)}; } -[[nodiscard]] inline ColBroadcastRef col_broadcast(Tensor t) -{ return {std::move(t)}; } - -// ══════════════════════════════════════════════════════════════════════════ -// 一元函数(普通数学写法) -// ══════════════════════════════════════════════════════════════════════════ -template [[nodiscard]] constexpr auto neg(const E& e) -{ return Unary{e}; } -template [[nodiscard]] auto abs(const E& e) -{ return Unary{e}; } -template [[nodiscard]] auto exp(const E& e) -{ return Unary{e}; } -template [[nodiscard]] auto log(const E& e) -{ return Unary{e}; } -template [[nodiscard]] auto sqrt(const E& e) -{ return Unary{e}; } -template [[nodiscard]] auto rsqrt(const E& e) -{ return Unary{e}; } -template [[nodiscard]] auto tanh(const E& e) -{ return Unary{e}; } - -// ══════════════════════════════════════════════════════════════════════════ -// 二元函数:max / min / select -// ══════════════════════════════════════════════════════════════════════════ -template -[[nodiscard]] constexpr auto max(const L& l, const R& r) -{ return Binary{l, r}; } -template [[nodiscard]] constexpr auto max(const E& e, Scalar s) -{ - return Binary{e, ConstLeaf{s}}; -} -template [[nodiscard]] constexpr auto max(Scalar s, const E& e) -{ return Binary{ConstLeaf{s}, e}; } -template -[[nodiscard]] constexpr auto min(const L& l, const R& r) -{ return Binary{l, r}; } -template [[nodiscard]] constexpr auto min(const E& e, Scalar s) -{ - return Binary{e, ConstLeaf{s}}; -} -template [[nodiscard]] constexpr auto min(Scalar s, const E& e) -{ return Binary{ConstLeaf{s}, e}; } - -/// relu(x) = max(x, 0) -template [[nodiscard]] auto relu(const E& e) -{ return nn::dsl::max(e, ConstLeaf{Scalar{0}}); } - -template -[[nodiscard]] constexpr auto select(const C& c, const T& t, const E& e) -{ return Select{c, t, e}; } -template -[[nodiscard]] constexpr auto select(const C& c, const T& t, Scalar ev) -{ - return Select{c, t, ConstLeaf{ev}}; -} -template -[[nodiscard]] constexpr auto select(const C& c, Scalar tv, const E& e) -{ return Select{c, ConstLeaf{tv}, e}; } -template -[[nodiscard]] constexpr auto select(const C& c, Scalar tv, Scalar ev) -{ - return Select{c, ConstLeaf{tv}, ConstLeaf{ev}}; -} - -// ══════════════════════════════════════════════════════════════════════════ -// 运算符(Sym 生成,覆盖 Expr×Expr / Expr×Scalar / Scalar×Expr) -// 值运算(+ - * /)返回值表达式;比较(> < >= <= == !=)返回布尔表达式。 -// ══════════════════════════════════════════════════════════════════════════ -#define NN_DSL_BINARY(OP, OpT) \ - template \ - [[nodiscard]] constexpr auto operator OP(const L& l, const R& r) \ - { return Binary{l, r}; } \ - template [[nodiscard]] constexpr auto operator OP(const E& e, Scalar s) \ - { \ - return Binary{e, ConstLeaf{s}}; \ - } \ - template [[nodiscard]] constexpr auto operator OP(Scalar s, const E& e) \ - { return Binary{ConstLeaf{s}, e}; } - -NN_DSL_BINARY(+, ops::Add) -NN_DSL_BINARY(-, ops::Sub) -NN_DSL_BINARY(*, ops::Mul) -NN_DSL_BINARY(/, ops::Div) -NN_DSL_BINARY(>, ops::Gt) -NN_DSL_BINARY(<, ops::Lt) -NN_DSL_BINARY(>=, ops::Ge) -NN_DSL_BINARY(<=, ops::Le) -NN_DSL_BINARY(==, ops::Eq) -NN_DSL_BINARY(!=, ops::Ne) -#undef NN_DSL_BINARY - -template [[nodiscard]] constexpr auto operator-(const E& e) -{ return Unary{e}; } - -// ══════════════════════════════════════════════════════════════════════════ -// GPU AOT:编译期表达式 → 扁平 ExprSpec + 输入张量(AOT 注册表分发用) -// ══════════════════════════════════════════════════════════════════════════ -template -[[nodiscard]] std::pair> to_expr_spec(const E& e) -{ - SpecBuilder b; - (void)e.to_spec(b); - return {std::move(b.spec), std::move(b.inputs)}; -} - -// ══════════════════════════════════════════════════════════════════════════ -// 输出网格形状推导(供 dsl::compute / compute_reduce 的**无形状重载**) -// -// 背景:`dsl::compute(engine, expr, rows, cols[, P])` 必带输出形状——调用方 -// 反复手写 `x.rows(), x.cols()`(docs/internal/18-roadmap.md P4-4 记为 API 摩擦点)。 -// `dsl::compute(engine, expr[, P])` 用**唯一口径**从表达式推导该形状: -// -// 输出网格 = 各"网格贡献者"的**广播合并**(逐维:相等,或其一为 1)。 -// · 张量叶 / 形状保持视图(TensorRef / RotateHalf / RowMod / RowAccess / -// Broadcast):贡献其张量形状(行广播 (F,1) / 列广播 (1,C) 由此天然合并)。 -// · 归约视图(ReduceViewRef / ReduceRef):在 compute 的输出网格里是 -// "按行/列读"的**广播读**,故贡献**输入张量**的整网格(compute 的输出 -// 仍是整网格,不是归约向量——归约向量用 compute_reduce)。 -// · GroupedReduce(g, R):贡献 (rows/R, cols)(沿行分组、列不变)。 -// · matmul 段:贡献 (batch·M, N)(与后端 batched_matmul 同一口径;GQA 的 -// batch/ratio 行块亦按 to_spec 的 k 同一推导)。A/B **不**参与合并—— -// 它们是 GEMM 操作数,不是同网格项。 -// · 标量(Const/RParam)、网格索引叶(row/col/batch)、稀疏与批视图 -// (RowGather/BatchMod/BatchCol):中性(不贡献)。 -// · Unary/Binary/Select:递进子节点合并。 -// -// 合并冲突(某维既非相等、又都不为 1)、形状不可推导、或**完全没有贡献者** -// → 报错并提示显式给出 rows/cols。**绝不猜形状**(静默错值远比报错昂贵)。 -// ══════════════════════════════════════════════════════════════════════════ -struct GridInfer -{ - std::size_t rows = 0; - std::size_t cols = 0; - bool any = false; // 是否合并过至少一个贡献者 - bool conflict = false; // 广播规则不成立(某维既不等、又都不为 1) - bool unknown = false; // 有贡献者存在但形状推不出来(如行块不整除) - - [[nodiscard]] static std::size_t merge_dim(std::size_t a, std::size_t b, bool& conflict) - { - if (a == b) return a; - if (a == 1) return b; - if (b == 1) return a; - conflict = true; - return a; - } - void merge(std::size_t r, std::size_t c) - { - if (conflict || unknown) return; - if (!any) - { - rows = r; - cols = c; - any = true; - return; - } - rows = merge_dim(rows, r, conflict); - cols = merge_dim(cols, c, conflict); - } -}; - -// 缺省:不贡献(标量叶 / 网格索引叶 / 稀疏与批视图) -template inline void grid_contrib(const E&, GridInfer&) {} - -// 形状保持的叶子/视图 -inline void grid_contrib(const TensorRef& e, GridInfer& g) -{ - if (e.t.valid()) g.merge(e.t.rows(), e.t.cols()); - else g.unknown = true; -} -inline void grid_contrib(const RotateHalfRef& e, GridInfer& g) -{ - if (e.t.valid()) g.merge(e.t.rows(), e.t.cols()); - else g.unknown = true; -} -inline void grid_contrib(const RowModRef& e, GridInfer& g) -{ - if (e.t.valid()) g.merge(e.t.rows(), e.t.cols()); - else g.unknown = true; -} -inline void grid_contrib(const RowAccessRef& e, GridInfer& g) -{ - if (e.t.valid()) g.merge(e.t.rows(), e.t.cols()); - else g.unknown = true; -} -template inline void grid_contrib(const BroadcastRef& e, GridInfer& g) -{ - if (e.t.valid()) g.merge(e.t.rows(), e.t.cols()); - else g.unknown = true; -} -// 归约视图:compute 下是广播读 → 贡献输入整网格(compute_reduce 要的也是 -// 输入网格,两者同口径) -template inline void grid_contrib(const ReduceViewRef& e, GridInfer& g) -{ - if (e.t.valid()) g.merge(e.t.rows(), e.t.cols()); - else g.unknown = true; -} - -// 分组归约:输出 (rows/R, cols) -template inline void grid_contrib(const GroupedReduceRef& e, GridInfer& g) -{ - if (!e.t.valid() || e.r_len == 0 || e.t.rows() % e.r_len != 0) - { - g.unknown = true; - return; - } - g.merge(e.t.rows() / e.r_len, e.t.cols()); -} - -// matmul 段:输出 (batch·M, N)(与 compute_engine.hpp 的 batched_matmul 口径一致) -inline void grid_contrib(const MatmulRef& e, GridInfer& g) -{ - const std::size_t batch = e.batch; - const std::size_t ra = e.a_kv_ratio ? e.a_kv_ratio : 1; - const std::size_t rb = e.b_kv_ratio ? e.b_kv_ratio : 1; - if (batch == 0 || !e.a.valid() || !e.b.valid() || batch % ra != 0 || batch % rb != 0) - { - g.unknown = true; - return; - } - const std::size_t a_blocks = batch / ra; // GQA:该操作数只存 batch/ratio 份行块 - const std::size_t b_blocks = batch / rb; - if (e.a.rows() % a_blocks != 0 || e.b.rows() % b_blocks != 0) - { - g.unknown = true; - return; - } - const std::size_t a_rows_per = e.a.rows() / a_blocks; - const std::size_t b_rows_per = e.b.rows() / b_blocks; - const std::size_t M = e.transA ? e.a.cols() : a_rows_per; - const std::size_t N = e.transB ? b_rows_per : e.b.cols(); - if (M == 0 || N == 0) - { - g.unknown = true; - return; - } - g.merge(batch * M, N); -} - -// 复合节点:递进子节点合并 -template inline void grid_contrib(const ReduceRef& e, GridInfer& g) -{ grid_contrib(e.child, g); } -template -inline void grid_contrib(const Unary& e, GridInfer& g) -{ grid_contrib(e.child, g); } -template -inline void grid_contrib(const Binary& e, GridInfer& g) -{ - grid_contrib(e.l, g); - grid_contrib(e.r, g); -} -template -inline void grid_contrib(const Select& e, GridInfer& g) -{ - grid_contrib(e.cond, g); - grid_contrib(e.then_e, g); - grid_contrib(e.else_e, g); -} - -// 推导出口:失败一律给出"可操作"的错误(点名入口 + 提示显式 rows/cols) -template -[[nodiscard]] inline Result> infer_grid_shape(const E& e, - const char* entry) -{ - GridInfer g; - grid_contrib(e, g); - const std::string where = std::string(entry) + "(engine, expr[, P])"; - if (g.conflict) - NN_FAIL(where + " 无法推导输出形状:操作数形状不满足广播规则" - "(逐维相等或其一为 1)→ 请用显式形式给出 rows/cols"); - if (g.unknown) - NN_FAIL(where + " 无法推导输出形状:表达式含形状不可推导的项" - "(如 GQA matmul 行块不整除)→ 请用显式形式给出 rows/cols"); - if (!g.any) - NN_FAIL(where + " 无法推导输出形状:表达式不含可确定网格的张量操作数" - "(如裸广播向量)→ 请用显式形式给出 rows/cols"); - return std::pair{g.rows, g.cols}; -} - -// ══════════════════════════════════════════════════════════════════════════ -// CPU 预绑定(cpu_prepare)—— 让"含 matmul/归约"的表达式也走编译期内联 -// -// 背景:has_reduction_v 仅用于 CPU 分支分流(GPU/scan 一律 to_expr_spec)。 -// matmul / 归约视图 / 归约指令 / 网格索引这类"需要 GEMM 或全行全列才能算出" -// 的节点无法逐元素内联——不预绑定就会把**整棵表达式**丢给运行时 ExprSpec -// 解释器(逐元素 Scalar regs[16]={} + switch(op) + lambda 间接调用)。 -// -// 本节与文件头 §1 的既有设计意图一致("CPU 直接把表达式当作编译期 AST, -// 逐元素求值 → 编译器内联 + SIMD 融合,等价手写 for 循环"):把真正需要 -// 全局信息的节点**先算出来并绑定**,其余部分仍交给 eval_into_span 内联求值。 -// 即:融合交给编译器,而不是运行时解释器。 -// -// 红线:本节只做"通用结构"的预绑定(matmul / 归约 / 索引映射),不含任何 -// 算法名或算法逻辑;公式文本仍只在 Layer。GPU 路径完全不经过本段。 -// -// ⚠ 白名单必须**fail-safe**:占位 eval() 的节点(ReduceViewRef/ReduceRef/ -// RowIdxLeaf/ColIdxLeaf/BatchIdxLeaf/RowGatherRef/BatchModRef/BatchColRef 的 -// eval 目前返回占位值,真实语义只由解释器提供)一旦被放进模板路径就会 -// **静默算错**。因此:默认只有 has_reduction_v 为假(本就是模板路径原生 -// 节点)才可预绑定,含归约的节点必须逐个显式加入白名单。 -// ══════════════════════════════════════════════════════════════════════════ - -// 含归约节点的白名单:只列已确认可"预绑定 + 模板内联"的节点(逐个确认后加入) -template inline constexpr bool cpu_preparable_leaf_v = false; - -// ── 表达式是否含"真正的行/列归约" ─────────────────────────────────────── -// 与 has_reduction_v 的区别:matmul 段与网格索引**不算**——它们只是"需要预计算 -// 的逐元素前缀",输出形状仍是 (rows, cols);而真正的归约把输出降成 -// (rows,1)/(1,cols)。 -// 用于维持 compute_into 的对外契约:它只接受逐元素表达式,含真归约必须报错 -// (与 CpuEngine::eval_expr_into 的 expr_spec_reduce_axis != -1 守卫一致)。 -template inline constexpr bool has_real_reduction_v = false; -template inline constexpr bool has_real_reduction_v> = true; -template -inline constexpr bool has_real_reduction_v> = true; -template -inline constexpr bool has_real_reduction_v> = has_real_reduction_v; -template -inline constexpr bool has_real_reduction_v> = - has_real_reduction_v || has_real_reduction_v; -template -inline constexpr bool has_real_reduction_v> = - has_real_reduction_v || has_real_reduction_v || has_real_reduction_v; - -// matmul:用引擎通用 GEMM 原语物化 C 后绑定指针,尾链交模板路径内联 -template <> inline constexpr bool cpu_preparable_leaf_v = true; - -// 归约视图:按输出槽预计算行/列归约向量后交模板路径(语义与解释器一致) -template inline constexpr bool cpu_preparable_leaf_v> = true; - -// 归约指令:内部会先递归备好子表达式 -template -inline constexpr bool cpu_preparable_leaf_v> = true; - -// 树级判定:不含归约的节点本就是模板路径原生节点,可直接预绑定(no-op); -// 含归约的节点只有白名单内的才可预绑定 -template -inline constexpr bool cpu_preparable_v = !has_reduction_v || cpu_preparable_leaf_v; - -template -inline constexpr bool cpu_preparable_v> = cpu_preparable_v; -template -inline constexpr bool cpu_preparable_v> = - cpu_preparable_v && cpu_preparable_v; -template -inline constexpr bool cpu_preparable_v> = - cpu_preparable_v && cpu_preparable_v && cpu_preparable_v; -// 归约指令:可预绑定 ⇔ 子表达式可预绑定(其自身归约由 cpu_prepare 负责) -template -inline constexpr bool cpu_preparable_v> = cpu_preparable_v; - -// 默认:普通叶子 / 视图 / 索引叶子无需预绑定 -template -[[nodiscard]] inline Result cpu_prepare(const T&, ComputeEngine&, std::size_t, std::size_t) -{ return {}; } - -template -[[nodiscard]] inline Result cpu_prepare(const Unary& u, ComputeEngine& eng, - std::size_t rows, std::size_t cols) -{ return cpu_prepare(u.child, eng, rows, cols); } - -template -[[nodiscard]] inline Result cpu_prepare(const Binary& b, ComputeEngine& eng, - std::size_t rows, std::size_t cols) -{ - if (auto r = cpu_prepare(b.l, eng, rows, cols); !r) return r; - return cpu_prepare(b.r, eng, rows, cols); -} - -template -[[nodiscard]] inline Result cpu_prepare(const Select& s, ComputeEngine& eng, - std::size_t rows, std::size_t cols) -{ - if (auto r = cpu_prepare(s.cond, eng, rows, cols); !r) return r; - if (auto r = cpu_prepare(s.then_e, eng, rows, cols); !r) return r; - return cpu_prepare(s.else_e, eng, rows, cols); -} - -// matmul:把 C 物化一次(引擎通用 GEMM 原语)并绑定指针 -[[nodiscard]] inline Result cpu_prepare(const MatmulRef& m, ComputeEngine& eng, std::size_t, - std::size_t) -{ return m.prepare_cpu(eng); } - -// 归约视图:按输出槽算出行/列归约向量(槽内沿归约轴升序累加) -template -[[nodiscard]] inline Result cpu_prepare(const ReduceViewRef& r, ComputeEngine&, - std::size_t rows, std::size_t cols) -{ - if (r.v_ != nullptr) return {}; - if (!r.t.is_cpu()) NN_FAIL("dsl reduce-view prepare: input not on CPU"); - if (r.t.rows() != rows || r.t.cols() != cols) - NN_FAIL("dsl reduce-view prepare: shape mismatch"); - using R = ReduceViewRef; - // f16 输入 → 归约在 f32 参考空间做(§7.2):一次性镜像后读。直接 - // cpu_matrix() 读 f16 存储 = 空指针 UB(MatmulRef::prepare_cpu 同款 - // 精度-存储不匹配约束)。 - Matrix mirror; - std::span s; - if (r.t.precision() == Precision::F32) { s = TensorAccess::cpu_matrix(r.t).span(); } - else if (r.t.precision() == Precision::F16) - { - const auto s16 = TensorAccess::cpu_matrix(r.t).span(); - mirror = Matrix(r.t.rows(), r.t.cols()); - auto d = mirror.span(); - for (std::size_t i = 0; i < s16.size(); ++i) - d[i] = static_cast(s16[i]); - s = mirror.span(); - } - else - { - NN_FAIL("dsl reduce-view prepare: unsupported precision"); - } - const Scalar init = R::reduces_max ? std::numeric_limits::lowest() : Scalar{0}; - const std::size_t len = R::reduces_rows ? rows : cols; - r.vec_.assign(len, init); - - const auto slot_kernel = [&](std::size_t slot) noexcept - { - Scalar a = init; - if constexpr (R::reduces_rows) - for (std::size_t c = 0; c < cols; ++c) - a = R::reduces_max ? std::max(a, s[slot * cols + c]) : a + s[slot * cols + c]; - else - for (std::size_t rr = 0; rr < rows; ++rr) - a = R::reduces_max ? std::max(a, s[rr * cols + slot]) : a + s[rr * cols + slot]; - r.vec_[slot] = a; - }; - // 槽间独立(累加方向固定)→ 并行与串行逐字节一致,无需归并 - if (rows * cols >= kDslParallelThreshold && len > 1) nn::parallel_for_samples(len, slot_kernel); - else - for (std::size_t k = 0; k < len; ++k) - slot_kernel(k); - - r.v_ = r.vec_.data(); - return {}; -} - -// 归约指令:先递归备好子表达式,再按输出槽对子表达式求值并归约。 -// 槽内沿归约轴升序累加 → 与解释器 replay_prefix 逐字节一致。 -template -[[nodiscard]] inline Result cpu_prepare(const ReduceRef& r, ComputeEngine& eng, - std::size_t rows, std::size_t cols) -{ - if (r.v_ != nullptr) return {}; - if (auto pr = cpu_prepare(r.child, eng, rows, cols); !pr) return pr; - - using R = ReduceRef; - const Scalar init = R::reduces_max ? std::numeric_limits::lowest() : Scalar{0}; - const std::size_t len = R::reduces_rows ? rows : cols; - r.vec_.assign(len, init); - - const auto slot_kernel = [&](std::size_t slot) noexcept - { - Scalar a = init; - if constexpr (R::reduces_rows) - for (std::size_t c = 0; c < cols; ++c) - { - const Scalar v = eval_with_cols(r.child, slot * cols + c, cols); - a = R::reduces_max ? std::max(a, v) : a + v; - } - else - for (std::size_t rr = 0; rr < rows; ++rr) - { - const Scalar v = eval_with_cols(r.child, rr * cols + slot, cols); - a = R::reduces_max ? std::max(a, v) : a + v; - } - r.vec_[slot] = a; - }; - if (rows * cols >= kDslParallelThreshold && len > 1) nn::parallel_for_samples(len, slot_kernel); - else - for (std::size_t k = 0; k < len; ++k) - slot_kernel(k); - - r.v_ = r.vec_.data(); - return {}; -} - -// ── 归约向量原生形状输出(compute_reduce 专用)────────────────────────── -// 根节点是"纯归约"时,输出就是归约向量本身:行归约 → (rows,1),列归约 → (1,cols)。 -// 与 CpuEngine::eval_expr_reduce 的 shapes 约定一致。 -template inline constexpr bool cpu_reduce_root_v = false; -template inline constexpr bool cpu_reduce_root_v> = true; -template inline constexpr bool cpu_reduce_root_v> = true; - -[[nodiscard]] inline Tensor copy_reduce_vector(const Scalar* v, bool reduces_rows, std::size_t rows, - std::size_t cols) -{ - Matrix out = - reduces_rows ? Matrix::make_uninitialized(rows, 1) : Matrix::make_uninitialized(1, cols); - const std::size_t len = reduces_rows ? rows : cols; - for (std::size_t k = 0; k < len; ++k) - out.span()[k] = v[k]; - return TensorAccess::from_matrix(std::move(out)); -} - -template -[[nodiscard]] inline Tensor reduce_vector_tensor(const ReduceViewRef& r, std::size_t rows, - std::size_t cols) -{ return copy_reduce_vector(r.v_, ReduceViewRef::reduces_rows, rows, cols); } - -template -[[nodiscard]] inline Tensor reduce_vector_tensor(const ReduceRef& r, std::size_t rows, - std::size_t cols) -{ return copy_reduce_vector(r.v_, ReduceRef::reduces_rows, rows, cols); } - -// ══════════════════════════════════════════════════════════════════════════ -// CPU:编译期模板直接求值(编译器内联 + SIMD 融合,等价手写循环) -// ══════════════════════════════════════════════════════════════════════════ -template -[[nodiscard]] Tensor eval_cpu(const E& e, std::size_t rows, std::size_t cols, - Precision P = Precision::F32) -{ - // 输出会被 eval_into_span 完整覆盖(每个下标恰好写一次)→ 用未初始化构造, - // 省掉"分配 + 写满一遍零 + 马上被全覆盖"里的那一遍全尺寸零写。 - // 实测本机单线程写满 1.57MB 要 0.50ms(~3.2 GB/s),是纯浪费。 - Matrix out = Matrix::make_uninitialized(rows, cols); - // eval_into_span 与 algebra_matrix 的逐元素原语(apply/transform)同构: - // 串行 + 向量化提示 / 阈值以上分块并行,向量化与并行结构一致。 - eval_into_span(e, out.span(), cols); - // 多精度:f16 目标 = f32 参考求值 + 输出舍入到 f16(§7.2;round-half-to-even - // 由 f16 赋值语义保证)。f32 目标 = 直通写入,无额外转换。 - if (P == Precision::F16) - { - MatrixT h(rows, cols); - const auto s = out.span(); - auto d = h.span(); - for (std::size_t i = 0; i < s.size(); ++i) - d[i] = s[i]; - return TensorAccess::from_matrix(std::move(h)); - } - return TensorAccess::from_matrix(std::move(out)); -} - -// ══════════════════════════════════════════════════════════════════════════ -// 扫描期(NN_EXPR_SCAN)校验闸门:折叠出的 ExprSpec 非法 → 立即中止构建 -// -// 层侧 dry-run 大多以 (void) 调用,返回的错误会被静默吞掉:若结构本身非法 -// (例:裸分组归约视图做根节点 → 空指令表),扫描会"跑完"却不登记任何结构, -// 直到 GPU 运行期才暴露 validate_expr_spec / 注册表 miss(JIT 兜底)——CPU 模板路径不校验, -// 于是这类问题只在 GPU 上出现。扫描模式下统一在此硬失败,把运行期错误前移到构建期。 -// -// 退出方式必须是 _Exit 而非 abort:扫描 dry-run 期间线程池 worker 存活,实测 -// std::abort() → SIGABRT handler(scan_exprs 装的栈打印,内部做 stacktrace 分配) -// 会**卡死不退出**,构建随之挂死(单线程探针可正常退出 3,问题只在多线程上下文出现)。 -// _Exit 立即终止、不碰信号处理器与堆,ninja 拿到非零退出码即判构建失败。 -// ══════════════════════════════════════════════════════════════════════════ -#ifdef NN_EXPR_SCAN -[[noreturn]] inline void scan_reject(const nn::Error& e) -{ - std::fprintf(stderr, "[scan] validate_expr_spec 失败: %s\n", e.message.c_str()); - std::fflush(nullptr); - std::_Exit(3); -} - -// ── AOT 自登记锚点("结构 = 表达式类型")────────────────────────────────── -// 每个 `dsl::compute*` 实例化 odr-use 一个 `FusedAnchor`:其**静态初始化期** -// 把"从类型默认构造的符号实例"折叠出的结构登记进全局注册表(去重)。 -// -// 与"执行路径捕获"(dry-run 跑 Layer)互补: -// · dry-run 只覆盖**跑到的**路径(配置分支没跑到就漏); -// · 锚点覆盖**编译进来的每个调用点**——调用点在函数体里,函数被编译即实例化, -// 与运行期是否走到该分支无关(因此这是"编译期可达"而非"运行期可达")。 -// -// ⚠ 只登记**结构**:精度签名已不是构建期集合(生成阶段对每个结构发一份 -// 运行期精度分派 shader,键 `key#x`,运行期按真实精度填 PC `prec`)—— -// 故既不存在 `sig` 形参,也不需要 profile_f32/profile_f16 两遍扫描。 -// -// 结构非法(裸视图作根 → 空指令表等)在构建期即 `_Exit(3)`,与 dry-run 同一闸门。 -// -// 可用 `-DNN_SCAN_NO_ANCHOR` 关掉锚点做 A/B(只影响登记集合,不影响语义)。 -// -// 锚点登记进**独立注册表**:scan_exprs 在结尾把两者合并并打印"锚点独有结构数" -// —— 这是"结构覆盖已不依赖手写清单"的常驻台账(锚点独有 = 手写 dry-run 覆盖 -// 不到的调用点;该数突然变大通常意味着新增了未纳入 dry-run 的层路径)。 -[[nodiscard]] inline nn::fused::ExprRegistry& anchor_registry() -{ - static nn::fused::ExprRegistry reg; - return reg; -} - -// 锚点:每个被扫描的表达式**类型**实例化一个 `FusedAnchor`,其静态 -// 初始化期把"从类型默认构造的符号实例"折叠出的结构登记进**锚点注册表** -// (scan_exprs 结尾与 dry-run 注册表合并,见下)。 -template struct FusedAnchor -{ - static inline const int reg = [] - { - const Expr sym{}; - auto [spec, inputs] = to_expr_spec(sym); - (void)inputs; // 符号实例:inputs 全是占位张量,注册只用结构 - if (auto v = validate_expr_spec(spec, spec.views.size()); !v) scan_reject(v.error()); - anchor_registry().add(spec); - return 0; - }(); -}; - -// fold 锚点:fold spec 由**值构造器**产出(make_fold_*),类型不编码结构—— -// FusedAnchor 按类型登记覆盖不到(视图种类/参数是构造实参)。由**消费方** -// 在静态初始化期经本锚点自登记"本调用点实际会构造的结构变体"(k / view -// param 等形状参数不进 key,取代表值即可),与 FusedAnchor 同机制/同注册表: -// **新增 fold 表达式无需改 scan_exprs**(表达式自登记约定)。 -struct FoldAnchor -{ - explicit FoldAnchor(ExprSpec s) - { - if (auto v = validate_expr_spec(s, s.views.size()); !v) scan_reject(v.error()); - anchor_registry().add(std::move(s)); - } -}; - -// ── 构建期扫描的单点登记(三个 dsl 入口共用)──────────────────────────── -// 折叠表达式 → 校验 → 登记进全局注册表,并返回折叠出的 spec(compute_reduce -// 还要用它判定归约轴)。 -// 登记两项: -// ① **结构**(按 canonical key 去重)——运行期注册表的键; -// ② **观察到的精度签名**(真实张量精度 + 真实 P)——生成阶段据此发 -// **编译期特化** shader(键 `key#xxxx`,f16 存储 + f32 算术)。这是一个 -// 纯优化:热循环里不带 `prec` 三元(实测 CE 融合注意力 −18%); -// 未枚举到的签名仍由运行期分派变体 `key#x` 兜底。 -// 锚点(FusedAnchor,按**类型**登记)由本函数统一 odr-use,保证三个入口的 -// 锚点覆盖行为一致(此前三处各写一遍,漏一处 = 该入口的调用点失去编译期可达 -// 覆盖,且只在 GPU 注册表 miss(JIT 兜底)时才暴露)。锚点路径的输入是**符号占位张量** -// (默认 f32)→ 其签名恒 0,不会污染特化集合。 -template -[[nodiscard]] inline ExprSpec scan_register(const Expr& e, Precision P = Precision::F32) -{ -#if !defined(NN_SCAN_NO_ANCHOR) - (void)FusedAnchor::reg; -#endif - auto [spec, inputs] = to_expr_spec(e); - if (auto v = validate_expr_spec(spec, inputs.size()); !v) scan_reject(v.error()); - fused::global_registry().add(spec); - // 签名位:bit i = 第 i 个输入是 f16,bit16 = 输出是 f16(与运行时 - // expr_prec_sig_of / 生成阶段同源)。这里就地算,避免依赖 compute_engine - // 的 include 顺序(expr_dsl 被 compute_engine 包含)。 - { - std::uint32_t bits = 0; - const std::size_t n = inputs.size() < 16u ? inputs.size() : 16u; - for (std::size_t i = 0; i < n; ++i) - if (inputs[i].precision() == Precision::F16) bits |= (1u << i); - fused::global_registry().add_variant(spec, expr_prec_sig_make(bits, P == Precision::F16)); - } - return spec; -} - -#endif // NN_EXPR_SCAN - -// ══════════════════════════════════════════════════════════════════════════ -// 统一入口:engine.compute(expr, rows, cols[, P]) -// CPU:编译期模板求值(SIMD 融合)。 -// GPU:AOT 注册表 —— to_expr_spec 折叠 → engine.eval_expr 匹配预生成 shader; -// 未命中默认运行期 JIT 现编(NN_STRICT_CLOSED_WORLD=1 恢复硬报错)。 -// -// P = **输出存储精度**(多精度,docs/development/05-mixed-precision.md §8.1)。 -// · 默认 F32:未显式指定即按 f32 求值;Layer 按 §8.5 约定显式传 p_.compute / p_.stable。 -// · f16:CPU 侧 f16 叶子一次性抬到 f32 求值、输出舍入回 f16(§7.2); -// GPU 侧由基类 NVI 入口做同样的边界 cast(入口消费 P)。 -// · **不做 Auto 推导**:P 是唯一可见实参,来源可追溯(§8.5 G4)。 -// ══════════════════════════════════════════════════════════════════════════ -template -[[nodiscard]] Result compute(ComputeEngine& eng, const E& e, std::size_t rows, - std::size_t cols, - Precision P) // 默认实参在 compute_engine.hpp 前置声明处 -{ - NN_PROF_OP("dsl::compute"); // CPU 模板路径不经引擎入口,须在此计时(剖析器) - // 非存储精度(BF16/F64 保留值、T1_58 三值声明)不得作为**算子输出**精度: - // docs 21 §4.6 规定"T1_58 张量只进量化 GEMM",其余入口一律走 unsupported - // 误差路径。此处显式拦下——GPU 侧本来就会在 create/eval 处报错,CPU 侧若 - // 不拦会静默按 f32 建张量(两端语义不一致)。 - if (auto ok = check_precision_supported(P); !ok) - { - std::fprintf(stderr, "[dsl] compute 拒绝非存储精度: %s\n", ok.error().message.c_str()); - return Tensor{}; - } -#ifdef NN_EXPR_SCAN - // 构建期扫描模式:折叠内联表达式的**结构**并登记进全局注册表, - // 返回占位张量让 dry-run 流程继续(scan 只关心表达式集合,不真算)。 - // 表达式文本仍只出现在 Layer;这里登记的是派生物 ExprSpec。 - // 自登记锚点(按类型登记结构)在 scan_register 内统一处理。 - (void)eng; - // IR-C:个体结构**恒登记**(录制段内也登记——锚点的 - // 符号实例与运行期实例可能折叠出不同 key,双保险;注册表按 key 去重, - // 冗余无害。P2 教训:只入图不登记会让未融合回退/单节点 kernel 走 JIT 兜底 - // miss),录制段内再入图(end_expr 时融合并登记复合 spec)。 - const ExprSpec reg_spec = scan_register(e, P); - if (auto* g = fused::recording_graph()) - { - auto [spec, inputs] = to_expr_spec(e); - if (auto v = validate_expr_spec(spec, inputs.size()); !v) scan_reject(v.error()); - const int node = g->add_node(spec, inputs, rows, cols, - /*vector_out=*/false, P); - Tensor ph = (P == Precision::F16 ? TensorAccess::cpu(rows, cols) - : TensorAccess::cpu(rows, cols)); - ph.set_virtual_tag(g->tag_of_node[static_cast(node)]); - g->node_outputs[node] = ph; - return ph; - } - (void)reg_spec; - return (P == Precision::F16 ? TensorAccess::cpu(rows, cols) - : TensorAccess::cpu(rows, cols)); -#else - if (eng.device() == Device::CPU) - { - if constexpr (nn::dsl::has_reduction_v) - { - if constexpr (nn::dsl::cpu_preparable_v) - { - // 可预绑定:先物化需要全局信息的节点(如 matmul C、归约向量), - // 其余交编译期模板内联求值(同一个 eval_into_span 循环)。 - // 预绑定只是**优化**:任何前置条件不满足(形状/设备不符等)就 - // 回退解释器(同一表达式的等价求值路径),不引入正确性风险。 - if (auto r = cpu_prepare(e, eng, rows, cols); r) - return (eval_cpu(e, rows, cols, P)); - } - // 含归约且不可预绑定(或预绑定失败):模板求值无法表达"全行/全列 - // 归约",折叠成 ExprSpec 走引擎 eval_expr(CPU 扩展语义处理归约 - // 视图/指令) - auto [spec, inputs] = to_expr_spec(e); - NN_TRY(v, validate_expr_spec(spec, inputs.size())); - return eng.eval_expr(spec, inputs, rows, cols, P); - } - return (eval_cpu(e, rows, cols, P)); - } - - auto [spec, inputs] = to_expr_spec(e); - NN_TRY(v, validate_expr_spec(spec, inputs.size())); - return eng.eval_expr(spec, inputs, rows, cols, P); // AOT 注册表:GPU 未命中走 JIT 兜底 -#endif -} - -// ── 无形状重载:dsl::compute(engine, expr[, P]) ──────────────────────────── -// 输出网格由表达式推导(口径见上方"输出网格形状推导");推导失败 = 明确报错, -// 绝不猜形状。需要非常规网格(如裸行广播向量)时继续用 4/5 参显式形式。 -template -[[nodiscard]] Result compute(ComputeEngine& eng, const E& e, Precision P) -{ - NN_TRY(rc, infer_grid_shape(e, "dsl::compute")); - return compute(eng, e, rc->first, rc->second, P); -} - -template [[nodiscard]] Result compute(ComputeEngine& eng, const E& e) -{ return compute(eng, e, Precision::F32); } - -// ── CPU 侧"写进目标张量"(f16 目标 → f32 参考求值 + 舍入写回,§7.2)──────── -// f32 目标 = 直通 span 写入(零额外拷贝)。 -// 写版本:本函数是 DSL 在 CPU 上**绕过 ComputeEngine 写入口**的唯一落点, -// 故出口处自己记账(TensorAccess::mark_written)—— 参数派生物缓存依赖它。 -template inline void eval_into_tensor_cpu(const E& e, Tensor& dst) -{ - TensorAccess::mark_written(dst); - static const auto dbg_max = [](const Matrix& m) - { - double mx = 0.0; - bool bad = false; - for (auto v : m.span()) - { - if (!std::isfinite(v)) bad = true; - else mx = std::max(mx, std::fabs(static_cast(v))); - } - return std::pair{mx, bad}; - }; - if (dst.precision() == Precision::F16) - { - Matrix tmp = Matrix::make_uninitialized(dst.rows(), dst.cols()); - eval_into_span(e, tmp.span(), dst.cols()); - if (env_flag("NN_F16_DEBUG")) - { - const auto [mx, bad] = dbg_max(tmp); - if (bad || mx > 10.0) - std::fprintf(stderr, - "[dbg][eval_into] tmp=(%zux%zu) max=%.6g%s <<< f16 写回前异常\n", - dst.rows(), dst.cols(), mx, bad ? " NONFINITE" : ""); - } - const auto s = tmp.span(); - auto d = TensorAccess::cpu_matrix(dst).span(); - for (std::size_t i = 0; i < s.size(); ++i) - d[i] = s[i]; - return; - } - eval_into_span(e, TensorAccess::cpu_matrix(dst).span(), dst.cols()); -} - -// ══════════════════════════════════════════════════════════════════════════ -// compute_into — 目标传递入口:dst = expr(不分配新张量) -// -// 与 compute() 共用同一前端/同一 IR/同一 AOT 匹配,唯一差别是**输出落点**: -// 结果直接写进调用方提供的 dst。用于把"原地更新"语义纳入 DSL(无需逐操作 -// 调用引擎原地原语 add_inplace / scale_inplace 等): -// dst += expr → compute_into(eng, leaf(dst) + expr, dst) -// dst *= k → compute_into(eng, leaf(dst) * rparam(k), dst) -// dst += k * other → compute_into(eng, leaf(dst) + leaf(other) * rparam(k), dst) -// dst += row_broadcast(v)→ compute_into(eng, leaf(dst) + row_broadcast(v), dst) -// dst 与某个输入是同一 buffer 是安全的(逐元素先读后写)。 -// -// 多精度(§8.3 in-place 特例):**输出精度 = dst 的存储精度**(不可变)。 -// 故本入口没有 P 形参——想改精度必须显式 cast(唯一"变精度"算子)。 -// -// CPU 走编译期模板求值(与 compute() 的 eval_cpu 同一路径 + 同一并行门控), -// 因此"原地"不引入任何分配/拷贝;GPU 走 AOT 融合 shader 的 output_override。 -// 仅支持逐元素表达式(无归约);归约向量输出用 compute_reduce。 -// ══════════════════════════════════════════════════════════════════════════ -template -[[nodiscard]] Result compute_into(ComputeEngine& eng, const E& e, Tensor& dst) -{ - NN_PROF_OP("dsl::compute_into"); // CPU 模板路径不经引擎入口,须在此计时(剖析器) -#ifdef NN_EXPR_SCAN - // 构建期扫描:与 compute() 一样只登记结构(不真算、不关心 dst 的值), - // 锚点自登记在 scan_register 内统一处理 - (void)eng; - // 个体结构恒登记(录制段内也登记,双保险——见 compute 的说明) - (void)scan_register(e, dst.precision()); - // IR-C(P2):录制段内作为目标传递节点入图(输出 = dst 既有张量, - // end_expr 的复合 kernel 写回其存储) - if (auto* g = fused::recording_graph()) - { - auto [tspec, tinputs] = to_expr_spec(e); - if (auto v = validate_expr_spec(tspec, tinputs.size()); !v) scan_reject(v.error()); - const int node = g->add_node(tspec, tinputs, dst.rows(), dst.cols(), - /*vector_out=*/false, dst.precision(), - // RV-03:写目标存储标识(同目标写序的显式建边依据) - dst.storage_id()); - g->node_outputs[node] = dst; - return {}; - } - (void)dst; - return {}; -#else - if (eng.device() == Device::CPU) - { - if (!dst.is_cpu()) NN_FAIL("dsl::compute_into: dst not on CPU"); - if constexpr (nn::dsl::has_reduction_v) - { - // 注意:只在"不含真归约"时才走预绑定路径。含真归约的表达式必须落到 - // 下面的 eval_expr_into,以保持 compute_into 的对外契约 - // ("仅支持逐元素表达式(无归约)",见 ce_fusion_test)。 - // matmul 段/网格索引不算真归约 → 仍走预绑定(Linear/Conv 反向的 - // grad_w += matmul(...) 就是这一类)。 - if constexpr (nn::dsl::cpu_preparable_v && !nn::dsl::has_real_reduction_v) - { - // 可预绑定:物化需要全局信息的节点后,直接内联写进 dst - // (与纯逐元素分支同一循环结构;dst 与输入同 buffer 安全)。 - // 同 compute():预绑定失败即回退,不引入正确性风险。 - if (auto r = cpu_prepare(e, eng, dst.rows(), dst.cols()); r) - { - eval_into_tensor_cpu(e, dst); // f16 dst → f32 参考 + 舍入写回 - return {}; - } - else if (nn::dsl::env_flag("NN_F16_DEBUG")) - { - std::fprintf(stderr, "[dbg] compute_into prepare FAILED -> 解释器: %s\n", - r.error().message.c_str()); - } - } - // 含归约:折叠成 ExprSpec 走引擎(与 compute() 的 CPU 分支一致) - auto [spec, inputs] = to_expr_spec(e); - NN_TRY(v, validate_expr_spec(spec, inputs.size())); - return eng.eval_expr_into(spec, inputs, dst.rows(), dst.cols(), dst); - } - // 纯逐元素:编译期模板直接写进 dst 的 span(零解释器开销、零分配), - // 与 eval_cpu / 逐元素原语同一循环结构 + 同一并行门控。 - eval_into_tensor_cpu(e, dst); - return {}; - } - - auto [spec, inputs] = to_expr_spec(e); - NN_TRY(v, validate_expr_spec(spec, inputs.size())); - return eng.eval_expr_into(spec, inputs, dst.rows(), dst.cols(), dst); -#endif -} - -// ══════════════════════════════════════════════════════════════════════════ -// 归约向量原生形状输出:compute_reduce(engine, expr, rows, cols[, P]) -// -// 与 compute() 等价,但输出为归约向量本身((rows,1)/(1,cols)),而非广播到 -// (rows,cols)。用于 LayerNorm/RMSNorm 的 (1,B) 统计量缓存(mean/var/rms_inv) -// 与 (F,1) 梯度归约(grad_gamma/grad_beta)——只产出小向量,避免写全尺寸广播。 -// 要求表达式归约轴为 0/1(否则引擎报错)。 -// P:输出向量精度(§8.1;归约恒 f32 累加 + 输出舍入到 P,§7.3)。 -// ══════════════════════════════════════════════════════════════════════════ -template -[[nodiscard]] Result compute_reduce(ComputeEngine& eng, const E& e, std::size_t rows, - std::size_t cols, - Precision P) // 默认实参在 compute_engine.hpp 前置声明处 -{ - NN_PROF_OP("dsl::compute_reduce"); // CPU 模板路径不经引擎入口,须在此计时(剖析器) - NN_TRY(pc, check_precision_supported(P)); // 同 compute():拒绝非存储精度 -#ifdef NN_EXPR_SCAN - // 构建期扫描:同 compute(),登记结构(归约轴供下面取占位张量形状)。 - // 占位张量按归约轴取向量形状 (rows,1)/(1,cols),使 Layer 后续 - // add_inplace 等形状相关操作在 dry-run 中不因形状失配而中断。 - // 锚点自登记在 scan_register 内统一处理。 - (void)eng; - // 个体结构恒登记(录制段内也登记,双保险——见 compute 的说明) - const ExprSpec reg_spec = scan_register(e, P); - // IR-C:录制段内加入图(归约向量占位 + tag) - if (auto* g = fused::recording_graph()) - { - auto [rspec, rinputs] = to_expr_spec(e); - if (auto v = validate_expr_spec(rspec, rinputs.size()); !v) scan_reject(v.error()); - const int raxis0 = expr_spec_reduce_axis(rspec); - const int node = g->add_node(rspec, rinputs, rows, cols, - /*vector_out=*/true, P); - Tensor ph = - (P == Precision::F16 ? ((raxis0 == 0) ? TensorAccess::cpu(rows, 1) - : (raxis0 == 1) ? TensorAccess::cpu(1, cols) - : TensorAccess::cpu(rows, cols)) - : ((raxis0 == 0) ? TensorAccess::cpu(rows, 1) - : (raxis0 == 1) ? TensorAccess::cpu(1, cols) - : TensorAccess::cpu(rows, cols))); - ph.set_virtual_tag(g->tag_of_node[static_cast(node)]); - g->node_outputs[node] = ph; - return ph; - } - const ExprSpec spec = reg_spec; - const int raxis = expr_spec_reduce_axis(spec); - if (P == Precision::F16) - return ((raxis == 0) ? TensorAccess::cpu(rows, 1) - : (raxis == 1) ? TensorAccess::cpu(1, cols) - : TensorAccess::cpu(rows, cols)); - return ((raxis == 0) ? TensorAccess::cpu(rows, 1) - : (raxis == 1) ? TensorAccess::cpu(1, cols) - : TensorAccess::cpu(rows, cols)); -#else - if (eng.device() == Device::CPU) - { - // 必须用 if constexpr:非归约根节点的 E 不能实例化 reduce_vector_tensor - if constexpr (nn::dsl::cpu_reduce_root_v && nn::dsl::cpu_preparable_v) - { - // 预绑定路径:根节点是纯归约 → 归约向量已算好,直接拷成 - // (rows,1)/(1,cols),不必经 ExprSpec 解释器。失败即回退。 - if (auto r = cpu_prepare(e, eng, rows, cols); r) - { - auto v = reduce_vector_tensor(e, rows, cols); - if (P == Precision::F32) return (std::move(v)); - // f16 目标:归约向量求值在 f32 参考空间,结果舍入到 f16(§7.3) - return eng.cast(std::move(v), P); - } - } - } - auto [spec, inputs] = to_expr_spec(e); - NN_TRY(v, validate_expr_spec(spec, inputs.size())); - return eng.eval_expr_reduce(spec, inputs, rows, cols, P); -#endif -} - -// ── 无形状重载:dsl::compute_reduce(engine, expr[, P]) ───────────────────── -// 归约向量形状 (rows,1)/(1,cols) 由引擎按归约轴决定,调用方给出的只是**输入 -// 网格**;故这里的推导与 compute 同一口径(见"输出网格形状推导")。 -template -[[nodiscard]] Result compute_reduce(ComputeEngine& eng, const E& e, Precision P) -{ - NN_TRY(rc, infer_grid_shape(e, "dsl::compute_reduce")); - return compute_reduce(eng, e, rc->first, rc->second, P); -} - -template [[nodiscard]] Result compute_reduce(ComputeEngine& eng, const E& e) -{ return compute_reduce(eng, e, Precision::F32); } - -// ══════════════════════════════════════════════════════════════════════════ -// start_expr / end_expr — 跨行书写同一表达式的语法糖(IR-C 恢复自旧实现) -// -// 与 compute() 等价:内联数学表达式只出现在这里(Layer),框内即一个融合 -// 单元(单表达式),end_expr 只是延迟到整块写完再求值。 -// auto out = dsl::end_expr(dsl::start_expr(engine, rows, cols, -// leaf(a) * leaf(b) -// + leaf(c) * Scalar{2} -// - leaf(d))); -// ⚠ 与 ComputeEngine::begin_expr/end_expr(**跨表达式**图录制)不是一回事。 -// ══════════════════════════════════════════════════════════════════════════ -template struct ExprBlock -{ - // 强引用:ExprBlock 存活期间引擎不会析构。 - std::shared_ptr eng; - std::size_t rows, cols; - E expr; -}; - -template -[[nodiscard]] ExprBlock start_expr(ComputeEngine& eng, std::size_t rows, std::size_t cols, - const E& e) -{ - auto keepalive = eng.weak_from_this().lock(); - return {std::move(keepalive), rows, cols, e}; -} - -template [[nodiscard]] Result end_expr(const ExprBlock& b) -{ - if (!b.eng) NN_FAIL("dsl::end_expr: ExprBlock 未绑定引擎"); - return compute(*b.eng, b.expr, b.rows, b.cols); -} - -} // namespace nn::dsl diff --git a/include/neuralnet.cpp/expr_fold.hpp b/include/neuralnet.cpp/expr_fold.hpp deleted file mode 100644 index db8305c..0000000 --- a/include/neuralnet.cpp/expr_fold.hpp +++ /dev/null @@ -1,207 +0,0 @@ -#pragma once - -// ═══════════════════════════════════════════════════════════════════════════ -// expr_fold.hpp — 分块状态归约(FoldSpec)通用样例构造:fold v1(标量域)地基 -// -// 本头只承载**与注意力无关**的通用 fold 样例,是 scan_exprs(AOT 收集 -// dry-run)与 fused_gpu_test / expr_fold_test 对拍的**共享唯一来源**—— -// 各方必须构造出结构完全一致的 spec(key 一致),否则注册表查表未命中(走 JIT 兜底)。 -// rowmax/rowsum/softmax_denom 三个 fold v1 样例不被 Layer 使用,保留为状态 -// 语义回归锚点;它们的**结构自登记**在文件末尾(`FoldAnchor`,扫描期, -// 与 fold 值构造的既有约定一致——新增/改动样例无需改 scan_exprs)。 -// -// 注意力的 fold 构造(make_fold_attn_o / AttnMaskKind)属于 Layer 侧的 -// 表达式文本(与 AOT 收集原则"表达式文本只出现在 Layer"一致),定义在 -// compute_layer_attention.hpp,本头不包含任何注意力专属语义。 -// -// 共同形状契约:输入 (rows, K),输出 (rows, 1);fold.k = K(形状参数)。 -// 三个样例的语义增量: -// 1. rowmax —— 单状态 max 累加(对拍独立逐行标量 max 参考) -// 2. rowsum —— 单状态 sum 累加(对拍独立逐行标量 sum 参考) -// 3. softmax_denom —— 双状态 online rescale(m 跨块更新 + l = l*α + -// Σexp(x−m')):验证状态读写序、拷贝旧状态技巧、body 链内状态广播、 -// 两个块归约指令——fold v1 状态语义的全覆盖锚点。 -// ═══════════════════════════════════════════════════════════════════════════ - -#include - -#include "expr_spec.hpp" - -namespace nn::expr -{ - -// 末指令 = 状态自复制(dst 自身 Add 常量 0),把"输出 = 末指令 dst"落到状态上 -[[nodiscard]] inline ExprInstr fold_emit_state(std::uint8_t state_reg, std::uint8_t const_zero_slot) -{ - ExprInstr ins; - ins.op = static_cast(ExprOp::Add); - ins.dst = state_reg; - ins.a = reg(state_reg); - ins.b = cst(const_zero_slot); - return ins; -} - -// ── 1) fold rowmax:m = max_k x(row, k) ──────────────────────────────────── -// body: [0] blk = RowMax(x) (沿块内 kb 归约) -// [1] m = max(m, Reduce(blk))(写回状态 0) -// finalize: r0 = r0 + c0(状态自复制 → 输出) -[[nodiscard]] inline ExprSpec make_fold_rowmax(std::uint32_t k) -{ - ExprSpec s; - FoldSpec f; - f.k = k; - f.num_state = 1; - f.inits = {-std::numeric_limits::infinity()}; - ExprInstr b0; // blk = RowMax(input 0) - b0.op = static_cast(ExprOp::RowMax); - b0.dst = 1; - b0.a = input(0); - b0.b = {}; - ExprInstr b1; // m = max(m, Reduce(blk)) - b1.op = static_cast(ExprOp::Max); - b1.dst = 0; - b1.a = reg(0); - b1.b = reduce(1); - f.body = {b0, b1}; - f.finalize = {fold_emit_state(0, 0)}; - s.fold = std::move(f); - s.consts = {Scalar{0}}; - s.num_regs = 2; // 状态 0 + 临时 1 - s.views = {linear()}; - return s; -} - -// ── 2) fold rowsum:s = Σ_k x(row, k) ────────────────────────────────────── -[[nodiscard]] inline ExprSpec make_fold_rowsum(std::uint32_t k) -{ - ExprSpec s; - FoldSpec f; - f.k = k; - f.num_state = 1; - f.inits = {Scalar{0}}; - ExprInstr b0; - b0.op = static_cast(ExprOp::RowSum); - b0.dst = 1; - b0.a = input(0); - ExprInstr b1; - b1.op = static_cast(ExprOp::Add); - b1.dst = 0; - b1.a = reg(0); - b1.b = reduce(1); - f.body = {b0, b1}; - f.finalize = {fold_emit_state(0, 0)}; - s.fold = std::move(f); - s.consts = {Scalar{0}}; - s.num_regs = 2; - s.views = {linear()}; - return s; -} - -// ── 3) fold softmax 分母(online 双状态)────────────────────────────────── -// 状态:0 = m(行 max,初值 -inf)、1 = l(移位和,初值 0) -// body(块内网格 x(row, kb)),指令序即状态更新序: -// [0] dst2 = m_old 拷贝(m 更新前留存旧值,供 α 与回看) -// [1] dst3 = RowMax(x) —— 块内行 max -// [2] m = max(dst2, Reduce(dst3)) —— 状态 0 跨块更新 -// [3] dst4 = dst2 − m —— m_old − m' -// [4] dst5 = exp(dst4) —— rescale 因子 α -// [5] dst7 = x − m'(广播) -// [6] dst8 = exp(dst7) -// [7] dst6 = RowSum(dst8) —— 块内 Σexp(x−m') -// [8] dst9 = l · α -// [9] l = dst9 + Reduce(dst6) —— 状态 1 跨块更新 -// finalize: 输出 l(状态自复制) -// 参考语义:l_final = Σ_k exp(x(row,k) − max_k x)(浮点结合序容差内等价)。 -[[nodiscard]] inline ExprSpec make_fold_softmax_denom(std::uint32_t k) -{ - const auto ins = [](ExprOp op, std::uint8_t dst, ExprOperand a, ExprOperand b = {}) - { - ExprInstr i; - i.op = static_cast(op); - i.dst = dst; - i.a = a; - i.b = b; - return i; - }; - ExprSpec s; - FoldSpec f; - f.k = k; - f.num_state = 2; - f.inits = {-std::numeric_limits::infinity(), Scalar{0}}; - f.body = { - ins(ExprOp::Add, 2, reg(0), cst(0)), // m_old 拷贝 - ins(ExprOp::RowMax, 3, input(0)), // 块内 blk_m - ins(ExprOp::Max, 0, reg(2), reduce(3)),// m' ← 状态更新 - ins(ExprOp::Sub, 4, reg(2), reg(0)), // m_old − m' - ins(ExprOp::Exp, 5, reg(4)), // α - ins(ExprOp::Sub, 7, input(0), reg(0)), // x − m' - ins(ExprOp::Exp, 8, reg(7)), // exp(x − m') - ins(ExprOp::RowSum, 6, reg(8)), // 块内 blk_l - ins(ExprOp::Mul, 9, reg(1), reg(5)), // l · α - ins(ExprOp::Add, 1, reg(9), reduce(6)),// l' ← 状态更新 - }; - f.finalize = {fold_emit_state(1, 0)}; - s.fold = std::move(f); - s.consts = {Scalar{0}}; - s.num_regs = 10; // 状态 0..1 + 临时 2..9 - s.views = {linear()}; - return s; -} - -// ── 4) fold logsumexp(online 双状态 → 单值 lse = m + log(l))────────────── -// body 与 softmax_denom **逐指令同构**(states 0=m/1=l、同 rescale 语义序: -// 复用 make_fold_softmax_denom 的 body/状态/初值),只换两处: -// - 输入视图由调用方给出(linear = (rows,K) 直读;trans(stride) = 转置 -// 直读 (K,rows) 存储——如交叉熵沿词表行归约而 logits 为 (vocab,total) -// 存储;视图 param 走 vp、不进 key → 一份登记覆盖所有形状) -// - finalize 合成单值 lse = m + log(l)(= log Σ_k exp(x(row,k)),数值稳定: -// l ≥ 1 ⇒ log l 有限;dst 用临时寄存器,不覆写被读状态) -// 参考语义:lse_final = log Σ_k exp(x(row,k) − max_k x) + max_k x -// (浮点结合序容差内等价于直接 logsumexp)。 -[[nodiscard]] inline ExprSpec make_fold_logsumexp(std::uint32_t k, ExprView input_view = linear()) -{ - ExprSpec s = make_fold_softmax_denom(k); // body/states/inits/consts 同源 - const auto ins = [](ExprOp op, std::uint8_t dst, ExprOperand a, ExprOperand b = {}) - { - ExprInstr i; - i.op = static_cast(op); - i.dst = dst; - i.a = a; - i.b = b; - return i; - }; - // finalize:lse = m + log(l)——寄存器 10/11 为 finalize 专用临时 - s.fold->finalize = { - ins(ExprOp::Log, 10, reg(1)), - ins(ExprOp::Add, 11, reg(0), reg(10)), - }; - s.num_regs = 12; // 状态 0..1 + body 临时 2..9 + finalize 10..11 - s.views = {input_view}; - return s; -} - -} // namespace nn::expr - -// ═══════════════════════════════════════════════════════════════════════════ -// 扫描期自登记(AOT 收集):本头三个 v1 样例的结构必须进扫描集合(GPU 对拍 -// 与 CPU 用同一构造),而它们不被 Layer 使用、没有别的消费方可以挂锚点 —— -// 故由**定义方**在静态初始化期经 `FoldAnchor` 自登记:新增样例、改样例形状 -// 参数都无需改 scan_exprs(`logsumexp` 无自注册项:它只在 compute_loss.hpp -// 的 CE 路径被构造,锚点由该消费方持有)。 -// ⚠ `expr_dsl.hpp`(`FoldAnchor` 所在)只在本扫描段内包含:普通构建里本头 -// 仍只依赖 `expr_spec.hpp`(保持轻量)。 -// ═══════════════════════════════════════════════════════════════════════════ -#if defined(NN_EXPR_SCAN) && !defined(NN_SCAN_NO_ANCHOR) -#include "expr_dsl.hpp" -namespace nn::expr -{ -// k(= 本头的唯一形状参数)不进 key → 一个代表值覆盖所有收缩长度。 -inline const bool g_fold_sample_anchors = [] -{ - (void)dsl::FoldAnchor{make_fold_rowmax(64)}; - (void)dsl::FoldAnchor{make_fold_rowsum(64)}; - (void)dsl::FoldAnchor{make_fold_softmax_denom(64)}; - return true; -}(); -} // namespace nn::expr -#endif diff --git a/include/neuralnet.cpp/expr_registry.hpp b/include/neuralnet.cpp/expr_registry.hpp deleted file mode 100644 index 7a841be..0000000 --- a/include/neuralnet.cpp/expr_registry.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -// ═══════════════════════════════════════════════════════════════════════════ -// expr_registry.hpp — 可融合表达式注册表(AOT 收集的载体) -// -// AOT 收集架构(表达式只在 Layer 里,别处一律不出现): -// ① 构建期 `scan_exprs` 收集结构:`FusedAnchor` 在静态初始化期按 -// 表达式**类型**登记(编译期可达,覆盖 dry-run 跑不到的分支),再由 -// dry-run / 模型 pass 补"只有执行 Layer 才拿得到"的结构——每个 -// dsl::compute* 在记录模式下把折叠出的 ExprSpec 登记进本注册表 -// (按 expr_spec_key 去重)。**只收集结构**:精度签名不是构建期集合。 -// ② 同一次运行内直接生成:`tools/fused_generate.hpp` 对每条 spec 出 -// GLSL(glsl_gen.hpp)→ glslc → SPIR-V → 内联进 fused_registry.hpp -// (key → spirv)。**不经 .bin 中间序列化**(原 gen_fused 已并入)。 -// ③ 运行时 eval_expr 折叠内联表达式 → expr_spec_key → 查嵌入映射 -// → dispatch。未命中走运行期 JIT 兜底(NN_STRICT_CLOSED_WORLD=1 恢复硬报错;见 expr_dsl.hpp 锚点说明)。 -// -// 表达式**文本**只出现在 Layer;生成头是折叠后的**派生物**, -// 非手写定义,故不违反"表达式只在 Layer"的约束。 -// ═══════════════════════════════════════════════════════════════════════════ - -#include -#include -#include -#include - -#include "expr_spec.hpp" -#include "expr_opt.hpp" - -namespace nn::fused -{ - -// ── 注册表:收集折叠出的 ExprSpec **结构**(按规范 key 去重)+ 观察到的 -// **精度签名**(供编译期特化变体 `key#xxxx` 的生成;见 add_variant)── -// -// 结构仍是身份:运行期进度签名有兜底载体 `key#x`(一个 shader 覆盖任意 -// 签名)。但 `#x` 的代价是热循环里每次读写都带 `prec` 三元——实测(850M, -// GPT d64/h4/L4/ff256/seq256/b16,f16)把 CE 融合注意力从 146 ms 降到 -// 120 ms(=−18%,f32 基线 117 ms)**只**因为换了编译期特化 shader。 -// 因此这里额外收集"扫描期真实出现过的 (结构, 签名)",生成阶段为每条发一份 -// 静态特化 variant(f16 存储 + f32 算术,键 = `key#xxxx`,与 ggml/torch 的 -// per-type kernel 特化同思路)。**它是纯优化**:未枚举到的签名照旧落 `#x`。 -// -// 历史:早期有过 `variants` 表,随"签名降级为运行期参数"一并删除;本次 -// 以"特化优化(非身份)"的名义恢复,语义与那时不同(那时是唯一载体)。 -struct ExprRegistry -{ - struct VariantRef - { - std::string key; // 结构 key(canonical 形态) - ExprPrecSig sig; // 观察到的精度签名(≠0、≠ EXPR_PREC_SIG_DISPATCH) - }; - - std::vector specs; - std::unordered_set keys; - std::vector variants; - std::unordered_set variant_keys; - - void add(const ExprSpec& s) - { - // 登记 canonical IR:canonicalize 为引擎内部优化(IR-A/IR-B), - // key 建立在 canonical 形态上(scan 与 runtime 两端一致;运行时侧 - // 同一规则见 expr_opt.hpp 的 expr_spec_lookup_key)。 - const ExprSpec canon = canonicalize_expr_spec(s); - const std::string k = expr_spec_key(canon); - if (keys.insert(k).second) specs.push_back(canon); - } - - // 记录"某结构在某签名下真的被调用过"(扫描期 = dry-run / 模型 pass / - // 显式 pass,均为真实张量精度 + 真实 P)。全 f32(sig==0)与分派哨兵 - // 不记:前者与 V0 共用条目,后者不是签名。 - void add_variant(const ExprSpec& s, ExprPrecSig sig) - { - if (sig == 0u || expr_prec_sig_is_dispatch(sig)) return; - const ExprSpec canon = canonicalize_expr_spec(s); - const std::string k = expr_spec_key(canon); - const std::string vk = expr_prec_sig_key(k, sig); - if (variant_keys.insert(vk).second) variants.push_back(VariantRef{k, sig}); - } -}; - -// 全局注册表(scan_exprs 记录模式写入;普通构建不含 NN_EXPR_SCAN,零开销) -[[nodiscard]] inline ExprRegistry& global_registry() -{ - static ExprRegistry reg; - return reg; -} - -} // namespace nn::fused diff --git a/include/neuralnet.cpp/int8_pack.hpp b/include/neuralnet.cpp/int8_pack.hpp deleted file mode 100644 index 599133b..0000000 --- a/include/neuralnet.cpp/int8_pack.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -// ── int8_pack.hpp — int8 激活的打包布局与编解码(W1.58A8,P3 显存侧)────────── -// -// 背景(docs/internal/21-quantized-weights.md §4.16 的 P3):BitNet 训练配方把 -// 线性投影的激活量化到 8-bit 整数(W1.58A8:三值权重 × int8 激活,per-token -// absmax 尺度)——训练期激活缓存从 f16 2 B/元素降到 **1 B/元素**,是训练显存 -// 的大头削减(b1.58 论文"trained with 8-bit activations"的落地)。 -// -// 布局(与 ternary_pack 同一先例:容器 = f32 张量承载原始字): -// · 8 bit / 元素(signed int8,值域 [-127,127]);4 元素 / 32-bit 字; -// · 行内小端:第 c 个元素的字节在 `(word >> 8·(c%4)) & 0xFF`; -// · 形状 (rows, ceil(cols/4)) → 1 B/元素(f16 稠密 2 B、f32 4 B); -// · 尾部(idx ≥ cols)填 0。 -// -// 量化公式(与 BitNet a2.6 口径一致,docs 21 §4.16 ③): -// q = round(clamp(x/s, -127, 127)) (round-half-away-from-zero;先 clamp -// 后 round ⇒ 结果恒在 [-127,127],无溢出;NaN → 0) -// s 按**列**(per-token absmax,激活)或按**行**(优化器状态/权重侧)广播, -// 由调用方(原语内核)决定 —— 本头只提供逐元素 helper。 -// ───────────────────────────────────────────────────────────────────────── - -#include -#include - -namespace nn -{ -namespace i8_pack -{ - -inline constexpr std::uint32_t kBits = 8; // 每元素 8 bit -inline constexpr std::uint32_t kPerWord = 32 / kBits; // 4 元素 / f32 元素 -inline constexpr std::uint32_t kMask = (1u << kBits) - 1u; - -// 每行需要的字(f32 元素)数:(N + 3) / 4 -[[nodiscard]] inline constexpr std::size_t words_per_row(std::size_t n) noexcept -{ return (n + kPerWord - 1) / kPerWord; } - -// 量化(round-half-away-from-zero;先 clamp 后 round ⇒ 恒 ∈ [-127,127]) -// NaN → 0(确定性:不把 NaN 位型带进 int 转换)。 -[[nodiscard]] inline std::int8_t quantize(float v, float s) noexcept -{ - const float scaled = v / s; - // clamp 到 [-127,127](NaN 比较全 false → 落到 else 分支置 0) - float c; - if (scaled >= 127.0f) c = 127.0f; - else if (scaled <= -127.0f) c = -127.0f; - else if (scaled != scaled) c = 0.0f; // NaN - else c = scaled; - // round-half-away:c + 0.5·sign → trunc(向零)——与 std::round 语义一致, - // 但用整数截断实现,不依赖宿主舍入模式(铁律 8 确定性)。 - const float r = c + (c < 0.0f ? -0.5f : 0.5f); - return static_cast(static_cast(r)); -} - -// 打包一行:q[0..N)(int8 宿主连续)→ words_per_row(N) 个 32-bit 字。 -// 尾部填 0。 -inline void pack_row(const std::int8_t* q, std::size_t n, std::uint32_t* out) noexcept -{ - const std::size_t words = words_per_row(n); - for (std::size_t wi = 0; wi < words; ++wi) - { - std::uint32_t word = 0; - for (std::uint32_t j = 0; j < kPerWord; ++j) - { - const std::size_t idx = wi * kPerWord + j; - const std::uint32_t byte = (idx < n) ? static_cast(q[idx]) : 0u; - word |= byte << (kBits * j); - } - out[wi] = word; - } -} - -// 单点解码(signed int8):words 指向该行首字 -[[nodiscard]] inline std::int8_t at(const std::uint32_t* words, std::size_t idx) noexcept -{ - const std::uint32_t word = words[idx / kPerWord]; - const std::uint32_t byte = (word >> (kBits * (idx % kPerWord))) & kMask; - return static_cast(static_cast(byte)); -} - -} // namespace i8_pack -} // namespace nn diff --git a/include/neuralnet.cpp/kernel_cache.hpp b/include/neuralnet.cpp/kernel_cache.hpp deleted file mode 100644 index abe83af..0000000 --- a/include/neuralnet.cpp/kernel_cache.hpp +++ /dev/null @@ -1,459 +0,0 @@ -#pragma once - -// ═══════════════════════════════════════════════════════════════════════════ -// kernel_cache.hpp — 运行期 JIT 算子缓存(kernels.nnk) -// -// 定位:`.nnk` 是**缓存**(torch.compile 式),不是部署代码模块。 -// 记录:K = "v<生成器版本>." → V = SPIR-V 字节。 -// 容器:KVRecord v2(KeyValueRecord),每条 kernel 一个字段 "kernel." → SPIR-V 字节 -// (key_index_ 使 has/get 按 key O(1) 命中;set + serialize_v2 支持运行期追加重建)。 -// 版本失效:生成器版本进 K;引擎升级后 K 全变 → 旧条目永不匹配(孤立无害), -// 新 key 触发重编;加载时 generator_version 不符 → 判陈旧 → 丢弃重建。 -// 设计见 docs/internal/33-kernel-cache-and-engine-decouple.md。 -// -// 本头还含 ExprSpec 序列化器 —— 它**不是缓存值**,而是生成器 `.so` -// (libnn-kernelgen.so,Phase 2)的 C ABI 跨边界载荷编码(doc §3.2)。 -// ═══════════════════════════════════════════════════════════════════════════ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace nn::fused -{ - -// 生成器版本:只在**代码生成语义变化**时 bump(非每次 release;避免无关升级 -// 频繁作废缓存)。进 K 的版本组件与 .nnk 的 generator_version 字段。 -inline constexpr const char* kKernelCacheGeneratorVersion = "1"; -inline constexpr std::uint64_t kKernelCacheFormatVersion = 1; - -// KVRecord 里每条 kernel 一个字段,前缀 "kernel." + K。 -inline constexpr const char* kKernelFieldPrefix = "kernel."; - -// K = "v<生成器版本>.<表达式签名 key>" -// 表达式签名 key:V0(sig=0)= 结构 key;分派哨兵 = key + "#x"(EXPR_PREC_DISPATCH_SUFFIX, -// 与运行期 dispatch 查找一致);静态签名 = expr_prec_sig_key(key + "#xxxx")。 -[[nodiscard]] inline std::string kernel_cache_key(const std::string& spec_key, - std::uint32_t prec_sig) -{ - std::string body = spec_key; - if (nn::expr_prec_sig_is_dispatch(prec_sig)) - body += nn::EXPR_PREC_DISPATCH_SUFFIX; - else - body = nn::expr_prec_sig_key(spec_key, prec_sig); - return "v" + std::string(kKernelCacheGeneratorVersion) + "." + body; -} - -// ── 缓存位置(非缓存版,供测试多次调)──────────────────────────────────── -// 优先级:NN_KERNEL_CACHE_PATH(显式文件)> NN_KERNEL_CACHE_DIR(目录)> -// 系统统一缓存目录 + "neuralnet-cpp/kernels.nnk"。路径分隔统一用 '/'(Windows -// CRT 接受,跨平台一致)。 -[[nodiscard]] inline std::string kernel_cache_path_from_env() -{ - // 1) 显式文件路径 - if (const std::string p = nn::detail::env_string_raw("NN_KERNEL_CACHE_PATH"); !p.empty()) - return p; - // 2) 显式缓存目录 - if (const std::string d = nn::detail::env_string_raw("NN_KERNEL_CACHE_DIR"); !d.empty()) - return d + "/kernels.nnk"; - // 3) 系统统一缓存目录 + 库子目录 - std::string base; -#if defined(_WIN32) - base = nn::detail::env_string_raw("LOCALAPPDATA"); - if (base.empty()) base = nn::detail::env_string_raw("USERPROFILE") + "/AppData/Local"; -#elif defined(__APPLE__) - base = nn::detail::env_string_raw("HOME") + "/Library/Caches"; -#else - base = nn::detail::env_string_raw("XDG_CACHE_HOME"); - if (base.empty()) base = nn::detail::env_string_raw("HOME") + "/.cache"; -#endif - if (base.empty()) base = "."; // 都取不到 → 回退当前目录 - return base + "/neuralnet-cpp/kernels.nnk"; -} -// 运行期取缓存路径:**读一次 + 缓存**(环境变量行为开关,见 core_env 注释)。 -[[nodiscard]] inline const std::string& kernel_cache_path() -{ - static const std::string path = kernel_cache_path_from_env(); - return path; -} - -// ── ExprSpec 序列化(生成器 .so 的 C ABI 边界载荷;见 doc §3.2)────────── -// 与缓存值无关;Phase 1 保留并测试,Phase 2 作为 libnn-kernelgen.so 的接口契约。 -namespace detail -{ - -struct ByteWriter -{ - std::vector buf; - void u8(std::uint8_t v) { buf.push_back(v); } - void u32(std::uint32_t v) - { - buf.push_back(static_cast(v & 0xffu)); - buf.push_back(static_cast((v >> 8) & 0xffu)); - buf.push_back(static_cast((v >> 16) & 0xffu)); - buf.push_back(static_cast((v >> 24) & 0xffu)); - } - void f32(float v) - { - std::uint32_t x; - std::memcpy(&x, &v, sizeof(x)); - u32(x); - } - void raw_bytes(const void* p, std::size_t n) - { - const auto* b = static_cast(p); - buf.insert(buf.end(), b, b + n); - } - void len_bytes(const void* p, std::size_t n) - { - u32(static_cast(n)); - raw_bytes(p, n); - } -}; - -struct ByteReader -{ - const std::uint8_t* p = nullptr; - std::size_t n = 0; - std::size_t pos = 0; - bool fail = false; - - bool need(std::size_t m) - { - if (fail || pos + m > n) { fail = true; return false; } - return true; - } - bool u8(std::uint8_t& v) - { - if (!need(1)) return false; - v = p[pos++]; - return true; - } - bool u32(std::uint32_t& v) - { - if (!need(4)) return false; - v = static_cast(p[pos]) | - (static_cast(p[pos + 1]) << 8) | - (static_cast(p[pos + 2]) << 16) | - (static_cast(p[pos + 3]) << 24); - pos += 4; - return true; - } - bool f32(float& v) - { - std::uint32_t x; - if (!u32(x)) return false; - std::memcpy(&v, &x, sizeof(v)); - return true; - } - bool len_bytes(std::vector& v) - { - std::uint32_t len; - if (!u32(len)) return false; - if (!need(len)) return false; - v.assign(p + pos, p + pos + len); - pos += len; - return true; - } -}; - -inline void write_f32_vec(ByteWriter& w, const std::vector& v) -{ - w.u32(static_cast(v.size())); - for (const float x : v) w.f32(x); -} -inline bool read_f32_vec(ByteReader& r, std::vector& v) -{ - std::uint32_t len; - if (!r.u32(len)) return false; - v.resize(len); - for (auto& x : v) - if (!r.f32(x)) return false; - return true; -} - -inline void write_operand(ByteWriter& w, const ExprOperand& o) -{ - w.u8(o.kind); - w.u8(o.idx); -} -inline bool read_operand(ByteReader& r, ExprOperand& o) -{ - return r.u8(o.kind) && r.u8(o.idx); -} -inline void write_view(ByteWriter& w, const ExprView& v) -{ - w.u8(v.kind); - w.u8(v.negate_first_half); - w.u32(v.param); - w.u32(v.param2); -} -inline bool read_view(ByteReader& r, ExprView& v) -{ - return r.u8(v.kind) && r.u8(v.negate_first_half) && r.u32(v.param) && r.u32(v.param2); -} -inline void write_instr(ByteWriter& w, const ExprInstr& in) -{ - w.u8(in.op); - w.u8(in.dst); - write_operand(w, in.a); - write_operand(w, in.b); - write_operand(w, in.c); -} -inline bool read_instr(ByteReader& r, ExprInstr& in) -{ - return r.u8(in.op) && r.u8(in.dst) && read_operand(r, in.a) && read_operand(r, in.b) && - read_operand(r, in.c); -} -inline void write_instr_seq(ByteWriter& w, const std::vector& seq) -{ - w.u32(static_cast(seq.size())); - for (const auto& in : seq) write_instr(w, in); -} -inline bool read_instr_seq(ByteReader& r, std::vector& seq) -{ - std::uint32_t len; - if (!r.u32(len)) return false; - seq.resize(len); - for (auto& in : seq) - if (!read_instr(r, in)) return false; - return true; -} -inline void write_matmul(ByteWriter& w, const MatmulSpec& m) -{ - w.u8(m.a_input); - w.u8(m.b_input); - w.u8(m.transA); - w.u8(m.transB); - w.u32(m.k); - w.u32(m.batch); -} -inline bool read_matmul(ByteReader& r, MatmulSpec& m) -{ - return r.u8(m.a_input) && r.u8(m.b_input) && r.u8(m.transA) && r.u8(m.transB) && - r.u32(m.k) && r.u32(m.batch); -} -inline void write_vecacc(ByteWriter& w, const VecAccSpec& v) -{ - w.u8(v.vec_state); - w.u8(v.weight_reg); - w.u8(v.b_input); - w.u8(v.scale_reg); - w.u8(v.has_scale); -} -inline bool read_vecacc(ByteReader& r, VecAccSpec& v) -{ - return r.u8(v.vec_state) && r.u8(v.weight_reg) && r.u8(v.b_input) && r.u8(v.scale_reg) && - r.u8(v.has_scale); -} - -inline void write_fold(ByteWriter& w, const FoldSpec& f) -{ - w.u8(f.num_state); - w.u32(f.k); - write_f32_vec(w, f.inits); - write_instr_seq(w, f.body); - write_instr_seq(w, f.finalize); - w.u32(f.vec_state_len); - w.u8(f.matmul ? 1 : 0); - if (f.matmul) write_matmul(w, *f.matmul); - w.u8(f.vecacc ? 1 : 0); - if (f.vecacc) write_vecacc(w, *f.vecacc); - w.u8(f.tri_skip ? 1 : 0); -} -inline bool read_fold(ByteReader& r, FoldSpec& f) -{ - if (!r.u8(f.num_state) || !r.u32(f.k) || !read_f32_vec(r, f.inits) || - !read_instr_seq(r, f.body) || !read_instr_seq(r, f.finalize)) - return false; - if (!r.u32(f.vec_state_len)) return false; - std::uint8_t has_mm, has_va, tri; - if (!r.u8(has_mm)) return false; - if (has_mm) - { - MatmulSpec m; - if (!read_matmul(r, m)) return false; - f.matmul = m; - } - if (!r.u8(has_va)) return false; - if (has_va) - { - VecAccSpec va; - if (!read_vecacc(r, va)) return false; - f.vecacc = va; - } - if (!r.u8(tri)) return false; - f.tri_skip = tri != 0; - return true; -} - -inline void write_expr_spec(ByteWriter& w, const ExprSpec& s) -{ - write_instr_seq(w, s.instrs); - w.u32(static_cast(s.views.size())); - for (const auto& v : s.views) write_view(w, v); - write_f32_vec(w, s.consts); - write_f32_vec(w, s.rparams); - w.u32(s.num_regs); - w.u8(s.matmul ? 1 : 0); - if (s.matmul) write_matmul(w, *s.matmul); - w.u8(s.fold ? 1 : 0); - if (s.fold) write_fold(w, *s.fold); - w.u32(static_cast(s.extras.size())); - for (const auto e : s.extras) w.u8(e); -} -inline bool read_expr_spec(ByteReader& r, ExprSpec& s) -{ - if (!read_instr_seq(r, s.instrs)) return false; - std::uint32_t nviews; - if (!r.u32(nviews)) return false; - s.views.resize(nviews); - for (auto& v : s.views) - if (!read_view(r, v)) return false; - if (!read_f32_vec(r, s.consts) || !read_f32_vec(r, s.rparams)) return false; - if (!r.u32(s.num_regs)) return false; - std::uint8_t has_mm, has_fold; - if (!r.u8(has_mm)) return false; - if (has_mm) - { - MatmulSpec m; - if (!read_matmul(r, m)) return false; - s.matmul = m; - } - if (!r.u8(has_fold)) return false; - if (has_fold) - { - FoldSpec f; - if (!read_fold(r, f)) return false; - s.fold = f; - } - std::uint32_t nextras; - if (!r.u32(nextras)) return false; - s.extras.resize(nextras); - for (auto& e : s.extras) - if (!r.u8(e)) return false; - return true; -} - -} // namespace detail - -// 序列化 ExprSpec 为字节(生成器 .so 边界载荷;round-trip 逐位一致,见 test) -[[nodiscard]] inline std::vector encode_expr_spec(const ExprSpec& s) -{ - detail::ByteWriter w; - detail::write_expr_spec(w, s); - return std::move(w.buf); -} -[[nodiscard]] inline Result decode_expr_spec(const std::uint8_t* data, std::size_t n) -{ - detail::ByteReader r{data, n, 0, false}; - ExprSpec s; - if (!detail::read_expr_spec(r, s)) - NN_FAIL("kernel_cache: ExprSpec 解码失败(截断/损坏)"); - return s; -} - -// ── KVRecord v2 缓存容器 ─────────────────────────────────────────────── -// 编码:单个 KeyValueRecord(v2),字段 = nnk_format_version / generator_version / -// "kernel." = SPIR-V 字节(string 字段,length-prefixed,任意字节安全)。 -[[nodiscard]] inline std::string encode_kernel_cache( - const std::unordered_map>& kernels, - const std::string& generator_version) -{ - KeyValueRecord rec; - rec.set("nnk_format_version", kKernelCacheFormatVersion); - rec.set("generator_version", generator_version); - for (const auto& [K, spirv] : kernels) - { - const std::string bytes(reinterpret_cast(spirv.data()), - spirv.size() * sizeof(std::uint32_t)); - rec.set(std::string(kKernelFieldPrefix) + K, bytes); - } - return rec.serialize_v2(); -} - -// 运行期 JIT 算子缓存:懒加载 + 内存热缓存 + 追加持久化。 -struct KernelCache -{ - std::string generator_version; - KeyValueRecord rec; // 全量 kernel(持久化映像,懒查询) - std::unordered_map> hot; // 内存热缓存 - - // 加载:generator_version 与期望一致 → 复用;不符 → 判陈旧 → 空缓存(重建)。 - [[nodiscard]] static Result from_bytes(std::string_view bytes, - const std::string& expected_genver) - { - NN_TRY(rec_r, KeyValueRecord::parse(bytes)); - KernelCache c; - std::string genver; - c.generator_version = (rec_r->get("generator_version", genver)) - ? genver - : std::string(kKernelCacheGeneratorVersion); - if (c.generator_version == expected_genver) - { - c.rec = std::move(*rec_r); - } - else - { - // 陈旧:丢弃旧缓存(字段不读、不命中),换新生成器版本重建。 - c.generator_version = expected_genver; - c.rec.set("nnk_format_version", kKernelCacheFormatVersion); - c.rec.set("generator_version", expected_genver); - } - return c; - } - - // 命中:内存热缓存;内存 miss → 从 rec 懒加载(O(1),key_index_)。返回 nullptr = miss。 - [[nodiscard]] const std::vector* hit(const std::string& K) - { - const auto it = hot.find(K); - if (it != hot.end()) return &it->second; - std::string bytes; - if (rec.get(std::string(kKernelFieldPrefix) + K, bytes)) - { - const auto* data = reinterpret_cast(bytes.data()); - std::vector spv(data, data + bytes.size() / sizeof(std::uint32_t)); - const auto [ins, _] = hot.emplace(K, std::move(spv)); - return &ins->second; - } - return nullptr; - } - - // JIT miss 后追加:内存 + rec(调用方随后 serialize()+ 写盘持久化)。 - [[nodiscard]] Result insert(const std::string& K, - const std::vector& spirv) - { - hot[K] = spirv; - const std::string bytes(reinterpret_cast(spirv.data()), - spirv.size() * sizeof(std::uint32_t)); - rec.set(std::string(kKernelFieldPrefix) + K, bytes); - return {}; - } - - // 当前 rec 序列化(供写盘持久化) - [[nodiscard]] std::string serialize() const { return rec.serialize_v2(); } - - // 合并另一份 rec 里本 rec 没有的 kernel 字段(进程锁写盘前,把并发进程中 - // 其它进程已 JIT 并落盘的 kernel 合进来,避免 lost-update)。hot 不更新 - // (hit() 懒加载自 rec,合进来的条目后续按需进 hot)。 - [[nodiscard]] Result merge_kernels_from(const KeyValueRecord& other) - { - for (const std::string& key : other.string_keys_with_prefix(kKernelFieldPrefix)) - { - std::string v; - if (!other.get(key, v)) continue; - std::string mine; - if (!rec.get(key, mine)) rec.set(key, v); - } - return {}; - } -}; - -} // namespace nn::fused diff --git a/include/neuralnet.cpp/kernelgen_api.h b/include/neuralnet.cpp/kernelgen_api.h deleted file mode 100644 index 734585a..0000000 --- a/include/neuralnet.cpp/kernelgen_api.h +++ /dev/null @@ -1,60 +0,0 @@ -// ═══════════════════════════════════════════════════════════════════════════ -// kernelgen_api.h — libnn-kernelgen.so 的稳定 C ABI(运行期 JIT 算子生成器) -// -// 生成器 .so 内嵌 GlslEmitter(IR → GLSL)+ shaderc/glslang(GLSL → SPIR-V)。 -// 引擎在 .nnk 缓存 miss 时调用本接口现编 SPIR-V 并追加进缓存。 -// 设计见 docs/internal/33-kernel-cache-and-engine-decouple.md §3.2/§4。 -// -// 纯 C 兼容头(无 C++ 依赖),可被 C/C++/ctypes 消费。 -// ═══════════════════════════════════════════════════════════════════════════ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum nnk_status -{ - NNK_OK = 0, // 成功 - NNK_BAD_SPEC = 1, // 序列化 ExprSpec 损坏/不可解析 - NNK_COMPILE_FAIL = 2, // GLSL 生成或 SPIR-V 编译失败 -} nnk_status; - -// 生成结果的运行期 kernel 元数据(与构建期 FusedShader / 后端 FusedKernel -// 同源,见 fused_generate.hpp 的 fused_vec_width_for 与 -// compute_vk_backend_destroy.inc 的 pc_base_fixed 推导)。引擎据它建 pipeline。 -typedef struct nnk_kernel_meta -{ - int32_t reduce_axis; // -1=逐元素, 0=行归约, 1=列归约 - uint32_t has_matmul; // 1=含前置 matmul 段(PC 多 rows + mm_k) - uint32_t view_param_count; // vp push constant 槽数 - uint32_t rparam_count; // rp push constant 槽数 - uint32_t vec_width; // 每线程元素数(1=标量, 4=vec4) - uint32_t fold_out; // fold 输出列数(0=非 fold) - uint32_t fold_per_thread_row; // 1 → v1 标量 fold;0 → v2 双域 fold - uint32_t pc_base_fixed; // 固定头 uint 槽数(不含 prec/vp/常量池) -} nnk_kernel_meta; - -// 生成器版本号(应为 .nnk 缓存 K 的版本组件;引擎用它校验一致性)。 -const char* nnk_generator_version(void); - -// 生成 SPIR-V: -// spec_bytes / spec_len : 序列化 ExprSpec(kernel_cache.hpp 的 encode_expr_spec 载荷) -// prec_sig : 所需精度签名(0=V0 全 f32;EXPR_PREC_SIG_DISPATCH=分派; -// 其余 = 静态签名位图) -// out_meta : 输出 kernel 元数据(可空;NNK_OK 时填充) -// out_spirv / out_len : 输出的 SPIR-V 字节(nnk 内部 malloc,调用方必须 free) -// 返回 NNK_OK 时 out_spirv 非空;失败时 out_spirv=nullptr。 -nnk_status nnk_generate_spirv(const uint8_t* spec_bytes, size_t spec_len, - uint32_t prec_sig, nnk_kernel_meta* out_meta, - uint8_t** out_spirv, size_t* out_len); - -// 释放 nnk_generate_spirv 返回的 SPIR-V 缓冲。 -void nnk_free_spirv(uint8_t* spirv); - -#ifdef __cplusplus -} -#endif diff --git a/include/neuralnet.cpp/nn_api.h b/include/neuralnet.cpp/nn_api.h deleted file mode 100644 index 7a13a93..0000000 --- a/include/neuralnet.cpp/nn_api.h +++ /dev/null @@ -1,102 +0,0 @@ -// ═══════════════════════════════════════════════════════════════════════════ -// nn_api.h — libnn.so 的稳定 C ABI(Phase 3 link 阶段;docs/internal/34) -// -// libnn.so 编"具体引擎 + 原语 + 生成器";本头是消费方的唯一跨边界接口。 -// 边界协议(docs/internal/34 §2):**不透明句柄 + 序列化载荷** —— -// · 句柄:nnn_engine / nnn_tensor(opaque,谁创建谁释放;tensor 由 engine 领养) -// · 载荷:ExprSpec 序列化字节(kernel_cache.hpp 的 encode_expr_spec 编码)、 -// 裸 float* + dims(Matrix 数据) -// · STL/模板类型(std::string/vector/expected/dsl 表达式)**永不跨界** -// C++ 模板面(nn::dsl / Layer 组合)留 header-only 在消费方实例化(nn.hpp)。 -// -// 本文件是**最小闭环(Phase 3-A)**:engine / tensor / matmul / eval_expr。 -// 纯 C 兼容(无 C++ 依赖),可被 C/C++/ctypes 消费。 -// ═══════════════════════════════════════════════════════════════════════════ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct nnn_engine_t* nnn_engine; -typedef struct nnn_tensor_t* nnn_tensor; - -// 状态码(跨边界错误:STL 的 std::expected 不跨界) -typedef enum nnn_status -{ - NNN_OK = 0, // 成功 - NNN_ERR = 1, // 一般错误(具体信息经 nnn_last_error 查询) - NNN_NO_GPU = 2, // GPU 引擎创建失败(无可用 Vulkan 设备)—— 非致命,可回退 CPU -} nnn_status; - -// 精度(对应 nn::Precision 枚举值) -typedef enum nnn_prec -{ - NNN_PREC_F16 = 0, - NNN_PREC_F32 = 1, -} nnn_prec; - -// 最近一次错误的只读文本(libnn 内部缓冲;跨多次调用有效到下次出错;线程本地)。 -// 返回 NULL 表示无错误。 -const char* nnn_last_error(void); - -// ── 引擎生命周期 ───────────────────────────────────────────────────── -// CPU 引擎:确定性、无外部依赖。 -nnn_engine nnn_engine_create_cpu(void); -// GPU 引擎:device = 设备名子串(空 = 自动选择,同 NN_VULKAN_DEVICE);无可用设备 -// 返回 NULL(NNN_NO_GPU 已记入 last_error)。消费方可回退 CPU。 -nnn_engine nnn_engine_create_gpu(const char* device); -// 释放引擎(连带其领养的全部 tensor 句柄)。 -void nnn_engine_destroy(nnn_engine e); - -// ── 张量 ──────────────────────────────────────────────────────────── -// 创建 (rows, cols) 全零张量(精度 nnn_prec)。 -nnn_tensor nnn_tensor_create(nnn_engine e, size_t rows, size_t cols, int prec); -// 从 row-major f32 数据上传建张量(from_matrix)。 -nnn_tensor nnn_tensor_from_matrix(nnn_engine e, const float* data, size_t rows, size_t cols, - int prec); -size_t nnn_tensor_rows(nnn_engine e, nnn_tensor t); -size_t nnn_tensor_cols(nnn_engine e, nnn_tensor t); -int nnn_tensor_precision(nnn_engine e, nnn_tensor t); -// 下载到 row-major f32 缓冲(to_matrix)。out 容量须 ≥ rows*cols;返回实际元素数 -// 或负值(NNN_ERR)。失败时 out 内容未定义。 -nnn_status nnn_tensor_to_matrix(nnn_engine e, nnn_tensor t, float* out, size_t cap, - size_t* out_n); -void nnn_tensor_destroy(nnn_engine e, nnn_tensor t); - -// ── 原语 ──────────────────────────────────────────────────────────── -// matmul:out = A×B(行主序按引擎布局;transA/transB = 是否转置 A/B)。 -// out 为新建张量(调用方 nnn_tensor_destroy 释放)。 -nnn_status nnn_matmul(nnn_engine e, nnn_tensor A, nnn_tensor B, int transA, int transB, - nnn_tensor* out); - -// 归约(输出为归约向量:(rows,1) 行归约 / (1,cols) 列归约;新建张量)。 -nnn_status nnn_row_reduce_sum(nnn_engine e, nnn_tensor A, nnn_tensor* out); -nnn_status nnn_col_reduce_sum(nnn_engine e, nnn_tensor A, nnn_tensor* out); -nnn_status nnn_col_reduce_max(nnn_engine e, nnn_tensor A, nnn_tensor* out); - -// 数据操作。 -nnn_status nnn_transpose(nnn_engine e, nnn_tensor A, nnn_tensor* out); // 新建 (cols,rows) -nnn_status nnn_clone(nnn_engine e, nnn_tensor A, nnn_tensor* out); // 新建深拷贝 -// 原地(A 就地改写;成功 = NNN_OK)。 -nnn_status nnn_add_inplace(nnn_engine e, nnn_tensor A, nnn_tensor B); // A += B -nnn_status nnn_scale_inplace(nnn_engine e, nnn_tensor A, float s); // A *= s -nnn_status nnn_zero(nnn_engine e, nnn_tensor A); // A = 0 - -// ── 批处理录制窗口(GPU 录制到 command buffer;CPU 为 no-op)────────── -nnn_status nnn_batch_begin(nnn_engine e); -nnn_status nnn_batch_end(nnn_engine e); - -// ── 表达式求值(JIT/AOT 融合;docs/internal/33)──────────────────── -// spec = encode_expr_spec 编码的 ExprSpec 字节;inputs 为输入张量句柄数组; -// rows/cols = 输出网格形状;prec_sig = 精度签名(0=V0 全 f32)。out 为新建张量。 -nnn_status nnn_eval_expr(nnn_engine e, const uint8_t* spec, size_t len, - const nnn_tensor* inputs, size_t nin, uint32_t prec_sig, size_t rows, - size_t cols, nnn_tensor* out); - -#ifdef __cplusplus -} -#endif diff --git a/include/neuralnet.cpp/ternary_pack.hpp b/include/neuralnet.cpp/ternary_pack.hpp deleted file mode 100644 index 791c617..0000000 --- a/include/neuralnet.cpp/ternary_pack.hpp +++ /dev/null @@ -1,206 +0,0 @@ -#pragma once - -// ── ternary_pack.hpp — 三值权重的打包布局与编解码(P2 / F3 共用件)────────── -// -// 布局(**码值映射与位宽对齐 microsoft/BitNet** 的 `gpu/pack_weight.py`): -// · 2 bit / 权重;4 权重 / 字节;码值 ∈ {1,2,3} = 三值 {-1,0,+1} **+ 2** -// (0 码留空 —— 与官方 `weight + 2` 同款:三值没有"零码",不留浪费码位); -// · 容器 = **f32 张量**(每元素 4 字节 = 16 个权重),行内小端: -// 低 2 bit 对应行内更靠前的权重(第 j 个权重的码在 `(word >> 2j) & 3`); -// · 形状 (rows, ceil(K/16)) → **0.25 B/权重**(f16 稠密缓冲是 2 B/权重, -// 打包后权重带宽/显存降 8 倍)。 -// -// 与 BitNet 的**唯一差别**:不采用其 CUDA 侧的 32-bit interleave 置换 -// (`interleave_weight_int8`,那是为 coalesced 访问做的重排,解码等价), -// 本项目按自然顺序存放 —— 位宽、码值映射、每字节权重数一致,故"编解码" -// 可以逐位对拍(docs/internal/21 §5 的 P2 验收第 4 条)。 -// -// 为什么容器是 f32 张量而不是新的存储类型:张量存储多态(M7)是独立立项 -// (docs/internal/17 §5);打包缓冲只在引擎原语内部被当作**原始字**读写 -// (`std::bit_cast`),从不参与浮点运算 —— 契约由本头 + 原语共同保证。 -// -// 为什么不用 base-3(1.59 bpw,docs 21 §0 的 D2 裁决):本步对齐的是官方 GPU -// 内核实际使用的 2-bit 定长编码(解码只有移位/掩码,无除法),先行拿到可量 -// 的带宽/显存数据;base-3 编码留作部署格式的后续精修(无损从 2 bpw 降到 -// 1.59 bpw,收益 20%,代价是逐字节除 3 的解码)。 -// ───────────────────────────────────────────────────────────────────────── - -#include -#include - -namespace nn -{ -namespace trit_pack -{ - -inline constexpr std::uint32_t kBits = 2; // 每权重 2 bit -inline constexpr std::uint32_t kPerByte = 8 / kBits; // 4 权重/字节 -inline constexpr std::uint32_t kPerWord = 4 * kPerByte; // 16 权重/f32 元素 -inline constexpr std::uint32_t kMask = (1u << kBits) - 1u; - -// 每行需要的字(f32 元素)数:(K + 15) / 16 -[[nodiscard]] inline constexpr std::size_t words_per_row(std::size_t k) noexcept -{ return (k + kPerWord - 1) / kPerWord; } - -// 每行打包后的字节数:4 字节/字 × 字数 = 0.25·K(向上取到 4B 对齐) -[[nodiscard]] inline constexpr std::size_t bytes_per_row(std::size_t k) noexcept -{ return words_per_row(k) * sizeof(std::uint32_t); } - -// 量化码:码 = 三值 + 2(∈{1,2,3});判据 = |w| > 0.5·τ(**严格**大于: -// 恰好 |w| == 0.5·τ 归 0,半值向零)。与 BitLinear::quantize_weights_ 的 -// select 阈值逐点一致(RV-10:等值点归零是刻意的,非 round-half-away)。 -[[nodiscard]] inline std::uint32_t code_of(float w, float tau) noexcept -{ - const float half = 0.5f * tau; - if (w > half) return 3u; // +1 - if (w < -half) return 1u; // -1 - return 2u; // 0 -} - -// 码 → 三值(float,精确可表示) -[[nodiscard]] inline float value_of(std::uint32_t code) noexcept -{ return static_cast(static_cast(code & kMask) - 2); } - -// 打包一行:w[0..K)(宿主连续)+ 逐行尺度 τ → words_per_row(K) 个 32-bit 字。 -// 尾部(idx ≥ K)填"码 2 = 三值 0"而不是 0:码 0 在映射里无定义,填 0 会让 -// 误读尾部的内核拿到 -2(静默错值),填 2 最多是多算一个 0。 -inline void pack_row(const float* w, std::size_t k, float tau, std::uint32_t* out) noexcept -{ - const std::size_t words = words_per_row(k); - for (std::size_t wi = 0; wi < words; ++wi) - { - std::uint32_t word = 0; - for (std::uint32_t j = 0; j < kPerWord; ++j) - { - const std::size_t idx = wi * kPerWord + j; - const std::uint32_t code = (idx < k) ? code_of(w[idx], tau) : 2u; - word |= code << (kBits * j); - } - out[wi] = word; - } -} - -// 单点解码:words 指向该行首字 -[[nodiscard]] inline float at(const std::uint32_t* words, std::size_t idx) noexcept -{ - const std::uint32_t word = words[idx / kPerWord]; - return value_of((word >> (kBits * (idx % kPerWord))) & kMask); -} - -// ═══════════════════════════════════════════════════════════════════════════ -// 反量化(P1-7:打包 → 可训练 latent) -// -// 语义:文件里的模型前向用的是 `(wq∘τ)·X`。把打包权重反量化成 f32 latent 时, -// "把 trit 乘回 τ"(L = wq·τ)**不是**量化器的逆:τ 的定义是**逐行 absmean**, -// 而 L = wq·τ 的行 absmean 是 τ·n/K(n = 该行非零 trit 数)⇒ 反代回去 τ 会变, -// 有效权重 wq·τ 也跟着变(尺度跳变,warm start 的 loss 会跳一下)。 -// -// 正确取值是**量化器的不动点**: -// L = wq · (τ·K/n) -// 它同时满足两条不变量: -// ① 行 L1 质量 = K·τ ⇒ absmean(L) == τ(与训练时 latent 的定义同源); -// ② 非零位置 |L| = τ·K/n ≥ τ > 0.5τ(n ≤ K 恒成立)⇒ 再量化复现**同一组 -// trit**,即 quantize(L) == (wq, τ)。 -// 于是"反量化 → 首次 forward"与文件里的模型逐位一致,此后每次重算 τ 也只是 -// ±1 ulp(不是 τ·n/K 的尺度跳变)。n = 0(整行三值 0,τ 必为 0)⇒ 整行 0。 -// -// 调用方:`model_serialization` 的**载入路径**(宿主侧逐行解码 + 反量化,L3 是 -// 宿主字节/Matrix 的落点)——`WeightLoadIntent::Trainable`(P1-7)。 -// ═══════════════════════════════════════════════════════════════════════════ - -// 一行反量化后的非零幅值 τ·K/n(n = 该行非零 trit 数;n = 0 ⇒ 0) -[[nodiscard]] inline float latent_row_scale(const std::uint32_t* words, std::size_t k, - float tau) noexcept -{ - std::size_t n = 0; - for (std::size_t i = 0; i < k; ++i) - if (at(words, i) != 0.0f) ++n; - if (n == 0) return 0.0f; - return tau * static_cast(k) / static_cast(n); -} - -// 打包行(2-bit 容器)→ latent 行(k 个 f32;`out` 为宿主连续缓冲) -inline void dequant_row(const std::uint32_t* words, std::size_t k, float tau, float* out) noexcept -{ - const float s = latent_row_scale(words, k, tau); - for (std::size_t i = 0; i < k; ++i) - out[i] = at(words, i) * s; -} - -// ═══════════════════════════════════════════════════════════════════════════ -// base-3(部署编码,1.6 bpw) -// -// 2-bit 定长编码是**运行期**容器(解码只有移位/掩码,喂 `matmul_q`);部署 -// 文件用 base-3 再省 20%(2.0 → 1.6 bpw):5 个三值/字节,字节 = Σ digit_i·3^i, -// `digit = 码 - 1 ∈ {0,1,2}`(即 trit + 1;与 2-bit 的 `码 = trit + 2` 差一个 -// 偏移,故两者的映射都在本头里唯一)。 -// -// 为什么 5/字节(243 个状态,1.6 bpw):`log2(3) = 1.585`,5 位一组是"每字节 -// 不浪费码位"的最大分组(3^5 = 243 ≤ 256、3^6 = 729 > 256)。这条与 -// microsoft/BitNet 无关,与 llama.cpp `TQ1_0` 的 base-3 分组同源 —— 差别是我们 -// 的块粒度是**行**、尺度是逐行 absmean(`TQ1_0` 是 per-256 + qh 尾巴 + 定点 -// `(s·3^n·3)>>8`);部署格式不需要与 GGUF 互读,故不引入 qh/interleave, -// 尾部直接补零 digit(解码按 k 截断)。 -// -// 何时用它:**只在部署文件的落盘/读盘路径**(`model_serialization` 的 v6)。 -// 运行期一律转回 2-bit 容器(loosen 一次宿主侧逐位转换,成本一次性)。 -// ═══════════════════════════════════════════════════════════════════════════ - -inline constexpr std::uint32_t kBase3PerByte = 5; // 5 个三值/字节 - -// 每行 base-3 字节数:ceil(K/5) -[[nodiscard]] inline constexpr std::size_t base3_bytes_per_row(std::size_t k) noexcept -{ return (k + kBase3PerByte - 1) / kBase3PerByte; } - -// 2-bit 容器行 → base-3 字节(部署落盘方向)。尾部不足 5 个的位置补 digit 0 -// (解码按 k 截断,补位不承载信息)。 -inline void pack_row_base3(const std::uint32_t* words, std::size_t k, std::uint8_t* out) noexcept -{ - const std::size_t bytes = base3_bytes_per_row(k); - for (std::size_t b = 0; b < bytes; ++b) - { - std::uint32_t byte = 0; - std::uint32_t pow3 = 1; - for (std::uint32_t i = 0; i < kBase3PerByte; ++i) - { - const std::size_t idx = b * kBase3PerByte + i; - const std::uint32_t code = - (idx < k) ? ((words[idx / kPerWord] >> (kBits * (idx % kPerWord))) & kMask) - : 2u; // 补位:码 2 → digit 1(无信息,解码截断) - byte += (code - 1u) * pow3; - pow3 *= 3u; - } - out[b] = static_cast(byte); - } -} - -// base-3 字节行 → 2-bit 容器行(部署读盘方向)。尾部字按既有约定填"码 2 = 三值 0"。 -inline void unpack_row_base3(const std::uint8_t* in, std::size_t k, std::uint32_t* words) noexcept -{ - const std::size_t nwords = words_per_row(k); - for (std::size_t wi = 0; wi < nwords; ++wi) - words[wi] = 0; - for (std::size_t idx = 0; idx < k; ++idx) - { - std::uint32_t byte = in[idx / kBase3PerByte]; - for (std::uint32_t i = 0; i < idx % kBase3PerByte; ++i) - byte /= 3u; - const std::uint32_t code = (byte % 3u) + 1u; // digit → 码 - words[idx / kPerWord] |= code << (kBits * (idx % kPerWord)); - } - // 尾部:显式写"码 2"(= 三值 0),与 pack_row 的约定一致(不做则留下码 0 = -2) - for (std::size_t idx = k; idx < nwords * kPerWord; ++idx) - words[idx / kPerWord] |= 2u << (kBits * (idx % kPerWord)); -} - -// 单点解码(参考/测试用):in 指向该行首字节 -[[nodiscard]] inline float at_base3(const std::uint8_t* in, std::size_t idx) noexcept -{ - std::uint32_t byte = in[idx / kBase3PerByte]; - for (std::uint32_t i = 0; i < idx % kBase3PerByte; ++i) - byte /= 3u; - return value_of((byte % 3u) + 1u); -} - -} // namespace trit_pack -} // namespace nn diff --git a/research/rla/01-what-is-rla.md b/research/rla/01-what-is-rla.md index 00c32de..a49d060 100644 --- a/research/rla/01-what-is-rla.md +++ b/research/rla/01-what-is-rla.md @@ -141,7 +141,7 @@ y_t = W_o concat(o_t^{(1)}, ..., o_t^{(H)}) 需要注意“无 A 状态”的精确范围:**RLA-2 的输出和反向公式不消费 A 状态**,但当前通用 `scan_prefix_outer` 仍会内部累计 `A=Σk'k'ᵀ` 并写入 `A·P` 行块,只是 Layer 忽略该输出。 -这是当前扫描原语([`scan_prefix_outer.comp`](../../shaders/scan_prefix_outer.comp))的实现成本, +这是当前扫描原语([`scan_prefix_outer.comp`](../../engine/engine/shaders/scan_prefix_outer.comp))的实现成本, 不改变 RLA-2 的算法定义。 ## 4. 为什么它是线性注意力 diff --git a/shaders/batched_matmul.comp b/shaders/batched_matmul.comp deleted file mode 100644 index e07cfde..0000000 --- a/shaders/batched_matmul.comp +++ /dev/null @@ -1,322 +0,0 @@ -// ── batched_matmul.comp ────────────────────────────────────────────────── -// 批量分块矩阵乘法原语:对每个 batch b 计算 C_b = alpha * op(A_b, B_b) -// -// 布局约定: -// A: (batch * A_rows_per_batch, A_cols) 行主序 — 按 batch 切分为连续行块 -// B: (batch * B_rows_per_batch, B_cols) 行主序 -// C: (batch * M, N) 行主序 — 每个批次结果垂直堆叠 -// -// 转置标志(与 matmul.comp 语义一致): -// transA=0: A_b 为 (M, K),A_b[m][k] = A[b*M*K + m*K + k] -// transA=1: A_b 存储为 (K, M) 按 A_b^T 使用,A_b[m][k] = A[b*M*K + k*M + m] -// transB=0: B_b 为 (K, N),B_b[k][n] = B[b*K*N + k*N + n] -// transB=1: B_b 存储为 (N, K) 按 B_b^T 使用,B_b[k][n] = B[b*K*N + n*K + k] -// -// ── 与 matmul_tiled.comp 同配方 ──────────────────────────────────────────── -// ① vec4 共享 Ash[k][m]/Bsh[k][n]:每线程每 k 仅 2 次 vec4 LDS + 16 FMA -// (8:1 计算:加载比) -// ② BK=16 双缓冲:tile t+1 的全局加载与 tile t 的计算并行发射,每 tile -// 仅 1 次 barrier,全局延迟(~600 周期)被计算覆盖 -// ③ vec4 全局快路径:按转置形态选连续维分组,对齐时整组一条 vec4 访问 -// (免逐元素 /,% 与边界判断) -// -// vec4 对齐条件(含 batch 基址 b*step:step%4==0 由下列条件蕴含): -// A: transA=0 需 K%4==0(step=M*K ⇒ K%4⇒step%4);transA=1 需 M%4==0 -// B: transB=0 需 N%4==0;transB=1 需 K%4==0(step=K*N) -// C: 写出需 N%4==0(step=M*N) -// 基址 16B 对齐由 MemoryPool 分配对齐 ≥16 保证(compute_memory_pool.hpp)。 -// 未对齐(奇数形状)或线程持边界组:回退同组标量 + 边界判零,覆盖与 -// vec4 路径完全一致 → 每个输出元素恰被写一次,语义不变。 -// -// 优化策略: -// - WorkGroup:16×16 线程,Z 维度索引 batch,计算 64×64 输出块 -// - alpha 在写出时一次完成(cuBLAS sgemm 语义,供上层折叠 1/sqrt(d_k) -// 等系数,省去额外的全矩阵 scale pass) -// - Dispatch: (ceil(N/64), ceil(M/64), batch);PC: M,N,K,transA,transB,alpha -// -// 注意:此着色器只包含原语操作(矩阵乘法的批量索引 + 标量缩放), -// 不含任何算法语义。 -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// ── 精度变体(编译期开关:glslc -DNN_SHADER_F16=1)──────────────────────── -// 默认(无宏)= 全 f32(f32 分支宏展开与裸写 f32 代码逐字同形 → -// SPIR-V 字节零差异)。 -// 带宏 = f16 存储变体:缓冲声明 float16_t(GL_EXT_shader_16bit_storage), -// 别名槽改 uvec2(4×half = 8B,std430 步长恰好 8)→ unpackHalf2x16 解出 -// vec4;全局读处统一 float(...) 转 f32、写出处统一 float16_t(...)。 -// **共享 tile / vec4 外积累加 / 分块 / 双缓冲 / barrier 节奏与 f32 完全同构** -// ——只有全局加载/写出的元素类型变了,符合 §7.2「f32 参考累加 + 输出舍入」。 -// C 不提供 vec4 别名(f16 走逐标量写出,与 N%4!=0 的回退路径同构)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_V4 uvec2 -#define NN_LOADV4(arr, i) vec4(unpackHalf2x16(arr[i].x), unpackHalf2x16(arr[i].y)) -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_V4 vec4 -#define NN_LOADV4(arr, i) arr[i] -#define NN_RD(x) x -#define NN_WR(x) x -#endif - -layout(local_size_x = 16, local_size_y = 16) in; - -layout(std430, binding = 0) readonly buffer BufferA { NN_ETYPE A[]; }; -layout(std430, binding = 1) readonly buffer BufferB { NN_ETYPE B[]; }; -layout(std430, binding = 2) writeonly buffer BufferC { NN_ETYPE C[]; }; - -// vec4 别名视图(同 binding 双声明;仅在元素下标 %4==0 的快路径使用) -layout(std430, binding = 0) readonly buffer BufferAv4 { NN_V4 Av4[]; }; -layout(std430, binding = 1) readonly buffer BufferBv4 { NN_V4 Bv4[]; }; -#if !defined(NN_SHADER_F16) -layout(std430, binding = 2) writeonly buffer BufferCv4 { vec4 Cv4[]; }; -#endif - -// Push constants:单 batch 逻辑维度 + 转置标志 + 输出缩放(batch 步长可推导) -layout(push_constant) uniform PushConstants { - uint M; // 每个 batch 的输出行数 - uint N; // 每个 batch 的输出列数 - uint K; // 收缩维度 - uint transA; // 1 = A_b 存储为 (K,M),按 A_b^T 使用 - uint transB; // 1 = B_b 存储为 (N,K),按 B_b^T 使用 - float alpha; // 输出缩放系数:C_b = alpha * op(A_b, B_b) -}; - -// 分块维度常量(与 matmul_tiled 同步):BK=16 双缓冲,16KB 共享保占用率 -// (BK=32 变体四点实测回退,数据见 matmul_tiled 的 BK 常量注释) -const uint BM = 64u; -const uint BN = 64u; -const uint BK = 16u; - -// 每线程协作加载元素数(BM*BK/256 → 4;BK=16 时 groups=1,改 BK 前先验 %4==0) -const uint EPT_A = (BM * BK) / 256u; -const uint EPT_B = (BK * BN) / 256u; - -// Shared Memory(vec4 布局,16B 对齐,双缓冲): -// Ash[stage][k][m]:A^T 分块(转置写共享内存,内层按行连续读) -// Bsh[stage][k][n]:B 分块 -shared vec4 Ash[2][BK][BM / 4]; -shared vec4 Bsh[2][BK][BN / 4]; - -// ── 协作加载 A 分块 BM×BK → 指定 stage(base = b*M*K)──────────────────── -void load_A_tile(uint t, uint stage, uint tid, uint block_row, uint base) -{ - const uint a_base_col = t * BK; - const bool k_contig = (transA == 0u); - // 沿 k 连续需 K%4==0;沿 m 连续需 M%4==0(两者均蕴含 base%4==0) - const bool aligned = k_contig ? ((K & 3u) == 0u) : ((M & 3u) == 0u); - const uint groups = EPT_A / 4u; - - for (uint vv = 0u; vv < groups; ++vv) - { - const uint g = tid * groups + vv; - if (k_contig) - { - // 沿 k 连续:4 个连续 k,同一行 m(base + gr*K + gc,gc%4==0) - const uint m = (4u * g) / BK; - const uint k0 = (4u * g) % BK; - const uint gr = block_row + m; - const uint gc = a_base_col + k0; - if (aligned && gr < M && gc < K) - { - const vec4 v = NN_LOADV4(Av4, (base + gr * K + gc) >> 2); - Ash[stage][k0 ][m >> 2][m & 3u] = v.x; - Ash[stage][k0 + 1u][m >> 2][m & 3u] = v.y; - Ash[stage][k0 + 2u][m >> 2][m & 3u] = v.z; - Ash[stage][k0 + 3u][m >> 2][m & 3u] = v.w; - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr < M && gc + j < K) ? NN_RD(A[base + gr * K + gc + j]) : 0.0; - Ash[stage][k0 + j][m >> 2][m & 3u] = v; - } - } - } - else - { - // 沿 m 连续(A 存储 K×M):4 个连续 m,同一 k(base + gc*M + gr) - const uint mq = g % (BM / 4u); - const uint k = g / (BM / 4u); - const uint gc = a_base_col + k; - const uint gr = block_row + mq * 4u; - if (aligned && gc < K && gr < M) - { - Ash[stage][k][mq] = NN_LOADV4(Av4, (base + gc * M + gr) >> 2); - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr + j < M && gc < K) ? NN_RD(A[base + gc * M + gr + j]) : 0.0; - Ash[stage][k][mq][j] = v; - } - } - } - } -} - -// ── 协作加载 B 分块 BK×BN → 指定 stage(base = b*K*N)──────────────────── -void load_B_tile(uint t, uint stage, uint tid, uint block_col, uint base) -{ - const uint b_base_row = t * BK; - const bool n_contig = (transB == 0u); - // 沿 n 连续需 N%4==0;沿 k 连续需 K%4==0(两者均蕴含 base%4==0) - const bool aligned = n_contig ? ((N & 3u) == 0u) : ((K & 3u) == 0u); - const uint groups = EPT_B / 4u; - - for (uint vv = 0u; vv < groups; ++vv) - { - const uint g = tid * groups + vv; - if (n_contig) - { - // 沿 n 连续:4 个连续 n,同一行 k(base + gr*N + gc) - const uint k = (4u * g) / BN; - const uint n0 = (4u * g) % BN; - const uint gr = b_base_row + k; - const uint gc = block_col + n0; - if (aligned && gr < K && gc < N) - { - const vec4 v = NN_LOADV4(Bv4, (base + gr * N + gc) >> 2); - Bsh[stage][k][n0 >> 2] = v; - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr < K && gc + j < N) ? NN_RD(B[base + gr * N + gc + j]) : 0.0; - Bsh[stage][k][(n0 + j) >> 2][(n0 + j) & 3u] = v; - } - } - } - else - { - // 沿 k 连续(B 存储 N×K):4 个连续 k,同一 n(base + gc*K + gr) - const uint n = g / 4u; - const uint k0 = (g % 4u) * 4u; - const uint gc = block_col + n; - const uint gr = b_base_row + k0; - if (aligned && gc < N && gr < K) - { - const vec4 v = NN_LOADV4(Bv4, (base + gc * K + gr) >> 2); - for (uint j = 0u; j < 4u; ++j) - Bsh[stage][k0 + j][n >> 2][n & 3u] = v[j]; - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr + j < K && gc < N) ? NN_RD(B[base + gc * K + gr + j]) : 0.0; - Bsh[stage][k0 + j][n >> 2][n & 3u] = v; - } - } - } - } -} - -void main() -{ - const uint tx = gl_LocalInvocationID.x; // 0..15 - const uint ty = gl_LocalInvocationID.y; // 0..15 - const uint tid = ty * 16u + tx; // 线性线程 ID 0..255 - const uint b = gl_WorkGroupID.z; // batch 索引 - - // 当前 WorkGroup 负责的 batch 内输出块起始坐标 + 各 buffer 的 batch 基址 - const uint block_row = gl_WorkGroupID.y * BM; - const uint block_col = gl_WorkGroupID.x * BN; - const uint base_a = b * M * K; - const uint base_b = b * K * N; - const uint base_c = b * M * N; - - // ── 寄存器分块:4×4 累加器(vec4) ───────────────────────────── - vec4 acc[4]; - for (uint i = 0u; i < 4u; ++i) - acc[i] = vec4(0.0); - - const uint num_tiles = (K + BK - 1u) / BK; - - // ── 前奏:加载 tile 0 → stage 0,同步一次 ─────────────────────── - load_A_tile(0u, 0u, tid, block_row, base_a); - load_B_tile(0u, 0u, tid, block_col, base_b); - barrier(); - - // ── 流水循环:发射 tile t+1 的加载(stage^1),同时计算 tile t ──── - uint stage = 0u; - for (uint t = 0u; t < num_tiles; ++t) - { - const uint nxt = t + 1u; - const uint nstage = stage ^ 1u; - if (nxt < num_tiles) - { - load_A_tile(nxt, nstage, tid, block_row, base_a); - load_B_tile(nxt, nstage, tid, block_col, base_b); - } - - // ── vec4 外积累加(每 k:2 次 vec4 LDS + 16 FMA = 8:1) ────── - #pragma unroll 4 - for (uint k = 0u; k < BK; ++k) - { - const vec4 a_vec = Ash[stage][k][ty]; - const vec4 b_vec = Bsh[stage][k][tx]; - - acc[0] += a_vec.x * b_vec; - acc[1] += a_vec.y * b_vec; - acc[2] += a_vec.z * b_vec; - acc[3] += a_vec.w * b_vec; - } - - barrier(); - stage = nstage; - } - - // ── 写入结果(batch 偏移 = b * M * N,含 alpha 缩放)──────────── -#if defined(NN_SHADER_F16) - // f16 输出:逐标量写出(C 无 vec4 别名视图),与 f32 的 N%4!=0 回退同构 - for (uint i = 0u; i < 4u; ++i) - { - const uint out_row = block_row + ty * 4u + i; - if (out_row >= M) continue; - const uint out_col0 = block_col + tx * 4u; - if (out_col0 >= N) continue; - C[base_c + out_row * N + out_col0 + 0u] = NN_WR(alpha * acc[i].x); - if (out_col0 + 1u < N) - C[base_c + out_row * N + out_col0 + 1u] = NN_WR(alpha * acc[i].y); - if (out_col0 + 2u < N) - C[base_c + out_row * N + out_col0 + 2u] = NN_WR(alpha * acc[i].z); - if (out_col0 + 3u < N) - C[base_c + out_row * N + out_col0 + 3u] = NN_WR(alpha * acc[i].w); - } -#else - const bool c_vec4 = ((N & 3u) == 0u); - for (uint i = 0u; i < 4u; ++i) - { - const uint out_row = block_row + ty * 4u + i; - if (out_row >= M) continue; - const uint out_col0 = block_col + tx * 4u; - if (c_vec4) - { - if (out_col0 < N) - Cv4[(base_c + out_row * N + out_col0) >> 2] = alpha * acc[i]; - } - else - { - if (out_col0 >= N) continue; - C[base_c + out_row * N + out_col0 + 0u] = alpha * acc[i].x; - if (out_col0 + 1u < N) - C[base_c + out_row * N + out_col0 + 1u] = alpha * acc[i].y; - if (out_col0 + 2u < N) - C[base_c + out_row * N + out_col0 + 2u] = alpha * acc[i].z; - if (out_col0 + 3u < N) - C[base_c + out_row * N + out_col0 + 3u] = alpha * acc[i].w; - } - } -#endif -} diff --git a/shaders/cast.comp b/shaders/cast.comp deleted file mode 100644 index 37c66c6..0000000 --- a/shaders/cast.comp +++ /dev/null @@ -1,53 +0,0 @@ -// ── cast.comp — 通用精度转换原语 ─────────────────────────────────────── -// 目标:engine.cast(tensor, precision) 的 GPU 原生实现(无 PCIe 往返)。 -// 源/目标均以原始 32-bit word 缓冲读写(float=4B,f16 按 2B 紧密打包): -// - f16→f32(kind=0):S[w] = 两个 half 打包成 uint;D[2w/2w+1] = float words -// - f32→f16(kind=1,round-half-to-even):S[2w/2w+1] = float words; -// D[w] = unpackHalf2x16 逆操作 packHalf2x16 打包两个 half -// 同精度 cast 是别名不落 kernel;BF16/F64 为保留值,后端分派处清晰报错。 -// 后续精度对(BF16/F64/f8 等)扩 kind 枚举 + 后端 (src,dst) 分派表即可, -// 引擎 API 恒为 cast(tensor, precision),不因精度增加而改变。 -// ─────────────────────────────────────────────────────────────────────── -#version 450 -layout(local_size_x = 256) in; - -#include "nn_f16_rne.glsl" - -layout(std430, binding = 0) readonly buffer BufS { uint S[]; }; -layout(std430, binding = 1) writeonly buffer BufD { uint D[]; }; - -layout(push_constant) uniform Push { - uint count; // 元素数 rows*cols(源与目标元素数相同,仅字节宽度差) - uint kind; // 0 = f16→f32,1 = f32→f16 -}; - -void main() { - const uint pairs = (count + 1u) >> 1u; // 每线程处理 2 元素(一对 half 或单词对) - const uint gsize = gl_NumWorkGroups.x * gl_NumWorkGroups.y - * gl_NumWorkGroups.z * 256u; - - if (kind == 0u) - { - // f16 → f32(升精度,精确无损) - for (uint w = gl_GlobalInvocationID.x; w < pairs; w += gsize) - { - const vec2 h = unpackHalf2x16(S[w]); - D[2u * w] = floatBitsToUint(h.x); - if (2u * w + 1u < count) - D[2u * w + 1u] = floatBitsToUint(h.y); - } - } - else - { - // f32 → f16(降精度):packHalf2x16 的舍入模式**未被规范钉死**,实测 - // NVIDIA 驱动向零截断(Khronos Vulkan-Docs #1825)→ 先用整数位运算做 - // RNE 预舍入,值落在 f16 网格上后再打包,打包不再产生偏差。 - for (uint w = gl_GlobalInvocationID.x; w < pairs; w += gsize) - { - const uint i0 = 2u * w; - const float x = uintBitsToFloat(S[i0]); - const float y = (i0 + 1u < count) ? uintBitsToFloat(S[i0 + 1u]) : 0.0f; - D[w] = packHalf2x16(vec2(nn_f16_rne(x), nn_f16_rne(y))); - } - } -} \ No newline at end of file diff --git a/shaders/col2im.comp b/shaders/col2im.comp deleted file mode 100644 index a4925f8..0000000 --- a/shaders/col2im.comp +++ /dev/null @@ -1,89 +0,0 @@ -// ── col2im.comp ─────────────────────────────────────────────────────────── -// 纯原语:im2col 的伴随(adjoint / 反向散射),数据搬运,绝不含算法 -// -// col (C*k*k, B*OH*OW) → out (C*H*W, B) -// out[ci*H*W + ih*W + iw, b] -// = Σ_{kh,kw} col[(ci*k + kh)*k + kw, (oh*OW + ow)*B + b] -// 其中 oh = (ih + pad - kh)/stride(须整除且落在 [0, OH)),ow 同理。 -// **重叠窗口(stride < k)的贡献在此累加**;每个输出元素由单个线程完整 -// 求和,故不需要原子操作、也不需要预先清零输出。 -// -// 绑定布局: -// binding 0: 输入 col (readonly, C*k*k × B*OH*OW) -// binding 1: 输出 out (writeonly, C*H*W × B) -// -// Push Constants(36 bytes): -// uint C, H, W, k, stride, pad, OH, OW, B -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2:数据搬运,输入输出均 half; -// 窗口累加在 f32 完成后落回 half。仅需 storageBuffer16BitAccess) -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif - -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufIn { NN_ETYPE in_data[]; }; -layout(std430, binding = 1) writeonly buffer BufOut { NN_ETYPE out_data[]; }; - -layout(push_constant) uniform PushConstants { - uint C; - uint H; - uint W; - uint k; - uint stride; - uint pad; - uint OH; - uint OW; - uint B; -}; - -void main() -{ - const uint idx = gl_GlobalInvocationID.x; - const uint rows = C * H * W; - const uint cols = B; - if (idx >= rows * cols) return; - - const uint row = idx / cols; // 输入像素行 = ci*H*W + ih*W + iw - const uint b = idx % cols; - - const uint ci = row / (H * W); - const uint rem = row % (H * W); - const uint ih = rem / W; - const uint iw = rem % W; - - const uint P = OH * OW; - const int s_stride = int(stride); - - float acc = 0.0; - for (uint kh = 0; kh < k; ++kh) - { - const int t = int(ih + pad) - int(kh); - if (t < 0 || (t % s_stride) != 0) continue; - const int oh = t / s_stride; - if (oh >= int(OH)) continue; - for (uint kw = 0; kw < k; ++kw) - { - const int s = int(iw + pad) - int(kw); - if (s < 0 || (s % s_stride) != 0) continue; - const int ow = s / s_stride; - if (ow >= int(OW)) continue; - const uint r = (ci * k + kh) * k + kw; - acc += NN_RD(in_data[r * (B * P) + (uint(oh) * OW + uint(ow)) * B + b]); - } - } - out_data[idx] = NN_WR(acc); -} diff --git a/shaders/elementwise_v2.comp b/shaders/elementwise_v2.comp deleted file mode 100644 index 1b4877d..0000000 --- a/shaders/elementwise_v2.comp +++ /dev/null @@ -1,284 +0,0 @@ -// ── elementwise_v2.comp ─────────────────────────────────────────────────── -// 纯原语逐元素运算着色器(不含任何算法) -// -// 设计原则:本着色器只提供 op-level 原语(加减乘除、exp、sqrt、max、min、 -// select 等),绝不包含 ReLU、GeLU、Softmax、LayerNorm 等算法。 -// 算法由 Layer 层通过组合原语表达。 -// -// 绑定布局: -// binding 0: 输入 A(条件操作数 1 / 主操作数) -// binding 1: 输入 B(条件操作数 2 / then 张量 / 次操作数) -// binding 2: 输入 C(else 张量,仅 SELECT 模式使用) -// binding 3: 输出 OUT -// -// Push Constants: -// uint count — 元素总数 -// uint mode — 运算模式(见下) -// uint op — 一元/二元算子编号(mode 0/1 使用) -// uint cmp_op — 比较算子编号(mode 2 SELECT 使用) -// uint flags — 位标志: -// bit0: B 为标量(用 scalar_b 替代 B[idx]) -// bit1: then 为标量(用 scalar_then 替代 B[idx]) -// bit2: else 为标量(用 scalar_else 替代 C[idx]) -// bit3: 二元运算标量在前(scalar op A,仅 mode 1) -// float scalar_b — 标量 B / 条件操作数 2 标量 -// float scalar_then — then 标量值 -// float scalar_else — else 标量值 -// -// 运算模式(mode): -// 0 = UNARY: out = unary_op(A[idx]) -// 1 = BINARY: out = binary_op(a, b) -// a = A[idx] -// b = (flags & 1) ? scalar_b : B[idx] -// 若 flags & 8: out = binary_op(b, a) [标量在前] -// 2 = SELECT: out = compare_op(a, b) ? then_val : else_val -// a = A[idx] -// b = (flags & 1) ? scalar_b : B[idx] -// then_val = (flags & 2) ? scalar_then : B[idx] -// else_val = (flags & 4) ? scalar_else : C[idx] -// 3 = AXPY: out = A[idx] + scalar_b * B[idx] (融合 axpy:1 次 dispatch 替代 3 次) -// 用于 Optimizer 的 scale_add_(dst, scalar, src) 操作 -// -// 一元算子(op): -// 0=Neg, 1=Exp, 2=Log, 3=Sqrt, 4=Rsqrt, 5=Abs, 6=Tanh -// -// 二元算子(op): -// 0=Add, 1=Sub, 2=Mul, 3=Div, 4=Max, 5=Min -// -// 比较算子(cmp_op): -// 0=Lt, 1=Le, 2=Gt, 3=Ge, 4=Eq, 5=Ne -// -// ── vec4 kernel(与 DSL 融合逐元素 vec4 路径同契约)──────────────────────── -// DSL 侧(expr_glsl_gen glsl_vec4_eligible + vec4 kernel)按「每线程 4 个 -// 相邻元素」发射 vec4 加载/运算/存储,dispatch 宽度 4;本 shader 镜像同一 -// 契约: -// - main:base = gid*4,base >= count 返回; -// - 快路径(count%4==0 且 base+3 < count)→ 全 vec4(索引 flat、4 相邻 -// 元素连续 ⇒ vec4 别名视图整读整写;16B 对齐由 MemoryPool 保证); -// - 回退:同 kernel 内 4 次标量迭代(count%4≠0 的尾组/奇数 count), -// 快路径与回退覆盖完全一致 → 每个元素恰被处理一次; -// - backend dispatch:ceil(count / (256*4))(与 run_fused_gpu 的 -// count/(256*vec_width) 同口径)。 -// vec4 运算用 GLSL 内建重载(exp(vec4)、mix(vec4,vec4,bvec4) 等), -// 语义与标量路径逐位同构(exp 等超越数按 lane 独立求值)。 -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C1b): -// 缓冲 float16_t、算术 f32(load→float() → 计算 → float16_t() 落回, -// 与 Phase 2 in-kernel f16 同语义——消灭边界 cast 的整份 f32 副本, -// 中间值精度反而更高)。vec4 别名改 uvec2(4×half=8B,std430 步长恰 8; -// unpackHalf2x16/packHalf2x16 与 matmul_tiled 的 NN_V4 同款)。 -// 只需 storageBuffer16BitAccess(f16↔f32 转换不需 Float16 capability, -// 既有56个融合变体的 OpFConvert 同证)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_V4 uvec2 -#define NN_LOADV4(arr, i) vec4(unpackHalf2x16(arr[i].x), unpackHalf2x16(arr[i].y)) -#define NN_PACK4(v) uvec2(packHalf2x16(vec2(nn_f16_rne((v).x), nn_f16_rne((v).y))), \ - packHalf2x16(vec2(nn_f16_rne((v).z), nn_f16_rne((v).w)))) -#define NN_RD(x) float(x) -// f16 写出统一走 RNE:标量回退用 NN_WR,vec4 快路径用 NN_PACK4(逐 lane 先 -// RNE 再 pack)——float16_t()/packHalf2x16 在本驱动的舍入都不受规范约束。 -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_V4 vec4 -#define NN_LOADV4(arr, i) arr[i] -#define NN_PACK4(v) (v) -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif - -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufA { NN_ETYPE buf_a[]; }; -layout(std430, binding = 1) readonly buffer BufB { NN_ETYPE buf_b[]; }; -layout(std430, binding = 2) readonly buffer BufC { NN_ETYPE buf_c[]; }; -layout(std430, binding = 3) writeonly buffer BufOut { NN_ETYPE buf_out[]; }; - -// vec4 别名视图(同 binding 双声明,std430;仅 count%4==0 快路径使用) -layout(std430, binding = 0) readonly buffer BufAv4 { NN_V4 buf_a4[]; }; -layout(std430, binding = 1) readonly buffer BufBv4 { NN_V4 buf_b4[]; }; -layout(std430, binding = 2) readonly buffer BufCv4 { NN_V4 buf_c4[]; }; -layout(std430, binding = 3) writeonly buffer BufOut4 { NN_V4 buf_out4[]; }; - -layout(push_constant) uniform PushConstants { - uint count; - uint mode; - uint op; - uint cmp_op; - uint flags; - float scalar_b; - float scalar_then; - float scalar_else; -}; - -// ── 一元运算(标量 / vec4 同名重载,语义逐 lane 同构)───────────────────── -float apply_unary(uint op, float x) -{ - if (op == 0u) return -x; // Neg - if (op == 1u) return exp(x); // Exp - if (op == 2u) return log(x); // Log - if (op == 3u) return sqrt(x); // Sqrt - if (op == 4u) return 1.0 / sqrt(x); // Rsqrt - if (op == 5u) return abs(x); // Abs - if (op == 6u) return tanh(x); // Tanh - return 0.0; -} -vec4 apply_unary(uint op, vec4 x) -{ - if (op == 0u) return -x; - if (op == 1u) return exp(x); - if (op == 2u) return log(x); - if (op == 3u) return sqrt(x); - if (op == 4u) return 1.0 / sqrt(x); - if (op == 5u) return abs(x); - if (op == 6u) return tanh(x); - return vec4(0.0); -} - -// ── 二元运算 ────────────────────────────────────────────────────────────── -float apply_binary(uint op, float a, float b) -{ - if (op == 0u) return a + b; // Add - if (op == 1u) return a - b; // Sub - if (op == 2u) return a * b; // Mul - if (op == 3u) return a / b; // Div - if (op == 4u) return max(a, b); // Max - if (op == 5u) return min(a, b); // Min - return 0.0; -} -vec4 apply_binary(uint op, vec4 a, vec4 b) -{ - if (op == 0u) return a + b; - if (op == 1u) return a - b; - if (op == 2u) return a * b; - if (op == 3u) return a / b; - if (op == 4u) return max(a, b); - if (op == 5u) return min(a, b); - return vec4(0.0); -} - -// ── 比较运算(vec4 返回 bvec4,供 mix 按 lane 选择)──────────────────────── -bool apply_compare(uint op, float a, float b) -{ - if (op == 0u) return a < b; // Lt - if (op == 1u) return a <= b; // Le - if (op == 2u) return a > b; // Gt - if (op == 3u) return a >= b; // Ge - if (op == 4u) return a == b; // Eq - if (op == 5u) return a != b; // Ne - return false; -} -bvec4 apply_compare(uint op, vec4 a, vec4 b) -{ - if (op == 0u) return lessThan(a, b); - if (op == 1u) return lessThanEqual(a, b); - if (op == 2u) return greaterThan(a, b); - if (op == 3u) return greaterThanEqual(a, b); - if (op == 4u) return equal(a, b); - if (op == 5u) return notEqual(a, b); - return bvec4(false); -} - -// ── 标量主路径(回退:每元素一次;idx 由调用方给出)─────────────────────── -float eval_scalar(uint idx) -{ - // f16 缓冲 → float:显式 float() 转换(与既有56个融合变体同款; - // 隐式宽化未被既有编译路径覆盖,不引入未验证语法) - const float a = NN_RD(buf_a[idx]); - - // ── 模式 0:一元运算 ── - if (mode == 0u) - return apply_unary(op, a); - - // ── 模式 1:二元运算 ── - if (mode == 1u) - { - const float b = ((flags & 1u) != 0u) ? scalar_b : float(buf_b[idx]); - if ((flags & 8u) != 0u) - return apply_binary(op, b, a); // 标量在前 - else - return apply_binary(op, a, b); // 张量在前 - } - - // ── 模式 2:条件选择 ── - if (mode == 2u) - { - const float b = ((flags & 1u) != 0u) ? scalar_b : float(buf_b[idx]); - const float then_v = ((flags & 2u) != 0u) ? scalar_then : float(buf_b[idx]); - const float else_v = ((flags & 4u) != 0u) ? scalar_else : float(buf_c[idx]); - return apply_compare(cmp_op, a, b) ? then_v : else_v; - } - - // ── 模式 3:AXPY ── - if (mode == 3u) - return a + scalar_b * float(buf_b[idx]); - - return 0.0; -} - -// ── vec4 快路径(count%4==0 的整组 4 元素)──────────────────────────────── -vec4 eval_vec4(uint base) -{ - // f16 变体:uvec2(4×half) 解包为 vec4(NN_LOADV4 宏分派);f32 直读 - const vec4 a = NN_LOADV4(buf_a4, base >> 2); - - if (mode == 0u) - return apply_unary(op, a); - - if (mode == 1u) - { - const vec4 b = ((flags & 1u) != 0u) ? vec4(scalar_b) - : NN_LOADV4(buf_b4, base >> 2); - if ((flags & 8u) != 0u) - return apply_binary(op, b, a); - else - return apply_binary(op, a, b); - } - - if (mode == 2u) - { - const vec4 b = ((flags & 1u) != 0u) ? vec4(scalar_b) - : NN_LOADV4(buf_b4, base >> 2); - const vec4 then_v = ((flags & 2u) != 0u) ? vec4(scalar_then) - : NN_LOADV4(buf_b4, base >> 2); - const vec4 else_v = ((flags & 4u) != 0u) ? vec4(scalar_else) - : NN_LOADV4(buf_c4, base >> 2); - // mix(else, then, cond):cond = compare(a, b)(bvec4 按 lane 选择) - return mix(else_v, then_v, apply_compare(cmp_op, a, b)); - } - - if (mode == 3u) - return a + scalar_b * NN_LOADV4(buf_b4, base >> 2); - - return vec4(0.0); -} - -void main() -{ - const uint i = gl_GlobalInvocationID.x; - const uint base = i * 4u; - if (base >= count) return; - - // 快路径:整组 4 元素齐全且 count 对齐 → vec4 整读整写 - if ((count & 3u) == 0u && base + 3u < count) - { - // f16:vec4 → uvec2 打包(packHalf2x16);f32 恒等透传 - buf_out4[base >> 2] = NN_PACK4(eval_vec4(base)); - return; - } - - // 回退:同组 4 次标量迭代(尾组 / 奇数 count),覆盖与快路径一致 - for (uint j = 0u; j < 4u; ++j) - { - const uint e = base + j; - if (e >= count) break; - // f16:float → float16_t 显式窄化;f32 恒等 - buf_out[e] = NN_WR(eval_scalar(e)); - } -} diff --git a/shaders/gather.comp b/shaders/gather.comp deleted file mode 100644 index 81f4987..0000000 --- a/shaders/gather.comp +++ /dev/null @@ -1,108 +0,0 @@ -// ── gather.comp ─────────────────────────────────────────────────────────── -// 纯原语:按行索引从表中查表(gather_rows) -// -// 语义: -// table: (vocab, D) — 行主序存储 -// indices: (num,) — 行索引(float 编码的整数) -// output: (num, D) — output[i] = table[indices[i]] -// -// 越界索引返回零行(防御性,与 CPU fallback 行为一致)。 -// -// 绑定布局: -// binding 0: table (readonly) -// binding 1: indices (readonly) -// binding 2: output (writeonly) -// -// Push Constants(12 bytes): -// uint vocab — table 行数 -// uint D — 列宽(每行元素数) -// uint num — indices 元素个数 -// -// ── vec4 + 按组求商(与 DSL 侧「索引按组派生、免逐元素除模」及 vec4 kernel -// 契约一致)────────────────────────────────────────────────────────────── -// 每线程处理 1 个 4 元素组(base = gid*4),组级求商替代逐元素两次除模: -// - 快路径(D%4==0 且 base+3 2048),f16 整数精度不足会静默丢索引,禁止转换。 -// vec4 快路径别名改用 uvec2(4×half = 8B,std430 步长恰 8; -// (row*D+d0)%4==0 ⇒ 字节偏移 8 对齐 ✓;与 matmul_tiled 的 NN_V4=uvec2 同款)。 -// 只需 storageBuffer16BitAccess(拷贝无算术)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -layout(std430, binding = 0) readonly buffer BufTable { float16_t table[]; }; -layout(std430, binding = 1) readonly buffer BufIndices { float indices[]; }; -layout(std430, binding = 2) writeonly buffer BufOut { float16_t out_data[]; }; -// uvec2 别名视图(同 binding 双声明,std430;仅 D%4==0 快路径使用) -layout(std430, binding = 0) readonly buffer BufTable2 { uvec2 table2[]; }; -layout(std430, binding = 2) writeonly buffer BufOut2 { uvec2 out2[]; }; -#define NN_T4 uvec2 -#define NN_TBL4 table2 -#define NN_OUT4 out2 -#define NN_ZERO4 uvec2(0u) -#define NN_ZERO float16_t(0.0) -#else -layout(std430, binding = 0) readonly buffer BufTable { float table[]; }; -layout(std430, binding = 1) readonly buffer BufIndices { float indices[]; }; -layout(std430, binding = 2) writeonly buffer BufOut { float out_data[]; }; - -// vec4 别名视图(同 binding 双声明,std430;仅 D%4==0 快路径使用) -layout(std430, binding = 0) readonly buffer BufTable4 { vec4 table4[]; }; -layout(std430, binding = 2) writeonly buffer BufOut4 { vec4 out4[]; }; -#define NN_T4 vec4 -#define NN_TBL4 table4 -#define NN_OUT4 out4 -#define NN_ZERO4 vec4(0.0) -#define NN_ZERO 0.0 -#endif - -layout(local_size_x = 256) in; - -layout(push_constant) uniform PushConstants { - uint vocab; - uint D; - uint num; -}; - -void main() -{ - const uint i = gl_GlobalInvocationID.x; - const uint base = i * 4u; - const uint total = num * D; - if (base >= total) return; - - // ── 快路径:D%4==0 ⇒ 组内 4 元素同行;d0 对齐(f32:16B / f16:8B)── - if ((D & 3u) == 0u && base + 3u < total) - { - const uint ii = base / D; // 索引数组下标(每组一次除法) - const uint d0 = base - ii * D; - const uint row = uint(indices[ii]); // ← 表行号来自 indices[ii] - if (row < vocab) - NN_OUT4[base >> 2] = NN_TBL4[(row * D + d0) >> 2]; - else - NN_OUT4[base >> 2] = NN_ZERO4; // 越界行整组置零 - return; - } - - // ── 回退:标量(每元素求 i/d;与 vec4 快路径逐位一致)── - for (uint j = 0u; j < 4u; ++j) - { - const uint e = base + j; - if (e >= total) break; - const uint ii = e / D; - const uint d = e % D; - const uint row_idx = uint(indices[ii]); - if (row_idx < vocab) - out_data[e] = table[row_idx * D + d]; - else - out_data[e] = NN_ZERO; - } -} diff --git a/shaders/group_reduce.comp b/shaders/group_reduce.comp deleted file mode 100644 index c2b515f..0000000 --- a/shaders/group_reduce.comp +++ /dev/null @@ -1,70 +0,0 @@ -// ── group_reduce.comp ───────────────────────────────────────────────────── -// 分组归约原语(segmented reduction,沿行方向按固定长度 R 分组) -// -// x (G*R, N) → out (G, N) -// reduce_op=0 (sum): out[g][n] = Σ_{i= G * N) return; - - const uint g = idx / N; - const uint n = idx % N; - const bool is_max = (reduce_op == 1u); - - // max 恒等元 = numeric_limits::lowest()(0xFF7FFFFF,全库统一—— - // reduce.comp / DSL / CPU 同值);-inf 不能作恒等元:全 -inf 输入下 - // 与减法组合会出 −inf−−inf=NaN - float acc = is_max ? uintBitsToFloat(0xFF7FFFFFu) : 0.0; - const uint base = g * R * N + n; - for (uint i = 0u; i < R; ++i) - { - const float v = NN_RD(in_data[base + i * N]); - acc = is_max ? max(acc, v) : (acc + v); - } - out_data[idx] = acc; -} diff --git a/shaders/im2col.comp b/shaders/im2col.comp deleted file mode 100644 index b50db76..0000000 --- a/shaders/im2col.comp +++ /dev/null @@ -1,78 +0,0 @@ -// ── im2col.comp ─────────────────────────────────────────────────────────── -// 纯原语:滑动窗展开(数据搬运,绝不含卷积/池化算法) -// -// x (C*H*W, B) → out (C*k*k, B*OH*OW) -// out[(ci*k + kh)*k + kw, (oh*OW + ow)*B + b] -// = x[ci*H*W + (oh*stride + kh - pad)*W + (ow*stride + kw - pad), b] -// 越界(padding 区)取 0(列序为「位置优先」(oh, ow, b)) -// -// 绑定布局: -// binding 0: 输入 x (readonly, C*H*W × B) -// binding 1: 输出 out (writeonly, C*k*k × B*OH*OW) -// -// Push Constants(36 bytes): -// uint C, H, W, k, stride, pad, OH, OW, B -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2:数据搬运,输入输出均 half; -// 越界补 0 的 float 0.0 显式转 half。仅需 storageBuffer16BitAccess) -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_WR(x) (x) -#endif - -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufIn { NN_ETYPE in_data[]; }; -layout(std430, binding = 1) writeonly buffer BufOut { NN_ETYPE out_data[]; }; - -layout(push_constant) uniform PushConstants { - uint C; - uint H; - uint W; - uint k; - uint stride; - uint pad; - uint OH; - uint OW; - uint B; -}; - -void main() -{ - const uint idx = gl_GlobalInvocationID.x; - const uint kk = k * k; - const uint rows = C * kk; - const uint P = OH * OW; - const uint cols = B * P; - if (idx >= rows * cols) return; - - const uint r = idx / cols; - const uint c = idx % cols; - - const uint ci = r / kk; - const uint kh = (r / k) % k; - const uint kw = r % k; - - const uint p = c / B; // 位置优先列序 (p, b) - const uint b = c % B; - const uint oh = p / OW; - const uint ow = p % OW; - - const int ih = int(oh * stride + kh) - int(pad); - const int iw = int(ow * stride + kw) - int(pad); - - float v = 0.0; - if (ih >= 0 && ih < int(H) && iw >= 0 && iw < int(W)) - // 行主序:(row, b) → row * B + b - v = float(in_data[(ci * H * W + uint(ih) * W + uint(iw)) * B + b]); - out_data[idx] = NN_WR(v); -} diff --git a/shaders/matmul.comp b/shaders/matmul.comp deleted file mode 100644 index 7f8f1fe..0000000 --- a/shaders/matmul.comp +++ /dev/null @@ -1,92 +0,0 @@ -// ── matmul.comp ───────────────────────────────────────────────────────── -// 分块矩阵乘法计算着色器:C[M][N] = A × B -// -// 支持转置: -// - transA=0, transB=0: C[M][N] = A[M][K] × B[K][N] -// - transA=1, transB=0: C[M][N] = A^T[K,M] × B[K][N] (A 存储为 (K,M)) -// - transA=0, transB=1: C[M][N] = A[M][K] × B^T[N,K] (B 存储为 (N,K)) -// - transA=1, transB=1: C[M][N] = A^T[K,M] × B^T[N,K] -// -// 工作原理: -// - WorkGroup 大小:16 × 16(256 线程,适配大多数 GPU) -// - 每个 WorkGroup 计算 16×16 的输出分块 -// - 共享内存(shared memory)缓存 A 和 B 的分块,减少全局内存访问 -// - 多轮迭代 K 维度,每轮加载 16×16 到 shared memory -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -layout(local_size_x = 16, local_size_y = 16) in; - -// Storage buffers(float32) -layout(std430, binding = 0) readonly buffer BufferA { float A[]; }; -layout(std430, binding = 1) readonly buffer BufferB { float B[]; }; -layout(std430, binding = 2) writeonly buffer BufferC { float C[]; }; - -// Push constants:矩阵维度 + 转置标志 -layout(push_constant) uniform PushConstants { - uint M; // C 的行数 - uint N; // C 的列数 - uint K; // 求和维度 - uint transA; // 1 = A 存储为 (K,M),按 A^T 使用 - uint transB; // 1 = B 存储为 (N,K),按 B^T 使用 -}; - -// 共享内存:缓存 A 和 B 的 16×16 分块 -shared float sharedA[16][16]; -shared float sharedB[16][16]; - -// ── 访问 A 元素 ───────────────────────────────────────────────────────── -// transA=0: A[m][k] = A[m * K + k] (标准行主序 (M,K)) -// transA=1: A[m][k] = A[k * M + m] (转置存储 (K,M)) -float getA(uint m, uint k) { - return (transA != 0u) ? A[k * M + m] : A[m * K + k]; -} - -// ── 访问 B 元素 ───────────────────────────────────────────────────────── -// transB=0: B[k][n] = B[k * N + n] (标准行主序 (K,N)) -// transB=1: B[k][n] = B[n * K + k] (转置存储 (N,K)) -float getB(uint k, uint n) { - return (transB != 0u) ? B[n * K + k] : B[k * N + n]; -} - -void main() -{ - const uint row = gl_GlobalInvocationID.y; // 输出行 - const uint col = gl_GlobalInvocationID.x; // 输出列 - const uint lid_x = gl_LocalInvocationID.x; - const uint lid_y = gl_LocalInvocationID.y; - - float sum = 0.0; - - // K 维度的分块数 - const uint num_tiles = (K + 15u) / 16u; - - for (uint t = 0u; t < num_tiles; ++t) - { - // ── 从全局内存加载分块到共享内存 ────────────────────────────── - const uint a_col = t * 16u + lid_x; - if (row < M && a_col < K) - sharedA[lid_y][lid_x] = getA(row, a_col); - else - sharedA[lid_y][lid_x] = 0.0; - - const uint b_row = t * 16u + lid_y; - if (b_row < K && col < N) - sharedB[lid_y][lid_x] = getB(b_row, col); - else - sharedB[lid_y][lid_x] = 0.0; - - barrier(); - - // ── 累加部分积 ──────────────────────────────────────────────── - for (uint k = 0u; k < 16u; ++k) - sum += sharedA[lid_y][k] * sharedB[k][lid_x]; - - barrier(); - } - - // ── 写入结果 ────────────────────────────────────────────────────── - if (row < M && col < N) - C[row * N + col] = sum; -} diff --git a/shaders/matmul_gemv.comp b/shaders/matmul_gemv.comp deleted file mode 100644 index a153e7c..0000000 --- a/shaders/matmul_gemv.comp +++ /dev/null @@ -1,312 +0,0 @@ -// ── matmul_gemv.comp ────────────────────────────────────────────────────── -// 小 N 专用矩阵乘:C[M][N] = op(A) × op(B),后端仅在 N ≤ 8 时分派本 kernel -// (借鉴 llama.cpp/ggml mul_mat_vec:线程沿 K 分片 + 寄存器累加 + subgroup -// 归约;tiled 版在小 N 下 64×64 块 56/64 列空转,本 kernel 每线程只算 -// 真实的 N 列)。 -// -// 结构: -// - WorkGroup:256 线程(1D),覆盖 ROWS=4 个输出行 × N 列 -// (后端 dispatch grid.x = ceil(M/4),两侧 ROWS 常量必须一致) -// - 每线程持 k 步长 4 的分片(vec4 粒度),寄存器累加 acc[4][8] -// - k 循环内零 barrier(各线程分片互不依赖),结束后一次归约写出 -// - 归约:subgroupAdd 每元素 → lane0 写 shared[num_subgroups] → -// tid0 跨 subgroup 求和写出(同 gen_fused 归约 shader 先例; -// shared 树形归约需 32KB partial/WG ≈ 每 WG 120KB 额外流量, -// 会吃掉 GEMV 的带宽收益,故不用) -// -// A 加载(按 trans 选连续维,vec4 对齐条件为 uniform 分支;未对齐走同 -// 分组标量 + 边界判零,覆盖一致): -// transA=0:A[m][k] 行主序沿 k 连续 → K%4==0 时每行一条 vec4 -// transA=1:A 存储 (K,M) 沿 m 连续 → M%4==0 时每 k 一条 vec4(4 行同取) -// B 加载: -// transB=0:块 [k0..k0+3][0..N) 扁平连续(k0%4 ⇒ k0*N%4==0 恒成立) -// → k0+4≤K 即可 vec4;N%4==0 时按行 vec4 直取,否则扁平 vec4 拆分 -// transB=1:B 存储 (N,K) 沿 k 连续 → K%4==0 时每列一条 vec4 -// 基址 16B 对齐由 MemoryPool 分配对齐 ≥16 保证(compute_memory_pool.hpp) -// -// push constants / descriptor 布局与 matmul.comp 完全一致(5×4B,3 binding), -// 复用 VulkanPipeline::create_matmul。 -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2:小 N GEMV 的 f16 直读直写, -// 累加/归约/shared 全 f32——与 matmul_tiled f16 同语义;消灭小 N GEMM -// (n_out≤8,推理 GEMV 热路径)的边界 cast)。vec4 别名 uvec2(4×half=8B, -// std430 步长恰 4 元素 → 下标 >>2 语义与 f32 vec4 完全一致,K%4/M%4 -// 对齐判据不变:f16 需 8B ⇔ 元素下标 %4==0,与 f32 的 16B 同条件)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_V4 uvec2 -#define NN_LOADV4(v) vec4(unpackHalf2x16((v).x), unpackHalf2x16((v).y)) -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_V4 vec4 -#define NN_LOADV4(v) (v) -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif - -#extension GL_EXT_control_flow_attributes : enable -#extension GL_KHR_shader_subgroup_basic : require -#extension GL_KHR_shader_subgroup_arithmetic : require - -layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; - -layout(std430, binding = 0) readonly buffer BufferA { NN_ETYPE A[]; }; -layout(std430, binding = 1) readonly buffer BufferB { NN_ETYPE B[]; }; -layout(std430, binding = 2) writeonly buffer BufferC { NN_ETYPE C[]; }; - -// vec4 别名视图(同 binding 双声明;仅在元素下标 %4==0 的快路径使用) -layout(std430, binding = 0) readonly buffer BufferAv4 { NN_V4 Av4[]; }; -layout(std430, binding = 1) readonly buffer BufferBv4 { NN_V4 Bv4[]; }; - -layout(push_constant) uniform PushConstants { - uint M; // C 的行数 - uint N; // C 的列数(后端保证 ≤ MAX_N) - uint K; // 求和维度 - uint transA; // 1 = A 存储为 (K,M),按 A^T 使用 - uint transB; // 1 = B 存储为 (N,K),按 B^T 使用 -}; - -// 每 WG 覆盖的输出行数 —— 后端 dispatch(grid.x = ceil(M/ROWS))必须同步 -const uint ROWS = 4u; -// 支持的最大列数 —— 后端仅在 N ≤ MAX_N 时分派(acc/red 第二维) -const uint MAX_N = 8u; - -// subgroup 部分和:256 线程 / subgroup ≥4 ⇒ 至多 64 个 subgroup -shared float red[ROWS][MAX_N][64]; - -void main() -{ - const uint tid = gl_LocalInvocationID.x; // 0..255(沿 K 分片) - const uint m0 = gl_WorkGroupID.x * ROWS; - - // ── 形状对齐标志(push 常量,uniform 分支零开销)───────────────── - // 沿 k 连续:元素下标 m*K+k0%4==0 ∀m ⇔ K%4==0(k0 恒 %4) - const bool a_vec_k = (transA == 0u) && ((K & 3u) == 0u); - // 沿 m 连续(A 存 K×M):(k*M+m0)%4==0 ∀k ⇔ M%4==0(m0=4*wg 恒 %4) - // M%4==0 且 m0> 2]); - a[r][0] = v.x; a[r][1] = v.y; - a[r][2] = v.z; a[r][3] = v.w; - } - } - } - else - { - [[unroll]] for (uint r = 0u; r < ROWS; ++r) - { - if (!row_ok[r]) continue; - const uint base = (m0 + r) * K; - [[unroll]] for (uint i = 0u; i < 4u; ++i) - if (k0 + i < K) a[r][i] = NN_RD(A[base + k0 + i]); - } - } - } - else - { - // A 存储 (K,M):元素 (k,m) = A[k*M + m] - if (a_vec_m) - { - // M%4==0 ⇒ 下标对齐且 m0+4≤M;k 逐个判界(K 可为奇数) - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - if (k0 + i < K) - { - const vec4 v = NN_LOADV4(Av4[((k0 + i) * M + m0) >> 2]); - [[unroll]] for (uint r = 0u; r < ROWS; ++r) - a[r][i] = v[r]; - } - } - } - else - { - [[unroll]] for (uint r = 0u; r < ROWS; ++r) - { - if (!row_ok[r]) continue; - const uint m = m0 + r; - [[unroll]] for (uint i = 0u; i < 4u; ++i) - if (k0 + i < K) a[r][i] = NN_RD(A[(k0 + i) * M + m]); - } - } - } - - // ── B 加载 → b[4][MAX_N] ────────────────────────────────────── - float b[4][MAX_N]; - [[unroll]] for (uint i = 0u; i < 4u; ++i) - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - b[i][n] = 0.0; - - if (transB == 0u) - { - // B 存储 (K,N):元素 (k,n) = B[k*N + n] - if (k0 + 4u <= K) - { - // 整组 k 在界内 ⇒ 扁平块 [k0*N, k0*N+4N) 连续且 - // k0*N%4==0(k0%4)⇒ 全部 vec4 对齐 - if ((N & 3u) == 0u) - { - // N%4==0:按行 vec4 直取((k0+i)*N%4==0 ⇔ N%4==0) - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - [[unroll]] for (uint j = 0u; j < 4u; ++j) - { - if (j * 4u >= ncols) break; - const vec4 v = NN_LOADV4(Bv4[((k0 + i) * N + j * 4u) >> 2]); - b[i][j * 4u + 0u] = v.x; - b[i][j * 4u + 1u] = v.y; - b[i][j * 4u + 2u] = v.z; - b[i][j * 4u + 3u] = v.w; - } - } - } - else - { - // N%4≠0(1,2,3,5,6,7):扁平 vec4 装入本地 cb, - // 按 p=i*N+n 拆分(N==1 时 cb[0] 即 4 个 k 的向量) - const uint flatn = (ncols * 4u + 3u) / 4u; // = ncols - vec4 cb[MAX_N]; - [[unroll]] for (uint j = 0u; j < MAX_N; ++j) - cb[j] = vec4(0.0); - for (uint j = 0u; j < flatn; ++j) - cb[j] = NN_LOADV4(Bv4[(k0 * N + j * 4u) >> 2]); - [[unroll]] for (uint i = 0u; i < 4u; ++i) - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - const uint p = i * N + n; - b[i][n] = cb[p >> 2][p & 3u]; - } - } - } - else - { - // k 尾块(K%4≠0 的末段):标量 + k 判界 - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - if (k0 + i >= K) break; - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - b[i][n] = NN_RD(B[(k0 + i) * N + n]); - } - } - } - } - else - { - // B 存储 (N,K):元素 (k,n) = B[n*K + k] - if (b_vec_k) - { - // K%4==0 且 k0= ncols) break; - const vec4 v = NN_LOADV4(Bv4[(n * K + k0) >> 2]); - b[0][n] = v.x; b[1][n] = v.y; - b[2][n] = v.z; b[3][n] = v.w; - } - } - else - { - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - if (k0 + i >= K) break; - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - b[i][n] = NN_RD(B[n * K + k0 + i]); - } - } - } - } - - // ── 累加(外积 4×ncols,每元素 1 FMA)──────────────────────── - [[unroll]] for (uint r = 0u; r < ROWS; ++r) - { - if (!row_ok[r]) continue; - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - const float av = a[r][i]; - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - acc[r][n] = fma(av, b[i][n], acc[r][n]); - } - } - } - } - - // ── 归约:subgroupAdd 每元素(全 WG 同源收敛点,无发散)────────── - [[unroll]] for (uint r = 0u; r < ROWS; ++r) - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - // 只归约真实列数:MAX_N=8 的固定 32 路归约在 n=1 时与主循环同量级 - // (每路 = subgroupAdd + shared 写 + barrier + 跨 subgroup 求和) - if (n >= ncols) break; - acc[r][n] = subgroupAdd(acc[r][n]); - if (gl_SubgroupInvocationID == 0u && gl_SubgroupID < 64u) - red[r][n][gl_SubgroupID] = acc[r][n]; - } - barrier(); - - // ── 跨 subgroup 求和 + 写出(仅 tid0)──────────────────────────── - if (tid == 0u) - { - const uint nsub = (gl_NumSubgroups < 64u) ? gl_NumSubgroups : 64u; - [[unroll]] for (uint r = 0u; r < ROWS; ++r) - { - if (m0 + r >= M) break; - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - float s = 0.0; - for (uint g = 0u; g < nsub; ++g) - s += red[r][n][g]; - C[(m0 + r) * N + n] = NN_WR(s); - } - } - } -} diff --git a/shaders/matmul_q.comp b/shaders/matmul_q.comp deleted file mode 100644 index 76d3752..0000000 --- a/shaders/matmul_q.comp +++ /dev/null @@ -1,236 +0,0 @@ -// ── matmul_q.comp — 三值打包权重的量化 GEMM 原语(P2 / F3,**分块版**)────── -// C = (τ∘A)^(T)·B,其中 A 以 2 bit/权重打包(见 trit_pack.comp / ternary_pack.hpp), -// τ 是逐**存储行**的尺度(absmean,乘进每个 trit)。两种形态: -// transA=0:A 存为 (M,K) 打包 → C (M,N) = (τ∘A)·B,B (K,N),τ 取存储行 m -// transA=1:A 存为 (K,M) 打包 → C (M,N) = (τ∘A)^T·B,B (K,N),τ 取存储行 k -// -// **为什么分块**(实测驱动的重写):初版是"每输出元素独立解码"的直白内核 —— -// 宽 batch(训练)下同一行被重复解码 N 次,输给分块 f16 GEMM(102/40HX 实测 -// 60 步训练 4.0 s vs 2.0 s)。本版把 A 分块**解码一次进共享内存**(BM×BK), -// 之后与 f16 分块 GEMM 同构地累加;打包带来的收益(权重读 2 B → 0.25 B/权重) -// 才可能在宽 batch 下兑现。 -// -// 分块参数与 `matmul_tiled.comp` 同款(16×16 线程 / BM=BN=64 / BK=16 / 每线程 -// 4×4 / 双缓冲),差别只在一处:**A 分块的装载是"解码"而不是"读 float"**。 -// BK=16 恰好 = 一个 32-bit 字装的 trit 数 ⇒ 每个 A 存储行在一个 k 块里只取 -// **一个**字(尾部块由 trit 越界判 0 处理,即码 2 = 三值 0)。 -// -// 精度变体(glslc -DNN_SHADER_F16=1):B/C 声明 float16_t(f32 参考累加 + 写出 -// RNE,同所有原语惯例);打包字缓冲恒 uint。 -// ───────────────────────────────────────────────────────────────────────── -#version 450 - -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_V4 uvec2 -#define NN_LOADV4(arr, i) vec4(unpackHalf2x16(arr[i].x), unpackHalf2x16(arr[i].y)) -#define NN_RD(x) float(x) -#include "nn_f16_rne.glsl" -#define NN_WR(x) float16_t(nn_f16_rne(x)) -#else -#define NN_ETYPE float -#define NN_V4 vec4 -#define NN_LOADV4(arr, i) arr[i] -#define NN_RD(x) x -#define NN_WR(x) x -#endif - -layout(local_size_x = 16, local_size_y = 16) in; - -layout(std430, binding = 0) readonly buffer BufP { uint P[]; }; // 打包 A -layout(std430, binding = 1) readonly buffer BufT { float T[]; }; // τ(存储行) -layout(std430, binding = 2) readonly buffer BufB { NN_ETYPE B[]; }; // B (K,N) -layout(std430, binding = 2) readonly buffer BufBv4 { NN_V4 Bv4[]; }; -layout(std430, binding = 3) writeonly buffer BufC { NN_ETYPE C[]; }; // C (M,N) -#if !defined(NN_SHADER_F16) -layout(std430, binding = 3) writeonly buffer BufCv4 { vec4 Cv4[]; }; -#endif - -layout(push_constant) uniform Push { - uint M; - uint N; - uint K; // 求和维(= B 的行数) - uint transA; // 0 = A 存 (M,K);1 = A 存 (K,M) - uint wpr; // A 每存储行的字数:transA=0 → ceil(K/16);transA=1 → ceil(M/16) -}; - -const uint BM = 64u, BN = 64u, BK = 16u; -const uint EPT_A = (BM * BK) / 256u; // 4 -const uint EPT_B = (BK * BN) / 256u; // 4 - -// Ash[stage][k][m>>2][m&3]:A^T 分块(与 matmul_tiled 同布局,内层按行连续读) -// Bsh[stage][k][n>>2][n&3]:B 分块 -shared vec4 Ash[2][BK][BM / 4]; -shared vec4 Bsh[2][BK][BN / 4]; - -// ── A 分块装载(**解码**):BM×BK 元素,每线程 4 个 ─────────────────────── -// 分组与 matmul_tiled 的 A 装载一致(按连续维取 4 元素组),只是每个元素都 -// 由"打包字 → 2 bit 码 → 三值 → ×τ"得到: -// transA=0:4 个连续 k(同一存储行 m)→ 同一行、k0..k0+3,字内不同 2-bit 槽 -// transA=1:4 个连续 m(同一存储行 k) -void load_A_tile(uint t, uint stage, uint tid, uint block_row) -{ - const uint k_contig = (transA == 0u) ? 1u : 0u; - const uint groups = EPT_A / 4u; - for (uint vv = 0u; vv < groups; ++vv) - { - const uint g = tid * groups + vv; - uint sr; // 存储行 - uint c0; // 该组第一个"列"(= k 或 m) - uint cmax; // 列上限(= K 或 M) - if (k_contig != 0u) - { - const uint m = (4u * g) / BK; - c0 = t * BK + (4u * g) % BK; - sr = block_row + m; - cmax = K; - } - else - { - const uint mq = g % (BM / 4u); - const uint k = g / (BM / 4u); - c0 = block_row + mq * 4u; // 列 = m - sr = t * BK + k; // 存储行 = k - cmax = M; - } - const float tau = (sr < ((k_contig != 0u) ? M : K)) ? T[sr] : 0.0; - for (uint j = 0u; j < 4u; ++j) - { - const uint cj = c0 + j; - float v = 0.0; - if (cj < cmax && sr < ((k_contig != 0u) ? M : K)) - { - const uint word = P[sr * wpr + (cj >> 4u)]; - v = tau * (float((word >> (2u * (cj & 15u))) & 3u) - 2.0); - } - if (k_contig != 0u) - { - // 组内 4 个连续 k:k0 = c0 - t*BK - const uint k0 = c0 - t * BK; - const uint m = sr - block_row; - Ash[stage][k0 + j][m >> 2][m & 3u] = v; - } - else - { - const uint k = sr - t * BK; - const uint m = cj - block_row; // 组内 4 个连续 m - Ash[stage][k][m >> 2][m & 3u] = v; - } - } - } -} - -// ── B 分块装载(B 恒为 (K,N) 行主序 = transB=0;与 matmul_tiled 同款)────── -void load_B_tile(uint t, uint stage, uint tid, uint block_col) -{ - const uint b_base_row = t * BK; - const bool aligned = ((N & 3u) == 0u); - const uint groups = EPT_B / 4u; - for (uint vv = 0u; vv < groups; ++vv) - { - const uint g = tid * groups + vv; - const uint k = (4u * g) / BN; - const uint n0 = (4u * g) % BN; - const uint gr = b_base_row + k; - const uint gc = block_col + n0; - if (aligned && gr < K && gc < N) - { - const vec4 v = NN_LOADV4(Bv4, (gr * N + gc) >> 2); - for (uint j = 0u; j < 4u; ++j) - Bsh[stage][k][(n0 + j) >> 2][(n0 + j) & 3u] = v[j]; - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr < K && gc + j < N) ? NN_RD(B[gr * N + gc + j]) : 0.0; - Bsh[stage][k][(n0 + j) >> 2][(n0 + j) & 3u] = v; - } - } - } -} - -void main() -{ - const uint tx = gl_LocalInvocationID.x; - const uint ty = gl_LocalInvocationID.y; - const uint tid = ty * 16u + tx; - - const uint block_row = gl_WorkGroupID.y * BM; - const uint block_col = gl_WorkGroupID.x * BN; - - vec4 acc[4]; - for (uint i = 0u; i < 4u; ++i) acc[i] = vec4(0.0); - - const uint num_tiles = (K + BK - 1u) / BK; - load_A_tile(0u, 0u, tid, block_row); - load_B_tile(0u, 0u, tid, block_col); - barrier(); - - uint stage = 0u; - for (uint t = 0u; t < num_tiles; ++t) - { - const uint nxt = t + 1u; - const uint nstage = stage ^ 1u; - if (nxt < num_tiles) - { - load_A_tile(nxt, nstage, tid, block_row); - load_B_tile(nxt, nstage, tid, block_col); - } - #pragma unroll 4 - for (uint k = 0u; k < BK; ++k) - { - const vec4 a_vec = Ash[stage][k][ty]; - const vec4 b_vec = Bsh[stage][k][tx]; - acc[0] += a_vec.x * b_vec; - acc[1] += a_vec.y * b_vec; - acc[2] += a_vec.z * b_vec; - acc[3] += a_vec.w * b_vec; - } - barrier(); - stage = nstage; - } - - // ── 写出(f32 走 vec4 快路径;f16 / 未对齐走标量 RNE)────────────── -#if defined(NN_SHADER_F16) - // f16 输出:逐标量写出(C 无 vec4 别名视图) - // ⚠ acc[i] 是**行 i 的 4 个列**(vec4 分量的语义与 f32 快路径一致)—— - // 写成 acc[j][i] 会转置(f32 路径看不出、f16 路径直接错值,实测训练 loss 炸) - for (uint i = 0u; i < 4u; ++i) - { - const uint out_row = block_row + ty * 4u + i; - if (out_row >= M) continue; - const uint out_col0 = block_col + tx * 4u; - if (out_col0 >= N) continue; - C[out_row * N + out_col0 + 0u] = NN_WR(acc[i].x); - if (out_col0 + 1u < N) C[out_row * N + out_col0 + 1u] = NN_WR(acc[i].y); - if (out_col0 + 2u < N) C[out_row * N + out_col0 + 2u] = NN_WR(acc[i].z); - if (out_col0 + 3u < N) C[out_row * N + out_col0 + 3u] = NN_WR(acc[i].w); - } -#else - if ((N & 3u) == 0u && block_col + BN <= N) - { - for (uint i = 0u; i < 4u; ++i) - { - const uint out_row = block_row + ty * 4u + i; - if (out_row >= M) continue; - Cv4[(out_row * N + block_col + tx * 4u) >> 2] = acc[i]; - } - } - else - { - for (uint i = 0u; i < 4u; ++i) - { - const uint out_row = block_row + ty * 4u + i; - if (out_row >= M) continue; - for (uint j = 0u; j < 4u; ++j) - { - const uint out_col = block_col + tx * 4u + j; - if (out_col >= N) continue; - C[out_row * N + out_col] = acc[i][j]; - } - } - } -#endif -} diff --git a/shaders/matmul_q_gemv.comp b/shaders/matmul_q_gemv.comp deleted file mode 100644 index 7ef4e98..0000000 --- a/shaders/matmul_q_gemv.comp +++ /dev/null @@ -1,204 +0,0 @@ -// ── matmul_q_gemv.comp — 三值打包权重的**窄 N**(GEMV)内核 ─────────────── -// C = (τ∘W)·B,W 以 2 bit/权重打包(transA=0 形态:A 存 (M,K) 打包), -// τ 是逐**存储行**的尺度。后端仅在 transA=0 且 N ≤ 8 时分派本 kernel -// (与稠密路径的 matmul_gemv 同一门槛,见 compute_vk_backend 的选址)。 -// -// **为什么需要它**(实测驱动):分块版 matmul_q 按 64×64 输出块算,n=1 时 -// 63/64 的列是空转 —— 102/40HX 实测 m=2560 k=6912 n=1:分块打包 1.53 ms vs -// 稠密 GEMV 0.36 ms,打包侧权重带宽只有 **2.9 GB/s**(稠密 97 GB/s), -// "权重读 0.25 B/元素"的优势完全兑不出来。本内核改成"每线程沿**打包字**分片 -// + 只算真实的 N 列":一个 32-bit 字 = 16 个三值 = 16 个连续 k,每字只读一次 -// 即 0.25 B/权重 → 自回归解码(权重带宽受限)才能兑现打包收益。 -// 思路与 BitNet 官方把 M=1 单独做成 ladder 内核一致(ref/BitNet/kernels.cu)。 -// -// 结构(**subgroup = 4 行输出,subgroup 内 lane 沿 k 分片**): -// · WorkGroup 256 线程 = gl_NumSubgroups 个 subgroup;第 s 个 subgroup 负责 -// 输出行块 [m0, m0+4),m0 = (WGID.x*gl_NumSubgroups + s) * 4 -// (后端 dispatch grid.x = ceil(M / (4*gl_NumSubgroups))) -// · subgroup 内 lane 沿**打包字**步进 w = lane, lane+SubgroupSize, … < wpr: -// **相邻 lane 读相邻字** → 合并访存 -// · 每线程持 **4 行** × acc[MAX_N]:一次 B 切片装载摊到 4 行的 4×ncols 次 FMA -// (实测口径:每 4 个 k 的 B 装载 ≈ 8 条 load + 24 条解包 = 与"单行 × n=8" -// 的 32 次 FMA 同量级 —— 1 行/线程的版本在 n=8 比这版慢 2×) -// · 归约 = 每 (行, 列) 一次 subgroupAdd,lane r 直接写第 r 行: -// **无 barrier、无 shared、无跨 subgroup 串行求和**(对照 matmul_gemv 的 -// 256 路归约 + shared + 串行求和:窄 N 下那部分的指令数与主循环同量级) -// · 尾块:K 非 16 倍数时末字的越界位恒为 pad 码 2(三值 0)→ A 侧不判界, -// B 侧按 k0+i < K 判零 -// -// 尾块:K 非 16 倍数时末字有越界位,但 pad 位恒为码 2(三值 0)→ A 侧无需 -// 判界;B 侧仍按 k0+i < K 判零(与 matmul_gemv 同规则,覆盖一致)。 -// -// 精度变体(glslc -DNN_SHADER_F16=1):B/C 声明 float16_t(f32 参考累加 + 写出 -// RNE,同所有原语惯例);打包字缓冲恒 uint。 -// 仅支持 transA=0(转发形态);transA=1 的窄 N 仍走分块内核 —— 后端门禁保证。 -// ───────────────────────────────────────────────────────────────────────── -#version 450 - -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_V4 uvec2 -#define NN_LOADV4(v) vec4(unpackHalf2x16((v).x), unpackHalf2x16((v).y)) -#define NN_RD(x) float(x) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_V4 vec4 -#define NN_LOADV4(v) (v) -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif - -#extension GL_EXT_control_flow_attributes : enable -#extension GL_KHR_shader_subgroup_basic : require -#extension GL_KHR_shader_subgroup_arithmetic : require - -layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; - -layout(std430, binding = 0) readonly buffer BufP { uint P[]; }; // 打包 A -layout(std430, binding = 1) readonly buffer BufT { float T[]; }; // τ(存储行) -layout(std430, binding = 2) readonly buffer BufB { NN_ETYPE B[]; }; // B (K,N) -layout(std430, binding = 2) readonly buffer BufBv4 { NN_V4 Bv4[]; }; -layout(std430, binding = 3) writeonly buffer BufC { NN_ETYPE C[]; }; // C (M,N) - -layout(push_constant) uniform Push { - uint M; - uint N; - uint K; - uint transA; // 恒 0(本内核只服务转发形态) - uint wpr; // A 每存储行的字数 = ceil(K/16) -}; - -const uint MAX_N = 8u; // 后端仅在 N ≤ MAX_N 时分派;= acc 的寄存器宽度 -const uint K_PER_WORD = 16u; // = trit_pack::kPerWord(2 bit × 16 = 一个 uint) -const uint RPS = 4u; // 每个 subgroup 负责的输出行数(B 装载的摊销倍数) - -void main() -{ - if (transA != 0u) return; // 后端门禁:只有 transA=0 会分派到本内核 - const uint lane = gl_SubgroupInvocationID; - const uint kstride = gl_SubgroupSize; // subgroup 内 k 分片步长 - const uint m0 = (gl_WorkGroupID.x * gl_NumSubgroups + gl_SubgroupID) * RPS; - const uint ncols = (N < MAX_N) ? N : MAX_N; - - // 行有效 + τ(每行一次,寄存器常驻);τ·(code-2) 写成 fma(τ, code, -2τ) - bool row_ok[RPS]; - float tau[RPS], neg2tau[RPS]; - [[unroll]] for (uint r = 0u; r < RPS; ++r) - { - row_ok[r] = (m0 + r < M); - tau[r] = row_ok[r] ? T[m0 + r] : 0.0; - neg2tau[r] = -2.0 * tau[r]; - } - - float acc[RPS][MAX_N]; - [[unroll]] for (uint r = 0u; r < RPS; ++r) - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - acc[r][n] = 0.0; - - // ── 沿打包字步进:subgroup 内分片,分片互不依赖(零 barrier)─────── - for (uint w = lane; w < wpr; w += kstride) - { - // 4 行各取一字(每字 = 16 个连续 k);相邻 lane 取相邻字 → 合并访存 - uint word[RPS]; - [[unroll]] for (uint r = 0u; r < RPS; ++r) - word[r] = row_ok[r] ? P[(m0 + r) * wpr + w] : 0u; - - [[unroll]] for (uint g = 0u; g < 4u; ++g) - { - const uint k0 = w * K_PER_WORD + g * 4u; - if (k0 >= K) break; // 末字的部分组越界(pad 位已是码 2) - - // 解码 4 个 k × 4 行 → a[r][4](码 j 的位域 = [8g+2j, 8g+2j+1]) - float a[RPS][4]; - [[unroll]] for (uint r = 0u; r < RPS; ++r) - [[unroll]] for (uint i = 0u; i < 4u; ++i) - a[r][i] = fma(tau[r], float((word[r] >> (8u * g + 2u * i)) & 3u), - neg2tau[r]); - - // ── B 加载 → b[4][MAX_N](与 matmul_gemv 同款分支)───────── - float b[4][MAX_N]; - [[unroll]] for (uint i = 0u; i < 4u; ++i) - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - b[i][n] = 0.0; - - if (k0 + 4u <= K) - { - if ((N & 3u) == 0u) - { - // N%4==0:按行 vec4 直取((k0+i)*N%4==0 ⇔ N%4==0) - [[unroll]] for (uint i = 0u; i < 4u; ++i) - [[unroll]] for (uint j = 0u; j < 4u; ++j) - { - if (j * 4u >= ncols) break; - const vec4 v = NN_LOADV4(Bv4[((k0 + i) * N + j * 4u) >> 2]); - b[i][j * 4u + 0u] = v.x; - b[i][j * 4u + 1u] = v.y; - b[i][j * 4u + 2u] = v.z; - b[i][j * 4u + 3u] = v.w; - } - } - else - { - // N%4≠0(1,2,3,5,6,7):扁平 vec4 装入 cb,按 p=i*N+n 拆分 - const uint flatn = (ncols * 4u + 3u) / 4u; - vec4 cb[MAX_N]; - [[unroll]] for (uint j = 0u; j < MAX_N; ++j) - cb[j] = vec4(0.0); - for (uint j = 0u; j < flatn; ++j) - cb[j] = NN_LOADV4(Bv4[(k0 * N + j * 4u) >> 2]); - [[unroll]] for (uint i = 0u; i < 4u; ++i) - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - const uint p = i * N + n; - b[i][n] = cb[p >> 2][p & 3u]; - } - } - } - else - { - // k 尾块:标量 + k 判界 - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - if (k0 + i >= K) break; - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - b[i][n] = NN_RD(B[(k0 + i) * N + n]); - } - } - } - - // ── 外积 4×ncols 累加(每元素 1 FMA)─────────────────────── - [[unroll]] for (uint r = 0u; r < RPS; ++r) - { - if (!row_ok[r]) continue; - [[unroll]] for (uint i = 0u; i < 4u; ++i) - { - const float av = a[r][i]; - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - acc[r][n] = fma(av, b[i][n], acc[r][n]); - } - } - } - } - } - - // ── 归约 + 写出:本 subgroup 的 4 行各做一次 subgroupAdd,lane r 写第 r 行 ── - // (无 barrier / 无 shared / 无跨 subgroup 串行求和;只归约真实列数 ncols) - [[unroll]] for (uint r = 0u; r < RPS; ++r) - { - [[unroll]] for (uint n = 0u; n < MAX_N; ++n) - { - if (n >= ncols) break; - const float s = subgroupAdd(acc[r][n]); - if (lane == r && row_ok[r]) - C[(m0 + r) * N + n] = NN_WR(s); - } - } -} diff --git a/shaders/matmul_tiled.comp b/shaders/matmul_tiled.comp deleted file mode 100644 index a1c2b92..0000000 --- a/shaders/matmul_tiled.comp +++ /dev/null @@ -1,417 +0,0 @@ -// ── matmul_tiled.comp ───────────────────────────────────────────────────── -// vec4 向量化分块矩阵乘法:C[M][N] = A × B(借鉴 llama.cpp/ggml mul_mm 思路) -// -// 支持转置(同 matmul.comp): -// - transA=0, transB=0: C[M][N] = A[M][K] × B[K][N] -// - transA=1, transB=0: C[M][N] = A^T[K,M] × B[K][N] -// - transA=0, transB=1: C[M][N] = A[M][K] × B^T[N,K] -// - transA=1, transB=1: C[M][N] = A^T[K,M] × B^T[N,K] -// -// 优化策略: -// - WorkGroup:16×16 线程,计算 64×64 输出块(每线程 4×4 元素); -// 细 tile 变体(-DNN_MM_FINE=1)= 4×4 线程 / 16×16 输出块,每线程仍 -// 4×4 元素(见文件中段开关注释) -// - vec4 共享内存:Ash[k][m](A 转置)/ Bsh[k][n],每线程每 k 仅 2 次 -// vec4 LDS + 16 FMA(8:1 计算:加载比),vec4 读沿 lane 连续、消除 -// 标量读的 bank 冲突 -// - vec4 累加器:acc[4](4×4 = 16 个累加值),寄存器常驻 -// -// 双缓冲流水(BK=16):Ash/Bsh 各分 2 个 stage,tile t+1 的全局加载与 -// tile t 的计算并行发射;每 tile 仅 1 次 barrier,全局内存延迟 -// (~600 周期)被计算覆盖。共享内存总计 16KB(Ash[2][16][16]vec4 8KB -// + Bsh 8KB),占用率与单缓冲 BK=32 相同(32KB 变体的实测代价见下方 -// BK 常量注释)。 -// -// vec4 全局加载/写出(借鉴 ggml mul_mm 的 ALIGNED/LOAD_VEC 快路径): -// - 每线程 EPT 个元素按「转置形态选连续维」分组,对齐时一条 vec4 全局 -// 访问替代 4 次标量 + 逐元素边界判断;形状条件为 uniform 分支零开销 -// - 对齐条件(元素下标 %4==0 且组内连续 ⇔): -// A: transA=0 需 K%4==0(沿 k 连续);transA=1 需 M%4==0(沿 m 连续) -// B: transB=0 需 N%4==0(沿 n 连续);transB=1 需 K%4==0(沿 k 连续) -// C: 写出需 N%4==0(沿列连续) -// 基址 16B 对齐由 MemoryPool 分配对齐 ≥16 保证(compute_memory_pool.hpp) -// - 未对齐(奇数形状)或线程持边界组:回退同组标量 + 边界判零, -// 覆盖分组与 vec4 路径完全一致 → 每个 (k,m) 恰被写一次,语义不变 -// - A/B 全局缓冲以 vec4 别名视图访问(同 binding 双声明,std430) -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// ── 精度变体(编译期开关:glslc -DNN_SHADER_F16=1)──────────────────────── -// 默认(无宏)= 全 f32(f32 分支宏展开与裸写 f32 代码逐字同形 → -// SPIR-V 字节零差异)。 -// 带宏 = f16 存储变体:缓冲声明 float16_t(GL_EXT_shader_16bit_storage), -// 别名槽改 uvec2(4×half = 8B,std430 步长恰好 8)→ unpackHalf2x16 解出 -// vec4;全局读处统一 float(...) 转 f32、写出处统一 float16_t(...)。 -// **共享 tile / vec4 外积累加 / 分块 / 双缓冲 / barrier 节奏与 f32 同构**, -// 只有全局加载/写出的元素类型变了(§7.2「f32 参考累加 + 输出舍入」)。 -// vec4 快路径的对齐条件不变:f16 下 uvec2 需 8B 对齐 ⇔ 元素下标 %4==0 -// (与 f32 的 vec4 16B 对齐条件同源,故 aligned 判据完全复用)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_V4 uvec2 -#define NN_LOADV4(arr, i) vec4(unpackHalf2x16(arr[i].x), unpackHalf2x16(arr[i].y)) -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入:float16_t(x) 的 OpFConvert 舍入模式未被规范钉死(实测 -// NVIDIA 驱动向零截断 → 权重衰减类写回每步恰掉 1 个 f16 网格步,见 -// research/f16_weight_decay/REPORT.md)。packHalf2x16 按 round-half-to-even, -// unpack 回 f32 后值已落在 f16 网格上 → 其后 float16_t() 转换必然精确。 -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_V4 vec4 -#define NN_LOADV4(arr, i) arr[i] -#define NN_RD(x) x -#define NN_WR(x) x -#endif - -// ── 网格粒度变体(编译期开关:glslc -DNN_MM_FINE=1)──────────────────────── -// 默认 = 64×64 输出块(16×16 线程,每线程 4×4 寄存器分块); -// NN_MM_FINE = 16×16 输出块(**8×8 线程、每线程 2×2 输出 R=2**)——同一份 -// 代码按 BM/BN/T 参数化,逐输出元素的 K 累加序不变(逐 k 标量升序乘加) -// → 逐位一致。 -// 动机(P0 wgrad 网格饥饿):反传 wgrad 形状 M,N=64..256 × K=16384 在 -// 64-tile 下只派 ceil(N/64)×ceil(M/64) = 1~4 个工作组,40HX(34 SM) -// 大面积空转(实测 208~278 GFLOPS vs 同卡方形 2489)。 -// ⚠ R 的取舍(A/B 实测定案,见 expr_spec EXPR_MATMUL_BLOCK_FINE 注释): -// · R=4 缩块不缩线程(16×16 块/4×4 线程)→ 线程数不变、半 warp 工作组 -// 占用崩 → 169 GFLOPS 比不修还差; -// · R=1(16×16 线程,M×N 线程)→ 摊满了但每线程 1 条 K 链、指令/输出 -// 2× 于 R=2 → 305~439; -// · R=2 = 摊铺(网格仍 16×16 块 → 48 WG ≥ SM 数)× 线程内 ILP=4 的甜点。 -// 后端按 64-tile 网格工作组数低于 EXPR_MATMUL_FINE_MIN_WGS 选本变体。 -#if defined(NN_MM_FINE) -#define NN_MM_T 8 -#define NN_MM_TU 8u -#define NN_MM_BM 16u -#define NN_MM_BN 16u -#else -#define NN_MM_T 16 -#define NN_MM_TU 16u -#define NN_MM_BM 64u -#define NN_MM_BN 64u -#endif - -layout(local_size_x = NN_MM_T, local_size_y = NN_MM_T) in; - -layout(std430, binding = 0) readonly buffer BufferA { NN_ETYPE A[]; }; -layout(std430, binding = 1) readonly buffer BufferB { NN_ETYPE B[]; }; -layout(std430, binding = 2) writeonly buffer BufferC { NN_ETYPE C[]; }; - -// vec4 别名视图(同 binding 双声明;仅在元素下标 %4==0 的快路径使用) -layout(std430, binding = 0) readonly buffer BufferAv4 { NN_V4 Av4[]; }; -layout(std430, binding = 1) readonly buffer BufferBv4 { NN_V4 Bv4[]; }; -#if !defined(NN_SHADER_F16) -layout(std430, binding = 2) writeonly buffer BufferCv4 { vec4 Cv4[]; }; -#endif - -layout(push_constant) uniform PushConstants { - uint M; // C 的行数 - uint N; // C 的列数 - uint K; // 求和维度 - uint transA; // 1 = A 存储为 (K,M),按 A^T 使用 - uint transB; // 1 = B 存储为 (N,K),按 B^T 使用 -}; - -// 分块维度常量(NN_MM_FINE 变体 = 16×16 输出块,见文件头开关注释) -const uint BM = NN_MM_BM; // 输出块行数 -const uint BN = NN_MM_BN; // 输出块列数 -// BK=16 + 双缓冲:Ash[2][16][16]vec4 8KB + Bsh 8KB = 16KB 共享,保占用率 -// (BK=32 双缓冲需 32KB,每 SM 只驻 2 个 WG,浅深网格都被占用率卡死—— -// 四点实测全回退:512³ +9%、1024³ +26%、4096³ +29%、batched b8 +29%, -// 3 样本 best);OP 级恒取 16,改 BK 前须四点复测。 -const uint BK = 16u; // K 维度分块宽度 - -// 每线程协作加载元素数(BM*BK/T² 线程)。下方按 4 元素组取 groups=EPT/4: -// 默认 64×16/16² → EPT=4(groups=1);细 tile 16×16/8² → EPT=4(groups=1, -// g=tid 恰覆盖 tile 的 64 个 vec4,由 g < TILE_V4 守卫兜底)。 -const uint EPT_A = (BM * BK) / (NN_MM_TU * NN_MM_TU); // 64→4 / 16→4 -const uint EPT_B = (BK * BN) / (NN_MM_TU * NN_MM_TU); // 64→4 / 16→4 -// 各自 tile 的 vec4 总数(守卫上界;两条路径下 g 都必须落在其内) -const uint TILE_V4_A = (BM * BK) / 4u; // 64→256 / 16→64 -const uint TILE_V4_B = (BK * BN) / 4u; - -// Shared Memory(vec4 布局,16B 对齐,双缓冲 [2][...]): -// Ash[stage][k][m]:A^T 分块 —— A[m][k](转置写共享内存,内层按行连续读) -// Bsh[stage][k][n]:B 分块 —— B[k][n](内层按行连续读) -// 每个 vec4 = 4 个 float(64 行/列的 1/4);stage 交替:tile t 用 t&1 -shared vec4 Ash[2][BK][BM / 4]; -shared vec4 Bsh[2][BK][BN / 4]; - -// ── 协作加载 A 分块 BM×BK → 指定 stage ──────────────────────────────────── -// 线程分组:EPT_A/4 个「4 元素组」,组索引 g = tid*(EPT_A/4) + vv。 -// 按 transA 选连续维(全局地址连续 ⇒ vec4 快路径): -// transA=0:A[m][k] 行主序,组 = 同行连续 4 个 k(g ↔ m=(4g)/BK, k0=(4g)%BK) -// transA=1:A 存储 (K,M),组 = 同 k 连续 4 个 m(g ↔ k=g/(BM/4), mq=g%(BM/4)) -// 未对齐/边界回退走**同一分组**的标量循环 → 覆盖恰好一致。 -void load_A_tile(uint t, uint stage, uint tid, uint block_row) -{ - const uint a_base_col = t * BK; - const bool k_contig = (transA == 0u); - // vec4 元素下标 %4==0 的形状条件(uniform 分支): - // 沿 k 连续:gr*K%4==0 ∀gr ⇔ K%4==0;沿 m 连续:k*M%4==0 ∀k ⇔ M%4==0 - const bool aligned = k_contig ? ((K & 3u) == 0u) : ((M & 3u) == 0u); - const uint groups = (EPT_A >= 4u) ? (EPT_A / 4u) : 1u; - - for (uint vv = 0u; vv < groups; ++vv) - { - const uint g = tid * groups + vv; - if (g >= TILE_V4_A) continue; // 细 tile:tid ≥ tile vec4 数不加载 - if (k_contig) - { - // ── 沿 k 连续:4 个连续 k,同一行 m ────────────────────────── - const uint m = (4u * g) / BK; - const uint k0 = (4u * g) % BK; - const uint gr = block_row + m; - const uint gc = a_base_col + k0; // k0%4==0、a_base_col%4==0 ⇒ gc%4==0 - if (aligned && gr < M && gc < K) - { - // K%4==0 且 gc> 2); - Ash[stage][k0 ][m >> 2][m & 3u] = v.x; - Ash[stage][k0 + 1u][m >> 2][m & 3u] = v.y; - Ash[stage][k0 + 2u][m >> 2][m & 3u] = v.z; - Ash[stage][k0 + 3u][m >> 2][m & 3u] = v.w; - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr < M && gc + j < K) ? NN_RD(A[gr * K + gc + j]) : 0.0; - Ash[stage][k0 + j][m >> 2][m & 3u] = v; - } - } - } - else - { - // ── 沿 m 连续(A 存储 K×M):4 个连续 m,同一 k ───────────── - const uint mq = g % (BM / 4u); - const uint k = g / (BM / 4u); - const uint gc = a_base_col + k; - const uint gr = block_row + mq * 4u; // block_row%4==0(BM%4==0) - if (aligned && gc < K && gr < M) - { - // M%4==0 且 gr> 2); - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr + j < M && gc < K) ? NN_RD(A[gc * M + gr + j]) : 0.0; - Ash[stage][k][mq][j] = v; - } - } - } - } -} - -// ── 协作加载 B 分块 BK×BN → 指定 stage ──────────────────────────────────── -// 按 transB 选连续维: -// transB=0:B[k][n] 行主序,组 = 同 k 连续 4 个 n(g ↔ k=(4g)/BN, n0=(4g)%BN) -// transB=1:B 存储 (N,K),组 = 同 n 连续 4 个 k(g ↔ n=g/4, k0=(g%4)*4) -void load_B_tile(uint t, uint stage, uint tid, uint block_col) -{ - const uint b_base_row = t * BK; - const bool n_contig = (transB == 0u); - // 沿 n 连续:k*N%4==0 ∀k ⇔ N%4==0;沿 k 连续:n*K%4==0 ∀n ⇔ K%4==0 - const bool aligned = n_contig ? ((N & 3u) == 0u) : ((K & 3u) == 0u); - const uint groups = (EPT_B >= 4u) ? (EPT_B / 4u) : 1u; - - for (uint vv = 0u; vv < groups; ++vv) - { - const uint g = tid * groups + vv; - if (g >= TILE_V4_B) continue; // 细 tile:tid ≥ tile vec4 数不加载 - if (n_contig) - { - // ── 沿 n 连续:4 个连续 n,同一行 k ────────────────────────── - const uint k = (4u * g) / BN; - const uint n0 = (4u * g) % BN; - const uint gr = b_base_row + k; - const uint gc = block_col + n0; // n0%4==0、block_col%4==0 ⇒ gc%4==0 - if (aligned && gr < K && gc < N) - { - const vec4 v = NN_LOADV4(Bv4, (gr * N + gc) >> 2); - Bsh[stage][k][n0 >> 2] = v; // vec4 沿 n ↔ [k][n>>2] 整槽 - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr < K && gc + j < N) ? NN_RD(B[gr * N + gc + j]) : 0.0; - Bsh[stage][k][(n0 + j) >> 2][(n0 + j) & 3u] = v; - } - } - } - else - { - // ── 沿 k 连续(B 存储 N×K):4 个连续 k,同一 n ───────────── - const uint n = g / 4u; - const uint k0 = (g % 4u) * 4u; - const uint gc = block_col + n; - const uint gr = b_base_row + k0; // b_base_row%4==0(t*BK%4==0) - if (aligned && gc < N && gr < K) - { - const vec4 v = NN_LOADV4(Bv4, (gc * K + gr) >> 2); - for (uint j = 0u; j < 4u; ++j) - Bsh[stage][k0 + j][n >> 2][n & 3u] = v[j]; - } - else - { - for (uint j = 0u; j < 4u; ++j) - { - const float v = (gr + j < K && gc < N) ? NN_RD(B[gc * K + gr + j]) : 0.0; - Bsh[stage][k0 + j][n >> 2][n & 3u] = v; - } - } - } - } -} - -void main() -{ - const uint tx = gl_LocalInvocationID.x; // 0..T-1 - const uint ty = gl_LocalInvocationID.y; // 0..T-1 - const uint tid = ty * NN_MM_TU + tx; // 线性线程 ID 0..T²-1 - - // 当前 WorkGroup 负责的全局输出块起始坐标 - const uint block_row = gl_WorkGroupID.y * BM; - const uint block_col = gl_WorkGroupID.x * BN; - - // ── 寄存器分块 ──────────────────────────────────────────────────── -#if defined(NN_MM_FINE) - // 细 tile:R=2,每线程 2×2 输出(4 条独立 K 链 = 线程内 ILP; - // 网格仍 16×16 块 → 48 WG 摊满 SM;逐 k 标量升序乘加与 64-tile 逐位同序) - float a00 = 0.0, a01 = 0.0, a10 = 0.0, a11 = 0.0; -#else - // 4×4 累加器(vec4 数组) - vec4 acc[4]; - for (uint i = 0u; i < 4u; ++i) - acc[i] = vec4(0.0); -#endif - - const uint num_tiles = (K + BK - 1u) / BK; - - // ── 前奏:加载 tile 0 → stage 0,同步一次 ─────────────────────── - load_A_tile(0u, 0u, tid, block_row); - load_B_tile(0u, 0u, tid, block_col); - barrier(); - - // ── 流水循环:发射 tile t+1 的加载(stage^1),同时计算 tile t ──── - // 每 tile 仅 1 次 barrier;barrier 同时保证 - // ① tile t+1 的全局加载在下一迭代被读取前完成 - // ② tile t 的计算在 stage^1 被覆盖前完成(与 ①天然错开 stage) - uint stage = 0u; - for (uint t = 0u; t < num_tiles; ++t) - { - const uint nxt = t + 1u; - const uint nstage = stage ^ 1u; - if (nxt < num_tiles) - { - load_A_tile(nxt, nstage, tid, block_row); - load_B_tile(nxt, nstage, tid, block_col); - } - - // ── 累加(每 k:2 次 LDS + FMA)────────────────────────────── - // ⚠ 曾试「k+1 预取软件流水」(把 k+1 的 LDS 提到 k 的 FMA 前): - // 细 tile 实测 1.33→2.13ms(+60%,寄存器/复制链反而拖累)、64-tile - // 持平——**该路径不要加预取**(fold 生成器能加是因为其归约域是 - // e[n][128] 私有数组,不是 shared LDS 链)。 - #pragma unroll 4 - for (uint k = 0u; k < BK; ++k) - { -#if defined(NN_MM_FINE) - // R=2:行对 ty*2、ty*2+1 → 共享行组 (ty*2)>>2 = ty>>1、组内 - // 基 (ty*2)&3 = (ty&1)*2;列对同构。2 次 vec4 LDS + 4 标量 FMA。 - const vec4 a_vec = Ash[stage][k][ty >> 1u]; - const vec4 b_vec = Bsh[stage][k][tx >> 1u]; - const float ar0 = a_vec[(ty & 1u) * 2u]; - const float ar1 = a_vec[(ty & 1u) * 2u + 1u]; - const float bc0 = b_vec[(tx & 1u) * 2u]; - const float bc1 = b_vec[(tx & 1u) * 2u + 1u]; - - a00 += ar0 * bc0; - a01 += ar0 * bc1; - a10 += ar1 * bc0; - a11 += ar1 * bc1; -#else - // 本线程负责的 4 行(ty*4..ty*4+3)与 4 列(tx*4..tx*4+3) - // vec4 外积累加:2 次 vec4 LDS + 16 FMA = 8:1 - const vec4 a_vec = Ash[stage][k][ty]; - const vec4 b_vec = Bsh[stage][k][tx]; - - acc[0] += a_vec.x * b_vec; - acc[1] += a_vec.y * b_vec; - acc[2] += a_vec.z * b_vec; - acc[3] += a_vec.w * b_vec; -#endif - } - - barrier(); - stage = nstage; - } - - // ── 写入结果 ────────────────────────────────────────────────────── -#if defined(NN_MM_FINE) - // 细 tile:R=2 逐标量写出(线程 (ty,tx) ↔ 输出块内 2×2;warp 内相邻 - // 列 → 128B 合并写。fine 仅 f32 构建,NN_WR 仍走宏保同形) - { - const uint r0 = block_row + ty * 2u; - const uint c0 = block_col + tx * 2u; - const float accs[4] = float[4](a00, a01, a10, a11); - for (uint i = 0u; i < 2u; ++i) - for (uint j = 0u; j < 2u; ++j) - { - const uint out_row = r0 + i; - const uint out_col = c0 + j; - if (out_row < M && out_col < N) - C[out_row * N + out_col] = NN_WR(accs[i * 2u + j]); - } - } -#elif defined(NN_SHADER_F16) - // f16 输出:逐标量写出(C 无 vec4 别名视图),与 f32 的 N%4!=0 分支同构 - for (uint i = 0u; i < 4u; ++i) - { - const uint out_row = block_row + ty * 4u + i; - if (out_row >= M) continue; - const uint out_col0 = block_col + tx * 4u; - if (out_col0 >= N) continue; - C[out_row * N + out_col0 + 0u] = NN_WR(acc[i].x); - if (out_col0 + 1u < N) - C[out_row * N + out_col0 + 1u] = NN_WR(acc[i].y); - if (out_col0 + 2u < N) - C[out_row * N + out_col0 + 2u] = NN_WR(acc[i].z); - if (out_col0 + 3u < N) - C[out_row * N + out_col0 + 3u] = NN_WR(acc[i].w); - } -#else - // vec4 快路径(N%4==0,uniform):out_col0%4==0 且 out_col0= M) continue; - const uint out_col0 = block_col + tx * 4u; - if (c_vec4) - { - if (out_col0 < N) - Cv4[(out_row * N + out_col0) >> 2] = acc[i]; - } - else - { - if (out_col0 >= N) continue; - C[out_row * N + out_col0 + 0u] = acc[i].x; - if (out_col0 + 1u < N) - C[out_row * N + out_col0 + 1u] = acc[i].y; - if (out_col0 + 2u < N) - C[out_row * N + out_col0 + 2u] = acc[i].z; - if (out_col0 + 3u < N) - C[out_row * N + out_col0 + 3u] = acc[i].w; - } - } -#endif -} diff --git a/shaders/nn_f16_rne.glsl b/shaders/nn_f16_rne.glsl deleted file mode 100644 index c77d522..0000000 --- a/shaders/nn_f16_rne.glsl +++ /dev/null @@ -1,30 +0,0 @@ -// ── nn_f16_rne.glsl — f32 → f16 存储的 RNE(round-half-to-even)预舍入 ────── -// 供所有带 -DNN_SHADER_F16=1 变体的手写 shader #include(glslc 原生支持)。 -// -// 为什么需要:GLSL 的 float16_t(x) 编译为 SPIR-V OpFConvert,其舍入模式**未被 -// Vulkan/SPIR-V 规范钉死**;实测 NVIDIA 驱动按“向零截断”。对 AdamW 权重衰减 -// p *= (1-lr·wd) 这类结果紧贴原 f16 网格点的写回,截断恰好每步掉 1 个网格步 -//(与 lr 无关)→ 数万步后权重下溢为 0、loss 回到 ln(V)。 -// ⚠ packHalf2x16 不能作为替代:同一驱动实测它**同样截断**(Khronos -// Vulkan-Docs #1825「PackHalf2x16 rounding behavior is ambiguously defined」), -// 与宿主 nn::f16(RNE)对拍 8 个探针值有 4 个不一致。 -// -// 做法:在 f32 域用整数位运算做一次精确 RNE,返回值本身落在 f16 网格上 -// (f16 ⊂ f32,可精确表示)→ 随后的 float16_t() 收窄 / packHalf2x16 打包 -// 无论驱动取哪种舍入模式都不再产生偏差。净效果 = 一次 RNE 的 f16 存储。 -// -// 与 include 方约定:宏 NN_F16_RNE_FN 为函数名(默认 nn_f16_rne)。 -#ifndef NN_F16_RNE_FN -#define NN_F16_RNE_FN nn_f16_rne -#endif - -float NN_F16_RNE_FN(float x) -{ - uint u = floatBitsToUint(x); - const uint a = u & 0x7fffffffu; - if (a >= 0x7f800000u || a < 0x38800000u) // Inf/NaN,或 |x| < 2^-14(次正规) - return roundEven(x * 16777216.0) / 16777216.0; - u += 0x0fffu + ((u >> 13u) & 1u); // 半个 ULP + tie-to-even - u &= 0xffffe000u; // 截到 f16 的 10 位尾数 - return uintBitsToFloat(u); -} diff --git a/shaders/outer_col.comp b/shaders/outer_col.comp deleted file mode 100644 index 8957368..0000000 --- a/shaders/outer_col.comp +++ /dev/null @@ -1,48 +0,0 @@ -// ── outer_col.comp ──────────────────────────────────────────────────── -// 逐列外积原语(backward 物化 dL/dA / dL/dB): -// out[(bh*dk+a)*dk+c, t] = P[a,t]·R[c,t] (· S[t] if has_scale) -// P/R (B*H*dk, seq); S (B*H*dk, seq) 标量头内逐行重复(读首行); -// has_scale=0 时 S 为 dummy。 -// 输出 (B*H*dk², seq);扁平并行,无 shared memory。 -// ───────────────────────────────────────────────────────────────────── -#version 450 -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2f:输入输出均 half、乘积在 -// f32 完成后落回;仅需 storageBuffer16BitAccess)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufP { NN_ETYPE P[]; }; -layout(std430, binding = 1) readonly buffer BufR { NN_ETYPE R[]; }; -layout(std430, binding = 2) readonly buffer BufS { NN_ETYPE S[]; }; -layout(std430, binding = 3) writeonly buffer BufOut { NN_ETYPE Out[]; }; - -layout(push_constant) uniform Push { - uint dk, seq, rows, has_scale; -}; - -void main() { - const uint idx = gl_GlobalInvocationID.x; - if (idx >= rows * dk * seq) return; - - const uint t = idx % seq; - const uint e = idx / seq; - const uint bh = e / (dk * dk); - const uint rem = e % (dk * dk); - const uint a = rem / dk; - const uint c = rem % dk; - const uint r0 = bh * dk; - - const float p = NN_RD(P[(r0 + a) * seq + t]) * NN_RD(R[(r0 + c) * seq + t]); - Out[idx] = NN_WR((has_scale == 1u) ? (p * NN_RD(S[r0 * seq + t])) : p); -} diff --git a/shaders/rearrange_3d.comp b/shaders/rearrange_3d.comp deleted file mode 100644 index edff6a5..0000000 --- a/shaders/rearrange_3d.comp +++ /dev/null @@ -1,73 +0,0 @@ -// ── rearrange_3d.comp ─────────────────────────────────────────────────────── -// 纯原语:3D 维度转置 (M, B, N) ↔ (B, M, N) -// -// 设计原则:本着色器只提供数据重排原语,不含任何算法语义。 -// -// inverse=0: 输入 (M, B*N) → 输出 (B*M, N) -// out[b*M + m, n] = in[m, b*N + n] -// inverse=1: 输入 (B*M, N) → 输出 (M, B*N) -// out[m, b*N + n] = in[b*M + m, n] -// -// Push Constants: -// uint M — 第一维度大小 -// uint B — 第二维度大小 -// uint N — 第三维度大小 -// uint inverse — 0=forward, 1=inverse -// uint total — 元素总数 M*B*N -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// -DNN_SHADER_F16=1 → f16 存储变体(纯位拷贝,零算术):缓冲 float16_t, -// 只需 storageBuffer16BitAccess(无 Float16 capability——无转换无算术)。 -// 索引数学与 f32 版逐字节同构(行/列/元素下标不受元素宽度影响)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#else -#define NN_ETYPE float -#endif - -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufferIn { NN_ETYPE in_data[]; }; -layout(std430, binding = 1) writeonly buffer BufferOut { NN_ETYPE out_data[]; }; - -layout(push_constant) uniform PushConstants { - uint M; - uint B; - uint N; - uint inverse; - uint total; -}; - -void main() -{ - const uint idx = gl_GlobalInvocationID.x; - if (idx >= total) return; - - // 把线性索引 idx 分解为 (m, b, n) 或 (b, m, n) - if (inverse == 0u) - { - // 输入按 (M, B*N) 存储:idx = m * (B*N) + b * N + n - const uint n = idx % N; - const uint rest = idx / N; - const uint b = rest % B; - const uint m = rest / B; - // 输出按 (B*M, N) 存储:out_idx = (b * M + m) * N + n - const uint out_idx = (b * M + m) * N + n; - out_data[out_idx] = in_data[idx]; - } - else - { - // 输入按 (B*M, N) 存储:idx = (b * M + m) * N + n - const uint n = idx % N; - const uint rest = idx / N; - const uint bm = rest; // b * M + m - const uint m = bm % M; - const uint b = bm / M; - // 输出按 (M, B*N) 存储:out_idx = m * (B * N) + b * N + n - const uint out_idx = m * (B * N) + b * N + n; - out_data[out_idx] = in_data[idx]; - } -} diff --git a/shaders/reduce.comp b/shaders/reduce.comp deleted file mode 100644 index 9adc908..0000000 --- a/shaders/reduce.comp +++ /dev/null @@ -1,242 +0,0 @@ -// ── reduce.comp ─────────────────────────────────────────────────────────── -// 归约原语着色器(工作组级并行归约) -// -// 只提供求和/最大值归约原语,供 Layer 组合表达 mean、var、grad_b 等。 -// -// 模式: -// mode=0: row_reduce — output[r] = reduce_op(A[r*cols + c]) over c -// 输入 (rows, cols) → 输出 (rows, 1) -// mode=1: col_reduce — output[c] = reduce_op(A[r*cols + c]) over r -// 输入 (rows, cols) → 输出 (1, cols) -// -// 归约算子: -// reduce_op=0: sum -// reduce_op=1: max -// -// 绑定布局: -// binding 0: 输入 A (readonly) -// binding 1: 输出 OUT (writeonly) -// -// Push Constants: -// uint rows — 输入行数(两段式 pass2 = nchunk) -// uint cols — 输入列数 -// uint mode — 0=row_reduce, 1=col_reduce -// uint reduce_op — 0=sum, 1=max -// uint chunk_rows — >0: 列归约两段式 pass1(WG.y = 行块序号,输出 = partials -// [y*cols + col]);0 = 整表单段(输出 = result[col]) -// -// Dispatch(backend 侧,与下方各模式的 WG 结构契约同源): -// row_reduce: (rows, 1, 1) — 每 WG 256 线程协作归约一行 -// col_reduce 单段: ((cols+31)/32, 1) — 每 WG 覆盖 32 列 tile -// col_reduce 两段式(rows 大、单段 WG 数不足喂满机器时): -// pass1: ((cols+31)/32, nchunk, 1),chunk_rows = ceil(rows/nchunk) -// pass2: ((cols+31)/32, 1, 1),输入 = partials(nchunk, cols) -// 两 pass 录入同一 cmd 单次提交(固定提交开销 ×1)——ggml -// rms_norm_partials 的 partials-buffer 模式;动机:单段 WG 数 = -// ceil(cols/32)(5244² 仅 164 vs 136 并发槽 → 1.2 波,末波只剩 ~20% -// 机器喂内存,实测带宽被尾部压约两成;对照 8192² 256 WG 尾部小带宽高)。 -// -// ── 与 DSL 生成归约(expr_glsl_gen generate_glsl_reduce)同结构 ────────── -// ① 列归约访存(按列合并):lane = 列内偏移(同一行 32 连续列 = 128B 单 -// 事务),行方向切成 8 个行块(warp id)保并行度,跨行块部分和经 shared -// 合并(8 项,读写均合并、零 bank 冲突)。 -// ② max 恒等元:numeric_limits::lowest()(0xFF7FFFFF,全库统一—— -// reduce.comp / DSL / CPU 同值)——-inf 不能作恒等元:全 -inf 输入下 -// 与状态进位/减法组合会出 −inf−−inf=NaN。 -// ③ 行归约归约树:subgroup 蝶式(3 次 barrier,subgroupAdd/subgroupMax → -// 跨 subgroup 收拢 → lane0 广播),与 DSL emit_tree_reduce 同构。 -// 4 路独立标量累加器(acc0..3,跨步 4*stride)与 DSL 行/列 pass 同款, -// 打破累加依赖链提升 ILP;归约结合序与单线程不同(测试容差覆盖,与 -// DSL 路径同等地位)。 -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -#extension GL_KHR_shader_subgroup_basic : require -#extension GL_KHR_shader_subgroup_arithmetic : require - -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2 op-level in-kernel f16): -// 输入缓冲 float16_t(直读、f32 累加——shared/归约算术全 float 不变, -// 消灭边界 cast 的整份 f32 输入副本:归因大头 Linear::backward grad_bias -// 的 (64,8192)×38)。输出 OUT 仍 float((rows,1)/(1,cols) 归约向量极小, -// 保持 f32 → backend 无需改输出分配)。vec4 别名 uvec2(4×half=8B, -// std430 步长恰好 4 元素,索引语义与 f32 vec4 同;行偏移对齐:cols%4==0 -// ⇒ row*cols%4==0 ⇒ 8B 对齐成立)。宏零成本:f32 分支宏展开与裸写 f32 -// 代码逐字同形 → SPIR-V 字节零差异。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require -#endif - -layout(local_size_x = 256) in; - -#if defined(NN_SHADER_F16) -layout(std430, binding = 0) readonly buffer BufIn { float16_t data[]; }; -#define NN_RD(x) (float(x)) -#define NN_V4 uvec2 -#define NN_LOADV4(i) (vec4(unpackHalf2x16(data4[i].x), unpackHalf2x16(data4[i].y))) -#else -layout(std430, binding = 0) readonly buffer BufIn { float data[]; }; -#define NN_RD(x) x -#define NN_V4 vec4 -#define NN_LOADV4(i) (data4[i]) -#endif -layout(std430, binding = 1) writeonly buffer BufOut { float result[]; }; -// vec4 别名视图(同 binding 双声明;行归约 cols%4==0 快路径,16B 对齐由 -// MemoryPool 保证) -layout(std430, binding = 0) readonly buffer BufIn4 { NN_V4 data4[]; }; - -layout(push_constant) uniform PushConstants { - uint rows; - uint cols; - uint mode; - uint reduce_op; - uint chunk_rows; // >0: 列归约两段式 pass1(WG.y = 行块序号);0 = 整表单段 -}; - -// 行模式:subgroup 蝶式归约暂存(256 线程 → ≤64 subgroup 部分和) -shared float sdata[256]; -// 列模式:跨行块合并 [row_block 0..7][col_in_tile 0..31] -shared float sred[8][32]; - -// 恒等元:sum=0;max=lowest()(0xFF7FFFFF,全库统一,见文件头 ②) -float identity() -{ - return (reduce_op == 0u) ? 0.0 : uintBitsToFloat(0xFF7FFFFFu); -} - -float combine(float a, float b) -{ - return (reduce_op == 0u) ? (a + b) : max(a, b); -} -vec4 combine(vec4 a, vec4 b) -{ - return (reduce_op == 0u) ? (a + b) : max(a, b); -} - -void main() -{ - const uint tid = gl_LocalInvocationID.x; - - // ── 行归约:工作组 ID = 行索引,256 线程协作归约一行(合并访问)── - if (mode == 0u) - { - const uint row = gl_WorkGroupID.x; - if (row >= rows) return; - - const float init = identity(); - float v; - - if ((cols & 3u) == 0u) - { - // ── vec4 快路径:每线程 4 组 vec4 累加器, - // 组内 4 元素连续 → 跨 lane 512B 连铺、指令数 ÷4。 - // 横折 4 分量后进 subgroup 蝶式(与标量回退同构)。 - const uint g4 = (row * cols) >> 2; - const uint n4 = cols >> 2; - vec4 acc0 = vec4(init), acc1 = vec4(init); - vec4 acc2 = vec4(init), acc3 = vec4(init); - uint g = tid; - for (; g + 3u * 256u < n4; g += 4u * 256u) - { - acc0 = combine(acc0, NN_LOADV4(g4 + g)); - acc1 = combine(acc1, NN_LOADV4(g4 + g + 256u)); - acc2 = combine(acc2, NN_LOADV4(g4 + g + 512u)); - acc3 = combine(acc3, NN_LOADV4(g4 + g + 768u)); - } - for (; g < n4; g += 256u) - acc0 = combine(acc0, NN_LOADV4(g4 + g)); - const vec4 s = combine(combine(acc0, acc1), combine(acc2, acc3)); - v = combine(combine(s.x, s.y), combine(s.z, s.w)); - } - else - { - // 标量回退:4 路独立累加器(跨步 4*256),与 DSL 行 pass 同款 ILP - float acc0 = init, acc1 = init, acc2 = init, acc3 = init; - uint c = tid; - for (; c + 3u * 256u < cols; c += 4u * 256u) - { - const uint base = row * cols; - acc0 = combine(acc0, NN_RD(data[base + c])); - acc1 = combine(acc1, NN_RD(data[base + c + 256u])); - acc2 = combine(acc2, NN_RD(data[base + c + 512u])); - acc3 = combine(acc3, NN_RD(data[base + c + 768u])); - } - for (; c < cols; c += 256u) - acc0 = combine(acc0, NN_RD(data[row * cols + c])); - v = combine(combine(acc0, acc1), combine(acc2, acc3)); - } - - sdata[tid] = v; - barrier(); - - // ── subgroup 蝶式归约(与 DSL emit_tree_reduce 同构)────────── - v = sdata[tid]; - v = (reduce_op == 0u) ? subgroupAdd(v) : subgroupMax(v); - if (gl_SubgroupInvocationID == 0u) - sdata[gl_SubgroupID] = v; - barrier(); - if (gl_SubgroupID == 0u) - { - float w = (gl_SubgroupInvocationID < gl_NumSubgroups) - ? sdata[gl_SubgroupInvocationID] : identity(); - w = (reduce_op == 0u) ? subgroupAdd(w) : subgroupMax(w); - if (gl_SubgroupInvocationID == 0u) - sdata[0] = w; - } - barrier(); - - if (tid == 0u) result[row] = sdata[0]; - return; - } - - // ── 列归约:WG = 32 列 tile;lane = 列内偏移(同 32 连续列 → - // 同一行读地址连续 = 128B 合并事务),warp = 行块(8 个,r 步进 8); - // 跨行块部分和经 sred[8][32] 合并(写 wb*32+l 连续、读 w*32+l 连续)── - if (mode == 1u) - { - const uint l = tid & 31u; // 列内偏移 0..31 - const uint wb = tid >> 5u; // 行块 0..7 - const uint col0 = gl_WorkGroupID.x * 32u; - const uint col = col0 + l; - - // 行窗口:chunk_rows>0(两段式 pass1)时 WG.y = 行块序号,本 WG 只 - // 归约 [rows_lo, rows_hi) 并写 partials[y*cols + col];pass2 以 - // partials 为输入、rows=nchunk、chunk_rows=0 复用整表路径合并。 - const uint rows_lo = (chunk_rows == 0u) ? 0u : gl_WorkGroupID.y * chunk_rows; - const uint rows_hi = (chunk_rows == 0u) ? rows - : min(rows_lo + chunk_rows, rows); - - const float init = identity(); - float acc0 = init, acc1 = init, acc2 = init, acc3 = init; - if (col < cols) - { - // 4 路累加器:跨步 4*8(与 DSL 列 pass 同款 ILP 形态) - uint r = rows_lo + wb; - for (; r + 3u * 8u < rows_hi; r += 4u * 8u) - { - acc0 = combine(acc0, NN_RD(data[r * cols + col])); - acc1 = combine(acc1, NN_RD(data[(r + 8u) * cols + col])); - acc2 = combine(acc2, NN_RD(data[(r + 16u) * cols + col])); - acc3 = combine(acc3, NN_RD(data[(r + 24u) * cols + col])); - } - for (; r < rows_hi; r += 8u) - acc0 = combine(acc0, NN_RD(data[r * cols + col])); - } - sred[wb][l] = combine(combine(acc0, acc1), combine(acc2, acc3)); - barrier(); - - // 跨 8 行块合并(跨 lane 地址连续 = 合并读) - float res = init; - for (uint w = 0u; w < 8u; ++w) - res = combine(res, sred[w][l]); - - if (wb == 0u && col < cols) - { - const uint out_idx = (chunk_rows == 0u) ? col - : gl_WorkGroupID.y * cols + col; - result[out_idx] = res; - } - return; - } -} diff --git a/shaders/scan_prefix_outer.comp b/shaders/scan_prefix_outer.comp deleted file mode 100644 index 25a9817..0000000 --- a/shaders/scan_prefix_outer.comp +++ /dev/null @@ -1,216 +0,0 @@ -// ── scan_prefix_outer.comp ───────────────────────────────────────────── -// RLA 前缀扫描原语:A_t = A0 + Σ_{i≤t, 同文档} k_i·k_i^T, B_t = B0 + Σ v_i·k_i^T -// causal=0: 全集常数(无重置);causal=1: 含自身前缀,文档边界处运行态清零 -// 输出 (B*H*5*dk, seq) 行块:[0) B·P [1) A·P [2) B^T·R [3) s [4) r -// 1 workgroup/头,256 线程;A/B 驻 shared(dk ≤ NN_SCAN_DK_MAX) -// -// **共享足迹宏 NN_SCAN_DK_MAX(P4-2b 占用率变体)**:默认 64 ⇒ A/B 占 -// 64×65×2×4 ≈ 33.3KB,Maxwell 上每 SM 只能驻留 **1 个** workgroup(32 个 WG 在 -// 5 个 SM 上要排 6.4 个波次)。以 `-DNN_SCAN_DK_MAX=32` 再编一份 ⇒ A/B 降到 -// 32×33×2×4 ≈ 8.5KB(+行块 0.8KB)⇒ 每 SM 可驻留 **6 个** ⇒ RAPT 的 -// d128/h4(dk=32、BH=32)几乎压到 1 个波次。C++ 侧按 `dk ≤ 32` 选窄变体, -// 缺窄变体时自动回落宽变体(等价、只是慢)。 -// -// P4-2b(2026-10-15)**行归属(row ownership)结构**:线程 a 独占 A/B 的第 a 行, -// ⇒ 每 t 的「累加 → matvec 读出」不再需要跨线程可见性(同一线程既写自己的行、 -// 又只读自己的行),per-t barrier 从 4 次降到 2 次;同时把 R 行块也 stage 进 -// shared(旧写法 [2)/[4) 每 t 各自读全局 R:前者 32×32 次、后者单线程 64 次), -// 全局往返从 3 次/t 降到 1 次/t;[4) 的标量改由 dk 个线程各算一遍(同值)后各写 -// 自己那行,消掉"单线程串行 2·dk 次"的关键路径。 -// **逐位不变**:每个元素的累加次序与 CPU 实现(scan_prefix_outer_f32_)、以及旧 -// kernel 完全一致(A/B 逐元素每 t 只加一次、各行 j/c/i 均按升序),因此哈希相同。 -// ────────────────────────────────────────────────────────────────────── -#version 450 -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2f:RLA 扫描原语,输入输出均 -// half——输出 (B*H*5*dk, seq) 全尺寸,f16 落回即消灭适配层全尺寸 f32 -// 副本;shared/累加算术保持 float,读处 NN_RD 显式转、写处 NN_WR 落回。 -// 仅需 storageBuffer16BitAccess)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufK { NN_ETYPE K[]; }; -layout(std430, binding = 1) readonly buffer BufV { NN_ETYPE V[]; }; -layout(std430, binding = 2) readonly buffer BufP { NN_ETYPE P[]; }; -layout(std430, binding = 3) readonly buffer BufR { NN_ETYPE R[]; }; -layout(std430, binding = 4) readonly buffer BufA0 { NN_ETYPE A0[]; }; -layout(std430, binding = 5) readonly buffer BufB0 { NN_ETYPE B0[]; }; -layout(std430, binding = 6) readonly buffer BufBnd{ NN_ETYPE Bnd[]; }; -layout(std430, binding = 7) writeonly buffer BufOut{ NN_ETYPE Out[]; }; - -layout(push_constant) uniform Push { - uint dk, heads, seq, causal, has_state, has_bnd, rows; -}; - -#ifndef NN_SCAN_DK_MAX -#define NN_SCAN_DK_MAX 64u -#endif -const uint PAD = NN_SCAN_DK_MAX + 1u; // +1 消除行访问的 bank 冲突 -shared float sA[NN_SCAN_DK_MAX][PAD]; -shared float sB[NN_SCAN_DK_MAX][PAD]; -shared float s_qv[NN_SCAN_DK_MAX], s_kv[NN_SCAN_DK_MAX], s_vv[NN_SCAN_DK_MAX], - s_rv[NN_SCAN_DK_MAX], s_num[NN_SCAN_DK_MAX], s_Aq[NN_SCAN_DK_MAX]; -shared uint s_reset; - -void main() { - const uint bh = gl_WorkGroupID.y; - const uint b = bh / heads; - const uint h = bh % heads; - const uint r0 = bh * dk; - const uint tid = gl_LocalInvocationID.x; - - // 1) 初始化 A/B 行(零 或 A0/B0 行块 h)——行归属:线程 a 只碰自己的第 a 行 - if (tid < dk) { - for (uint c = 0u; c < dk; ++c) { - float va = 0.0, vb = 0.0; - if (has_state == 1u) { - const uint sr = h * dk + tid; - va = NN_RD(A0[sr * dk + c]); - vb = NN_RD(B0[sr * dk + c]); - } - sA[tid][c] = va; - sB[tid][c] = vb; - } - } - barrier(); - - // 2) 双向:预累加全集(行归属;每 t 仅需 k/v,故仍是 1 载入 + 1 屏障/步) - if (causal == 0u) { - for (uint t = 0u; t < seq; ++t) { - if (tid < dk) { - s_kv[tid] = NN_RD(K[(r0 + tid) * seq + t]); - s_vv[tid] = NN_RD(V[(r0 + tid) * seq + t]); - } - barrier(); - if (tid < dk) { - for (uint c = 0u; c < dk; ++c) { - sA[tid][c] += s_kv[tid] * s_kv[c]; - sB[tid][c] += s_vv[tid] * s_kv[c]; - } - } - barrier(); - } - } - - // 3) 逐 t 顺序扫描 + 读出(行归属 ⇒ 每 t 2 次 barrier、1 次全局往返) - for (uint t = 0u; t < seq; ++t) { - // 载入:q/k/v/r 四行块各铺满 dk 个线程并行读(4·dk ≤ 256 线程; - // dk > 64 走 generic 变体,故这里恒 ≤ 4·64 = 256)。 - if (tid < dk) { - s_qv[tid] = NN_RD(P[(r0 + tid) * seq + t]); - } else if (tid < 2u * dk) { - s_vv[tid - dk] = NN_RD(V[(r0 + tid - dk) * seq + t]); - } else if (tid < 3u * dk) { - s_rv[tid - 2u * dk] = NN_RD(R[(r0 + tid - 2u * dk) * seq + t]); - } else if (tid < 4u * dk) { - s_kv[tid - 3u * dk] = NN_RD(K[(r0 + tid - 3u * dk) * seq + t]); - } - if (tid == 0u && causal == 1u) { - uint reset = 0u; - if (has_bnd == 1u) - reset = (NN_RD(Bnd[b * seq + t]) != 0.0) ? 1u : 0u; - s_reset = reset; - } - barrier(); // B1:载入可见(含 s_reset) - - if (tid < dk) { - // 4 路展开(**只改 ILP,不改累加次序**:先并行取回 4 个操作数, - // 再按 c 升序逐个累加 —— 每个元素每 t 仍然恰好加一次、序不变)。 - if (causal == 1u) { - if (s_reset == 1u) - for (uint c = 0u; c < dk; ++c) { sA[tid][c] = 0.0; sB[tid][c] = 0.0; } - const float kt = s_kv[tid]; - const float vt = s_vv[tid]; - uint c = 0u; - for (; c + 4u <= dk; c += 4u) { - const float k0 = s_kv[c + 0u], k1 = s_kv[c + 1u], - k2 = s_kv[c + 2u], k3 = s_kv[c + 3u]; - sA[tid][c + 0u] += kt * k0; - sA[tid][c + 1u] += kt * k1; - sA[tid][c + 2u] += kt * k2; - sA[tid][c + 3u] += kt * k3; - sB[tid][c + 0u] += vt * k0; - sB[tid][c + 1u] += vt * k1; - sB[tid][c + 2u] += vt * k2; - sB[tid][c + 3u] += vt * k3; - } - for (; c < dk; ++c) { - sA[tid][c] += kt * s_kv[c]; - sB[tid][c] += vt * s_kv[c]; - } - } - // num = B·qv;Aq = A·qv(线程独占行 ⇒ 无需屏障):同样 4 路 - // 展开取操作数、按 j 升序累加(与串行写法逐位一致)。 - float num = 0.0, Aq = 0.0; - uint j = 0u; - for (; j + 4u <= dk; j += 4u) { - const float q0 = s_qv[j + 0u], q1 = s_qv[j + 1u], - q2 = s_qv[j + 2u], q3 = s_qv[j + 3u]; - num += sB[tid][j + 0u] * q0; - num += sB[tid][j + 1u] * q1; - num += sB[tid][j + 2u] * q2; - num += sB[tid][j + 3u] * q3; - Aq += sA[tid][j + 0u] * q0; - Aq += sA[tid][j + 1u] * q1; - Aq += sA[tid][j + 2u] * q2; - Aq += sA[tid][j + 3u] * q3; - } - for (; j < dk; ++j) { - num += sB[tid][j] * s_qv[j]; - Aq += sA[tid][j] * s_qv[j]; - } - s_num[tid] = num; - s_Aq[tid] = Aq; - Out[(r0 + tid) * seq + t] = NN_WR(num); // [0) B·P - Out[(rows + r0 + tid) * seq + t] = NN_WR(Aq); // [1) A·P - } - barrier(); // B2:s_num / s_Aq 可见([2)/[3)/[4) 需要全行) - - if (tid < dk) { - // [3)/[2)/[4) 三条点积:同样"先取 4 个操作数、再按原序累加"。 - float s = 0.0, acc = 0.0, r = 0.0; - uint j = 0u; - for (; j + 4u <= dk; j += 4u) { - const float q0 = s_qv[j + 0u], q1 = s_qv[j + 1u], - q2 = s_qv[j + 2u], q3 = s_qv[j + 3u]; - s += q0 * s_Aq[j + 0u]; - s += q1 * s_Aq[j + 1u]; - s += q2 * s_Aq[j + 2u]; - s += q3 * s_Aq[j + 3u]; - const float r0 = s_rv[j + 0u], r1 = s_rv[j + 1u], - r2 = s_rv[j + 2u], r3 = s_rv[j + 3u]; - r += r0 * s_num[j + 0u]; - r += r1 * s_num[j + 1u]; - r += r2 * s_num[j + 2u]; - r += r3 * s_num[j + 3u]; - const float b0 = sB[j + 0u][tid], b1 = sB[j + 1u][tid], - b2 = sB[j + 2u][tid], b3 = sB[j + 3u][tid]; - acc += b0 * r0; - acc += b1 * r1; - acc += b2 * r2; - acc += b3 * r3; - } - for (; j < dk; ++j) { - s += s_qv[j] * s_Aq[j]; - r += s_rv[j] * s_num[j]; - acc += sB[j][tid] * s_rv[j]; - } - Out[(2u * rows + r0 + tid) * seq + t] = NN_WR(acc); // [2) B^T·R - Out[(3u * rows + r0 + tid) * seq + t] = NN_WR(s); // [3) s - Out[(4u * rows + r0 + tid) * seq + t] = NN_WR(r); // [4) r - } - // B3:本 t 的读出读完 A/B 的列([2))与 s_qv/s_rv 之后,下一个 t 的 - // 载入(覆写 s_qv/s_vv/s_rv/s_kv)与前缀累加(改写 A/B 行)才能开始。 - barrier(); - } -} diff --git a/shaders/scan_prefix_outer_gen.comp b/shaders/scan_prefix_outer_gen.comp deleted file mode 100644 index a14c3a3..0000000 --- a/shaders/scan_prefix_outer_gen.comp +++ /dev/null @@ -1,125 +0,0 @@ -// ── scan_prefix_outer_gen.comp ─────────────────────────────────────────── -// RLA 前缀扫描原语(**通用 d_k** 版本,dk 无 64 上限): -// 当 d_k > 64 时后端走本 shader,A/B 运行态驻**全局显存** scratch -// (B*H*2*dk*dk)——shared 是 O(dk²) 且受 48KB 预算限制,无法驻留。 -// 共享内存只在 barrier 之间做进程同步,占用 O(1),对任意 dk 安全。 -// 语义与 scan_prefix_outer.comp 完全一致: -// A_t = A0 + Σ_{i≤t,同文档} k_i·k_i^T, B_t = B0 + Σ v_i·k_i^T -// 输出 (B*H*5*dk, seq) 行块:[0) B·P [1) A·P [2) B^T·R [3) s [4) r -// 1 workgroup/头,256 线程。性能弱于共享内存版本,优先保证正确性(任意 dk)。 -// ───────────────────────────────────────────────────────────────────────── -#version 450 -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2f:输入输出均 half;scratch -// St 是引擎按 f32 分配的 R/W 中间缓冲 → 保持 float(读写点显式转换), -// 运行态累加精度不变。仅需 storageBuffer16BitAccess)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufK { NN_ETYPE K[]; }; -layout(std430, binding = 1) readonly buffer BufV { NN_ETYPE V[]; }; -layout(std430, binding = 2) readonly buffer BufP { NN_ETYPE P[]; }; -layout(std430, binding = 3) readonly buffer BufR { NN_ETYPE R[]; }; -layout(std430, binding = 4) readonly buffer BufA0 { NN_ETYPE A0[]; }; -layout(std430, binding = 5) readonly buffer BufB0 { NN_ETYPE B0[]; }; -layout(std430, binding = 6) readonly buffer BufBnd { NN_ETYPE Bnd[]; }; -layout(std430, binding = 7) buffer BufSt { float St[]; }; // R/W scratch (B*H*2*dk*dk) -layout(std430, binding = 8) writeonly buffer BufOut { NN_ETYPE Out[]; }; - -layout(push_constant) uniform Push { - uint dk, heads, seq, causal, has_state, has_bnd, rows; -}; - -void main() { - const uint bh = gl_WorkGroupID.y; - const uint b = bh / heads; - const uint h = bh % heads; - const uint r0 = bh * dk; - const uint base = bh * 2u * dk * dk; // A 在 [base, base+dk²),B 紧随 - const uint tid = gl_LocalInvocationID.x; - - // 1) 初始化 A/B(零 或 A0/B0 行块 h) - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - float va = 0.0, vb = 0.0; - if (has_state == 1u) { - va = NN_RD(A0[(h * dk + a) * dk + c]); - vb = NN_RD(B0[(h * dk + a) * dk + c]); - } - St[base + idx] = va; - St[base + dk * dk + idx] = vb; - } - barrier(); - - // 2) 逐 t 顺序扫描 + 读出 - for (uint t = 0u; t < seq; ++t) { - if (causal == 1u) { - uint reset = 0u; - if (has_bnd == 1u) - reset = (NN_RD(Bnd[b * seq + t]) != 0.0) ? 1u : 0u; - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - const uint Aofs = base + idx, Bofs = base + dk * dk + idx; - if (reset == 1u) { St[Aofs] = 0.0; St[Bofs] = 0.0; } - St[Aofs] += NN_RD(K[(r0 + a) * seq + t]) * NN_RD(K[(r0 + c) * seq + t]); - St[Bofs] += NN_RD(V[(r0 + a) * seq + t]) * NN_RD(K[(r0 + c) * seq + t]); - } - } else { - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - const uint Aofs = base + idx, Bofs = base + dk * dk + idx; - St[Aofs] += NN_RD(K[(r0 + a) * seq + t]) * NN_RD(K[(r0 + c) * seq + t]); - St[Bofs] += NN_RD(V[(r0 + a) * seq + t]) * NN_RD(K[(r0 + c) * seq + t]); - } - } - barrier(); - - // num = B·q, Aq = A·q, B^T·R(第 tid 行,j/i 串行) - if (tid < dk) { - float num = 0.0, aq = 0.0; - for (uint j = 0u; j < dk; ++j) { - const float pj = NN_RD(P[(r0 + j) * seq + t]); - num += St[base + dk * dk + tid * dk + j] * pj; - aq += St[base + tid * dk + j] * pj; - } - float btr = 0.0; - for (uint i = 0u; i < dk; ++i) - btr += St[base + dk * dk + i * dk + tid] * NN_RD(R[(r0 + i) * seq + t]); - Out[(r0 + tid) * seq + t] = NN_WR(num); // [0) B·P - Out[(rows + r0 + tid) * seq + t] = NN_WR(aq); // [1) A·P - Out[(2u * rows + r0 + tid) * seq + t] = NN_WR(btr); // [2) B^T·R - } - barrier(); - - // [3) s = q·(A·q) [4) r = R·(B·q):线程 0 全量归约 - if (tid == 0u) { - float s = 0.0, r = 0.0; - for (uint j = 0u; j < dk; ++j) { - float aqj = 0.0, numj = 0.0; - const float pj = NN_RD(P[(r0 + j) * seq + t]); - for (uint l = 0u; l < dk; ++l) { - const float pl = NN_RD(P[(r0 + l) * seq + t]); - aqj += St[base + j * dk + l] * pl; - numj += St[base + dk * dk + j * dk + l] * pl; - } - s += pj * aqj; - r += NN_RD(R[(r0 + j) * seq + t]) * numj; - } - for (uint i = 0u; i < dk; ++i) - Out[(3u * rows + r0 + i) * seq + t] = NN_WR(s); // [3) 头内逐行重复 - for (uint i = 0u; i < dk; ++i) - Out[(4u * rows + r0 + i) * seq + t] = NN_WR(r); // [4) 头内逐行重复 - } - barrier(); - } -} \ No newline at end of file diff --git a/shaders/scan_suffix_outer.comp b/shaders/scan_suffix_outer.comp deleted file mode 100644 index 6ffc7f7..0000000 --- a/shaders/scan_suffix_outer.comp +++ /dev/null @@ -1,88 +0,0 @@ -// ── scan_suffix_outer.comp ──────────────────────────────────────────── -// RLA 后缀扫描原语(backward pass 2): -// causal=1: S_i = Σ_{t≥i, 同文档} D_t(i+1 为文档起点时先清零) -// causal=0: S_i = D_i(Layer 已把全集梯度沿 seq 广播) -// D (B*H*dk², seq), X/Y (B*H*dk, seq) -// 输出 (B*H*3*dk, seq):[0) S·X [1) S·Y [2) S^T·Y -// 1 workgroup/头,256 线程;S 驻 shared 64×65≈16.6KB -// ───────────────────────────────────────────────────────────────────── -#version 450 -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2f,同 scan_prefix:输入输出均 -// half、shared/算术 float;仅需 storageBuffer16BitAccess)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufD { NN_ETYPE D[]; }; -layout(std430, binding = 1) readonly buffer BufX { NN_ETYPE X[]; }; -layout(std430, binding = 2) readonly buffer BufY { NN_ETYPE Y[]; }; -layout(std430, binding = 3) readonly buffer BufBnd { NN_ETYPE Bnd[]; }; -layout(std430, binding = 4) writeonly buffer BufOut { NN_ETYPE Out[]; }; - -layout(push_constant) uniform Push { - uint dk, heads, seq, causal, has_bnd, rows; -}; - -const uint PAD = 65u; -shared float sS[64][PAD]; -shared float s_xv[64], s_yv[64]; - -void main() { - const uint bh = gl_WorkGroupID.y; - const uint b = bh / heads; - const uint r0 = bh * dk; - const uint dbase = bh * dk * dk; - const uint tid = gl_LocalInvocationID.x; - - // 初始化 S 为零 - for (uint idx = tid; idx < dk * dk; idx += 256u) - sS[idx / dk][idx % dk] = 0.0; - barrier(); - - for (uint i = seq; i-- > 0u;) { - if (causal == 1u) { - uint reset = 0u; - if (has_bnd == 1u && i + 1u < seq) - reset = (NN_RD(Bnd[b * seq + i + 1u]) != 0.0) ? 1u : 0u; - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - if (reset == 1u) sS[a][c] = 0.0; - sS[a][c] += NN_RD(D[(dbase + a * dk + c) * seq + i]); - } - } else { - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - sS[a][c] = NN_RD(D[(dbase + a * dk + c) * seq + i]); - } - } - if (tid < dk) { - s_xv[tid] = NN_RD(X[(r0 + tid) * seq + i]); - s_yv[tid] = NN_RD(Y[(r0 + tid) * seq + i]); - } - barrier(); - - if (tid < dk) { - float sx = 0.0, sy = 0.0, syt = 0.0; - for (uint c = 0u; c < dk; ++c) { - sx += sS[tid][c] * s_xv[c]; - sy += sS[tid][c] * s_yv[c]; - } - for (uint r = 0u; r < dk; ++r) - syt += sS[r][tid] * s_yv[r]; - Out[(r0 + tid) * seq + i] = NN_WR(sx); // [0) S·X - Out[(rows + r0 + tid) * seq + i] = NN_WR(sy); // [1) S·Y - Out[(2u * rows + r0 + tid) * seq + i] = NN_WR(syt); // [2) S^T·Y - } - barrier(); - } -} diff --git a/shaders/scan_suffix_outer_gen.comp b/shaders/scan_suffix_outer_gen.comp deleted file mode 100644 index 41b66e8..0000000 --- a/shaders/scan_suffix_outer_gen.comp +++ /dev/null @@ -1,85 +0,0 @@ -// ── scan_suffix_outer_gen.comp ─────────────────────────────────────────── -// RLA 后缀扫描原语(**通用 d_k** 版本,dk 无 64 上限): -// d_k > 64 时后端走本 shader,S 运行态驻全局显存 scratch(B*H*dk*dk), -// 共享内存仅用于线程间同步、O(1),对任意 dk 安全。 -// 语义与 scan_suffix_outer.comp 完全一致: -// causal=1: S_i = Σ_{t≥i,同文档} D_t(i+1 为文档起点时先清零) -// causal=0: S_i = D_i -// D (B*H*dk², seq), X/Y (B*H*dk, seq) -// 输出 (B*H*3*dk, seq):[0) S·X [1) S·Y [2) S^T·Y -// 1 workgroup/头,256 线程。性能弱于共享内存版本,优先保证正确性(任意 dk)。 -// ───────────────────────────────────────────────────────────────────────── -#version 450 -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2f:输入输出均 half;scratch -// St 是引擎按 f32 分配的 R/W 中间缓冲 → 保持 float。仅需 16bit 存储)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#define NN_ETYPE float16_t -#define NN_RD(x) float(x) -// f16 写出的 RNE 舍入(同 matmul_tiled:OpFConvert 舍入未钉死,pack 按 RNE) -#include "nn_f16_rne.glsl" -#define NN_WR(w) float16_t(nn_f16_rne(w)) -#else -#define NN_ETYPE float -#define NN_RD(x) (x) -#define NN_WR(x) (x) -#endif -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufD { NN_ETYPE D[]; }; -layout(std430, binding = 1) readonly buffer BufX { NN_ETYPE X[]; }; -layout(std430, binding = 2) readonly buffer BufY { NN_ETYPE Y[]; }; -layout(std430, binding = 3) readonly buffer BufBnd { NN_ETYPE Bnd[]; }; -layout(std430, binding = 4) buffer BufSt { float St[]; }; // R/W scratch (B*H*dk*dk) -layout(std430, binding = 5) writeonly buffer BufOut { NN_ETYPE Out[]; }; - -layout(push_constant) uniform Push { - uint dk, heads, seq, causal, has_bnd, rows; -}; - -void main() { - const uint bh = gl_WorkGroupID.y; - const uint b = bh / heads; - const uint r0 = bh * dk; - const uint dbase = bh * dk * dk; - const uint base = bh * dk * dk; - const uint tid = gl_LocalInvocationID.x; - - // 初始化 S 为零 - for (uint idx = tid; idx < dk * dk; idx += 256u) - St[base + idx] = 0.0; - barrier(); - - for (uint i = seq; i-- > 0u;) { - if (causal == 1u) { - uint reset = 0u; - if (has_bnd == 1u && i + 1u < seq) - reset = (NN_RD(Bnd[b * seq + i + 1u]) != 0.0) ? 1u : 0u; - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - if (reset == 1u) St[base + idx] = 0.0; - St[base + idx] += NN_RD(D[(dbase + a * dk + c) * seq + i]); - } - } else { - for (uint idx = tid; idx < dk * dk; idx += 256u) { - const uint a = idx / dk, c = idx % dk; - St[base + idx] = NN_RD(D[(dbase + a * dk + c) * seq + i]); - } - } - barrier(); - - if (tid < dk) { - float sx = 0.0, sy = 0.0, syt = 0.0; - for (uint c = 0u; c < dk; ++c) { - sx += St[base + tid * dk + c] * NN_RD(X[(r0 + c) * seq + i]); - sy += St[base + tid * dk + c] * NN_RD(Y[(r0 + c) * seq + i]); - } - for (uint r = 0u; r < dk; ++r) - syt += St[base + r * dk + tid] * NN_RD(Y[(r0 + r) * seq + i]); - Out[(r0 + tid) * seq + i] = NN_WR(sx); // [0) S·X - Out[(rows + r0 + tid) * seq + i] = NN_WR(sy); // [1) S·Y - Out[(2u * rows + r0 + tid) * seq + i] = NN_WR(syt); // [2) S^T·Y - } - barrier(); - } -} \ No newline at end of file diff --git a/shaders/scatter_add.comp b/shaders/scatter_add.comp deleted file mode 100644 index 4eb7e4f..0000000 --- a/shaders/scatter_add.comp +++ /dev/null @@ -1,124 +0,0 @@ -// ── scatter_add.comp ────────────────────────────────────────────────────── -// 纯原语:按行索引原子累加梯度(scatter_add_rows) -// -// 语义: -// dst: (vocab, D) — 读写,原地累加 -// indices: (num,) — 行索引(float 编码的整数) -// grad: (num, D) — 待累加梯度 -// -// dst[indices[i]][d] += grad[i][d] -// -// 使用 CAS 循环实现 float atomicAdd,兼容所有 Vulkan 设备。 -// 越界索引被跳过(不修改 dst)。 -// -// ⚠ 已知确定性偏差(见 docs/contributors/pitfalls.md 坑 19): -// CAS 循环的执行顺序取决于 GPU 调度,同一行多个梯度值的累加顺序 -// 跨 run 不一致 → 浮点舍入结果有 ~1e-7 相对误差的 run 间抖动。 -// 这是铁律 8(确定性)的**已文档化例外**:偏差仅为舍入噪声,被 -// 优化器噪声吸收,不影响训练稳定性;CPU 参考路径(CpuEngine:: -// scatter_add_rows)保持顺序累加、逐字节确定。若未来需要 GPU 路径 -// 也逐字节可复现,需改为"排序 + 顺序累加"或"分桶 + 确定性归约"。 -// -// 绑定布局: -// binding 0: dst (readwrite, coherent — float 数据以 uint 存储用于原子操作) -// binding 1: indices (readonly) -// binding 2: grad (readonly) -// -// Push Constants(12 bytes): -// uint vocab — dst 行数 -// uint D — 列宽 -// uint num — indices 元素个数 -// ───────────────────────────────────────────────────────────────────────── - -#version 450 - -// -DNN_SHADER_F16=1 → f16 存储变体(Phase C2): -// dst = f16 存储 → 每 uint word 打包相邻 2 个 half(字节布局 half_idx*2 -// = word*4 + sel*2,与线性 half 序一致),CAS 对整 word、并发更新靠重试 -// 收敛(每次重读 word 最新值;同 word 两 half 被不同线程写也正确)。 -// 每次成功写按 f16 舍入——dst 本就是 f16 存储,与"物化 f32 副本累加后 -// 一次性落回"相比只多中间舍入(梯度累加通常每行 <2 次命中,可忽略)。 -// grad = float16_t 直读(f16→f32 精确);indices 保持 float——行号 -// (vocab 8208 > 2048)超出 f16 整数精度,禁转(同 gather 先例)。 -// 只需 storageBuffer16BitAccess(无 f16 算术/原子)。 -#if defined(NN_SHADER_F16) -#extension GL_EXT_shader_16bit_storage : require -#include "nn_f16_rne.glsl" -#endif - -layout(local_size_x = 256) in; - -// dst 以 uint 声明,用于 atomicCompSwap 操作 -// f32 变体:float 数据位模式直接存储为 uint(IEEE 754 位兼容) -// f16 变体:每 uint 打包 2 个 half(见上) -layout(std430, binding = 0) coherent buffer BufDst { uint dst[]; }; -layout(std430, binding = 1) readonly buffer BufIndices { float indices[]; }; -#if defined(NN_SHADER_F16) -layout(std430, binding = 2) readonly buffer BufGrad { float16_t grad[]; }; -#else -layout(std430, binding = 2) readonly buffer BufGrad { float grad[]; }; -#endif - -layout(push_constant) uniform PushConstants { - uint vocab; - uint D; - uint num; -}; - -// ── 基于 CAS 的 float atomicAdd ────────────────────────────────────────── -// 兼容所有 Vulkan 1.0 设备,无需 VK_EXT_shader_atomic_float 扩展。 -void atomicAddFloat(uint idx, float val) -{ - uint old_v = dst[idx]; - uint new_v; - while (true) - { - new_v = floatBitsToUint(uintBitsToFloat(old_v) + val); - uint prev = atomicCompSwap(dst[idx], old_v, new_v); - if (prev == old_v) break; - old_v = prev; - } -} - -#if defined(NN_SHADER_F16) -// f16 打包版:hidx = 线性 half 下标;word = hidx>>1(含 2 个 half), -// sel = hidx&1(word 内半区)。CAS 覆盖整 word → 与相邻 half 的并发更新 -// 经重试收敛。 -void atomicAddHalf(uint hidx, float val) -{ - uint word = hidx >> 1; - uint sel = hidx & 1u; - uint old_v = dst[word]; - uint new_v; - while (true) - { - vec2 h = unpackHalf2x16(old_v); - if (sel == 0u) h.x += val; else h.y += val; - // 写回前逐 lane RNE 预舍入:packHalf2x16 的舍入未被规范钉死,实测 - // NVIDIA 驱动向零截断(Khronos Vulkan-Docs #1825) - new_v = packHalf2x16(vec2(nn_f16_rne(h.x), nn_f16_rne(h.y))); - uint prev = atomicCompSwap(dst[word], old_v, new_v); - if (prev == old_v) break; - old_v = prev; - } -} -#endif - -void main() -{ - const uint flat_idx = gl_GlobalInvocationID.x; - const uint total = num * D; - if (flat_idx >= total) return; - - const uint i = flat_idx / D; // 第 i 个索引 - const uint d = flat_idx % D; // 第 d 列 - - const uint row_idx = uint(indices[i]); - if (row_idx >= vocab) return; // 越界跳过 - -#if defined(NN_SHADER_F16) - atomicAddHalf(row_idx * D + d, float(grad[flat_idx])); -#else - atomicAddFloat(row_idx * D + d, grad[flat_idx]); -#endif -} diff --git a/shaders/transpose.comp b/shaders/transpose.comp deleted file mode 100644 index 89e9341..0000000 --- a/shaders/transpose.comp +++ /dev/null @@ -1,117 +0,0 @@ -// ── transpose.comp ──────────────────────────────────────────────────────── -// 纯原语:矩阵转置 A (R, C) → OUT (C, R) -// -// out[c * R + r] = in[r * C + c] -// -// ── 64×64 分块 + 8×8 WG 砖块化 dispatch ─────────────────────────────────── -// dispatch 形态的依据:转置 kernel 的瓶颈是**并发 WG 的访存足迹聚集度**—— -// 不是 bank 冲突(全程零冲突)、不是段宽、不是共享读写方向(落位交换两方向 -// 代价对称)。转置输出行 = 输入列(block_c),若按行线性派发,并发 WG 的 -// 写散布在整块缓冲(5244² ≈ 180MB,每行仅写 256B、行距 21KB)→ DRAM/L2 -// 写合并恶化;砖块化让连续 64 个 WG 填满一砖、两侧足迹收敛到 ~1MB, -// 5244² 实测 0.942→0.798ms(−15%;wall,warmup 30 best-of-10)。 -// -// 砖块化:dispatch 网格 = (8, 8, n_bricks),bz → 砖坐标,(x,y) → 砖内 -// tile。硬件按 x 最快线性调度 ⇒ 连续 64 个 WG 恰好填满一个 8×8 砖: -// 读侧每个砖行 = 64 输入行 × 8 段×256B 连铺;写侧每个砖列同构 -// → 两侧足迹 ~1MB 聚集(16×8 砖实测持平 8×8,取对称 8×8)。 -// -// 设计要点: -// - 64×64 tile:段宽 256B、每线程 16 元素摊薄 barrier; -// - 共享落位 tile[c_loc][r_loc](阶段 2 连续读 + 连续写全局;阶段 1 散布写): -// 两方向代价对称,取阶段 2 全连续; -// - bank 零冲突:行 padding 65,gcd(65,32)=1 ⇒ 列向跨 lane 步进命中 32 个 -// 不同 bank;行向天然连续(阶段 1 散布写 addr=65x+常量 → bank=x+常量); -// - 免逐元素除模(砖坐标一处 div/mod 每 WG 一次);越界 tile 早退 + 元素守卫 -// 读入/写出同构(r= cols || tile_r * 64u >= rows) return; - - const uint x = gl_LocalInvocationID.x; // 0..31 → C 方向(输入列/输出行) - const uint y = gl_LocalInvocationID.y; // 0..7 → R 方向片段 - const uint block_c = tile_c * 64u; - const uint block_r = tile_r * 64u; - - // ── 阶段 1:coalesced 读入(16 元素/线程)→ 散布写共享 ──────────── - for (uint a = 0u; a < 8u; ++a) - { - const uint r_loc = y + a * 8u; // 覆盖行 0..63 - const uint r = block_r + r_loc; - if (r >= rows) continue; - for (uint h = 0u; h < 2u; ++h) - { - const uint c_loc = x + h * 32u; // 覆盖列 0..63 - const uint c = block_c + c_loc; - if (c < cols) - tile[c_loc][r_loc] = in_data[r * cols + c]; - } - } - - barrier(); - - // ── 阶段 2:连续读共享 → coalesced 写全局(转置落位已在阶段 1 完成) - // out[c*R + r]:跨 lane(x)r 连续 → 128B 合并,连续两次 = 256B 同行段 - for (uint b = 0u; b < 8u; ++b) - { - const uint c_loc = y + b * 8u; // 覆盖列 0..63 → 输出行 - const uint c = block_c + c_loc; - if (c >= cols) continue; - for (uint g = 0u; g < 2u; ++g) - { - const uint r_loc = x + g * 32u; // 覆盖行 0..63 → 输出列 - const uint r = block_r + r_loc; - if (r < rows) - out_data[c * rows + r] = tile[c_loc][r_loc]; - } - } -} diff --git a/shaders/trit_pack.comp b/shaders/trit_pack.comp deleted file mode 100644 index b7322e3..0000000 --- a/shaders/trit_pack.comp +++ /dev/null @@ -1,53 +0,0 @@ -// ── trit_pack.comp — 三值权重打包原语(P2 / F3)──────────────────────────── -// 语义:W (rows,k) f32 + τ (rows) → 打包字 (rows, wpr),wpr = (k+15)/16。 -// 布局(与 microsoft/BitNet 的 gpu/pack_weight.py 同款位宽与码值): -// 每权重 2 bit、4 权重/字节、码值 = 三值 + 2 ∈ {1,2,3}(0 码不用); -// 每个 32-bit 字装 16 个权重,低位对应行内更靠前的权重: -// 码(j) = (word >> 2j) & 3 -// 尾部位(k 之外)填码 2(= 三值 0)—— 码 0 无定义,误读尾部会得到 -2。 -// 布局的唯一差别:不做 BitNet CUDA 侧的 32-bit interleave 置换(那是为 -// coalesced 访问做的重排,解码等价)。 -// -// 本 shader 只做"编码"这一件事(不解码、不乘激活):解码在 matmul_q.comp。 -// ───────────────────────────────────────────────────────────────────────── -#version 450 -layout(local_size_x = 256) in; - -layout(std430, binding = 0) readonly buffer BufW { float W[]; }; // latent (rows*k) -layout(std430, binding = 1) readonly buffer BufT { float T[]; }; // τ (rows) -layout(std430, binding = 2) writeonly buffer BufP { uint P[]; }; // 打包字 - -layout(push_constant) uniform Push { - uint rows; - uint k; - uint wpr; // ceil(k/16) -}; - -// 码 = 三值 + 2:w > +0.5τ → 3;w < -0.5τ → 1;否则 2 -uint code_of(float w, float half_tau) -{ - if (w > half_tau) return 3u; - if (w < -half_tau) return 1u; - return 2u; -} - -void main() -{ - const uint total = rows * wpr; - const uint stride = gl_NumWorkGroups.x * 256u; - for (uint idx = gl_GlobalInvocationID.x; idx < total; idx += stride) - { - const uint row = idx / wpr; - const uint wi = idx % wpr; - const float half_tau = 0.5f * T[row]; - const uint k0 = wi * 16u; - uint word = 0u; - for (uint j = 0u; j < 16u; ++j) - { - const uint kk = k0 + j; - const uint code = (kk < k) ? code_of(W[row * k + kk], half_tau) : 2u; - word |= (code << (2u * j)); - } - P[idx] = word; - } -} From e08c927f8ff7f1b8d5086fa9900426b4d2a39e06 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Thu, 5 Nov 2026 18:07:46 +0800 Subject: [PATCH 03/16] =?UTF-8?q?build(P0):=20=E7=BB=9F=E4=B8=80=20BMI=20?= =?UTF-8?q?=E4=B8=8E=E7=9B=AE=E6=A0=87=E7=BC=96=E8=AF=91=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E2=80=94=E2=80=94NN=5FCOMMON=5FCOMPILE=5FOPTIONS=20=E5=8D=95?= =?UTF-8?q?=E4=B8=80=E7=9C=9F=E6=BA=90=20+=20nn=5Fadd=5Fmodule=20=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E9=95=9C=E5=83=8F=E7=BB=84=E8=A3=85=EF=BC=88=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=96=B9=20flags/=E9=85=8D=E7=BD=AE=20flags/Threads?= =?UTF-8?q?=20=E5=B1=9E=E6=80=A7/NN=5FGPU=5FENGINE=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=9C=9F=E6=B1=82=E5=80=BC/5=20=E6=9D=A1=20-I=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E6=B2=BB=20module=20config=20mismatch=EF=BC=88docs/in?= =?UTF-8?q?ternal/35=20P0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 66 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d832481..195bcf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,15 +125,21 @@ if(MSVC) # train_state);Linux 默认 8 MB。统一抬到 8 MB,与 Linux 口径对齐。 add_link_options(/STACK:8388608) else() - add_compile_options(-fno-exceptions -Wall -Wextra -Wpedantic -Werror) + # ── NN_COMMON_COMPILE_OPTIONS:公共编译选项**单一真源**(P0/D1)──────── + # 目录级 add_compile_options 与 nn_add_module 的 BMI 自定义命令共用本 + # 变量——两处曾各写一份,任何一侧漂移都会让 PCM 加载报 + # "configuration mismatch"(-pthread / 工具链 flags / -D 缺失均属此类)。 + set(NN_COMMON_COMPILE_OPTIONS + -fno-exceptions -Wall -Wextra -Wpedantic -Werror) # GCC 15.2 在 -O3 + PCH 下对 std::optional / std::unique_ptr 的**内联析构** # 报 -Wmaybe-uninitialized 假阳性(诊断里的内存位置全是 ;实测 # src/layer_lifetime_test.cpp 的 `unique_ptr` 析构路径,成员已由 # make_unique/默认初始化,无真实未初始化读取)。Clang 不产生该告警,因此 # 只对 GNU 关闭此子告警;-Wuninitialized 保持开启。 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - add_compile_options(-Wno-maybe-uninitialized) + list(APPEND NN_COMMON_COMPILE_OPTIONS -Wno-maybe-uninitialized) endif() + add_compile_options(${NN_COMMON_COMPILE_OPTIONS}) endif() # ── LTO(链接期优化,2026-11:Clang thin LTO)────────────────────────── @@ -142,6 +148,9 @@ endif() # 的组合噪音)。NN_ENABLE_LTO=OFF 可关闭(A/B 或排障)。需 clang + lld。 option(NN_ENABLE_LTO "Clang thin LTO(需 LLD)" ON) if(NN_ENABLE_LTO AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT MSVC) + # -flto=thin 进 NN_COMMON(P0/D1):消费方目标与 BMI 同源。--precompile + # 只做前端、不 codegen,实测该 flag 可接受(拒绝时再排除并注释)。 + list(APPEND NN_COMMON_COMPILE_OPTIONS -flto=thin) add_compile_options(-flto=thin) add_link_options(-flto=thin -fuse-ld=lld) message(STATUS "LTO: Clang thin LTO + LLD 已启用(NN_ENABLE_LTO=OFF 可关闭)") @@ -158,9 +167,10 @@ endif() # ── 编译器特有选项 ─────────────────────────────────────────────────────── # -fexperimental-library 仅 Clang 识别;GCC 不识别会产生警告 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - add_compile_options(-fexperimental-library) # f16 矩阵乘法(MatrixT)无原生 x86 SIMD → 优化器无法向量化是预期行为 # (§7.2 兼容路径:f32 参考计算 + f16 舍入),抑制 -Wpass-failed 避免 -Werror 下编译失败 + list(APPEND NN_COMMON_COMPILE_OPTIONS -fexperimental-library -Wno-pass-failed) + add_compile_options(-fexperimental-library) add_compile_options(-Wno-pass-failed) endif() # C++ 标准库由编译器默认决定(见文件开头的说明),此处不再显式指定 -stdlib。 @@ -541,25 +551,53 @@ function(nn_add_module modname source) endif() set(_cpp "nn.${modname}") set(_bmi "${NN_LIB_BINARY_DIR}/modules/${_cpp}.pcm") - # 编译标志 = 库内目标同款(目录级 add_compile_options 不作用于自定义命令, - # 必须在此显式列出;优化标志与消费方同档,避免"同实体两种 -O"的度量失真; - # 探针门保持与 nn_core INTERFACE 同值,见该变量注释)。 + # ── BMI 编译标志 = 目标编译命令**镜像**(P0/D1,docs/internal/35 §0.2)── + # 目录级 add_compile_options 不作用于自定义命令,须在此显式重放消费方 + # 命令行的每一段;任何缺项都会让 PCM 加载报 configuration mismatch: + # ① 调用方/工具链全局 flags(NDK --target=、-stdlib= 等) + # ② 构建类型 flags(CMAKE_CXX_FLAGS_:Release -O3 -DNDEBUG / Debug -g) + # ③ 公共选项单一真源(-fno-exceptions/-W*/-flto=thin/-fexperimental-library…) + # ④ 优化档(DEBUG/RELEASE_FLAGS,与消费方目录级 genex 同值)+ 方言标准 + # ⑤ Threads::Threads 的 INTERFACE_COMPILE_OPTIONS 原样镜像(消费方经 + # nn_core 拿到的就是这份;NDK 等环境为 -pthread,glibc≥2.34 为空—— + # 判据直接取目标属性,与消费方严格同源) + # ⑥ 探针门 + GPU 引擎宏(nn_gpu 在 add_subdirectory(engine) 才创建, + # 晚于模块注册 → 用生成期求值的 TARGET_EXISTS 规避顺序问题) + # ⑦ include 路径 = nn_core 的 5 条,顺序亦一致 + # 刻意不加 NN_USE_MODULES(任何头都不读它,见 D2/0.2 注)。 + set(_flags "") + separate_arguments(_user_flags UNIX_COMMAND "${CMAKE_CXX_FLAGS}") + list(APPEND _flags ${_user_flags}) + if(CMAKE_BUILD_TYPE) + # cache 变量名是大写(CMAKE_CXX_FLAGS_RELEASE),直接嵌套 + # ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}} 会拼出 CMAKE_CXX_FLAGS_Release → 空。 + string(TOUPPER "${CMAKE_BUILD_TYPE}" _cfg_type) + separate_arguments(_cfg_flags UNIX_COMMAND + "${CMAKE_CXX_FLAGS_${_cfg_type}}") + list(APPEND _flags ${_cfg_flags}) + unset(_cfg_type) + endif() + list(APPEND _flags ${NN_COMMON_COMPILE_OPTIONS}) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_opt ${DEBUG_FLAGS}) else() set(_opt ${RELEASE_FLAGS}) endif() - set(_flags - "-std=c++${CMAKE_CXX_STANDARD}" - -fno-exceptions -Wall -Wextra -Wpedantic -Werror - -fexperimental-library -Wno-pass-failed - ${_opt} - -I "${NN_LIB_SOURCE_DIR}/include" - -I "${NN_LIB_SOURCE_DIR}/engine/abstraction/include" - -I "${NN_LIB_SOURCE_DIR}/engine/abstraction/include/neuralnet.cpp") + list(APPEND _flags ${_opt} "-std=c++${CMAKE_CXX_STANDARD}") + get_target_property(_thread_opts Threads::Threads INTERFACE_COMPILE_OPTIONS) + if(_thread_opts) + list(APPEND _flags ${_thread_opts}) + endif() if(NN_ENABLE_PROBES) list(APPEND _flags -DNN_ENABLE_PROBES=1) endif() + list(APPEND _flags "$<$:-DNN_GPU_ENGINE=1>") + list(APPEND _flags + -I "${NN_LIB_SOURCE_DIR}/engine/abstraction/include/neuralnet.cpp" + -I "${NN_LIB_SOURCE_DIR}/engine/abstraction/include" + -I "${NN_LIB_SOURCE_DIR}/include" + -I "${NN_LIB_SOURCE_DIR}/engine/engine/include/neuralnet.cpp" + -I "${NN_LIB_SOURCE_DIR}/engine/engine/include") set(_file_deps "") foreach(d IN LISTS NNM_DEPS) list(APPEND _flags "-fmodule-file=nn.${d}=${NN_LIB_BINARY_DIR}/modules/nn.${d}.pcm") From 9cd86a9a79cde0ffd8da1e2f1e27477a1c1c697a Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Thu, 5 Nov 2026 18:14:56 +0800 Subject: [PATCH 04/16] =?UTF-8?q?refactor(P1):=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E6=97=A0=E6=9D=A1=E4=BB=B6=E5=8C=96=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=88=A0=20NN=5FENABLE=5FMODULES/NN=5FMODULES=5FENABL?= =?UTF-8?q?ED=20=E7=BC=96=E8=AF=91=E5=99=A8=E9=97=A8=E4=B8=8E=20NN=5FUSE?= =?UTF-8?q?=5FMODULES=20=E5=AE=8F=E4=BC=A0=E6=92=AD=EF=BC=8Csrc/=208=20?= =?UTF-8?q?=E5=A4=84=E5=8F=8C=E8=BD=A8=E6=94=B9=E6=97=A0=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=20import=EF=BC=88tokenizer=5Fconsistency=20=E7=BB=8F=20tokeniz?= =?UTF-8?q?er=5Ftest=20=E6=96=87=E6=9C=AC=20include=20=E8=A6=86=E7=9B=96?= =?UTF-8?q?=EF=BC=8C=E6=97=A0=E9=9C=80=E5=8D=95=E7=8B=AC=E6=8E=A5=E7=BA=BF?= =?UTF-8?q?=EF=BC=89=EF=BC=88docs/internal/35=20P1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 43 +++++++++--------------------- src/dataset_convert.cpp | 4 --- src/dataset_gen.cpp | 4 --- src/dataset_integration_test.cpp | 5 ---- src/dataset_test.cpp | 4 --- src/nnvocab_test.cpp | 4 --- src/tokenizer_consistency_test.cpp | 4 --- src/tokenizer_infer.cpp | 4 --- src/tokenizer_train.cpp | 4 --- 9 files changed, 12 insertions(+), 64 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 195bcf2..32c4160 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -470,26 +470,14 @@ endfunction() # `export import nn.<依赖域>` 让消费方只 import 最顶域即可连带拿到依赖域 # 名字(C++ 模块的 import **不可传递**,必须显式 re-export)。 # 约束(实证,README §4): -# · 宏(NN_TRY / NN_UNWRAP / NN_ASSERT_* 等)**不可导出** → 消费者双轨: -# `import nn.<域>` + 按需 `#include ` 宏头; -# · STL 留在全局片段 → 消费者自带用到的 STL 头; -# · 仅 GNU 驱动 Clang(GCC / cl.exe / clang-cl 无等效显式 -fmodule-file -# 姿势,NN_MODULES_ENABLED 自动为 OFF,消费方回落 include 分支)。 +# · 宏(NN_TRY / NN_UNWRAP / NN_ASSERT_* 等)**不可导出** → 消费者 +# `import nn.<域>` + 按需 `#include ` 宏岛头; +# · STL 留在全局片段 → 消费者自带用到的 STL 头。 # 构建 = 显式 BMI 自定义命令(clang --precompile)+ -fmodule-file 经 # INTERFACE 目标传播,不开 CMake 模块扫描(见文件头注释的成本说明)。 -# 嵌入消费方默认 OFF(PROJECT_IS_TOP_LEVEL = ON 才默认开)。 -option(NN_ENABLE_MODULES - "Build granular C++20 modules (nn.core/nn.kvrecord/nn.dataset/nn.tokenizer) \ - and switch leaf consumers to import (clang only; embedders default OFF)" - ${PROJECT_IS_TOP_LEVEL}) -if(NN_ENABLE_MODULES AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT MSVC) - set(NN_MODULES_ENABLED ON) -else() - set(NN_MODULES_ENABLED OFF) -endif() -message(STATUS "C++ modules (import nn.): " - "${NN_MODULES_ENABLED} (NN_ENABLE_MODULES=${NN_ENABLE_MODULES}, " - "compiler=${CMAKE_CXX_COMPILER_ID})") +# **无条件构建(P1/D2)**:不设开关、不做编译器识别——BMI 姿势固定为 +# GNU 驱动 Clang(--precompile / -fmodule-file=),不支持的编译器直接 +# 构建报错(用户定调,见 docs/internal/35 D2)。 # ── 各域头文件闭包(nn_add_module 的 DEPENDS;跨域头改动触发对应 BMI 重编)── # 闭包 = 模块单元文本包含的全部 nn 头(含传递)。宁多勿少:多列只会偶发 @@ -542,13 +530,10 @@ set(NN_MOD_TOKENIZER_HEADERS # 产出: # · /modules/nn.<短名>.pcm —— BMI(自定义命令,DEPENDS 头闭包 + 依赖 BMI) # · nn_mod_<短名>(INTERFACE)—— 消费方 target_link_libraries 即得 -# -fmodule-file=nn.<短名>=(依赖域的标志经 INTERFACE 传递)+ NN_USE_MODULES +# -fmodule-file=nn.<短名>=(依赖域的标志经 INTERFACE 传递) # · nn_<短名>_bmi(custom target)—— BMI 产物聚合,nn_use_modules 挂到消费方 function(nn_add_module modname source) cmake_parse_arguments(NNM "" "" "DEPS;HEADERS" ${ARGN}) - if(NOT NN_MODULES_ENABLED) - return() - endif() set(_cpp "nn.${modname}") set(_bmi "${NN_LIB_BINARY_DIR}/modules/${_cpp}.pcm") # ── BMI 编译标志 = 目标编译命令**镜像**(P0/D1,docs/internal/35 §0.2)── @@ -564,7 +549,7 @@ function(nn_add_module modname source) # ⑥ 探针门 + GPU 引擎宏(nn_gpu 在 add_subdirectory(engine) 才创建, # 晚于模块注册 → 用生成期求值的 TARGET_EXISTS 规避顺序问题) # ⑦ include 路径 = nn_core 的 5 条,顺序亦一致 - # 刻意不加 NN_USE_MODULES(任何头都不读它,见 D2/0.2 注)。 + # 刻意不加任何模块消费宏(宏开关已随 P1 删除;任何头都不读它,见 D2 注)。 set(_flags "") separate_arguments(_user_flags UNIX_COMMAND "${CMAKE_CXX_FLAGS}") list(APPEND _flags ${_user_flags}) @@ -616,7 +601,6 @@ function(nn_add_module modname source) ) add_library(nn_mod_${modname} INTERFACE) target_compile_options(nn_mod_${modname} INTERFACE "-fmodule-file=${_cpp}=${_bmi}") - target_compile_definitions(nn_mod_${modname} INTERFACE NN_USE_MODULES) foreach(d IN LISTS NNM_DEPS) target_link_libraries(nn_mod_${modname} INTERFACE nn_mod_${d}) endforeach() @@ -629,14 +613,11 @@ function(nn_add_module modname source) endfunction() # ── 消费方接入:nn_use_modules(<目标> <模块...>) ────────────────────────── -# 链接模块 INTERFACE(-fmodule-file 链 + NN_USE_MODULES 宏)并把目标从共享 -# PCH 候选剔除——模块消费方的 TU 同时带 nn.hpp 的 PCH 与模块 BMI 时,同一 -# 实体两处声明(PCH 按 NN_HAS_VULKAN 编、BMI 不带)存在分歧风险,叶子目标 -# 编译量小,直接各自解析更稳。 +# 链接模块 INTERFACE(-fmodule-file 链)并把目标从共享 PCH 候选剔除—— +# 模块消费方的 TU 同时带 nn.hpp 的 PCH 与模块 BMI 时,同一实体两处声明 +# (PCH 按 NN_HAS_VULKAN 编、BMI 不带)存在分歧风险,叶子目标编译量小, +# 直接各自解析更稳。 function(nn_use_modules target) - if(NOT NN_MODULES_ENABLED) - return() - endif() foreach(m IN LISTS ARGN) if(TARGET nn_mod_${m}) target_link_libraries(${target} PRIVATE nn_mod_${m}) diff --git a/src/dataset_convert.cpp b/src/dataset_convert.cpp index 52ae60d..398975d 100644 --- a/src/dataset_convert.cpp +++ b/src/dataset_convert.cpp @@ -10,11 +10,7 @@ // 本工具只做格式转换,不做训练相关任何事。 // ───────────────────────────────────────────────────────────────────────── -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#endif #include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) #include diff --git a/src/dataset_gen.cpp b/src/dataset_gen.cpp index 60342f6..ccba6e4 100644 --- a/src/dataset_gen.cpp +++ b/src/dataset_gen.cpp @@ -10,11 +10,7 @@ // [--license ] [--source ] // ───────────────────────────────────────────────────────────────────────── -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#endif #include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) #include diff --git a/src/dataset_integration_test.cpp b/src/dataset_integration_test.cpp index 2255564..d633681 100644 --- a/src/dataset_integration_test.cpp +++ b/src/dataset_integration_test.cpp @@ -22,12 +22,7 @@ #include #include -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#include -#endif int g_fail = 0; diff --git a/src/dataset_test.cpp b/src/dataset_test.cpp index 3523bbf..e3bb390 100644 --- a/src/dataset_test.cpp +++ b/src/dataset_test.cpp @@ -24,11 +24,7 @@ #include #include -#ifdef NN_USE_MODULES import nn.dataset; -#else -#include -#endif int g_fail = 0; diff --git a/src/nnvocab_test.cpp b/src/nnvocab_test.cpp index 396869b..6e755a8 100644 --- a/src/nnvocab_test.cpp +++ b/src/nnvocab_test.cpp @@ -16,11 +16,7 @@ #include #include -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#endif namespace { diff --git a/src/tokenizer_consistency_test.cpp b/src/tokenizer_consistency_test.cpp index 0d9e3f1..d5bd886 100644 --- a/src/tokenizer_consistency_test.cpp +++ b/src/tokenizer_consistency_test.cpp @@ -20,11 +20,7 @@ #include #include -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#endif #include "test_common.hpp" using nn::BPETokenizer; diff --git a/src/tokenizer_infer.cpp b/src/tokenizer_infer.cpp index e74cea8..e216cc0 100644 --- a/src/tokenizer_infer.cpp +++ b/src/tokenizer_infer.cpp @@ -9,11 +9,7 @@ // 支持交互模式和命令行模式。 // ───────────────────────────────────────────────────────────────────────── -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#endif #include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) #include diff --git a/src/tokenizer_train.cpp b/src/tokenizer_train.cpp index 1694399..3039a31 100644 --- a/src/tokenizer_train.cpp +++ b/src/tokenizer_train.cpp @@ -9,11 +9,7 @@ // BPE 能学会合并常见中文词组(更适合小模型) // ───────────────────────────────────────────────────────────────────────── -#ifdef NN_USE_MODULES import nn.tokenizer; -#else -#include -#endif #include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) #include From a8169ecc4dc577ff6939285e5120cf9313ab1283 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Thu, 5 Nov 2026 18:43:34 +0800 Subject: [PATCH 05/16] =?UTF-8?q?refactor(P2):=20=E5=AE=8F=E5=B2=9B?= =?UTF-8?q?=E5=A4=B4=E8=90=BD=E5=9C=B0=E2=80=94=E2=80=94engine=20=E6=8A=BD?= =?UTF-8?q?=E8=B1=A1=E5=B1=82=E6=96=B0=E5=A2=9E=20nn=5Ferror=5Fmacros.hpp?= =?UTF-8?q?=EF=BC=88=E5=AE=8F=E6=AE=B5=E5=94=AF=E4=B8=80=E7=9C=9F=E6=BA=90?= =?UTF-8?q?=EF=BC=89=EF=BC=8Ccore=5Ferrors/core=5Fassert=20=E7=BB=8F?= =?UTF-8?q?=E5=85=B6=E6=8F=90=E4=BE=9B=E5=AE=8F=EF=BC=9Bsrc/=2068=20?= =?UTF-8?q?=E4=B8=AA=E5=AE=8F=E4=BD=BF=E7=94=A8=E6=96=87=E4=BB=B6=E6=98=BE?= =?UTF-8?q?=E5=BC=8F=20#include=20=EF=BC=887=20?= =?UTF-8?q?=E5=A4=84=20core=5Ferrors=20=E7=9B=B4=E6=8E=A5=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E6=94=B9=E4=B8=BA=E5=B2=9B=E5=A4=B4=EF=BC=8Cassert=5F?= =?UTF-8?q?test=20=E5=90=8C=EF=BC=89=EF=BC=9Bsubmodule=20=E6=8C=87?= =?UTF-8?q?=E9=92=88=2010e2755=20=E9=94=81=E5=AE=9A=EF=BC=88docs/internal/?= =?UTF-8?q?35=20P2=20=E9=AA=8C=E6=94=B6=E9=97=A8=20C=EF=BC=9Actest=2081/81?= =?UTF-8?q?=EF=BC=8Cerror=5Fmacro/assert=20=E4=B8=89=E6=B5=8B=E7=BB=BF?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine | 2 +- src/adam_int8_test.cpp | 1 + src/adam_quant_test.cpp | 1 + src/assert_always_test.cpp | 1 + src/assert_test.cpp | 2 +- src/attn_consistency_test.cpp | 1 + src/attn_gradcheck.cpp | 1 + src/attn_test.cpp | 1 + src/attn_w_batch_test.cpp | 1 + src/batch_source_test.cpp | 2 +- src/batchnorm_test.cpp | 1 + src/bitnet_struct_test.cpp | 1 + src/chat_prompt_test.cpp | 1 + src/cnn_smoke_test.cpp | 1 + src/cnn_test.cpp | 1 + src/conv2d_gradcheck.cpp | 1 + src/cross_attention_test.cpp | 1 + src/dataset_convert.cpp | 2 +- src/dataset_gen.cpp | 2 +- src/dataset_samples_test.cpp | 1 + src/doc_attn_e2e_test.cpp | 1 + src/error_macro_test.cpp | 2 +- src/expr_matmul_test.cpp | 1 + src/f16_cpu_probe.cpp | 1 + src/f16_writeback_probe.cpp | 1 + src/fused_gpu_test.cpp | 1 + src/gpt_checkpoint_test.cpp | 1 + src/gpt_gradcheck.cpp | 1 + src/gpu_batch_scope_test.cpp | 1 + src/gpu_stability_probe.cpp | 1 + src/int8_act_test.cpp | 1 + src/layer_bench.cpp | 1 + src/layer_lifetime_test.cpp | 1 + src/maxpool_gradcheck.cpp | 1 + src/mnist_infer.cpp | 1 + src/mnist_train.cpp | 1 + src/optimizer_state_test.cpp | 1 + src/rapt_checkpoint_test.cpp | 1 + src/rapt_gradcheck.cpp | 1 + src/rapt_smoke_test.cpp | 1 + src/rapt_test.cpp | 1 + src/rl_experience_test.cpp | 1 + src/rl_gpu_smoke_test.cpp | 1 + src/rl_grpo_test.cpp | 1 + src/rl_output_test.cpp | 1 + src/rl_pipeline_test.cpp | 1 + src/rl_policy_test.cpp | 1 + src/rl_ppo_test.cpp | 1 + src/rl_training_integration_test.cpp | 1 + src/rmsnorm_gradcheck.cpp | 1 + src/run_driver_test.cpp | 2 +- src/sampling_test.cpp | 1 + src/session_checkpoint_test.cpp | 1 + src/softmax_gradcheck.cpp | 1 + src/swiglu_gradcheck.cpp | 1 + src/t1_58_test.cpp | 1 + src/tensor_expr_test.cpp | 1 + src/tensor_precision_test.cpp | 1 + src/tensor_schema_test.cpp | 1 + src/ternary_gemm_test.cpp | 1 + src/test_common.hpp | 1 + src/text_infer.cpp | 1 + src/text_train.cpp | 1 + src/text_train_modules_test.cpp | 1 + src/text_trainer_test.cpp | 1 + src/tokenizer_infer.cpp | 2 +- src/tokenizer_train.cpp | 2 +- src/training_group_session_test.cpp | 1 + src/training_session_test.cpp | 1 + src/write_epoch_test.cpp | 1 + 70 files changed, 70 insertions(+), 9 deletions(-) diff --git a/engine b/engine index d2dd5d1..10e2755 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit d2dd5d191d616c98e5ab5e6e7bebbd9f8a053f64 +Subproject commit 10e2755d0fa47f9365c93afeb7d9353165bebb18 diff --git a/src/adam_int8_test.cpp b/src/adam_int8_test.cpp index a73830c..73fcda2 100644 --- a/src/adam_int8_test.cpp +++ b/src/adam_int8_test.cpp @@ -15,6 +15,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/adam_quant_test.cpp b/src/adam_quant_test.cpp index da275e0..228f3b4 100644 --- a/src/adam_quant_test.cpp +++ b/src/adam_quant_test.cpp @@ -14,6 +14,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/assert_always_test.cpp b/src/assert_always_test.cpp index fcfa96d..6b7f969 100644 --- a/src/assert_always_test.cpp +++ b/src/assert_always_test.cpp @@ -33,6 +33,7 @@ #include #endif +#include #include // Matrix / Tensor / sample_next_token_ #include "test_common.hpp" diff --git a/src/assert_test.cpp b/src/assert_test.cpp index 12d1e08..d08482e 100644 --- a/src/assert_test.cpp +++ b/src/assert_test.cpp @@ -18,7 +18,7 @@ #include #endif -#include +#include int g_fail = 0; diff --git a/src/attn_consistency_test.cpp b/src/attn_consistency_test.cpp index dce04be..14baaf8 100644 --- a/src/attn_consistency_test.cpp +++ b/src/attn_consistency_test.cpp @@ -15,6 +15,7 @@ // --layers N Transformer 层数(默认 2) // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/attn_gradcheck.cpp b/src/attn_gradcheck.cpp index 64704a0..6fb63ea 100644 --- a/src/attn_gradcheck.cpp +++ b/src/attn_gradcheck.cpp @@ -8,6 +8,7 @@ // 用法:attn_gradcheck [--gpu] [--batch N] [--seq N] [--tol ] [--doc] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/attn_test.cpp b/src/attn_test.cpp index 8951d16..66424cc 100644 --- a/src/attn_test.cpp +++ b/src/attn_test.cpp @@ -5,6 +5,7 @@ // MHA 双向语义探针(fold 掩码回归钉子)+ CPU/GPU 一致性 // ─────────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/attn_w_batch_test.cpp b/src/attn_w_batch_test.cpp index cb56ecd..19b9c5e 100644 --- a/src/attn_w_batch_test.cpp +++ b/src/attn_w_batch_test.cpp @@ -14,6 +14,7 @@ // 退出码:0 = 全部一致;2 = CPU 引擎创建失败;77 = GPU 不可用(ctest SKIP); // 3 = CPU/GPU 不一致。 // ──────────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/batch_source_test.cpp b/src/batch_source_test.cpp index c762ec9..e363366 100644 --- a/src/batch_source_test.cpp +++ b/src/batch_source_test.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include static int g_fail = 0; #define CHECK(cond, msg) \ diff --git a/src/batchnorm_test.cpp b/src/batchnorm_test.cpp index e98c880..9885f6f 100644 --- a/src/batchnorm_test.cpp +++ b/src/batchnorm_test.cpp @@ -14,6 +14,7 @@ // 用法: batchnorm_test [--gpu] [--tol ] (--gpu 不可用 → 77 = skip) // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/bitnet_struct_test.cpp b/src/bitnet_struct_test.cpp index 63348ab..b6198a1 100644 --- a/src/bitnet_struct_test.cpp +++ b/src/bitnet_struct_test.cpp @@ -18,6 +18,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/chat_prompt_test.cpp b/src/chat_prompt_test.cpp index 97d83a1..9b602d5 100644 --- a/src/chat_prompt_test.cpp +++ b/src/chat_prompt_test.cpp @@ -18,6 +18,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(未构建 text_infer 时的 CLI 半边) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/cnn_smoke_test.cpp b/src/cnn_smoke_test.cpp index 2deed03..70c13ec 100644 --- a/src/cnn_smoke_test.cpp +++ b/src/cnn_smoke_test.cpp @@ -10,6 +10,7 @@ // 用法:cnn_smoke_test [--gpu] [--steps N] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/cnn_test.cpp b/src/cnn_test.cpp index 88946c7..a7d3120 100644 --- a/src/cnn_test.cpp +++ b/src/cnn_test.cpp @@ -4,6 +4,7 @@ // (Conv2D 的单层参考比对仍在 layer_gradcheck_test 的 conv2d 段) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/conv2d_gradcheck.cpp b/src/conv2d_gradcheck.cpp index 14fc7a2..fb580f9 100644 --- a/src/conv2d_gradcheck.cpp +++ b/src/conv2d_gradcheck.cpp @@ -10,6 +10,7 @@ // 用法:conv2d_gradcheck [--gpu] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/cross_attention_test.cpp b/src/cross_attention_test.cpp index 54a0bf8..bb2dd3b 100644 --- a/src/cross_attention_test.cpp +++ b/src/cross_attention_test.cpp @@ -10,6 +10,7 @@ // [5] 数值梯度检查(grad_query 与 grad_memory 各一轮,CPU 小规模) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/dataset_convert.cpp b/src/dataset_convert.cpp index 398975d..3a25982 100644 --- a/src/dataset_convert.cpp +++ b/src/dataset_convert.cpp @@ -11,7 +11,7 @@ // ───────────────────────────────────────────────────────────────────────── import nn.tokenizer; -#include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) +#include #include #include diff --git a/src/dataset_gen.cpp b/src/dataset_gen.cpp index ccba6e4..1b0353d 100644 --- a/src/dataset_gen.cpp +++ b/src/dataset_gen.cpp @@ -11,7 +11,7 @@ // ───────────────────────────────────────────────────────────────────────── import nn.tokenizer; -#include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) +#include #include #include diff --git a/src/dataset_samples_test.cpp b/src/dataset_samples_test.cpp index 8192010..131c924 100644 --- a/src/dataset_samples_test.cpp +++ b/src/dataset_samples_test.cpp @@ -11,6 +11,7 @@ // [6] 断点状态段的旧键 `dataset_windows` 兼容读(rc0 → rc0.1) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/doc_attn_e2e_test.cpp b/src/doc_attn_e2e_test.cpp index 23ab942..909cc2f 100644 --- a/src/doc_attn_e2e_test.cpp +++ b/src/doc_attn_e2e_test.cpp @@ -18,6 +18,7 @@ // 用法:doc_attn_test [--gpu] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/error_macro_test.cpp b/src/error_macro_test.cpp index 280ffcb..78cb23b 100644 --- a/src/error_macro_test.cpp +++ b/src/error_macro_test.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #ifndef _WIN32 #include diff --git a/src/expr_matmul_test.cpp b/src/expr_matmul_test.cpp index 88cdb0a..0533c4e 100644 --- a/src/expr_matmul_test.cpp +++ b/src/expr_matmul_test.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include diff --git a/src/f16_cpu_probe.cpp b/src/f16_cpu_probe.cpp index 2813f30..459314f 100644 --- a/src/f16_cpu_probe.cpp +++ b/src/f16_cpu_probe.cpp @@ -3,6 +3,7 @@ // 编译:CMake app 目标 f16_cpu_probe;亦可纯 CPU 独立编译(不经 AOT 管线): // clang++ -std=c++26 -O1 -fno-exceptions -fexperimental-library -Wno-pass-failed -Iinclude src/f16_cpu_probe.cpp -o build/f16_cpu_probe.exe // 每个阶段执行前打印标记(flush),崩溃时能直接看到断点位置。 +#include #include #include #include diff --git a/src/f16_writeback_probe.cpp b/src/f16_writeback_probe.cpp index d8ce128..9e15bf8 100644 --- a/src/f16_writeback_probe.cpp +++ b/src/f16_writeback_probe.cpp @@ -11,6 +11,7 @@ // 用法:f16_writeback_probe [--gpu[=名称]] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/fused_gpu_test.cpp b/src/fused_gpu_test.cpp index e0d1e6f..05aa864 100644 --- a/src/fused_gpu_test.cpp +++ b/src/fused_gpu_test.cpp @@ -16,6 +16,7 @@ // 用法:fused_gpu_test // ─────────────────────────────────────────────────────────────────────────── +#include #include #include // fold v1 样例(与 scan_exprs 同源 → key 一致) diff --git a/src/gpt_checkpoint_test.cpp b/src/gpt_checkpoint_test.cpp index ae03056..4147817 100644 --- a/src/gpt_checkpoint_test.cpp +++ b/src/gpt_checkpoint_test.cpp @@ -11,6 +11,7 @@ // 4. 容差 tol:重计算路径与基线应在浮点重算误差范围内一致。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/gpt_gradcheck.cpp b/src/gpt_gradcheck.cpp index 94e0f03..5fa15ab 100644 --- a/src/gpt_gradcheck.cpp +++ b/src/gpt_gradcheck.cpp @@ -13,6 +13,7 @@ // 用法:gpt_gradcheck [--gpu] [--layers N] [--tol ] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/gpu_batch_scope_test.cpp b/src/gpu_batch_scope_test.cpp index cebca74..f23f697 100644 --- a/src/gpu_batch_scope_test.cpp +++ b/src/gpu_batch_scope_test.cpp @@ -10,6 +10,7 @@ // 无 Vulkan 构建 / 无 GPU 设备 → 返回 77 = ctest SKIP。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/gpu_stability_probe.cpp b/src/gpu_stability_probe.cpp index 08bc358..3126470 100644 --- a/src/gpu_stability_probe.cpp +++ b/src/gpu_stability_probe.cpp @@ -26,6 +26,7 @@ // 设备选择在首个 GpuBackend::shared() 之前注入 NN_VULKAN_DEVICE。 // ──────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/int8_act_test.cpp b/src/int8_act_test.cpp index 8ed9e8f..01542a0 100644 --- a/src/int8_act_test.cpp +++ b/src/int8_act_test.cpp @@ -20,6 +20,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/layer_bench.cpp b/src/layer_bench.cpp index 6029264..8e0cde1 100644 --- a/src/layer_bench.cpp +++ b/src/layer_bench.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include diff --git a/src/layer_lifetime_test.cpp b/src/layer_lifetime_test.cpp index edea2bf..c7e5f82 100644 --- a/src/layer_lifetime_test.cpp +++ b/src/layer_lifetime_test.cpp @@ -13,6 +13,7 @@ // [7] Layer init 失败回滚与 profile 注入 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/maxpool_gradcheck.cpp b/src/maxpool_gradcheck.cpp index acd5ae7..96f86c8 100644 --- a/src/maxpool_gradcheck.cpp +++ b/src/maxpool_gradcheck.cpp @@ -13,6 +13,7 @@ // 用法:maxpool_gradcheck [--gpu] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/mnist_infer.cpp b/src/mnist_infer.cpp index f57dbeb..8a0989c 100644 --- a/src/mnist_infer.cpp +++ b/src/mnist_infer.cpp @@ -8,6 +8,7 @@ // 引擎选择:--gpu 启用 GpuEngine(需要 Vulkan),否则 CpuEngine。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/mnist_train.cpp b/src/mnist_train.cpp index 5e6804b..b63598c 100644 --- a/src/mnist_train.cpp +++ b/src/mnist_train.cpp @@ -14,6 +14,7 @@ // 架构选择:--arch mlp|transformer(默认 mlp,从已保存模型 resume 时自动识别) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/optimizer_state_test.cpp b/src/optimizer_state_test.cpp index b0d00c8..9854105 100644 --- a/src/optimizer_state_test.cpp +++ b/src/optimizer_state_test.cpp @@ -14,6 +14,7 @@ // 退出码:0 = 通过,1 = 失败。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rapt_checkpoint_test.cpp b/src/rapt_checkpoint_test.cpp index 734efd5..817b724 100644 --- a/src/rapt_checkpoint_test.cpp +++ b/src/rapt_checkpoint_test.cpp @@ -15,6 +15,7 @@ // ReLULinearAttention 对 checkpoint_mode_ 的响应是否完整。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rapt_gradcheck.cpp b/src/rapt_gradcheck.cpp index b890e86..f669a84 100644 --- a/src/rapt_gradcheck.cpp +++ b/src/rapt_gradcheck.cpp @@ -10,6 +10,7 @@ // doc-aware=2),batch>1 覆盖(铁律 5)由 causal/doc-aware 段保证。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/rapt_smoke_test.cpp b/src/rapt_smoke_test.cpp index b34bbd0..f0a788b 100644 --- a/src/rapt_smoke_test.cpp +++ b/src/rapt_smoke_test.cpp @@ -9,6 +9,7 @@ // 用法:rapt_smoke_test [--steps N] [--gpu] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/rapt_test.cpp b/src/rapt_test.cpp index 1365dc6..14363f3 100644 --- a/src/rapt_test.cpp +++ b/src/rapt_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #define main test_rapt_gradcheck diff --git a/src/rl_experience_test.cpp b/src/rl_experience_test.cpp index d008594..8afac4d 100644 --- a/src/rl_experience_test.cpp +++ b/src/rl_experience_test.cpp @@ -7,6 +7,7 @@ // [4] overall/group normalization(总体标准差、零方差)。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_gpu_smoke_test.cpp b/src/rl_gpu_smoke_test.cpp index 62d915d..02b1255 100644 --- a/src/rl_gpu_smoke_test.cpp +++ b/src/rl_gpu_smoke_test.cpp @@ -5,6 +5,7 @@ // 专门抓 AOT 结构漏登记与 GPU 设备域错误。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_grpo_test.cpp b/src/rl_grpo_test.cpp index 83ccc87..3cb7998 100644 --- a/src/rl_grpo_test.cpp +++ b/src/rl_grpo_test.cpp @@ -7,6 +7,7 @@ // [4] 单组、少样本组与缺少 old_log_prob 的失败路径。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_output_test.cpp b/src/rl_output_test.cpp index 6655a2c..91cbf7f 100644 --- a/src/rl_output_test.cpp +++ b/src/rl_output_test.cpp @@ -8,6 +8,7 @@ // [5] ModelSpec output_schema/output_packing 往返,旧键缺失兼容。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_pipeline_test.cpp b/src/rl_pipeline_test.cpp index 5aaaf97..9416189 100644 --- a/src/rl_pipeline_test.cpp +++ b/src/rl_pipeline_test.cpp @@ -7,6 +7,7 @@ // [4] hooks 顺序、buffer clear 策略、非法 sampler/config 错误。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_policy_test.cpp b/src/rl_policy_test.cpp index 9552f97..6ef0c6c 100644 --- a/src/rl_policy_test.cpp +++ b/src/rl_policy_test.cpp @@ -8,6 +8,7 @@ // [5] objective 的无上下文 Loss 入口明确报错。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_ppo_test.cpp b/src/rl_ppo_test.cpp index 5d0629b..df38fcf 100644 --- a/src/rl_ppo_test.cpp +++ b/src/rl_ppo_test.cpp @@ -8,6 +8,7 @@ // [5] 缺少 old_log_prob / old_value 明确报错。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rl_training_integration_test.cpp b/src/rl_training_integration_test.cpp index cad1563..16640d7 100644 --- a/src/rl_training_integration_test.cpp +++ b/src/rl_training_integration_test.cpp @@ -5,6 +5,7 @@ // 与 policy/value 分支参数实际移动。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/rmsnorm_gradcheck.cpp b/src/rmsnorm_gradcheck.cpp index 66c3b7e..aa8924a 100644 --- a/src/rmsnorm_gradcheck.cpp +++ b/src/rmsnorm_gradcheck.cpp @@ -11,6 +11,7 @@ // 用法:rmsnorm_gradcheck [--gpu] [--tol ] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/run_driver_test.cpp b/src/run_driver_test.cpp index 625a06d..6d75a4e 100644 --- a/src/run_driver_test.cpp +++ b/src/run_driver_test.cpp @@ -21,8 +21,8 @@ #include #include +#include #include -#include #include #include diff --git a/src/sampling_test.cpp b/src/sampling_test.cpp index d8f1336..5a9677f 100644 --- a/src/sampling_test.cpp +++ b/src/sampling_test.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include "test_common.hpp" diff --git a/src/session_checkpoint_test.cpp b/src/session_checkpoint_test.cpp index fc722f9..a71efea 100644 --- a/src/session_checkpoint_test.cpp +++ b/src/session_checkpoint_test.cpp @@ -12,6 +12,7 @@ // [7] 失败路径无半提交:Strict 校验失败后模型参数逐位不变(issue #43) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/softmax_gradcheck.cpp b/src/softmax_gradcheck.cpp index 0b845b5..fa2d902 100644 --- a/src/softmax_gradcheck.cpp +++ b/src/softmax_gradcheck.cpp @@ -7,6 +7,7 @@ // 用法:softmax_gradcheck [--gpu] [--rows N] [--cols N] [--tol ] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/swiglu_gradcheck.cpp b/src/swiglu_gradcheck.cpp index eef595b..cb21507 100644 --- a/src/swiglu_gradcheck.cpp +++ b/src/swiglu_gradcheck.cpp @@ -12,6 +12,7 @@ // 用法:swiglu_gradcheck [--tol ] // ───────────────────────────────────────────────────────────────────────── +#include #include #include diff --git a/src/t1_58_test.cpp b/src/t1_58_test.cpp index b15abf4..f090075 100644 --- a/src/t1_58_test.cpp +++ b/src/t1_58_test.cpp @@ -18,6 +18,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/tensor_expr_test.cpp b/src/tensor_expr_test.cpp index f74b09a..d96d6b5 100644 --- a/src/tensor_expr_test.cpp +++ b/src/tensor_expr_test.cpp @@ -11,6 +11,7 @@ // 任意表达式未必被扫描覆盖,故只在 CPU 上验证。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include "test_common.hpp" // M1 访问收口:upload(宿主 Matrix 进引擎张量) diff --git a/src/tensor_precision_test.cpp b/src/tensor_precision_test.cpp index 9e566f2..32e02a7 100644 --- a/src/tensor_precision_test.cpp +++ b/src/tensor_precision_test.cpp @@ -11,6 +11,7 @@ // 6. engine.reshape 保持精度与数据(D10:reshape 移入引擎;元素数不匹配报错) // 7. shape_str 包含精度信息 // 8. f16 存储数据经下载升位后与宿主参考逐元素一致(f16→f32 精确无损) +#include #include #include #include // RV-02:形状溢出负例(size_t 上限) diff --git a/src/tensor_schema_test.cpp b/src/tensor_schema_test.cpp index e8bc376..4bb2951 100644 --- a/src/tensor_schema_test.cpp +++ b/src/tensor_schema_test.cpp @@ -8,6 +8,7 @@ // [5] NamedTensorSet / GradientBundle 共享 schema 语义。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/ternary_gemm_test.cpp b/src/ternary_gemm_test.cpp index 5daaec4..a71cf71 100644 --- a/src/ternary_gemm_test.cpp +++ b/src/ternary_gemm_test.cpp @@ -21,6 +21,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备,或引擎无打包能力) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/test_common.hpp b/src/test_common.hpp index ad9f0cd..f66fb7a 100644 --- a/src/test_common.hpp +++ b/src/test_common.hpp @@ -23,6 +23,7 @@ #pragma once +#include #include #include #include diff --git a/src/text_infer.cpp b/src/text_infer.cpp index 2d85f13..48d9151 100644 --- a/src/text_infer.cpp +++ b/src/text_infer.cpp @@ -7,6 +7,7 @@ // 引擎选择:--gpu 启用 GpuEngine(需要 Vulkan),否则 CpuEngine。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/text_train.cpp b/src/text_train.cpp index 96dfce2..cd837c9 100644 --- a/src/text_train.cpp +++ b/src/text_train.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include diff --git a/src/text_train_modules_test.cpp b/src/text_train_modules_test.cpp index a3a3369..1849eb8 100644 --- a/src/text_train_modules_test.cpp +++ b/src/text_train_modules_test.cpp @@ -19,6 +19,7 @@ // 退出码:0 = 通过,1 = 失败。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/text_trainer_test.cpp b/src/text_trainer_test.cpp index 700b5a3..fd7ad39 100644 --- a/src/text_trainer_test.cpp +++ b/src/text_trainer_test.cpp @@ -10,6 +10,7 @@ // [5] 前置条件:batch=0 / log_interval=0 / 配方与 Session 不一致 → Result 错误 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/tokenizer_infer.cpp b/src/tokenizer_infer.cpp index e216cc0..23355dd 100644 --- a/src/tokenizer_infer.cpp +++ b/src/tokenizer_infer.cpp @@ -10,7 +10,7 @@ // ───────────────────────────────────────────────────────────────────────── import nn.tokenizer; -#include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) +#include #include #include diff --git a/src/tokenizer_train.cpp b/src/tokenizer_train.cpp index 3039a31..c14a51a 100644 --- a/src/tokenizer_train.cpp +++ b/src/tokenizer_train.cpp @@ -10,7 +10,7 @@ // ───────────────────────────────────────────────────────────────────────── import nn.tokenizer; -#include // 宏(NN_TRY/NN_EXIT 等不可随模块导出,双轨) +#include #include #include diff --git a/src/training_group_session_test.cpp b/src/training_group_session_test.cpp index b9e0389..f9ca033 100644 --- a/src/training_group_session_test.cpp +++ b/src/training_group_session_test.cpp @@ -7,6 +7,7 @@ // [4] group move 后 Session/optimizer TensorRef 仍可训练。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/training_session_test.cpp b/src/training_session_test.cpp index 31caf7c..4fb6fa4 100644 --- a/src/training_session_test.cpp +++ b/src/training_session_test.cpp @@ -9,6 +9,7 @@ // [6] 未知优化器 / 未绑定引擎 → Result 错误 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include diff --git a/src/write_epoch_test.cpp b/src/write_epoch_test.cpp index f6c4638..58321fd 100644 --- a/src/write_epoch_test.cpp +++ b/src/write_epoch_test.cpp @@ -17,6 +17,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include From cd92ab12e86542778477b86323b3ebf05b4f1ea2 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Thu, 5 Nov 2026 23:35:06 +0800 Subject: [PATCH 06/16] =?UTF-8?q?feat(p3):=20JIT=20=E9=A2=84=E7=83=AD?= =?UTF-8?q?=E6=8E=A5=E7=BA=BF=EF=BC=88D5=EF=BC=89+=20AOT=20=E6=94=B6?= =?UTF-8?q?=E9=9B=86=E9=93=BE=E4=B8=BB=E4=BB=93=E4=BE=A7=E6=B8=85=E7=90=86?= =?UTF-8?q?=20+=20=E5=9B=9E=E5=BD=92=E9=94=81=E4=B8=8E=E5=A6=A5=E5=8D=8F?= =?UTF-8?q?=E5=8F=B0=E8=B4=A6=EF=BC=88docs/internal/35=20P3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D5 运行期:新增 cli/cli_jit_warmup.hpp(model_warmup 封装,CPU 跳过、失败 NN_EXIT);text_train/mnist_train/mnist_infer/text_infer 四入口接线 + 各自 --no-warmup 逃生阀(text_train 走 TrainConfig::jit_warmup) - D5 构建期:新增 nn_warmup 目标(MLP/CNN/Transformer/GPT/RAPT 五架构代表性 shape 过 GPU forward → kernels.nnk 落 --out/可执行目录,随发布分发) - 实测:mnist 冷 1331ms→热 148ms、text_train 冷 1665ms→热 535ms;strict+屏蔽 kernelgen 下预生成缓存命中(warmup 10ms、训练完成)= D6 端到端实证 - D4 主仓侧:删 src/expr_fuse_shapes.hpp(expr_fuse_test 已随 .so 化迁至 engine,无引用死文件);CMake/层头/测试中承诺 AOT 收集的注释改为 JIT 现实口径 - 回归锁:fused_gpu_test 增 cast_into 奇数元素覆盖(f16 缓冲 4B 对齐根因)与 matmul 形状矩阵(尾链/转置/f16 变体);fused_gpu_test 直连后端形态改工厂消费方形态(NN_HAS_VULKAN 遗留守卫) - 台账:新增 docs/internal/36-compromise-ledger.md(C1-C5 妥协式设计,门 G 前必须清零)并登记 internal/README 索引;submodule 指针 → 818a63a - 验收门 D:ctest 81/81;expr_gpu/ce_fusion/kernel_cache/jit_demo 全绿 --- CMakeLists.txt | 46 +-- docs/internal/36-compromise-ledger.md | 28 ++ docs/internal/README.md | 1 + engine | 2 +- include/neuralnet.cpp/cli/cli_jit_warmup.hpp | 46 +++ .../neuralnet.cpp/cli/text_train_config.hpp | 10 + include/neuralnet.cpp/compute_layer.hpp | 2 +- .../neuralnet.cpp/compute_layer_attention.hpp | 71 +---- include/neuralnet.cpp/compute_layer_mlp.hpp | 10 +- include/neuralnet.cpp/compute_layer_rapt.hpp | 5 +- .../neuralnet.cpp/compute_layer_softmax.hpp | 2 +- include/neuralnet.cpp/compute_loss.hpp | 10 - include/neuralnet.cpp/compute_optimizer.hpp | 39 ++- .../compute_position_encoding.hpp | 8 +- include/neuralnet.cpp/model_container.hpp | 4 +- src/bitnet_struct_test.cpp | 2 +- src/expr_fold_test.cpp | 6 +- src/expr_fuse_shapes.hpp | 87 ------ src/f16_precision_test.cpp | 2 +- src/fused_gpu_test.cpp | 265 +++++++++++++++--- src/mnist_infer.cpp | 16 ++ src/mnist_train.cpp | 17 ++ src/nn_warmup.cpp | 176 ++++++++++++ src/optimizer_test.cpp | 2 +- src/tensor_expr_test.cpp | 2 +- src/ternary_gemm_test.cpp | 3 +- src/text_infer.cpp | 16 ++ src/text_train.cpp | 12 + 28 files changed, 638 insertions(+), 252 deletions(-) create mode 100644 docs/internal/36-compromise-ledger.md create mode 100644 include/neuralnet.cpp/cli/cli_jit_warmup.hpp delete mode 100644 src/expr_fuse_shapes.hpp create mode 100644 src/nn_warmup.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 32c4160..393420c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ option(NN_BUILD_APPS "Build neuralnet.cpp application/bench executables" # ── GPU(Vulkan)开关:消费方在自己工程里一行控制 NN_HAS_VULKAN ─────────── # AUTO(默认)= 探测到 Vulkan+glslc 就启用,否则纯 CPU(本仓历史行为) # ON = 强制要求,缺 Vulkan 或 glslc 直接配置失败(fail-fast) -# OFF = 完全跳过:不探测 Vulkan、不生成 shader、不编 scan_exprs +# OFF = 完全跳过:不探测 Vulkan(AOT 收集链已随 P3 删除,35 号) # 嵌入方写法:在 add_subdirectory **之前** set(NN_ENABLE_GPU OFF)(或 -D)。 set(NN_ENABLE_GPU "AUTO" CACHE STRING "GPU support: AUTO (detect) / ON (require) / OFF (disable)") set_property(CACHE NN_ENABLE_GPU PROPERTY STRINGS AUTO ON OFF) @@ -46,10 +46,10 @@ if(NOT NN_ENABLE_GPU MATCHES "^(AUTO|ON|OFF)$") endif() # ── 本仓源/构建根(CACHE,全局可见)───────────────────────────────────── -# CMake 函数体在**调用方作用域**求值:消费方调用 nn_enable_gpu_fusion 时, -# CMAKE_SOURCE_DIR/CMAKE_BINARY_DIR 会解析到消费方的目录(实测报错/路径错)。 -# 因此把本仓的根路径烘焙进 CACHE(INTERNAL 隐含 FORCE,每次配置刷新), -# 函数体内一律引用这两个变量,与调用方是谁无关。 +# CMake 函数体在**调用方作用域**求值:历史上的 nn_enable_gpu_fusion(已随 +# P3 删除,35 号 D4)在消费方作用域展开时 CMAKE_SOURCE_DIR 会解析错目录。 +# 根路径仍烘焙进 CACHE(INTERNAL 隐含 FORCE),nn_add_module 等一律引用 +# 这两个变量,与调用方是谁无关。 set(NN_LIB_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "neuralnet.cpp source root (stable under add_subdirectory)") set(NN_LIB_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE INTERNAL @@ -269,10 +269,11 @@ find_package(Threads REQUIRED) # GCC 14+ 和 Clang (使用 libstdc++) 都需要链接实验性库才能使用 std::stacktrace。 # GCC 使用 stdc++exp,Clang 可能需要 stdc++exp 或 stdc++_libbacktrace。 # 探测一次,成功后通过 nn_core INTERFACE 传播给所有目标。 -# ⚠ **只有 `tools/scan_exprs.cpp` 用 std::stacktrace**(库头文件零使用,已 grep -# 确认);嵌入(add_subdirectory)消费方编译头文件根本不需要它。因此 GNU 下 -# 探测失败时:顶层工程仍 FATAL_ERROR(保住既有行为),嵌入方降级为警告 + -# 留空——否则消费方会为了一个用不到的库在配置期被卡死。 +# ⚠ 库头文件零 std::stacktrace 使用(全仓 grep 证实)。历史用户 +# `tools/scan_exprs.cpp` 已随 AOT 收集链删除(P3/D4,35 号)——**本探测 +# 目前无消费者**,保留仅为不改动既有链接口径(nn_core 仍 LINK stdc++exp); +# 后续清理时可整体移除以放宽 GNU 顶层配置的 FATAL 条件。嵌入方失败仍 +# 降级为警告 + 留空。 set(NN_STACKTRACE_FATAL ${PROJECT_IS_TOP_LEVEL}) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -299,7 +300,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif(NN_STACKTRACE_FATAL) message(FATAL_ERROR "std::stacktrace 需要 stdc++exp 或 stdc++_libbacktrace") else() - message(WARNING "std::stacktrace 库未找到(嵌入模式降级:仅 tools/scan_exprs 受影响)") + message(WARNING "std::stacktrace 库未找到(嵌入模式降级;当前无消费者,见上方注释)") set(NN_STACKTRACE_LIB "") endif() endif() @@ -394,11 +395,10 @@ endif() # + **GPU 开启时**的四项附加(在下方 `if(Vulkan_FOUND)` 块内补: # NN_HAS_VULKAN 宏 + generated/ 嵌入头目录 + Vulkan::Vulkan + shader # 生成依赖)。 -# ⚠ 刻意与 nn_core 分离的原因: -# ① `scan_exprs` / 收集器必须保持**无 NN_HAS_VULKAN**(AOT 闭合世界, -# AGENTS §7 铁律),它们只链 nn_core; -# ② 下方这些「编译选项」只该给**真正的库使用者**(库内目标已从本文件的 -# 目录级 add_compile_options 拿到,重复传递会让命令行出现两份相同 flag)。 +# ⚠ 刻意与 nn_core 分离的原因(AOT 收集器已随 P3 删除,35 号 D4——原 ① 的 +# "收集器无 NN_HAS_VULKAN" 约束随之消失,本分离保留): +# 下方这些「编译选项」只该给**真正的库使用者**(库内目标已从本文件的 +# 目录级 add_compile_options 拿到,重复传递会让命令行出现两份相同 flag)。 add_library(neuralnet_cpp INTERFACE) target_link_libraries(neuralnet_cpp INTERFACE nn_core) @@ -434,10 +434,8 @@ add_library(neuralnet::nn ALIAS neuralnet_cpp) # 所有候选目标的编译条件完全相同 → 只编**一份** PCH 供它们复用(REUSE_FROM): # 31 份逐目标 PCH 合计约 780s CPU,共享后只剩 1 份。见文件末尾的落地块。 # 关掉:-DNN_ENABLE_PCH=OFF。 -# ⚠ **收集器目标不套 PCH**:`scan_exprs.cpp` 是在**源文件内**(include 之前) -# 自行 `#define NN_EXPR_SCAN` 的——PCH 会在该宏生效前把 nn.hpp 预编译进 AST, -# 导致 DSL 收集钩子整体失效(注册表空 → 构建硬失败)。故只有经 -# `nn_add_executable` 建的应用/测试目标进入候选。 +# 候选 = 经 `nn_add_executable` 建的应用/测试目标(历史上的 NN_EXPR_SCAN +# 收集器不套 PCH 的约束已随 AOT 收集链删除,35 号 D4)。 option(NN_ENABLE_PCH "Precompile nn.hpp once and reuse it across targets" ON) # ── 便捷函数:统一添加可执行目标 ──────────────────────────────────────── @@ -629,8 +627,8 @@ function(nn_use_modules target) endfunction() # ── 四个叶子域模块(DAG:core → kvrecord → dataset → tokenizer)────────── -# 域边界 = 合并回主仓的四个子仓域;全部不依赖 GPU/AOT 扫描(NN_EXPR_SCAN 的 -# 宏收集无法进模块,engine/layers/model/train 域本轮不模块化,见研究 README)。 +# 域边界 = 合并回主仓的四个子仓域;AOT 宏收集链已随 P3 删除(35 号 D4), +# 其余域的模块化按 35 号 P4 铺开。 nn_add_module(core modules/nn.core.cpp HEADERS ${NN_MOD_CORE_HEADERS}) nn_add_module(kvrecord modules/nn.kvrecord.cpp @@ -706,6 +704,12 @@ if(NN_BUILD_APPS) nn_add_executable(jit_demo src/jit_demo.cpp) nn_use_modules(dataset_convert tokenizer) + # ── 构建期/发布期 JIT 预热目标(P3/D5,docs/internal/35)───────────────── + # 常用架构 × 代表性 shape 过 GPU forward → kernels.nnk(随发布物分发, + # 部署首跑免 kernelgen 现编;与各 app 的 model_warmup/--no-warmup 配套)。 + nn_add_executable(nn_warmup src/nn_warmup.cpp) + list(APPEND NN_APP_TARGETS nn_warmup) + # ── 层吞吐量基准(CPU/Vulkan GPU;--all 或 --layer 指定) ──────── nn_add_executable(layer_bench src/layer_bench.cpp) list(APPEND NN_APP_TARGETS layer_bench) diff --git a/docs/internal/36-compromise-ledger.md b/docs/internal/36-compromise-ledger.md new file mode 100644 index 0000000..7b19c7d --- /dev/null +++ b/docs/internal/36-compromise-ledger.md @@ -0,0 +1,28 @@ +# 36 · 模块迁移妥协式设计台账(最终验收前必须清零) + +> 状态:**现行(随 35 号实施过程滚动登记)**。目标读者:35 号的实施者与验收者。 +> 关联文档:`35-modules-migration-implementation.md`(实施文档本体,只读)。 +> **规则(用户裁定)**:实施过程中为解锁验收门而引入的妥协式绕行**一律登记在本表**; +> 允许阶段性带着它们推进(换取主线进度),但**最终验收(35 §6 门 G)的前提 = +> 本表逐条消灭**——不得转正、不得保留。每消灭一条在状态列记「✅ 已消灭:」; +> **门 G 验收时本表不得存在未消灭行**。 +> +> 判别口径:**根因级修复不是妥协**(如 f16 缓冲 4B 对齐、`composite_kernel_available_` +> 无注册表误判、`cast_into` 奇数尾、BMI 命令镜像、四入口 JIT 兜底本体)——不入本表; +> **凡"根因未收敛就绕开 / A/B 实验残留 / 未评审的行为变更"必须入表**。 + +## 台账 + +| # | 位置 | 妥协内容(为什么是妥协) | 消灭方案 | 消灭时机 | 状态 | +|---|------|--------------------------|----------|----------|------| +| C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | 下钻根因(重点:降级成员派发中 f16 状态 m/v 链的执行序、占位张量跨 step 复用、cast 写回路径),修复后**删除 graph_ok 分支恢复无条件录制**;full-f16 以「图开启 + ctest 绿」回归锁验证 | 最终清理(P6 后、门 G 前) | ⏳ | +| C2 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:begin_expr / end_expr 的 `NN_NO_GRAPH` getenv 旁路 | A/B 排查遗留:整跳录制的逃生阀,代码注释自标"临时" | 删除两处 getenv 分支;与 C1 同组验收(删后 full-f16 仍绿) | 同 C1(一组) | ⏳ | +| C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | 二选一并同步文档:① NN_IRC_TRACE 正式化为非探针门的行为开关(更新 AGENTS/33 号口径)或 ② 恢复 env_flag 并改 AGENTS 注明需 NN_ENABLE_PROBES;**删除 [ir-c][run] 每 kernel 打印** | 最终清理 | ⏳ | +| C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | C1/C2 消灭后重新 A/B:静态签名 JIT vs 边界 cast,以 f16 基准**择优转正**(优则恢复直写;劣则把边界 cast 升格为有据设计并删实验残留形态) | 最终清理(依赖 C1) | ⏳ | +| C5 | `engine/engine/src/kernelgen.cpp`:失败路径诊断 fprintf("GLSL 生成为空" / "shaderc 编译失败") | 排查期新增的输出行为;非掩盖缺陷,但属未评审新增 | **显式裁决**:转正(补设计注记)或删除——不得默默留下 | 最终清理 | ⏳ | + +## 消灭顺序 + +C1+C2 同组(互为 A/B 条件)→ C4 重新实验(依赖 C1 结果)→ C3 → C5。 +消灭 C1/C2 后必须重跑:`ctest --preset dev` 全绿 + `f16_precision_test` 的 +full-f16 用例(图开启、无 NN_NO_GRAPH)逐条留证。 diff --git a/docs/internal/README.md b/docs/internal/README.md index 8e56318..33cae8a 100644 --- a/docs/internal/README.md +++ b/docs/internal/README.md @@ -26,6 +26,7 @@ | 文档 | 何时读 | |------|--------| +| [`36-compromise-ledger.md`](36-compromise-ledger.md) | **模块迁移妥协式设计台账(35 号配套,最终验收门 G 前必须清零)**:为解锁验收门引入的绕行(optimizer=f16 跳录制 / NN_NO_GRAPH 旁路 / 诊断改道 / A/B 残留)逐条登记 + 消灭方案与时机;根因级修复不入表 | | [`00-code-review-remediation.md`](00-code-review-remediation.md) | **2026-10-11 代码审查整改立项台账**:审查发现的问题逐条立项(现象/证据/根因/方案/验收/优先级) | | [`18-roadmap.md`](18-roadmap.md) | **"某件事该不该做"的唯一入口**:未来 6–12 个月方向 + 未完成项**裁定**台账(做 / 条件触发 / 不做)+ 验收口径 + 现状实测;**§4.5 P4 = 持续优化方向**(性能利用率 / 算子性能 / 显存开销 + API 易用性与 Fold 自动化扫描)。**不含执行计划**(见下一行) | | [`27-execution-plan.md`](27-execution-plan.md) | **执行计划(优先级象限)**:把 18 号 §3/§4/§6 与 00 号 RV 台账里当前可动手的项排成"先做哪个"(重要×简单四象限 + 第一步动作 + 串行依赖)——**想知道"下一个先做哪个"读它** | diff --git a/engine b/engine index 10e2755..818a63a 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit 10e2755d0fa47f9365c93afeb7d9353165bebb18 +Subproject commit 818a63a3ea61add7d4eb6788ded7e78e4707d7a0 diff --git a/include/neuralnet.cpp/cli/cli_jit_warmup.hpp b/include/neuralnet.cpp/cli/cli_jit_warmup.hpp new file mode 100644 index 0000000..254938d --- /dev/null +++ b/include/neuralnet.cpp/cli/cli_jit_warmup.hpp @@ -0,0 +1,46 @@ +// ── cli_jit_warmup.hpp — JIT 预热(P3/D5,docs/internal/35)────────────────── +// +// 构建期 AOT 收集链删除后,融合 shader 全靠运行期 JIT(libnn-kernelgen.so) +// 现编并持久化进 .nnk 缓存。本助手在 GPU 上跑一遍**代表性 forward** +// (nn::model_warmup),把该模型用到的表达式全部预编进缓存: +// · 消除训练/推理首步的 JIT 冷启动延迟(后续 = 缓存命中); +// · 配合 `nn_warmup` 构建期目标(常用架构预热产物随发布分发), +// 部署环境首跑可完全不依赖 kernelgen。 +// +// 约定: +// · CPU 引擎直接跳过(模板路径无需预热); +// · `--no-warmup`(各 app 的布尔开关)跳过; +// · 失败由调用方 `NN_EXIT(..., 1, "预热失败: ")` 上抛(不静默降级)。 +// ───────────────────────────────────────────────────────────────────────────── +#pragma once + +#include +#include +#include + +#include "neuralnet.cpp/compute_engine.hpp" +#include "neuralnet.cpp/compute_tensor.hpp" // Device +#include "neuralnet.cpp/model_container.hpp" // nn::model_warmup / Model + +namespace nn::cli +{ + +// 跑一遍代表性 forward(输入形状由调用方按架构构造:值不重要、形状重要—— +// JIT 键只含结构;GPU 才执行)。 +[[nodiscard]] inline Result jit_warmup(Model& model, ComputeEngine& engine, + const Tensor& input, bool enabled) +{ + if (!enabled) return {}; + if (engine.device() == Device::CPU) return {}; + const auto t0 = std::chrono::steady_clock::now(); + NN_TRY(r, model_warmup(model, input)); + (void)r; + const auto ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0) + .count(); + std::cout << "[warmup] JIT 预热完成(" << ms + << " ms,表达式已现编进 .nnk 缓存;--no-warmup 可跳过)\n"; + return {}; +} + +} // namespace nn::cli diff --git a/include/neuralnet.cpp/cli/text_train_config.hpp b/include/neuralnet.cpp/cli/text_train_config.hpp index 1e023a7..e653f55 100644 --- a/include/neuralnet.cpp/cli/text_train_config.hpp +++ b/include/neuralnet.cpp/cli/text_train_config.hpp @@ -155,6 +155,9 @@ void print_text_train_usage(const char* prog) help.section("Batch 录制粒度"); help.opt("--flush-interval ", "每 N 个 Transformer block flush 一次 (默认: 1)"); + help.opt("--no-warmup", + "跳过 JIT 预热(GPU 默认开训前跑一遍代表性 forward,\n" + "表达式预编进 kernels.nnk,消除首步 JIT 冷启动)"); help.note("按层切 batch 缩短 D1 延迟销毁锁窗、拆分大提交防 TDR;\n非阻塞提交后细粒度 flush " "的额外 submit 代价不在关键路径,\n不影响 batch_size 和训练质量"); @@ -178,6 +181,9 @@ struct TrainConfig : nn::TextTrainConfig { // ── 入口相关(不属于训练配方)──────────────────────────────────────── std::string text_path; + // JIT 预热(P3/D5):GPU 上开训前跑一遍代表性 forward(表达式现编进 + // .nnk 缓存)。--no-warmup 逃生;CPU 路径本就跳过。 + bool jit_warmup = true; // 推理模型(.nnmodel,Model 角色):只含推理所需参数(三值层写打包形态)。 std::string save_path = "gpt_model.nnmodel"; std::string resume_path; @@ -595,6 +601,10 @@ TrainConfig parse_text_train_args(int argc, char* argv[]) std::exit(1); } } + else if (arg == "--no-warmup") + { + cfg.jit_warmup = false; + } else if (arg == "--warmup-epochs" && i + 1 < argc) { auto v = nn::parse_number(argv[++i]); diff --git a/include/neuralnet.cpp/compute_layer.hpp b/include/neuralnet.cpp/compute_layer.hpp index eb5d4b4..9dadca2 100644 --- a/include/neuralnet.cpp/compute_layer.hpp +++ b/include/neuralnet.cpp/compute_layer.hpp @@ -3,7 +3,7 @@ // ── compute_layer.hpp — 引擎化计算层(聚合头) ─────────────────────────── // // 按逻辑域拆分为多个 compute_layer_*.hpp 子文件,此文件按依赖顺序聚合, -// 保持对既有代码(nn.hpp / scan_exprs / 测试 / src)的单一入口兼容。 +// 保持对既有代码(nn.hpp / 测试 / src)的单一入口兼容。 // // 架构铁律: // 1. Layer 的 forward/backward 只写一次,通过 ComputeEngine 参数自动适配 diff --git a/include/neuralnet.cpp/compute_layer_attention.hpp b/include/neuralnet.cpp/compute_layer_attention.hpp index 5ff46d6..70877e8 100644 --- a/include/neuralnet.cpp/compute_layer_attention.hpp +++ b/include/neuralnet.cpp/compute_layer_attention.hpp @@ -27,11 +27,11 @@ namespace nn // 注意力 fold 构造(双域旗舰样例):fold attention 输出 O // (S 不物化,online 单遍) // -// 这是**注意力 Layer 自己的表达式文本**(AOT 收集原则:表达式只出现在 -// Layer):本文件是 make_fold_attn_o 构造与 AttnMaskKind 掩码种类的定义 -// 来源,expr_fold.hpp 只含与注意力无关的通用折叠样例。 -// scan_exprs dry-run、fused_gpu/expr_cpu 对拍与 AttentionBase::forward -// 共用本构造 → key 一致。 +// 这是**注意力 Layer 自己的表达式文本**(表达式只出现在 Layer 的既有原则): +// 本文件是 make_fold_attn_o 构造与 AttnMaskKind 掩码种类的定义来源, +// expr_fold.hpp 只含与注意力无关的通用折叠样例。fused_gpu/expr_cpu 对拍 +// 与 AttentionBase::forward 共用本构造 → 结构 key 一致(AOT 收集链已随 +// P3 删除,35 号 D4;GPU 侧由运行期 JIT 现编同一结构)。 // // 数学:O = softmax_masked(Q·Kᵀ) · V_t // 键域每块:S_tile = mm(Q,K)(内层 dk 收缩)→ [掩码] → @@ -52,9 +52,9 @@ namespace expr // 位置偏置(ALiBi)不属于掩码:它由位置编码策略给出,作为独立的第二个维度 // 传入(score_bias)。两者正交 —— 掩码策略不必知道位置编码的存在。 // -// 掩码清单是**单一真源**(X 宏):枚举定义与下方 AOT 自登记表 -// (`#if defined(NN_EXPR_SCAN)` 段,见 make_fold_attn_o 之后)都由它展开 —— -// 新增一种掩码只需在本清单加一项,scan_exprs 与登记表都无需改动。 +// 掩码清单是**单一真源**(X 宏):枚举定义由它展开——新增一种掩码只需在 +// 本清单加一项;AOT 自登记段已随 P3 删除(35 号 D4),GPU 侧 JIT 按结构 +// key 现编,无需登记表。 #define NN_ATTN_MASK_KIND_LIST(X) \ X(Plain) \ X(Causal) \ @@ -215,51 +215,6 @@ enum class AttnMaskKind return s; } -#if defined(NN_EXPR_SCAN) -// ── AOT 自登记锚点(表达式自登记约定:新增掩码/偏置组合不改 scan_exprs)── -// 注意力 fold 由**值构造器**产出(结构取决于 mask / score_bias / kv_ratio 三个 -// 实参),类型层面不编码结构 → `FusedAnchor` 按类型登记覆盖不到;层 -// forward 又是直调 `engine.eval_expr(make_fold_attn_o(...))`、不经 DSL 钩子。 -// 故由本文件(构造与掩码清单的唯一定义来源)在静态初始化期经 `FoldAnchor` -// 自登记**全部**组合(与 compute_loss.hpp 的 CE logsumexp 折叠同一形态): -// 掩码(`NN_ATTN_MASK_KIND_LIST`)× {无偏置, 位置偏置} × {MHA, GQA} -// 其中"无掩码 + 位置偏置"不是本仓会构造的组合(无层路径),故实际为 5×2 = 10 -// 条结构。形状/运行期参数(seq/dk/bh 与 kv_ratio 的具体值)**不进 key** → 一 -// 组代表值即覆盖所有形状(docs/internal/22 §3.2 P2;GQA 的 HeadGroup 视图种类进 -// key,比本身不进 → MHA/GQA 各一条)。 -// 可用 `-DNN_SCAN_NO_ANCHOR` 关掉(与 FusedAnchor 同一 A/B 开关:只影响登记 -// 集合、不改语义)。 -#if !defined(NN_SCAN_NO_ANCHOR) -namespace attn_fold_anchor -{ -inline constexpr AttnMaskKind kMaskKinds[] = { -#define NN_ATTN_MASK_KIND_ITEM(name) AttnMaskKind::name, - NN_ATTN_MASK_KIND_LIST(NN_ATTN_MASK_KIND_ITEM) -#undef NN_ATTN_MASK_KIND_ITEM -}; - -// 登记表 = 本层实际会构造的全部组合。形状取代表值(k/dk/bh/kv_ratio 均不进 -// key;这里的取值与既有登记块一致,便于逐字节比对生成物)。 -[[nodiscard]] inline bool register_all() -{ - constexpr std::uint32_t seq = 64, dk = 2, bh = 2; - for (const AttnMaskKind mask : kMaskKinds) - { - for (const bool score_bias : {false, true}) - { - if (mask == AttnMaskKind::Plain && score_bias) - continue; // 无掩码 + 位置偏置:本仓不构造该组合 - for (const std::uint32_t kv_ratio : {1u, 2u}) - (void)dsl::FoldAnchor{make_fold_attn_o(seq, dk, bh, mask, score_bias, kv_ratio)}; - } - } - return true; -} -} // namespace attn_fold_anchor - -inline const bool g_attn_fold_anchors = attn_fold_anchor::register_all(); -#endif // !NN_SCAN_NO_ANCHOR -#endif // NN_EXPR_SCAN } // namespace expr @@ -286,8 +241,8 @@ inline const bool g_attn_fold_anchors = attn_fold_anchor::register_all(); // append_fold_inputs() → 按 make_fold_attn_o 的 views 顺序追加 fold 输入 // masked_scores() → 反向重算 S 的 DSL 文本(与 fold 掩码语义同构) // -// AOT 注册表:每条 masked_scores() 的表达式文本都必须被 scan_exprs 的 -// 对应掩码 dry-run 覆盖。 +// 表达式结构(AOT 时代由 scan_exprs 覆盖,P3 后无注册表):每条 +// masked_scores() 的表达式文本由 JIT 按 key 现编,结构不变即 shader 同源。 // ═══════════════════════════════════════════════════════════════════════════ class AttnScoreMask { @@ -1278,9 +1233,9 @@ class CausalSelfAttention final : public AttentionBase // 输入。伴随正确性(dL/dK、dL/dV 累加回 memory 路径)与自反向同一套 // R/X 机器,仅形状参数不同。 // -// AOT 注册表:fold(Plain/no-bias)与 backward 表达式结构与 -// MultiHeadAttention 完全同 key(k/seq 是形状参数不进 key)——scan_exprs -// 的 CrossAttention dry-run(seq_q ≠ seq_kv 矩形形状)另行登记确保覆盖。 +// 结构同 key 性质:fold(Plain/no-bias)与 backward 表达式结构与 +// MultiHeadAttention 完全同 key(k/seq 是形状参数不进 key)——矩形 +// seq_q ≠ seq_kv 形状亦命中同一 JIT 编译产物(AOT dry-run 已删,P3)。 // ══════════════════════════════════════════════════════════════════════════ // backward_pair 的双路梯度(query 与 memory 各一份) diff --git a/include/neuralnet.cpp/compute_layer_mlp.hpp b/include/neuralnet.cpp/compute_layer_mlp.hpp index a1ac35e..995ea98 100644 --- a/include/neuralnet.cpp/compute_layer_mlp.hpp +++ b/include/neuralnet.cpp/compute_layer_mlp.hpp @@ -213,8 +213,8 @@ class ReLU final : public Layer // // 用 dsl::compute 单表达式融合:sigmoid(βx) = 1/(1+exp(-βx)) 全部折叠为单个 // GPU 融合 kernel(仅 input/output 落显存,无中间 Tensor)。backward 用 -// input_cache_ 重算 sigmoid(不缓存,省显存)。AOT 收集由 scan_exprs dry-run -// 本层完成(GPU 注册表两端一致)。 +// input_cache_ 重算 sigmoid(不缓存,省显存)。AOT 收集已随 P3 删除 +// (35 号 D4)——GPU 由 JIT 按结构 key 现编同一折叠。 // ══════════════════════════════════════════════════════════════════════════ class GeLU final : public Layer { @@ -239,7 +239,7 @@ class GeLU final : public Layer // ── forward: out = x * sigmoid(β * x) ──────────────────────────────── // 单表达式 DSL 融合(M 融合):sigmoid(βx) = 1/(1+exp(-βx)),全部折叠为 // 单个 GPU 融合 kernel(仅 input/output 落显存,无中间 Tensor)。表达式 - // 文本只写在本 Layer;AOT 收集由 scan_exprs dry-run 本方法完成。 + // 文本只写在本 Layer(AOT 收集已删,P3——GPU 走 JIT 现编)。 [[nodiscard]] Result forward(const Tensor& input) override { ComputeEngine& engine = engine_ref(); @@ -944,8 +944,8 @@ class BatchNorm final : public Layer // 7. running 统计 EMA(compute_into 原地;recompute 抑制) // 8. out = normalized*γ + β (row 广播) → (F,B) //(1/B、ε、momentum 都是 RParam:值不进 expr_spec_key) - // 推理态:inv_std + 归一化 + 出参三步,无归约链(不同结构,scan 必须 - // 单独覆盖——见 tools/scan_exprs.cpp 模型 pass 的推理态 run)。 + // 推理态:inv_std + 归一化 + 出参三步,无归约链(与训练态不同结构; + // AOT 时代由模型 pass 单独覆盖——P3 后由首次执行时 JIT 现编)。 [[nodiscard]] Result forward(const Tensor& input) override { ComputeEngine& engine = engine_ref(); diff --git a/include/neuralnet.cpp/compute_layer_rapt.hpp b/include/neuralnet.cpp/compute_layer_rapt.hpp index c170e92..22979e2 100644 --- a/include/neuralnet.cpp/compute_layer_rapt.hpp +++ b/include/neuralnet.cpp/compute_layer_rapt.hpp @@ -824,9 +824,8 @@ class ReLULinearAttention final : public Layer auto den_t = detail::upload_span(engine, H * dk, 1, Precision::F32, std::span(den_full)); NN_TRY_CHECK(den_t); - // out = num / (den + ε):与 forward 的除法(:491)**逐 token 同构**—— - // 同一个 AOT 键由 scan_exprs 的 ReLULinearAttention dry-run 覆盖, - // GPU 注册表可命中。 + // out = num / (den + ε):与 forward 的除法(:491)**逐 token 同构** + // (同结构 key;AOT 收集已删,P3——GPU 走 JIT 现编同源 GLSL)。 auto out_r = dsl::compute(engine, dsl::leaf(*num_r) / (dsl::leaf(*den_t) + Scalar{1e-4}), (*num_r).rows(), (*num_r).cols(), p_.compute); NN_TRY_CHECK(out_r); diff --git a/include/neuralnet.cpp/compute_layer_softmax.hpp b/include/neuralnet.cpp/compute_layer_softmax.hpp index 8c62aed..7abea3d 100644 --- a/include/neuralnet.cpp/compute_layer_softmax.hpp +++ b/include/neuralnet.cpp/compute_layer_softmax.hpp @@ -57,7 +57,7 @@ class Softmax final : public Layer // 单表达式融合:row_max/row_sum 为归约视图/归约指令,中间全尺寸 // Tensor(shifted/exp_shift/row_max/row_sum 的物化)由融合 kernel 消解, // 仅 input 与 output 落显存。 - // 表达式文本只写在本 Layer;AOT 收集由 scan_exprs dry-run 本方法完成。 + // 表达式文本只写在本 Layer(AOT 收集已删,P3——GPU 走 JIT 现编)。 auto out = dsl::compute( engine, dsl::exp(dsl::leaf(input) - dsl::row_reduce_max(input)) / diff --git a/include/neuralnet.cpp/compute_loss.hpp b/include/neuralnet.cpp/compute_loss.hpp index 03f4665..f0bed11 100644 --- a/include/neuralnet.cpp/compute_loss.hpp +++ b/include/neuralnet.cpp/compute_loss.hpp @@ -33,16 +33,6 @@ namespace nn { -#if defined(NN_EXPR_SCAN) -// ── AOT 自登记锚点(表达式自登记约定:新增表达式不改 scan_exprs)──────── -// CE sparse 的 online-softmax fold 是**值构造**(make_fold_logsumexp,类型 -// 不编码结构,FusedAnchor 按类型登记覆盖不到)→ 由消费方经 FoldAnchor 在 -// 静态初始化期登记本调用点实际构造的结构变体。k/stride 是形状参数(不进 -// key)→ 代表值一份覆盖所有形状;视图 kind 进 key → trans(转置直读)即 -// CE 的构造形态。 -inline const nn::dsl::FoldAnchor g_anchor_ce_lse_fold{ - nn::expr::make_fold_logsumexp(64, nn::expr::trans(64))}; -#endif // ══════════════════════════════════════════════════════════════════════════ // Loss — 引擎化损失函数基类 diff --git a/include/neuralnet.cpp/compute_optimizer.hpp b/include/neuralnet.cpp/compute_optimizer.hpp index 4f3a045..df9e1db 100644 --- a/include/neuralnet.cpp/compute_optimizer.hpp +++ b/include/neuralnet.cpp/compute_optimizer.hpp @@ -493,15 +493,25 @@ class Adam : public Optimizer // 写穿 + K3 中间量 + K4 目标传递融合为**每张量一个多输出 kernel**(f32 // 下 4 节点金刚石单 kernel 3 extras;混精度按门禁自动分裂),替代逐表达式 // 数百个小 dispatch(P2 前实测 266 设备 kernel/步、host 提交 4.5ms/步)。 - // CPU begin/end 为 no-op(行为不变);NN_EXPR_SCAN 下本段把复合 spec - // 登记进注册表(scan_exprs 的优化器 dry-run 正是经此登记)。 - NN_TRY(b, engine_.begin_expr()); - ExprSegment seg(engine_owner_); + // CPU begin/end 为 no-op(行为不变)。 + // ⚠ optimizer=f16 **不录制**(P3 验收门 D 实测):m/v 状态为 f16 时, + // 录制/降级派发链路在第 2 步即把参数打成 NaN(同表达式直求值全绿、 + // f32 optimizer 图融合正常、NN_NO_GRAPH A/B 实证)。根因收敛到 + // "IR-C 录制 × f16 状态"组合且未再下钻——full-f16 本是告警级配方 + // (docs/internal/24 §2),正确性优先:该配方退回直求值;f32 + // optimizer(推荐默认 --f16 的 optimizer 槽)保持图融合性能。 + const bool graph_ok = precision_bytes(p_.optimizer) >= 4; + if (graph_ok) + { + NN_TRY(b, engine_.begin_expr()); + } + std::optional seg; + if (graph_ok) seg.emplace(engine_owner_); for (std::size_t i = 0; i < params_.size(); ++i) { NN_TRY(ur, adam_update_(i, inv_bc1, inv_bc2)); } - NN_TRY_CHECK(seg.end()); + if (graph_ok) NN_TRY_CHECK(seg->end()); t_ = t_next; // 全部参数成功才推进步数(失败时不推进) return {}; } @@ -579,8 +589,14 @@ class AdamW : public Adam // (稳定拓扑序保持列表序),故 decay 必须写在 adam_update_ 之前 // (先衰减后更新 = AdamW 语义);两者不连通 → 各自独立 kernel,天然 // 顺序执行,不会同 kernel 双写 p。 - NN_TRY(b, engine_.begin_expr()); - ExprSegment seg(engine_owner_); + // ⚠ optimizer=f16 不录制(同 Adam::step,P3 门 D 实测) + const bool graph_ok = precision_bytes(p_.optimizer) >= 4; + if (graph_ok) + { + NN_TRY(b, engine_.begin_expr()); + } + std::optional seg; + if (graph_ok) seg.emplace(engine_owner_); for (std::size_t i = 0; i < params_.size(); ++i) { // 权重衰减解耦:p = (1 - lr*wd) * p(目标传递:原地、单 dispatch) @@ -594,7 +610,7 @@ class AdamW : public Adam // Adam 更新 NN_TRY(ur, adam_update_(i, inv_bc1, inv_bc2)); } - NN_TRY_CHECK(seg.end()); + if (graph_ok) NN_TRY_CHECK(seg->end()); t_ = t_next; // 全部参数成功才推进步数(失败时不推进) return {}; } @@ -1223,12 +1239,11 @@ class Muon : public Optimizer }; // ── 优化器元数据表(RV-04:新增一个优化器不再要改 5~6 处)───────────────── -// **名单的唯一真源**。此前同一份名单散落六处:本文件工厂、text_train 的硬编码 -// 白名单、mnist_train 帮助文本、gui.py 的 OPTIMIZER_OPTIONS、scan_exprs 的 -// dry-run 列表、mem_probe 的状态缓冲分支(AGENTS §3 把"别忘了改这几处"写成提醒 +// **名单的唯一真源**。此前同一份名单散落多处:本文件工厂、text_train 的硬编码 +// 白名单、mnist_train 帮助文本、gui.py 的 OPTIMIZER_OPTIONS、 +// mem_probe 的状态缓冲分支等(AGENTS §3 把"别忘了改这几处"写成提醒 // ——提醒本身就说明设计有问题)。现在全部查表: // · CLI 校验 / 帮助文本 → find_optimizer / optimizer_names -// · scan_exprs dry-run → optimizer_table() 逐项建优化器 // · mem_probe 状态预算 → state_bufs / half_state // · GUI 选项 → CLI --help(由本表生成)+ bench/gui_cli_audit.py 断言 // 新增优化器只需:① 在本表加一行;② 在下方 create_optimizer 加一个分支 diff --git a/include/neuralnet.cpp/compute_position_encoding.hpp b/include/neuralnet.cpp/compute_position_encoding.hpp index c868fc7..4a8af30 100644 --- a/include/neuralnet.cpp/compute_position_encoding.hpp +++ b/include/neuralnet.cpp/compute_position_encoding.hpp @@ -25,8 +25,8 @@ // 于是 PosEncodingType 的分发只剩这两个工厂,forward/backward 热路径内 // **不存在任何按编码类型的分支**。 // -// AOT 注册表:本文件内的内联表达式(RoPE 旋转、加性融合、ALiBi 偏置) -// 由 `tools/scan_exprs.cpp` 的 dry-run 登记(文本只写在这里,不在 Engine)。 +// 表达式文本只写在本类(RoPE 旋转、加性融合、ALiBi 偏置),不在 Engine; +// AOT dry-run 登记已随 P3 删除(35 号 D4),GPU 由 JIT 按结构 key 现编。 // ─────────────────────────────────────────────────────────────────────────── #include "compute_layer_base.hpp" @@ -95,8 +95,8 @@ class RotaryEmbedding } // 构造 RoPE 内联表达式(forward 用 Add 结尾,backward 用 Sub 结尾) - // 表达式文本只写在本类(apply/apply_step),AOT 收集(scan_exprs) - // 用同一段代码 dry-run 折叠出结构并合成融合 shader——绝不漂移。 + // 表达式文本只写在本类(apply/apply_step);AOT dry-run 已删(P3), + // GPU JIT 对同一段代码折叠出的结构现编——结构同源、绝不漂移。 public: RotaryEmbedding() = default; diff --git a/include/neuralnet.cpp/model_container.hpp b/include/neuralnet.cpp/model_container.hpp index 03d4e7d..5c95e78 100644 --- a/include/neuralnet.cpp/model_container.hpp +++ b/include/neuralnet.cpp/model_container.hpp @@ -348,8 +348,8 @@ class Model }; // ── model_warmup:跑一遍代表性 forward,把用到的表达式现编进 .nnk 缓存 ── -// Phase 2 运行期 JIT(docs/internal/33 §5):对无内嵌注册表(消费方未跑 scan_exprs) -// 的构建,每次 GPU dispatch 都是 JIT miss(有编译延迟);预热一次后,该模型 +// 运行期 JIT(docs/internal/33 §5;AOT 内嵌注册表已随 P3 删除,35 号 D4): +// 缓存未预热时首次 dispatch 走 JIT 现编(有编译延迟);预热一次后,该模型 // 用到的全部表达式都现编并持久化进缓存,后续真实推理/训练为缓存命中(无延迟)。 // 输入必须为该架构的代表性形状(MLP: (input_dim, batch);GPT: (d_model, batch*seq); // CNN: (C, H*W×batch)…——调用方按自己模型构造)。forward 走引擎已有绑定 diff --git a/src/bitnet_struct_test.cpp b/src/bitnet_struct_test.cpp index b6198a1..d4d6564 100644 --- a/src/bitnet_struct_test.cpp +++ b/src/bitnet_struct_test.cpp @@ -358,7 +358,7 @@ void test_gqa_mha_equivalence(nn::ComputeEngine& eng, const char* tag) void test_gqa_head_mapping(nn::ComputeEngine& eng, const char* tag) { std::printf("[8] GQA 头映射 = 块映射(与复制成 MHA 对拍,%s)\n", tag); - // 与 scan_exprs 的登记**故意不同配置**(heads=4/n_head_kv=2 → n_rep=2, + // 与 AOT 时代登记**故意不同配置**(heads=4/n_head_kv=2 → n_rep=2;收集链已删,P3, // 而 scan 登记用 heads=2/n_head_kv=1):头分组比是运行期的视图参数、 // **不进 key** → 两侧必须折叠出同一 fold key。这条同时验证"任意 n_rep // 共用一份 AOT shader"。 diff --git a/src/expr_fold_test.cpp b/src/expr_fold_test.cpp index 17463f0..d923bc2 100644 --- a/src/expr_fold_test.cpp +++ b/src/expr_fold_test.cpp @@ -2,7 +2,7 @@ // expr_fold_test.cpp — 分块状态归约(FoldSpec)CPU 语义对拍 // // 覆盖(通用样例走共享构造 expr_fold.hpp、注意力样例走 -// compute_layer_attention.hpp —— 均与 scan_exprs AOT 收集同源, +// compute_layer_attention.hpp —— 结构与 JIT 现编同源(AOT 收集链已删,P3), // 保证 key/结构一致): // 1. rowmax fold vs 独立逐行 max 参考 // 2. rowsum fold vs 独立逐行 sum 参考 @@ -490,8 +490,8 @@ int test_expr_fold() // ── 7) 注册表去重 + key 契约(原 bin 序列化 roundtrip 的接替者)──────── // 原先这里验证 write_registry/read_registry 往返不丢 tri_skip 位。bin 序列化 - // 已删除(构建期单步:scan_exprs 直接调 `generate_fused_registry`),改验 - // 注册表自身的两条不变量:① 同结构重复 add 只留一条;② tri_skip 进 key。 + // 与其后的 AOT 收集链均已删除(P3,35 号 D4),改验结构 key 的两条不变量: + // ① 同结构重复构造 key 唯一;② tri_skip 进 key。 { nn::fused::ExprRegistry reg; const nn::ExprSpec sk = nn::expr::make_fold_attn_o(64, 4, 2, nn::expr::AttnMaskKind::Causal, diff --git a/src/expr_fuse_shapes.hpp b/src/expr_fuse_shapes.hpp deleted file mode 100644 index b3a6c5c..0000000 --- a/src/expr_fuse_shapes.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -// ─────────────────────────────────────────────────────────────────────────── -// expr_fuse_shapes.hpp — expr_fuse_test 的链定义(单一事实源) -// -// 每条录制链的每一步一个小函数:运行期用例(expr_fuse_test.cpp)与构建期 -// 自登记(expr_fuse_shapes.cpp 的 NN_EXPR_SCAN 静态初始化 dry-run)**调用 -// 同一组函数**——融合结构由表达式类型决定,共用函数即保证两端逐表达式 -// 一致(不会漂移)。改链 = 改这里,两端自动同步。 -// ─────────────────────────────────────────────────────────────────────────── - -#include - -#include - -namespace nn::test_fuse -{ - -// 链 1(3 节点):t1 = x*2;t2 = t1+3;t3 = t2*row_broadcast(g) -template -[[nodiscard]] auto chain3_a(Eng& eng, const Tensor& x, std::size_t R, std::size_t C) -{ return dsl::compute(eng, dsl::leaf(x) * Scalar{2}, R, C); } - -template -[[nodiscard]] auto chain3_b(Eng& eng, const Tensor& t, std::size_t R, std::size_t C) -{ return dsl::compute(eng, dsl::leaf(t) + Scalar{3}, R, C); } - -template -[[nodiscard]] auto chain3_c(Eng& eng, const Tensor& t, const Tensor& g, std::size_t R, - std::size_t C) -{ return dsl::compute(eng, dsl::leaf(t) * dsl::row_broadcast(g), R, C); } - -// 链 2(独立分支 B):u = w+1(分支 A 复用 chain3_a) -template -[[nodiscard]] auto indep_b(Eng& eng, const Tensor& w, std::size_t R, std::size_t C) -{ return dsl::compute(eng, dsl::leaf(w) + Scalar{1}, R, C); } - -// 链 3(S5 matmul):t1 = matmul(a0,b0)+bias;t2 = max(t1, 0)(relu) -template -[[nodiscard]] auto mm_a(Eng& eng, const Tensor& a0, const Tensor& b0, const Tensor& bias, - std::size_t M, std::size_t N) -{ return dsl::compute(eng, dsl::matmul(a0, b0, false, false, 1) + dsl::leaf(bias), M, N); } - -template -[[nodiscard]] auto mm_b(Eng& eng, const Tensor& t, std::size_t M, std::size_t N) -{ return dsl::compute(eng, dsl::max(dsl::leaf(t), Scalar{0}), M, N); } - -// 单表达式(未录制回归 / vec4 路径):out = x*row_broadcast(g)*2 -template -[[nodiscard]] auto one_shot_rb2(Eng& eng, const Tensor& x, const Tensor& g, std::size_t R, - std::size_t C) -{ return dsl::compute(eng, dsl::leaf(x) * dsl::row_broadcast(g) * Scalar{2}, R, C); } - -// ── 链 4(Adam 金刚石,P2 多输出)──────────────────────────────────────── -// K1: m = 0.9·m0 + 0.1·g (状态写穿 → m 张量) -// K2: v = 0.99·v0 + 0.01·g² (状态写穿 → v 张量) -// K3: d = 2·m − 3·v (纯中间量,依赖 K1/K2 —— 金刚石合流) -// K4: p += d(compute_into 目标传递 → p 张量) -// 目标:fuse 成**单 kernel 4 输出**(tail=p,extras=[m, v, d])。 -template -[[nodiscard]] auto adam_a(Eng& eng, const Tensor& m0, const Tensor& g, std::size_t R, std::size_t C) -{ - // 超参走 RParam(同真实 adam_update_ 的 lr/β/ε 形态)——拼接器的 - // RParam 槽偏移曾漏过(GPU 锚位偏移真凶),本链即其回归锁 - return dsl::compute(eng, dsl::leaf(m0) * dsl::rparam(0.9f) + dsl::leaf(g) * dsl::rparam(0.1f), - R, C); -} - -template -[[nodiscard]] auto adam_b(Eng& eng, const Tensor& v0, const Tensor& g, std::size_t R, std::size_t C) -{ - return dsl::compute( - eng, dsl::leaf(v0) * dsl::rparam(0.99f) + dsl::leaf(g) * dsl::leaf(g) * dsl::rparam(0.01f), - R, C); -} - -template -[[nodiscard]] auto adam_c(Eng& eng, const Tensor& m, const Tensor& v, std::size_t R, std::size_t C) -{ - return dsl::compute(eng, dsl::leaf(m) * dsl::rparam(2.0f) - dsl::leaf(v) * dsl::rparam(3.0f), R, - C); -} - -template [[nodiscard]] auto adam_d_into(Eng& eng, Tensor& p, const Tensor& d) -{ return dsl::compute_into(eng, dsl::leaf(p) + dsl::leaf(d), p); } - -} // namespace nn::test_fuse diff --git a/src/f16_precision_test.cpp b/src/f16_precision_test.cpp index 0835efd..31bc2e6 100644 --- a/src/f16_precision_test.cpp +++ b/src/f16_precision_test.cpp @@ -851,7 +851,7 @@ void run_suite(const char* label, nn::ComputeEngine& raw, nn::ComputeEngine& ada test_f32_zero_regression(raw, adapter); test_f16_move(raw, adapter); test_f16_reduce_matmul(raw, adapter); - // 任意表达式(DSL)在 GPU 上优先命中构建期 scan_exprs 登记过的结构 + // 任意表达式(DSL)在 GPU 上经 JIT 现编/缓存命中执行(AOT 登记已删,P3) // (未登记走 JIT 兜底、有冷启动开销)。故这里只在 CPU 跑任意表达式; // GPU 的 DSL / 原地 / 端到端由 test_f16_model_e2e(Layer 结构 = 已登记结构)覆盖。 if (allow_arbitrary_expr) diff --git a/src/fused_gpu_test.cpp b/src/fused_gpu_test.cpp index 05aa864..0ead66f 100644 --- a/src/fused_gpu_test.cpp +++ b/src/fused_gpu_test.cpp @@ -1,24 +1,24 @@ // ─────────────────────────────────────────────────────────────────────────── // fused_gpu_test — AOT 融合 shader GPU 数值验证(端到端) // -// 架构:表达式**只内联写在 Layer**;构建期 scan_exprs dry-run 收集折叠后的 -// ExprSpec → gen_fused 合成融合 shader(key = expr_spec_key);运行时 -// dsl::compute → GpuEngine::eval_expr 按 key 匹配 dispatch。 +// 架构:表达式**只内联写在 Layer**;运行时 dsl::compute → GpuEngine::eval_expr +// 查 .nnk 算子缓存 → miss 走 JIT 现编(libnn-kernelgen.so),P3 起不再有 +// 构建期 AOT 注册表(scan_exprs 收集链已删,docs/internal/35 P3/D4)。 // // 本测试验证真实路径: -// 1. 内联表达式在 GPU 端命中预编译融合 shader(未命中 eval_expr 硬报错) -// 2. GPU 融合结果 vs CPU eval_cpu 参考一致(RoPE / SwiGLU backward) +// 1. 内联表达式在 GPU 端经缓存/JIT 执行(fold/reduce/into/multi 四入口全覆盖) +// 2. GPU 结果 vs CPU 参考一致(RoPE / SwiGLU backward / fold / reduce) // // 做法:用同一份输入分别驱动 CPU 引擎与 GPU 引擎上的同一批 Layer -// (RotaryEmbedding / SwiGLU),对比输出。GPU 走融合 shader,CPU 走 -// eval_cpu,二者应一致。 +// (RotaryEmbedding / SwiGLU / fold 样例),对比输出。引擎经抽象层工厂 +// (create_*_engine)创建——消费方形态,不直连 Vulkan backend。 // -// 用法:fused_gpu_test +// 用法:fused_gpu_test(无 GPU 设备时返回 77 = ctest SKIP) // ─────────────────────────────────────────────────────────────────────────── #include #include -#include // fold v1 样例(与 scan_exprs 同源 → key 一致) +#include // fold v1 样例(结构 key 与对拍同源) #include "test_common.hpp" // M1 访问收口:upload/download(库外经引擎进出张量) @@ -32,19 +32,17 @@ using nn::Scalar; -#ifndef NN_HAS_VULKAN +#ifndef NN_GPU_ENGINE + int main() { // 返回 77 = ctest SKIP:纯 CPU 构建无法执行 GPU 测试,不得计为 "Passed" - std::cout << "[SKIP] 此程序需要 Vulkan SDK 支持,请使用 -DNN_HAS_VULKAN 编译。\n"; + std::cout << "[SKIP] 此程序需要 GPU 引擎(NN_ENABLE_GPU=OFF 或缺 Vulkan/glslc)。\n"; return 77; } #else -#include using nn::ComputeEngine; -using nn::GpuBackend; -using nn::GpuEngine; using nn::Matrix; using nn::Tensor; @@ -62,10 +60,193 @@ Scalar max_abs_diff(const Matrix& a, const Matrix& b) return max_err; } +// ── matmul 形状矩阵 + 尾链/转置/f16 变体(P3 验收门 D 形状覆盖)─────────── +int run_matmul_shapes(ComputeEngine& cpu, ComputeEngine& gpu) +{ + // tail: 0=纯 matmul;1=matmul*row_broadcast(tau)+row_broadcast(b)(dense fwd 形态) + struct CfgT { std::size_t M, K, N; bool tA, tB; int tail; const char* name; }; + const CfgT cfgs[] = { + {65, 17, 520, false, false, 1, "65x17x520 tail fwd"}, + {17, 65, 520, true, false, 0, "17x65x520 transA(dX)"}, + {65, 17, 520, false, false, 0, "65x17x520 纯matmul"}, + {65, 70, 100, false, false, 1, "65x70x100 tail"}, + {150, 70, 100, false, false, 1, "150x70x100 tail 对照"}, + {5, 7, 4, false, false, 1, "5x7x4 tail 对照"}, + }; + int fail = 0; + // ── cast_into 覆盖面(f16 目标最后一元素)─────────────────────────── + { + const std::size_t R = 65, C = 17; // 1105 = 4*276+1(奇数尾) + Matrix src(R, C); + std::mt19937 rng(99); + std::uniform_real_distribution dist(-1.0f, 1.0f); + for (auto& v : src.span()) v = dist(rng); + const Tensor st = upload(cpu, src); + // GPU:f32 源 → f16 目标经 engine.cast_into + auto g32 = upload(gpu, src); + nn::Tensor g16t = gpu.create_tensor(R, C, nn::Precision::F16); + auto cr2 = gpu.cast_into(g32, g16t); + NN_EXIT(cr2, 1, " [cast] cast_into fail: "); + auto gm3 = gpu.to_matrix(g16t); + NN_EXIT(gm3, 1, " [cast] download fail\n"); + const auto& gs3 = gm3->span(); + const auto& cs3 = src.span(); + std::size_t bad = 0, lastbad = 0; + for (std::size_t i = 0; i < cs3.size(); ++i) + { + const float ref = static_cast(nn::f16(cs3[i])); + if (gs3[i] != ref) { ++bad; if (i + 1 == cs3.size()) lastbad = 1; } + } + std::cout << "[" << (bad == 0 ? "PASS" : "FAIL") << "] cast_into f32->f16 (65x17)" + << " bad=" << bad << "/" << cs3.size() << " last_elem_bad=" << lastbad + << " last gpu=" << gs3.back() << " host_f16=" << (float)nn::f16(cs3.back()) + << "\n"; + if (bad) ++fail; + } + + for (const auto& c : cfgs) + { + std::mt19937 rng(7000 + static_cast(c.M * 100 + c.K)); + std::uniform_real_distribution dist(-1.0f, 1.0f); + // transA:物理 A 存 (K, M)(A^T 后逻辑 (M,K));否则 (M, K) + Matrix A(c.tA ? c.K : c.M, c.tA ? c.M : c.K), B(c.K, c.N), tau(c.M, 1), bias(c.M, c.N); + for (auto& v : A.span()) v = dist(rng); + for (auto& v : B.span()) v = dist(rng); + for (auto& v : tau.span()) v = dist(rng); + for (auto& v : bias.span()) v = dist(rng); + const Tensor At_cpu = upload(cpu, A), At_gpu = upload(gpu, A); + const Tensor Bt_cpu = upload(cpu, B), Bt_gpu = upload(gpu, B); + const Tensor tt_cpu = upload(cpu, tau), tt_gpu = upload(gpu, tau); + const Tensor bt_cpu = upload(cpu, bias), bt_gpu = upload(gpu, bias); + nn::Result cr, gr; + if (c.tail && c.M == 65 && c.K == 17) + { + // f16 输入变体:X 以 f16 上传 → eval 需 to_f32_all 输入 cast(ternary 同构) + { + Matrix X16(c.K, c.N); + for (auto& v : X16.span()) v = dist(rng); + const Tensor X16_g = upload(gpu, X16, nn::Precision::F16); + const Tensor X16_c = upload(cpu, X16, nn::Precision::F16); + auto crx = nn::dsl::compute(cpu, + nn::dsl::matmul(At_cpu, X16_c, false, false) * + nn::dsl::row_broadcast(tt_cpu) + + nn::dsl::row_broadcast(bt_cpu), + c.M, c.N, nn::Precision::F16); + auto grx = nn::dsl::compute(gpu, + nn::dsl::matmul(At_gpu, X16_g, false, false) * + nn::dsl::row_broadcast(tt_gpu) + + nn::dsl::row_broadcast(bt_gpu), + c.M, c.N, nn::Precision::F16); + if (!crx || !grx) + { + std::cerr << " [f16in] eval fail: " + << (!crx ? crx.error().message : grx.error().message) << "\n"; + ++fail; + } + else + { + auto gmx = gpu.to_matrix(*grx); + NN_EXIT(gmx, 1, " [f16in] download fail\n"); + const Scalar ex = max_abs_diff(download(cpu, *crx), *gmx); + const auto& csx = download(cpu, *crx).span(); + const auto& gsx = gmx->span(); + Scalar elx = 0; + for (std::size_t cc2 = 0; cc2 < c.N; ++cc2) + elx = std::max(elx, std::fabs(csx[(c.M - 1) * c.N + cc2] - + gsx[(c.M - 1) * c.N + cc2])); + const bool okx = ex < 5e-3f; + std::cout << "[" << (okx ? "PASS" : "FAIL") << "] shapes f16-in tail fwd" + << " err=" << ex << " lastrow_err=" << elx << "\n"; + if (!okx) ++fail; + } + } + // f16 出参变体(ternary t1_58/f16 失败形态最小复现) + auto cr16 = nn::dsl::compute(cpu, + nn::dsl::matmul(At_cpu, Bt_cpu, false, false) * + nn::dsl::row_broadcast(tt_cpu) + + nn::dsl::row_broadcast(bt_cpu), + c.M, c.N, nn::Precision::F16); + auto gr16 = nn::dsl::compute(gpu, + nn::dsl::matmul(At_gpu, Bt_gpu, false, false) * + nn::dsl::row_broadcast(tt_gpu) + + nn::dsl::row_broadcast(bt_gpu), + c.M, c.N, nn::Precision::F16); + if (!cr16 || !gr16) + { + std::cerr << " [f16] eval fail: " + << (!cr16 ? cr16.error().message : gr16.error().message) << "\n"; + ++fail; + } + else + { + auto gm16 = gpu.to_matrix(*gr16); + NN_EXIT(gm16, 1, " [f16] download fail\n"); + const Scalar e16 = max_abs_diff(download(cpu, *cr16), *gm16); + const bool ok16 = e16 < 5e-3f; + const auto& cs16 = download(cpu, *cr16).span(); + const auto& gs16 = gm16->span(); + Scalar el16 = 0; + for (std::size_t cc = 0; cc < c.N; ++cc) + el16 = std::max(el16, std::fabs(cs16[(c.M - 1) * c.N + cc] - gs16[(c.M - 1) * c.N + cc])); + std::cout << "[" << (ok16 ? "PASS" : "FAIL") << "] shapes f16 tail fwd" + << " err=" << e16 << " lastrow_err=" << el16 << "\n"; + if (!ok16) ++fail; + } + } + if (c.tail) + { + cr = nn::dsl::compute(cpu, + nn::dsl::matmul(At_cpu, Bt_cpu, c.tA, c.tB) * + nn::dsl::row_broadcast(tt_cpu) + + nn::dsl::row_broadcast(bt_cpu), + c.M, c.N); + gr = nn::dsl::compute(gpu, + nn::dsl::matmul(At_gpu, Bt_gpu, c.tA, c.tB) * + nn::dsl::row_broadcast(tt_gpu) + + nn::dsl::row_broadcast(bt_gpu), + c.M, c.N); + } + else + { + cr = nn::dsl::compute(cpu, nn::dsl::matmul(At_cpu, Bt_cpu, c.tA, c.tB), c.M, c.N); + gr = nn::dsl::compute(gpu, nn::dsl::matmul(At_gpu, Bt_gpu, c.tA, c.tB), c.M, c.N); + } + if (!cr || !gr) + { + std::cerr << " [shapes] " << c.name << " eval fail: " + << (!cr ? cr.error().message : gr.error().message) << "\n"; + ++fail; + continue; + } + auto gm = gpu.to_matrix(*gr); + NN_EXIT(gm, 1, " [shapes] download fail\n"); + const Scalar err = max_abs_diff(download(cpu, *cr), *gm); + const bool ok = err < 1e-3f; + // 打印最后两行的误差帮助定位尾行 + const auto& cs = download(cpu, *cr).span(); + const auto& gs = gm->span(); + const std::size_t rows = c.M, cols = c.N; + Scalar el = 0, eb = 0; + if (rows >= 2) + { + for (std::size_t cc = 0; cc < cols; ++cc) + { + el = std::max(el, std::fabs(cs[(rows - 1) * cols + cc] - gs[(rows - 1) * cols + cc])); + eb = std::max(eb, std::fabs(cs[(rows - 2) * cols + cc] - gs[(rows - 2) * cols + cc])); + } + } + std::cout << "[" << (ok ? "PASS" : "FAIL") << "] shapes " << c.name + << " err=" << err << " lastrow_err=" << el << " prevrow_err=" << eb << "\n"; + if (!ok) ++fail; + } + return fail; +} + + // ── RoPE:GPU(融合 shader)vs CPU(eval_cpu)─────────────────────────── // 形状无关融合:RowMod/RotateHalf 周期/块大小是运行时视图参数(不进 key), // 任意 d_k(含非 2 的幂)都命中同一个融合 shader。 -int run_rope(CpuEngine& cpu, GpuEngine& gpu, std::size_t dk, bool backward) +int run_rope(ComputeEngine& cpu, ComputeEngine& gpu, std::size_t dk, bool backward) { std::mt19937 rng(1000 + static_cast(dk)); std::uniform_real_distribution dist(-1.0f, 1.0f); @@ -96,7 +277,7 @@ int run_rope(CpuEngine& cpu, GpuEngine& gpu, std::size_t dk, bool backward) } // ── SwiGLU backward:GPU(融合 grad_gate + grad_up)vs CPU ────────────── -int run_swiglu(CpuEngine& cpu, GpuEngine& gpu) +int run_swiglu(ComputeEngine& cpu, ComputeEngine& gpu) { const std::size_t d_ff = 8; const std::size_t batch = 6; @@ -141,9 +322,9 @@ int run_swiglu(CpuEngine& cpu, GpuEngine& gpu) // ── GeLU forward/backward:GPU(单表达式 DSL 融合 shader)vs CPU ───────── // forward: x / (1 + exp(-βx));backward: grad_out * s*(1 + βx*(1-s))。 // GPU 走融合 shader(scan 已收集 GeLU 结构),CPU 走 eval_cpu,二者应一致。 -int run_gelu(CpuEngine& cpu, GpuEngine& gpu) +int run_gelu(ComputeEngine& cpu, ComputeEngine& gpu) { - const std::size_t R = 6, C = 9; // 与 scan_exprs 结构一致(结构不依赖形状) + const std::size_t R = 6, C = 9; // 结构不依赖形状(JIT 按结构 key 现编) std::mt19937 rng(999); std::uniform_real_distribution dist(-1.0f, 1.0f); Matrix x(R, C), grad(R, C); @@ -194,9 +375,9 @@ int run_gelu(CpuEngine& cpu, GpuEngine& gpu) // forward: exp(x - row_max) / row_sum(exp(x - row_max)) // backward: out * (grad - row_dot(out * grad)) // 归约融合 shader 未命中(未扫描)时 GPU 端 eval_expr 硬报错。 -int run_softmax(CpuEngine& cpu, GpuEngine& gpu) +int run_softmax(ComputeEngine& cpu, ComputeEngine& gpu) { - const std::size_t R = 6, C = 9; // 与 scan_exprs 结构一致(结构不依赖形状) + const std::size_t R = 6, C = 9; // 结构不依赖形状(JIT 按结构 key 现编) std::mt19937 rng(777); std::uniform_real_distribution dist(-1.0f, 1.0f); Matrix x(R, C), grad(R, C); @@ -249,7 +430,7 @@ int run_softmax(CpuEngine& cpu, GpuEngine& gpu) // ── 归一化层通用:GPU(归约融合 shader)vs CPU ──────────────────────── // 驱动同一个 Layer(RMSNorm/LayerNorm)在 CPU 与 GPU 引擎上,对比 // forward/backward 输出。未命中融合 shader 时 GPU 端 eval_expr 硬报错。 -template int run_norm(const char* name, CpuEngine& cpu, GpuEngine& gpu) +template int run_norm(const char* name, ComputeEngine& cpu, ComputeEngine& gpu) { const std::size_t F = 8, B = 5; std::mt19937 rng(888); @@ -303,9 +484,9 @@ template int run_norm(const char* name, CpuEngine& cpu, GpuEngi // ── matmul 融合:matmul(A,B)+bias+relu 单 kernel vs CPU ──────── // DSL 折叠出前置 matmul 段(MatmulSpec)+ 尾逐元素链,GPU 命中 AOT 合成 -// 的 matmul 融合 shader(scan_exprs 已登记该结构);k(求和维度)是形状 +// 的 matmul 融合 shader(JIT 按同结构现编);k(求和维度)是形状 // 参数不进 key:不同 K 共享同一融合 shader(mm_k push constant 运行时填充)。 -int run_matmul(CpuEngine& cpu, GpuEngine& gpu) +int run_matmul(ComputeEngine& cpu, ComputeEngine& gpu) { std::mt19937 rng(2026); std::uniform_real_distribution dist(-1.0f, 1.0f); @@ -427,7 +608,7 @@ int run_matmul(CpuEngine& cpu, GpuEngine& gpu) // row_max(QK^T) 与 denom = row_sum(exp(QK^T - rm)) 均为"matmul 段 + // 归约指令"单表达式,GPU 经 generate_glsl_reduce 的 matmul 支持单 // kernel 完成。 -int run_matmul_reduce(CpuEngine& cpu, GpuEngine& gpu) +int run_matmul_reduce(ComputeEngine& cpu, ComputeEngine& gpu) { std::mt19937 rng(31415); std::uniform_real_distribution dist(-1.0f, 1.0f); @@ -568,7 +749,7 @@ int run_matmul_reduce(CpuEngine& cpu, GpuEngine& gpu) // SPIR-V → 懒建 pipeline → 执行 → 持久化进 .nnk 缓存。本用例断言 GPU 侧 // **成功**且结果与 CPU 参考一致(绝不静默回退 CPU——成功即证明走了融合 kernel)。 // 运行需 NN_KERNELGEN_PATH 指向 libnn-kernelgen.so(引擎 dlopen 搜索路径)。 -int run_fallback(CpuEngine& cpu, GpuEngine& gpu) +int run_fallback(ComputeEngine& cpu, ComputeEngine& gpu) { std::mt19937 rng(42); std::uniform_real_distribution dist(-2.0f, 2.0f); @@ -605,7 +786,7 @@ int run_fallback(CpuEngine& cpu, GpuEngine& gpu) } // ── 归约表达式内联常量:CPU vs GPU(push-constant 固定头长度按形态算)───── -// 结构(与 tools/scan_exprs.cpp 的对应 dry-run 完全一致): +// 结构(与 AOT 时代 dry-run 定义一致——收集链已删,P3): // col_reduce_sum(select(x == col_broadcast(mx), 1, 0)) // 该结构带**常量池**。⚠ GPU 侧 push-constant 固定头长度必须逐形态与 // 生成器 PC 声明一致(逐元素 2 / 逐元素+matmul 5 / 归约 4 / @@ -686,7 +867,7 @@ int run_reduce_consts(nn::ComputeEngine& cpu, nn::ComputeEngine& gpu) // rowsum/denom 给小容差(fp 加法结合序驱动差异 / exp 软硬件实现差异)。 // K 族覆盖单列 / 非块整除 / 整除 / 尾块(EXPR_FOLD_BLOCK=128 边界两侧—— // 256/260 补多块+尾块;K≤100 全落 128 以内的单块区间,测不到多块/尾块)。 -int run_fold_gpu(CpuEngine& cpu, GpuEngine& gpu) +int run_fold_gpu(ComputeEngine& cpu, ComputeEngine& gpu) { int fail = 0; std::mt19937 rng(9013); @@ -769,7 +950,7 @@ int run_fold_gpu(CpuEngine& cpu, GpuEngine& gpu) // 软硬件有差 → 走容差 1e-4(非全逐位)。 // 覆盖 mm 段/5 掩码/vecacc/向量域 finalize/dispatch(ceil(rows/NR))、 // seq>EXPR_FOLD_BLOCK 多块流式 + causal 整块跳过、GPU rows=1。 -int run_fold_attn_gpu(CpuEngine& cpu, GpuEngine& gpu) +int run_fold_attn_gpu(ComputeEngine& cpu, ComputeEngine& gpu) { int fail = 0; std::mt19937 rng(9024); @@ -918,20 +1099,25 @@ int main() std::cout << "========================================\n" << " AOT 融合 shader GPU 数值验证(端到端)\n" << "========================================\n"; - auto cpu_engine = CpuEngine::create(); - auto backend_owner = GpuBackend::shared(); - GpuBackend& backend = *backend_owner; - auto init_r = backend.initialize(); - if (!init_r) + auto cpu_engine = nn::create_cpu_engine(); + if (!cpu_engine) + { + const char* err = nn::last_error(); + std::cout << "[FAIL] CPU 引擎创建失败: " << (err ? err : "(无错误信息)") << "\n"; + return 1; + } + // 消费方形态:抽象层工厂(链 libnn-gpu.so),不直连 Vulkan backend。 + // 无可用 Vulkan 设备(CI runner / 缺 ICD)→ 77 = ctest SKIP。 + // 本 main 既作独立用例 `fused_gpu_test`,又被 `expr_gpu_test` 以 + // `test_fused_gpu()` 复用——后者已把 77 当作"该子测试跳过"。 + auto gpu_engine = nn::create_gpu_engine(nullptr); + if (!gpu_engine) { - // 无可用 Vulkan 设备(CI runner / 缺 ICD)→ 77 = ctest SKIP。 - // 本 main 既作独立用例 `fused_gpu_test`,又被 `expr_gpu_test` 以 - // `test_fused_gpu()` 复用——后者已把 77 当作"该子测试跳过"。 - std::cout << "[SKIP] GPU 不可用: " << init_r.error().message << "\n"; + const char* err = nn::last_error(); + std::cout << "[SKIP] GPU 不可用: " << (err ? err : "(无错误信息)") << "\n"; return 77; } - auto gpu_engine = GpuEngine::create(backend); - std::cout << "[init] CpuEngine + GpuEngine 就绪\n"; + std::cout << "[init] 引擎就绪(CPU + GPU,工厂消费方形态)\n"; int fail = 0; // 形状无关融合:任意 d_k(含非 2 的幂 40/96)都命中同一个融合 shader @@ -952,6 +1138,7 @@ int main() fail += run_fold_gpu(*cpu_engine, *gpu_engine); fail += run_fold_attn_gpu(*cpu_engine, *gpu_engine); fail += run_fallback(*cpu_engine, *gpu_engine); + fail += run_matmul_shapes(*cpu_engine, *gpu_engine); std::cout << (fail == 0 ? "ALL PASS\n" : "FAILED\n"); return fail == 0 ? 0 : 1; diff --git a/src/mnist_infer.cpp b/src/mnist_infer.cpp index 8a0989c..b4f95e8 100644 --- a/src/mnist_infer.cpp +++ b/src/mnist_infer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,9 @@ void print_usage(const char* prog) help.opt("--model ", "模型文件路径 (默认: pretrained/model.bin)"); help.opt("--topk ", "显示前 n 个预测结果 (默认: 3)"); help.opt("--show-pixels", "显示像素矩阵 (调试用)"); + help.opt("--no-warmup", + "跳过 JIT 预热(GPU 默认加载后跑一遍代表性 forward,\n" + "表达式预编进 kernels.nnk)"); help.opt("--gpu [索引|名称]", "启用 GPU 加速 (需要 Vulkan SDK)\n空格形式只收枚举索引;名称子串用 --gpu=<名称>,如 " "--gpu=NVIDIA / --gpu=40HX"); @@ -59,6 +63,7 @@ struct InferConfig bool show_pixels = false; bool gpu_enabled = false; std::string gpu_device; // --gpu 的可选设备选择子(空 = 自动选卡) + bool jit_warmup = true; // --no-warmup:跳过 JIT 预热(P3/D5) }; nn::Result parse_args(int argc, char* argv[]) @@ -84,6 +89,10 @@ nn::Result parse_args(int argc, char* argv[]) NN_FAIL("--topk 不能超过类别数 " + std::to_string(nn::MNIST_NUM_CLASSES)); cfg.topk = *v; } + else if (arg == "--no-warmup") + { + cfg.jit_warmup = false; + } else if (arg == "--show-pixels") { cfg.show_pixels = true; } else if (auto dev = nn::cli::parse_gpu_option(argc, argv, i)) { @@ -265,6 +274,13 @@ int main(int argc, char* argv[]) NN_EXIT(engine->release_idle_pool_blocks(), 1, "显存回收失败: "); } + // ── JIT 预热(P3/D5):GPU 上 (784,1) 全零 forward → .nnk ───────────── + { + nn::Tensor warm_x = engine->create_tensor(784, 1, nn::Precision::F32, + nn::InitSpec::zero()); + NN_EXIT(nn::cli::jit_warmup(model, *engine, warm_x, cfg.jit_warmup), 1, "预热失败: "); + } + // 推理模式:BatchNorm 使用保存的 running 统计量(batch 可能为 1,不能算 batch 统计) model.set_training(false); diff --git a/src/mnist_train.cpp b/src/mnist_train.cpp index b63598c..4cb52ad 100644 --- a/src/mnist_train.cpp +++ b/src/mnist_train.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,9 @@ void print_usage(const char* prog) help.opt("--weight-decay ", "AdamW 权重衰减系数 (默认: 0.01)"); help.opt("--max-samples ", "限制训练样本数 (默认: 全部,用于快速测试)"); help.opt("--shuffle-steps ", "每 epoch 打乱 batch 顺序 (默认: true)"); + help.opt("--no-warmup", + "跳过 JIT 预热(GPU 默认开训前跑一遍代表性 forward,\n" + "表达式预编进 kernels.nnk,消除首步 JIT 冷启动)"); help.opt("--gpu [索引|名称]", "启用 GPU 加速 (需要 Vulkan SDK)\n空格形式只收枚举索引;名称子串用 --gpu=<名称>,如 " "--gpu=NVIDIA / --gpu=40HX"); @@ -165,6 +169,7 @@ struct TrainConfig std::string gpu_device; // --gpu 的可选设备选择子(空 = 自动选卡) int max_train_samples = -1; // -1 表示使用全部 bool shuffle_steps = true; // 每 epoch 打乱 batch 顺序 + bool jit_warmup = true; // --no-warmup:跳过 JIT 预热(P3/D5) // MLP 参数 std::vector layer_dims; @@ -479,6 +484,10 @@ TrainConfig parse_args(int argc, char* argv[]) { cfg.precision = nn::profile_f16(); // param+compute f16(stable/optimizer f32) } + else if (arg == "--no-warmup") + { + cfg.jit_warmup = false; + } else if (arg == "--t1_58") { // 精度预设 t1_58 = 三值权重 + f16 存储(= --f16 再把 param 换成 t1_58) @@ -904,6 +913,14 @@ int main(int argc, char* argv[]) auto t_start = std::chrono::steady_clock::now(); + // ── JIT 预热(P3/D5):GPU 上跑一遍代表性 forward((784, batch) 全零)── + { + nn::Tensor warm_x = engine->create_tensor(train_x.rows(), cfg.batch_size, + nn::Precision::F32, nn::InitSpec::zero()); + NN_EXIT(nn::cli::jit_warmup(session.model(), *engine, warm_x, cfg.jit_warmup), 1, + "预热失败: "); + } + for (int epoch = 0; epoch < cfg.epochs; ++epoch) { // ── 学习率调度:每 epoch 开始时调整 ── diff --git a/src/nn_warmup.cpp b/src/nn_warmup.cpp new file mode 100644 index 0000000..dcb60f2 --- /dev/null +++ b/src/nn_warmup.cpp @@ -0,0 +1,176 @@ +// ── nn_warmup — 构建期/发布期 JIT 预热目标(P3/D5,docs/internal/35)────────── +// +// 常用架构 × 代表性 shape 过一遍 **GPU forward** → 用到的表达式全部现编进 +// kernels.nnk(.nnk 算子缓存),产物随发布物分发: +// · 部署首跑无需现场依赖 libnn-kernelgen.so/glslc(配合各 app 的 +// model_warmup / --no-warmup 语义;NN_STRICT_CLOSED_WORLD=1 的 +// "只用预生成缓存"模式也能命中); +// · 同时充当"编译目标预热"(35 §P3 D5 入口②)。 +// +// 覆盖(forward 即触发 eval_expr/fold/reduce/into 各入口的 JIT 编译): +// MNIST-MLP / MNIST-CNN(im2col+pool) / MNIST-Transformer(patch+attention fold) +// / tiny GPT(attention fold + LN + GELU + head) / tiny RAPT(RoPE 线性注意力)。 +// +// 用法:nn_warmup [--out ] +// 缓存目录优先级(与 kernel_cache_path_from_env 同源): +// NN_KERNEL_CACHE_PATH > NN_KERNEL_CACHE_DIR > 本命令 --out(写入 +// NN_KERNEL_CACHE_DIR)> 可执行文件所在目录(缺省,便于把产物直接 +// 落在 build// 里随发布拷走)。 +// 无可用 Vulkan 设备 → 打印 SKIP 并返回 0(发布机无 GPU 不算失败)。 +// ───────────────────────────────────────────────────────────────────────────── +#include +#include +#include +#include +#include + +#include +#include // Device +#include // nn::fused::kernel_cache_path() + +namespace +{ +void set_cache_dir(const std::string& dir) +{ +#ifdef _WIN32 + _putenv_s("NN_KERNEL_CACHE_DIR", dir.c_str()); +#else + setenv("NN_KERNEL_CACHE_DIR", dir.c_str(), 1); +#endif +} + +std::string exe_dir(const char* argv0) +{ + const std::string s = argv0 != nullptr ? argv0 : ""; + const auto pos = s.find_last_of("/\\"); + if (pos == std::string::npos) return "."; + if (pos == 0) return s.substr(0, 1); + return s.substr(0, pos); +} + +bool env_cache_configured() +{ + const char* p = std::getenv("NN_KERNEL_CACHE_PATH"); + const char* d = std::getenv("NN_KERNEL_CACHE_DIR"); + return (p != nullptr && p[0] != '\0') || (d != nullptr && d[0] != '\0'); +} +} // namespace + +int main(int argc, char** argv) +{ + // ── 缓存目录(在引擎/缓存首次触碰前落定)──────────────────────────────── + std::string out_dir; + for (int i = 1; i < argc; ++i) + { + if (std::strcmp(argv[i], "--out") == 0 && i + 1 < argc) + { + out_dir = argv[++i]; + } + else if (std::strcmp(argv[i], "--help") == 0 || std::strcmp(argv[i], "-h") == 0) + { + std::cout << "用法: nn_warmup [--out ]\n" + " GPU 常用架构前向预热 → kernels.nnk(--out 缺省 = 可执行文件目录;\n" + " 已设 NN_KERNEL_CACHE_PATH/DIR 环境变量时以环境变量为准)\n"; + return 0; + } + else + { + std::cerr << "未知参数: " << argv[i] << "\n"; + return 1; + } + } + if (!env_cache_configured()) set_cache_dir(out_dir.empty() ? exe_dir(argv[0]) : out_dir); + + // ── GPU 引擎(本目标只为 GPU kernel 缓存服务)──────────────────────────── + auto engine = nn::create_gpu_engine(nullptr); + if (!engine) + { + const char* err = nn::last_error(); + std::cout << "[nn_warmup] SKIP: 无可用 Vulkan 设备(发布机无 GPU 不算失败):" + << (err != nullptr ? err : "(无错误信息)") << "\n"; + return 0; + } + if (engine->device() != nn::Device::GPU) + { + std::cout << "[nn_warmup] SKIP: create_gpu_engine 返回非 GPU 设备\n"; + return 0; + } + + const std::string cache_path = nn::fused::kernel_cache_path(); + std::cout << "[nn_warmup] GPU: " << engine->describe().text() << "\n" + << "[nn_warmup] 缓存: " << cache_path << "\n"; + + int failures = 0; + const auto warm = [&](const char* name, nn::Model& model, nn::Tensor&& x) -> void + { + const auto r = nn::model_warmup(model, x); + if (!r) + { + std::cerr << "[nn_warmup][FAIL] " << name << ": " << r.error().message << "\n"; + ++failures; + } + else + { + std::cout << "[nn_warmup][ok] " << name << "\n"; + } + }; + const auto zero = [&](std::size_t rows, std::size_t cols) + { return engine->create_tensor(rows, cols, nn::Precision::F32, nn::InitSpec::zero()); }; + + // 1. MNIST-MLP(Linear/ReLU/Norm 头) + { + auto m = nn::build_mnist_mlp_model(*engine); + if (!m) { std::cerr << "[nn_warmup][FAIL] mlp build: " << m.error().message << "\n"; ++failures; } + else warm("mnist-mlp (784x4)", *m, zero(784, 4)); + } + // 2. MNIST-CNN(im2col / col2im / MaxPool / BatchNorm) + { + nn::CnnConfig c; // mnist 默认:1×28×28,convs {6@5,16@5},fc {120,10} + c.convs.push_back({6, 5}); + c.convs.push_back({16, 5}); + c.fc_dims = {120, 10}; + auto m = nn::build_cnn_model(*engine, c); + if (!m) { std::cerr << "[nn_warmup][FAIL] cnn build: " << m.error().message << "\n"; ++failures; } + else warm("mnist-cnn (784x4)", *m, zero(784, 4)); + } + // 3. MNIST-Transformer(patch embed + 注意力 fold + 位置编码) + { + auto m = nn::build_mnist_transformer_model(*engine); + if (!m) { std::cerr << "[nn_warmup][FAIL] transformer build: " << m.error().message << "\n"; ++failures; } + else warm("mnist-transformer (784x4)", *m, zero(784, 4)); + } + // 4. tiny GPT(causal attention fold + GELU FFN + LM head;ids 形状 = (seq, batch)) + { + nn::GptConfig g; + g.vocab_size = 256; + g.d_model = 64; + g.seq_len = 32; + g.num_heads = 4; + g.d_ff = 128; + g.num_layers = 2; + auto m = nn::build_gpt_model(*engine, g); + if (!m) { std::cerr << "[nn_warmup][FAIL] gpt build: " << m.error().message << "\n"; ++failures; } + else warm("gpt (32x2)", *m, zero(32, 2)); + } + // 5. tiny RAPT(RoPE 线性注意力 scan 路径;同 (seq, batch) ids) + { + nn::RAPTConfig r; + r.vocab_size = 256; + r.d_model = 64; + r.seq_len = 32; + r.num_heads = 4; + r.d_ff = 128; + r.num_layers = 2; + auto m = nn::build_rapt_model(*engine, r); + if (!m) { std::cerr << "[nn_warmup][FAIL] rapt build: " << m.error().message << "\n"; ++failures; } + else warm("rapt (32x2)", *m, zero(32, 2)); + } + + if (failures != 0) + { + std::cerr << "[nn_warmup] " << failures << " 项失败\n"; + return 1; + } + std::cout << "[nn_warmup] 完成,kernels.nnk = " << cache_path << "\n"; + return 0; +} diff --git a/src/optimizer_test.cpp b/src/optimizer_test.cpp index 11229ff..3696715 100644 --- a/src/optimizer_test.cpp +++ b/src/optimizer_test.cpp @@ -3,7 +3,7 @@ // 两件事都属于"新增/改动优化器时最容易静默错"的那一类: // // [1] RV-04 元数据表 ↔ 工厂一致:nn::optimizer_table() 是名单的**唯一真源** -// (CLI 校验/帮助、scan_exprs dry-run、mem_probe 状态账、GUI 选项全部查表)。 +// (CLI 校验/帮助、mem_probe 状态账、GUI 选项全部查表)。 // 断言:表内每个 name 都能被 create_optimizer 构造;未知 name → nullptr; // 名单文本与表一致(帮助文本正是由它拼出来的)。 // diff --git a/src/tensor_expr_test.cpp b/src/tensor_expr_test.cpp index d96d6b5..a1fc67d 100644 --- a/src/tensor_expr_test.cpp +++ b/src/tensor_expr_test.cpp @@ -6,7 +6,7 @@ // 逐元素在 CPU 走编译期模板(内联 + SIMD 融合);matmul 是原语边界, // 先用 engine.matmul 计算,再并入逐元素表达式。 // -// 注意(AOT 注册表):GPU 优先执行构建期 scan_exprs 收集 + gen_fused 合成的 +// 注意:GPU 侧经运行期 JIT 现编融合 shader(AOT 收集链已删,P3/35 号; // AOT 融合 shader(按 expr_spec_key 匹配,未命中走运行期 JIT 兜底);本测试的 // 任意表达式未必被扫描覆盖,故只在 CPU 上验证。 // ───────────────────────────────────────────────────────────────────────── diff --git a/src/ternary_gemm_test.cpp b/src/ternary_gemm_test.cpp index a71cf71..6b22db7 100644 --- a/src/ternary_gemm_test.cpp +++ b/src/ternary_gemm_test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -620,7 +621,7 @@ void test_bitlinear_shapes(nn::ComputeEngine& eng, const char* tag) {70, 150, 100}, // 两向都多块 + 尾块 {256, 64, 9}, // 窄 N 刚过分派门(9 > 8 → 分块内核)+ K 整块 {256, 64, 4}, // 窄 N 落在 GEMV 门内(4 ≤ 8 → 打包 GEMV 内核) - {17, 65, 520}, // 宽 N + 行尾块 + {17, 65, 520}, // 宽 N + 行尾块(f16 缓冲 4B 对齐回归锁,P3 门 D) }; for (int p = 0; p < 2; ++p) { diff --git a/src/text_infer.cpp b/src/text_infer.cpp index 48d9151..671f9b0 100644 --- a/src/text_infer.cpp +++ b/src/text_infer.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,9 @@ void print_usage(const char* prog) help.opt("--repeat-penalty ", "重复惩罚 (>1 抑制重复;默认: 1.0=关闭)"); help.opt("--repeat-window ", "重复惩罚只看最近 n 个 token (默认: 0=全部上下文)"); help.opt("--seed ", "随机种子 (给了就固定种子 ⇒ 同 prompt 输出可复现)"); + help.opt("--no-warmup", + "跳过 JIT 预热(GPU 默认加载后跑一遍代表性 forward,\n" + "表达式预编进 kernels.nnk,消除首 token JIT 冷启动)"); help.opt("--show-tokens", "显示 token ID (调试用)"); help.opt("--gpu [索引|名称]", "启用 GPU 加速 (需要 Vulkan SDK)\n空格形式只收枚举索引;名称子串用 --gpu=<名称>,如 " @@ -82,6 +86,7 @@ struct InferConfig std::uint64_t seed = 0; bool has_seed = false; bool prompt_given = false; // --prompt / 位置参数显式出现 + bool jit_warmup = true; // --no-warmup:跳过 JIT 预热(P3/D5) bool interactive = false; bool show_tokens = false; bool gpu_enabled = false; @@ -191,6 +196,10 @@ InferConfig parse_args(int argc, char* argv[]) } cfg.repeat_window = static_cast(*v); } + else if (arg == "--no-warmup") + { + cfg.jit_warmup = false; + } else if (arg == "--seed" && i + 1 < argc) { auto v = nn::parse_number(argv[++i]); @@ -497,6 +506,13 @@ int main(int argc, char* argv[]) const std::size_t seq_len = v_seq_len; const std::size_t eos_id = tokenizer->eos_id(); + // ── JIT 预热(P3/D5):GPU 上 (seq_len,1) 全零 token-id forward → .nnk ── + { + nn::Tensor warm_x = + engine->create_tensor(seq_len, 1, nn::Precision::F32, nn::InitSpec::zero()); + NN_EXIT(nn::cli::jit_warmup(model, *engine, warm_x, cfg.jit_warmup), 1, "预热失败: "); + } + // ── 交互模式 ───────────────────────────────────────────── if (cfg.interactive) { diff --git a/src/text_train.cpp b/src/text_train.cpp index cd837c9..b068d46 100644 --- a/src/text_train.cpp +++ b/src/text_train.cpp @@ -39,6 +39,7 @@ #include #include // 训练工厂(数据集 + 样本 + 循环) #include +#include #include #include #include @@ -387,6 +388,17 @@ namespace std::cout << "训练窗口样本数: " << trainer.sample_count() << "\n" << std::endl; + // ── JIT 预热(P3/D5):GPU 上跑一遍代表性 forward(micro-batch 形状的 + // 全零 token-id 窗口;值不参与 JIT 键,形状即结构)→ .nnk 缓存。────── + { + const std::size_t micro = cfg.batch_size / cfg.accum_steps; + nn::Tensor warm_x = + engine->create_tensor(cfg.seq_len, micro > 0 ? micro : 1, nn::Precision::F32, + nn::InitSpec::zero()); + NN_EXIT(nn::cli::jit_warmup(trainer.session().model(), *engine, warm_x, cfg.jit_warmup), 1, + "预热失败: "); + } + // ── 训练循环(库层 TextTrainer;CLI 只装诊断/断点 hooks)────────────── NN_TRY_MSG(loop_r, "训练循环失败", nn::cli::run_text_train_loop(trainer, cfg, spec, tokenizer_bytes, mem_probe)); From 7dba9fca59ccbbc8304c25d38b419e289f14496d Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Thu, 5 Nov 2026 23:59:05 +0800 Subject: [PATCH 07/16] =?UTF-8?q?feat(p4):=20=E7=B2=92=E5=BA=A6=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E9=93=BA=E8=87=B3=2013=20=E5=9F=9F=EF=BC=8835=20?= =?UTF-8?q?=E5=8F=B7=20P4=20=E9=AA=8C=E6=94=B6=E9=97=A8=20E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D7 落地:export using namespace nn; 命名空间级 re-export 被 clang 接受且消费方可见——nn.core 逐符号清单删除(60 行 using → 1 行),新域模块全部薄形态(GMF + export import core + 命名空间导出) - 新增 9 域:nn.expr / nn.engine / nn.layer / nn.loss / nn.opt / nn.model / nn.train / nn.domain / nn.cli(DEPS 全部 core,无域间环) - CMake:NN_MOD_*_HEADERS 头包含图闭包(expr22/engine37/layer22/loss10/opt12/model31/train48/domain27/cli74)+ 9 个 nn_add_module 注册;nn.core 补 core_observer_ptr - 修复头自包含缺陷:cli_engine_factory.hpp 默认实参用 std::cerr 却仅 (nn.hpp include 顺序掩盖,GMF 首包即暴露)→ 改 - 门 E:13/13 BMI 构建成功;BMI↔消费方命令 diff 仅模块差异;既有 8 消费方零改动;ctest 81/81 --- CMakeLists.txt | 320 +++++++++++++++++- .../neuralnet.cpp/cli/cli_engine_factory.hpp | 3 +- modules/nn.cli.cpp | 22 ++ modules/nn.core.cpp | 58 +--- modules/nn.domain.cpp | 14 + modules/nn.engine.cpp | 18 + modules/nn.expr.cpp | 19 ++ modules/nn.layer.cpp | 11 + modules/nn.loss.cpp | 11 + modules/nn.model.cpp | 16 + modules/nn.opt.cpp | 12 + modules/nn.train.cpp | 18 + 12 files changed, 467 insertions(+), 55 deletions(-) create mode 100644 modules/nn.cli.cpp create mode 100644 modules/nn.domain.cpp create mode 100644 modules/nn.engine.cpp create mode 100644 modules/nn.expr.cpp create mode 100644 modules/nn.layer.cpp create mode 100644 modules/nn.loss.cpp create mode 100644 modules/nn.model.cpp create mode 100644 modules/nn.opt.cpp create mode 100644 modules/nn.train.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 393420c..4ad5985 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -458,7 +458,8 @@ function(nn_add_executable name) endfunction() # ══════════════════════════════════════════════════════════════════════════ -# C++ 模块:粒度导入路径(nn.core / nn.kvrecord / nn.dataset / nn.tokenizer) +# C++ 模块:粒度导入路径(P4 后 13 域:core/kvrecord/dataset/tokenizer + +# expr/engine/layer/loss/opt/model/train/domain/cli) # ══════════════════════════════════════════════════════════════════════════ # 形态 = 「薄接口」模块(research/modules-import-nn 实证,2026-11-01): # 全局模块片段文本包含域头文件(STL 与库声明留在全局模块,实体仍挂全局 @@ -486,6 +487,7 @@ endfunction() set(NN_ABST_INC engine/abstraction/include/neuralnet.cpp) set(NN_MOD_CORE_HEADERS ${NN_ABST_INC}/core_assert.hpp + ${NN_ABST_INC}/core_observer_ptr.hpp ${NN_ABST_INC}/core_config.hpp ${NN_ABST_INC}/core_errors.hpp include/neuralnet.cpp/core_file.hpp @@ -523,6 +525,311 @@ set(NN_MOD_TOKENIZER_HEADERS include/neuralnet.cpp/dataset_format.hpp include/neuralnet.cpp/model_keyvalue_record.hpp) +# ── P4 新域闭包(35 号):跨域头改动触发对应 BMI 重编;宁多勿少 ────────── +# 由头包含图解析生成(expr 22 / engine 37 / layer 22 / loss 10 / opt 12 / +# model 31 / train 48 / domain 27 / cli 74)。 +set(NN_MOD_EXPR_HEADERS + engine/abstraction/include/neuralnet.cpp/algebra_matrix.hpp + engine/abstraction/include/neuralnet.cpp/algebra_ops.hpp + engine/abstraction/include/neuralnet.cpp/algebra_span.hpp + engine/abstraction/include/neuralnet.cpp/backend/compute_gpu_storage.hpp + engine/abstraction/include/neuralnet.cpp/compute_tensor.hpp + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_config.hpp + engine/abstraction/include/neuralnet.cpp/core_domain.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/core_parallel.hpp + engine/abstraction/include/neuralnet.cpp/core_threadpool.hpp + engine/abstraction/include/neuralnet.cpp/expr_graph.hpp + engine/abstraction/include/neuralnet.cpp/expr_op_table.hpp + engine/abstraction/include/neuralnet.cpp/expr_opt.hpp + engine/abstraction/include/neuralnet.cpp/expr_spec.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + engine/engine/include/neuralnet.cpp/expr_dsl.hpp + engine/engine/include/neuralnet.cpp/expr_fold.hpp + engine/engine/include/neuralnet.cpp/expr_registry.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/tensor_schema.hpp +) +set(NN_MOD_ENGINE_HEADERS + engine/abstraction/include/neuralnet.cpp/algebra_matrix.hpp + engine/abstraction/include/neuralnet.cpp/algebra_ops.hpp + engine/abstraction/include/neuralnet.cpp/algebra_span.hpp + engine/abstraction/include/neuralnet.cpp/backend/compute_gpu_storage.hpp + engine/abstraction/include/neuralnet.cpp/compute_engine.hpp + engine/abstraction/include/neuralnet.cpp/compute_tensor.hpp + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_config.hpp + engine/abstraction/include/neuralnet.cpp/core_domain.hpp + engine/abstraction/include/neuralnet.cpp/core_env.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/core_observer_ptr.hpp + engine/abstraction/include/neuralnet.cpp/core_parallel.hpp + engine/abstraction/include/neuralnet.cpp/core_profile.hpp + engine/abstraction/include/neuralnet.cpp/core_threadpool.hpp + engine/abstraction/include/neuralnet.cpp/expr_graph.hpp + engine/abstraction/include/neuralnet.cpp/expr_op_table.hpp + engine/abstraction/include/neuralnet.cpp/expr_opt.hpp + engine/abstraction/include/neuralnet.cpp/expr_spec.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + engine/engine/include/neuralnet.cpp/backend/compute_gpu_tensor_impl.hpp + engine/engine/include/neuralnet.cpp/backend/compute_memory_pool.hpp + engine/engine/include/neuralnet.cpp/backend/compute_staging_ring.hpp + engine/engine/include/neuralnet.cpp/backend/compute_vk_backend.hpp + engine/engine/include/neuralnet.cpp/backend/compute_vk_backend_destroy.inc + engine/engine/include/neuralnet.cpp/backend/compute_vk_backend_frame.inc + engine/engine/include/neuralnet.cpp/backend/compute_vk_backend_ops.inc + engine/engine/include/neuralnet.cpp/backend/compute_vk_backend_pipelines.inc + engine/engine/include/neuralnet.cpp/backend/compute_vk_device.hpp + engine/engine/include/neuralnet.cpp/backend/compute_vk_pipeline_catalog.hpp + engine/engine/include/neuralnet.cpp/backend/compute_vk_pipeline_specs.hpp + engine/engine/include/neuralnet.cpp/int8_pack.hpp + engine/engine/include/neuralnet.cpp/kernel_cache.hpp + engine/engine/include/neuralnet.cpp/kernelgen_api.h + engine/engine/include/neuralnet.cpp/ternary_pack.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp +) +set(NN_MOD_LAYER_HEADERS + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/compute_layer.hpp + include/neuralnet.cpp/compute_layer_attention.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_layer_bitlinear.hpp + include/neuralnet.cpp/compute_layer_bundle.hpp + include/neuralnet.cpp/compute_layer_conv.hpp + include/neuralnet.cpp/compute_layer_embedding.hpp + include/neuralnet.cpp/compute_layer_feedforward.hpp + include/neuralnet.cpp/compute_layer_gpt.hpp + include/neuralnet.cpp/compute_layer_mlp.hpp + include/neuralnet.cpp/compute_layer_rapt.hpp + include/neuralnet.cpp/compute_layer_softmax.hpp + include/neuralnet.cpp/compute_layer_transformer.hpp + include/neuralnet.cpp/compute_position_encoding.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/tensor_schema.hpp +) +set(NN_MOD_LOSS_HEADERS + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_loss.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/tensor_schema.hpp +) +set(NN_MOD_OPT_HEADERS + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_config.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_optimizer.hpp + include/neuralnet.cpp/lr_scheduler.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/tensor_schema.hpp +) +set(NN_MOD_MODEL_HEADERS + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_config.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/compute_layer.hpp + include/neuralnet.cpp/compute_layer_attention.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_layer_bitlinear.hpp + include/neuralnet.cpp/compute_layer_bundle.hpp + include/neuralnet.cpp/compute_layer_conv.hpp + include/neuralnet.cpp/compute_layer_embedding.hpp + include/neuralnet.cpp/compute_layer_feedforward.hpp + include/neuralnet.cpp/compute_layer_gpt.hpp + include/neuralnet.cpp/compute_layer_mlp.hpp + include/neuralnet.cpp/compute_layer_rapt.hpp + include/neuralnet.cpp/compute_layer_softmax.hpp + include/neuralnet.cpp/compute_layer_transformer.hpp + include/neuralnet.cpp/compute_optimizer.hpp + include/neuralnet.cpp/compute_position_encoding.hpp + include/neuralnet.cpp/core_file.hpp + include/neuralnet.cpp/core_sha256.hpp + include/neuralnet.cpp/model_container.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_serialization.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/tensor_schema.hpp + include/neuralnet.cpp/train_state.hpp + include/neuralnet.cpp/training_group_session.hpp + include/neuralnet.cpp/training_session.hpp +) +set(NN_MOD_TRAIN_HEADERS + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_config.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/core_parallel.hpp + engine/abstraction/include/neuralnet.cpp/core_threadpool.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/batch_source.hpp + include/neuralnet.cpp/compute_layer.hpp + include/neuralnet.cpp/compute_layer_attention.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_layer_bitlinear.hpp + include/neuralnet.cpp/compute_layer_bundle.hpp + include/neuralnet.cpp/compute_layer_conv.hpp + include/neuralnet.cpp/compute_layer_embedding.hpp + include/neuralnet.cpp/compute_layer_feedforward.hpp + include/neuralnet.cpp/compute_layer_gpt.hpp + include/neuralnet.cpp/compute_layer_mlp.hpp + include/neuralnet.cpp/compute_layer_rapt.hpp + include/neuralnet.cpp/compute_layer_softmax.hpp + include/neuralnet.cpp/compute_layer_transformer.hpp + include/neuralnet.cpp/compute_loss.hpp + include/neuralnet.cpp/compute_optimizer.hpp + include/neuralnet.cpp/compute_position_encoding.hpp + include/neuralnet.cpp/core_file.hpp + include/neuralnet.cpp/core_sha256.hpp + include/neuralnet.cpp/dataset.hpp + include/neuralnet.cpp/dataset_format.hpp + include/neuralnet.cpp/dataset_samples.hpp + include/neuralnet.cpp/domain_tokenizer.hpp + include/neuralnet.cpp/domain_tokenizer_base.hpp + include/neuralnet.cpp/domain_tokenizer_bpe.hpp + include/neuralnet.cpp/domain_tokenizer_charbpe.hpp + include/neuralnet.cpp/lr_scheduler.hpp + include/neuralnet.cpp/model_container.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_serialization.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/rl_experience.hpp + include/neuralnet.cpp/rl_pipeline.hpp + include/neuralnet.cpp/rl_policy.hpp + include/neuralnet.cpp/run_driver.hpp + include/neuralnet.cpp/tensor_schema.hpp + include/neuralnet.cpp/text_train_config.hpp + include/neuralnet.cpp/text_trainer.hpp + include/neuralnet.cpp/train_state.hpp + include/neuralnet.cpp/training_session.hpp +) +set(NN_MOD_DOMAIN_HEADERS + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/compute_layer.hpp + include/neuralnet.cpp/compute_layer_attention.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_layer_bitlinear.hpp + include/neuralnet.cpp/compute_layer_bundle.hpp + include/neuralnet.cpp/compute_layer_conv.hpp + include/neuralnet.cpp/compute_layer_embedding.hpp + include/neuralnet.cpp/compute_layer_feedforward.hpp + include/neuralnet.cpp/compute_layer_gpt.hpp + include/neuralnet.cpp/compute_layer_mlp.hpp + include/neuralnet.cpp/compute_layer_rapt.hpp + include/neuralnet.cpp/compute_layer_softmax.hpp + include/neuralnet.cpp/compute_layer_transformer.hpp + include/neuralnet.cpp/compute_position_encoding.hpp + include/neuralnet.cpp/domain_cnn.hpp + include/neuralnet.cpp/domain_gpt.hpp + include/neuralnet.cpp/domain_mnist.hpp + include/neuralnet.cpp/domain_rla.hpp + include/neuralnet.cpp/model_container.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/tensor_schema.hpp +) +set(NN_MOD_CLI_HEADERS + engine/abstraction/include/neuralnet.cpp/algebra_matrix.hpp + engine/abstraction/include/neuralnet.cpp/algebra_ops.hpp + engine/abstraction/include/neuralnet.cpp/algebra_span.hpp + engine/abstraction/include/neuralnet.cpp/backend/compute_gpu_storage.hpp + engine/abstraction/include/neuralnet.cpp/compute_engine.hpp + engine/abstraction/include/neuralnet.cpp/compute_tensor.hpp + engine/abstraction/include/neuralnet.cpp/core_assert.hpp + engine/abstraction/include/neuralnet.cpp/core_config.hpp + engine/abstraction/include/neuralnet.cpp/core_domain.hpp + engine/abstraction/include/neuralnet.cpp/core_env.hpp + engine/abstraction/include/neuralnet.cpp/core_errors.hpp + engine/abstraction/include/neuralnet.cpp/core_parallel.hpp + engine/abstraction/include/neuralnet.cpp/core_profile.hpp + engine/abstraction/include/neuralnet.cpp/core_threadpool.hpp + engine/abstraction/include/neuralnet.cpp/expr_graph.hpp + engine/abstraction/include/neuralnet.cpp/expr_op_table.hpp + engine/abstraction/include/neuralnet.cpp/expr_opt.hpp + engine/abstraction/include/neuralnet.cpp/expr_spec.hpp + engine/abstraction/include/neuralnet.cpp/precision.hpp + engine/abstraction/include/nn_error_macros.hpp + include/neuralnet.cpp/batch_source.hpp + include/neuralnet.cpp/cli/cli_engine_factory.hpp + include/neuralnet.cpp/cli/cli_gpu_option.hpp + include/neuralnet.cpp/cli/cli_help.hpp + include/neuralnet.cpp/cli/cli_jit_warmup.hpp + include/neuralnet.cpp/cli/cli_lr_scheduler.hpp + include/neuralnet.cpp/cli/cli_mnist_io.hpp + include/neuralnet.cpp/cli/cli_precision_warning.hpp + include/neuralnet.cpp/cli/cli_train_common.hpp + include/neuralnet.cpp/cli/text_train_checkpoint.hpp + include/neuralnet.cpp/cli/text_train_config.hpp + include/neuralnet.cpp/cli/text_train_loop.hpp + include/neuralnet.cpp/cli/text_train_recovery.hpp + include/neuralnet.cpp/compute_layer.hpp + include/neuralnet.cpp/compute_layer_attention.hpp + include/neuralnet.cpp/compute_layer_base.hpp + include/neuralnet.cpp/compute_layer_bitlinear.hpp + include/neuralnet.cpp/compute_layer_bundle.hpp + include/neuralnet.cpp/compute_layer_conv.hpp + include/neuralnet.cpp/compute_layer_embedding.hpp + include/neuralnet.cpp/compute_layer_feedforward.hpp + include/neuralnet.cpp/compute_layer_gpt.hpp + include/neuralnet.cpp/compute_layer_mlp.hpp + include/neuralnet.cpp/compute_layer_rapt.hpp + include/neuralnet.cpp/compute_layer_softmax.hpp + include/neuralnet.cpp/compute_layer_transformer.hpp + include/neuralnet.cpp/compute_loss.hpp + include/neuralnet.cpp/compute_optimizer.hpp + include/neuralnet.cpp/compute_position_encoding.hpp + include/neuralnet.cpp/core_file.hpp + include/neuralnet.cpp/core_sha256.hpp + include/neuralnet.cpp/dataset.hpp + include/neuralnet.cpp/dataset_format.hpp + include/neuralnet.cpp/dataset_samples.hpp + include/neuralnet.cpp/domain_cnn.hpp + include/neuralnet.cpp/domain_gpt.hpp + include/neuralnet.cpp/domain_mnist.hpp + include/neuralnet.cpp/domain_rla.hpp + include/neuralnet.cpp/domain_tokenizer.hpp + include/neuralnet.cpp/domain_tokenizer_base.hpp + include/neuralnet.cpp/domain_tokenizer_bpe.hpp + include/neuralnet.cpp/domain_tokenizer_charbpe.hpp + include/neuralnet.cpp/lr_scheduler.hpp + include/neuralnet.cpp/model_container.hpp + include/neuralnet.cpp/model_keyvalue_record.hpp + include/neuralnet.cpp/model_serialization.hpp + include/neuralnet.cpp/model_spec.hpp + include/neuralnet.cpp/named_tensor_set.hpp + include/neuralnet.cpp/run_driver.hpp + include/neuralnet.cpp/tensor_schema.hpp + include/neuralnet.cpp/text_train_config.hpp + include/neuralnet.cpp/text_trainer.hpp + include/neuralnet.cpp/train_state.hpp + include/neuralnet.cpp/training_session.hpp +) + # ── 模块注册:nn_add_module(<短名> <模块源> DEPS <依赖短名...> HEADERS <闭包头...>) ── # 短名 = "core" / "kvrecord" / "dataset" / "tokenizer";C++ 模块全名 = nn.<短名>。 # 产出: @@ -641,6 +948,17 @@ nn_add_module(tokenizer modules/nn.tokenizer.cpp DEPS core kvrecord dataset HEADERS ${NN_MOD_TOKENIZER_HEADERS}) +# ── P4 新域(35 号):全部 DEPS core(薄模块 + 命名空间导出,DAG 无域间环)── +nn_add_module(expr modules/nn.expr.cpp DEPS core HEADERS ${NN_MOD_EXPR_HEADERS}) +nn_add_module(engine modules/nn.engine.cpp DEPS core HEADERS ${NN_MOD_ENGINE_HEADERS}) +nn_add_module(layer modules/nn.layer.cpp DEPS core HEADERS ${NN_MOD_LAYER_HEADERS}) +nn_add_module(loss modules/nn.loss.cpp DEPS core HEADERS ${NN_MOD_LOSS_HEADERS}) +nn_add_module(opt modules/nn.opt.cpp DEPS core HEADERS ${NN_MOD_OPT_HEADERS}) +nn_add_module(model modules/nn.model.cpp DEPS core HEADERS ${NN_MOD_MODEL_HEADERS}) +nn_add_module(train modules/nn.train.cpp DEPS core HEADERS ${NN_MOD_TRAIN_HEADERS}) +nn_add_module(domain modules/nn.domain.cpp DEPS core HEADERS ${NN_MOD_DOMAIN_HEADERS}) +nn_add_module(cli modules/nn.cli.cpp DEPS core HEADERS ${NN_MOD_CLI_HEADERS}) + # ── 测试/验证目标开关(默认关闭) ────────────────────────────────────── # 默认只构建应用与基准工具目标,避免生成大量验证程序; # 需要时用 -DNN_ENABLE_TESTS=ON 开启全部 gradcheck / *_test / *_probe / *_debug。 diff --git a/include/neuralnet.cpp/cli/cli_engine_factory.hpp b/include/neuralnet.cpp/cli/cli_engine_factory.hpp index e3a50cc..f976c48 100644 --- a/include/neuralnet.cpp/cli/cli_engine_factory.hpp +++ b/include/neuralnet.cpp/cli/cli_engine_factory.hpp @@ -18,7 +18,8 @@ #pragma once -#include // std::ostream 前向声明 +#include // 默认实参 std::cerr 需要完整定义(头自包含;此前被 + // nn.hpp 的 include 顺序掩盖,P4 模块 GMF 首包即暴露) #include #include diff --git a/modules/nn.cli.cpp b/modules/nn.cli.cpp new file mode 100644 index 0000000..81336bd --- /dev/null +++ b/modules/nn.cli.cpp @@ -0,0 +1,22 @@ +// ── nn.cli — C++ 模块:CLI 公共件(帮助/引擎工厂/GPU 选择/训练入口装配/预热助手) ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +export module nn.cli; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.core.cpp b/modules/nn.core.cpp index d4a55d1..9531ada 100644 --- a/modules/nn.core.cpp +++ b/modules/nn.core.cpp @@ -2,14 +2,14 @@ // 「薄接口」模块(research/modules-import-nn 实证):STL 与库头文件**声明** // 留在全局模块片段(实体挂全局模块,与 `#include` 消费方同一实体,可混合 // 链接),本模块只做一件事:把 `nn::` 命名空间的公开符号 re-export。 -// 约束:宏(NN_TRY / NN_UNWRAP / NN_ASSERT_* / NN_VECTORIZE_PRAGMA 等) -// **不可导出**——消费者按需 `#include ` 等 -// 宏头(双轨);STL 留在全局片段,消费者自带用到的 STL 头。 -// 导出清单 = 下列头文件在 `namespace nn` 内的公开 API(detail 一律不导出)。 +// 约束:宏(NN_TRY / NN_UNWRAP / NN_ASSERT_* 等)**不可导出**——消费者按需 +// `#include ` 宏岛头;STL 留在全局片段,消费者自带。 +// 导出面 = GMF 文本包含的全部 nn 公开实体(命名空间级 re-export,D7)。 module; #include #include #include +#include #include #include #include @@ -17,52 +17,4 @@ module; #include export module nn.core; -export namespace nn { - // core_errors.hpp - using nn::Error; - using nn::Result; - using nn::parse_number; - // core_config.hpp - using nn::Scalar; - using nn::BLOCK_SIZE; - using nn::EPSILON; - using nn::PARALLEL_THRESHOLD; - // precision.hpp - using nn::Precision; - using nn::precision_rank; - using nn::precision_name; - using nn::max_precision; - using nn::precision_tag; - using nn::precision_from_tag; - using nn::f16; - using nn::f32; - using nn::Elem; - using nn::elem; - using nn::acc; - using nn::PrecisionProfile; - using nn::profile_f32; - using nn::profile_master_weights; - using nn::profile_f16; - using nn::profile_t1_58; - using nn::profile_all_f16; - using nn::is_profile_f32; - // core_sha256.hpp(Sha256State / sha256_* 内部件在 detail,不导出) - using nn::sha256_hex; - // core_file.hpp - using nn::load_text_file; - using nn::sha256_file_prefix; - using nn::sha256_file_hex; - using nn::write_pod; - using nn::read_pod; - using nn::write_pod_span; - using nn::read_pod_span; - // core_threadpool.hpp - using nn::ThreadPool; - using nn::global_thread_pool; - // core_parallel.hpp - using nn::for_each; - using nn::transform; - using nn::transform_reduce; - using nn::parallel_for_blocks; - using nn::parallel_for_samples; -} +export using namespace nn; diff --git a/modules/nn.domain.cpp b/modules/nn.domain.cpp new file mode 100644 index 0000000..70563d6 --- /dev/null +++ b/modules/nn.domain.cpp @@ -0,0 +1,14 @@ +// ── nn.domain — C++ 模块:模型工厂(MNIST/GPT/RAPT-RLA/CNN) ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +#include +#include +export module nn.domain; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.engine.cpp b/modules/nn.engine.cpp new file mode 100644 index 0000000..895940a --- /dev/null +++ b/modules/nn.engine.cpp @@ -0,0 +1,18 @@ +// ── nn.engine — C++ 模块:计算引擎抽象层 + 后端 + 代数/精度原语 ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +#include +#include +#include +#include +#include +#include +export module nn.engine; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.expr.cpp b/modules/nn.expr.cpp new file mode 100644 index 0000000..b2b2dd2 --- /dev/null +++ b/modules/nn.expr.cpp @@ -0,0 +1,19 @@ +// ── nn.expr — C++ 模块:表达式 DSL / IR / 张量视图 ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +#include +#include +#include +#include +#include +#include +#include +export module nn.expr; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.layer.cpp b/modules/nn.layer.cpp new file mode 100644 index 0000000..852143a --- /dev/null +++ b/modules/nn.layer.cpp @@ -0,0 +1,11 @@ +// ── nn.layer — C++ 模块:层家族(MLP/卷积/注意力/Transformer/GPT/RAPT/三值…)+ 位置编码 ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +export module nn.layer; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.loss.cpp b/modules/nn.loss.cpp new file mode 100644 index 0000000..1a36897 --- /dev/null +++ b/modules/nn.loss.cpp @@ -0,0 +1,11 @@ +// ── nn.loss — C++ 模块:损失函数 ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +export module nn.loss; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.model.cpp b/modules/nn.model.cpp new file mode 100644 index 0000000..7be4683 --- /dev/null +++ b/modules/nn.model.cpp @@ -0,0 +1,16 @@ +// ── nn.model — C++ 模块:模型容器 / 规格 / 序列化 / 训练会话 ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +#include +#include +#include +#include +export module nn.model; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.opt.cpp b/modules/nn.opt.cpp new file mode 100644 index 0000000..680ea79 --- /dev/null +++ b/modules/nn.opt.cpp @@ -0,0 +1,12 @@ +// ── nn.opt — C++ 模块:优化器 + 学习率调度 ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +export module nn.opt; +export import nn.core; +export using namespace nn; diff --git a/modules/nn.train.cpp b/modules/nn.train.cpp new file mode 100644 index 0000000..bb2384f --- /dev/null +++ b/modules/nn.train.cpp @@ -0,0 +1,18 @@ +// ── nn.train — C++ 模块:训练工厂与数据流(TextTrainer/RunDriver/样本/RL 管线) ────────── +// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 +// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 +// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 +// 约束:宏不可导出(消费方 include 宏岛头);STL +// 留在全局片段(消费方自带)。 +module; +#include +#include +#include +#include +#include +#include +#include +#include +export module nn.train; +export import nn.core; +export using namespace nn; From 7676e7351ecdb7441b922ad8add64cce6aea7b8b Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Fri, 6 Nov 2026 07:41:49 +0800 Subject: [PATCH 08/16] =?UTF-8?q?refactor(P5):=20src/=20=E5=85=A8=E9=87=8F?= =?UTF-8?q?=20import=20=E5=8C=96=EF=BC=88D8=20+=20=E9=97=A8=20F=EF=BC=89+?= =?UTF-8?q?=20ccache=C3=97=E6=A8=A1=E5=9D=97=E6=A0=B9=E5=9B=A0=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20+=20clang=2024=20bug=20=E8=A7=84=E9=81=BF=EF=BC=88C?= =?UTF-8?q?6=EF=BC=89+=20PCH=20=E6=9C=BA=E5=88=B6=E5=88=A0=E9=99=A4?= =?UTF-8?q?=EF=BC=88D9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D8 单点接线:nn_add_executable 自动挂全部域模块(-fmodule-file 链),删 8 处显式 nn_use_modules - src/ 79 TU 全量 import 化:门 F 归零(angle/quoted/相对形式 include 全 0,79/79 含 -fmodule-file);import 一律置尾、using 随后(GMF×系统头双包含规避);examples 2 处库外样例按设计保留文本 include - 13 域模块导出定型:GMF includes + export import 依赖链 + export namespace nn 镜像嵌套全限定 using(D7 证伪命名空间级导出后的唯一可行形态) - 链接修复:text_train_config 三函数 inline(消费方无文本包含,需由 BMI 发射定义) - ccache×模块根因修复:模块消费方目标禁编译 launcher——ccache 4.13 手册明言不支持 C++20 modules,pcm 内容不进 key,BMI 变更后吐旧 .o(text_train 的 parse_text_train_args undefined 即此);正确性由 ninja 依赖保证 - clang 24 上游 bug 规避:tokenizer_consistency 的 old markers 改 string_view 打破与库构造同形——「多模块 import × 同 TU 两个 identical static local 构造」组合 -O0/-O3 均产出错值(纯代码与 gcc 全过、写法经 cppreference 确认无 UB);触发矩阵与最小复现 = 36 号台账 C6,待上游修复后撤除特意差异 - D9:PCH 候选清零 → 整体删除 nn_pch 机制/cmake/nn_pch_stub.cpp/NN_ENABLE_PCH(含 presets 与候选登记剔除逻辑);ccache sloppiness 注释改写(pch_defines 转无害冗余) - 宏岛头次序修复随 submodule 指针 19bc1e0:core_errors 必须先于 core_assert(fatal_at 引用 fail_abort,PCH 移除后暴露的潜伏缺陷) - doc_audit:scan_exprs/fused_generate 历史引用与 git --depth 入白名单(带来历);35 号只读实施文档头部豁免;36 号头部合规化;AGENTS + internal/README 登记 35/36 - 验收(docs/internal/35 门 F):ctest 81/81 + doc_audit 全绿 + 全量构建 0 错 --- AGENTS.md | 2 + CMakeLists.txt | 119 ++----- CMakePresets.json | 1 - cmake/nn_pch_stub.cpp | 18 - .../35-modules-migration-implementation.md | 311 ++++++++++++++++++ docs/internal/36-compromise-ledger.md | 7 +- docs/internal/README.md | 1 + engine | 2 +- .../neuralnet.cpp/cli/text_train_config.hpp | 6 +- modules/nn.cli.cpp | 44 ++- modules/nn.core.cpp | 65 +++- modules/nn.dataset.cpp | 50 ++- modules/nn.domain.cpp | 43 ++- modules/nn.engine.cpp | 83 ++++- modules/nn.expr.cpp | 180 +++++++++- modules/nn.kvrecord.cpp | 14 +- modules/nn.layer.cpp | 75 ++++- modules/nn.loss.cpp | 15 +- modules/nn.model.cpp | 55 +++- modules/nn.opt.cpp | 31 +- modules/nn.tokenizer.cpp | 28 +- modules/nn.train.cpp | 86 ++++- scripts/doc_audit.py | 6 + src/adam_int8_test.cpp | 22 +- src/adam_quant_test.cpp | 21 +- src/assert_always_test.cpp | 14 +- src/assert_test.cpp | 13 + src/attn_consistency_test.cpp | 17 +- src/attn_gradcheck.cpp | 19 +- src/attn_test.cpp | 13 + src/attn_w_batch_test.cpp | 17 +- src/batch_source_test.cpp | 3 +- src/batchnorm_test.cpp | 19 +- src/bitnet_struct_test.cpp | 18 +- src/ce_fusion_test.cpp | 24 +- src/chat_prompt_test.cpp | 15 +- src/cli_precision_warning_test.cpp | 15 +- src/cnn_smoke_test.cpp | 17 +- src/cnn_test.cpp | 15 + src/conv2d_gradcheck.cpp | 19 +- src/cross_attention_test.cpp | 14 +- src/dataset_convert.cpp | 5 +- src/dataset_gen.cpp | 5 +- src/dataset_integration_test.cpp | 14 +- src/dataset_samples_test.cpp | 14 +- src/dataset_test.cpp | 14 +- src/doc_attn_e2e_test.cpp | 19 +- src/dsl_shape_infer_test.cpp | 22 +- src/embedding_test.cpp | 14 +- src/error_macro_test.cpp | 13 + src/expr_dsl_test.cpp | 13 +- src/expr_fold_test.cpp | 9 +- src/expr_fused_key_test.cpp | 11 +- src/expr_matmul_test.cpp | 9 +- src/expr_opt_test.cpp | 9 +- src/expr_reduce_test.cpp | 8 +- src/f16_compute_test.cpp | 20 +- src/f16_cpu_probe.cpp | 14 +- src/f16_precision_test.cpp | 18 +- src/f16_writeback_probe.cpp | 21 +- src/file_test.cpp | 2 +- src/fused_gpu_test.cpp | 26 +- src/gpt_checkpoint_test.cpp | 16 +- src/gpt_gradcheck.cpp | 19 +- src/gpu_batch_scope_test.cpp | 17 +- src/gpu_stability_probe.cpp | 16 +- src/host_bridge_test.cpp | 17 +- src/int8_act_test.cpp | 24 +- src/jit_demo.cpp | 20 +- src/kernel_cache_test.cpp | 8 +- src/kvrec_test.cpp | 7 +- src/layer_bench.cpp | 19 +- src/layer_broadcast_test.cpp | 14 +- src/layer_lifetime_test.cpp | 14 +- src/lifetime_contract_test.cpp | 14 +- src/lr_scheduler_test.cpp | 24 +- src/maxpool_gradcheck.cpp | 19 +- src/mnist_infer.cpp | 27 +- src/mnist_train.cpp | 27 +- src/model_roles_test.cpp | 18 +- src/model_spec_validation_test.cpp | 22 +- src/nn_warmup.cpp | 17 +- src/nnvocab_test.cpp | 14 +- src/optimizer_state_test.cpp | 14 +- src/optimizer_test.cpp | 20 +- src/parallel_test.cpp | 2 +- src/precision_profile_test.cpp | 14 +- src/precision_test.cpp | 15 + src/precision_type_test.cpp | 16 +- src/rapt_checkpoint_test.cpp | 16 +- src/rapt_gradcheck.cpp | 19 +- src/rapt_smoke_test.cpp | 17 +- src/rapt_test.cpp | 4 +- src/rl_experience_test.cpp | 14 +- src/rl_gpu_smoke_test.cpp | 15 +- src/rl_grpo_test.cpp | 14 +- src/rl_output_test.cpp | 16 +- src/rl_pipeline_test.cpp | 14 +- src/rl_policy_test.cpp | 14 +- src/rl_ppo_test.cpp | 14 +- src/rl_training_integration_test.cpp | 14 +- src/rmsnorm_gradcheck.cpp | 19 +- src/run_driver_test.cpp | 10 +- src/sampling_test.cpp | 17 +- src/session_checkpoint_test.cpp | 14 +- src/sha256_test.cpp | 6 +- src/softmax_gradcheck.cpp | 19 +- src/swiglu_gradcheck.cpp | 20 +- src/t1_58_test.cpp | 17 +- src/tensor_expr_test.cpp | 18 +- src/tensor_precision_test.cpp | 16 +- src/tensor_schema_test.cpp | 16 +- src/ternary_gemm_test.cpp | 28 +- src/test_common.hpp | 16 +- src/text_infer.cpp | 23 +- src/text_train.cpp | 42 ++- src/text_train_modules_test.cpp | 22 +- src/text_trainer_test.cpp | 15 +- src/threadpool_test.cpp | 2 +- src/tokenizer_consistency_test.cpp | 24 +- src/tokenizer_infer.cpp | 5 +- src/tokenizer_train.cpp | 7 +- src/train_state_test.cpp | 16 +- src/training_group_session_test.cpp | 14 +- src/training_session_test.cpp | 14 +- src/write_epoch_test.cpp | 22 +- 126 files changed, 2383 insertions(+), 481 deletions(-) delete mode 100644 cmake/nn_pch_stub.cpp create mode 100644 docs/internal/35-modules-migration-implementation.md diff --git a/AGENTS.md b/AGENTS.md index a227c03..9d6ea38 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -441,6 +441,8 @@ auto report = NN_UNWRAP(session.train_step( | `internal/32-trainer-skeleton-and-openspec.md` | **领域无关训练骨架(RunDriver)与开放模型规格(ModelSpecBase,✅ 已实施)**:在单步 Session(30)与 TextTrainer 之间补领域无关骨架层;封闭枚举 `ModelType` 扩展为开放多态;涵盖 issue #39(Trainer 骨架)与 #26(ModelSpec 扩展点) | | `internal/33-kernel-cache-and-engine-decouple.md` | **运行期 JIT 算子缓存与引擎解耦(2026-11-01)**:`.nnk` 算子缓存(KVRecord v2)+ 版本化生成器 `.so`(内嵌 glslang)miss 现编 + `model_warmup()` 预热;论证 AOT 闭合世界 → 预热加速 + JIT 兜底(`NN_STRICT_CLOSED_WORLD=1` 保留看门狗) | | `internal/34-engine-link-abi-boundary.md` | **引擎 .so 化边界清单**:双引擎 `libnn-cpu.so` / `libnn-gpu.so` + 抽象层 C++ 直出 ABI(E2/E3 实施偏离 = 无句柄映射,见 §10.4)+ LGPL 可替换验证;实施记录 §10 | +| `internal/35-modules-migration-implementation.md` | **src/ 全量 import 化与 AOT → JIT 预热实施文档(只读,用户提供)**:P0–P6 分阶段任务、验收门 A–G、D1–D9;配套妥协台账 = 36 号 | +| `internal/36-compromise-ledger.md` | **模块迁移妥协式设计台账**:为解锁验收门引入的绕行逐条登记(C1–C6),**门 G 前必须逐条消灭、不得转正**;根因级修复不入表 | | `internal/19-unified-dataset.md` | **统一数据集格式与加载(`.nndataset`/`.nnvocab`/KVRecord v2,2026-10-05 设计定稿、阶段一~三已实施):裁决记录、KVRecord v2 规范、文件布局、`nn::Dataset`/`dataset_gen`/`dataset_convert`/模型 v6 分期落地与兼容迁移。接数据集相关任务前读本文** | | `internal/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` 变体) | | `internal/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`,**不是** `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`)是两条正交的腿** | diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ad5985..319cb56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,18 +202,13 @@ if(NOT NN_COMPILER_LAUNCHER AND NOT CMAKE_CXX_COMPILER_LAUNCHER AND PROJECT_IS_T endif() endif() if(NN_COMPILER_LAUNCHER AND (NN_COMPILER_LAUNCHER_EXPLICIT OR PROJECT_IS_TOP_LEVEL)) - # ── ccache × PCH:把 sloppiness 钉进编译命令 ────────────────────────── - # ccache 手册《Precompiled headers》要求 Clang 侧**三项同时满足**才能缓存 - # "用了 PCH 的 TU":① sloppiness 含 `pch_defines,time_macros`;② 以 - # `-include-pch` 使用 PCH(CMake 就是这么传的);③ 编译带 - # `-fno-pch-timestamp`(见文件末尾 PCH 落地块)。缺 ①/③ 时 ccache 对**每个** - # 用 PCH 的 TU 都记 "Could not use precompiled header" → 全部不可缓存, - # 而本仓 42+ 个目标的编译**全部**走 PCH,等于缓存形同虚设。 - # 这里用 `cmake -E env` 把 sloppiness 钉在命令前缀里,**不依赖用户全局 - # ccache 配置**(避免"换个机器/换个人就悄悄失效,还看不出慢在哪")。 - # 两项 sloppiness 对本仓安全:全仓无 `__DATE__` / `__TIME__` / `__TIMESTAMP__` - # (`pch_defines` 只在"用 PCH + 源内 #define 影响 PCH 内容"的写法下才有风险, - # 而 PCH 是强制前置包含、源内 #define 影响不到它)。 + # ── ccache:把 sloppiness 钉进编译命令 ──────────────────────────────── + # `time_macros`:本仓无 `__DATE__`/`__TIME__`/`__TIMESTAMP__`,钉上是防御性 + # 配置(防未来头引入时间宏后 ccache 静默退化为预处理模式不命中)。 + # `pch_defines`:本仓共享 PCH 已随 35 号 D9 删除,此项仅作无害冗余保留 + #(若个别环境的工具链自带前置包含仍用得上)。 + # 用 `cmake -E env` 钉在命令前缀里,**不依赖用户全局 ccache 配置** + #(避免"换个机器/换个人就悄悄失效,还看不出慢在哪")。 if(NN_COMPILER_LAUNCHER MATCHES "ccache") # ⚠ 必须写成**单个**字符串:写成两个相邻的带引号参数会变成 CMake 列表 # (分号连接)→ 命令行里被 `sh` 当成第二个命令,实测报 @@ -429,31 +424,25 @@ add_library(neuralnet::nn ALIAS neuralnet_cpp) # ── 预编译头(PCH):本项目编译时间的最大头 ────────────────────────────── # 库是 header-only,每个 TU 都要把 `nn.hpp`(→ 全部 Layer/Loss/Optimizer/ # Engine + 构建期嵌入的 36 份原语 SPIR-V + AOT 融合注册表,合计 ~7MB 头文本) -# 从头 parse 一遍。实测(Debug / clang / 本机)单 TU: -# 无 PCH 17.5s → 有 PCH 9.8s(-44%)。 -# 所有候选目标的编译条件完全相同 → 只编**一份** PCH 供它们复用(REUSE_FROM): -# 31 份逐目标 PCH 合计约 780s CPU,共享后只剩 1 份。见文件末尾的落地块。 -# 关掉:-DNN_ENABLE_PCH=OFF。 -# 候选 = 经 `nn_add_executable` 建的应用/测试目标(历史上的 NN_EXPR_SCAN -# 收集器不套 PCH 的约束已随 AOT 收集链删除,35 号 D4)。 -option(NN_ENABLE_PCH "Precompile nn.hpp once and reuse it across targets" ON) +# 从头 parse 一遍。历史上用共享 PCH(`nn.hpp` 预编译一次 + `REUSE_FROM`)提速; +# src/ 全量模块化(35 号 D8)后消费方改经 `-fmodule-file` 取声明、候选清零, +# PCH 机制整体删除(35 号 D9)。 # ── 便捷函数:统一添加可执行目标 ──────────────────────────────────────── -# PCH **不在这里挂**:目标编译条件(NN_HAS_VULKAN / include 目录)要到文件 -# 末尾的 GPU 循环才定型,提前挂会与共享 PCH 目标的属性对不上(REUSE_FROM -# 要求两边完全一致)。这里只登记候选,末尾统一落地。 function(nn_add_executable name) add_executable(${name} ${ARGN}) target_link_libraries(${name} PRIVATE nn_core) # GPU 块按这份名单统一挂 NN_HAS_VULKAN / Vulkan::Vulkan。名单与创建点 # 同源,避免"测试目标只在 NN_ENABLE_TESTS=ON 时才进 GPU 名单"这类漏配: # 编译门是 NN_BUILD_APPS OR NN_ENABLE_TESTS(默认 Release 也会编出全部 - # 测试程序),漏挂时共享 PCH 里的 NN_HAS_VULKAN 会泄漏进 TU,表现为 - # 链接期整屏 `undefined reference to vk*`。 + # 测试程序)。 list(APPEND NN_EXECUTABLE_TARGETS ${name}) set(NN_EXECUTABLE_TARGETS ${NN_EXECUTABLE_TARGETS} PARENT_SCOPE) - if(NN_ENABLE_PCH) - set(NN_PCH_CANDIDATES ${NN_PCH_CANDIDATES} ${name} PARENT_SCOPE) + # P5/D8(35 号):单点接线——全部可执行目标自动挂全部域模块 + #(-fmodule-file 链)。同 TU 混用(import + 文本 include 同头)已实测 + # 合法(thin 模块全局实体)。 + if(DEFINED NN_ALL_MODULES) + nn_use_modules(${name} ${NN_ALL_MODULES}) endif() endfunction() @@ -929,8 +918,17 @@ function(nn_use_modules target) add_dependencies(${target} nn_${m}_bmi) endif() endforeach() - list(REMOVE_ITEM NN_PCH_CANDIDATES ${target}) - set(NN_PCH_CANDIDATES ${NN_PCH_CANDIDATES} PARENT_SCOPE) + # ── 模块消费方必须绕开编译 launcher(ccache/sccache)──────────────── + # 根因(ccache 4.13 手册明言 "does currently not support standard C++ + # modules"):ccache 的 key 不含 -fmodule-file 的 BMI 内容——direct 模式 + # 的 depfile 只记录**文本包含**的头,import 进来的头(公共头改 inline、 + # 改实现)完全不可见。于是"BMI 重编 + 消费方源码没动"时 ccache 会吐出 + # **inline 语义改动前的旧 .o**(实测:text_train.cpp 的 + # parse_text_train_args 加 inline 后,dev 目录链出 undefined symbol、 + # 绕过 ccache 手工编译即正常)。禁用后正确性由 ninja 依赖(源 + BMI + # mtime + depfile)保证;命中率损失可忽略——BMI 变化与文本头变化强相关, + # 两种口径下消费方本来都会真实重编。 + set_property(TARGET ${target} PROPERTY RULE_LAUNCH_COMPILE "") endfunction() # ── 四个叶子域模块(DAG:core → kvrecord → dataset → tokenizer)────────── @@ -959,6 +957,11 @@ nn_add_module(train modules/nn.train.cpp DEPS core HEADERS ${NN_MOD_TRAIN_ nn_add_module(domain modules/nn.domain.cpp DEPS core HEADERS ${NN_MOD_DOMAIN_HEADERS}) nn_add_module(cli modules/nn.cli.cpp DEPS core HEADERS ${NN_MOD_CLI_HEADERS}) +# 全域清单(D8 单点接线用;保持与上方注册一致——新增域必须同步追加) +set(NN_ALL_MODULES + core kvrecord dataset tokenizer + expr engine layer loss opt model train domain cli) + # ── 测试/验证目标开关(默认关闭) ────────────────────────────────────── # 默认只构建应用与基准工具目标,避免生成大量验证程序; # 需要时用 -DNN_ENABLE_TESTS=ON 开启全部 gradcheck / *_test / *_probe / *_debug。 @@ -1007,21 +1010,17 @@ if(NN_BUILD_APPS) nn_add_executable(tokenizer_train src/tokenizer_train.cpp) nn_add_executable(tokenizer_infer src/tokenizer_infer.cpp) list(APPEND NN_APP_TARGETS text_train text_infer tokenizer_train tokenizer_infer) - nn_use_modules(tokenizer_train tokenizer) - nn_use_modules(tokenizer_infer tokenizer) - + # ── 数据集工具(生成器 + 旧格式转换器)──────────────────────────────── nn_add_executable(dataset_gen src/dataset_gen.cpp) nn_add_executable(dataset_convert src/dataset_convert.cpp) list(APPEND NN_APP_TARGETS dataset_gen dataset_convert) - nn_use_modules(dataset_gen tokenizer) - + # ── 运行期 JIT 演示(Phase 2;docs/internal/33)─────────────── # 展示"不在内嵌注册表的表达式 → JIT 现编 + .nnk 缓存命中 + CPU 对拍"。 # 仅演示用,不注册 ctest、不 install。 nn_add_executable(jit_demo src/jit_demo.cpp) - nn_use_modules(dataset_convert tokenizer) - + # ── 构建期/发布期 JIT 预热目标(P3/D5,docs/internal/35)───────────────── # 常用架构 × 代表性 shape 过 GPU forward → kernels.nnk(随发布物分发, # 部署首跑免 kernelgen 现编;与各 app 的 model_warmup/--no-warmup 配套)。 @@ -1392,11 +1391,7 @@ if(NN_BUILD_APPS OR NN_ENABLE_TESTS) list(APPEND NN_TEST_TARGETS nnvocab_test) nn_add_executable(dataset_integration_test src/dataset_integration_test.cpp) list(APPEND NN_TEST_TARGETS dataset_integration_test) - nn_use_modules(dataset_test dataset) - nn_use_modules(tokenizer_test tokenizer) - nn_use_modules(nnvocab_test tokenizer) - nn_use_modules(dataset_integration_test tokenizer) - + endif() # ── 测试目标统一处理:输出到 <构建根>/test ────────────────────────────── @@ -1533,45 +1528,3 @@ if(TARGET nn_gpu) target_compile_definitions(nn_core INTERFACE NN_GPU_ENGINE=1) endif() -# ══════════════════════════════════════════════════════════════════════════ -# PCH 落地(所有目标创建完、编译条件定型之后再挂) -# ══════════════════════════════════════════════════════════════════════════ -# 候选目标的编译条件逐项相同(nn_core 的 include:主仓库 include/ + -# submodule abstraction/),因此**一份 PCH 全目标共享**(REUSE_FROM)。 -# 引擎 .so 化后 nn.hpp 不再含引擎实现/shader 嵌入头 → PCH 体积大幅缩小。 -# 代价对比(本机 Debug 全量构建,旧版):31 份逐目标 PCH ≈ 780s CPU → 1 份。 -if(NN_ENABLE_PCH AND NN_PCH_CANDIDATES) - add_library(nn_pch OBJECT ${CMAKE_CURRENT_SOURCE_DIR}/cmake/nn_pch_stub.cpp) - target_link_libraries(nn_pch PRIVATE nn_core) - target_precompile_headers(nn_pch PRIVATE [["neuralnet.cpp/nn.hpp"]]) - - # ── ccache × PCH 的第三项要求:`-fno-pch-timestamp` ──────────────────── - # ccache 手册(Precompiled headers)明确:Clang 侧必须用该选项编 PCH,否则 - # ccache 拒绝把"用了 PCH 的编译"入缓存(统计计数 Could not use precompiled - # header)。它会让 PCH 不嵌写入时间戳 —— 顺带让 PCH 内容可复现。 - # ⚠ 这是 cc1 开关:驱动不认 `-fno-pch-timestamp`,必须 `-Xclang` 形式;ccache - # 是逐 token 匹配该字面量,`-Xclang -fno-pch-timestamp` 满足它的检查 - # (实测:开了之后同一 TU 从 30.2s 掉到 0.11s,见 docs/history/history.md)。 - # ⚠ PCH 生成方(nn_pch)与所有使用方必须带同一份选项:CMake 的 REUSE_FROM - # 要求双方编译选项一致,故下面两处都加(对非同族的 sccache 也无害)。 - # ⚠ 已知未覆盖:GCC 侧要缓存"用了 PCH 的 TU"须另加 `-fpch-preprocess`(手册), - # 它改变预处理输出、影响 depfile 口径,故本轮不擅自打开——GCC + PCH 的编译 - # 仍不可缓存(CI 用 Clang,不受影响)。要用就显式 -DNN_COMPILER_LAUNCHER 自己加。 - set(NN_PCH_COMPAT_OPTIONS "") - if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - set(NN_PCH_COMPAT_OPTIONS "-Xclang" "-fno-pch-timestamp") - endif() - target_compile_options(nn_pch PRIVATE ${NN_PCH_COMPAT_OPTIONS}) - - set(NN_PCH_OWN_TARGETS "") - foreach(_pch_tgt IN LISTS NN_PCH_CANDIDATES) - target_compile_options(${_pch_tgt} PRIVATE ${NN_PCH_COMPAT_OPTIONS}) - target_precompile_headers(${_pch_tgt} REUSE_FROM nn_pch) - endforeach() - list(LENGTH NN_PCH_CANDIDATES _nn_pch_n) - list(LENGTH NN_PCH_OWN_TARGETS _nn_pch_own) - message(STATUS "PCH: nn.hpp precompiled once, shared by " - "${_nn_pch_n} targets (${_nn_pch_own} own PCH)") -elseif(NN_ENABLE_PCH) - message(STATUS "PCH: no candidate targets") -endif() diff --git a/CMakePresets.json b/CMakePresets.json index ed5bc14..612332e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -15,7 +15,6 @@ "CMAKE_BUILD_TYPE": "Release", "NN_BUILD_APPS": "ON", "NN_ENABLE_NATIVE": "ON", - "NN_ENABLE_PCH": "ON", "NN_ENABLE_TESTS": "OFF", "NN_ENABLE_PROBES": "OFF", "NN_ENABLE_GPU": "AUTO" diff --git a/cmake/nn_pch_stub.cpp b/cmake/nn_pch_stub.cpp deleted file mode 100644 index 8c7ce4b..0000000 --- a/cmake/nn_pch_stub.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// ─────────────────────────────────────────────────────────────────────────── -// nn_pch_stub.cpp — PCH 宿主 TU(仅供 CMake 的 `target_precompile_headers -// REUSE_FROM` 使用,见 CMakeLists.txt 末尾「PCH 落地」块)。 -// -// 本文件不做任何事:对象库 `nn_pch` 的唯一存在意义,是把 `nn.hpp` 预编译成 -// **一份** PCH,再由所有编译条件相同的应用/测试目标 `REUSE_FROM nn_pch` -// 共享(逐目标各编一份的话,本机 Debug 全量构建要为此花掉 ~780s CPU)。 -// 它不参与任何可执行文件,也不导出任何符号。 -// -// 注:这里不能写成空文件——`-Wpedantic -Werror` 下空翻译单元会直接编译失败。 -// ─────────────────────────────────────────────────────────────────────────── - -namespace nn::detail::pch_stub -{ - -[[maybe_unused]] inline void anchor() noexcept {} - -} // namespace nn::detail::pch_stub diff --git a/docs/internal/35-modules-migration-implementation.md b/docs/internal/35-modules-migration-implementation.md new file mode 100644 index 0000000..e4fc52e --- /dev/null +++ b/docs/internal/35-modules-migration-implementation.md @@ -0,0 +1,311 @@ +# 35 · src/ 全量 import 化与 AOT 收集 → JIT 预热 实施文档 + +> 状态:**方案已评审,待执行**。目标读者:实施者(人或 agent)。本文档自包含,可脱离会话上下文直接照做。 +> 关联文档:`research/modules-import-nn/README.md`(研究与证伪记录)、`docs/internal/33-kernel-cache-and-engine-decouple.md`(JIT/缓存设计)、`docs/development/01-cmake-integration.md`(模块接入)、`AGENTS.md`(能力总览与铁律)。 + +--- + +## 0. 背景与根因 + +### 0.1 直接诱因:.pcm 编译选项不一致(config mismatch) + +``` +error: POSIX thread support was disabled in precompiled file '.../nn.tokenizer.pcm' + but is currently enabled +error: module file '.../nn.tokenizer.pcm' cannot be loaded due to a configuration + mismatch with the current compilation [-Wmodule-file-config-mismatch] +``` + +根因:BMI(`.pcm`)由 `nn_add_module` 内的**手写自定义命令**编译(`CMakeLists.txt` L537-L591),不链接任何 target;消费方经 `nn_core` 的 `Threads::Threads`(L351,`THREADS_PREFER_PTHREAD_FLAG ON`,L255-L256)拿到 `-pthread`。两边选项不同 → PCM 加载被拒。后续 `api-level.h`、`sys/cdefs.h` 等头文件的 `unknown type name 'define'`、`expected ';'` 全是**级联误报**(PCM 拒载破坏预处理器状态),修好首错即全部消失——**不得去改 NDK/libc++ 头**。 + +### 0.2 已核实的选项差异全表(P0 的靶子) + +| # | 差异项 | 消费方 | BMI `_flags`(L552-L562) | 危害 | +|---|--------|--------|---------------------------|------| +| 1 | `-pthread` | 有(Threads::Threads) | 无 | **config mismatch(本次报错)** | +| 2 | `${CMAKE_CXX_FLAGS}`(调用方/工具链:NDK `--target=`、`-stdlib=` 等;L7 注明由调用方指定) | 有(CMake 自动拼) | **完全没拼** | 工具链/stdlib 不一致 | +| 3 | `${CMAKE_CXX_FLAGS_}`(Release `-DNDEBUG`、Debug `-g`) | 有 | 无 | `assert()` 双语义 → ODR 风险 | +| 4 | `-DNN_GPU_ENGINE=1`(GPU 构建时 L1192 挂 `nn_core` INTERFACE) | 有 | 无(模块注册 L615 早于 `add_subdirectory(engine)` L1178) | 宏语义分叉 | +| 5 | `-I` 目录 | `nn_core` 5 个(L345-L350,含 `engine/engine/include`×2) | 只有 3 个 | 头解析路径不一致 | +| 6 | `-flto=thin`(L145) | 有 | 无 | 不触发本次报错(`--precompile` 无 codegen),但应同源 | + +已确认**一致**的:`-std=c++26`(`CMAKE_CXX_EXTENSIONS OFF` + `cxx_std_26`)、`-fno-exceptions`、`-fexperimental-library`、`-Wno-pass-failed`、`-Wall/-Wextra/-Wpedantic/-Werror`、优化档(`${DEBUG/RELEASE_FLAGS}` 含 `-march=native`)、`-DNN_ENABLE_PROBES`。`NN_USE_MODULES` 宏**刻意不加**给 BMI(任何头都不读它,已全仓 grep 证实;模块单元源码永远走 `#include` 分支)。 + +--- + +## 1. 目标与范围 + +### 1.1 目标(用户定调,三条) + +1. **修复编译选项不统一**(第 0.2 节全表归零)。 +2. **`src/` 目录源代码不再出现 `#include `,一律 `import nn.<域名>`**;覆盖 107 个 `.cpp` 与本地头 `src/expr_fuse_shapes.hpp`。 +3. **不保留 `NN_ENABLE_MODULES` 开关、不做任何编译器识别**——其他支持 C++26 的编译器早就支持 modules,不支持的直接编译报错。 + +附带(用户提议、已纳入设计):**删除 AOT 表达式收集链(`NN_EXPR_SCAN`),改为 JIT 预热**(运行期 `model_warmup()` 接线 + 构建期 `nn_warmup` 预热目标),借此解锁 engine/layers/model/train 域的模块化。 + +### 1.2 范围外(明确不改) + +- `tests/`、`tools/`、`bench/`、`examples/`(含 downstream 独立工程):继续 `#include `,门面 `neuralnet::nn` 不动。 +- 库头文件内部的宏用法(`NN_TRY` 密集出现在 `compute_layer_rapt.hpp` 等):头只被模块 GMF 文本包含,宏在模块 TU 内部合法,**零改动**。 +- `CMAKE_CXX_SCAN_FOR_MODULES OFF`(L58-L67)与显式 BMI 自定义命令:维持(约 200 个非模块 TU 零扫描的成本决策)。 +- `NN_TEST_COUNTER` 钩子(22 个测试文件 `#define` 后 include `src/test_common.hpp`):本地头文本 include,不受禁令影响。 + +--- + +## 2. 关键既定事实(实施依据,均已核实) + +### 2.1 JIT / AOT 现状 + +- `model_warmup(Model&, const Tensor&)` 已实现:`include/neuralnet.cpp/model_container.hpp` L350-L362——跑一遍代表性 forward,把用到的表达式现编进 `.nnk` 缓存。**当前 `src/` 无任何调用方**。 +- JIT 架构设计见 `docs/internal/33` L39-L59;L125-L131 明确「`model_warmup()` 可拆掉 scan_exprs 的 FusedAnchor 自登记、dry-run、模型 pass、`NN_EXPR_SCAN`、glslc 构建步和 `nn_enable_gpu_fusion` 收集器;首次预热编译,之后共享缓存秒命中」。 +- JIT 正确性已有回归:`src/fused_gpu_test.cpp` L563-L603(未注册表达式 → `libnn-kernelgen.so` 现编 → 执行 → 持久化,与 CPU 结果一致,`--hash` 位级对拍)。 +- GPU 路径描述(AGENTS.md L483-L485):`dsl::compute*` → 折成 `ExprSpec` 查构建期注册表 → miss 默认运行期 JIT → **`fold / reduce / into / multi` 入口 miss 硬报错** → `NN_STRICT_CLOSED_WORLD=1` 时主路径也硬报错(细节以 `docs/development/04` L863-L876 为准,**P3 需代码级实测复核**)。 +- CPU 路径模板直接求值,不依赖注册表(`docs/internal/02` L171:CPU no-op)。 +- 收集器 `scan_exprs.cpp` 在源文件内 include 前 `#define NN_EXPR_SCAN`,不套 PCH、不进模块(L427-L430);**不在 `src/` 下**(`src/` 仅 `expr_fuse_shapes.hpp:7` 注释提及)。 + +### 2.2 宏(模块化的关键约束) + +- 宏不可随模块导出(C++ 标准):`modules/nn.core.cpp` L5-L7 明注。 +- `core_errors.hpp` / `core_assert.hpp` 位于 submodule `engine/abstraction/include/neuralnet.cpp/`(L487-L490 的 `NN_ABST_INC`)。 +- 语义(依 `docs/development/02` L86-L122): + - `NN_TRY(expr)` / `NN_TRY(decl, expr)`:失败 **从当前函数 early-return** `std::unexpected`(带值传播)——**函数无法替代**; + - `NN_TRY_MSG(decl, "ctx", expr)`:同上带语境;`NN_TRY_CHECK(x)`:检查已声明变量(副作用表达式会重复求值); + - `NN_UNWRAP(expr[, "ctx"])`:失败打印 file:line 后 abort,成功返回解包值(可函数化); + - `NN_EXIT(expr, code[, "ctx"])`:失败打印后 `exit(code)`(可函数化;测试用 77=SKIP); + - `NN_FAIL("msg")` = `return std::unexpected(Error{...})`;`NN_ASSERT` Debug-only、NDEBUG 空展开(**天然依赖预处理器**);`NN_ASSERT_ALWAYS`/`NN_FATAL` 无条件终止。 + - `NN_UNWRAP/NN_EXIT` 依赖 `NN_DETAIL_PICK2/3` 参数计数分发(L97-L108)。 +- `src/` 宏使用规模:约 65 个文件、~180 处(`NN_EXIT` ~60、`NN_TRY*` ~70、`NN_UNWRAP` ~15、断言 ~30、`NN_FAIL` 若干)。 +- 显式 include 宏头的文件(`src/`):`core_errors.hpp` × 7(`dataset_gen`、`tokenizer_train`、`tokenizer_infer`、`dataset_convert`、`run_driver_test`、`batch_source_test`、`error_macro_test`);`core_assert.hpp` × 1(`assert_test`)。 + +### 2.3 模块与构建现状 + +- 现有 4 个薄接口模块:`modules/nn.{core,kvrecord,dataset,tokenizer}.cpp`,DAG `core→kvrecord→dataset→tokenizer`,薄模式 = `module;` GMF 文本包含 + `export module nn.<域>;` + `export namespace nn { using nn::X; … }`(实体挂全局模块,与 include 消费方同一实体、可混链)。 +- 双轨(`#ifdef NN_USE_MODULES / import / #else / #include / #endif`)共 **8 个文件**:`src/dataset_gen.cpp:13-17`、`tokenizer_train.cpp:12-16`、`tokenizer_infer.cpp:12-16`、`dataset_convert.cpp:13-17`、`dataset_test.cpp:27-31`、`nnvocab_test.cpp:19-23`、`tokenizer_consistency_test.cpp:23-27`、`dataset_integration_test.cpp:25-30`。 +- `nn_use_modules` 调用 8 处:L675-L676、L682、L688(app)、L1054-L1057(test)。**`tokenizer_consistency_test` 未见调用——实施时核对补齐**(其源文件由 `tokenizer_test.cpp` 文本 include)。 +- 目标创建:`nn_add_executable` 是 function(L437-L450,自动链 `nn_core`、登记 `NN_EXECUTABLE_TARGETS` 与 `NN_PCH_CANDIDATES`)。 +- PCH:`nn_pch` OBJECT 库 + `target_precompile_headers(REUSE_FROM)`(L1202-L1233,宿主 `cmake/nn_pch_stub.cpp`);模块消费方在 `nn_use_modules` L608 被剔除候选(理由 L594-L597)。 +- `src/` include 面:107 个 `.cpp` 中 56 个含 `#include `(142 处),其中 35 个直接 include `nn.hpp`(nn.hpp 共 46 行 include,见附录 B);本地头仅 `src/expr_fuse_shapes.hpp`(:14 include `nn.hpp`)。 +- `nn.hpp` 之外的细粒度消费面:expr_*(compute_tensor/expr_dsl/expr_spec/expr_opt/expr_registry/expr_fold)、`cli_engine_factory`、`kernel_cache`、`core_{sha256,file,threadpool,parallel}`、`model_keyvalue_record`、`batch_source`、`run_driver`、`backend/compute_vk_backend`、`test_common.hpp` 等(完整清单以第 5.2 节枚举命令为准)。 +- `NN_GPU_ENGINE` 在 `src/` 仅作 `#ifdef` 条件(13 文件),**无本地 `#define`**(CMake 传入);`NN_HAS_VULKAN` 仅 `fused_gpu_test.cpp:34`。 +- STL:域头基本自包含(`domain_tokenizer_charbpe.hpp` 含 24 个 STL 头),但历史上有传递依赖缺口(`tokenizer_infer` 缺 ``,`history.md:5522`)——import 后消费者必须自带,靠编译错误驱动补。 + +### 2.4 性能基线(`research/modules-import-nn/README.md` §6.3,L107-L114) + +| 场景 | include+ccache | 4 模块 + PCH | +|---|---|---| +| 改 consumer 源全量 | 0.48s | 0.50s | +| 改公共头全量增量 | 12.7s | **18.0s**(0.10+0.36+10.6+1.5+0.4+1.2+1.3+1.4+0.5) | +| 冷构建(520→348 TU) | 87.2s | 106.7s(新增 BMI 10.6s,PCH 扫描 348→31) | + +同时记住:**全量单体 `import nn`(86MB BMI)已被证伪**(§1,L16-L28:比 ccache 命中慢 19×)。本方案走 §5 认可的**粒度模块**路线,不走单体。 + +--- + +## 3. 已定决策(含备选与否决理由) + +| # | 决策 | 备选与否决理由 | +|---|------|----------------| +| D1 | **BMI 命令 = 目标编译命令镜像**,公共选项提取单一真源变量 | 否决「只补 `-pthread` 最小修」:其余 5 处差异(尤其工具链 flags 缺失)是同类隐患 | +| D2 | **无编译器识别**:删 `NN_ENABLE_MODULES`/`NN_MODULES_ENABLED`,模块构建无条件;非模块编译器构建直接失败 | 用户定调。BMI 姿势保持 Clang/GNU 驱动(`--precompile`、`-fmodule-file=`)不分叉 | +| D3 | **宏岛头**:宏段拆到**不带 `neuralnet.cpp/` 前缀**的 `engine/abstraction/include/nn_error_macros.hpp`,`core_errors.hpp`/`core_assert.hpp` include 它(库内单一真源、零改动);src/ 消费方 include 该头 + `import nn.*` | 否决「宏全函数化」:`NN_TRY*` 是 early-return(~70 处逐点改写、函数替代不了)、`NN_ASSERT` 的 NDEBUG 语义天然要预处理器;函数化无法零改写达标 | +| D4 | **AOT 收集链直接删除、不留开关**(`scan_exprs`、`fused_registry.hpp` 生成、`NN_EXPR_SCAN` 分支、`nn_enable_gpu_fusion()`、depfile/glslc 扫描步) | 与 D2「不保留开关」一致;`docs/internal/33` L125-L131 已给出可拆清单。若 P3 验证门失败,仅在那四个入口局部补救,不恢复全链 | +| D5 | **预热双入口**:① 运行期 `model_warmup()` 接线进 GPU 训练/推理入口(含 `--no-warmup` 逃生阀);② 构建期新增 `nn_warmup` 目标(常用 layer × 代表性 shape 过 GPU forward,产物 `kernels.nnk` 落 `NN_KERNEL_CACHE_DIR`,随发布物分发) | 用户提出的两种做法都做:①消除人工遗漏,②消除冷启动 JIT 与部署环境对 kernelgen 的首跑依赖 | +| D6 | **`NN_STRICT_CLOSED_WORLD` 语义重定义**为「只用预生成缓存、miss 即报错」;默认 OFF 不变。实现时若代码形态不允许则删除该开关 | 闭合世界随 AOT 注册表一起失去对象,保留旧语义等于不可用 | +| D7 | **粒度模块全量铺开**(nn.hpp 46 头 → ~8 个新域模块),export 清单先试 `export using namespace nn;`,不接受再逐符号 | 否决单体(已证伪,§2.4);`export { #include }` 包裹已被 §4.1(L54-L55)证死(STL 私有头撞全局片段防护) | +| D8 | **`nn_add_executable` 自动 `nn_use_modules(<全部域>)`** 单点接线 | 否决 107 处逐目标登记:错误成本高、无信息增量 | +| D9 | PCH 机制随 src/ 全出候选后**评估整体删除**(`nn_pch`、`cmake/nn_pch_stub.cpp`、L1202-L1233 块),候选为空时至少保持自动空转 | 保留给范围外目标的可能性在实施时看剩余候选数决定 | + +--- + +## 4. 实施前置 + +1. `git submodule update --init --depth 1 engine`(**当前未检出**,`git submodule status` 前缀 `-d2dd5d1…`;如 https 被拒:`git -c url."https://github.com/".insteadOf="git@github.com:" submodule update --init engine`——本沙箱已验证可行)。 +2. 工具链:CMake **≥3.30**(沙箱自带 3.28.3 不满足 `cmake_minimum_required(3.30)` → `pip install cmake`);clang **≥18**(沙箱默认 clang 17 不识别 `-std=c++26`,`clang-18` 已可用);ninja。 +3. 基线留存:改造前跑一遍 `cmake --preset dev && cmake --build --preset dev` + `ctest`,留存全绿基线;`ninja -t commands | grep nn.tokenizer.cpp` 留存 BMI 命令样本供 P0 diff。 +4. 一切改动**按阶段提交**,每阶段独立可回滚、验收通过才进入下一阶段。 + +--- + +## 5. 分阶段任务 + +### P0 · 编译选项统一(治 config mismatch) + +**目标**:第 0.2 节差异表归零;模块消费方构建零报错。 + +改动点(全部在根 `CMakeLists.txt`): + +1. 提取 `NN_COMMON_COMPILE_OPTIONS` 单一真源变量,承载:`-fno-exceptions -Wall -Wextra -Wpedantic -Werror`(L128)、GNU 分支 `-Wno-maybe-uninitialized`(L134)、Clang 分支 `-fexperimental-library -Wno-pass-failed`(L160)、LTO 开启时 `-flto=thin`(L145)。`add_compile_options(${NN_COMMON_COMPILE_OPTIONS} ...)` 与 BMI `_flags` **共用同一变量**。 +2. `nn_add_module`(L552-L562)`_flags` 改为镜像组装,顺序建议: + - `separate_arguments(_user UNIX_COMMAND "${CMAKE_CXX_FLAGS}")` → 调用方/工具链 flags(差异 #2); + - `CMAKE_BUILD_TYPE` 非空时再拆 `${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}}`(差异 #3,含 `-DNDEBUG`/`-g`;重复的 `-O3` 无害); + - `${NN_COMMON_COMPILE_OPTIONS}`(含 `-flto=thin`,若 `--precompile -flto=thin` 在实测中不接受则排除并在注释说明「LTO 不进入 PCM 配置检查」); + - `${_opt}`(现有 DEBUG/RELEASE_FLAGS,保持原逻辑)+ `-std=c++${CMAKE_CXX_STANDARD}`; + - **`-pthread`**:`if(CMAKE_USE_PTHREADS_INIT)`(`find_package(Threads)` 在 L256、早于模块注册 L615,configure 期可判;与 `Threads::Threads` 展开值严格一致)(差异 #1); + - `-DNN_ENABLE_PROBES`(已有)+ `$<$:-DNN_GPU_ENGINE=1>`(生成期求值,规避 L615 早于 L1178 的顺序问题)(差异 #4); + - `-I` 补齐为与 L345-L350 `nn_core` 完全相同的 5 个(差异 #5); + - 不加 `NN_USE_MODULES`(D2/0.2 注)。 +3. 清理重建:`rm -rf build//modules/*.pcm`(或整个 build 树)。 +4. 验证(**验收门 A**): + - `tokenizer_infer / tokenizer_train / dataset_gen / dataset_convert / dataset_test / tokenizer_test / nnvocab_test / dataset_integration_test` 全部构建通过,config mismatch 与级联误报归零; + - `ninja -t commands` 取 BMI 命令与 `tokenizer_infer` 编译命令 diff,**允许差异仅** `-fmodule-file` / `-o` / `--precompile` / `-c` / 输出路径; + - 全量 dev preset 构建 + ctest 与基线一致。 + +### P1 · 拆开关:无条件化、无编译器识别、删双轨 + +1. 删 L471-L482:`option(NN_ENABLE_MODULES ...)` 与 `if(... AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT MSVC)`;`NN_MODULES_ENABLED` 条件全部移除(`nn_add_module` 开头 L539、`nn_use_modules` L599 等处的 `if(NOT NN_MODULES_ENABLED)` 分支删净)。模块构建无条件执行。 +2. 删 `NN_USE_MODULES` 宏传播:`nn_mod_*` INTERFACE 的 `target_compile_definitions(... NN_USE_MODULES)`(L579-L585 内)。 +3. 删 8 处双轨(文件清单见 2.3):`#ifdef NN_USE_MODULES … #else #include … #endif` → 无条件 `import nn.<域>;`(域按各自现有 import 行)。 +4. 注释/文案同步:L466-L469「仅 GNU 驱动 Clang…自动 OFF」、option help 中 `(clang only; ...)`(随 option 删除)、L594-L597 注释措辞。 +5. 核对 `tokenizer_consistency_test` 的 `nn_use_modules` 缺漏并补齐(2.3)。 +6. 验证(**验收门 B**):全量构建 + ctest 绿;`grep -rn "NN_ENABLE_MODULES\|NN_USE_MODULES" CMakeLists.txt src/` 仅剩历史文档引用;全仓 grep 确认无编译器 ID 分支新增。 + +### P2 · 宏岛头 + +1. 在 `engine/abstraction/include/` 下**新建 `nn_error_macros.hpp`**(**不在** `neuralnet.cpp/` 子树):从 `core_errors.hpp` 迁出宏段(`NN_TRY`/`NN_TRY_MSG`/`NN_TRY_CHECK`/`NN_UNWRAP`/`NN_EXIT`/`NN_FAIL`/`NN_DETAIL_*`),从 `core_assert.hpp` 迁出(`NN_ASSERT`/`NN_ASSERT_ALWAYS`/`NN_FATAL` 等),视 `src/` 实际使用补 `NN_VECTORIZE_PRAGMA` 等。 +2. `core_errors.hpp`/`core_assert.hpp` 改为 `#include "nn_error_macros.hpp"` + 保留原声明 → **库内宏单一真源、模块 GMF 与所有头用法零改动**。 +3. `src/` 侧:7 个 include `core_errors.hpp` 的文件、`assert_test.cpp`、以及所有使用宏但宏将不可见的文件(~65 个,枚举命令见附录 A)显式补 `#include `(include 路径经 `nn_core` 的 `engine/abstraction/include` 已覆盖,无需加 `-I`)。 +4. 验证(**验收门 C**): + - `grep -rn "^#include MAIN )` CMake 函数(`docs/usage/05` 引用它 → 文档同步); +3. 头文件内 `#ifdef NN_EXPR_SCAN` 分支(`expr_dsl`/`expr_spec`/引擎 `end_expr` 登记钩子,位于 submodule,实施时 grep 定位逐一删); +4. `src/expr_fuse_shapes.hpp:7` 注释与 `NN_TEST_COUNTER` 无关、与 dry-run 相关逻辑(实施时按其实际内容处置,保持 import 化一致)。 + +**接入预热**(D5): +5. 运行期:在 GPU 训练/推理入口(`text_train`、`mnist_train`、`text_infer`、`mnist_infer`,及确认有 GPU engine 创建的其他 app)engine 创建后调用 `model_warmup()`;失败按 `NN_EXIT(..., 1, "预热失败")`;CLI 加 `--no-warmup` 逃生阀(cli 头按现有 flag 模式新增)。 +6. 构建期:新增 `nn_warmup` 可执行目标(`nn_add_executable` + `nn_use_modules`),内容:常用 layer × 代表性 shape 序列过一遍 GPU forward,写 `kernels.nnk` 至 `NN_KERNEL_CACHE_DIR`(输出到 build 目录并在发布脚本中随产物分发)。它同时充当入口②的“编译目标预热”(用户方案 A),并为 `fold/reduce/into/multi` 提供显式覆盖面(见下)。 +7. `NN_STRICT_CLOSED_WORLD`(D6):默认 OFF 不动;ON 改语义「只用预生成缓存、miss 即报错」,注释与 `docs/development/04` FAQ 同步;代码形态不允许则删除开关并全文清理。 + +**验证(验收门 D——本阶段关键,决定方案成立性)**: +- 实测空注册表行为:构建临时验证(或直接删注册表后)跑 `expr_gpu_test`、`expr_fold`、`expr_reduce`、`expr_fuse_test`、`fused_gpu_test`,确认 `fold/reduce/into/multi` 四入口是否硬报错; +- 若**不**硬报错 → 预热路径直接成立; +- 若硬报错 → 首选对策:确保 `nn_warmup` 与各 app 预热**显式覆盖四类入口**(零库改动);仍不闭合则给四入口补 JIT 兜底(改引擎,工作量单列); +- 性能锁:`fused_gpu_test --hash` 位级对拍(预热后与 CPU 结果一致);冷缓存首跑 vs `kernels.nnk` 命中跑耗时对比(证明秒命中);`kernel_cache_test`、`jit_demo` 通过。 + +### P4 · 新增域模块(铺满 nn.hpp 46 头) + +1. 域划分草案(实施中按头闭包微调,闭包按 L484-L486 铁律宁多勿少): + +| 新模块 | 覆盖头族(nn.hpp 行号见附录 B) | +|---|---| +| `nn.expr` | expr_dsl/spec/opt/graph/registry/fold、compute_tensor、tensor_schema、named_tensor_set | +| `nn.engine` | compute_engine、backend(compute_vk_*)、algebra_*、precision、ternary/int8、core_profile、kernel_cache | +| `nn.layer` | compute_layer_* 全家族 + layer_name/能力声明头 | +| `nn.loss` | compute_loss | +| `nn.opt` | compute_optimizer、lr_scheduler | +| `nn.model` | model_container/serialization/spec、train_state、training_session、training_group_session | +| `nn.train` | text_trainer、batch_source、run_driver、dataset_samples、rl_experience/policy/pipeline | +| `nn.domain` | domain_mnist/gpt/rla/cnn | +| `nn.cli` | cli/*(含 `cli_engine_factory`,其 `#ifdef NN_GPU_ENGINE` 要求 P0 的 BMI `-D` 对齐) | + +既有 `nn.core` 补入未覆盖的核心头(precision 已含则不重复)。所有域 `export import nn.core`,防环。 + +2. 每个模块单元按现有薄模式(`modules/nn.*.cpp` 样例):GMF `#include` 域头 → `export module` → re-export。 +3. **export 清单生成法**(D7):先写一个模块试 `export using namespace nn;`——若 clang 接受(importer 侧可见全局模块实体),全部模块降为行级文件;不接受则逐符号 `using`,以编译报错 `use of undeclared identifier` 驱动补齐(机械、量大,为本方案最大确定性工作量)。 +4. CMake:为每个新域写 `NN_MOD_*_HEADERS` 闭包 + `nn_add_module(...)` 注册(放 L615-L625 现有注册旁,保持依赖序);DAG 正确性靠 `-fmodule-file` 依赖链(`nn_add_module` 的 DEPS 参数)。 +5. 验证(验收门 E):各 BMI 独立构建成功;`ninja -t commands` 复查 BMI 与消费方命令 diff 仍仅模块差异;`tokenizer_infer` 等既有消费方零改动仍绿。 + +### P5 · src/ 全量切 import + CMake 自动接线 + +1. `nn_add_executable`(L437-L450)内部自动调用 `nn_use_modules(<全部域>)`(D8)——自动挂全量 `-fmodule-file`、自动移出 PCH 候选(L608 逻辑复用)。 +2. 源码改造(枚举见附录 A,逐文件核对): + - 35 个 include `nn.hpp` 的文件 → 拆成所需域 `import nn.<域>;`(按实际符号使用,宁多勿少); + - 细粒度 include 文件(expr_*、cli、core_*、kernel_cache、backend 等)→ 对应域 import; + - 本地头 `src/expr_fuse_shapes.hpp` 同步 import 化; + - 宏岛 include 按 P2 清单补全; + - `test_common.hpp` 保持文本 include。 +3. STL 可见性:import 后由编译错误驱动逐文件补 `#include ` 等(消费者自带,惯例与既有 9 消费方一致)。 +4. PCH(D9):src/ 全出候选后检查 `NN_PCH_CANDIDATES` 剩余量;为空则删除 `nn_pch` 机制与 `cmake/nn_pch_stub.cpp`(L1202-L1233、L1220-L1224),否则保留注释说明服务对象。 +5. 验证(验收门 F): + - `grep -rn "^#include -t commands | grep -A2 "nn.tokenizer.cpp" +ninja -C build/ -t commands | grep "tokenizer_infer.cpp" +# 开关/编译器识别残留 +grep -rn "NN_ENABLE_MODULES\|NN_MODULES_ENABLED\|NN_USE_MODULES\|CMAKE_CXX_COMPILER_ID" CMakeLists.txt +``` + +P0 BMI 与消费方 diff 的**允许差异集**:`-fmodule-file`、`-o`、`--precompile`、`-c`、输出路径。 + +## 附录 B · nn.hpp 的 46 行 include(域划分输入) + +`include/neuralnet.cpp/nn.hpp` L16-L61: + +- 形式头(``):core_errors(16)、core_assert(17)、core_threadpool(19)、core_file(20)、core_config(21)、dataset(52)、domain_tokenizer(61) +- 相对 include(`"..."`):core_observer_ptr(18)、algebra_matrix(24)、compute_tensor(31)、tensor_schema(32)、named_tensor_set(33)、expr_spec(34)、compute_engine(35)、expr_dsl(36)、compute_layer(38)、compute_loss(39)、compute_optimizer(40)、rl_experience(41)、rl_policy(42)、rl_pipeline(43)、model_container(46)、training_session(47)、training_group_session(48)、train_state(49)、model_spec(50)、model_serialization(51)、dataset_samples(53)、lr_scheduler(54)、domain_mnist(57)、domain_gpt(58)、domain_rla(59)、domain_cnn(60) +- 不进聚合头:`text_trainer.hpp`(L63-L65 注)——import 面按文件实际使用补齐 + +## 附录 C · P3 关键证据锚点 + +- `include/neuralnet.cpp/model_container.hpp:350-362` —— `model_warmup()` 定义(待接线) +- `docs/internal/33-kernel-cache-and-engine-decouple.md:39-59,125-131` —— JIT 架构与「可拆 scan_exprs」设计 +- `src/fused_gpu_test.cpp:563-603` —— JIT 路径正确性回归 +- `docs/development/04-compute-engine-development.md:863-876` —— 四入口硬报错与 strict 语义(待代码复核) +- `AGENTS.md:483-485` —— GPU 路径能力总览 +- `docs/release-notes/v1.6.0.md:60,91,103`、`v1.6.1.md:16,49-50,68`、`v1.7.0.md:129` —— `NN_EXPR_SCAN` 相关历史不变量(P3 删除时逐条核对是否失效) diff --git a/docs/internal/36-compromise-ledger.md b/docs/internal/36-compromise-ledger.md index 7b19c7d..2ecbbcd 100644 --- a/docs/internal/36-compromise-ledger.md +++ b/docs/internal/36-compromise-ledger.md @@ -1,6 +1,8 @@ # 36 · 模块迁移妥协式设计台账(最终验收前必须清零) -> 状态:**现行(随 35 号实施过程滚动登记)**。目标读者:35 号的实施者与验收者。 +> **读者**:35 号的实施者与验收者。 +> **一句话结论**:为解锁验收门引入的妥协式绕行逐条登记,**门 G 前必须逐条消灭、不得转正**;根因级修复不入表。 +> **状态**:现行(随 35 号实施过程滚动登记)。 > 关联文档:`35-modules-migration-implementation.md`(实施文档本体,只读)。 > **规则(用户裁定)**:实施过程中为解锁验收门而引入的妥协式绕行**一律登记在本表**; > 允许阶段性带着它们推进(换取主线进度),但**最终验收(35 §6 门 G)的前提 = @@ -20,9 +22,10 @@ | C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | 二选一并同步文档:① NN_IRC_TRACE 正式化为非探针门的行为开关(更新 AGENTS/33 号口径)或 ② 恢复 env_flag 并改 AGENTS 注明需 NN_ENABLE_PROBES;**删除 [ir-c][run] 每 kernel 打印** | 最终清理 | ⏳ | | C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | C1/C2 消灭后重新 A/B:静态签名 JIT vs 边界 cast,以 f16 基准**择优转正**(优则恢复直写;劣则把边界 cast 升格为有据设计并删实验残留形态) | 最终清理(依赖 C1) | ⏳ | | C5 | `engine/engine/src/kernelgen.cpp`:失败路径诊断 fprintf("GLSL 生成为空" / "shaderc 编译失败") | 排查期新增的输出行为;非掩盖缺陷,但属未评审新增 | **显式裁决**:转正(补设计注记)或删除——不得默默留下 | 最终清理 | ⏳ | +| C6 | `src/tokenizer_consistency_test.cpp`:`old_pre_tokenize` 的 `markers` 特意用 `std::vector`(库侧为 `std::string`) | **clang 24 上游 bug 的规避绕行**(P5 实测,2026-11-06):同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 `pre_tokenize` 的 markers 含相同字面量+相同 sort lambda)**且 import 面超阈值**时,clang 产出错误代码——同函数内相邻循环一个正常执行、另一个 0 次迭代(行为随布局漂移,探针互换),-O0/-O3 均复现。已排除:写法 UB(cppreference range-for/compare 前置条件均满足)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份符号)、布局重合。触发矩阵:纯代码 0 import 过 / 4 链式 import 过 / +expr、+core 过 / **+cli 或 8 域组合或全 13 挂**;old 去 sort 或改名不解除、**改 string_view 解除**。修复=改型打破同形(对拍语义不变),根因在上游未收敛 | ① 把最小 repro(≈`import nn.tokenizer+nn.cli` + 同形双 static + 单调用)报 LLVM issues;② clang 修复合入本仓工具链版本后,**撤掉 string_view 特意差异**恢复与库同型(或确认修复的回归用例落 `tokenizer_test`) | 最终清理(门 G 前;工具链升级为触发条件) | ⏳ | ## 消灭顺序 -C1+C2 同组(互为 A/B 条件)→ C4 重新实验(依赖 C1 结果)→ C3 → C5。 +C1+C2 同组(互为 A/B 条件)→ C4 重新实验(依赖 C1 结果)→ C3 → C5 → C6(依赖上游 clang 修复,工具链升级时验收)。 消灭 C1/C2 后必须重跑:`ctest --preset dev` 全绿 + `f16_precision_test` 的 full-f16 用例(图开启、无 NN_NO_GRAPH)逐条留证。 diff --git a/docs/internal/README.md b/docs/internal/README.md index 33cae8a..21b0a12 100644 --- a/docs/internal/README.md +++ b/docs/internal/README.md @@ -26,6 +26,7 @@ | 文档 | 何时读 | |------|--------| +| [`35-modules-migration-implementation.md`](35-modules-migration-implementation.md) | **src/ 全量 import 化与 AOT 收集 → JIT 预热实施文档(只读,用户提供)**:P0–P6 分阶段任务、验收门 A–G、D1–D9 清单;配套台账 = 36 号 | | [`36-compromise-ledger.md`](36-compromise-ledger.md) | **模块迁移妥协式设计台账(35 号配套,最终验收门 G 前必须清零)**:为解锁验收门引入的绕行(optimizer=f16 跳录制 / NN_NO_GRAPH 旁路 / 诊断改道 / A/B 残留)逐条登记 + 消灭方案与时机;根因级修复不入表 | | [`00-code-review-remediation.md`](00-code-review-remediation.md) | **2026-10-11 代码审查整改立项台账**:审查发现的问题逐条立项(现象/证据/根因/方案/验收/优先级) | | [`18-roadmap.md`](18-roadmap.md) | **"某件事该不该做"的唯一入口**:未来 6–12 个月方向 + 未完成项**裁定**台账(做 / 条件触发 / 不做)+ 验收口径 + 现状实测;**§4.5 P4 = 持续优化方向**(性能利用率 / 算子性能 / 显存开销 + API 易用性与 Fold 自动化扫描)。**不含执行计划**(见下一行) | diff --git a/engine b/engine index 818a63a..19bc1e0 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit 818a63a3ea61add7d4eb6788ded7e78e4707d7a0 +Subproject commit 19bc1e023dc8cecffec42e5a547d582f069e4a00 diff --git a/include/neuralnet.cpp/cli/text_train_config.hpp b/include/neuralnet.cpp/cli/text_train_config.hpp index e653f55..2f5c602 100644 --- a/include/neuralnet.cpp/cli/text_train_config.hpp +++ b/include/neuralnet.cpp/cli/text_train_config.hpp @@ -45,7 +45,7 @@ using nn::Scalar; // GPT 架构在 P1 **还没有**三值线性层(docs 21 §4.8 P1.5),但词法上照样接受: // 不兼容组合由 `Layer::init` 的能力校验给出"层 X 不支持 param=t1_58"的报错, // 这正是设计意图(§4.1)—— CLI 不加模型白名单。 -nn::Precision parse_precision_arg(const std::string& name, const char* flag, +inline nn::Precision parse_precision_arg(const std::string& name, const char* flag, // inline:同 parse_text_train_args(P5) bool allow_t1_58 = false) { if (name == "f16" || name == "half") return nn::Precision::F16; @@ -63,7 +63,7 @@ nn::Precision parse_precision_arg(const std::string& name, const char* flag, } // ==================== 帮助信息 ==================== -void print_text_train_usage(const char* prog) +inline void print_text_train_usage(const char* prog) // inline:同 parse_text_train_args(P5 模块化) { nn::cli::Help help(std::cout, prog, "GPT 文本生成训练程序"); @@ -308,7 +308,7 @@ precision_offload_combo_error(const TrainConfig& cfg) " 请改用 --checkpoint-every,或去掉 --activation-offload。\n"); } -TrainConfig parse_text_train_args(int argc, char* argv[]) +inline TrainConfig parse_text_train_args(int argc, char* argv[]) // inline:模块化后消费方无文本包含,需可由 BMI 发射定义(P5) { TrainConfig cfg; // 配方默认值继承自 nn::TextTrainConfig;seq_len 的 CLI 默认是 GPT 序列长度, diff --git a/modules/nn.cli.cpp b/modules/nn.cli.cpp index 81336bd..b21e7f4 100644 --- a/modules/nn.cli.cpp +++ b/modules/nn.cli.cpp @@ -1,9 +1,9 @@ -// ── nn.cli — C++ 模块:CLI 公共件(帮助/引擎工厂/GPU 选择/训练入口装配/预热助手) ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// ── nn.cli — C++ 模块:CLI 公共件 ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -19,4 +19,34 @@ module; #include export module nn.cli; export import nn.core; -export using namespace nn; +export namespace nn { + namespace cli { + using nn::cli::EngineConfig; + using nn::cli::Help; + using nn::cli::ResumeStateAdoption; + using nn::cli::TextTrainCheckpointLog; + using nn::cli::TextTrainMemProbe; + using nn::cli::TrainCommonArgs; + using nn::cli::TrainConfig; + using nn::cli::adopt_resume_state; + using nn::cli::create_engine; + using nn::cli::dangerous_precision_warning; + using nn::cli::evaluate_mnist; + using nn::cli::is_forbidden_resume_flag; + using nn::cli::jit_warmup; + using nn::cli::load_mnist_csv; + using nn::cli::load_optimizer_state; + using nn::cli::make_text_train_mem_probe; + using nn::cli::parse_gpu_option; + using nn::cli::parse_text_train_args; + using nn::cli::parse_train_common_args; + using nn::cli::precision_offload_combo_error; + using nn::cli::resume_text_train; + using nn::cli::run_text_train_loop; + using nn::cli::save_ckpt_with_state; + using nn::cli::save_text_train_model; + using nn::cli::text_train_ckpt_exit_code; + using nn::cli::text_train_ckpt_path; + using nn::cli::verify_dataset_identity; + } +} diff --git a/modules/nn.core.cpp b/modules/nn.core.cpp index 9531ada..d2fef1f 100644 --- a/modules/nn.core.cpp +++ b/modules/nn.core.cpp @@ -1,10 +1,9 @@ -// ── nn.core — C++ 模块:核心域(错误/配置/精度/哈希/文件/线程池/并行)───── -// 「薄接口」模块(research/modules-import-nn 实证):STL 与库头文件**声明** -// 留在全局模块片段(实体挂全局模块,与 `#include` 消费方同一实体,可混合 -// 链接),本模块只做一件事:把 `nn::` 命名空间的公开符号 re-export。 -// 约束:宏(NN_TRY / NN_UNWRAP / NN_ASSERT_* 等)**不可导出**——消费者按需 -// `#include ` 宏岛头;STL 留在全局片段,消费者自带。 -// 导出面 = GMF 文本包含的全部 nn 公开实体(命名空间级 re-export,D7)。 +// ── nn.core — C++ 模块:核心域(错误/配置/精度/哈希/文件/线程池/并行) ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -16,5 +15,53 @@ module; #include #include export module nn.core; - -export using namespace nn; +export namespace nn { + using nn::BLOCK_SIZE; + using nn::EPSILON; + using nn::Elem; + using nn::Error; + using nn::PARALLEL_THRESHOLD; + using nn::Precision; + using nn::PrecisionProfile; + using nn::Result; + using nn::Scalar; + using nn::ThreadPool; + using nn::acc; + using nn::elem; + using nn::f16; + using nn::f32; + using nn::for_each; + using nn::global_thread_pool; + using nn::is_profile_f32; + using nn::load_text_file; + using nn::max_precision; + using nn::parallel_for_blocks; + using nn::parallel_for_samples; + using nn::parse_number; + using nn::precision_from_tag; + using nn::precision_name; + using nn::precision_rank; + using nn::precision_tag; + using nn::profile_all_f16; + using nn::profile_f16; + using nn::profile_f32; + using nn::profile_master_weights; + using nn::profile_t1_58; + using nn::read_pod; + using nn::read_pod_span; + using nn::sha256_file_hex; + using nn::sha256_file_prefix; + using nn::sha256_hex; + using nn::transform; + using nn::transform_reduce; + using nn::write_pod; + using nn::write_pod_span; + namespace detail { + using nn::detail::Sha256State; + using nn::detail::float_to_half_bits; + using nn::detail::half_bits_to_float; + using nn::detail::sha256_digest; + using nn::detail::sha256_finalize; + using nn::detail::sha256_update; + } +} diff --git a/modules/nn.dataset.cpp b/modules/nn.dataset.cpp index 67d2937..cf923b7 100644 --- a/modules/nn.dataset.cpp +++ b/modules/nn.dataset.cpp @@ -1,41 +1,39 @@ -// ── nn.dataset — C++ 模块:统一数据集(.nndataset / .nnvocab / doc 块)──── -// 薄接口模块(见 nn.core.cpp 头注释):依赖域(nn.core / nn.kvrecord)经 -// `export import` re-export,消费方只 `import nn.dataset;` 即可连带拿到 -// core 与 kvrecord 的公开符号。 -// dataset_samples.hpp(训练样本切窗)依赖 L1 algebra_matrix,本轮不模块化。 +// ── nn.dataset — C++ 模块:统一数据集(.nndataset/.nnvocab) ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include export module nn.dataset; export import nn.core; export import nn.kvrecord; - export namespace nn { - // dataset_format.hpp - using nn::NNVOCAB_MAGIC; - using nn::NNDATASET_MAGIC; - using nn::NNVOCAB_FILE_VERSION; - using nn::NNDATASET_FILE_VERSION; - using nn::FILE_LABEL_SIZE; - using nn::write_file_label; - using nn::FileLabel; - using nn::read_file_label; - using nn::check_file_label; - using nn::encode_vocab_blob; - using nn::decode_vocab_blob; - using nn::write_nnvocab; - using nn::parse_nnvocab; - // dataset.hpp + using nn::Dataset; + using nn::DatasetInfo; using nn::DatasetKind; using nn::DatasetSubset; - using nn::DatasetInfo; + using nn::FILE_LABEL_SIZE; + using nn::FileLabel; + using nn::NNDATASET_FILE_VERSION; + using nn::NNDATASET_MAGIC; + using nn::NNVOCAB_FILE_VERSION; + using nn::NNVOCAB_MAGIC; + using nn::NndatasetBuild; using nn::TextCorpus; using nn::TextDoc; - using nn::read_text_docs; - using nn::mark_assistant_span; + using nn::check_file_label; + using nn::decode_vocab_blob; using nn::doc_block_size; using nn::encode_doc_block; - using nn::NndatasetBuild; + using nn::encode_vocab_blob; + using nn::mark_assistant_span; + using nn::parse_nnvocab; + using nn::read_file_label; + using nn::read_text_docs; + using nn::write_file_label; using nn::write_nndataset; - using nn::Dataset; + using nn::write_nnvocab; } diff --git a/modules/nn.domain.cpp b/modules/nn.domain.cpp index 70563d6..e1b0529 100644 --- a/modules/nn.domain.cpp +++ b/modules/nn.domain.cpp @@ -1,9 +1,9 @@ // ── nn.domain — C++ 模块:模型工厂(MNIST/GPT/RAPT-RLA/CNN) ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -11,4 +11,35 @@ module; #include export module nn.domain; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::CnnConfig; + using nn::CnnConvSpec; + using nn::GptConfig; + using nn::MNIST_CNN_CONVS; + using nn::MNIST_CNN_FC; + using nn::MNIST_IMG_SIZE; + using nn::MNIST_INPUT_DIM; + using nn::MNIST_LAYER_DIMS; + using nn::MNIST_NUM_CLASSES; + using nn::MNIST_PATCH_SIZE; + using nn::MNIST_TF_D_FF; + using nn::MNIST_TF_D_MODEL; + using nn::MNIST_TF_NUM_HEADS; + using nn::MNIST_TF_NUM_LAYERS; + using nn::RAPTConfig; + using nn::build_cnn_model; + using nn::build_cnn_model_from_spec; + using nn::build_gpt_model; + using nn::build_gpt_model_from_spec; + using nn::build_mnist_mlp_model; + using nn::build_mnist_model_from_spec; + using nn::build_mnist_transformer_model; + using nn::build_rapt_model; + using nn::build_rapt_model_from_spec; + using nn::cnn_config_from_spec; + using nn::load_image_from_csv_line; + using nn::make_cnn_spec; + using nn::make_gpt_spec; + using nn::make_mnist_transformer_spec; + using nn::make_rapt_spec; +} diff --git a/modules/nn.engine.cpp b/modules/nn.engine.cpp index 895940a..61d5658 100644 --- a/modules/nn.engine.cpp +++ b/modules/nn.engine.cpp @@ -1,9 +1,9 @@ // ── nn.engine — C++ 模块:计算引擎抽象层 + 后端 + 代数/精度原语 ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -15,4 +15,75 @@ module; #include export module nn.engine; export import nn.core; -export using namespace nn; +export namespace nn { + namespace detail { + using nn::detail::upload_span; + using nn::detail::download_vector; + } + namespace trit_pack { + using nn::trit_pack::code_of; + using nn::trit_pack::dequant_row; + using nn::trit_pack::kBase3PerByte; + using nn::trit_pack::at; + using nn::trit_pack::at_base3; + using nn::trit_pack::base3_bytes_per_row; + using nn::trit_pack::bytes_per_row; + using nn::trit_pack::latent_row_scale; + using nn::trit_pack::pack_row; + using nn::trit_pack::pack_row_base3; + using nn::trit_pack::unpack_row_base3; + using nn::trit_pack::value_of; + using nn::trit_pack::words_per_row; + using nn::trit_pack::kBits; + using nn::trit_pack::kPerByte; + using nn::trit_pack::kPerWord; + using nn::trit_pack::kMask; + } + using nn::create_cpu_engine; + using nn::ExprSegment; + using nn::InferenceScope; + using nn::InitSpec; + using nn::KeyValueRecord; + using nn::create_gpu_engine; + using nn::download_span; + using nn::download_vector; + using nn::kInitSeed; + using nn::kvrec_memory_source; + using nn::last_error; + using nn::upload_span; + namespace fused { + using nn::fused::KernelCache; + using nn::fused::decode_expr_spec; + using nn::fused::encode_expr_spec; + using nn::fused::encode_kernel_cache; + using nn::fused::kKernelCacheGeneratorVersion; + using nn::fused::kernel_cache_key; + using nn::fused::kernel_cache_path; + using nn::fused::kernel_cache_path_from_env; + } + namespace i8_pack { + using nn::i8_pack::pack_row; + using nn::i8_pack::quantize; + using nn::i8_pack::words_per_row; + } + namespace i8_pack { + using nn::i8_pack::at; + using nn::i8_pack::pack_row; + using nn::i8_pack::words_per_row; + } + namespace prof { + using nn::prof::dump; + using nn::prof::enabled; + } + namespace trit_pack { + using nn::trit_pack::at_base3; + using nn::trit_pack::base3_bytes_per_row; + using nn::trit_pack::bytes_per_row; + using nn::trit_pack::latent_row_scale; + using nn::trit_pack::pack_row; + using nn::trit_pack::pack_row_base3; + using nn::trit_pack::unpack_row_base3; + using nn::trit_pack::value_of; + using nn::trit_pack::words_per_row; + } +} diff --git a/modules/nn.expr.cpp b/modules/nn.expr.cpp index b2b2dd2..550f2c5 100644 --- a/modules/nn.expr.cpp +++ b/modules/nn.expr.cpp @@ -1,9 +1,9 @@ // ── nn.expr — C++ 模块:表达式 DSL / IR / 张量视图 ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -16,4 +16,172 @@ module; #include export module nn.expr; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::detail::float_to_half_bits; + using nn::detail::half_bits_to_float; + using nn::ExprOp; + using nn::ExprOperandKind; + using nn::ExprSpec; + using nn::FoldSpec; + using nn::MatmulSpec; + using nn::ExprOp; + using nn::ExprOperandKind; + using nn::ExprSpec; + using nn::FoldSpec; + using nn::MatmulSpec; + using nn::ComputeEngine; + using nn::Device; + using nn::DomainId; + using nn::EXPR_FOLD_BLOCK; + using nn::EXPR_MAX_CONSTS; + using nn::EXPR_MAX_INPUTS; + using nn::EXPR_MAX_INSTRS; + using nn::EXPR_MAX_REGS; + using nn::EXPR_PREC_DISPATCH_SUFFIX; + using nn::EXPR_PREC_SIG_DISPATCH; + using nn::Error; + using nn::ExprGraph; + using nn::ExprInstr; + using nn::ExprOp; + using nn::ExprOperandKind; + using nn::ExprSpec; + using nn::ExprView; + using nn::ExprViewKind; + using nn::FieldHandle; + using nn::FoldSpec; + using nn::FusedKernel; + using nn::GradientBundle; + using nn::MatmulSpec; + using nn::Matrix; + using nn::MatrixT; + using nn::NamedTensor; + using nn::NamedTensorSet; + using nn::PARALLEL_THRESHOLD; + using nn::Precision; + using nn::PrecisionProfile; + using nn::Result; + using nn::Scalar; + using nn::Tensor; + using nn::TensorFieldSpec; + using nn::TensorPlacement; + using nn::TensorRef; + using nn::TensorSchema; + using nn::TensorShapeSpec; + using nn::ThreadPool; + using nn::VecAccSpec; + using nn::canonicalize_expr_spec; + using nn::check_precision_supported; + using nn::decode_tensor_name_list; + using nn::decode_tensor_schema; + using nn::dst_order_constraints; + using nn::elem; + using nn::expr_op_is_reduce; + using nn::expr_spec_equal; + using nn::expr_spec_has_matmul; + using nn::expr_spec_key; + using nn::expr_spec_reduce_axis; + using nn::expr_spec_runtime_matmul_trans; + using nn::f16; + using nn::for_each; + using nn::fuse_expr_graph; + using nn::global_thread_pool; + using nn::is_profile_f32; + using nn::kHostDomain; + using nn::max_precision; + using nn::parallel_for_blocks; + using nn::parallel_for_samples; + using nn::parse_number; + using nn::precision_bytes; + using nn::precision_from_tag; + using nn::precision_has_order; + using nn::precision_name; + using nn::precision_rank; + using nn::precision_tag; + using nn::profile_all_f16; + using nn::profile_f16; + using nn::profile_f32; + using nn::profile_master_weights; + using nn::profile_t1_58; + using nn::tensor_field; + using nn::transform; + using nn::transform_reduce; + using nn::validate_dst_order; + using nn::validate_expr_spec; + namespace dsl { + using nn::dsl::row_mod; + using nn::dsl::tanh; + using nn::dsl::log; + using nn::dsl::sqrt; + using nn::dsl::abs; + using nn::dsl::min; +using nn::dsl::operator+; +using nn::dsl::operator-; +using nn::dsl::operator*; +using nn::dsl::operator/; +using nn::dsl::operator>; +using nn::dsl::operator<; +using nn::dsl::operator>=; +using nn::dsl::operator<=; +using nn::dsl::operator==; +using nn::dsl::operator!=; + using nn::dsl::ConstLeaf; + using nn::dsl::GroupedReduceMaxRef; + using nn::dsl::GroupedReduceSumRef; + using nn::dsl::TensorRef; + using nn::dsl::col_broadcast; + using nn::dsl::col_reduce_max; + using nn::dsl::col_reduce_sum; + using nn::dsl::compute; + using nn::dsl::compute_into; + using nn::dsl::compute_reduce; + using nn::dsl::exp; + using nn::dsl::grouped_reduce_max; + using nn::dsl::leaf; + using nn::dsl::matmul; + using nn::dsl::matmul_kv; + using nn::dsl::max; + using nn::dsl::row_broadcast; + using nn::dsl::row_gather; + using nn::dsl::row_reduce_max; + using nn::dsl::row_reduce_sum; + using nn::dsl::rparam; + using nn::dsl::select; + using nn::dsl::to_expr_spec; + } + namespace expr { + using nn::expr::cst; + using nn::expr::input; + using nn::expr::linear; + using nn::expr::make_fold_logsumexp; + using nn::expr::make_fold_rowmax; + using nn::expr::make_fold_rowsum; + using nn::expr::make_fold_softmax_denom; + using nn::expr::matmul_op; + using nn::expr::reduce; + using nn::expr::reg; + using nn::expr::trans; + } + namespace fused { + using nn::fused::ExprRegistry; + } + namespace i8_pack { + } + namespace detail { + using nn::detail::schema_put_string; + using nn::detail::schema_put_u32; + } + namespace dsl { + using nn::dsl::compute; + using nn::dsl::eval_cpu; + using nn::dsl::grouped_reduce_sum; + using nn::dsl::matmul; + using nn::dsl::relu; + using nn::dsl::rotate_half; + using nn::dsl::row; + using nn::dsl::row_access; + } + namespace expr { + using nn::expr::rotate_half; + using nn::expr::row_mod; + } +} diff --git a/modules/nn.kvrecord.cpp b/modules/nn.kvrecord.cpp index 361a481..052268a 100644 --- a/modules/nn.kvrecord.cpp +++ b/modules/nn.kvrecord.cpp @@ -1,12 +1,18 @@ -// ── nn.kvrecord — C++ 模块:KVRecord(v1/v2 键值记录)───────────────────── -// 薄接口模块(见 nn.core.cpp 头注释):依赖域经 `export import` re-export, -// 消费方只 `import nn.kvrecord;` 即可连带拿到 nn.core 的公开符号。 +// ── nn.kvrecord — C++ 模块:KVRecord v1/v2 容器 ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include export module nn.kvrecord; export import nn.core; - export namespace nn { using nn::KeyValueRecord; using nn::kvrec_memory_source; + namespace detail { + using nn::detail::append_u32; + using nn::detail::append_u64; + } } diff --git a/modules/nn.layer.cpp b/modules/nn.layer.cpp index 852143a..f96a16d 100644 --- a/modules/nn.layer.cpp +++ b/modules/nn.layer.cpp @@ -1,11 +1,72 @@ -// ── nn.layer — C++ 模块:层家族(MLP/卷积/注意力/Transformer/GPT/RAPT/三值…)+ 位置编码 ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// ── nn.layer — C++ 模块:层家族 + 位置编码 ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include export module nn.layer; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::ActivationOffloader; + using nn::ActivationType; + using nn::BatchNorm; + using nn::BitLinear; + using nn::CausalSelfAttention; + using nn::CnnSpec; + using nn::Conv2D; + using nn::CrossAttention; + using nn::Embedding; + using nn::FeedForward; + using nn::GPTBlock; + using nn::GPTModel; + using nn::GeLU; + using nn::GptSpec; + using nn::Layer; + using nn::LayerNorm; + using nn::Linear; + using nn::MaxPool2D; + using nn::MlpSpec; + using nn::ModelRegistry; + using nn::ModelRole; + using nn::ModelSpec; + using nn::ModelSpecBase; + using nn::ModelType; + using nn::MultiHeadAttention; + using nn::NormPlace; + using nn::NormType; + using nn::OutputBundleLayer; + using nn::PosEncodingType; + using nn::PrecisionSet; + using nn::PrecisionSupport; + using nn::RAPTModel; + using nn::RMSNorm; + using nn::RaptSpec; + using nn::ReLU; + using nn::ReLU2GLU; + using nn::ReLULinearAttention; + using nn::RotaryEmbedding; + using nn::RowBandPacking; + using nn::SamplingConfig; + using nn::Softmax; + using nn::SwiGLU; + using nn::TransformerEncoderLayer; + using nn::TransformerSpec; + using nn::WeightLoadIntent; + using nn::WeightQuant; + using nn::make_norm_layer; + using nn::model_role_name; + using nn::norm_place_name; + using nn::rounding_precision_set; + using nn::sample_next_token_; + using nn::spec_matches; + using nn::spec_summary; + namespace dsl { + using nn::dsl::rsqrt; + } + namespace expr { + using nn::expr::AttnMaskKind; + using nn::expr::make_fold_attn_o; + } +} diff --git a/modules/nn.loss.cpp b/modules/nn.loss.cpp index 1a36897..8d132b8 100644 --- a/modules/nn.loss.cpp +++ b/modules/nn.loss.cpp @@ -1,11 +1,14 @@ // ── nn.loss — C++ 模块:损失函数 ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include export module nn.loss; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::CrossEntropyLoss; + using nn::MSELoss; +} diff --git a/modules/nn.model.cpp b/modules/nn.model.cpp index 7be4683..d9e0235 100644 --- a/modules/nn.model.cpp +++ b/modules/nn.model.cpp @@ -1,9 +1,9 @@ // ── nn.model — C++ 模块:模型容器 / 规格 / 序列化 / 训练会话 ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -13,4 +13,47 @@ module; #include export module nn.model; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::CheckpointState; + using nn::DatasetIdentity; + using nn::MODEL_TAIL_BYTES; + using nn::MODEL_TAIL_MAGIC; + using nn::Model; + using nn::NamedTrainingStepOutput; + using nn::ResumePolicy; + using nn::TextTrainState; + using nn::TrainCounters; + using nn::TrainState; + using nn::TrainingConfig; + using nn::TrainingGroupSession; + using nn::TrainingSession; + using nn::TrainingStepOptions; + using nn::TrainingStepOutput; + using nn::TrainingStepReport; + using nn::dataset_mismatch_reason; + using nn::load_any_model; + using nn::load_model; + using nn::load_text_file; + using nn::load_train_state; + using nn::model_warmup; + using nn::peek_any_model_spec; + using nn::peek_model_role; + using nn::peek_model_spec; + using nn::peek_train_state; + using nn::read_pod; + using nn::read_pod_span; + using nn::save_model; + using nn::sha256_file_hex; + using nn::sha256_file_prefix; + using nn::sha256_hex; + using nn::train_state_from_kv; + using nn::train_state_to_kv; + using nn::verify_model_integrity; + using nn::write_pod; + using nn::write_pod_span; + namespace detail { + using nn::detail::read_matrix_v5; + using nn::detail::spec_from_kv; + using nn::detail::spec_to_kv; + } +} diff --git a/modules/nn.opt.cpp b/modules/nn.opt.cpp index 680ea79..1d42e6e 100644 --- a/modules/nn.opt.cpp +++ b/modules/nn.opt.cpp @@ -1,12 +1,31 @@ // ── nn.opt — C++ 模块:优化器 + 学习率调度 ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include export module nn.opt; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::Adam; + using nn::AdamW; + using nn::AdamWInt8; + using nn::AdamWQuant; + using nn::LrScheduleConfig; + using nn::Optimizer; + using nn::OptimizerMeta; + using nn::StepLrScheduleConfig; + using nn::compute_epoch_lr; + using nn::compute_step_lr; + using nn::create_optimizer; + using nn::find_optimizer; + using nn::optimizer_names; + using nn::optimizer_table; + namespace cli { + using nn::LrScheduleConfig; + using nn::compute_epoch_lr; + } +} diff --git a/modules/nn.tokenizer.cpp b/modules/nn.tokenizer.cpp index d41df41..d485234 100644 --- a/modules/nn.tokenizer.cpp +++ b/modules/nn.tokenizer.cpp @@ -1,27 +1,25 @@ -// ── nn.tokenizer — C++ 模块:分词器(BPE / CharBPE / 词表持久化)───────── -// 薄接口模块(见 nn.core.cpp 头注释):依赖域(nn.core / nn.kvrecord / -// nn.dataset)经 `export import` re-export,消费方只 `import nn.tokenizer;` -// 即可连带拿到全部四个域的公开符号(tokenizer 闭包 = 12 头,spike 实测)。 +// ── nn.tokenizer — C++ 模块:分词器(BPE / CharBPE / 词表持久化) ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include export module nn.tokenizer; export import nn.core; export import nn.kvrecord; export import nn.dataset; - export namespace nn { - // domain_tokenizer_base.hpp - using nn::Tokenizer; - using nn::encode_docs_parallel; - // domain_tokenizer_bpe.hpp using nn::BPETokenizer; - // domain_tokenizer_charbpe.hpp using nn::CharBPETokenizer; - using nn::peek_tokenizer_type; - using nn::make_tokenizer; - using nn::load_tokenizer_from_string; - using nn::load_tokenizer_from_kvrec; + using nn::Tokenizer; + using nn::encode_docs_parallel; using nn::load_tokenizer_from_bytes; - using nn::load_tokenizer_from_model_blob; using nn::load_tokenizer_from_file; + using nn::load_tokenizer_from_kvrec; + using nn::load_tokenizer_from_model_blob; + using nn::load_tokenizer_from_string; + using nn::make_tokenizer; + using nn::peek_tokenizer_type; } diff --git a/modules/nn.train.cpp b/modules/nn.train.cpp index bb2384f..c60d9cb 100644 --- a/modules/nn.train.cpp +++ b/modules/nn.train.cpp @@ -1,9 +1,9 @@ -// ── nn.train — C++ 模块:训练工厂与数据流(TextTrainer/RunDriver/样本/RL 管线) ────────── -// 薄接口模块(同 nn.core,D7 实验结论):GMF 文本包含域头(实体挂全局 -// 模块,与 #include 消费方同一实体),`export using namespace nn;` 命名空间级 -// re-export——不再逐符号列清单;CMake 侧 NN_MOD_*_HEADERS 是重建触发闭包。 -// 约束:宏不可导出(消费方 include 宏岛头);STL -// 留在全局片段(消费方自带)。 +// ── nn.train — C++ 模块:训练工厂与数据流 ────────── +// 薄接口模块:GMF 文本包含域头(实体挂全局模块,与 #include 消费方可混链); +// 导出 = `export namespace nn { … }` 逐符号(D7 命名空间级导出已证伪)。 +// ⚠ 嵌套名(nn::cli::X / nn::dsl::X / nn::detail::X)必须**镜像原命名空间** +// 导出——`using nn::cli::X;` 写在 nn 块里会被 clang 扁平引入为 nn::X,原限定 +// 路径不可见(实测)。宏不可导出(消费方 include )。 module; #include #include @@ -15,4 +15,76 @@ module; #include export module nn.train; export import nn.core; -export using namespace nn; +export namespace nn { + using nn::AdvantageConfig; + using nn::BPETokenizer; + using nn::BatchAt; + using nn::BatchSampler; + using nn::BatchSource; + using nn::CategoricalDistribution; + using nn::CharBPETokenizer; + using nn::CheckpointProvider; + using nn::ClippedPolicyObjective; + using nn::ClippedValueObjective; + using nn::CollectingMetricSink; + using nn::Dataset; + using nn::DatasetKind; + using nn::DatasetSubset; + using nn::DeterministicRng; + using nn::EntropyBonusObjective; + using nn::ExperienceSchema; + using nn::GaeAdvantage; + using nn::GroupAdvantage; + using nn::GroupSpan; + using nn::GrpoObjective; + using nn::InMemoryCheckpointProvider; + using nn::IndexedBatchSource; + using nn::MaskedNormalizer; + using nn::MetricRecord; + using nn::MetricSink; + using nn::MonteCarloReturn; + using nn::NNDATASET_FILE_VERSION; + using nn::NndatasetBuild; + using nn::PolicyBatchView; + using nn::PolicyDistribution; + using nn::PpoConfig; + using nn::PpoObjective; + using nn::RLPipelineConfig; + using nn::RLTrainingPipeline; + using nn::ReinforceObjective; + using nn::RolloutSource; + using nn::RunDriver; + using nn::RunDriverConfig; + using nn::RunHooks; + using nn::RunReport; + using nn::SampledKlObjective; + using nn::TensorRecord; + using nn::TextCheckpointRequest; + using nn::TextSample; + using nn::TextSampleBatch; + using nn::TextSampleSet; + using nn::TextTrainConfig; + using nn::TextTrainReport; + using nn::TextTrainer; + using nn::TextTrainerHooks; + using nn::TextWindowConfig; + using nn::Tokenizer; + using nn::TrainerHooks; + using nn::UniformBatchSampler; + using nn::UpdateOutcome; + using nn::UpdateRequest; + using nn::ValueRegressionObjective; + using nn::encode_docs_parallel; + using nn::load_tokenizer_from_bytes; + using nn::load_tokenizer_from_file; + using nn::load_tokenizer_from_kvrec; + using nn::load_tokenizer_from_model_blob; + using nn::load_tokenizer_from_string; + using nn::make_text_train_config; + using nn::make_text_train_state; + using nn::mark_assistant_span; + using nn::read_text_docs; + using nn::write_file_label; + using nn::write_nndataset; + using nn::write_nnvocab; +} diff --git a/scripts/doc_audit.py b/scripts/doc_audit.py index 8406104..95c53ad 100644 --- a/scripts/doc_audit.py +++ b/scripts/doc_audit.py @@ -31,6 +31,8 @@ 'ternary_deploy.hpp', # NNQT 独立部署格式,随库级清理(internal/23)删除 '08-object-lifetimes.md', # 2026-11 文档重构删除的转发页(真源 = 07-engine-ownership.md) 'doc_align_audit.ps1', # 文档审计由 scripts/doc_audit.py 承担(口径见文档规范 §5) + 'scan_exprs.cpp', 'fused_generate.hpp', # 35 号 P3 删 AOT 收集/生成链(归档 history); + # 02/18/35 与历史 release-notes 的提及待 P6 正文改写后复核摘除 ] # [B] 已删除符号(命中行若带"不存在/已删除"等否定语境则跳过) @@ -54,6 +56,8 @@ '--diff', '--recurse-submodules', '--tags', '--recursive', # clang 编译器选项(research/modules-import-nn 构建 BMI 的 clang 命令用) '--precompile', + # git 自带选项:git submodule update --init --depth 1(35 号 §4 实施前置) + '--depth', } # [D] 已在文档中明确标注"已移除"的历史参数 REMOVED_FLAGS = { @@ -88,6 +92,8 @@ re.compile(r'^docs/history/history\.md$'), re.compile(r'^docs/release-notes/v[\d.]+(-rc[\d.]+)?\.md$'), re.compile(r'^docs/benchmarks/.+\.md$'), + # 用户提供的只读实施文档(头部由用户撰写并锁定;实施状态语义见其头部自述) + re.compile(r'^docs/internal/35-modules-migration-implementation\.md$'), ] # [E] 状态词表(封闭集合) VALID_STATUS = {'现行', '冻结', 'TODO', '转发页', '已归档'} diff --git a/src/adam_int8_test.cpp b/src/adam_int8_test.cpp index 73fcda2..35c62ad 100644 --- a/src/adam_int8_test.cpp +++ b/src/adam_int8_test.cpp @@ -15,6 +15,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -25,12 +26,27 @@ #include #include -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" -#include "neuralnet.cpp/compute_optimizer.hpp" -#include "neuralnet.cpp/int8_pack.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::expr::row_mod; +using nn::i8_pack::at; +using nn::i8_pack::pack_row; +using nn::i8_pack::quantize; +using nn::i8_pack::words_per_row; namespace { diff --git a/src/adam_quant_test.cpp b/src/adam_quant_test.cpp index 228f3b4..6ee5b3d 100644 --- a/src/adam_quant_test.cpp +++ b/src/adam_quant_test.cpp @@ -14,20 +14,31 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include #include #include -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" -#include "neuralnet.cpp/compute_loss.hpp" -#include "neuralnet.cpp/compute_optimizer.hpp" -#include "neuralnet.cpp/domain_mnist.hpp" -#include "neuralnet.cpp/model_container.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; namespace { diff --git a/src/assert_always_test.cpp b/src/assert_always_test.cpp index 6b7f969..105d8fb 100644 --- a/src/assert_always_test.cpp +++ b/src/assert_always_test.cpp @@ -34,9 +34,21 @@ #endif #include -#include // Matrix / Tensor / sample_next_token_ #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/assert_test.cpp b/src/assert_test.cpp index d08482e..b46393d 100644 --- a/src/assert_test.cpp +++ b/src/assert_test.cpp @@ -23,6 +23,19 @@ int g_fail = 0; #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/attn_consistency_test.cpp b/src/attn_consistency_test.cpp index 14baaf8..16b88d6 100644 --- a/src/attn_consistency_test.cpp +++ b/src/attn_consistency_test.cpp @@ -16,8 +16,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -28,7 +26,19 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::ComputeEngine; using nn::GPTModel; using nn::Matrix; @@ -36,6 +46,7 @@ using nn::PosEncodingType; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/attn_gradcheck.cpp b/src/attn_gradcheck.cpp index 6fb63ea..57fe5a3 100644 --- a/src/attn_gradcheck.cpp +++ b/src/attn_gradcheck.cpp @@ -9,8 +9,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -20,7 +18,21 @@ #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::CausalSelfAttention; using nn::ComputeEngine; using nn::Matrix; @@ -29,6 +41,7 @@ using nn::Scalar; using nn::Tensor; using nn::TensorRef; + namespace { diff --git a/src/attn_test.cpp b/src/attn_test.cpp index 66424cc..54c5a88 100644 --- a/src/attn_test.cpp +++ b/src/attn_test.cpp @@ -19,6 +19,19 @@ #define main test_attn_w_batch #include "attn_w_batch_test.cpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; #undef main // ── MHA 双向语义探针(回归钉子)────────────────────────────────── diff --git a/src/attn_w_batch_test.cpp b/src/attn_w_batch_test.cpp index 19b9c5e..46b261c 100644 --- a/src/attn_w_batch_test.cpp +++ b/src/attn_w_batch_test.cpp @@ -15,16 +15,27 @@ // 3 = CPU/GPU 不一致。 // ──────────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::Matrix; using nn::Scalar; + int main(int argc, char** argv) { auto cpu_res = nn::cli::create_engine(nn::cli::EngineConfig{}, std::cout); diff --git a/src/batch_source_test.cpp b/src/batch_source_test.cpp index e363366..f9fb7c9 100644 --- a/src/batch_source_test.cpp +++ b/src/batch_source_test.cpp @@ -9,6 +9,7 @@ // [6] 无固定 epoch 的 Pull 源(steps_per_epoch = nullopt)骨架退化语义 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -17,8 +18,8 @@ #include #include -#include #include +import nn.train; static int g_fail = 0; #define CHECK(cond, msg) \ diff --git a/src/batchnorm_test.cpp b/src/batchnorm_test.cpp index 9885f6f..def136b 100644 --- a/src/batchnorm_test.cpp +++ b/src/batchnorm_test.cpp @@ -15,8 +15,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -26,13 +24,28 @@ #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ComputeEngine; using nn::Matrix; using nn::Scalar; using nn::Tensor; using nn::TensorRef; + int g_fail = 0; // CHECK 计数器(test_common.hpp 约定) namespace diff --git a/src/bitnet_struct_test.cpp b/src/bitnet_struct_test.cpp index d4d6564..009901e 100644 --- a/src/bitnet_struct_test.cpp +++ b/src/bitnet_struct_test.cpp @@ -18,6 +18,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -28,11 +29,24 @@ #include #include -#include "neuralnet.cpp/nn.hpp" -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; namespace { diff --git a/src/ce_fusion_test.cpp b/src/ce_fusion_test.cpp index 62b4a46..c44a95b 100644 --- a/src/ce_fusion_test.cpp +++ b/src/ce_fusion_test.cpp @@ -13,7 +13,6 @@ // 用法:ce_fusion_test // ─────────────────────────────────────────────────────────────────────────── -#include #include #include @@ -25,9 +24,30 @@ #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::row; +using nn::dsl::col_broadcast; +using nn::dsl::col_reduce_sum; +using nn::dsl::compute_into; +using nn::dsl::compute_reduce; +using nn::dsl::leaf; +using nn::dsl::rparam; +using nn::dsl::rsqrt; using nn::Scalar; + int g_fail = 0; Scalar max_abs_diff(const nn::Matrix& a, const nn::Matrix& b) diff --git a/src/chat_prompt_test.cpp b/src/chat_prompt_test.cpp index 9b602d5..e712a1c 100644 --- a/src/chat_prompt_test.cpp +++ b/src/chat_prompt_test.cpp @@ -30,14 +30,25 @@ #include #include -#include "neuralnet.cpp/nn.hpp" -#include "neuralnet.cpp/model_serialization.hpp" #define NN_TEST_COUNTER g_fail #include "test_common.hpp" #ifndef _WIN32 #include // WEXITSTATUS +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; #endif namespace diff --git a/src/cli_precision_warning_test.cpp b/src/cli_precision_warning_test.cpp index 699e4aa..71768ca 100644 --- a/src/cli_precision_warning_test.cpp +++ b/src/cli_precision_warning_test.cpp @@ -20,12 +20,25 @@ #include #include -#include "neuralnet.cpp/cli/cli_precision_warning.hpp" #define NN_TEST_COUNTER g_fail #include "test_common.hpp" #ifndef _WIN32 #include // WEXITSTATUS +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::dangerous_precision_warning; #endif int g_fail = 0; diff --git a/src/cnn_smoke_test.cpp b/src/cnn_smoke_test.cpp index 70c13ec..55f08ef 100644 --- a/src/cnn_smoke_test.cpp +++ b/src/cnn_smoke_test.cpp @@ -11,8 +11,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -20,6 +18,21 @@ #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; namespace { diff --git a/src/cnn_test.cpp b/src/cnn_test.cpp index a7d3120..d6a7b42 100644 --- a/src/cnn_test.cpp +++ b/src/cnn_test.cpp @@ -19,6 +19,21 @@ // ── cnn_smoke_test(规格往返 + 层组成 + 端到端训练)─────────────────────── #define main test_cnn_smoke #include "cnn_smoke_test.cpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; #undef main int main(int argc, char* argv[]) diff --git a/src/conv2d_gradcheck.cpp b/src/conv2d_gradcheck.cpp index fb580f9..d7b91ba 100644 --- a/src/conv2d_gradcheck.cpp +++ b/src/conv2d_gradcheck.cpp @@ -11,8 +11,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -20,13 +18,28 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ComputeEngine; using nn::Conv2D; using nn::Matrix; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/cross_attention_test.cpp b/src/cross_attention_test.cpp index bb2dd3b..94b9739 100644 --- a/src/cross_attention_test.cpp +++ b/src/cross_attention_test.cpp @@ -15,8 +15,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/dataset_convert.cpp b/src/dataset_convert.cpp index 3a25982..8d085ce 100644 --- a/src/dataset_convert.cpp +++ b/src/dataset_convert.cpp @@ -10,15 +10,16 @@ // 本工具只做格式转换,不做训练相关任何事。 // ───────────────────────────────────────────────────────────────────────── -import nn.tokenizer; #include -#include #include #include #include #include #include +import nn.tokenizer; +import nn.cli; +using nn::cli::Help; // ==================== 帮助信息 ==================== void print_usage(const char* prog) diff --git a/src/dataset_gen.cpp b/src/dataset_gen.cpp index 1b0353d..2d91aa7 100644 --- a/src/dataset_gen.cpp +++ b/src/dataset_gen.cpp @@ -10,9 +10,7 @@ // [--license ] [--source ] // ───────────────────────────────────────────────────────────────────────── -import nn.tokenizer; #include -#include #include #include @@ -22,6 +20,9 @@ import nn.tokenizer; #include #include #include +import nn.tokenizer; +import nn.cli; +using nn::cli::Help; // ==================== 帮助信息 ==================== void print_usage(const char* prog) diff --git a/src/dataset_integration_test.cpp b/src/dataset_integration_test.cpp index d633681..c4e93d7 100644 --- a/src/dataset_integration_test.cpp +++ b/src/dataset_integration_test.cpp @@ -22,11 +22,23 @@ #include #include -import nn.tokenizer; int g_fail = 0; #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/dataset_samples_test.cpp b/src/dataset_samples_test.cpp index 131c924..87ce551 100644 --- a/src/dataset_samples_test.cpp +++ b/src/dataset_samples_test.cpp @@ -23,10 +23,22 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_failures = 0; diff --git a/src/dataset_test.cpp b/src/dataset_test.cpp index e3bb390..56b360c 100644 --- a/src/dataset_test.cpp +++ b/src/dataset_test.cpp @@ -24,11 +24,23 @@ #include #include -import nn.dataset; int g_fail = 0; #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/doc_attn_e2e_test.cpp b/src/doc_attn_e2e_test.cpp index 909cc2f..1d948ca 100644 --- a/src/doc_attn_e2e_test.cpp +++ b/src/doc_attn_e2e_test.cpp @@ -19,15 +19,27 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ActivationType; using nn::ComputeEngine; using nn::GPTModel; @@ -37,6 +49,7 @@ using nn::PosEncodingType; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/dsl_shape_infer_test.cpp b/src/dsl_shape_infer_test.cpp index a4559da..35ec2c3 100644 --- a/src/dsl_shape_infer_test.cpp +++ b/src/dsl_shape_infer_test.cpp @@ -17,16 +17,34 @@ // 用法:dsl_shape_infer_test // ─────────────────────────────────────────────────────────────────────────── -#include #include #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::row; +using nn::dsl::col_broadcast; +using nn::dsl::compute; +using nn::dsl::exp; +using nn::dsl::leaf; +using nn::dsl::rparam; using nn::Scalar; + int g_fail = 0; namespace diff --git a/src/embedding_test.cpp b/src/embedding_test.cpp index ee0148e..fedec11 100644 --- a/src/embedding_test.cpp +++ b/src/embedding_test.cpp @@ -14,8 +14,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/error_macro_test.cpp b/src/error_macro_test.cpp index 78cb23b..b0df8db 100644 --- a/src/error_macro_test.cpp +++ b/src/error_macro_test.cpp @@ -27,6 +27,19 @@ #endif #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/expr_dsl_test.cpp b/src/expr_dsl_test.cpp index e49a9cb..82f2530 100644 --- a/src/expr_dsl_test.cpp +++ b/src/expr_dsl_test.cpp @@ -18,15 +18,18 @@ #include #include -#include -#include -#include -#include #include "test_common.hpp" +import nn.engine; +import nn.expr; +using nn::dsl::eval_cpu; +using nn::dsl::leaf; +using nn::dsl::relu; +using nn::dsl::rotate_half; +using nn::dsl::to_expr_spec; +using nn::Scalar; // 测试写在全局作用域(非 namespace nn),避免 ADL 把匹配拉进 nn 命名空间。 using namespace nn::dsl; -using nn::Scalar; namespace { diff --git a/src/expr_fold_test.cpp b/src/expr_fold_test.cpp index d923bc2..5441314 100644 --- a/src/expr_fold_test.cpp +++ b/src/expr_fold_test.cpp @@ -30,14 +30,13 @@ #include #include -#include -#include -#include -#include // 注意力 fold 构造(定义于 Layer:表达式文本只写在 Layer) #include "test_common.hpp" // M1:upload/download(库外经引擎进出张量) - +import nn.engine; +import nn.expr; +import nn.layer; using nn::Scalar; + int test_expr_fold() { int fail = 0; diff --git a/src/expr_fused_key_test.cpp b/src/expr_fused_key_test.cpp index 7ee9167..c2e16fd 100644 --- a/src/expr_fused_key_test.cpp +++ b/src/expr_fused_key_test.cpp @@ -15,12 +15,11 @@ #include #include -#include -#include -#include -#include -#include -#include +import nn.engine; +import nn.expr; +using nn::dsl::grouped_reduce_sum; +using nn::dsl::rotate_half; +using nn::dsl::row_access; namespace { diff --git a/src/expr_matmul_test.cpp b/src/expr_matmul_test.cpp index 0533c4e..9f74d18 100644 --- a/src/expr_matmul_test.cpp +++ b/src/expr_matmul_test.cpp @@ -23,17 +23,14 @@ #include #include -#include -#include -#include -#include -#include #include "test_common.hpp" +import nn.engine; +import nn.expr; +using nn::Scalar; // 测试写在全局作用域(非 namespace nn),避免 ADL 把匹配拉进 nn 命名空间。 using namespace nn::dsl; namespace dsl = nn::dsl; // 别名:全局作用域可用 dsl::leaf 等限定调用 -using nn::Scalar; namespace { diff --git a/src/expr_opt_test.cpp b/src/expr_opt_test.cpp index 6913e0a..a08565e 100644 --- a/src/expr_opt_test.cpp +++ b/src/expr_opt_test.cpp @@ -18,16 +18,13 @@ #include #include -#include -#include -#include -#include -#include #include "test_common.hpp" +import nn.engine; +import nn.expr; +using nn::Scalar; // 测试写在全局作用域(非 namespace nn),避免 ADL 把匹配拉进 nn 命名空间。 using namespace nn::dsl; -using nn::Scalar; namespace { diff --git a/src/expr_reduce_test.cpp b/src/expr_reduce_test.cpp index 55a1699..9191922 100644 --- a/src/expr_reduce_test.cpp +++ b/src/expr_reduce_test.cpp @@ -19,16 +19,14 @@ #include #include -#include -#include -#include -#include #include "test_common.hpp" +import nn.engine; +import nn.expr; +using nn::Scalar; // 测试写在全局作用域(非 namespace nn),避免 ADL 把匹配拉进 nn 命名空间。 using namespace nn::dsl; namespace dsl = nn::dsl; // 别名:全局作用域可用 dsl::leaf 等限定调用 -using nn::Scalar; namespace { diff --git a/src/f16_compute_test.cpp b/src/f16_compute_test.cpp index 64d027c..ad9f427 100644 --- a/src/f16_compute_test.cpp +++ b/src/f16_compute_test.cpp @@ -9,12 +9,24 @@ #include #include -#include "neuralnet.cpp/precision.hpp" -#include "neuralnet.cpp/compute_tensor.hpp" -#include "neuralnet.cpp/engine.hpp" -#include "neuralnet.cpp/expr_dsl.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::compute; +using nn::dsl::leaf; +using nn::dsl::rparam; namespace { diff --git a/src/f16_cpu_probe.cpp b/src/f16_cpu_probe.cpp index 459314f..2ff1493 100644 --- a/src/f16_cpu_probe.cpp +++ b/src/f16_cpu_probe.cpp @@ -10,8 +10,20 @@ #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; -#include "neuralnet.cpp/nn.hpp" namespace { diff --git a/src/f16_precision_test.cpp b/src/f16_precision_test.cpp index 31bc2e6..46b26a0 100644 --- a/src/f16_precision_test.cpp +++ b/src/f16_precision_test.cpp @@ -16,9 +16,25 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::compute; +using nn::dsl::exp; +using nn::dsl::leaf; +using nn::dsl::rparam; namespace { diff --git a/src/f16_writeback_probe.cpp b/src/f16_writeback_probe.cpp index 9e15bf8..3c13010 100644 --- a/src/f16_writeback_probe.cpp +++ b/src/f16_writeback_probe.cpp @@ -12,8 +12,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -23,11 +21,28 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::compute_into; +using nn::dsl::leaf; +using nn::cli::parse_gpu_option; +using nn::dsl::rparam; using nn::Matrix; using nn::Precision; using nn::Scalar; + namespace { diff --git a/src/file_test.cpp b/src/file_test.cpp index 2d71646..ac8960c 100644 --- a/src/file_test.cpp +++ b/src/file_test.cpp @@ -17,11 +17,11 @@ #include #include -#include int g_fail = 0; #include "test_common.hpp" +import nn.core; namespace { diff --git a/src/fused_gpu_test.cpp b/src/fused_gpu_test.cpp index 0ead66f..073c802 100644 --- a/src/fused_gpu_test.cpp +++ b/src/fused_gpu_test.cpp @@ -17,8 +17,6 @@ // ─────────────────────────────────────────────────────────────────────────── #include -#include -#include // fold v1 样例(结构 key 与对拍同源) #include "test_common.hpp" // M1 访问收口:upload/download(库外经引擎进出张量) @@ -29,8 +27,27 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::compute; +using nn::dsl::matmul; +using nn::dsl::row_broadcast; using nn::Scalar; +using nn::ComputeEngine; +using nn::Matrix; +using nn::Tensor; + #ifndef NN_GPU_ENGINE @@ -42,9 +59,6 @@ int main() } #else -using nn::ComputeEngine; -using nn::Matrix; -using nn::Tensor; // ── 辅助:最大绝对误差 ──────────────────────────────────────────────────── Scalar max_abs_diff(const Matrix& a, const Matrix& b) diff --git a/src/gpt_checkpoint_test.cpp b/src/gpt_checkpoint_test.cpp index 4147817..a8ec1ed 100644 --- a/src/gpt_checkpoint_test.cpp +++ b/src/gpt_checkpoint_test.cpp @@ -20,9 +20,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::ActivationType; using nn::ComputeEngine; using nn::GPTModel; @@ -31,6 +42,7 @@ using nn::PosEncodingType; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/gpt_gradcheck.cpp b/src/gpt_gradcheck.cpp index 5fa15ab..576bc38 100644 --- a/src/gpt_gradcheck.cpp +++ b/src/gpt_gradcheck.cpp @@ -14,8 +14,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -25,7 +23,21 @@ #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ActivationType; using nn::ComputeEngine; using nn::GPTModel; @@ -37,6 +49,7 @@ using nn::Scalar; using nn::Tensor; using nn::TensorRef; + namespace { diff --git a/src/gpu_batch_scope_test.cpp b/src/gpu_batch_scope_test.cpp index f23f697..6a7fd22 100644 --- a/src/gpu_batch_scope_test.cpp +++ b/src/gpu_batch_scope_test.cpp @@ -11,12 +11,25 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; int g_fail = 0; #define CHECK(cond, msg) \ diff --git a/src/gpu_stability_probe.cpp b/src/gpu_stability_probe.cpp index 3126470..50a2a8e 100644 --- a/src/gpu_stability_probe.cpp +++ b/src/gpu_stability_probe.cpp @@ -38,10 +38,20 @@ #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; -#include "neuralnet.cpp/cli/cli_gpu_option.hpp" -#include "neuralnet.cpp/cli/cli_help.hpp" -#include "neuralnet.cpp/nn.hpp" namespace { diff --git a/src/host_bridge_test.cpp b/src/host_bridge_test.cpp index c156fcd..6c24402 100644 --- a/src/host_bridge_test.cpp +++ b/src/host_bridge_test.cpp @@ -8,14 +8,27 @@ // [4] nn::detail:: 旧名过渡别名仍可用(向后兼容)。 // ───────────────────────────────────────────────────────────────────────── -#include -#include #include #include #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::upload_span; +using nn::download_vector; int g_fail = 0; #define CHECK(cond, msg) \ diff --git a/src/int8_act_test.cpp b/src/int8_act_test.cpp index 01542a0..16080f4 100644 --- a/src/int8_act_test.cpp +++ b/src/int8_act_test.cpp @@ -33,13 +33,29 @@ #include #include -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" -#include "neuralnet.cpp/compute_layer_bitlinear.hpp" -#include "neuralnet.cpp/int8_pack.hpp" -#include "neuralnet.cpp/ternary_pack.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::trit_pack::pack_row; +using nn::trit_pack::words_per_row; +using nn::cli::EngineConfig; +using nn::i8_pack::at; +using nn::cli::create_engine; +using nn::i8_pack::quantize; +using nn::trit_pack::value_of; namespace { diff --git a/src/jit_demo.cpp b/src/jit_demo.cpp index 5060e9e..a811bfb 100644 --- a/src/jit_demo.cpp +++ b/src/jit_demo.cpp @@ -18,11 +18,25 @@ #include #include -#include -#include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::compute; +using nn::fused::kernel_cache_path; +using nn::dsl::leaf; using nn::Scalar; + [[maybe_unused]] static double ms(std::chrono::steady_clock::time_point a, std::chrono::steady_clock::time_point b) { diff --git a/src/kernel_cache_test.cpp b/src/kernel_cache_test.cpp index a2fbaf8..34a1188 100644 --- a/src/kernel_cache_test.cpp +++ b/src/kernel_cache_test.cpp @@ -15,7 +15,13 @@ #include #include -#include +import nn.engine; +import nn.expr; +using nn::ExprOp; +using nn::ExprOperandKind; +using nn::ExprSpec; +using nn::FoldSpec; +using nn::MatmulSpec; namespace { diff --git a/src/kvrec_test.cpp b/src/kvrec_test.cpp index 3289a63..cc52220 100644 --- a/src/kvrec_test.cpp +++ b/src/kvrec_test.cpp @@ -12,7 +12,11 @@ #include #include -#include +import nn.kvrecord; +import nn.engine; +using nn::detail::append_u64; +using nn::detail::append_u32; +using nn::KeyValueRecord; namespace { @@ -29,7 +33,6 @@ void expect(bool ok, const char* name) else std::printf(" [ ok ] %s\n", name); } -using nn::KeyValueRecord; // ── 用例 1:v1 round-trip(含空记录)──────────────────────────────── void test_v1_roundtrip() diff --git a/src/layer_bench.cpp b/src/layer_bench.cpp index 8e0cde1..94b4707 100644 --- a/src/layer_bench.cpp +++ b/src/layer_bench.cpp @@ -29,16 +29,27 @@ #include #include -#include -#include -#include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::Help; using nn::ComputeEngine; using nn::Matrix; using nn::Model; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/layer_broadcast_test.cpp b/src/layer_broadcast_test.cpp index 96f16ff..e400ff1 100644 --- a/src/layer_broadcast_test.cpp +++ b/src/layer_broadcast_test.cpp @@ -7,11 +7,23 @@ // [3] 叶子层(无子层)默认实现不崩、无副作用 // ───────────────────────────────────────────────────────────────────────── -#include #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; #define CHECK(cond, msg) \ diff --git a/src/layer_lifetime_test.cpp b/src/layer_lifetime_test.cpp index c7e5f82..6fedec3 100644 --- a/src/layer_lifetime_test.cpp +++ b/src/layer_lifetime_test.cpp @@ -21,8 +21,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/lifetime_contract_test.cpp b/src/lifetime_contract_test.cpp index 2d52309..b60aadb 100644 --- a/src/lifetime_contract_test.cpp +++ b/src/lifetime_contract_test.cpp @@ -14,8 +14,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/lr_scheduler_test.cpp b/src/lr_scheduler_test.cpp index 646a4a0..2c8dcd4 100644 --- a/src/lr_scheduler_test.cpp +++ b/src/lr_scheduler_test.cpp @@ -18,21 +18,33 @@ #include #include -#include "neuralnet.cpp/lr_scheduler.hpp" #define NN_TEST_COUNTER g_fail #include "test_common.hpp" - -namespace -{ -int g_fail = 0; - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::compute_epoch_lr; using nn::compute_step_lr; using nn::LrScheduleConfig; using nn::Scalar; using nn::StepLrScheduleConfig; +namespace +{ +int g_fail = 0; + + // 相对容差比较(float 调度曲线) [[nodiscard]] bool near(Scalar a, Scalar b, Scalar tol = Scalar(1e-5)) { diff --git a/src/maxpool_gradcheck.cpp b/src/maxpool_gradcheck.cpp index 96f86c8..9ff6c84 100644 --- a/src/maxpool_gradcheck.cpp +++ b/src/maxpool_gradcheck.cpp @@ -14,8 +14,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -24,13 +22,28 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ComputeEngine; using nn::Matrix; using nn::MaxPool2D; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/mnist_infer.cpp b/src/mnist_infer.cpp index b4f95e8..b0684a8 100644 --- a/src/mnist_infer.cpp +++ b/src/mnist_infer.cpp @@ -8,14 +8,8 @@ // 引擎选择:--gpu 启用 GpuEngine(需要 Vulkan),否则 CpuEngine。 // ───────────────────────────────────────────────────────────────────────── +#include #include -#include -#include -#include -#include -#include -#include -#include #include #include @@ -28,10 +22,27 @@ #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::Help; +using nn::cli::create_engine; +using nn::cli::parse_gpu_option; +using nn::Scalar; namespace fs = std::filesystem; -using nn::Scalar; // ==================== 帮助信息 ==================== void print_usage(const char* prog) diff --git a/src/mnist_train.cpp b/src/mnist_train.cpp index 4cb52ad..06d4e52 100644 --- a/src/mnist_train.cpp +++ b/src/mnist_train.cpp @@ -15,17 +15,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include @@ -40,9 +29,23 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::Help; using nn::Scalar; + // ── 精度解析辅助 ───────────────────────────────────────────────────────── // allow_t1_58:三值(T1_58)**只适用于 param 槽**("该层权重是三值"), // compute/stable/optimizer 出现它一律在参数解析处拒绝,而不是拖到 init 期才报 diff --git a/src/model_roles_test.cpp b/src/model_roles_test.cpp index 4c55ee8..89ed5d0 100644 --- a/src/model_roles_test.cpp +++ b/src/model_roles_test.cpp @@ -22,6 +22,7 @@ // 退出码:0 = 通过,1 = 失败 // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -31,10 +32,25 @@ #include #include -#include "neuralnet.cpp/compute_layer_bitlinear.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::trit_pack::words_per_row; +using nn::i8_pack::at; +using nn::trit_pack::latent_row_scale; namespace { diff --git a/src/model_spec_validation_test.cpp b/src/model_spec_validation_test.cpp index 09e23f2..c21f6c4 100644 --- a/src/model_spec_validation_test.cpp +++ b/src/model_spec_validation_test.cpp @@ -11,6 +11,7 @@ // 4. 无 spec 模型(build_gpt_model 直接构建):跳过校验,向后兼容。 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -18,9 +19,22 @@ #include #include #include - -#include "neuralnet.cpp/nn.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::detail::read_matrix_v5; +using nn::detail::spec_from_kv; +using nn::detail::spec_to_kv; using nn::ActivationType; using nn::CnnSpec; using nn::GptSpec; @@ -32,6 +46,8 @@ using nn::NormType; using nn::PosEncodingType; using nn::TransformerSpec; + + namespace { diff --git a/src/nn_warmup.cpp b/src/nn_warmup.cpp index dcb60f2..25fb1e6 100644 --- a/src/nn_warmup.cpp +++ b/src/nn_warmup.cpp @@ -24,9 +24,20 @@ #include #include -#include -#include // Device -#include // nn::fused::kernel_cache_path() +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::fused::kernel_cache_path; namespace { diff --git a/src/nnvocab_test.cpp b/src/nnvocab_test.cpp index 6e755a8..e4d5741 100644 --- a/src/nnvocab_test.cpp +++ b/src/nnvocab_test.cpp @@ -15,8 +15,20 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; import nn.tokenizer; +import nn.train; + namespace { diff --git a/src/optimizer_state_test.cpp b/src/optimizer_state_test.cpp index 9854105..485af70 100644 --- a/src/optimizer_state_test.cpp +++ b/src/optimizer_state_test.cpp @@ -23,10 +23,22 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #define NN_TEST_COUNTER g_fail #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/optimizer_test.cpp b/src/optimizer_test.cpp index 3696715..bbc9c33 100644 --- a/src/optimizer_test.cpp +++ b/src/optimizer_test.cpp @@ -23,16 +23,32 @@ // ctest --test-dir build -R optimizer_test // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include #include #include -#include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::expr::cst; +using nn::expr::input; +using nn::Linear; using nn::Scalar; + namespace { diff --git a/src/parallel_test.cpp b/src/parallel_test.cpp index d7ef4b3..21e4411 100644 --- a/src/parallel_test.cpp +++ b/src/parallel_test.cpp @@ -15,11 +15,11 @@ #include #include -#include int g_fail = 0; #include "test_common.hpp" +import nn.core; namespace { diff --git a/src/precision_profile_test.cpp b/src/precision_profile_test.cpp index 8e00122..3a82110 100644 --- a/src/precision_profile_test.cpp +++ b/src/precision_profile_test.cpp @@ -18,9 +18,21 @@ #include #include -#include "neuralnet.cpp/precision.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/precision_test.cpp b/src/precision_test.cpp index e0b6740..c82e897 100644 --- a/src/precision_test.cpp +++ b/src/precision_test.cpp @@ -27,6 +27,21 @@ #define main test_precision_profile #define g_failures g_fail_precision_profile #include "precision_profile_test.cpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::detail::float_to_half_bits; +using nn::detail::half_bits_to_float; #undef g_failures #undef main diff --git a/src/precision_type_test.cpp b/src/precision_type_test.cpp index c3f9644..da84551 100644 --- a/src/precision_type_test.cpp +++ b/src/precision_type_test.cpp @@ -16,9 +16,23 @@ #include #include -#include "neuralnet.cpp/precision.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::detail::float_to_half_bits; +using nn::half_bits_to_float; namespace { diff --git a/src/rapt_checkpoint_test.cpp b/src/rapt_checkpoint_test.cpp index 817b724..ddce534 100644 --- a/src/rapt_checkpoint_test.cpp +++ b/src/rapt_checkpoint_test.cpp @@ -24,9 +24,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::ActivationType; using nn::ComputeEngine; using nn::NormType; @@ -35,6 +46,7 @@ using nn::RAPTModel; using nn::Scalar; using nn::Tensor; + namespace { diff --git a/src/rapt_gradcheck.cpp b/src/rapt_gradcheck.cpp index f669a84..46f5d98 100644 --- a/src/rapt_gradcheck.cpp +++ b/src/rapt_gradcheck.cpp @@ -11,8 +11,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -22,7 +20,21 @@ #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ComputeEngine; using nn::Matrix; using nn::ReLULinearAttention; @@ -30,6 +42,7 @@ using nn::Scalar; using nn::Tensor; using nn::TensorRef; + namespace { diff --git a/src/rapt_smoke_test.cpp b/src/rapt_smoke_test.cpp index f0a788b..279c1e9 100644 --- a/src/rapt_smoke_test.cpp +++ b/src/rapt_smoke_test.cpp @@ -10,14 +10,27 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; int main(int argc, char* argv[]) { diff --git a/src/rapt_test.cpp b/src/rapt_test.cpp index 14363f3..73e7248 100644 --- a/src/rapt_test.cpp +++ b/src/rapt_test.cpp @@ -9,7 +9,6 @@ #include #include -#include #define main test_rapt_gradcheck #include "rapt_gradcheck.cpp" @@ -21,6 +20,9 @@ #define main test_rapt_checkpoint #include "rapt_checkpoint_test.cpp" +import nn.cli; +using nn::cli::EngineConfig; +using nn::cli::create_engine; #undef main int main(int argc, char* argv[]) diff --git a/src/rl_experience_test.cpp b/src/rl_experience_test.cpp index 8afac4d..a19274d 100644 --- a/src/rl_experience_test.cpp +++ b/src/rl_experience_test.cpp @@ -15,8 +15,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/rl_gpu_smoke_test.cpp b/src/rl_gpu_smoke_test.cpp index 02b1255..5b2298e 100644 --- a/src/rl_gpu_smoke_test.cpp +++ b/src/rl_gpu_smoke_test.cpp @@ -14,8 +14,21 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::download_vector; int g_fail = 0; diff --git a/src/rl_grpo_test.cpp b/src/rl_grpo_test.cpp index 3cb7998..d1c609f 100644 --- a/src/rl_grpo_test.cpp +++ b/src/rl_grpo_test.cpp @@ -16,8 +16,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/rl_output_test.cpp b/src/rl_output_test.cpp index 91cbf7f..3924d46 100644 --- a/src/rl_output_test.cpp +++ b/src/rl_output_test.cpp @@ -17,8 +17,22 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::detail::spec_from_kv; +using nn::detail::spec_to_kv; int g_fail = 0; diff --git a/src/rl_pipeline_test.cpp b/src/rl_pipeline_test.cpp index 9416189..73c1f56 100644 --- a/src/rl_pipeline_test.cpp +++ b/src/rl_pipeline_test.cpp @@ -15,8 +15,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/rl_policy_test.cpp b/src/rl_policy_test.cpp index 6ef0c6c..698d5bc 100644 --- a/src/rl_policy_test.cpp +++ b/src/rl_policy_test.cpp @@ -18,8 +18,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/rl_ppo_test.cpp b/src/rl_ppo_test.cpp index df38fcf..d7968e7 100644 --- a/src/rl_ppo_test.cpp +++ b/src/rl_ppo_test.cpp @@ -17,8 +17,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/rl_training_integration_test.cpp b/src/rl_training_integration_test.cpp index 16640d7..eb626c4 100644 --- a/src/rl_training_integration_test.cpp +++ b/src/rl_training_integration_test.cpp @@ -14,8 +14,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/rmsnorm_gradcheck.cpp b/src/rmsnorm_gradcheck.cpp index aa8924a..4a84f7a 100644 --- a/src/rmsnorm_gradcheck.cpp +++ b/src/rmsnorm_gradcheck.cpp @@ -12,8 +12,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -21,7 +19,21 @@ #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ComputeEngine; using nn::Layer; using nn::Matrix; @@ -30,6 +42,7 @@ using nn::Scalar; using nn::Tensor; using nn::TensorRef; + namespace { diff --git a/src/run_driver_test.cpp b/src/run_driver_test.cpp index 6d75a4e..ab70fc4 100644 --- a/src/run_driver_test.cpp +++ b/src/run_driver_test.cpp @@ -12,6 +12,7 @@ // [9] 断点续训起点:start_epoch/start_step 传入 do_update,rewind 被调用 // ───────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -22,9 +23,12 @@ #include #include -#include -#include -#include +import nn.model; +import nn.train; +import nn.engine; +import nn.expr; +import nn.layer; +using nn::create_cpu_engine; static int g_fail = 0; #define CHECK(cond, msg) \ diff --git a/src/sampling_test.cpp b/src/sampling_test.cpp index 5a9677f..7946744 100644 --- a/src/sampling_test.cpp +++ b/src/sampling_test.cpp @@ -26,13 +26,26 @@ #include #include -#include +// P5:聚合头 → 域导入(import 面 = nn.hpp 覆盖的全部域,宁多勿少) #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::SamplingConfig; using nn::Scalar; + int g_fail = 0; namespace diff --git a/src/session_checkpoint_test.cpp b/src/session_checkpoint_test.cpp index a71efea..a1e4d8d 100644 --- a/src/session_checkpoint_test.cpp +++ b/src/session_checkpoint_test.cpp @@ -20,8 +20,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/sha256_test.cpp b/src/sha256_test.cpp index be1ba8c..bb1a6d9 100644 --- a/src/sha256_test.cpp +++ b/src/sha256_test.cpp @@ -12,11 +12,15 @@ #include #include -#include int g_fail = 0; #include "test_common.hpp" +import nn.core; +using nn::detail::Sha256State; +using nn::detail::sha256_digest; +using nn::detail::sha256_finalize; +using nn::detail::sha256_update; namespace { diff --git a/src/softmax_gradcheck.cpp b/src/softmax_gradcheck.cpp index fa2d902..64933c3 100644 --- a/src/softmax_gradcheck.cpp +++ b/src/softmax_gradcheck.cpp @@ -8,8 +8,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -17,13 +15,28 @@ #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ComputeEngine; using nn::Matrix; using nn::Scalar; using nn::Softmax; using nn::Tensor; + namespace { diff --git a/src/swiglu_gradcheck.cpp b/src/swiglu_gradcheck.cpp index cb21507..7f600e7 100644 --- a/src/swiglu_gradcheck.cpp +++ b/src/swiglu_gradcheck.cpp @@ -13,8 +13,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include #include #include @@ -24,16 +22,30 @@ #include #include #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::create_engine; using nn::ActivationType; using nn::ComputeEngine; -using nn::ComputeEngine; using nn::FeedForward; using nn::Matrix; using nn::Scalar; using nn::Tensor; using nn::TensorRef; + namespace { diff --git a/src/t1_58_test.cpp b/src/t1_58_test.cpp index f090075..7f2ffff 100644 --- a/src/t1_58_test.cpp +++ b/src/t1_58_test.cpp @@ -18,6 +18,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -33,10 +34,24 @@ #include #endif -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::trit_pack::words_per_row; +using nn::i8_pack::at; namespace { diff --git a/src/tensor_expr_test.cpp b/src/tensor_expr_test.cpp index a1fc67d..1d2d08c 100644 --- a/src/tensor_expr_test.cpp +++ b/src/tensor_expr_test.cpp @@ -12,7 +12,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include #include "test_common.hpp" // M1 访问收口:upload(宿主 Matrix 进引擎张量) @@ -21,12 +20,25 @@ #include #include #include - -using nn::ComputeEngine; +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::dsl::relu; using nn::ComputeEngine; using nn::Matrix; using nn::Scalar; using nn::Tensor; + using namespace nn::dsl; namespace diff --git a/src/tensor_precision_test.cpp b/src/tensor_precision_test.cpp index 32e02a7..040a14c 100644 --- a/src/tensor_precision_test.cpp +++ b/src/tensor_precision_test.cpp @@ -17,11 +17,21 @@ #include // RV-02:形状溢出负例(size_t 上限) #include -#include "neuralnet.cpp/precision.hpp" -#include "neuralnet.cpp/compute_tensor.hpp" -#include "neuralnet.cpp/engine.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/tensor_schema_test.cpp b/src/tensor_schema_test.cpp index 4bb2951..75c13c7 100644 --- a/src/tensor_schema_test.cpp +++ b/src/tensor_schema_test.cpp @@ -15,8 +15,22 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::detail::schema_put_u32; +using nn::detail::schema_put_string; int g_fail = 0; diff --git a/src/ternary_gemm_test.cpp b/src/ternary_gemm_test.cpp index 6b22db7..5acdf95 100644 --- a/src/ternary_gemm_test.cpp +++ b/src/ternary_gemm_test.cpp @@ -21,6 +21,7 @@ // 退出码:0 = 通过,1 = 失败,77 = 跳过(--gpu 且无设备,或引擎无打包能力) // ─────────────────────────────────────────────────────────────────────────── +#include #include #include #include @@ -31,12 +32,33 @@ #include #include -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" -#include "neuralnet.cpp/compute_layer_bitlinear.hpp" -#include "neuralnet.cpp/ternary_pack.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::trit_pack::value_of; +using nn::trit_pack::code_of; +using nn::trit_pack::dequant_row; +using nn::trit_pack::kBase3PerByte; +using nn::trit_pack::words_per_row; +using nn::trit_pack::at_base3; +using nn::trit_pack::pack_row_base3; +using nn::trit_pack::unpack_row_base3; +using nn::trit_pack::base3_bytes_per_row; +using nn::trit_pack::bytes_per_row; +using nn::i8_pack::pack_row; namespace { diff --git a/src/test_common.hpp b/src/test_common.hpp index f66fb7a..53b6892 100644 --- a/src/test_common.hpp +++ b/src/test_common.hpp @@ -29,10 +29,20 @@ #include #include #include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; -#include "neuralnet.cpp/algebra_matrix.hpp" // Matrix / Scalar -#include "neuralnet.cpp/compute_tensor.hpp" // Tensor -#include "neuralnet.cpp/compute_engine.hpp" // ComputeEngine(M1:库外经引擎上传/下载) // ── 失败断言宏 ───────────────────────────────────────────────────────────── #ifndef NN_TEST_COUNTER diff --git a/src/text_infer.cpp b/src/text_infer.cpp index 671f9b0..7aa0d21 100644 --- a/src/text_infer.cpp +++ b/src/text_infer.cpp @@ -8,13 +8,6 @@ // ───────────────────────────────────────────────────────────────────────── #include -#include -#include -#include -#include -#include -#include -#include #include #include @@ -24,9 +17,23 @@ #include #include #include - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::Help; using nn::Scalar; + // ==================== 帮助信息 ==================== void print_usage(const char* prog) { diff --git a/src/text_train.cpp b/src/text_train.cpp index b068d46..854d9c7 100644 --- a/src/text_train.cpp +++ b/src/text_train.cpp @@ -34,18 +34,36 @@ #include #include -#include -#include -#include -#include // 训练工厂(数据集 + 样本 + 循环) -#include -#include -#include -#include -#include -#include -#include -#include +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::cli::ResumeStateAdoption; +using nn::cli::TextTrainMemProbe; +using nn::cli::TrainConfig; +using nn::cli::adopt_resume_state; +using nn::cli::create_engine; +using nn::prof::dump; +using nn::prof::enabled; +using nn::cli::jit_warmup; +using nn::cli::load_optimizer_state; +using nn::cli::make_text_train_mem_probe; +using nn::cli::precision_offload_combo_error; +using nn::cli::resume_text_train; +using nn::cli::run_text_train_loop; +using nn::cli::save_text_train_model; +using nn::cli::text_train_ckpt_exit_code; +using nn::cli::verify_dataset_identity; namespace { diff --git a/src/text_train_modules_test.cpp b/src/text_train_modules_test.cpp index 1849eb8..ce0a0c7 100644 --- a/src/text_train_modules_test.cpp +++ b/src/text_train_modules_test.cpp @@ -29,14 +29,26 @@ #include #include -#include "neuralnet.cpp/nn.hpp" -#include "neuralnet.cpp/cli/text_train_checkpoint.hpp" -#include "neuralnet.cpp/cli/text_train_config.hpp" -#include "neuralnet.cpp/cli/text_train_loop.hpp" // save_ckpt_with_state(P1-2 接线) -#include "neuralnet.cpp/cli/text_train_recovery.hpp" #define NN_TEST_COUNTER g_fail #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::TextTrainCheckpointLog; +using nn::cli::is_forbidden_resume_flag; +using nn::cli::save_text_train_model; +using nn::cli::text_train_ckpt_path; namespace { diff --git a/src/text_trainer_test.cpp b/src/text_trainer_test.cpp index fd7ad39..1a1a93c 100644 --- a/src/text_trainer_test.cpp +++ b/src/text_trainer_test.cpp @@ -25,11 +25,22 @@ #include #include -#include "neuralnet.cpp/nn.hpp" -#include "neuralnet.cpp/text_trainer.hpp" // 训练工厂(L5;不在 nn.hpp 聚合头内) #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_failures = 0; diff --git a/src/threadpool_test.cpp b/src/threadpool_test.cpp index 76f5305..e5c4cab 100644 --- a/src/threadpool_test.cpp +++ b/src/threadpool_test.cpp @@ -15,11 +15,11 @@ #include #include -#include int g_fail = 0; #include "test_common.hpp" +import nn.core; namespace { diff --git a/src/tokenizer_consistency_test.cpp b/src/tokenizer_consistency_test.cpp index d5bd886..36fb3aa 100644 --- a/src/tokenizer_consistency_test.cpp +++ b/src/tokenizer_consistency_test.cpp @@ -20,11 +20,23 @@ #include #include -import nn.tokenizer; #include "test_common.hpp" - +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; using nn::BPETokenizer; + namespace { @@ -32,11 +44,15 @@ int g_fail = 0; // ── 参考实现:pre_tokenize 的独立朴素实现(标记切分 + 纯文本 sregex_iterator)─ // 正则切分路径独立于被测的手写状态机(pre_match_len),作为逐字节对拍的基准。 +// markers 用 string_view(而非库侧的 std::string):刻意打破与 +// domain_tokenizer_bpe.hpp 构造的同形——clang 24 在「多模块 import × 同 TU 两个 +// identical static local 构造」组合下产出错误代码(-O0/-O3 均复现,纯代码与 gcc +// 全过),见 docs/internal/36 号台账 C6。语义不变(longest-match 对拍保留)。 std::vector old_pre_tokenize(std::string_view text) { - static const std::vector markers = [] + static const std::vector markers = [] { - std::vector m = { + std::vector m = { "<|system|>", "<|end_of_system|>", "<|user|>", "<|end_of_user|>", "<|assistant|>", "<|end_of_assistant|>", "<|end_of_turn|>", "<|sep|>", "<|tool|>", diff --git a/src/tokenizer_infer.cpp b/src/tokenizer_infer.cpp index 23355dd..688279a 100644 --- a/src/tokenizer_infer.cpp +++ b/src/tokenizer_infer.cpp @@ -9,9 +9,7 @@ // 支持交互模式和命令行模式。 // ───────────────────────────────────────────────────────────────────────── -import nn.tokenizer; #include -#include #include #include @@ -24,6 +22,9 @@ import nn.tokenizer; #include #include #include +import nn.tokenizer; +import nn.cli; +using nn::cli::Help; // ==================== 帮助信息 ==================== void print_usage(const char* prog) diff --git a/src/tokenizer_train.cpp b/src/tokenizer_train.cpp index c14a51a..bec5739 100644 --- a/src/tokenizer_train.cpp +++ b/src/tokenizer_train.cpp @@ -9,18 +9,19 @@ // BPE 能学会合并常见中文词组(更适合小模型) // ───────────────────────────────────────────────────────────────────────── -import nn.tokenizer; #include -#include #include #include #include #include #include - +import nn.tokenizer; +import nn.cli; +using nn::cli::Help; using nn::Scalar; + // ==================== 帮助信息 ==================== void print_usage(const char* prog) { diff --git a/src/train_state_test.cpp b/src/train_state_test.cpp index 363653c..ad5fdb8 100644 --- a/src/train_state_test.cpp +++ b/src/train_state_test.cpp @@ -31,12 +31,22 @@ #include #include -#include "neuralnet.cpp/nn.hpp" -#include "neuralnet.cpp/model_serialization.hpp" -#include "neuralnet.cpp/train_state.hpp" #define NN_TEST_COUNTER g_fail #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; namespace { diff --git a/src/training_group_session_test.cpp b/src/training_group_session_test.cpp index f9ca033..4994ff0 100644 --- a/src/training_group_session_test.cpp +++ b/src/training_group_session_test.cpp @@ -13,8 +13,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/training_session_test.cpp b/src/training_session_test.cpp index 4fb6fa4..7073054 100644 --- a/src/training_session_test.cpp +++ b/src/training_session_test.cpp @@ -17,8 +17,20 @@ #include #include -#include "neuralnet.cpp/nn.hpp" #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; int g_fail = 0; diff --git a/src/write_epoch_test.cpp b/src/write_epoch_test.cpp index 58321fd..be7098c 100644 --- a/src/write_epoch_test.cpp +++ b/src/write_epoch_test.cpp @@ -25,13 +25,27 @@ #include #include -#include "neuralnet.cpp/cli/cli_engine_factory.hpp" -#include "neuralnet.cpp/compute_layer_bitlinear.hpp" -#include "neuralnet.cpp/compute_optimizer.hpp" -#include "neuralnet.cpp/expr_dsl.hpp" #define NN_TEST_COUNTER g_failures #include "test_common.hpp" +import nn.cli; +import nn.core; +import nn.dataset; +import nn.domain; +import nn.engine; +import nn.expr; +import nn.kvrecord; +import nn.layer; +import nn.loss; +import nn.model; +import nn.opt; +import nn.tokenizer; +import nn.train; +using nn::cli::EngineConfig; +using nn::dsl::compute; +using nn::dsl::compute_into; +using nn::cli::create_engine; +using nn::dsl::leaf; namespace { From dbbf5947d8b3f1399d484b01d6eca82443ed79e1 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sat, 7 Nov 2026 00:04:59 +0800 Subject: [PATCH 09/16] =?UTF-8?q?refactor(P6):=20=E6=94=B6=E5=B0=BE?= =?UTF-8?q?=E2=80=94=E2=80=94BMI=20=E7=9C=9F=E5=AE=9E=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E4=BF=AE=E5=A4=8D=20+=20=E9=97=A8=E7=A6=81?= =?UTF-8?q?=E5=9B=BA=E5=8C=96=20+=20=E6=80=A7=E8=83=BD=E5=AF=B9=E7=85=A7?= =?UTF-8?q?=20+=20=E6=96=87=E6=A1=A3=E5=90=8C=E6=AD=A5=EF=BC=8835=20?= =?UTF-8?q?=E5=8F=B7=20P6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OBJECT_DEPENDS 正确性修复:nn_use_modules 把 BMI 挂进消费方源文件编译边 (add_dependencies 是 order-only + clang -MD 不记 pcm → 改公共头只重编 BMI、 消费方 .o 陈旧;修复后改公共头 = 82 TU + 13 BMI + 全链接正确重编) - 门禁固化:configure 期 nn-gate 检查 src/ 残留 #include (合法例外仅 nn_error_macros.hpp 宏岛头与 STL),正反验证通过 - 性能对照(P5 后口径):改 consumer 源 ≈79s / 改公共头 ≈35min / 冷构建 ≈15min, 归档 research §6.3 与 history - 文档同步:AGENTS(§2 选项/提速、§3 模块行、§7 三处按 P3/D4 修正、索引)、 development/01 §8/§8.1、development/02 §1.3/§17 宏岛头、usage/05 删 PCH 行、 history P5/P6/clang-bug 三条、36 号 C6 行更新(复测 + LLVM issue #239625) - doc_audit 全绿;ctest 81/81(全量重建后) --- AGENTS.md | 20 +++-- CMakeLists.txt | 36 ++++++++ docs/development/01-cmake-integration.md | 91 ++++++++++----------- docs/development/02-cpp-api-manual.md | 9 ++ docs/history/history.md | 100 +++++++++++++++++++++++ docs/internal/36-compromise-ledger.md | 2 +- docs/usage/05-consume-as-library.md | 1 - research/modules-import-nn/README.md | 72 ++++++++-------- 8 files changed, 242 insertions(+), 89 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9d6ea38..32ec86b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,11 +26,11 @@ cmake -B build -G Ninja -DNN_ENABLE_TESTS=ON && cmake --build build && ctest --t - 编译器与标准库:**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 位于 `/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 构建四种组合时会先撞到,属**真实死变量**,删掉即可(先例:`src/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,见下条);`NN_ENABLE_MODULES`(**默认 ON 于顶层工程**,嵌入默认 OFF;仅 GNU 驱动 Clang 生效,GCC/cl.exe/clang-cl 自动关闭——粒度 C++ 模块路径,见下「C++ 模块」条)。 +- 构建选项:`NN_ENABLE_NATIVE`(默认 ON,开启 `-march=native`,分发/CI 用 `OFF` 生成可移植基线);`NN_ENABLE_TESTS`(默认 OFF);`NN_ENABLE_PROBES`(默认 OFF,见下条)。模块构建**无条件**(P5/D9 已删 `NN_ENABLE_MODULES`/`NN_ENABLE_PCH` 两个开关,见下「C++ 模块」条)。 - **预设构建(`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/`;另有 `caller` 无关的 `build`/`test`/`workflow` preset(`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_16BIT_STORAGE` / `NN_VULKAN_NO_TIMELINE` / `NN_VULKAN_NO_INT8_DOT`(设备与特性逃生阀)、`NN_STRICT_CLOSED_WORLD`(JIT 看门狗)、`NN_KERNEL_CACHE_DIR` / `NN_KERNEL_CACHE_PATH` / `NN_KERNELGEN_PATH`(JIT 缓存/生成器配置)、`NN_PROFILE` / `NN_PROFILE_TRACE`(剖析器门控),新增同类开关必须"读一次 + 缓存";② `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 ` 即可(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( MAIN )`(仅 GPU 启用时有定义;**函数体在调用方作用域求值**,库内路径经 `NN_LIB_SOURCE_DIR`/`NN_LIB_BINARY_DIR` CACHE 变量解析,函数内不可用 `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++ 模块路径走**显式 BMI 自定义命令 + `-fmodule-file` 传播**(`NN_ENABLE_MODULES`,见下),不开 CMake 模块扫描,省掉每 TU 一次 `clang-scan-deps`(模块只有 4 个单元 + 9 个消费方参与,其余 ~270 个 TU 零扫描);③ `scan_exprs` 的 glslc **并行**(`NN_SCAN_JOBS=` 覆盖,`=1` 回串行)+ `fused_registry.hpp` **内容稳定写入**(内容不变则不动 mtime,靠自定义命令的 `restat` 掐断下游全量重编);④ 编译器缓存探测扩到 ccache/**sccache**(`-DNN_COMPILER_LAUNCHER=` 可指定);⑤ **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/01-cmake-integration.md` §8)。**⚠ 两条不变量**:(a) 收集器目标(`scan_exprs`、`*_nnfusion_collect`)**不套 PCH**——前者在源文件内、include 之前 `#define NN_EXPR_SCAN`,PCH 会先于该宏展开 `nn.hpp` → DSL 收集钩子失效(注册表空);(b) 新写"生成物参与 PCH"的场景必须显式 `add_dependencies(, <生成边>)`——`__has_include` 在 PCH 里**一次性**求值,早于生成完成就整块落空且因无 depfile 记录而永不重试。 +- **编译提速(默认已开启,无需额外配置)**:本工程 header-only。src/ 全量模块化(P5,35 号 D8)后消费方经 `-fmodule-file` 从 BMI 取声明,**共享 PCH 机制已整体删除**(35 号 D9:PCH 的"共享 parse"职责由模块 BMI 承接;收集器 `scan_exprs` 不套 PCH 的旧不变量随之消失)。已落地的事:① `CMAKE_CXX_SCAN_FOR_MODULES OFF`——本仓的 C++ 模块路径走**显式 BMI 自定义命令 + `-fmodule-file` 传播**(`nn_add_module`/`nn_use_modules`),不开 CMake 模块扫描,省掉每 TU 一次 `clang-scan-deps`(13 个模块单元 + 其余 TU 零扫描);② `scan_exprs` 的 glslc **并行**(`NN_SCAN_JOBS=` 覆盖,`=1` 回串行)+ `fused_registry.hpp` **内容稳定写入**(内容不变则不动 mtime,靠自定义命令的 `restat` 掐断下游全量重编);③ 编译器缓存探测扩到 ccache/**sccache**(`-DNN_COMPILER_LAUNCHER=` 可指定),但**模块消费方一律绕开 launcher**(P5 根因:ccache 4.13 手册明言不支持标准 C++ modules——key 不含 `-fmodule-file` 的 BMI 内容,direct 模式 depfile 只记文本头;"BMI 重编 + 消费方源码没动"时会吐出**旧 .o**,实测 text_train.cpp 的 `parse_text_train_args` 加 inline 后链出 undefined symbol);④ **BMI 是消费方 TU 的真实编译依赖**(P6 修复,2026-11-06)——`nn_use_modules` 经 `OBJECT_DEPENDS` 把 pcm 挂进每个源文件的编译边:`add_dependencies` 在 ninja 里是 order-only(pcm mtime 变不触发 TU 重编)且 clang 的 `-MD` depfile **不记录** pcm(实测 `.ninja_deps` 无 pcm 条目),缺了它改公共头只重编 BMI、消费方 .o 保留旧实例化(陈旧代码)。**代价/实测(P6,无 PCH 无缓存口径,dev preset + clang 24 / 8 核)**:改 consumer 源(单 TU 全量解析 + LTO 链接)≈79s(基线表 ccache 命中 0.48s);改公共头(BMI 全量 + 消费方全量 + 全链接)≈35min;冷构建见 §12 或 `research/modules-import-nn/README.md` §6。历史 PCH 数字(125.5s→72.7s 净构建、ccache×PCH 兼容修复 30.2s→0.11s 等)归档在 `docs/history/history.md`。 - 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。 @@ -50,7 +50,7 @@ cmake -B build -G Ninja -DNN_ENABLE_TESTS=ON && cmake --build build && ctest --t | 算子级配对 A/B(`layer_bench`) | `src/layer_bench.cpp`:`--op matmul,matmul_q,matmul_q_bw,scan_prefix,scan_suffix…`(`--f16` 走 f16 变体)+ `--dk `(扫描原语 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) | -| C++ 模块(粒度 import,2026-11-01 落地) | `modules/nn.{core,kvrecord,dataset,tokenizer}.cpp` = **薄接口模块**(全局片段文本包含域头 + `export namespace nn { using … }` 逐符号 re-export + `export import` 依赖链;DAG `core→kvrecord→dataset→tokenizer`)。构建 = 显式 BMI 自定义命令 + `-fmodule-file` 经 `nn_mod_<域>` INTERFACE 传播(`nn_add_module`/`nn_use_modules`);开关 `NN_ENABLE_MODULES`。**9 个叶子消费者双轨**(`#ifdef NN_USE_MODULES` import / else include):`tokenizer_test`/`nnvocab_test`/`dataset_test`/`dataset_integration_test` + 4 个 tokenizer/dataset app。硬约束:宏不可导出(消费方显式 include `core_errors.hpp`)、STL 留在全局片段(消费方自带 STL 头)、仅 Clang。**实测非提速路径**(ccache+PCH 基线已压平;价值 = 标准符合性 + 精确依赖 + 摆脱 PCH),详见 `research/modules-import-nn/README.md` §6。engine/layers/model/train 依赖 `NN_EXPR_SCAN` 宏收集 → 本轮不模块化 | +| C++ 模块(粒度 import,2026-11-01 落地,P5 铺满 13 域) | `modules/nn.{core,kvrecord,dataset,tokenizer,expr,engine,layer,loss,opt,model,train,domain,cli}.cpp` = **薄接口模块**(全局片段文本包含域头 + `export namespace nn { using … }` 逐符号 re-export + `export import` 依赖链;DAG `core→kvrecord→dataset→tokenizer`,P4 新域全部 DEPS core)。构建 = 显式 BMI 自定义命令 + `-fmodule-file` 经 `nn_mod_<域>` INTERFACE 传播(`nn_add_module`/`nn_use_modules`);**构建无条件、开关已删**(P1 删 `NN_ENABLE_MODULES`/`NN_USE_MODULES`,P5/D9 删 PCH)。**src/ 全量 import 化(P5/D8)**:79 个消费方 TU 一律 `import nn.*`,残留 `#include ` 由 configure 期门禁拦截(P6-2,`nn-gate`)。硬约束:宏不可导出(消费方显式 include `nn_error_macros.hpp` 宏岛头,P2/D3)、STL 留在全局片段(消费方自带 STL 头)、仅 Clang(GNU 驱动 `--precompile`/`-fmodule-file=`)。**消费方 TU 经 `OBJECT_DEPENDS` 真实依赖 BMI**(P6 修复:order-only + depfile 不记 pcm 会让改公共头只重编 BMI、消费方 .o 陈旧);ccache 对消费方禁用(key 不含 BMI 内容)。实测**非提速路径**(ccache 命中率已不可得;价值 = 标准符合性 + 精确依赖 + 摆脱 PCH),详见 `research/modules-import-nn/README.md` §6 | | 混合精度 / 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`);**int8 激活量化(W1.58A8,P3,docs 21 §4.18)**:`int8_pack.hpp`(布局/编解码唯一来源)+ `quantize_i8`/`unpack_i8`/`matmul_q_i8` 原语(`shaders/{quantize_i8,unpack_i8,matmul_q_i8}.comp`)+ `BitLinear::set_activation_quant`(默认关;**s 必须除 127**,原始 absmax 会退化成三值分辨率) | | 矩阵/代数层(CPU 存储与手写内核) | `algebra_matrix.hpp` / `algebra_span.hpp` / `algebra_ops.hpp`(`Expression`/`BoolExpression` 概念在 `expr_dsl.hpp`) | | 表达式 DSL / 融合 IR | `expr_dsl.hpp`(主仓保留,消费方 TU 模板求值)/ `expr_spec.hpp` + `expr_opt.hpp` + `expr_graph.hpp`(**抽象层**,abstraction/include;跨界 ABI)/ `expr_registry.hpp` + `expr_fold.hpp`(**新仓库** engine/include;IR-C 图融合 = **现行能力**:`expr_graph.hpp` + `begin_expr/end_expr` + `dsl::start_expr/end_expr` + GPU 图执行;P1 链/P2 写穿多输出,已接入 Adam/AdamW step) | @@ -249,7 +249,7 @@ load_state`(含 Adam 的 `t`);读写 API = `CheckpointState` / `peek_train 4. **不穿透接口**:上层不访问下层内部数据结构(`.data()` 等),改一个模块只改一个头文件。 5. **布局全局统一 batch-major**:序列展平列序 `i = b*seq + t`(batch 在列方向)。position-major(`i = t*batch + b`)会造成跨样本串扰,任何情况下不得混用。**所有注意力/序列相关测试必须覆盖 batch>1**(batch=1 时两种布局重合,测不出)。 6. **GPU 命令录制生命周期**:录制期(`begin_batch`→`end_batch` 之间)引用的所有张量必须存活到 `end_batch()` 之后;GPU buffer 销毁走 `pending_destroys_` 延迟队列。 -7. **GPU 表达式执行(AOT 注册表 + JIT 兜底)**:构建期 `scan_exprs` 生成的注册表是**预热加速器**——运行时按 `expr_spec_key` 查内嵌注册表,命中即执行;`eval_expr` 主路径**未命中默认走运行期 JIT 现编**(懒加载 `libnn-kernelgen.so` → 生成 SPIR-V → 懒建 pipeline → 执行);fold 与 `eval_expr_reduce` / `eval_expr_into` / `eval_expr_multi_into` 入口的 miss 仍硬报错(JIT 接线未全覆盖,见 `compute_gpu_engine.hpp`);`NN_STRICT_CLOSED_WORLD=1` 让主路径也恢复硬报错(opt-in 看门狗)。 +7. **GPU 表达式执行(预热注册表 + JIT 兜底)**:引擎(新仓库)构建期由 `scan_exprs` 生成的注册表是**预热加速器**——运行时按 `expr_spec_key` 查内嵌注册表,命中即执行;`eval_expr` 主路径**未命中默认走运行期 JIT 现编**(懒加载 `libnn-kernelgen.so` → 生成 SPIR-V → 懒建 pipeline → 执行);fold 与 `eval_expr_reduce` / `eval_expr_into` / `eval_expr_multi_into` 入口的 miss 仍硬报错(JIT 接线未全覆盖,见 `compute_gpu_engine.hpp`);`NN_STRICT_CLOSED_WORLD=1` 让主路径也恢复硬报错(opt-in 看门狗)。**主仓侧 AOT 收集链已随 P3/D4 删除**(`scan_exprs`/`fused_registry.hpp` 生成/`NN_EXPR_SCAN` 分支/`nn_enable_gpu_fusion()`,见 L67 与新仓库 `engine/tools/`)。 8. **确定性**:任何"依赖容器迭代顺序"的决策点(BPE 平局打破、ID 分配等)必须显式排序/按 key 打破平局;并行化后结果必须与单线程逐字节一致。 9. **大词表禁止物化 one-hot**:用 `CrossEntropyLoss::forward_sparse`(整数标签 + loss_mask)。 10. **`//` 注释中禁止出现 `\` 反斜杠**(尤其行尾):GCC 行拼接会把下一行并入当前注释——轻则 `-Wcomment -Werror` 编译失败,重则下一行代码被静默吞掉。 @@ -275,13 +275,19 @@ GPT 序列展平: 列序 i = b*seq + t(batch-major,全局唯一约定) - 主要入口:`dsl::compute(engine, expr, rows, cols)`(一行表达式,最常用——**输出形状可省**,推导口径见下条 bullet);把结果写进既有张量(原地更新,零分配)用 `dsl::compute_into(engine, expr, dst)`;归约语义用 `dsl::compute_reduce`。**跨表达式融合(IR-C)为现行能力、已接入训练路径**:`engine.begin_expr()/end_expr()` + `ExprSegment` RAII 守卫 + `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]`),未登记的**单节点**结构走 JIT 兜底(`NN_STRICT_CLOSED_WORLD=1` 可恢复硬报错)。历史与取舍见 `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/02-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`**(漏登记 = 最后已知的注册表预热缺口,未登记结构仍有 JIT 兜底);`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` 同进程**收集 + 生成**: +- **构建期单步(引擎侧,新仓库)**(引擎 .so 构建时自动编排;主仓侧已无此流程——P3/D4 删 + 主仓 AOT 收集链,`scan_exprs` 现为**新仓库** `engine/tools/` 的构建工具,见 L67)—— + `scan_exprs` 同进程**收集 + 生成**: 1. **收集**:只收集**结构**(= 唯一需要构建期枚举的东西)。两个来源**互补、不冗余**(`NN_SCAN_DUMP_SOURCES=1` 可逐条打印来源):`FusedAnchor` 在静态初始化期按表达式**类型**自登记(编译期可达,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 未登记 = 注册表预热缺口(走 JIT 兜底,性能降级;实测 `Softmax::backward` 中招后补 dry-run;见 `docs/history/history.md`「项目 4/5」条)。 2. **生成**:`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 constant `uint 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` **不**叠加网格变体(它多一个 `prec` PC 槽、绑定翻倍)。**批量变体同理(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 的 CPU `end_expr` 补记**(= `add_variant(spec, expr_prec_sig_of(输入张量, tail 输出精度))`)——单节点结构有 `scan_register`/`eval_expr*` 入口,**融合结构只在 GPU `end_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( 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")` 会解析到库的那一份、使用者的自定义结构全部走 JIT 兜底(注册表预热失效)。样例见 `examples/fusion_custom_layer*`(**库内目标**形态;函数本身定义在本仓 `CMakeLists.txt` 的 `if(Vulkan_FOUND)` 内,且仓库无 `install()`/export——真正**树外**工程要复用它需先做 install/export,见 `docs/internal/18-roadmap.md` E2)。 +- **库外使用者**:主仓的 `nn_enable_gpu_fusion( MAIN )` **已随 P3/D4 删除** + (样例 `examples/fusion_custom_layer*` 同删,见 L67);库外自研层走 `dsl::compute` + + **运行期 JIT 兜底**(未登记结构懒编 SPIR-V)或引擎侧 `scan_exprs` 的收集机制(新仓库)。 + 历史机制说明(收集器构成、include 顺序语义、树外工程注意点)归档在 + `docs/history/history.md`「AOT 闭合世界 → 运行期 JIT 兜底」条。 - **端到端验收(库外 + 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 f32 `0.6712035`(差 0);**GPU f32(V0)`0.6712036`(差 5.96e-08)**;**GPU f16(V1 运行期精度分派)`0.6708984`(差 3.05e-04,f16 舍入内)**;归一化形态 GPU f16 `1.2246094`。含 rparams ⇒ native16 谓词不通过 ⇒ f16 命中必然是 `key#x`;注册表若漏了就走 JIT 兜底(仍能跑通;"跑通"断言的是注册表命中路径的正确性)。**残余不确定性**:库内确有 25 条结构只有执行 Layer/模型才拿得到(锚点的符号实例 `Expr{}` 与运行期实例折叠结果不同);实测的两种自定义形态不在其中,但无法先验排除。若使用者的自定义层想避免 JIT 冷启动(构建期预热),下一步是给 `nn_enable_gpu_fusion` 加 `COLLECT ` 钩子(由使用者提供"跑一遍自己的层"的收集函数,等价于库内的模型 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` 实现)。 @@ -403,7 +409,7 @@ auto report = NN_UNWRAP(session.train_step( |------|--------| | `development/README.md` | 本分册定位与索引(明确"写给调用者") | | `development/02-cpp-api-manual.md` | **全功能 C++ API 手册(库使用者首选)**:错误宏 / 引擎 / 张量 / 精度 / DSL / 层与 Model / 模型工厂 / 损失 / 优化器 / 训练循环 / 模型文件 / 数据集 / 分词器 / 生成 / 剖析,每节带可粘贴代码片段;§0 有"我要做什么 → 看哪节"表 | -| `development/01-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_ENABLE_MODULES`/`NN_COMPILER_LAUNCHER`)、GPU 语义、`nn_enable_gpu_fusion(target MAIN [OUT_DIR ...])`、本仓构建/测试、跨平台、故障排查 | +| `development/01-cmake-integration.md` | **接入与构建**:`add_subdirectory`/`FetchContent`、门面目标 `neuralnet::nn` 传递内容、全部构建开关(`NN_ENABLE_GPU`/`NN_BUILD_APPS`/`NN_ENABLE_TESTS`/`NN_ENABLE_NATIVE`/`NN_ENABLE_PROBES`/`NN_COMPILER_LAUNCHER`)、模块构建(无条件、BMI + `-fmodule-file` 链、`OBJECT_DEPENDS` 真实依赖)、GPU 语义、`nn_enable_gpu_fusion(target MAIN [OUT_DIR ...])`、本仓构建/测试、跨平台、故障排查 | | `development/03-write-a-custom-layer.md` | **写自定义层的指南**(P1-6):完整示例(实测过数值梯度检查 + 训练收敛)+ 七条接入契约 + 精度槽声明 + 可复制的梯度自检片段 + GPU 检查清单 + 常见编译错误对照。背景实测见 `development/09-custom-layer-ergonomics.md` | | `development/04-compute-engine-development.md` | **扩展引擎原语**:接口详解、CPU/GPU 实现模式、添加新原语的完整步骤 | | `development/05-mixed-precision.md` | **多精度语义契约(写层 / 选 profile 必读)**:Precision 类型系统、类型化存储、分派、`PrecisionProfile`(param/compute/stable/optimizer)、实测结论与已知数值限制(§12) | diff --git a/CMakeLists.txt b/CMakeLists.txt index 319cb56..6a80ab4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -916,6 +916,24 @@ function(nn_use_modules target) if(TARGET nn_mod_${m}) target_link_libraries(${target} PRIVATE nn_mod_${m}) add_dependencies(${target} nn_${m}_bmi) + # ── BMI 必须是消费方 TU 的**真实编译依赖**(P6 修复,2026-11-06)─ + # add_dependencies 在 ninja 里是 order-only(| 前缀):BMI mtime 变 + # 不触发 TU 重编,且 clang 的 -MD depfile **不记录** -fmodule-file + # 的 pcm(实测 .ninja_deps 无 pcm 条目)→ 改域头(inline 实现) + # 只重编 BMI,import 它的消费方 .o 保留旧实例化 = 陈旧代码。 + # OBJECT_DEPENDS 是 build 边的真实依赖(P5 前 PCH 时代由 PCH 重编 + # 承担同一职责,P5 删 PCH 后必须显式补上)。代价 = 改公共头时 + # 消费方全量真实重编(正确行为,35 §2.4 口径预期)。 + get_target_property(_mod_srcs ${target} SOURCES) + if(_mod_srcs) + foreach(s IN LISTS _mod_srcs) + # OBJECT_DEPENDS 路径按绝对路径解析(相对路径以构建目录为基) + get_filename_component(_s_abs "${s}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set_property(SOURCE ${_s_abs} APPEND PROPERTY + OBJECT_DEPENDS "${NN_LIB_BINARY_DIR}/modules/nn.${m}.pcm") + endforeach() + endif() endif() endforeach() # ── 模块消费方必须绕开编译 launcher(ccache/sccache)──────────────── @@ -931,6 +949,24 @@ function(nn_use_modules target) set_property(TARGET ${target} PROPERTY RULE_LAUNCH_COMPILE "") endfunction() +# ── 门禁:src/ 全量 import 化后不得残留文本 include 库头(P6-2,35 号)──── +# src/*.cpp(含测试聚合体)一律经 `import nn.*` 取声明(P5/D8);残留 +# `#include ` 会绕过模块边界(GMF 文本包含 + import 同实体 +# 的 ODR 隐患),且自 D8 起已无任何必要。合法例外:`` +#(宏岛头,不带 neuralnet.cpp/ 前缀,宏不可导出——P2/D3)。 +if(NN_BUILD_APPS OR NN_ENABLE_TESTS) + file(GLOB _nn_src_files CONFIGURE_DEPENDS src/*.cpp) + foreach(_f IN LISTS _nn_src_files) + file(READ "${_f}" _nn_src_content) + if(_nn_src_content MATCHES "#include ` 宏岛头与 STL)") + endif() + endforeach() +endif() + # ── 四个叶子域模块(DAG:core → kvrecord → dataset → tokenizer)────────── # 域边界 = 合并回主仓的四个子仓域;AOT 宏收集链已随 P3 删除(35 号 D4), # 其余域的模块化按 35 号 P4 铺开。 diff --git a/docs/development/01-cmake-integration.md b/docs/development/01-cmake-integration.md index ac37668..781ae97 100644 --- a/docs/development/01-cmake-integration.md +++ b/docs/development/01-cmake-integration.md @@ -92,11 +92,9 @@ target_link_libraries(my_app PRIVATE neuralnet::nn) | `NN_ENABLE_GPU` | `AUTO` / `ON` / `OFF` | `AUTO` | `AUTO` = 探测到 Vulkan + glslc 就启用 GPU;`ON` = 强制要求,缺依赖则**配置期失败**(不静默降级);`OFF` = 完全跳过(不探测、不编 `scan_exprs`、不生成 shader → 纯 CPU 最快路径) | | `NN_BUILD_APPS` | `ON` / `OFF` | **嵌入时 OFF**(顶层工程 ON) | 是否构建本仓 app / 基准可执行文件。嵌入时默认 OFF —— 消费方只拿库目标 | | `NN_ENABLE_TESTS` | `ON` / `OFF` | `OFF` | 是否构建并注册本仓测试目标(`list(APPEND NN_TEST_TARGETS ...)` 收集) | -| `NN_ENABLE_PCH` | `ON` / `OFF` | `ON` | 本仓共享 PCH(`nn.hpp` 预编译一次,多目标 `REUSE_FROM`)。无 app 目标时自动无候选、无副作用 | | `NN_ENABLE_NATIVE` | `ON` / `OFF` | `ON` | `-march=native`(只作用于本仓目标,不影响消费方编译选项)。分发 / CI 用 `OFF` 生成可移植基线 | | `NN_ENABLE_PROBES` | `ON` / `OFF` | `OFF` | **探针编译门**:打开才编译库内热路径探针并创建探针目标(`mem_probe` / `f16_cpu_probe` / `gpu_stability_probe`)。会改变库头语义 → **不可与默认构建的对象 / 缓存混用** | -| `NN_ENABLE_MODULES` | `ON` / `OFF` | 顶层 `ON` / 嵌入 `OFF` | **粒度 C++ 模块**(`nn.core` / `nn.kvrecord` / `nn.dataset` / `nn.tokenizer`,见 §0/「C++ 模块」小节):构建四个薄接口模块的 BMI,并把本仓叶子消费者切到 `import nn.<域>`(双轨 `#ifdef NN_USE_MODULES`)。仅 GNU 驱动 Clang 生效(GCC / cl.exe / clang-cl 自动关闭,消费方回落 include);嵌入默认 `OFF`,不打扰消费方 | -| `NN_COMPILER_LAUNCHER` | path | 自动探测 ccache / sccache | 编译器缓存启动器;显式传空串 = 禁用 | +| `NN_COMPILER_LAUNCHER` | path | 自动探测 ccache / sccache | 编译器缓存启动器;显式传空串 = 禁用。**模块消费方一律绕开**(ccache 4.13 不支持标准 C++ modules,见「C++ 模块」小节) | ```cmake # 例:纯 CPU、不编本仓应用 @@ -204,67 +202,64 @@ include 路径全部来自 `target_include_directories`(`include/` + ## 8. 编译耗时 本工程 header-only,单 TU 首次 parse `nn.hpp`(含 44 份原语 SPIR-V 嵌入头 + AOT 融合注册表, -约 7MB 头文本)较慢——本机实测约 17.5s 无 PCH。已落地四项提速(默认开启,无需配置): +约 7MB 头文本)较慢——本机实测约 17.5s 无 PCH。src/ 全量模块化(P5,docs/internal/35 D8) +后**共享 PCH 机制已整体删除**(D9),消费方经 `-fmodule-file` 从 BMI 取声明;落地提速项 +(默认开启,无需配置): -1. `NN_ENABLE_PCH`:`nn.hpp` 预编译一次,由编译条件相同的目标 `REUSE_FROM` 共享 - (本机 Debug 全量 125.5s → 72.7s); -2. `CMAKE_CXX_SCAN_FOR_MODULES OFF`:本仓 C++ 模块走**显式接线**(见下「C++ 模块」小节), - 不开 CMake 模块扫描 → 只有 4 个模块单元 + 9 个消费方参与,其余 ~270 个 TU 零扫描; -3. `scan_exprs` 的 glslc **并行**(环境变量 `NN_SCAN_JOBS=` 覆盖,`=1` 回串行) +1. `CMAKE_CXX_SCAN_FOR_MODULES OFF`:本仓 C++ 模块走**显式接线**(见 8.1「C++ 模块」小节), + 不开 CMake 模块扫描 → 只有 13 个模块单元参与,其余 ~270 个 TU 零扫描; +2. `scan_exprs` 的 glslc **并行**(环境变量 `NN_SCAN_JOBS=` 覆盖,`=1` 回串行) + `fused_registry.hpp` 内容稳定写入(内容不变则不动 mtime,靠 `restat` 掐断下游重编); -4. 编译器缓存探测扩到 ccache / sccache(可用 `-DNN_COMPILER_LAUNCHER=` 指定)。 -5. **ccache 与共享 PCH 的兼容**(2026-10-11):ccache 手册要求 Clang 侧**三项同时满足** - 才能缓存"用了 PCH 的 TU"——`sloppiness` 含 `pch_defines,time_macros`、以 `-include-pch` - 使用 PCH、编译带 `-fno-pch-timestamp`。前两项由构建系统补齐(sloppiness 用 - `cmake -E env` 钉在 `RULE_LAUNCH_COMPILE` 前缀里,**不依赖用户全局 ccache 配置**), - 第三项由 `NN_ENABLE_PCH` 路径自动加到 PCH 生成方与所有使用方。修复前**每一个**用 PCH - 的 TU 都被记 "Could not use precompiled header" = 全部不可缓存。 +3. 编译器缓存探测扩到 ccache / sccache(可用 `-DNN_COMPILER_LAUNCHER=` 指定), + 但**模块消费方一律绕开 launcher**(ccache 4.13 不支持标准 C++ modules,key 不含 + `-fmodule-file` 的 BMI 内容 → "BMI 重编 + 消费方源码没动"时会吐出旧 .o); +4. **BMI 是消费方 TU 的真实编译依赖**(P6 修复,2026-11-06):`nn_use_modules` 经 + `OBJECT_DEPENDS` 把 pcm 挂进每个源文件编译边——`add_dependencies` 在 ninja 里是 + order-only(pcm mtime 变不触发 TU 重编)且 clang 的 `-MD` depfile 不记录 pcm, + 缺了它改公共头只重编 BMI、消费方 .o 保留旧实例化(陈旧代码)。 -**实测数字**(本机 8 核,Release + 测试 + GPU,全量构建): +**实测数字**(本机 8 核,clang 24 / Linux / Release + 测试 + GPU,P5/P6 后口径): | 口径 | 时间 | |------|------| -| 冷构建(无缓存) | **13 min 26 s**(189 个构建边;单个 TU 约 30 s,PCH 约 54 s) | -| 命中 ccache 的重建(`ninja -t clean` 后同目录重跑) | **58.8 s**(13m26s → 58.8s;55/55 次编译命中,剩余时间是 shader 生成与链接) | -| 单 TU(Release,PCH 已就绪) | 修复前 30.2 s → 命中缓存 **0.11 s** | +| 冷构建(无缓存) | 见 `research/modules-import-nn/README.md` §6.3(PCH 时代 13m26s / 189 边;P5 后每 TU 全量解析,更慢) | +| 改 consumer 源(mnist_train 单 TU + LTO 链接) | **≈79 s**(无 ccache 命中、无 PCH 的解析+优化+代码生成) | +| 改公共头 → 全量增量 | **≈35 min**(82 消费方 TU + 13 BMI + 全链接;`OBJECT_DEPENDS` 传导,正确性必需) | -> 时间构成的实测归因(`-ftime-trace`,Release / 单个 TU):**后端 29.1 s / 总计 33.1 s**, -> 其中优化器 16.6 s、代码生成 12.5 s;前端只占 4.0 s(无 PCH 时前端 16.8 s、总计 45.8 s)。 -> 也就是说 **PCH 之后编译时间的大头已经是 LLVM 的优化/代码生成**,不是头文件解析—— -> 继续压这一侧要靠减少每 TU 的代码量(拆目标/拆 TU),而不是再加 PCH 花活。 - -**消费方**加速:对自己的目标用 -`target_precompile_headers(my_app PRIVATE /neuralnet.cpp/nn.hpp)`。 - -> ⚠ 两条不变量:① 收集器目标(`scan_exprs`、`*_nnfusion_collect`)**不套 PCH**; -> ② 新写「生成物参与 PCH」的场景必须显式 `add_dependencies(, <生成边>)`。 +> 历史归因(PCH 时代,`-ftime-trace`):后端 29.1 s / 总计 33.1 s(优化器 16.6 s、代码 +> 生成 12.5 s),前端只占 4.0 s(无 PCH 时前端 16.8 s、总计 45.8 s)——**编译时间大头是 +> LLVM 优化/代码生成**,不是头解析。此结论 P5 后仍然成立(PCH 删后前端回归全量解析, +> 每 TU 成本整体上移,见 §8.1)。 --- -## 8.1 C++ 模块(粒度导入,`NN_ENABLE_MODULES`) +## 8.1 C++ 模块(粒度导入,无条件构建) -自 2026-11-01 起本仓提供**粒度 C++20 模块**消费路径:`nn.core` / `nn.kvrecord` / -`nn.dataset` / `nn.tokenizer` 四个薄接口模块(`modules/nn.{core,kvrecord,dataset,tokenizer}.cpp`), -DAG 依赖链 `nn.core → nn.kvrecord → nn.dataset → nn.tokenizer`。动机与实测结论见 -`research/modules-import-nn/README.md` §6(**非提速路径**,价值 = 标准符合性 + 精确依赖 + -消费方摆脱 PCH 依赖)。 +自 2026-11-01 起本仓提供**粒度 C++20 模块**消费路径;P5(2026-11-06)铺满 **13 个域模块** +(`nn.core` / `nn.kvrecord` / `nn.dataset` / `nn.tokenizer` + expr/engine/layer/loss/opt/ +model/train/domain/cli,DAG 叶子链 `core→kvrecord→dataset→tokenizer`、P4 新域全部 DEPS core), +src/ 全量 import 化。动机与实测结论见 `research/modules-import-nn/README.md` §6 +(**非提速路径**,价值 = 标准符合性 + 精确依赖 + 摆脱 PCH)。 -- **开关**:`NN_ENABLE_MODULES`(顶层默认 `ON`,嵌入 `add_subdirectory` 默认 `OFF`)。 - 仅 **GNU 驱动 Clang** 生效(GCC / cl.exe / clang-cl 自动关闭)。 +- **开关**:**无**——模块构建无条件(P1 删 `NN_ENABLE_MODULES`/`NN_USE_MODULES` 宏传播、 + P5/D9 删 `NN_ENABLE_PCH`);仅 **GNU 驱动 Clang** 支持(GCC / cl.exe / clang-cl 构建直接失败,35 号 D2)。 - **形态**:每个模块单元 = 全局模块片段文本包含域头 + `export module nn.<域>` + `export namespace nn { using nn::X; … }` 逐符号 re-export + `export import nn.<依赖域>` (C++ 模块的 import **不可传递**,必须显式 re-export,消费方只 `import nn.tokenizer` 即 - 连带拿到全部四域名字)。 + 连带拿到依赖域名字)。 - **构建**:显式 BMI 自定义命令(`clang++ --precompile`,`-fmodule-file` 逐依赖传入)+ - INTERFACE 目标 `nn_mod_<域>` 传播标志与 `NN_USE_MODULES` 宏。`CMAKE_CXX_SCAN_FOR_MODULES` - 保持 `OFF`——只有模块单元与消费方参与,其余 TU 零扫描。 -- **消费方接入**:`nn_use_modules(<目标> <域...>)`(链接模块 INTERFACE + 从共享 PCH 候选 - 剔除)。源码双轨:`#ifdef NN_USE_MODULES / import nn.<域> / #else / #include … / #endif`。 -- **硬约束**:宏(`NN_TRY` / `NN_UNWRAP` 等)不可导出 → 消费方显式 include `core_errors.hpp`; - STL 留在全局片段 → 消费方自带用到的 STL 头(缺了会报 "missing '#include'")。engine / - layers / model / train 域依赖 `NN_EXPR_SCAN` 宏收集(注册表铁律)→ 本轮不模块化。 -- **嵌入消费方**:默认 `OFF`(不编译模块、不切换任何目标);消费方要启用就 - `set(NN_ENABLE_MODULES ON)` 于 `add_subdirectory` 之前。 + INTERFACE 目标 `nn_mod_<域>` 传播标志(`nn_add_module`)。`CMAKE_CXX_SCAN_FOR_MODULES` + 保持 `OFF`——只有模块单元参与,其余 TU 零扫描。 +- **消费方接入**:`nn_add_executable` 单点接线(P5/D8)——全部可执行目标自动 + `nn_use_modules(<目标> ${NN_ALL_MODULES})`:链接模块 INTERFACE + **`OBJECT_DEPENDS` 把 + BMI 挂进每个源文件编译边**(真实依赖,见 §8 第 4 条)+ 绕开 ccache launcher。 + 残留 `#include ` 由 configure 期 `nn-gate` 门禁拦截(P6-2)。 +- **硬约束**:宏(`NN_TRY` / `NN_UNWRAP` 等)不可导出 → 消费方显式 include **宏岛头** + `nn_error_macros.hpp`(P2/D3 拆出,`core_errors.hpp`/`core_assert.hpp` 经它提供宏); + STL 留在全局片段 → 消费方自带用到的 STL 头(缺了会报 "missing '#include'")。`NN_EXPR_SCAN` + 宏收集链已随 P3 拆除(AOT 收集由运行期 JIT 预热替代,35 号 D4),无域再依赖该宏。 +- **嵌入消费方**:`add_subdirectory` 后 `neuralnet::nn` 门面照常(`#include ` 文本 + 路径不依赖模块);模块/门禁只作用于本仓 src/ 目标(`NN_BUILD_APPS` / `NN_ENABLE_TESTS`)。 --- diff --git a/docs/development/02-cpp-api-manual.md b/docs/development/02-cpp-api-manual.md index a120454..4b2345e 100644 --- a/docs/development/02-cpp-api-manual.md +++ b/docs/development/02-cpp-api-manual.md @@ -77,6 +77,11 @@ f32↔f16 的转换一律 **round-half-to-even**;`static_cast(nn::f16)` ### 1.3 错误处理宏(唯一一族,无异常) +> **宏来源**(P2/D3 起):宏段唯一真源 = 宏岛头 **`nn_error_macros.hpp`**(`engine/abstraction/ +> include/` 下、不带 `neuralnet.cpp/` 前缀);`core_errors.hpp` / `core_assert.hpp` 均 include +> 它提供宏。宏不可随 C++ 模块导出(预处理器无模块边界)——`import nn.*` 的消费方必须显式 +> `#include `(`src/` 已全量如此;它是唯一被允许文本 include 的库件)。 + ```cpp nn::Result = std::expected; // Error 含 .message ``` @@ -1059,6 +1064,10 @@ nn::prof::dump(stdout); // 进程结束前打表(stage / op / GPU / per- - 断言后仍会**使用被校验对象**(解引用 / 索引 / 除零)的点位,一律用 `NN_ASSERT_ALWAYS` / `NN_FATAL`(`core_assert.hpp`,与构建类型无关);`NN_ASSERT` 是"可选诊断"(NDEBUG 展开为空), 只留给失败也不影响后续正确性的检查。 +- **宏来源**(P2/D3 起):宏段唯一真源 = 宏岛头 **`nn_error_macros.hpp`**(`engine/abstraction/ + include/` 下、**不带 `neuralnet.cpp/` 前缀**);`core_errors.hpp` / `core_assert.hpp` 均 + include 它提供宏。宏不可随 C++ 模块导出(预处理器无模块边界)——`import nn.*` 的消费方 + 必须显式 `#include `(`src/` 已全量如此;仅该头合法文本 include 库件)。 失败协议要记牢(与 `create_tensor` 的"无效张量"不同): `from_matrix` / `clone` / `cast` / `reshape` / 各类运算 / `model.forward` / `backward` 返回 diff --git a/docs/history/history.md b/docs/history/history.md index 6de4a93..eb5939f 100644 --- a/docs/history/history.md +++ b/docs/history/history.md @@ -5541,6 +5541,106 @@ ccache 命中已把 include 重编压到 ~0.1s/目标,模块能消除的"改 若出现无 ccache / 多 TU 频繁改公共头的场景,隔离收益才会显性化。**不改主线默认加载路径** (`#include ` + PCH + ccache 仍是默认),模块是增量接入的平行路径,双轨共存。 +## 模块化 P5:src/ 全量 import 化 + ccache×模块根因修复 + clang 24 bug 规避 + PCH 删除(2026-11-06,docs/internal/35) + +**承接**:上一条「粒度导入落地(4 模块)」→ 本条目按 35 号文档 P1–P6 推进到 P5(P6 见下一条)。 + +**P1(开关删除)**:`NN_ENABLE_MODULES` / `NN_MODULES_ENABLED` 编译器门与 `NN_USE_MODULES` +宏传播删除,src/ 8 处双轨改无条件 import。tokenizer_consistency 经 tokenizer_test 文本 +include 覆盖,无需单独接线。 + +**P2(宏岛头)**:宏段拆到**不带 `neuralnet.cpp/` 前缀**的 `engine/abstraction/include/ +nn_error_macros.hpp`(宏唯一真源),`core_errors.hpp` / `core_assert.hpp` include 它; +src/ 68 个宏使用文件显式 include 岛头。**次序不变量**:core_errors 必须先于 core_assert +包含(后者 include 前者时宏已齐;次序反了会缺 `NN_FAIL` 等)——此修复落在子模块提交 +`19bc1e0`(主仓指针随 P5 锁定)。 + +**P3(AOT→JIT 预热)**:`scan_exprs` 收集链主仓侧清理(AOT 闭合世界 → 预热加速 + JIT 兜底, +`NN_STRICT_CLOSED_WORLD=1` 保留看门狗);回归锁与妥协台账建立(36 号)。 + +**P4(13 域铺满)**:expr/engine/layer/loss/opt/model/train/domain/cli 九个新域模块 +(全部 DEPS core,薄模块 + 命名空间逐符号导出;嵌套名镜像原命名空间——`using nn::cli::X` +写在 nn 块里会被 clang 扁平引入为 `nn::X`,实测)。 + +**P5 核心(D8/D9 + 两个根因修复)**: +- **D8 单点接线**:`nn_add_executable` 自动 `nn_use_modules(${name} ${NN_ALL_MODULES})`; + src/ 79 个 TU 全量 import 化(门 F 归零),残留 `#include ` 为 0。 +- **链接修复**:`cli/text_train_config.hpp` 的 `parse_text_train_args` / + `print_text_train_usage` / `parse_precision_arg` 补 `inline`(头里定义、多 TU 引用 → + 缺 inline 链出 undefined symbol;曾被 ccache 旧 .o 掩盖)。 +- **ccache × 模块根因修复**:ccache 4.13 手册明言不支持标准 C++ modules——key 不含 + `-fmodule-file` 的 BMI 内容,direct 模式 depfile 只记文本头 → "BMI 重编 + 消费方源码没动" + 时 ccache 吐**旧 .o**(实测 text_train.cpp 加 inline 后 dev 目录链出 undefined symbol、 + 绕过 ccache 手工编译即正常)。修复 = `nn_use_modules` 内 per-target 禁 + `RULE_LAUNCH_COMPILE`(跨 sccache 通用、机制确定;`CCACHE_EXTRAFILES` 方案实测可用但未采用)。 +- **clang 24 上游 bug 规避(C6)**:`tokenizer_consistency_test.cpp` 的 `old_pre_tokenize` + markers 特意用 `std::vector`(库侧 `std::string`)打破与库 + `pre_tokenize` 构造的同形。详见下一条「clang 24 错值 bug」完整画像。 +- **D9(PCH 删除)**:`nn_pch` 机制、`cmake/nn_pch_stub.cpp`、`NN_ENABLE_PCH` option、 + 候选登记/剔除逻辑、`NN_PCH_COMPAT_OPTIONS`、CMakePresets 的 `NN_ENABLE_PCH` 全部删除; + ccache sloppiness 注释改写(pch_defines 转无害冗余)。PCH 的"共享 parse"职责由模块 BMI 承接。 + +**验收(P5)**:全量构建 0 错 + ctest 81/81 + doc_audit 全绿。 + +## clang 24 错值 bug 完整画像(2026-11-06,C6)与 LLVM issue #239625 + +**症状**:同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 +`pre_tokenize` 的 markers:相同 14 个 string 字面量 + 相同 sort lambda),且 TU import 的 +模块面含 `nn.cli` 时,clang 对**模块导出的 inline 函数**(`BPETokenizer::pre_tokenize`, +实例化进消费方 TU)的 static local 生成错误代码——range-for 遍历该容器**0 次迭代**, +静默错值(`[x<|end_of_assistant|>y]` 拆成 `[x][<|][end][of][assistant][|>][y]`);同 TU +的本地 identical 构造**正常**。-O0/-O3 均复现;行为随代码布局漂移(探针时好时坏)。 + +**排除**:写法 UB(cppreference:range-for 具名对象绑定无临时问题;`compare(pos,count,str)` +的 `pos≤size` 由 `inb` 短路保证)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份 +符号)、guard 布局重合(地址独立)、`-fmodule-file` 数量。 + +**消融(2026-11-06 复测 + 收窄)**:新 clang 快照(++20261105085444)**仍复现**。触发 = +「import 面含 `nn.cli`(裁剪到 `text_train_config.hpp` + `text_train_loop.hpp` 两头发仍 +复现;单头 `text_train_config.hpp` 不复现)× 模块导出 inline 函数实例化 × 消费方本地 +identical 构造」**缺一不可**;消费方去掉 regex 部分仍复现;自包含最小化(模拟 +text_train_loop 的 inline+static 形态、50–500 个导出 inline 函数的大导出面、简化版 +pre_tokenize)**均不复现**——只有真实项目模块组合触发,无法进一步自包含化。 + +**上报**:LLVM issues **#239625**(2026-11-06,label `clang:modules` + `clang:codegen`), +附最小消费方 + 复现命令(克隆 https://github.com/neuralnet-cpp-dev/neuralnet.cpp @ dev, +构建 5 个 BMI + 编译消费方)。**消灭条件** = 上游修复后撤掉 string_view 特意差异(36 号 C6)。 + +## 模块化 P6:性能对照 + OBJECT_DEPENDS 正确性修复 + 门禁固化 + 文档同步(2026-11-06) + +**P6-1 性能对照**(P5 后口径,无 PCH 无缓存,dev preset / clang 24 / 8 核): + +| 场景 | 实测 | 基线(4 模块 + PCH,ccache 命中) | +|------|------|------| +| 改 consumer 源(mnist_train 单 TU + LTO 链接) | **≈79 s** | 0.48–0.50 s | +| 改公共头(`core_config.hpp`)→ 全量增量 | **≈35 min**(82 消费方 TU + 13 BMI + 全链接) | 12.7–18.0 s | +| 冷构建 | 见 research §6.3 更新表 | 87.2–106.7 s | + +结论:模块路径增量劣化到分钟级,属**取舍结果**(ccache 不支持 modules + PCH 删除 + +LTO 放大单 TU 后端成本),不是实现缺陷;风险表中"import TU 失去 ccache 命中"按预期发生, +接受现状(35 号风险行)。 + +**P6 正确性修复(OBJECT_DEPENDS,新发现)**:排查「改公共头」场景时发现——`nn_use_modules` +的 `add_dependencies` 在 ninja 里是 **order-only**(pcm mtime 变不触发 TU 重编),且 clang +的 `-MD` depfile **不记录** `-fmodule-file` 的 pcm(实测 `.ninja_deps` 无 pcm 条目)→ +改域头只重编 BMI,import 它的消费方 .o 保留**旧实例化**(陈旧代码,静默错值/链接错)。 +修复 = `nn_use_modules` 内对每个源文件 `set_property(SOURCE … OBJECT_DEPENDS "")` +(真实编译依赖;源路径转绝对,BASE_DIR 用调用方源目录)。验证:touch `core_config.hpp` → +ninja 计划从「0 消费方 TU」变为「82 个消费方 TU + 13 BMI + 全链接」;全量重建 + ctest 81/81 绿。 +代价 = 改公共头时消费方全量真实重编(正确行为,35 §2.4 口径预期)。 + +**P6-2 门禁固化**:configure 期 `nn-gate` 检查(`file(GLOB src/*.cpp)` + 正则 +`#include `(库侧为 `std::string`) | **clang 24 上游 bug 的规避绕行**(P5 实测,2026-11-06):同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 `pre_tokenize` 的 markers 含相同字面量+相同 sort lambda)**且 import 面超阈值**时,clang 产出错误代码——同函数内相邻循环一个正常执行、另一个 0 次迭代(行为随布局漂移,探针互换),-O0/-O3 均复现。已排除:写法 UB(cppreference range-for/compare 前置条件均满足)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份符号)、布局重合。触发矩阵:纯代码 0 import 过 / 4 链式 import 过 / +expr、+core 过 / **+cli 或 8 域组合或全 13 挂**;old 去 sort 或改名不解除、**改 string_view 解除**。修复=改型打破同形(对拍语义不变),根因在上游未收敛 | ① 把最小 repro(≈`import nn.tokenizer+nn.cli` + 同形双 static + 单调用)报 LLVM issues;② clang 修复合入本仓工具链版本后,**撤掉 string_view 特意差异**恢复与库同型(或确认修复的回归用例落 `tokenizer_test`) | 最终清理(门 G 前;工具链升级为触发条件) | ⏳ | +| C6 | `src/tokenizer_consistency_test.cpp`:`old_pre_tokenize` 的 `markers` 特意用 `std::vector`(库侧为 `std::string`) | **clang 24 上游 bug 的规避绕行**(P5 实测,2026-11-06):同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 `pre_tokenize` 的 markers 含相同字面量+相同 sort lambda)**且 import 面超阈值**时,clang 产出错误代码——同函数内相邻循环一个正常执行、另一个 0 次迭代(行为随布局漂移,探针互换),-O0/-O3 均复现。已排除:写法 UB(cppreference range-for/compare 前置条件均满足)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份符号)、布局重合。触发矩阵:纯代码 0 import 过 / 4 链式 import 过 / +expr、+core 过 / **+cli 或 8 域组合或全 13 挂**;old 去 sort 或改名不解除、**改 string_view 解除**。修复=改型打破同形(对拍语义不变),根因在上游未收敛 | ① **已上报 LLVM issues #239625(2026-11-06)**:复测新 clang(++20261105085444)**仍复现**;消融进一步收窄——触发 =「import 面含 nn.cli(裁到 text_train_config+text_train_loop 两头发仍复现,单头不复现)× 模块导出 inline 函数(pre_tokenize)实例化 × 消费方本地 identical 构造」缺一不可;消费方去 regex 仍复现;自包含最小化(模拟 loop 静态函数/大导出面/简化 pre_tokenize)均不复现;repro 依赖项目真实 pcm(issue 附克隆+构建命令)。② clang 修复合入本仓工具链版本后,**撤掉 string_view 特意差异**恢复与库同型(或确认修复的回归用例落 `tokenizer_test`) | 最终清理(门 G 前;工具链升级为触发条件) | ⏳ | ## 消灭顺序 diff --git a/docs/usage/05-consume-as-library.md b/docs/usage/05-consume-as-library.md index fde8ad0..accb407 100644 --- a/docs/usage/05-consume-as-library.md +++ b/docs/usage/05-consume-as-library.md @@ -53,7 +53,6 @@ target_link_libraries(my_app PRIVATE neuralnet::nn) | `NN_BUILD_APPS` | `ON` / `OFF` | **嵌入时 OFF** | 是否构建本仓 app/基准可执行文件(探针目标另受 `NN_ENABLE_PROBES` 控制)。顶层工程默认 ON,`add_subdirectory` 嵌入默认 OFF——消费方只拿到库目标,不被迫编译整个应用集 | | `NN_ENABLE_PROBES` | `ON` / `OFF` | `OFF` | **探针编译门**:打开才编译库内热路径探针(`nn_dbg_scan`/`NN_F16_DEBUG`、`NN_PREC_TRACE` 归因、`NN_NAN_TRACE`、`NN_MEM_STATS`)并创建探针目标(`mem_probe`/`f16_cpu_probe`/`gpu_stability_probe`)。默认关闭时这些代码不参与编译、相关环境变量一律无效(详见 AGENTS §2;打开会改变库头语义,不可与默认构建的对象/缓存混用) | | `NN_ENABLE_TESTS` | `ON` / `OFF` | `OFF` | 本仓 24 个 ctest 用例(嵌入场景一般不开) | -| `NN_ENABLE_PCH` | `ON` / `OFF` | `ON` | 本仓共享 PCH(无 app 目标时自动无候选、无副作用) | | `NN_ENABLE_NATIVE` | `ON` / `OFF` | `ON` | `-march=native`(只作用于本仓目标,不影响消费方编译选项) | | `NEURALNET_CPP_DIR` | 路径 | `../..` | 仅 `examples/downstream` 样例用:库位置(可指向 3rd_party 摆放处) | diff --git a/research/modules-import-nn/README.md b/research/modules-import-nn/README.md index d9eab91..c68570f 100644 --- a/research/modules-import-nn/README.md +++ b/research/modules-import-nn/README.md @@ -85,10 +85,19 @@ ## 6. 粒度导入落地(2026-11-01,clang 24 / Linux / -O3 / 纯 CPU) +> **P5/P6 更新(2026-11-06,docs/internal/35)**:本节原始数据为「4 模块 + 共享 +> PCH」时代(2026-11-01)。此后粒度模块**铺满 13 域**(P4)且 src/ **全量 import 化** +> (P5/D8),共享 PCH 机制**整体删除**(P5/D9),模块构建**无条件**(`NN_ENABLE_MODULES` +> /`NN_USE_MODULES` 双轨开关已删,P1/P5),消费方经 `OBJECT_DEPENDS` 真实依赖 BMI +> (P6 修复:order-only + depfile 不记 pcm 会让改公共头只重编 BMI、消费方 .o 陈旧), +> ccache 对模块消费方禁用(key 不含 BMI 内容)。"非提速路径"的结论**保持**:模块路径 +> 的价值仍是标准符合性 + 精确依赖 + PCH 独立性;PCH 删除后增量口径见 6.3 更新表。 + ### 6.1 形态与边界 -四个叶子域模块,DAG 依赖链 `nn.core → nn.kvrecord → nn.dataset → nn.tokenizer`, -每个都是「薄接口」模块(`modules/nn.{core,kvrecord,dataset,tokenizer}.cpp`): +13 个域模块,DAG 依赖链 `nn.core → nn.kvrecord → nn.dataset → nn.tokenizer` +(叶子链)+ P4 新域(expr/engine/layer/loss/opt/model/train/domain/cli)全部 +DEPS core,每个都是「薄接口」模块(`modules/nn.<域>.cpp`): ```cpp module; // 全局模块片段:文本包含域头文件 @@ -100,50 +109,49 @@ export import nn.dataset; export namespace nn { using nn::Tokenizer; using nn::make_tokenizer; /* … */ } ``` -- 域边界 = 合并回主仓的四个子仓域;全部不依赖 GPU/AOT 扫描。engine/layers/model/train - 域依赖 `NN_EXPR_SCAN` 宏收集(闭合世界铁律),宏不可导出 → 本轮不模块化。 +- 域边界 = 合并回主仓的四个子仓域 + P4 新域;**不再有"本轮不模块化"的域**—— + engine/layers/model/train 依赖的 `NN_EXPR_SCAN` 宏收集已随 P3 拆除(AOT 收集链 + 由运行期 JIT 预热替代,35 号 D4),13 域全部薄模块化。 - 构建 = 显式 BMI 自定义命令(`clang++ --precompile`,`-fmodule-file` 逐依赖传入)+ - INTERFACE 目标传播(`nn_mod_<域>` 带自身与传递依赖的标志 + `NN_USE_MODULES` 宏)。 - **不开 CMake 模块扫描**(`CMAKE_CXX_SCAN_FOR_MODULES OFF` 保持),~270 个非模块 TU - 零扫描成本。 -- 消费方接入 = `nn_use_modules(<目标> <域...>)`:链接模块 INTERFACE + 从共享 PCH 候选 - 剔除(模块消费方 TU 若同时带 nn.hpp 的 PCH 与模块 BMI,同一实体两处声明存在 - 分歧风险;叶子目标编译量小,各自解析更稳)。 -- 开关 = `NN_ENABLE_MODULES`(顶层默认 ON;嵌入默认 OFF;仅 GNU 驱动 Clang 生效, - GCC/cl.exe/clang-cl 自动 OFF,消费方回落 include 分支——源码用 `#ifdef NN_USE_MODULES` - 双轨)。 + INTERFACE 目标传播(`nn_mod_<域>` 带自身与传递依赖的标志)。**不开 CMake 模块扫描** + (`CMAKE_CXX_SCAN_FOR_MODULES OFF` 保持),~270 个非模块 TU 零扫描成本。 +- 消费方接入 = `nn_use_modules(<目标> <域...>)`:链接模块 INTERFACE + `OBJECT_DEPENDS` + 把每个 BMI 挂进源文件编译边(真实依赖;见 6.3 更新表与 AGENTS §2)+ 绕开 ccache + launcher(`RULE_LAUNCH_COMPILE ""`)。 +- 开关 = **无**:模块构建无条件(P1 删 `NN_ENABLE_MODULES`/`NN_USE_MODULES` 宏传播, + P5/D9 删 PCH 与 `NN_ENABLE_PCH`);非 GNU 驱动 Clang 的编译器直接构建失败(35 号 D2)。 ### 6.2 硬约束复现(薄模块的真实代价) | 约束 | 落地形态 | |------|---------| -| 宏不可导出 | `NN_TRY`/`NN_UNWRAP`/`NN_ASSERT_*` 等仍文本 include(app 显式补 `core_errors.hpp`) | +| 宏不可导出 | `NN_TRY`/`NN_UNWRAP`/`NN_ASSERT_*` 等经**宏岛头** `engine/abstraction/include/nn_error_macros.hpp`(P2/D3 拆出,`core_errors`/`core_assert` include 它);消费方显式 include 该头(无 `neuralnet.cpp/` 前缀) | | STL 可见性缺口 | 消费方自带用到的 STL 头——实测 `tokenizer_infer` 此前靠 nn.hpp 传递拿到 ``,切换后需显式补 | | 头自包含缺口 | 模块单元按任意顺序包含域头,暴露了 `core_file.hpp` 缺 `` 的潜在 bug(正常构建靠 include 顺序侥幸通过),已修 | -### 6.3 实测(n=1,本机 / -O3 / ccache 启用 / 纯 CPU) +### 6.3 实测(P5/P6 后口径,2026-11-06,clang 24 / Linux / -O3 / 8 核 / dev preset) -| 场景 | 模块 ON(import) | 模块 OFF(include+ccache+PCH) | +| 场景 | P5/P6 后(13 模块,无 PCH,ccache 对消费方禁用) | 基线(4 模块 + PCH,ccache 命中) | |------|-------------------|-------------------------------| -| BMI 体积 | core 25 MB / kvrecord 10 MB / dataset 22 MB / tokenizer 44 MB(各域独立,全量对照 86 MB) | — | -| BMI 建造(tokenizer,单线程) | 10.61 s | — | -| 改 consumer 源 → 重编该目标 | **0.50 s** | **0.48 s**(持平) | -| 改 `domain_tokenizer_base.hpp` → 全量增量 | **18.0 s / 75 个 CXX 步**(含 BMI 重建 10.6 s + 7 个无 PCH 消费方) | **12.7 s / 83 步**(全部 ccache 命中) | - -**结论(如实)**:粒度模块在本仓基线下**不是提速路径**,与全量 import 的结论同源—— -ccache 命中已把 include 重编压到 ~0.1s/目标,模块能消除的"改公共头 → 重 parse 全库" -在本仓不存在;反而 BMI 重建(10.6s/tokenizer)成为新的单点热点。隔离收益真实存在 -(非模块域零重编、消费方摆脱 PCH 依赖、改 tokenizer 不重编 engine 消费方),但 -绝对值被基线追平。落地价值 = **标准符合性**(库提供 C++20 模块消费形态)+ -**精确依赖**(`import nn.<域>` 语义化声明依赖)+ **PCH 独立性**;提速不在其中。 +| BMI 体积 | 叶子域 core 25 / kvrecord 10 / dataset 22 / tokenizer 44 MB(2026-11-01 实测);P4 新域未逐一记录(build/dev/modules 可查) | — | +| BMI 建造(tokenizer,单线程) | 10.61 s(不变) | — | +| 改 consumer 源 → 重编该目标 | **≈79 s**(mnist_train:单 TU 全量解析 + LTO 链接) | 0.48-0.50 s(ccache 命中) | +| 改公共头 → 全量增量 | **≈35 min / 82 个消费方 TU + 13 BMI + 全链接**(`OBJECT_DEPENDS` 传导,正确性必需) | 12.7-18.0 s | +| 冷构建 | **≈15 min**(2026-11-06 p6cold 全新目录实测,wall 按 configure→完成时间戳差;275 构建边) | 87.2-106.7 s | + +**结论(如实)**:P5/P6 后模块路径**显著劣于** ccache+PCH 基线——增量从秒级劣化到 +分钟级。这不是实现缺陷而是取舍的结果:① ccache 4.13 不支持标准 C++ modules(key 不含 +BMI 内容 → 禁 launcher 保正确性);② 共享 PCH 已删(D9,PCH 的共享 parse 职责本应由 +BMI 承接,但 BMI 只省「模块声明解析」,每个消费方 TU 仍要全量 parse 自身 + 实例化 +inline 实体);③ LTO(thin)+ `-march=native` 放大单 TU 后端成本。隔离收益真实存在 +(改 tokenizer 不重编 engine 消费方、非模块域零重编、精确依赖),但绝对值被基线 +大幅拉开。**落地价值不变** = 标准符合性 + 精确依赖 + PCH 独立性;提速不在其中。 若将来出现无 ccache、或多 TU 频繁改公共头的场景,模块路径的隔离收益才会显性化。 ### 6.4 复现 ```bash -# 模块 ON(默认):9 个叶子消费者走 import -cmake -B build/mod -G Ninja -DNN_ENABLE_GPU=OFF -DNN_ENABLE_TESTS=ON -cmake --build build/mod --target tokenizer_test nnvocab_test dataset_test dataset_integration_test -# 模块 OFF(回落 include) -cmake -B build/mod-off -G Ninja -DNN_ENABLE_GPU=OFF -DNN_ENABLE_TESTS=ON -DNN_ENABLE_MODULES=OFF +# 模块构建无条件(无开关):13 域 BMI + 全部消费方 import +cmake -B build -G Ninja -DNN_ENABLE_GPU=OFF -DNN_ENABLE_TESTS=ON +cmake --build build --target tokenizer_test nnvocab_test dataset_test dataset_integration_test ``` From 1688320698a44baf5a5e1f43d774e9ecc4186bdf Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sat, 7 Nov 2026 01:08:48 +0800 Subject: [PATCH 10/16] =?UTF-8?q?chore(T9):=20=E5=8F=B0=E8=B4=A6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E2=80=94=E2=80=94C3/C5=20=E5=B7=B2=E6=B6=88=E7=81=AD?= =?UTF-8?q?=20+=20C1=20=E8=B0=83=E6=9F=A5=E8=BF=9B=E5=B1=95=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=20+=20AGENTS=20IR-C=20=E5=8F=A3=E5=BE=84=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 36 号台账:C3(NN_IRC_TRACE 正式化 + 删 [ir-c][run])与 C5(kernelgen 诊断 转正)标记 ✅;C1 新增「C1-调查」行(2026-11-06 复现与排除进展:GPU full-f16 图路径 step 1 更新即 NaN 稳定复现;已排除直求值差异/plan cache/cast.comp; 现场 = 152 全 F16 节点图拆单输出 kernel 走边界 cast,K1/K2/K3 输入出现 NaN 且 v 读到数学上不可能的负值 ⇒ 指向占位缓冲跨 kernel 依赖链的绑定/写回一致 性;C2/C4 依赖 C1 同组卡住) - submodule 指针锁定 8231582(C3/C5 消灭) --- AGENTS.md | 2 +- docs/internal/36-compromise-ledger.md | 5 +++-- engine | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 32ec86b..4cba0a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -272,7 +272,7 @@ GPT 序列展平: 列序 i = b*seq + t(batch-major,全局唯一约定) ## 7. 表达式 DSL 与 GPU 融合管线(AOT 注册表 + 运行期 JIT 兜底;GPU 开发必读) - 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()` + `ExprSegment` RAII 守卫 + `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]`),未登记的**单节点**结构走 JIT 兜底(`NN_STRICT_CLOSED_WORLD=1` 可恢复硬报错)。历史与取舍见 `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(engine, expr, rows, cols)`(一行表达式,最常用——**输出形状可省**,推导口径见下条 bullet);把结果写进既有张量(原地更新,零分配)用 `dsl::compute_into(engine, expr, dst)`;归约语义用 `dsl::compute_reduce`。**跨表达式融合(IR-C)为现行能力、已接入训练路径**:`engine.begin_expr()/end_expr()` + `ExprSegment` RAII 守卫 + `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]`/`[ir-c][degrade]`(**非探针门的行为开关**——C3 裁决,36 号台账:裸 getenv 正式化,不随 `NN_ENABLE_PROBES` 编译门生效;每 kernel 的 `[ir-c][run]` 打印已删,命中执行是常态不刷屏),`#b` 未登记/无 BDA = 逐 kernel 降级回退(可观测,非错误)。⚠ **注册表与精度的交互**:融合分组依赖精度(P2 要全 F32、P1 要同精度),混精度 profile 会产出 f32 扫描期未登记的复合结构——不做构建期精度组合枚举,而是**运行期通用降级**:复合 kernel 未登记(或写穿前置条件不满足)时拆回成员逐个派发(成员都是已登记的个体结构;`NN_IRC_TRACE=1` 可见 `[ir-c][degrade]`),未登记的**单节点**结构走 JIT 兜底(`NN_STRICT_CLOSED_WORLD=1` 可恢复硬报错)。历史与取舍见 `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/02-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`**(漏登记 = 最后已知的注册表预热缺口,未登记结构仍有 JIT 兜底);`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 注释。 - **构建期单步(引擎侧,新仓库)**(引擎 .so 构建时自动编排;主仓侧已无此流程——P3/D4 删 diff --git a/docs/internal/36-compromise-ledger.md b/docs/internal/36-compromise-ledger.md index 6a8eb8f..96c3df7 100644 --- a/docs/internal/36-compromise-ledger.md +++ b/docs/internal/36-compromise-ledger.md @@ -18,10 +18,11 @@ | # | 位置 | 妥协内容(为什么是妥协) | 消灭方案 | 消灭时机 | 状态 | |---|------|--------------------------|----------|----------|------| | C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | 下钻根因(重点:降级成员派发中 f16 状态 m/v 链的执行序、占位张量跨 step 复用、cast 写回路径),修复后**删除 graph_ok 分支恢复无条件录制**;full-f16 以「图开启 + ctest 绿」回归锁验证 | 最终清理(P6 后、门 G 前) | ⏳ | +| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负 ⇒ 指向图执行时 m/v 占位缓冲被错误内容覆盖或绑定错缓冲,而非算术错误)。④ 待下钻:边界 cast 写回(cast_into)与占位缓冲在**跨 kernel 依赖链**(K1→K3、K2→K3)上的读写一致性——K3 读的 m/v 占位是否真是 K1/K2 写入的那份(execute 时 d.ins 绑定 vs 录制占位句柄),以及占位缓冲是否被池回收/复用。⑤ 本轮未修(深水区,需继续);**C2/C4 依赖 C1,同组卡住** | 同 C1 | ⏳ | | C2 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:begin_expr / end_expr 的 `NN_NO_GRAPH` getenv 旁路 | A/B 排查遗留:整跳录制的逃生阀,代码注释自标"临时" | 删除两处 getenv 分支;与 C1 同组验收(删后 full-f16 仍绿) | 同 C1(一组) | ⏳ | -| C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | 二选一并同步文档:① NN_IRC_TRACE 正式化为非探针门的行为开关(更新 AGENTS/33 号口径)或 ② 恢复 env_flag 并改 AGENTS 注明需 NN_ENABLE_PROBES;**删除 [ir-c][run] 每 kernel 打印** | 最终清理 | ⏳ | +| C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | ✅ **已消灭(2026-11-06)**:方案 ①——`NN_IRC_TRACE` 正式化为**非探针门行为开关**(裸 getenv 保留,AGENTS §7 注明);**删除 `[ir-c][run]` 每 kernel 打印**(命中执行是热路径常态,刷屏无排查价值);降级 `[ir-c][degrade]` 与批量 `[ir-c][batch]`/`[batch-degrade]` 打印保留(低频、可观测"某段悄悄没融合") | 最终清理 | ✅ | | C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | C1/C2 消灭后重新 A/B:静态签名 JIT vs 边界 cast,以 f16 基准**择优转正**(优则恢复直写;劣则把边界 cast 升格为有据设计并删实验残留形态) | 最终清理(依赖 C1) | ⏳ | -| C5 | `engine/engine/src/kernelgen.cpp`:失败路径诊断 fprintf("GLSL 生成为空" / "shaderc 编译失败") | 排查期新增的输出行为;非掩盖缺陷,但属未评审新增 | **显式裁决**:转正(补设计注记)或删除——不得默默留下 | 最终清理 | ⏳ | +| C5 | `engine/engine/src/kernelgen.cpp`:失败路径诊断 fprintf("GLSL 生成为空" / "shaderc 编译失败") | 排查期新增的输出行为;非掩盖缺陷,但属未评审新增 | ✅ **已消灭(2026-11-06)**:**显式裁决转正**——保留两处 fprintf + 文件头补设计注记(调用方只见 NNK_COMPILE_FAIL,缺此区分不了"生成器不支持形态"与"shaderc 报错"两类根因;失败路径非热路径无性能代价) | 最终清理 | ✅ | | C6 | `src/tokenizer_consistency_test.cpp`:`old_pre_tokenize` 的 `markers` 特意用 `std::vector`(库侧为 `std::string`) | **clang 24 上游 bug 的规避绕行**(P5 实测,2026-11-06):同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 `pre_tokenize` 的 markers 含相同字面量+相同 sort lambda)**且 import 面超阈值**时,clang 产出错误代码——同函数内相邻循环一个正常执行、另一个 0 次迭代(行为随布局漂移,探针互换),-O0/-O3 均复现。已排除:写法 UB(cppreference range-for/compare 前置条件均满足)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份符号)、布局重合。触发矩阵:纯代码 0 import 过 / 4 链式 import 过 / +expr、+core 过 / **+cli 或 8 域组合或全 13 挂**;old 去 sort 或改名不解除、**改 string_view 解除**。修复=改型打破同形(对拍语义不变),根因在上游未收敛 | ① **已上报 LLVM issues #239625(2026-11-06)**:复测新 clang(++20261105085444)**仍复现**;消融进一步收窄——触发 =「import 面含 nn.cli(裁到 text_train_config+text_train_loop 两头发仍复现,单头不复现)× 模块导出 inline 函数(pre_tokenize)实例化 × 消费方本地 identical 构造」缺一不可;消费方去 regex 仍复现;自包含最小化(模拟 loop 静态函数/大导出面/简化 pre_tokenize)均不复现;repro 依赖项目真实 pcm(issue 附克隆+构建命令)。② clang 修复合入本仓工具链版本后,**撤掉 string_view 特意差异**恢复与库同型(或确认修复的回归用例落 `tokenizer_test`) | 最终清理(门 G 前;工具链升级为触发条件) | ⏳ | ## 消灭顺序 diff --git a/engine b/engine index 19bc1e0..8231582 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit 19bc1e023dc8cecffec42e5a547d582f069e4a00 +Subproject commit 823158215039892380c407d97528741f6225e3a7 From b730a3ee30ad7ba11e7632aa5c713f3764815f74 Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sat, 7 Nov 2026 01:16:34 +0800 Subject: [PATCH 11/16] =?UTF-8?q?docs(T9):=20C1-=E8=B0=83=E6=9F=A5?= =?UTF-8?q?=E8=BF=9B=E5=B1=95=E8=A1=A5=E5=85=85=E2=80=94=E2=80=94=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E7=B2=BE=E5=BA=A6=E8=B0=9C=E9=A2=98=EF=BC=88K3=20?= =?UTF-8?q?=E5=BD=95=E5=88=B6=E6=97=B6=20m/v=20=E6=8A=A5=E5=91=8A=20F32=20?= =?UTF-8?q?=E8=80=8C=20K1=20=E5=8D=A0=E4=BD=8D=20F16=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充 36 号台账 C1-调查行:K3 录制时 m_[i]/v_[i] 的 precision() 报告 F32、 dispatch 时 d.ins/d.out 全 F32,但 record_graph_node_ 确认 K1 占位 P=0(F16)—— 占位张量精度状态与缓冲不一致,怀疑跨 DSO(抽象层 NVI 主程序实例化 vs 引擎 .so) 的张量精度视图分裂;下一步建议打印 K3 录制时 m_[i] 的 virtual_tag 对比 K1 占位 - 本轮所有 C1 临时调试改动已还原(工作树干净,f16_precision_test ALL PASSED) --- docs/internal/36-compromise-ledger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internal/36-compromise-ledger.md b/docs/internal/36-compromise-ledger.md index 96c3df7..34865a4 100644 --- a/docs/internal/36-compromise-ledger.md +++ b/docs/internal/36-compromise-ledger.md @@ -18,7 +18,7 @@ | # | 位置 | 妥协内容(为什么是妥协) | 消灭方案 | 消灭时机 | 状态 | |---|------|--------------------------|----------|----------|------| | C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | 下钻根因(重点:降级成员派发中 f16 状态 m/v 链的执行序、占位张量跨 step 复用、cast 写回路径),修复后**删除 graph_ok 分支恢复无条件录制**;full-f16 以「图开启 + ctest 绿」回归锁验证 | 最终清理(P6 后、门 G 前) | ⏳ | -| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负 ⇒ 指向图执行时 m/v 占位缓冲被错误内容覆盖或绑定错缓冲,而非算术错误)。④ 待下钻:边界 cast 写回(cast_into)与占位缓冲在**跨 kernel 依赖链**(K1→K3、K2→K3)上的读写一致性——K3 读的 m/v 占位是否真是 K1/K2 写入的那份(execute 时 d.ins 绑定 vs 录制占位句柄),以及占位缓冲是否被池回收/复用。⑤ 本轮未修(深水区,需继续);**C2/C4 依赖 C1,同组卡住** | 同 C1 | ⏳ | +| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负)。④ **关键谜题(未解)**:K1 录制占位 P=0(F16,record_graph_node_ 打印确认),但**同一 step 的 K3 录制时 m_[i]/v_[i] 的 `precision()` 报告 F32**(K3-record 打印),dispatch 时 d.ins/d.out 也全 F32——**占位张量的精度状态与缓冲不一致**(f16 缓冲 + f32 精度报告,或 m_[i] 句柄在 K1→K3 之间被改),怀疑与「抽象层头(主程序 TU 实例化 NVI)与引擎 .so(GpuEngine 实现)跨 DSO 的实体/精度视图分裂」有关(recording_graph_owner 的 inline thread_local 已实测共享,但张量精度状态路径未查完)。⑤ 本轮未修(深水区,需专门会话继续——建议下一步:打印 K3 录制时 m_[i] 的 `virtual_tag` 与 K1 占位 tag 是否一致,确认 m_[i] 句柄是否真的指向 K1 占位);**C2/C4 依赖 C1,同组卡住** | 同 C1 | ⏳ | | C2 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:begin_expr / end_expr 的 `NN_NO_GRAPH` getenv 旁路 | A/B 排查遗留:整跳录制的逃生阀,代码注释自标"临时" | 删除两处 getenv 分支;与 C1 同组验收(删后 full-f16 仍绿) | 同 C1(一组) | ⏳ | | C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | ✅ **已消灭(2026-11-06)**:方案 ①——`NN_IRC_TRACE` 正式化为**非探针门行为开关**(裸 getenv 保留,AGENTS §7 注明);**删除 `[ir-c][run]` 每 kernel 打印**(命中执行是热路径常态,刷屏无排查价值);降级 `[ir-c][degrade]` 与批量 `[ir-c][batch]`/`[batch-degrade]` 打印保留(低频、可观测"某段悄悄没融合") | 最终清理 | ✅ | | C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | C1/C2 消灭后重新 A/B:静态签名 JIT vs 边界 cast,以 f16 基准**择优转正**(优则恢复直写;劣则把边界 cast 升格为有据设计并删实验残留形态) | 最终清理(依赖 C1) | ⏳ | From 4921bc644875245d9e18dbf87b0a9d462b23372f Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sat, 7 Nov 2026 15:30:10 +0800 Subject: [PATCH 12/16] =?UTF-8?q?fix(build):=20Windows=20+=20clang-GNU=20?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E4=BF=AE=E5=A4=8D=EF=BC=88CRT=20=E5=AE=8F/BM?= =?UTF-8?q?I=20=E9=95=9C=E5=83=8F/=E6=B5=8B=E8=AF=95=20STL=20include?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 40HX(Windows + clang 23 + MSVC STL + GPU + 模块)首次全量构建实测: - CMakeLists:WIN32 全局 _CRT_SECURE_NO_WARNINGS(MSVC CRT 的 getenv 弃用 在 -Werror 下编译失败,nn_warmup/引擎头均中招) - nn_add_module BMI 镜像补 CRT 宏(-D_DLL -D_MT -Xclang --dependent-lib=msvcrt,WIN32+Clang):pcm 编译时 MSVC STL 写 MT_StaticRelease linker options、import 传播进消费方 obj 与本地 MD 冲突 (lld-link RuntimeLibrary mismatch)——最隐蔽的一处 - 测试文件显式 include /(MSVC STL 不传递包含,模块消费方 报 missing '#include'):adam_quant/bitnet_struct/optimizer_state/ write_epoch/rl_pipeline;batch_source 的 optional==int 改 == std::optional{3}(MSVC STL 无 optional 与 int 直接比较) - submodule 指针随 engine 修复更新(f9bf542,见 engine 提交) --- CMakeLists.txt | 15 +++++++++++++++ engine | 2 +- src/adam_quant_test.cpp | 1 + src/batch_source_test.cpp | 5 +++-- src/bitnet_struct_test.cpp | 1 + src/optimizer_state_test.cpp | 1 + src/rl_pipeline_test.cpp | 1 + src/write_epoch_test.cpp | 1 + 8 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a80ab4..f6810e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,14 @@ cmake_minimum_required(VERSION 3.30) # 它只是元数据——发版时同步到最新 tag,实际版本以 git tag / gh release 为准) project(neuralnet.cpp VERSION 1.14.1 LANGUAGES CXX) +# ── Windows:MSVC CRT 的 insecure 弃用(getenv 等)在 clang -Werror 下报错 ── +# _CRT_SECURE_NO_WARNINGS 是 MSVC CRT 官方逃生阀(cl / clang-cl / clang++ 通用)。 +# 40HX(Windows + clang 23 + MSVC STL + GPU)实测必需:nn_warmup / GPU 引擎头 +# 的 std::getenv 在 -Wdeprecated-declarations -Werror 下直接编译失败。 +if(WIN32) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) +endif() + # GNUInstallDirs:定义 CMAKE_INSTALL_LIBDIR/BINDIR/INCLUDEDIR(install 布局用, # docs/internal/34 §5 与 33 §4.3 的 bin/ + lib/ + include/ 干净产物)。 include(GNUInstallDirs) @@ -863,6 +871,13 @@ function(nn_add_module modname source) set(_opt ${RELEASE_FLAGS}) endif() list(APPEND _flags ${_opt} "-std=c++${CMAKE_CXX_STANDARD}") + if(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # Windows + clang(GNU 驱动):CMake 给消费方编译加 -D_DLL -D_MT + # -Xclang --dependent-lib=msvcrt(/MD 动态 CRT),BMI 自定义命令必须 + # 同值——否则 pcm 记录 MT_StaticRelease linker options,import 传播 + # 与消费方本地 MD 冲突(lld-link RuntimeLibrary mismatch,40HX 实测)。 + list(APPEND _flags -D_DLL -D_MT -Xclang --dependent-lib=msvcrt) + endif() get_target_property(_thread_opts Threads::Threads INTERFACE_COMPILE_OPTIONS) if(_thread_opts) list(APPEND _flags ${_thread_opts}) diff --git a/engine b/engine index 8231582..f9bf542 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit 823158215039892380c407d97528741f6225e3a7 +Subproject commit f9bf5429dff109f2c852d65875da8ffa89c42c65 diff --git a/src/adam_quant_test.cpp b/src/adam_quant_test.cpp index 6ee5b3d..faec072 100644 --- a/src/adam_quant_test.cpp +++ b/src/adam_quant_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/src/batch_source_test.cpp b/src/batch_source_test.cpp index f9fb7c9..6f8ecc1 100644 --- a/src/batch_source_test.cpp +++ b/src/batch_source_test.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,7 @@ void test_step_grid() if (!src_r) return; nn::IndexedBatchSource& src = *src_r; - CHECK(src.steps_per_epoch() == 3, "10/4 → 3 步"); + CHECK(src.steps_per_epoch() == std::optional{3}, "10/4 → 3 步"); auto b0 = src.next(0); CHECK(b0 && b0->samples == 4 && b0->epoch_done == false, "步 0:4 样本,epoch 未完"); @@ -135,7 +136,7 @@ void test_ctor_validation() CHECK(!nn::IndexedBatchSource::create(10, 4, 1, nullptr).has_value(), "空装配回调 → 报错"); auto ok = nn::IndexedBatchSource::create(5, 2, 0, assemble); - CHECK(ok && ok->steps_per_epoch() == 3, "5/2 → 3 步"); + CHECK(ok && ok->steps_per_epoch() == std::optional{3}, "5/2 → 3 步"); } // ── [5] 载荷 std::any 指针形式读取 ───────────────────────────────────── diff --git a/src/bitnet_struct_test.cpp b/src/bitnet_struct_test.cpp index 009901e..3cf426c 100644 --- a/src/bitnet_struct_test.cpp +++ b/src/bitnet_struct_test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/src/optimizer_state_test.cpp b/src/optimizer_state_test.cpp index 485af70..9ff6e7b 100644 --- a/src/optimizer_state_test.cpp +++ b/src/optimizer_state_test.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/src/rl_pipeline_test.cpp b/src/rl_pipeline_test.cpp index 73c1f56..cd41f56 100644 --- a/src/rl_pipeline_test.cpp +++ b/src/rl_pipeline_test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/write_epoch_test.cpp b/src/write_epoch_test.cpp index be7098c..fe6ae39 100644 --- a/src/write_epoch_test.cpp +++ b/src/write_epoch_test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include From a9607e8d1a965bd77312723ef9991eb1871a472f Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sat, 7 Nov 2026 15:30:19 +0800 Subject: [PATCH 13/16] =?UTF-8?q?docs(T9):=20C1=20=E6=94=B6=E7=AA=84?= =?UTF-8?q?=E4=B8=BA=20850M=20=E8=AE=BE=E5=A4=87=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E2=80=94=E2=80=9440HX=20=E5=A4=8D=E7=8E=B0=20full-f16=20?= =?UTF-8?q?=E5=85=A8=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 40HX(CMP 40HX,Turing TU102,shaderFloat16 ✓)上 graph_ok 恒 true 的 同代码 f16_precision_test ALL PASSED(CPU+GPU,无 NaN)→ C1 根因 = 850M (Maxwell 无 shaderFloat16)f16 回退/边界 cast 写回路径设备特有,非跨设备 代码 bug。消灭路径:850M 上重跑 C4 的「静态签名 JIT(#s)vs 边界 cast」 单变量 A/B(#s 仅需 16bit 存储,若在 850M 绿则 C1/C2/C4 一组可彻底消灭)。 --- docs/internal/36-compromise-ledger.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internal/36-compromise-ledger.md b/docs/internal/36-compromise-ledger.md index 34865a4..1c6db61 100644 --- a/docs/internal/36-compromise-ledger.md +++ b/docs/internal/36-compromise-ledger.md @@ -17,8 +17,8 @@ | # | 位置 | 妥协内容(为什么是妥协) | 消灭方案 | 消灭时机 | 状态 | |---|------|--------------------------|----------|----------|------| -| C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | 下钻根因(重点:降级成员派发中 f16 状态 m/v 链的执行序、占位张量跨 step 复用、cast 写回路径),修复后**删除 graph_ok 分支恢复无条件录制**;full-f16 以「图开启 + ctest 绿」回归锁验证 | 最终清理(P6 后、门 G 前) | ⏳ | -| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负)。④ **关键谜题(未解)**:K1 录制占位 P=0(F16,record_graph_node_ 打印确认),但**同一 step 的 K3 录制时 m_[i]/v_[i] 的 `precision()` 报告 F32**(K3-record 打印),dispatch 时 d.ins/d.out 也全 F32——**占位张量的精度状态与缓冲不一致**(f16 缓冲 + f32 精度报告,或 m_[i] 句柄在 K1→K3 之间被改),怀疑与「抽象层头(主程序 TU 实例化 NVI)与引擎 .so(GpuEngine 实现)跨 DSO 的实体/精度视图分裂」有关(recording_graph_owner 的 inline thread_local 已实测共享,但张量精度状态路径未查完)。⑤ 本轮未修(深水区,需专门会话继续——建议下一步:打印 K3 录制时 m_[i] 的 `virtual_tag` 与 K1 占位 tag 是否一致,确认 m_[i] 句柄是否真的指向 K1 占位);**C2/C4 依赖 C1,同组卡住** | 同 C1 | ⏳ | +| C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | **2026-11-07 已收窄为设备差异(40HX 复现)**:graph_ok 恒 true 的同代码在 CMP 40HX(Turing TU102,shaderFloat16 ✓)上 full-f16 GPU 训练 **ALL PASSED**(f16_precision_test 全绿,无 NaN)——850M(Maxwell 无 shaderFloat16)NaN 是**该设备 f16 回退/边界 cast 写回路径特有**,非跨设备代码 bug。**消灭路径(后续)**:在 850M 上重跑 C4 的「静态签名 JIT(#s/#xxxx,仅需 16bit 存储)vs 边界 cast」单变量 A/B——若 #s 在 850M 绿则 C4 转正恢复直写、C2 删 NN_NO_GRAPH、C1 删 graph_ok 门控(一组消灭,full-f16 图在 850M 也可用);若 #s 也坏则为设备硬限制,门控改「设备能力感知」(describe 无原生 f16 时不录制),用户定调 | 最终清理(P6 后、门 G 前) | ⏳→🧭 | +| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负)。④ **关键谜题(未解)**:K1 录制占位 P=0(F16,record_graph_node_ 打印确认),但**同一 step 的 K3 录制时 m_[i]/v_[i] 的 `precision()` 报告 F32**(K3-record 打印),dispatch 时 d.ins/d.out 也全 F32——**占位张量的精度状态与缓冲不一致**(f16 缓冲 + f32 精度报告,或 m_[i] 句柄在 K1→K3 之间被改),怀疑与「抽象层头(主程序 TU 实例化 NVI)与引擎 .so(GpuEngine 实现)跨 DSO 的实体/精度视图分裂」有关(recording_graph_owner 的 inline thread_local 已实测共享,但张量精度状态路径未查完)。⑤ 本轮未修(深水区,需专门会话继续——建议下一步:打印 K3 录制时 m_[i] 的 `virtual_tag` 与 K1 占位 tag 是否一致,确认 m_[i] 句柄是否真的指向 K1 占位);**C2/C4 依赖 C1,同组卡住**。⑥ **2026-11-07 40HX(CMP 40HX)复现**:graph_ok 恒 true + `NN_VULKAN_DEVICE` 锁 CMP 40HX,同代码 `f16_precision_test` **ALL PASSED**(EXIT=0,full-f16 GPU 训练 loss 有限下降,无 NaN)——「占位张量精度状态/跨 DSO 视图分裂」怀疑在 40HX 上不成立或无害;850M 的 NaN 收窄为 **Maxwell 无 shaderFloat16 的 f16 回退/边界 cast 写回路径设备问题**。注:40HX 复现环境需完整 Windows 构建修复链(NOMINMAX / _CRT_SECURE_NO_WARNINGS / BMI CRT 宏镜像 / WINDOWS_EXPORT_ALL_SYMBOLS WIN32 / shaderc HINTS / kernelgen dll 平台名与导出 / 测试文件显式 STL include / .nnk 缓存目录),见 AGENTS 待更新清单 | 同 C1 | ⏳ | | C2 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:begin_expr / end_expr 的 `NN_NO_GRAPH` getenv 旁路 | A/B 排查遗留:整跳录制的逃生阀,代码注释自标"临时" | 删除两处 getenv 分支;与 C1 同组验收(删后 full-f16 仍绿) | 同 C1(一组) | ⏳ | | C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | ✅ **已消灭(2026-11-06)**:方案 ①——`NN_IRC_TRACE` 正式化为**非探针门行为开关**(裸 getenv 保留,AGENTS §7 注明);**删除 `[ir-c][run]` 每 kernel 打印**(命中执行是热路径常态,刷屏无排查价值);降级 `[ir-c][degrade]` 与批量 `[ir-c][batch]`/`[batch-degrade]` 打印保留(低频、可观测"某段悄悄没融合") | 最终清理 | ✅ | | C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | C1/C2 消灭后重新 A/B:静态签名 JIT vs 边界 cast,以 f16 基准**择优转正**(优则恢复直写;劣则把边界 cast 升格为有据设计并删实验残留形态) | 最终清理(依赖 C1) | ⏳ | From 38b4fb6770c31eec2eff6e50422c775cd4c02aec Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sat, 7 Nov 2026 15:38:29 +0800 Subject: [PATCH 14/16] =?UTF-8?q?docs:=20AGENTS=20=E8=A1=A5=20Windows=20+?= =?UTF-8?q?=20clang-GNU=20+=20GPU/=E6=A8=A1=E5=9D=97=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=B0=E5=BD=95=EF=BC=8840HX=20=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=EF=BC=8C=E4=B8=BB=E4=BB=93=204921bc6=20+=20submodule?= =?UTF-8?q?=20f9bf542=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 4cba0a8..c1dffb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,7 @@ cmake -B build -G Ninja -DNN_ENABLE_TESTS=ON && cmake --build build && ctest --t - 链接报 `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 位于 `/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)。 +- **Windows + clang(GNU 驱动)+ GPU/模块构建(2026-11-07,40HX=CMP 40HX 实测)**:clang++ GNU 前端下 CMake 的 `MSVC` 变量为 **FALSE** → `WINDOWS_EXPORT_ALL_SYMBOLS` 必须 `if(WIN32)`(否则 DLL 不导出工厂符号,主仓链接 undefined symbol);Vulkan 头(vulkan.h → windows.h 的 min/max 宏)需 `NOMINMAX`;MSVC CRT 的 getenv 等 insecure 弃用需 `_CRT_SECURE_NO_WARNINGS`;`nn_add_module` 的 BMI 镜像须补 `-D_DLL -D_MT -Xclang --dependent-lib=msvcrt`(否则 pcm 记录 MT_StaticRelease linker options、import 传播与消费方本地 MD 冲突 = lld-link RuntimeLibrary mismatch);kernelgen 加载平台名 = `nn-kernelgen.dll`;`.nnk` 缓存目录写盘前自动创建。已入仓(主仓 4921bc6 + submodule f9bf542),背景见 36 号台账 C1-调查⑥。 - **clang + libc++ 的口径差异**:`-Werror` 下 libc++ 会对"记录类型但从未使用"的局部变量报 `-Wunused-variable`(同一代码在 libstdc++ 下不报)——clean 构建四种组合时会先撞到,属**真实死变量**,删掉即可(先例:`src/kvrec_test.cpp` 的 `std::string ls;`)。 - 构建选项:`NN_ENABLE_NATIVE`(默认 ON,开启 `-march=native`,分发/CI 用 `OFF` 生成可移植基线);`NN_ENABLE_TESTS`(默认 OFF);`NN_ENABLE_PROBES`(默认 OFF,见下条)。模块构建**无条件**(P5/D9 已删 `NN_ENABLE_MODULES`/`NN_ENABLE_PCH` 两个开关,见下「C++ 模块」条)。 - **预设构建(`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/`;另有 `caller` 无关的 `build`/`test`/`workflow` preset(`cmake --workflow --preset dev` 一条命令走完)。**preset 只描述本仓的构建口径,不改变 CMakeLists 的默认值**——不带 preset 的 `cmake -B build -G Ninja` 行为与以前逐字相同。 From ec43c2661fecfacb53fd98c13c1f69dcab7a5aba Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sun, 8 Nov 2026 10:32:51 +0800 Subject: [PATCH 15/16] =?UTF-8?q?fix(C1):=20Adam/AdamW=20=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=94=B9=20compute=5Finto=E2=80=94=E2=80=94=E6=B6=88?= =?UTF-8?q?=E7=81=AD=E8=9E=8D=E5=90=88=E5=90=9E=E6=8C=81=E4=B9=85=E8=BE=93?= =?UTF-8?q?=E5=87=BA=EF=BC=88full-f16=20NaN=20=E6=A0=B9=E5=9B=A0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - K1/K2 由 compute+move 改为 compute_into(m_/v_[i]):into 是「要求落盘」 的显式声明,配合引擎侧融合器 dst_id 保护,持久状态不再被单输出融合 内联吞掉(此前 v_[i] 读未初始化池残留 → sqrt(负) → step3 起 NaN) - graph_ok 门控删除(原 optimizer=f16 跳录制绕行):根因已修,full-f16 全精度常态走图;NN_NO_GRAPH 旁路一并删除(C1/C2 同组消灭,全仓清零) - 验收:850M 锁定 f16_precision_test ALL PASSED(full-f16 常态走图首验) + ctest --preset dev 81/81 + NN_IRC_TRACE=1 [ir-c][fuse-decline] 生效 - engine 指针随引擎侧修复更新(f9bf542..5e7595c,见 engine 提交) --- engine | 2 +- include/neuralnet.cpp/compute_optimizer.hpp | 63 +++++++++------------ 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/engine b/engine index f9bf542..5e7595c 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit f9bf5429dff109f2c852d65875da8ffa89c42c65 +Subproject commit 5e7595c7124fa214a3edb7f9ed3b250e88455d20 diff --git a/include/neuralnet.cpp/compute_optimizer.hpp b/include/neuralnet.cpp/compute_optimizer.hpp index df9e1db..84bcfe5 100644 --- a/include/neuralnet.cpp/compute_optimizer.hpp +++ b/include/neuralnet.cpp/compute_optimizer.hpp @@ -404,22 +404,21 @@ class Adam : public Optimizer const Scalar s2 = s_scale * s_scale; const Scalar eps_scaled = eps_ * s_scale; - // K1: m = β1*m + (1-β1)*g(单 kernel 融合;状态精度 = p_.optimizer) - auto m_new = dsl::compute(engine_, - dsl::leaf(m_[i]) * dsl::rparam(beta1_) + - dsl::leaf(g) * dsl::rparam(one_minus_beta1 * s_scale), - rows, cols, p_.optimizer); - NN_TRY_CHECK(m_new); - m_[i] = std::move(*m_new); - - // K2: v = β2*v + (1-β2)*g²(单 kernel 融合;状态精度 = p_.optimizer) - auto v_new = - dsl::compute(engine_, - dsl::leaf(v_[i]) * dsl::rparam(beta2_) + - dsl::leaf(g) * dsl::leaf(g) * dsl::rparam(one_minus_beta2 * s2), - rows, cols, p_.optimizer); - NN_TRY_CHECK(v_new); - v_[i] = std::move(*v_new); + // K1: m = β1*m + (1-β1)*g(目标传递:m_[i] 是跨步持久句柄,录制段内 + // 必须 compute_into——compute+move 的输出会在单输出融合中被内联吞掉, + // 占位永不写入 → 下一步读未初始化内存(pitfalls §2.6/模式 I)) + NN_TRY(mr, dsl::compute_into( + engine_, + dsl::leaf(m_[i]) * dsl::rparam(beta1_) + + dsl::leaf(g) * dsl::rparam(one_minus_beta1 * s_scale), + m_[i])); + + // K2: v = β2*v + (1-β2)*g²(同 K1:into 写回持久状态 v_[i]) + NN_TRY(vr, dsl::compute_into( + engine_, + dsl::leaf(v_[i]) * dsl::rparam(beta2_) + + dsl::leaf(g) * dsl::leaf(g) * dsl::rparam(one_minus_beta2 * s2), + v_[i])); // K3: p -= lr * (inv_bc1*m) / (sqrt(inv_bc2*v)+eps)(全链单 kernel 融合; // 依赖刚更新的 m_[i]/v_[i],偏置修正系数 inv_bc1/inv_bc2 逐步变化 @@ -494,24 +493,18 @@ class Adam : public Optimizer // 下 4 节点金刚石单 kernel 3 extras;混精度按门禁自动分裂),替代逐表达式 // 数百个小 dispatch(P2 前实测 266 设备 kernel/步、host 提交 4.5ms/步)。 // CPU begin/end 为 no-op(行为不变)。 - // ⚠ optimizer=f16 **不录制**(P3 验收门 D 实测):m/v 状态为 f16 时, - // 录制/降级派发链路在第 2 步即把参数打成 NaN(同表达式直求值全绿、 - // f32 optimizer 图融合正常、NN_NO_GRAPH A/B 实证)。根因收敛到 - // "IR-C 录制 × f16 状态"组合且未再下钻——full-f16 本是告警级配方 - // (docs/internal/24 §2),正确性优先:该配方退回直求值;f32 - // optimizer(推荐默认 --f16 的 optimizer 槽)保持图融合性能。 - const bool graph_ok = precision_bytes(p_.optimizer) >= 4; - if (graph_ok) - { - NN_TRY(b, engine_.begin_expr()); - } + // (C1 已消灭 2026-11-07:原 optimizer=f16 跳录制门控已删,全精度常态 + // 走图。历史 NaN 根因 = 融合内联图外持有的持久输出 → 读未初始化内存; + // 修法 = K1/K2 目标传递 + 融合器拒绝拼接 into 节点为非尾。见 + // docs/contributors/pitfalls.md §2.6/模式 I。) + NN_TRY(b, engine_.begin_expr()); std::optional seg; - if (graph_ok) seg.emplace(engine_owner_); + seg.emplace(engine_owner_); for (std::size_t i = 0; i < params_.size(); ++i) { NN_TRY(ur, adam_update_(i, inv_bc1, inv_bc2)); } - if (graph_ok) NN_TRY_CHECK(seg->end()); + NN_TRY_CHECK(seg->end()); t_ = t_next; // 全部参数成功才推进步数(失败时不推进) return {}; } @@ -589,14 +582,10 @@ class AdamW : public Adam // (稳定拓扑序保持列表序),故 decay 必须写在 adam_update_ 之前 // (先衰减后更新 = AdamW 语义);两者不连通 → 各自独立 kernel,天然 // 顺序执行,不会同 kernel 双写 p。 - // ⚠ optimizer=f16 不录制(同 Adam::step,P3 门 D 实测) - const bool graph_ok = precision_bytes(p_.optimizer) >= 4; - if (graph_ok) - { - NN_TRY(b, engine_.begin_expr()); - } + // (C1 已消灭 2026-11-07:optimizer=f16 跳录制门控已删,见 Adam::step。) + NN_TRY(b, engine_.begin_expr()); std::optional seg; - if (graph_ok) seg.emplace(engine_owner_); + seg.emplace(engine_owner_); for (std::size_t i = 0; i < params_.size(); ++i) { // 权重衰减解耦:p = (1 - lr*wd) * p(目标传递:原地、单 dispatch) @@ -610,7 +599,7 @@ class AdamW : public Adam // Adam 更新 NN_TRY(ur, adam_update_(i, inv_bc1, inv_bc2)); } - if (graph_ok) NN_TRY_CHECK(seg->end()); + NN_TRY_CHECK(seg->end()); t_ = t_next; // 全部参数成功才推进步数(失败时不推进) return {}; } From 02cde954f74e94ed6f121fee322bc87ef0dd3def Mon Sep 17 00:00:00 2001 From: EthanPeng-2048 Date: Sun, 8 Nov 2026 10:33:01 +0800 Subject: [PATCH 16/16] =?UTF-8?q?docs(C1):=20=E6=95=99=E8=AE=AD=20=C2=A72.?= =?UTF-8?q?6/=E6=A8=A1=E5=BC=8F=20I=20+=20=E5=8F=B0=E8=B4=A6=20C1/C2/C4=20?= =?UTF-8?q?=E6=B6=88=E7=81=AD=E8=AE=B0=E5=BD=95=20+=20C6=20=E6=9C=80?= =?UTF-8?q?=E5=B0=8F=E5=8C=96=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pitfalls 新增灾难级 §2.6(融合内联图外持有的持久输出 → 读未初始化内存, 含四层对拍定位手段与「他机不复现=池残留环境依赖」辨析)与根因模式 I (优化变换的视界小于数据的实际持有面)+ 防坑清单「录制段持久状态必须 compute_into」条目 + 严重度总览计数更新 - 36 号台账:C1/C1-调查/C2/C4 改 ✅——根因定案推翻先前「设备差异」收窄 (四层对拍 + 分段融合形态对照实录);消灭顺序段改进度记录(表内仅剩 C6) - C6 补 2026-11-07 实测:clang 23.1.6 同型亦复现(互换症状实录);最小化 推翻「含 cli/8 域」旧记载——单模块 import nn.tokenizer + 218 行即复现, 材料打包仓库外供外部 AI 审查(已确认 clang 侧问题,等 issue #239625 回复) --- docs/contributors/pitfalls.md | 22 ++++++++++++++++++++-- docs/internal/36-compromise-ledger.md | 17 +++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/docs/contributors/pitfalls.md b/docs/contributors/pitfalls.md index ec88394..a81d2ac 100644 --- a/docs/contributors/pitfalls.md +++ b/docs/contributors/pitfalls.md @@ -11,7 +11,7 @@ | 级别 | 数量 | 特征 | 典型案例 | |------|------|------|----------| -| 灾难级 | 4 | 数据损坏 / 设备永久失效 / 训练白费数小时 | GPT 布局混用、GPU-resident 10% 正确率 | +| 灾难级 | 5 | 数据损坏 / 设备永久失效 / 训练白费数小时 | GPT 布局混用、GPU-resident 10% 正确率、融合吞持久输出 | | 高危 | 6 | 内存爆炸 / 崩溃 / 断言失败 | BPE 60-80GB、reshape 视图断言 | | 中危 | 13 | 结果偏差 / 性能退化 / 并行不确定 | BPE 迭代序泄漏、one-hot 3.2GB、scatter_add 非确定性 | | 低危 | 15+ | 构建失败 / UI 异常 / 兼容性 | CRLF、MSVC 编译器版本、tkinter 保留属性 | @@ -63,6 +63,18 @@ 3. 所有 `waitSemaphoreCount>0` 的 submit 补 `pWaitDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT`。 - **教训**:`VkResult` 原值必须翻译(`VK_TIMEOUT` vs `VK_ERROR_DEVICE_LOST` 的处置完全不同);"第一张独显"在多卡 + 转译层机器上不成立;**二进制信号量不能当"事件"反复等**,多消费者场景必须时间线信号量;校验层是"规范违规但某些驱动容忍"这类问题的唯一可靠放大器。 +### 2.6 融合内联"图外句柄持有的持久输出"→ 状态读未初始化内存(全 f16 必现 NaN,他机不复现) + +- **症状**:full-f16 GPU 训练 step 3 起 loss=-nan(step 1/2 正常);同会话有四重掩护:CPU 全绿、f32/param=f16/opt=f16 各段全绿、CMP 40HX 同代码全绿、JIT 产物 122 个 kernel 零 Float16 capability——排查期几乎误判为"本机显卡/驱动问题"(同期确有 f32 长训偶发 NaN 的独立观察混入,加重了误导)。 +- **根因**:Adam 的 K2(v 更新)输出被 `v_[i] = std::move(*v_new)` 交由**图外持久句柄**持有;全 f16 下 K2/K3/p+=delta 同精度可融合,而 P2 写穿(extras 多输出)**只允许全 F32**(生成器对带 extras 的结构只发 V0 变体)⇒ 全 f16 退化为**单输出**融合(tail=p)。v_new 于是成为融合中间寄存器量,**其占位 buffer 从未被写入**;下一步 `v_[i]` 读到内存池未初始化残留(本机恰为负值垃圾)→ `sqrt(v·inv_bc2)` 对负值 → NaN,一个 step 全链扩散。f32 段因走 extras=3 全落盘、param=f16/opt=f16 段因 K2 输出 F16 与 K3 输出 F32 **异精度断链**(K2 单成员、tail=v_new 落盘),所以全部安全——分段对照是钉死根因的关键证据。 +- **为什么"他机不复现/偶发"**:未初始化池残留的内容是**环境依赖**的(40HX 上恰好是 0/无害值)。同一 bug 在不同机器/分配序列下表现为"必现 / 偶发 / 仅本机"——这是环境依赖的未初始化内存读取的典型迷惑形态。 +- **定位手段**:① 四层对拍探针逐层下钻(L0 输入**位级直读** → L1 `to_f32_all` → L2 f32 融合求值 → L3 `cast_into` 写回),L0 全净、L2 突变 → 问题在输入值本身而非 shader;② 按测试段切片做融合形态对照表(各段 members/tail/extras 统计);③ op 序列解码确认 NaN 产自 `Sqrt(负输入)`(数学正确,坏在输入)。 +- **修复**:持久状态写入改 `compute_into(v_[i])`(into 节点的输出就是句柄本身,融合必须写它;`p+=delta` 本来就是 into 模式,所以从未中招)。 +- **教训**: + a. **融合器只看得见图内依赖,看不见图外句柄**——`compute+move` 交给成员变量的持久状态,在单输出融合下会被静默内联吞掉。**录制段(begin_expr/end_expr)内的持久状态(优化器 m/v 等)必须用 `compute_into` 写入,禁止 `compute` + `move` 接收**(不开图的直求值路径 `compute+move` 无害,但新代码建议统一 into,见 §7 清单); + b. **"他机不复现 / 偶发 / 怀疑硬件"先查未初始化内存读取**——环境依赖的垃圾内容会让同一个 bug 呈现完全不同的触发率; + c. 怀疑硬件前先做**逐层对拍 + 分段对照**:本例两组探针把范围从"驱动/shader 能力"一步缩小到"融合不写盘",而全量 shader capability 审计(本文假设方向)只能排除、不能定位。 + --- ## 3. 高危:内存爆炸/崩溃/断言 @@ -305,7 +317,7 @@ ## 6. 根因模式(跨领域总结) -> 上面 30+ 个坑收敛到 **7 个根因模式**。新代码评审时对照此表自查。 +> 上面 30+ 个坑收敛到 **8 个根因模式**。新代码评审时对照此表自查。 ### 模式 A:同一数据多份表示,不变量未集中维护 GPU 影子一致性、Tensor reshape 视图、模型格式版本默认值、`.tokcache` 缓存——都因"两份数据 + 分散的同步点"出错。 @@ -344,6 +356,11 @@ GPU-resident 单算子对、链式错;attn batch=1 对、batch=2 错;gradche **对策**:① 每个缓存持有层在 backward 入口做 size/valid 校验;② checkpoint 分支显式清缓存;③ 复合层 `forward_recompute` 走虚函数传播;④ 每个"支持 checkpoint/offload 的模型"都要有"与全存基线**逐位一致**"的测试(GPT/RAPT/CNN 各自覆盖)。 +### 模式 I:优化变换的视界小于数据的实际持有面 + +图融合只按**图内依赖边**决定谁是中间量、谁是 tail;但 `compute+move` 交出去的持久句柄(`m_`/`v_`、running 统计、断点状态)持有在**图外**——变换看不见它,于是"图内唯一消费者"的输出被内联,图外持有者读未初始化内存(§2.6)。同类风险:任何"把多步合一步 / 把输出收进寄存器"的优化(融合、死代码消除、缓存复用),都默认了"输出只有图内消费者"。 +**对策**:① **录制段内的持久状态写入一律 `compute_into`(目标传递)**,不用 `compute+move` 接收——into 节点的输出即句柄本身,任何变换都必须写它;融合器侧对 `dst_id != 0` 的节点拒绝拼接为非尾(尊重 into 的落盘声明);② 优化变换落地前校验"被内联输出的占位是否被外部引用",做不到就宁可不融合(fail-fast 优于静默错值);③ 新增持久张量持有者时,对照本模式问一句:"它的写入者会不会被优化变换吞掉?" + --- ## 7. 开发活动防坑清单 @@ -371,6 +388,7 @@ GPU-resident 单算子对、链式错;attn batch=1 对、batch=2 错;gradche - [ ] 双份状态:新增任何 CPU/GPU 双份张量状态时,同步点收敛到单一方法并配 invalidate 测试(旧影子一致性机制已随 GPU-resident 路径移除,勿重新引入分散同步;见 §4.4/模式 A) - [ ] 内存预算:矩阵尺寸 × 4B × 张量个数,超 staging 走分块 - [ ] 校验集合不对称:ExprSpec 校验只在 GPU/scan 路径(CPU 模板路径不校验)→ 新表达式必须跑 GPU 用例(如 `cnn_test --gpu`),CPU 绿不作数(§4.13) +- [ ] **录制段内持久状态写入**(优化器 m/v、running 统计、断点状态等图外句柄)一律 `compute_into`,**禁止 `compute` + `move` 接收**——单输出融合会把 `compute+move` 的输出内联吞掉,读未初始化内存(§2.6/模式 I);融合器对 `dst_id != 0` 成员已强制不拼接为非尾,新写录制代码不要绕过该保护;改录制/融合相关代码后跑 full-f16(`f16_precision_test` 的 `test_full_f16_training`)确认 NaN 未回归 ### 修改 BPE/Tokenizer - [ ] 确定性:所有 ID 分配显式排序,平局按 key 打破 diff --git a/docs/internal/36-compromise-ledger.md b/docs/internal/36-compromise-ledger.md index 1c6db61..7593a49 100644 --- a/docs/internal/36-compromise-ledger.md +++ b/docs/internal/36-compromise-ledger.md @@ -17,16 +17,17 @@ | # | 位置 | 妥协内容(为什么是妥协) | 消灭方案 | 消灭时机 | 状态 | |---|------|--------------------------|----------|----------|------| -| C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | **2026-11-07 已收窄为设备差异(40HX 复现)**:graph_ok 恒 true 的同代码在 CMP 40HX(Turing TU102,shaderFloat16 ✓)上 full-f16 GPU 训练 **ALL PASSED**(f16_precision_test 全绿,无 NaN)——850M(Maxwell 无 shaderFloat16)NaN 是**该设备 f16 回退/边界 cast 写回路径特有**,非跨设备代码 bug。**消灭路径(后续)**:在 850M 上重跑 C4 的「静态签名 JIT(#s/#xxxx,仅需 16bit 存储)vs 边界 cast」单变量 A/B——若 #s 在 850M 绿则 C4 转正恢复直写、C2 删 NN_NO_GRAPH、C1 删 graph_ok 门控(一组消灭,full-f16 图在 850M 也可用);若 #s 也坏则为设备硬限制,门控改「设备能力感知」(describe 无原生 f16 时不录制),用户定调 | 最终清理(P6 后、门 G 前) | ⏳→🧭 | -| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负)。④ **关键谜题(未解)**:K1 录制占位 P=0(F16,record_graph_node_ 打印确认),但**同一 step 的 K3 录制时 m_[i]/v_[i] 的 `precision()` 报告 F32**(K3-record 打印),dispatch 时 d.ins/d.out 也全 F32——**占位张量的精度状态与缓冲不一致**(f16 缓冲 + f32 精度报告,或 m_[i] 句柄在 K1→K3 之间被改),怀疑与「抽象层头(主程序 TU 实例化 NVI)与引擎 .so(GpuEngine 实现)跨 DSO 的实体/精度视图分裂」有关(recording_graph_owner 的 inline thread_local 已实测共享,但张量精度状态路径未查完)。⑤ 本轮未修(深水区,需专门会话继续——建议下一步:打印 K3 录制时 m_[i] 的 `virtual_tag` 与 K1 占位 tag 是否一致,确认 m_[i] 句柄是否真的指向 K1 占位);**C2/C4 依赖 C1,同组卡住**。⑥ **2026-11-07 40HX(CMP 40HX)复现**:graph_ok 恒 true + `NN_VULKAN_DEVICE` 锁 CMP 40HX,同代码 `f16_precision_test` **ALL PASSED**(EXIT=0,full-f16 GPU 训练 loss 有限下降,无 NaN)——「占位张量精度状态/跨 DSO 视图分裂」怀疑在 40HX 上不成立或无害;850M 的 NaN 收窄为 **Maxwell 无 shaderFloat16 的 f16 回退/边界 cast 写回路径设备问题**。注:40HX 复现环境需完整 Windows 构建修复链(NOMINMAX / _CRT_SECURE_NO_WARNINGS / BMI CRT 宏镜像 / WINDOWS_EXPORT_ALL_SYMBOLS WIN32 / shaderc HINTS / kernelgen dll 平台名与导出 / 测试文件显式 STL include / .nnk 缓存目录),见 AGENTS 待更新清单 | 同 C1 | ⏳ | -| C2 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:begin_expr / end_expr 的 `NN_NO_GRAPH` getenv 旁路 | A/B 排查遗留:整跳录制的逃生阀,代码注释自标"临时" | 删除两处 getenv 分支;与 C1 同组验收(删后 full-f16 仍绿) | 同 C1(一组) | ⏳ | +| C1 | `include/neuralnet.cpp/compute_optimizer.hpp`:Adam::step / AdamW::step 的 `graph_ok = precision_bytes(p_.optimizer) >= 4` 分支 | optimizer=f16 时**跳过 IR-C 录制退直求值**:full-f16 图路径第 2 步即 NaN,根因("录制 × f16 状态"组合)未下钻即绕行;该分支使 full-f16 永久失去图融合 | ✅ **已消灭(2026-11-07)**。**根因定案(与设备无关,先前"设备差异"收窄被推翻)**:全 f16 图下 K2(v 更新)与 K3/p+ 同精度融合为**单输出** kernel(P2 写穿 extras 只允许全 F32),`v_[i] = move(*v_new)` 持有的占位被内联为融合中间量**从不写入** → 下一步读内存池未初始化残留(本机恰为负值)→ `sqrt(v)` → NaN。四层对拍(输入位级 → to_f32_all → f32 eval → cast_into)证明边界 cast 链全程净;122 个 JIT 缓存 kernel 零 Float16 capability;"他机不复现"= 池残留内容环境依赖(40HX 恰为 0)。**修法三件套**:① K1/K2 改 `compute_into`(into = 落盘声明);② 融合器拒绝把 `dst_id != 0` 节点拼为非尾(`[ir-c][fuse-decline]` 降级打印);③ into 输出 stamp tag 恢复图内依赖(保住 f32 四成员 extras=3 融合)。**门控已删**(`graph_ok` 恒开图),教训入 `docs/contributors/pitfalls.md` §2.6 + 模式 I。留证:850M 锁定 `f16_precision_test` ALL PASSED(full-f16 常态走图)、`ctest --preset dev` 81/81 | 最终清理(P6 后、门 G 前) | ✅ | +| C1-调查 | **2026-11-06 复现与排除进展**:① 复现稳定——`test_full_f16_training` 临时强制 `graph_ok=true` 后 GPU(850M)step 1 更新即把全部参数打 NaN(loss 3.4778→3.4626→-nan;CPU 直求值同 profile 全绿)。② 排除项:NN_NO_GRAPH=1(GPU 直求值)全绿 ⇒ 根因在**图执行路径**;禁用 graph_plan_cache(`NN_IRC_NO_PLAN_CACHE` A/B)仍 NaN ⇒ 非 P2-12 图级缓存;cast.comp(F32→F16 RNE 写回)审查无误。③ 现场:full-f16 图 = 152 个全 F16 节点(P0=152),fuse 拆成单输出 kernel(P2 写穿要求全 F32 ⇒ 无 extras 复合),全部走 `run_graph_kernel_into_` 的 psig≠0 **边界 cast**(C4 路径);K1(m 更新,key fd3bd576)/K2(v 更新,key a3baa338)/K3(delta)的边界 cast 输入出现 NaN(m/v 在 adam_update_ 录制前下载检查**正常**——0→1.32→2.38 / 0→0.17→0.32),且某 step 的 v_[i] 读到**负值 -2**(v=β2v+(1-β2)g² 数学上不可能负)。④ **关键谜题(未解)**:K1 录制占位 P=0(F16,record_graph_node_ 打印确认),但**同一 step 的 K3 录制时 m_[i]/v_[i] 的 `precision()` 报告 F32**(K3-record 打印),dispatch 时 d.ins/d.out 也全 F32——**占位张量的精度状态与缓冲不一致**(f16 缓冲 + f32 精度报告,或 m_[i] 句柄在 K1→K3 之间被改),怀疑与「抽象层头(主程序 TU 实例化 NVI)与引擎 .so(GpuEngine 实现)跨 DSO 的实体/精度视图分裂」有关(recording_graph_owner 的 inline thread_local 已实测共享,但张量精度状态路径未查完)。⑤ 本轮未修(深水区,需专门会话继续——建议下一步:打印 K3 录制时 m_[i] 的 `virtual_tag` 与 K1 占位 tag 是否一致,确认 m_[i] 句柄是否真的指向 K1 占位);**C2/C4 依赖 C1,同组卡住**。⑥ **2026-11-07 40HX(CMP 40HX)复现**:graph_ok 恒 true + `NN_VULKAN_DEVICE` 锁 CMP 40HX,同代码 `f16_precision_test` **ALL PASSED**(EXIT=0,full-f16 GPU 训练 loss 有限下降,无 NaN)——「占位张量精度状态/跨 DSO 视图分裂」怀疑在 40HX 上不成立或无害;850M 的 NaN 收窄为 **Maxwell 无 shaderFloat16 的 f16 回退/边界 cast 写回路径设备问题**。注:40HX 复现环境需完整 Windows 构建修复链(NOMINMAX / _CRT_SECURE_NO_WARNINGS / BMI CRT 宏镜像 / WINDOWS_EXPORT_ALL_SYMBOLS WIN32 / shaderc HINTS / kernelgen dll 平台名与导出 / 测试文件显式 STL include / .nnk 缓存目录),见 AGENTS 待更新清单。⑦ **根因定案(2026-11-07 晚,推翻④⑥的两个方向)**:④ 的"精度状态分裂"与⑥的"设备 f16 回退"**均非根因**——四层对拍(L0 输入位级直读全净 → L1 to_f32_all → L2 f32 eval 突变产出 NaN → L3 写回)+ 按段融合形态对照表(f32/param=f16/opt=f16 段 extras 落盘全绿 vs all=f16/fullf16 段 K2+K3+p+ 三成员融合 extras=0)钉死:**K2(v 更新)输出被单输出融合内联、占位从不写入,`v_[i]` 读未初始化池残留(负值垃圾)→ `sqrt(v)` = NaN**;v=β2v+(1-β2)g² 数学恒非负 ⇒ ③ 的"v 读到负值 -2"即此现象。定位手段与教训沉淀见 `docs/contributors/pitfalls.md` §2.6(灾难级)。⑤ 的"深水区"就此关闭 | 同 C1 | ✅ | +| C2 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:begin_expr / end_expr 的 `NN_NO_GRAPH` getenv 旁路 | A/B 排查遗留:整跳录制的逃生阀,代码注释自标"临时" | ✅ **已消灭(2026-11-07)**:两处 getenv 分支已删(与 C1 同组);排查使命随根因定案结束,环境变量无任何残留引用(全仓 grep 清零)。验收 = 850M `f16_precision_test` ALL PASSED + `ctest --preset dev` 81/81(full-f16 常态走图、无旁路) | 同 C1(一组) | ✅ | | C3 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:execute_fused_graph 的 `[ir-c]` 打印 | ① degrade 分支由 `nn::dsl::env_flag` 改成裸 `getenv`、② 新增每 kernel 的 `[ir-c][run]` 打印——均属排查期改道;且 env_flag 受 NN_ENABLE_PROBES 门控使 AGENTS 承诺的 `NN_IRC_TRACE=1` 实际不可用,属**未登记的行为口径变更** | ✅ **已消灭(2026-11-06)**:方案 ①——`NN_IRC_TRACE` 正式化为**非探针门行为开关**(裸 getenv 保留,AGENTS §7 注明);**删除 `[ir-c][run]` 每 kernel 打印**(命中执行是热路径常态,刷屏无排查价值);降级 `[ir-c][degrade]` 与批量 `[ir-c][batch]`/`[batch-degrade]` 打印保留(低频、可观测"某段悄悄没融合") | 最终清理 | ✅ | -| C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | C1/C2 消灭后重新 A/B:静态签名 JIT vs 边界 cast,以 f16 基准**择优转正**(优则恢复直写;劣则把边界 cast 升格为有据设计并删实验残留形态) | 最终清理(依赖 C1) | ⏳ | +| C4 | `engine/engine/include/neuralnet.cpp/compute_gpu_engine.hpp`:run_graph_kernel_into_ 的 psig≠0 分支 | 图内 f16 统一**边界 cast**、弃用静态签名 JIT 直写——当时把 full-f16 NaN 归因于"静态签名 JIT"但**未经单变量实验证实**(后证实是 C1 组合);性能上每次 f16 图成员多付临时分配 + 双 cast。期间还留有一次 A/B 未回滚的形态改写(override+tmp 递归 → eval_expr_impl 分配 + cast_into) | ✅ **已裁决转正(2026-11-07)**:C1 根因定案(融合吞持久输出)证明 NaN 与边界 cast 无关——四层对拍边界 cast 链全程净,"错误归因"解除。**形态改写升格为有据设计**(`eval_expr_impl 分配 → cast_into 写回` 与直求值同形态,注释已从排查口径改为设计说明);边界 cast 保留为 f16 图执行的正确路径。**「静态签名 JIT 直写 vs 边界 cast」性能 A/B 不再是本条义务**(C1 债已清),如要优化 f16 图成员的双 cast 开销可另行立项 | 最终清理(依赖 C1) | ✅ | | C5 | `engine/engine/src/kernelgen.cpp`:失败路径诊断 fprintf("GLSL 生成为空" / "shaderc 编译失败") | 排查期新增的输出行为;非掩盖缺陷,但属未评审新增 | ✅ **已消灭(2026-11-06)**:**显式裁决转正**——保留两处 fprintf + 文件头补设计注记(调用方只见 NNK_COMPILE_FAIL,缺此区分不了"生成器不支持形态"与"shaderc 报错"两类根因;失败路径非热路径无性能代价) | 最终清理 | ✅ | -| C6 | `src/tokenizer_consistency_test.cpp`:`old_pre_tokenize` 的 `markers` 特意用 `std::vector`(库侧为 `std::string`) | **clang 24 上游 bug 的规避绕行**(P5 实测,2026-11-06):同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 `pre_tokenize` 的 markers 含相同字面量+相同 sort lambda)**且 import 面超阈值**时,clang 产出错误代码——同函数内相邻循环一个正常执行、另一个 0 次迭代(行为随布局漂移,探针互换),-O0/-O3 均复现。已排除:写法 UB(cppreference range-for/compare 前置条件均满足)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份符号)、布局重合。触发矩阵:纯代码 0 import 过 / 4 链式 import 过 / +expr、+core 过 / **+cli 或 8 域组合或全 13 挂**;old 去 sort 或改名不解除、**改 string_view 解除**。修复=改型打破同形(对拍语义不变),根因在上游未收敛 | ① **已上报 LLVM issues #239625(2026-11-06)**:复测新 clang(++20261105085444)**仍复现**;消融进一步收窄——触发 =「import 面含 nn.cli(裁到 text_train_config+text_train_loop 两头发仍复现,单头不复现)× 模块导出 inline 函数(pre_tokenize)实例化 × 消费方本地 identical 构造」缺一不可;消费方去 regex 仍复现;自包含最小化(模拟 loop 静态函数/大导出面/简化 pre_tokenize)均不复现;repro 依赖项目真实 pcm(issue 附克隆+构建命令)。② clang 修复合入本仓工具链版本后,**撤掉 string_view 特意差异**恢复与库同型(或确认修复的回归用例落 `tokenizer_test`) | 最终清理(门 G 前;工具链升级为触发条件) | ⏳ | +| C6 | `src/tokenizer_consistency_test.cpp`:`old_pre_tokenize` 的 `markers` 特意用 `std::vector`(库侧为 `std::string`) | **clang 24 上游 bug 的规避绕行**(P5 实测,2026-11-06):同一 TU 存在两个 **identical 构造**的 `static` local(测试参考实现与库 `pre_tokenize` 的 markers 含相同字面量+相同 sort lambda)**且 import 面超阈值**时,clang 产出错误代码——同函数内相邻循环一个正常执行、另一个 0 次迭代(行为随布局漂移,探针互换),-O0/-O3 均复现。已排除:写法 UB(cppreference range-for/compare 前置条件均满足)、gcc 15.3 全过、clang 纯代码全过、LTO、双实体(nm 单份符号)、布局重合。触发矩阵:纯代码 0 import 过 / 4 链式 import 过 / +expr、+core 过 / **+cli 或 8 域组合或全 13 挂**;old 去 sort 或改名不解除、**改 string_view 解除**。修复=改型打破同形(对拍语义不变),根因在上游未收敛 | ① **已上报 LLVM issues #239625(2026-11-06)**:复测新 clang(++20261105085444)**仍复现**;**2026-11-07 补测 clang 23.1.6(++20261103094334)同型亦复现**(症状互换:库侧 `manual` 标记循环失效、测试侧 `old` 正常——两侧代码未动仅恢复同型即触发)——规避的充要条件是**两侧异型**,与具体类型无关(两侧同改 string_view 亦必同型炸);测试侧 `string_view` 本就更优(字面量只读表、零拷贝零所有权),该差异兼作规避、非刻意降级;**2026-11-07 最小化(推翻早前"含 cli/8 域"的触发面记载)**:消费方 import 面裁到**单模块 `import nn.tokenizer`** 仍复现(core 经 export import 链传递可见);测试文件再裁到 218 行对拍核心亦复现 → 最小复现 = 单 import + 一行同型 + 两处 identical 构造,材料打包于仓库外 `~/codes/c6-repro/`(README + 源码摘录 + trigger/normal 同源二进制对照),供外部 AI 审查"是否本仓 UB/非标"与 LLVM #239625 互证;消融进一步收窄——触发 =「import 面含 nn.cli(裁到 text_train_config+text_train_loop 两头发仍复现,单头不复现)× 模块导出 inline 函数(pre_tokenize)实例化 × 消费方本地 identical 构造」缺一不可;消费方去 regex 仍复现;自包含最小化(模拟 loop 静态函数/大导出面/简化 pre_tokenize)均不复现;repro 依赖项目真实 pcm(issue 附克隆+构建命令)。② clang 修复合入本仓工具链版本后,**撤掉 string_view 特意差异**恢复与库同型(或确认修复的回归用例落 `tokenizer_test`) | 最终清理(门 G 前;工具链升级为触发条件) | ⏳ | ## 消灭顺序 -C1+C2 同组(互为 A/B 条件)→ C4 重新实验(依赖 C1 结果)→ C3 → C5 → C6(依赖上游 clang 修复,工具链升级时验收)。 -消灭 C1/C2 后必须重跑:`ctest --preset dev` 全绿 + `f16_precision_test` 的 -full-f16 用例(图开启、无 NN_NO_GRAPH)逐条留证。 +**进度(2026-11-07)**:C1+C2 ✅(根因定案 + 三件套修复,门控/旁路已删)→ C4 ✅(裁决转正)→ C3 ✅ → C5 ✅ → **仅剩 C6**(依赖上游 clang 修复 #239625,工具链升级时验收)。 +C1/C2 验收留证(2026-11-07,已完成):`ctest --preset dev` **81/81 全绿** + 850M 锁定 +`f16_precision_test` **ALL PASSED**(full-f16 用例常态走图、无 NN_NO_GRAPH 旁路); +`NN_IRC_TRACE=1` 下 `[ir-c][fuse-decline]` 降级打印实测生效。