读者:项目开发者(本仓内部工作文档)。本分册定位见
README.md;历史归档在../history.md。 状态:现行。
本文只记录当前状态:引擎接口清单、表达式求值机制、与目标形态的差距。 目的:把当前代码里实际并存的计算 API 与遗留物列清楚,作为事实底座;全部结论来自 当前工作树的源码与构建配置,复现命令见
bench/doc_inventory.ps1与 §9。当前数字(2026-11-08 实测):引擎 virtual 59 个方法(
bench/doc_inventory.ps1口径;NVI 公开入口 +*_impl虚实现两套命名都在该脚本的清单里);Layer/Loss/Optimizer 直调 38 个(含begin_expr——由Adam::step/AdamW::step打开 IR-C 录制段;含cast_into——层内结果按 profile 落精度;含scale_inplace/col_reduce_max等经dsl之外的量化/RL 路径); 其余 21 个公开入口只服务 DSL lowering / 序列化 / CLI / 适配层 / 测试,见 §2); CPU 求值机制 2 套 (DSL 模板路径 + IR 解释器,见 §3);ctest 注册数随新增用例增长(完整枚举只写在docs/internal/18§2, 此处不重复以免漂移)。error_macro_test(错误处理宏族NN_UNWRAP/NN_EXIT/NN_TRY_MSG/NN_FAIL)与kvrec_test/nnvocab_test/dataset_test(统一数据集 KVRecord v2 /.nnvocab/.nndataset,见docs/internal/19)均在主仓src/(2026-11-01 子仓合并回主仓后回归主仓 ctest);dsl_shape_infer_test=dsl::compute/compute_reduce的无形状重载(推导口径 + 负例)。历史演进与收敛记录(计算类原语全量迁 DSL、算子收敛与遗留物 清理、接口数字 58→49 / 直调 35→23 的过程)已移入
docs/history/history.md;M4 后直调 23→21:from_matrix/to_matrix退出 L2(铁律 #12 / 17 §3 D11,层自算辅助 数据改走nn::upload_span/download_span,它们是自由函数、不计入engine.<op>(口径)。 IR-C(移除后已恢复):begin_expr/end_expr+expr_graph.hpp现行, P1/P2 已落地,Adam::step/AdamW::step直调begin_expr(直调 21→22)。移除期记录见docs/history/history.md,定位修正与取舍见03-ir-optimization.md§5.3。
引擎设计历经 5 代(纯 Matrix → forward_gpu 双实现 → eager → begin_expr/end_expr → dsl::compute),
当前仍在编译、被调用的形态(IR-C 录制已恢复,与 dsl::compute 并存):
| 世代 | 现状 |
|---|---|
| 纯 Matrix(无 Tensor) | algebra_* 在用,是 Tensor 的底层存储 + CPU 手写 kernel |
forward_gpu / backward_gpu 双实现 |
不存在(仅注释残留) |
| eager(直接调引擎算子) | Layer 仍直调 38 个算子,且全部是基础设施 / 数据搬运 / 状态扫描 / fold 执行(结构自登记) / 三值打包 / 张量形变(见 §2.1);计算类原语一律走 DSL |
begin_expr / end_expr(IR-C 录制图) |
✅ 现行(P1 机制 + P2 写穿多输出;expr_graph.hpp,见 §4.2/§4.3) |
dsl::compute |
✅ 当前主推,被 Layer 大量使用 |
| 实现 | 文件 | 大小 | 状态 |
|---|---|---|---|
CpuEngine |
compute_cpu_engine.hpp |
88.5 KB | 默认引擎 |
GpuEngine |
compute_gpu_engine.hpp |
58.0 KB | Vulkan(NN_HAS_VULKAN) |
不存在 CUDA 引擎:
compute_cuda_engine.hpp、backend/compute_cuda_backend.hpp、cuda/目录与全库NN_HAS_CUDA/ CLI--cuda均不存在,勿声称支持 CUDA;历史快照见 git 分支legacy/cuda(移除范围清单见docs/history/history.md)。
文件:compute_engine.hpp;数字可用 bench/doc_inventory.ps1 复现(见 §9)。
多精度 Phase 2 加入 cast_into/copy_into/supports_native_data_move/
supports_expr_precision_variant 与 Precision P 参数的运算类原语。
(统计口径:compute_layer*.hpp + compute_loss.hpp + compute_optimizer.hpp +
model_container.hpp + compute_position_encoding.hpp 中出现的 engine.<op>(,排除
engine_.reset(...) 这类非算子调用;
复现:bench/doc_inventory.ps1)
begin_batch, begin_expr, cast_into, clone, col_reduce_max, col2im, copy_into,
create_offload_buffer, create_tensor, device, end_batch, eval_expr, flush_batch,
gather_rows, get, im2col, insert_rows, matmul_q, matmul_q_i8, matmul_q_t,
offload_restore, offload_save, outer_col, quantize_i8, quantized_adamw_step,
rearrange_3d, reshape, scale_inplace, scan_prefix_outer, scan_suffix_outer,
scatter_add_rows, slice_rows, transpose, trit_pack_into, try_create_tensor,
unpack_i8, weak_from_this, zero
(9 个计算类原语不在此列:matmul/matmul_with_bias/batched_matmul/
row_reduce_sum/col_reduce_sum/grouped_reduce_sum/grouped_reduce_max/
add_inplace/accumulate 一律不经 Layer 直调
——Layer 写 dsl::compute/compute_into/compute_reduce(归类见 §7.2)。
这 38 个按设计保留:基础设施 / 张量形变(begin/end/flush_batch、create/offload_*、
clone、reshape、try_create_tensor、zero)、数据搬运(transpose/slice_rows/insert_rows/
rearrange_3d/gather_rows/scatter_add_rows/im2col/col2im——DSL 表达式是"每输出元素
独立计算"模型,搬运类的不规则索引/原子累加不属于它)、状态扫描(scan_prefix_outer/
scan_suffix_outer/outer_col——顺序状态机,DSL 无此语义)、eval_expr(fold 执行
路径:构造与登记同在定义方经 FoldAnchor 自登记——通用样例在 expr_fold.hpp、
注意力组合在 compute_layer_attention.hpp),begin_expr(IR-C
录制段入口:Adam::step/AdamW::step 打开图录制,ExprSegment 守卫收口)。
三值/量化原语(matmul_q/matmul_q_t/matmul_q_i8/trit_pack_into/quantize_i8/
unpack_i8/quantized_adamw_step/scale_inplace/col_reduce_max——BitLinear 与
int8/AdamW 量化路径的 op-level 原语,不进 DSL 注册表,见 docs/internal/21 §4.12)。
from_matrix/to_matrix 自 M4 起退出本列(铁律 #12:L2+ 禁用 Matrix 型 I/O
动词),层自算辅助数据改走 nn::upload_span/download_span/download_vector
(自由函数,不进 engine.<op>( 统计)。)
改写 Layer 直调点 / 扩展 DSL 时的现行规则:
- 新增/改写表达式后必须做真实形态的运行期验证:GPU 走运行期 JIT(
libnn-kernelgen现编 SPIR-V,.nnk缓存复用),失败以NNK_COMPILE_FAIL等明确错误暴露、不会静默; 验收应覆盖 CPU 两套机制(§3 机制 1/2)+ GPU JIT(nn::model_warmup()预热后再跑一遍, 区分"首次编译"与"命中缓存"两条路径)。 - 带 alpha 的批量 matmul 写
dsl::matmul(...) * dsl::rparam(alpha)尾链: CPU(累加后乘)/GPU(写出时乘)/DSL(尾指令乘)三端同序,数值等价;alpha 必须 rparam 不能 const(值进 key 会破坏形状无关融合,与 S7 教训 1 同族)。 - 新增 ExprViewKind 的落点清单(以 grouped_reduce 为参照):
expr_spec.hpp枚举+validate →expr_dsl.hppSpecBuilder helper + 叶子 + 自由函数 +has_reduction_v=false显式特化 →expr_glsl_gen.hppglsl_view_readcase →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排除集 自动正确——无需改。
其余入口正是 §7.3 的"eager 算子 = DSL 的内部 lowering 目标"形态:计算类原语只被
DSL lowering 内部(MatmulRef::prepare* 调 eng.matmul/batched_matmul、
ReduceViewRef::prepare 调各 reduce)、基类 NVI 边界 cast 入口转发(原 PrecisionEngine,P-1 已下沉)与
测试/bench(f16_*_test/gpu_f16_test/layer_bench/f16_cpu_probe 等)使用。
精确集合以 bench/doc_inventory.ps1 的"not layer-called"清单为准(59 − 38 = 21 个)。
| 算子 | 实际调用方 |
|---|---|
eval_expr_into / eval_expr_reduce |
dsl::compute_into / dsl::compute_reduce(expr_dsl.hpp)+ 基类 NVI 入口转发 |
matmul / matmul_with_bias / batched_matmul |
DSL lowering(MatmulRef::prepare*)+ 基类 NVI 入口转发 + 测试/bench |
row_reduce_sum / col_reduce_sum / grouped_reduce_sum / grouped_reduce_max |
DSL lowering(ReduceViewRef::prepare)+ 基类 NVI 入口转发 + 测试 |
add_inplace / accumulate |
基类 NVI 入口(accumulate 为非虚入口本体,无 _impl)+ 测试/bench/probe |
import / import_impl |
跨设备/引擎拉取(M6 新增,17 §4.3):GpuEngine::import_impl = 原 ensure_gpu 的直传路径(16 §2 的 43 处调用点已全部改名 import);基类默认 = 同设备别名/引擎内 cast + 跨设备宿主中转 |
from_matrix / to_matrix |
I/O 层(domain_*.hpp 数据集与初值、model_serialization.hpp、cli_*、测试)——M4 起 L2+ 禁用(铁律 #12 / 17 §3 D11) |
cast / copy_from |
model_serialization.hpp + f16 测试 / gradcheck |
cast_into / copy_into / supports_* |
基类 NVI 边界 cast 的"写回原存储"路径(compute_engine.hpp)+ 引擎实现互调(CPU cast_into 同精度分支转 copy_into)+ write 的 f16 目标(经 copy_from → f32 上传 + cast_into) |
pool_stats |
src/text_train.cpp(池账本统计) |
release_idle_pool_blocks |
cli/cli_mnist_io.hpp、src/text_train.cpp(mem_probe 探针已随引擎 .so 化删除,T9) |
submit_scalar_readback / poll_scalar_readback / scalar_readback_slots |
标量回读(loss / ce_fusion_test),Layer 不直调 |
device |
引擎外部少量判断 |
(已删除算子的历史调用方快照见 docs/history/history.md。)
| # | 机制 | 实现位置 | 并行度(实测) | 谁在用 |
|---|---|---|---|---|
| 1 | DSL 模板路径 eval_cpu(e, rows, cols) → eval_into_span |
expr_dsl.hpp:181 |
并行 + 向量化(n≥PARALLEL_THRESHOLD 走 nn::parallel_for_samples 分块,块内 NN_VECTORIZE_PRAGMA;不 per-element 重导 cpu_matrix().span()) |
dsl::compute 的无归约分支 + dsl::compute_into 的逐元素分支 |
| 2 | DSL IR 解释器 eval_expr_impl |
compute_cpu_engine.hpp:1251-1789 |
串行(逐元素 switch 分派 + 归约前缀重放) | dsl::compute 的含归约/广播/matmul/索引视图分支(expr_dsl.hpp:680)+ compute_reduce |
| 3 | GPU 运行期 JIT eval_expr |
compute_gpu_engine.hpp |
shader 并行(.nnk 缓存命中直接派发,未命中现编) |
GPU 全部表达式 |
分流判据:has_reduction_v<E>(expr_dsl.hpp:455-486)。注意它把 MatmulRef 也算作"归约"
(:462-464),所以连 matmul 都被推进了解释器。DSL 用量(include/neuralnet.cpp 全量
计数):dsl::compute( 133 处、dsl::compute_into( 34 处、dsl::compute_reduce( 30 处
(其中含 matmul/broadcast/归约的落到机制 2)。
现状判定:机制 1 与机制 2 是 DSL 在 CPU 上的两条路,一条内联、一条解释——这是全部 CPU 求值路径。机制 2 逐元素 switch 分派 + 归约前缀重放,实测比等价原语慢 1.5–3.8 倍 (诊断实测,结论数字保留)。
Layer 仍直调的 38 个算子(§2.1)走的就是这条路;GPU 侧每个都是独立的 backend kernel dispatch,即"每算子一次 kernel 开销"的求值方式——计算类原语不经此路径(一律写 DSL 表达式,见 §2.1/§7.2),eager 只剩基础设施、不可约原语与 IR-C 录制段开合。
现行。ComputeEngine::begin_expr/end_expr 是虚入口(引擎 virtual 59 个
方法之一),expr_graph.hpp 提供 ExprGraph/ExprGraphNode/fuse_expr_graph/
recording_graph_owner、per-graph node_outputs、全局唯一占位 tag 分配器 next_virtual_tag(),
以及 P2-12 图级计划缓存(graph_cache_key/FusedKernelPlan/instantiate_plan)。GPU 真录制
(execute_fused_graph:图 → 融合分析 → kernel 序列 → JIT dispatch,kernel 输出经
output_override 直接写进尾节点占位存储 + run_graph_kernel_into_);CPU 普通运行
begin_expr/end_expr 是 no-op(CPU 不做图融合)。P2 还新增
引擎入口 eval_expr_multi_into(NVI + 虚 eval_expr_multi_into_impl)与 ExprSpec.extras
(多输出写穿,≤4)。IR 流水线现状 = IR-A/B/C/D(expr_opt.hpp/expr_graph.hpp/expr_emitter.hpp)。
移除记录(原 §4.2.1–§4.2.3 盘点与提交时间线)作为历史见 docs/history/history.md。
现行(expr_dsl.hpp 的 ExprBlock):让单个表达式跨行书写,
end_expr 延迟到整块写完再求值。⚠ 与 ComputeEngine::begin_expr/end_expr(跨表达式图录制)
不是一回事(历史记录见 docs/history/history.md)。
expr_dsl.hpp:646-690,Layer 全面使用。
| 项 | 规模 | 判定 |
|---|---|---|
| DSL IR 解释器(机制 2:含归约/matmul 表达式的 CPU 串行解释) | compute_cpu_engine.hpp 内 |
|
pool_stats() |
接口 1 个 | ✅ 活(src/text_train.cpp 池账本统计) |
NN_EXPR_SCAN 构建期收集链(expr_registry.hpp / scan_exprs / fused_registry.hpp) |
— | ✅ 不存在(表达式执行改为运行期 JIT:libnn-kernelgen + .nnk 缓存 + nn::model_warmup();见 docs/internal/33) |
不属于遗留物:IR-A/B/C(
expr_opt.hpp/expr_graph.hpp)、IR-D(expr_emitter.hpp)是同一 IR 流水线的 阶段;eager 算子中 §7.2 ③ 的不可约原语按设计保留为层可见。 其余历史遗留项(CUDA 全链、旧代数 AST、axpy_inplace/broadcast_*/elementwise_*/offload_store·offload_load/elementwise_select_scalar_cond/ 引擎row_reduce_max等无根算子、nn::one_hot、Matrix::multiply_transposed_add_to、CpuEmitter残留)均不存在——原清单与判定见docs/history/history.md(IR-C 不在其列:它已被恢复为现行能力,见 §4.2)。
目标形态:
- Layer 只写
dsl::compute/compute_reduce(不直接调计算类 eager 算子); - 引擎保留一组"纯算子",但只服务于 DSL 的 lowering,不再作为 Layer 的公开 API;
- 干掉重复的代际产物。
按现状,差距在四处:
| 差距 | 现状 | 说明 |
|---|---|---|
| A. Layer 直调算子 | 38 个(全部为基础设施/张量形变/数据搬运/状态扫描/fold 执行(自登记)/IR-C 录制段开合/三值量化打包) | ✅ 计算类原语已全部退出 Layer 直调:Layer 只写 DSL;保留的 38 个按 §7.2 分类属"必须保留为层可见原语"(见 §2.1);M4 起 from_matrix/to_matrix 也已退出 L2 |
| B. 求值机制 | CPU 2 套 + GPU JIT | |
| C. 融合世代 | eager / dsl 并存 | eager 中待收敛的重复逐元素路径已清空,只剩 §7.2 ③ 不可约原语按设计公开;IR-C(图录制融合)现行(见 §4.2 与 03-ir-optimization.md §5.3) |
| D. 死代码 | 无 | 无调用点接口已清空(历史清单见 docs/history/history.md) |
注意:A 与 C 不是纯删除——scan_prefix_outer / outer_col(RLA/RAPT)、offload_*(activation offload)、
begin_batch/end_batch(GPU 命令录制)这些不是普通逐元素/矩阵算子,DSL 目前表达不了。
所以"只保留 dsl::compute + 纯算子"需要先给这几类定去向(DSL 内建?独立子系统?),
否则会卡在 RLA/offload 上。
用代码检验(不是推测):
- GPU:
eval_expr(compute_gpu_engine.hpp:942)→canonicalize_expr_spec→expr_spec_key→backend_.run_fused_gpu(...)(:985)。全程不调用engine.matmul这类 eager 算子。 - CPU:
eval_expr_impl(compute_cpu_engine.hpp:1251)的 matmul 段直接调Matrix::multiply_to_span(:1432);逐元素链用自己的循环。
即 DSL 真正依赖的是 algebra_matrix.hpp 的 Matrix 级内核 + expr_spec.hpp 的 IR,
不是 ComputeEngine 的 59 个 virtual 方法。dsl::matmul 折成的是 MatmulSpec(IR 前置段),
不是对 engine.matmul 的调用。
所以"DSL 依赖 eager 算子"这句话,只在"eager 算子是 DSL 的 lowering 目标"这个意义上成立, 而在"DSL 需要它们公开存在"这个意义上不成立。
| 类别 | 算子 | 处置 |
|---|---|---|
| ① 与 DSL 重复(DSL 完全能表达) | add_inplace、scale_inplace、accumulate |
Layer 不直调(一律 dsl::compute_into 等);engine 版本仅作适配层转发 / 内部 lowering 与测试目标(zero 属基础设施,见 §2.1) |
| ② DSL 已有对应 IR 叶子 | matmul/matmul_with_bias/batched_matmul、row_reduce_sum、col_reduce_sum、col_reduce_max、grouped_reduce_sum/grouped_reduce_max、row_gather、row_mod、row_access、rotate_half、batch_mod、batch_col、select |
DSL 自带;engine 版本仅供 lowering |
| ③ DSL 表达不了(真·不可约) | scan_prefix_outer/scan_suffix_outer/outer_col(RLA/RAPT)、im2col/col2im(CNN 卷积/池化窗口展开)、offload_save/offload_restore/create_offload_buffer、begin_batch/end_batch/flush_batch、slice_rows/insert_rows/rearrange_3d/transpose、gather_rows/scatter_add_rows、create_tensor/from_matrix/to_matrix/clone/cast/copy_from |
必须保留为层可见原语 |
L1 层可见 : dsl::compute / compute_reduce + §7.2 ③ 的不可约原语
L2 内部 : eager 算子 = DSL 的 lowering 目标(不再对层公开)
L3 后端 : SIMD / 线程池 / GLSL kernel
判据一句话:凡是能被 DSL 表达的,就不该再作为层的公开算子存在。
代价:
- 每次直调 = 一次 kernel dispatch(GPU)/ 一次 Tensor 物化——这正是 DSL 被造出来要消灭的东西
(对比
eval_expr一次 dispatch 完成整条链)。 - 与 DSL 重复的逐元素 eager 路径若保留,就会在 CPU 上造成多套求值机制并存——重复部分 已清空:当前 CPU 求值只剩 §3 的两套,Layer 直调的 38 个全属基础设施 / 数据搬运 / 状态扫描 / 不可约原语 / IR-C 录制段开合。
好处(必须承认):
- eager 曾是闭合世界的逃生舱(运行期 JIT 兜底落地后,主路径 miss 走现编、不再硬报错)。GPU 的 DSL 未命中缓存时同样走 JIT(
libnn-kernelgen现编 SPIR-V,.nnk缓存命中直接派发),已无严格模式开关; 而 eager 走手写 shader(engine/engine/shaders/*.comp),不需要运行期枚举。 所以 §7.2 ③ 那批结构原语留在 eager 侧是设计上的必需,不是历史包袱。
- 输出缓冲 / 原地语义:已具备——
dsl::compute_into(eng, expr, dst)(expr_dsl.hpp), optimizer / 残差加法 / LayerNorm 梯度累加 / CE 梯度均已迁移(CPU 走eval_into_span模板内联,GPU 走run_fused_gpu的output_override)。 - 跨表达式融合:已由 IR-C 落地——
begin_expr/end_expr+expr_graph.hpp图融合 (写穿多输出,Adam::step/AdamW::step已接入,见 §4.2);不需要写穿的表达式仍可写成 单个dsl::compute(取舍与定位修正见03-ir-optimization.md§5.3)。 - CPU lowering(当前唯一缺口)——逐元素路径已向量化+并行(机制 1),但含归约/matmul 的表达式仍走串行解释器(机制 2,实测比等价原语慢 1.5–3.8 倍,结论见 §3)。
方向对(DSL 确实是 IR 优化能力所在),目标表述为:
"DSL 作为唯一前端 + 一个封闭的不可约原语集合;eager 算子降为内部 lowering 目标。"
- 若坚持"eager 算子继续公开",等于保留两套逐元素系统,DSL 的融合收益会在层边界漏掉; 这时的"保留 DSL + eager"只是把当前状态合法化,不是最优。
- 当前状态与剩余工程项:§7.2 ① ② 的计算类原语已全部退出 Layer 直调(Layer 只写 DSL, 见 §2.1),不可约原语按设计保留为层可见;剩余工程项是 §7.5 第 3 条的 CPU lowering。
决策前草案(原 §8.1–§8.10:自动作用域 + 逃逸检测 + 自动 flush)与其后的删除记录作为历史见 docs/history/history.md。定位修正(非否决,系当年实现
不完整)后已恢复并通用化:现行形态 = 显式录制 API(engine.begin_expr()/end_expr() +
ExprSegment RAII 守卫)承载图级融合,P1(链融合)+ P2(写穿多输出)已落地,
Adam::step/AdamW::step 为首个生产调用方(见 §4.2 与 03-ir-optimization.md §5.3)。
# 一键复现本文 §2 的两个数字(virtual 数 / Layer 直调集合 / 未直调集合),
# 并输出第 [4] 节 L2+ 分层审计(铁律 #12 门禁:L2-VIOLATIONS 必须为 0):
pwsh -File bench\doc_inventory.ps1