Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3cf591c
融合 AOT 步骤①:常量池值退出 expr_spec_key(key 只描述结构)
EthanPeng-2048 Oct 1, 2026
b960450
融合 AOT 步骤②:matmul 转置退出 expr_spec_key(运行期 operand layout)
EthanPeng-2048 Oct 1, 2026
148d1d7
融合 AOT 步骤③:分组归约 R 退出 expr_spec_key(运行期循环)
EthanPeng-2048 Oct 1, 2026
31051a3
融合 AOT 步骤⑤前置:符号张量不再触发 CPU 视图缓存断言
EthanPeng-2048 Oct 1, 2026
41b4374
融合 AOT 步骤⑤:dsl::compute 自登记锚点(结构覆盖不再依赖手写清单)
EthanPeng-2048 Oct 1, 2026
63b7d72
融合 AOT 步骤⑥(修订):锚点覆盖台账 + 签名缺口报告
EthanPeng-2048 Oct 1, 2026
03f9383
融合 AOT 步骤⑥·补:模型级签名 pass(run-only 签名 7/7 → 2/7)
EthanPeng-2048 Oct 1, 2026
484ae35
融合 AOT 步骤⑥·补2:模型 pass 配置矩阵 + 分项来源台账
EthanPeng-2048 Oct 1, 2026
98f887a
融合 AOT 步骤⑥·补3:删除 13 个已被模型 pass 覆盖的 per-layer dry-run 块(−223 行)
EthanPeng-2048 Oct 1, 2026
226b80f
run-only 签名回填:fold · 文档掩码 f16 变体(--f16 --doc-mask 的 miss)
EthanPeng-2048 Oct 1, 2026
145a267
run-only 签名回填:MNIST/CNN f16 训练的 8 条 miss(含 per-layer 块删除引入的 2 条)
EthanPeng-2048 Oct 1, 2026
0c68197
AOT 融合 key 契约测试:把"结构 = 表达式类型"锁进 CI
EthanPeng-2048 Oct 1, 2026
0bdaa97
AOT 融合构建期收为单步:gen_fused 并入 scan_exprs,删除 bin 序列化
EthanPeng-2048 Oct 1, 2026
2c29e02
文档修正:去掉对已删除文件 gen_fused.cpp 的当前态引用
EthanPeng-2048 Oct 1, 2026
b48dd5a
run-only 签名回填:text_train(tinystories smoke)× f16 的 2 条 miss
EthanPeng-2048 Oct 1, 2026
9300bf8
锚点价值运行时验证 + 闭合世界报错带 key
EthanPeng-2048 Oct 1, 2026
06157d5
文档同步:AGENTS.md 跟进 AOT 融合现状 + 13 号清单归档入库
EthanPeng-2048 Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions AGENTS.md

Large diffs are not rendered by default.

33 changes: 11 additions & 22 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -467,40 +467,29 @@ if(Vulkan_FOUND)
nn_embed_shader(im2col_f16 ${CMAKE_SOURCE_DIR}/shaders/im2col.comp -DNN_SHADER_F16=1)
nn_embed_shader(col2im_f16 ${CMAKE_SOURCE_DIR}/shaders/col2im.comp -DNN_SHADER_F16=1)

# 2. AOT 融合 shader:scan_exprs + gen_fused
set(NN_EXPR_SCAN_BIN ${CMAKE_BINARY_DIR}/generated/expr_specs.bin)
# 2. AOT 融合 shader:scan_exprs(**单步** = 收集 + 生成)
# 收集:FusedAnchor 自登记(按表达式类型)+ dry-run + 模型 pass
# 生成:expr_emitter(默认 GLSL)→ glslc → 内联 SPIR-V → fused_registry.hpp
# 原 gen_fused 已并入 scan_exprs 的生成阶段(tools/fused_generate.hpp),
# 不再经 expr_specs.bin 中间文件。
set(NN_FUSED_REGISTRY_HPP ${CMAKE_BINARY_DIR}/generated/fused_registry.hpp)

