Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
51d7256
submodule: engine → d2dd5d1(kernel_cache 宏族同步)
EthanPeng-2048 Nov 2, 2026
b28bfd7
refactor: 移除主仓与 compute 子仓重复的代码——删 shaders/(23 个 .comp 与 engine 完全相同且…
EthanPeng-2048 Nov 2, 2026
e08c927
build(P0): 统一 BMI 与目标编译选项——NN_COMMON_COMPILE_OPTIONS 单一真源 + nn_add_mo…
EthanPeng-2048 Nov 5, 2026
9cd86a9
refactor(P1): 模块构建无条件化——删 NN_ENABLE_MODULES/NN_MODULES_ENABLED 编译器门与 …
EthanPeng-2048 Nov 5, 2026
a8169ec
refactor(P2): 宏岛头落地——engine 抽象层新增 nn_error_macros.hpp(宏段唯一真源),core_er…
EthanPeng-2048 Nov 5, 2026
cd92ab1
feat(p3): JIT 预热接线(D5)+ AOT 收集链主仓侧清理 + 回归锁与妥协台账(docs/internal/35 P3)
EthanPeng-2048 Nov 5, 2026
7dba9fc
feat(p4): 粒度模块铺至 13 域(35 号 P4 验收门 E)
EthanPeng-2048 Nov 5, 2026
7676e73
refactor(P5): src/ 全量 import 化(D8 + 门 F)+ ccache×模块根因修复 + clang 24 bu…
EthanPeng-2048 Nov 5, 2026
dbbf594
refactor(P6): 收尾——BMI 真实编译依赖修复 + 门禁固化 + 性能对照 + 文档同步(35 号 P6)
EthanPeng-2048 Nov 6, 2026
1688320
chore(T9): 台账更新——C3/C5 已消灭 + C1 调查进展记录 + AGENTS IR-C 口径同步
EthanPeng-2048 Nov 6, 2026
b730a3e
docs(T9): C1-调查进展补充——关键精度谜题(K3 录制时 m/v 报告 F32 而 K1 占位 F16)
EthanPeng-2048 Nov 6, 2026
4921bc6
fix(build): Windows + clang-GNU 构建修复(CRT 宏/BMI 镜像/测试 STL include)
EthanPeng-2048 Nov 7, 2026
a9607e8
docs(T9): C1 收窄为 850M 设备问题——40HX 复现 full-f16 全绿
EthanPeng-2048 Nov 7, 2026
38b4fb6
docs: AGENTS 补 Windows + clang-GNU + GPU/模块构建修复记录(40HX 实测,主仓 4921bc6 …
EthanPeng-2048 Nov 7, 2026
ec43c26
fix(C1): Adam/AdamW 状态改 compute_into——消灭融合吞持久输出(full-f16 NaN 根因)
EthanPeng-2048 Nov 8, 2026
02cde95
docs(C1): 教训 §2.6/模式 I + 台账 C1/C2/C4 消灭记录 + C6 最小化修正
EthanPeng-2048 Nov 8, 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
25 changes: 17 additions & 8 deletions AGENTS.md

Large diffs are not rendered by default.

645 changes: 498 additions & 147 deletions CMakeLists.txt

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 0 additions & 18 deletions cmake/nn_pch_stub.cpp

This file was deleted.

22 changes: 20 additions & 2 deletions docs/contributors/pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 保留属性 |
Expand Down Expand Up @@ -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. 高危:内存爆炸/崩溃/断言
Expand Down Expand Up @@ -305,7 +317,7 @@

## 6. 根因模式(跨领域总结)

> 上面 30+ 个坑收敛到 **7 个根因模式**。新代码评审时对照此表自查。
> 上面 30+ 个坑收敛到 **8 个根因模式**。新代码评审时对照此表自查。

### 模式 A:同一数据多份表示,不变量未集中维护
GPU 影子一致性、Tensor reshape 视图、模型格式版本默认值、`.tokcache` 缓存——都因"两份数据 + 分散的同步点"出错。
Expand Down Expand Up @@ -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. 开发活动防坑清单
Expand Down Expand Up @@ -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 打破
Expand Down
91 changes: 43 additions & 48 deletions docs/development/01-cmake-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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、不编本仓应用
Expand Down Expand Up @@ -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=<n>` 覆盖,`=1` 回串行)
1. `CMAKE_CXX_SCAN_FOR_MODULES OFF`:本仓 C++ 模块走**显式接线**(见 8.1「C++ 模块」小节),
不开 CMake 模块扫描 → 只有 13 个模块单元参与,其余 ~270 个 TU 零扫描;
2. `scan_exprs` 的 glslc **并行**(环境变量 `NN_SCAN_JOBS=<n>` 覆盖,`=1` 回串行)
+ `fused_registry.hpp` 内容稳定写入(内容不变则不动 mtime,靠 `restat` 掐断下游重编);
4. 编译器缓存探测扩到 ccache / sccache(可用 `-DNN_COMPILER_LAUNCHER=<path>` 指定)。
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=<path>` 指定),
但**模块消费方一律绕开 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 <path>/neuralnet.cpp/nn.hpp)`。

> ⚠ 两条不变量:① 收集器目标(`scan_exprs`、`*_nnfusion_collect`)**不套 PCH**;
> ② 新写「生成物参与 PCH」的场景必须显式 `add_dependencies(<pch_target>, <生成边>)`。
> 历史归因(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 <neuralnet.cpp/…>` 由 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 <nn.hpp>` 文本
路径不依赖模块);模块/门禁只作用于本仓 src/ 目标(`NN_BUILD_APPS` / `NN_ENABLE_TESTS`)。

---

Expand Down
Loading
Loading