# 2a. scan_exprs:dry-run Layer 收集内联表达式结构 → expr_specs.bin
set(SCAN_SRC ${CMAKE_SOURCE_DIR}/tools/scan_exprs.cpp)
set(SCAN_SRC ${CMAKE_SOURCE_DIR}/tools/scan_exprs.cpp)
set(FUSED_GEN_SRC ${CMAKE_SOURCE_DIR}/tools/fused_generate.hpp)
add_executable(scan_exprs ${SCAN_SRC})
target_include_directories(scan_exprs PRIVATE ${CMAKE_SOURCE_DIR}/include/neuralnet.cpp)
target_link_libraries(scan_exprs PRIVATE nn_core Vulkan::Vulkan)
add_custom_command(
OUTPUT ${NN_EXPR_SCAN_BIN}
OUTPUT ${NN_FUSED_REGISTRY_HPP}
# scan_exprs 是原生可执行文件,直接写文件、不会自建目录。
# Ninja 会自动创建自定义命令的输出目录,但 Makefiles 生成器不会,
# 全新构建时 generated/ 尚未存在会写入失败(见 08-pitfalls)。
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/generated
COMMAND scan_exprs ${NN_EXPR_SCAN_BIN}
DEPENDS scan_exprs ${SCAN_SRC}
COMMENT "[GPU] Scanning Layer expressions (scan_exprs) → expr_specs.bin"
)

# 2b. gen_fused:读 bin → glsl_gen → glslc → 内联 SPIR-V → fused_registry.hpp
set(FUSED_GEN_SRC ${CMAKE_SOURCE_DIR}/tools/gen_fused.cpp)
add_executable(gen_fused ${FUSED_GEN_SRC})
target_include_directories(gen_fused PRIVATE ${CMAKE_SOURCE_DIR}/include/neuralnet.cpp)
target_link_libraries(gen_fused PRIVATE nn_core)
add_custom_command(
OUTPUT ${NN_FUSED_REGISTRY_HPP}
# 同 scan_exprs:gen_fused 也是原生可执行文件,需先确保输出目录存在
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/generated
COMMAND gen_fused ${CMAKE_BINARY_DIR}/generated ${GLSLC} ${NN_EXPR_SCAN_BIN}
COMMAND scan_exprs ${CMAKE_BINARY_DIR}/generated ${GLSLC}
${CMAKE_SOURCE_DIR}/tools/prec_backfill.txt
DEPENDS gen_fused ${FUSED_GEN_SRC} ${NN_EXPR_SCAN_BIN}
DEPENDS scan_exprs ${SCAN_SRC} ${FUSED_GEN_SRC}
${CMAKE_SOURCE_DIR}/tools/prec_backfill.txt
COMMENT "[GPU] Synthesizing fused shaders (gen_fused) → fused_registry.hpp"
COMMENT "[GPU] Scan + synthesize fused shaders (scan_exprs) → fused_registry.hpp"
)

list(APPEND GPU_SHADER_HPPS ${NN_FUSED_REGISTRY_HPP})
Expand Down
85 changes: 69 additions & 16 deletions docs/development/02-operator-fusion.md

Large diffs are not rendered by default.

22 changes: 19 additions & 3 deletions docs/development/03-ir-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ expr_spec_key(spec) ≡ expr_spec_key(canonicalize_expr_spec(spec))

即 **key 一定在 canonical IR 上计算**。scan(构建期折叠时)与 runtime(运行时折叠时)必须都先 canonicalize 再算 key,保证两端一致。

**key 只描述"结构",运行期数值一律不进 key**(进了会让每个取值组合各编译一份**逐字节相同**的 shader,并让"结构 = 表达式类型"不成立):

| 字段 | 进 key | 运行期载体 |
|------|--------|-----------|
| 指令 / 视图 / 寄存器数 | ✅ | — |
| 常量池**个数** | ✅(决定 push constant 布局) | — |
| 常量池**值** | ❌ | push constant `c<i>`(`ExprOperandKind::Const` 发射成 `c<idx>`) |
| `rparams` 个数 / 值 | 个数 ✅ / 值 ❌ | push constant `rp<i>` |
| 视图形状参数(RowMod/RotateHalf/BatchMod/BatchCol/RowAccess 的 param) | ❌ | push constant `vp<i>` |
| `matmul.k` / `matmul.batch` | ❌ | push constant `mm_k` / `mm_batch` |
| `matmul.a_input/b_input`(输入槽位) | ✅ | — |
| `matmul.transA/transB` | ❌ | push constant `mm_trans`(bit0=transA / bit1=transB;shader 内两条加载路径 + uniform 分支,同一结构覆盖 4 种转置组合) |
| fold 段 matmul 的 transA/transB | ✅(fold 的 mm 段生成期定死,**尚未收敛**) | — |
| fold 的 `k` / `vec_state_len` | ❌ | push constant |
| 分组归约视图的 `R` | ❌ | push constant `vp<i>`(读取走 `gr_r<slot>()` 辅助函数内的运行期循环,累加顺序与 CPU 模板路径一致) |

### 4.2 确定性铁律

- **pass 遍历顺序必须固定**(如始终按指令序从前到后、视图按输入序、常量按出现序)。
Expand Down Expand Up @@ -151,9 +167,9 @@ IR → GlslEmitter(当前唯一注册后端)

- `ExprEmitter` 纯接口(name/generate/generate_reduce)+ `emitter_registry`(按后端名选择工厂)。
- `GlslEmitter`(`expr_glsl_gen.hpp` 的 generate_glsl/generate_glsl_reduce 封装)。
- `gen_fused` 经 emitter 注册表选择后端(默认 glsl);`--list-backends` 展示可用后端。
- 生成阶段 经 emitter 注册表选择后端(默认 glsl);`--list-backends` 展示可用后端。

> **当前只有 `glsl` 一个注册后端**(`GlslEmitter`);`gen_fused --list-backends` 可列出。
> **当前只有 `glsl` 一个注册后端**(`GlslEmitter`);`scan_exprs --list-backends` 可列出。
> 接口 + 注册表即 IR-D 的全部交付:新后端实现 `ExprEmitter` 并注册即可,运行时无需包含该头(与 `glsl_gen` 一致)。

---
Expand All @@ -170,7 +186,7 @@ IR → GlslEmitter(当前唯一注册后端)
### IR-A / IR-B 实现要点(`expr_opt.hpp`)

- `fold_constants_and_algebra`(常量池去重 + 保守常量折叠 + 代数化简)、`dead_code_elimination`、`renumber_registers`(寄存器连续重编号 + 常量池清理)、`common_subexpression_elimination`(Fanout 归一化 + 哈希复用)、`allocate_registers_liveness`(liveness 线性扫描,确定性贪心)、`canonicalize_expr_spec`(完整链)。
- **canonical IR 接入 key**:`ExprRegistry::add/contains`、CPU `eval_expr_impl`、GPU `eval_expr/eval_expr_reduce` 全部先 `canonicalize_expr_spec` 再算 key;scan/gen_fused 存 canonical spec → shader 按 canonical 合成。**dispatch 必须用 canonical 的 consts**(折叠可能增删常量池)。
- **canonical IR 接入 key**:`ExprRegistry::add/contains`、CPU `eval_expr_impl`、GPU `eval_expr/eval_expr_reduce` 全部先 `canonicalize_expr_spec` 再算 key;scan/生成阶段 存 canonical spec → shader 按 canonical 合成。**dispatch 必须用 canonical 的 consts**(折叠可能增删常量池)。
- **关键不变量**:canonicalize 不改变 views/inputs(顺序、内容),只优化 instrs/consts/num_regs → 运行时输入绑定布局不变;输出指令(最后一条)恒为真实寄存器(glsl_gen 输出 `r<last_dst>`)。
- **glsl_gen 配套**:寄存器"先声明、后赋值"(`float r0, r1, ...;` + 纯赋值),兼容 liveness 复用同号寄存器(否则 GLSL redefinition)。
- **关键坑**:① regalloc 复用后归约指令 dst 与逐元素 dst 必须**区段分离**(validate 要求 reduce_dst/elem_dst 按号互斥);② 各 pass 重映射只处理 `expr_instr_num_operands(op)` 实际使用的操作数(未用 b/c 是默认哨兵 {0,0},不得当 Reg(0) 重编号)。
Expand Down
6 changes: 3 additions & 3 deletions docs/development/05-mixed-precision.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ loss = ce.forward_sparse(engine, logits, labels, mask, vocab,
| `scan_exprs` 双 pass | 整段 dry-run 收进 `dry_run(engine, profile)`:`profile_f32`(sig==0,旧行为)与 `profile_f16`(输入张量按 compute 精度创建 → 与运行时同源)。f16 pass **直接传原生引擎即可**——P-1 后 f16 边界 cast 由基类 NVI 入口统一处理(历史上直喂原生 `CpuEngine` = heap corruption(0xC0000374,实测);适配层已删除、职责并入基类)。 |
| bin v9 | 规格表之后的**变体段**:每个变体只存 `{sig, 基础结构下标}`——变体与基础结构同 key(精度不进 `expr_spec_key`),故不重复序列化 spec 体(也免读写不对称风险)。 |
| `GlslEmitter` | `generate/generate_reduce` 带 `sig` 形参(默认 0 → **GLSL 与不带变体时逐字节相同**)。带类型输出 = 缓冲声明 `float16_t` + `#extension GL_EXT_shader_16bit_storage` + 读 `float(x)` / 写 `float16_t(v)`;算术默认全 f32(§7.2)。视图内部有算术的分支(RotateHalf 的取负、RowGather 的 `uint(...)` 索引)必须**在叶子处**转换——否则 glslc 报 `'-' : wrong operand type ... float16_t`。 |
| `gen_fused` | 每变体独立 shader(文件名/标识符 `key_sighex`,注册键 `key#sig`),`FusedShader` 带 `prec_sig`;生成器不支持的形态 → **跳过并告警**(不是失败)。 |
| 生成阶段 | 每变体独立 shader(文件名/标识符 `key_sighex`,注册键 `key#sig`),`FusedShader` 带 `prec_sig`;生成器不支持的形态 → **跳过并告警**(不是失败)。 |
| 设备 | 查询并启用 `storageBuffer16BitAccess`(`NN_VULKAN_NO_16BIT_STORAGE=1` 可强制回退);未启用时后端跳过 `key#sig` 键(运行时自然回退边界 cast)。 |
| 运行时 | `run_fused_gpu` 输入是**类型擦除的 buffer 视图**;`FusedInputs{owners, bufs}` **必须同时持有 owner**——只存裸 `GpuBuffer*` 会在录制中途释放上传缓冲(铁律 6,实测 `A+=B err=0.5`)。`GpuEngine::eval_expr` 按 `(key,sig)` 优先命中变体,**f16 输出按 2B/元素分配缓冲并重贴 `GpuTensorF16`**(漏了 = shader 只写前半 + f32 标签 → 训练 loss=NaN);f16 进原生引擎却无变体 → 明确报错(绝不把 f16 buffer 绑到 f32 shader)。适配层 `supports_expr_precision_variant` 前置查询:命中则直吃 f16、未命中回退边界 cast。 |

Expand Down Expand Up @@ -625,15 +625,15 @@ loss = ce.forward_sparse(engine, logits, labels, mask, vocab,
| **归约 kernel** | `generate_glsl_reduce(name, spec, sig)`:输入 `float16_t` + `rd()/wr()` 在读写点统一转换(含 `emit_mm_decl` 点积、行/列两个 pass 的直接索引读、`operand()` 的视图读与广播读、输出写) | 含归约的变体可生成 |
| **目标传递** | `GpuEngine::eval_expr_into` / `eval_expr_reduce` 带 `(key,sig)` 变体匹配(输出精度 = `dst.precision()` / `P`),f16 输出重贴 `GpuTensorF16`(归约向量形状按 raxis 取 `(rows,1)/(1,cols)`);适配层这两入口先查 `supports_expr_precision_variant` 再回退 cast | `compute_into` / `compute_reduce` 不再必然走边界 cast |

`gen_fused` 的基础结构数与变体条目数以构建输出 `[scan]`/`[gen]` 为准(变体含 scan 预测 + 回填清单,见 ⑤)。
生成阶段 的基础结构数与变体条目数以构建输出 `[scan]`/`[gen]` 为准(变体含 scan 预测 + 回填清单,见 ⑤)。

#### ④ 实测收益

matmul 段 / 归约 / 目标传递三类变体落地后的同窗交错实测(f32 与 f16 峰值、耗时、backward transient 分桶变化)见 `docs/history.md`;**当前(含 op-level f16 GEMM)的最终数字见 §12.11**。

#### ⑤ 运行时签名回填(当前)

`[prec][miss]`(`NN_PREC_TRACE=1`)打印"请求了非零签名却没命中带类型变体"的 `(key, sig, 形态)`——这类**扫描时看不到的运行时签名**(如 Linear matmul 段的 `in=[f16,f32,f32]` 混合签名、Norm 归约链 `[f32,f16]` 输入)miss 时走边界 cast。**当前机制 = 回填清单** `tools/prec_backfill.txt`(Phase D3):miss 复现 → 追加一行 `<key> <sig>` → `gen_fused` 对命中的 `(结构, 签名)` 额外发射变体(与 scan 变体同一代码路径,重复条目幂等去重;结构不在 bin / 生成器不支持时告警跳过)。**fold 的带类型变体已生成**(Phase D2,键 `fkey#sig`,3 输入全 f16 + 输出 f16,算术/状态进位 f32)。
`[prec][miss]`(`NN_PREC_TRACE=1`)打印"请求了非零签名却没命中带类型变体"的 `(key, sig, 形态)`——这类**扫描时看不到的运行时签名**(如 Linear matmul 段的 `in=[f16,f32,f32]` 混合签名、Norm 归约链 `[f32,f16]` 输入)miss 时走边界 cast。**当前机制 = 回填清单** `tools/prec_backfill.txt`(Phase D3):miss 复现 → 追加一行 `<key> <sig>` → 生成阶段 对命中的 `(结构, 签名)` 额外发射变体(与 scan 变体同一代码路径,重复条目幂等去重;结构不在注册表 / 生成器不支持时告警跳过)。**fold 的带类型变体已生成**(Phase D2,键 `fkey#sig`,3 输入全 f16 + 输出 f16,算术/状态进位 f32)。

---

Expand Down
9 changes: 6 additions & 3 deletions docs/development/12-compute-engine-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,12 @@ scan_suffix_outer/outer_col——顺序状态机,DSL 无此语义)、`eval_e
3. **新增 ExprViewKind 的落点清单**(以 grouped_reduce 为参照):`expr_spec.hpp`
枚举+validate → `expr_dsl.hpp` SpecBuilder helper + 叶子 + 自由函数 +
`has_reduction_v=false` 显式特化 → `expr_glsl_gen.hpp` `glsl_view_read` case
(param 编译期展开)→ `compute_cpu_engine.hpp` 解释器两处(形状校验 switch +
读取 switch)。GPU 侧无 per-view 分派(只填 vp push constant,非 runtime param
的 view 不涉及);`glsl_vec4_eligible` 白名单与 `glsl_view_uses_row/col` 排除集
→ `compute_cpu_engine.hpp` 解释器两处(形状校验 switch + 读取 switch)。
**param 是否进 key 取决于 `expr_view_has_runtime_param`**:进(结构参数)= case
内按 `v.param` 编译期展开;不进(运行期视图参数)= case 内只发一个读取调用,
另需一处生成器侧辅助函数(如分组归约的 `glsl_emit_grouped_reduce_helpers` 发
`gr_r<i>()` 运行期循环)并在生成器 PC 之后调用。GPU 侧无 per-view 分派(只填 vp
push constant);`glsl_vec4_eligible` 白名单与 `glsl_view_uses_row/col` 排除集
自动正确——无需改。

### 2.2 不被 Layer 直接调用(其余 28 个:DSL lowering / 序列化 / CLI / 适配层 / 测试)
Expand Down
Loading
Loading