Repository navigation
Expand file tree
/
Copy pathCMakeLists.txt
More file actions
1328 lines (1207 loc) · 85.8 KB
/
Copy pathCMakeLists.txt
File metadata and controls
1328 lines (1207 loc) · 85.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# CMake 3.30+ required for C++26 standard support (stable in 3.30+)
cmake_minimum_required(VERSION 3.30)
# ── 编译器与标准库:一律使用默认,不由本文件指定 ──────────────────────────
# 编译器:用 CMake 默认探测结果,或调用方显式 -DCMAKE_CXX_COMPILER / CXX 环境变量。
# 标准库:用编译器默认(Linux clang/gcc 默认 libstdc++)。如需 libc++,由调用方
# 通过 -DCMAKE_CXX_FLAGS / -DCMAKE_EXE_LINKER_FLAGS 统一指定(编译与链接必须一致,
# 否则链接期会出现 std 符号 ABI 不匹配)。
# 本文件不再隐式挑选编译器或 stdlib,避免与调用方/CI 的设置冲突。
# VERSION 以 git tag 为准(当前 v1.12.0;本仓无 CONFIGURE_FILE 消费该字段,
# 它只是元数据——发版时同步到最新 tag,实际版本以 git tag / gh release 为准)
project(neuralnet.cpp VERSION 1.13.0 LANGUAGES CXX)
# ══════════════════════════════════════════════════════════════════════════
# 作为库被消费(add_subdirectory / FetchContent 嵌入 3rd_party)的门控
# ══════════════════════════════════════════════════════════════════════════
# PROJECT_IS_TOP_LEVEL:本文件是顶层工程(正常开发/CI)时 ON;被消费方
# add_subdirectory 嵌入时 OFF。据此默认值翻转,让嵌入方**只拿到库目标**,
# 不被迫编译本仓 12 个 app/探针/基准(可显式 -DNN_BUILD_APPS=ON 打开)。
option(NN_BUILD_APPS "Build neuralnet.cpp application/bench executables"
${PROJECT_IS_TOP_LEVEL})
# ── GPU(Vulkan)开关:消费方在自己工程里一行控制 NN_HAS_VULKAN ───────────
# AUTO(默认)= 探测到 Vulkan+glslc 就启用,否则纯 CPU(本仓历史行为)
# ON = 强制要求,缺 Vulkan 或 glslc 直接配置失败(fail-fast)
# OFF = 完全跳过:不探测 Vulkan、不生成 shader、不编 scan_exprs
# 嵌入方写法:在 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)
if(NOT NN_ENABLE_GPU MATCHES "^(AUTO|ON|OFF)$")
message(FATAL_ERROR "NN_ENABLE_GPU must be AUTO/ON/OFF, got '${NN_ENABLE_GPU}'")
endif()
# ── 本仓源/构建根(CACHE,全局可见)─────────────────────────────────────
# CMake 函数体在**调用方作用域**求值:消费方调用 nn_enable_gpu_fusion 时,
# CMAKE_SOURCE_DIR/CMAKE_BINARY_DIR 会解析到消费方的目录(实测报错/路径错)。
# 因此把本仓的根路径烘焙进 CACHE(INTERNAL 隐含 FORCE,每次配置刷新),
# 函数体内一律引用这两个变量,与调用方是谁无关。
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
"neuralnet.cpp binary root (stable under add_subdirectory)")
# ── C++ 模块:本项目不使用 ────────────────────────────────────────────────
# C++20 起 CMake 默认为每个源文件追加一次 `clang-scan-deps` 依赖扫描
# (build.ninja 里的 *.obj.ddi / CXX.dd / *.obj.modmap 三条边),本项目
# **没有任何 `import`/`export module`**(header-only 库),扫描是纯浪费:
# 实测每个 TU 约 2.1s(占单文件编译时间 ~12%)。关闭后 `.ddi` 边整体消失,
# 编译命令里的 `@*.obj.modmap` 响应文件也不再生成。
# 若将来真的要引入 C++ 模块,删掉这一行即可恢复默认行为。
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
# ── C++26 标准 ──────────────────────────────────────────────────────────
# CMake 4.3 尚未内置 MSVC 的 CXX26 方言标志映射(VS2026 cl 19.51 也不认
# /std:c++26,C++26 特性走 /std:c++latest),故对 MSVC 用 CMAKE_CXX_STANDARD=23
# 让方言探测通过,再显式传 /std:c++latest。Clang/GCC 走标准 CMAKE_CXX_STANDARD=26。
if(MSVC)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
set(CMAKE_CXX_EXTENSIONS OFF)
add_compile_options(/std:c++latest)
else()
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()
# ── 默认 Release 模式,可通过 -DCMAKE_BUILD_TYPE=Debug 切换 ────────────────
# 仅顶层工程时设置:嵌入(add_subdirectory)时不得强改消费方的构建类型。
if(PROJECT_IS_TOP_LEVEL AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
# ── 公共编译标志 ─────────────────────────────────────────────────────────
# 项目规范:禁止异常、开启严格警告。按编译器选择对应标志:
# - Clang/GCC:-fno-exceptions -Wall -Wextra -Wpedantic -Werror
# - MSVC: /W4 /permissive- /Zc:preprocessor(严格一致性);保留 /EHsc
# (ppltasks.h 需要),不强制 /WX(MSVC 与 clang 警告集不同,
# 代码未按 MSVC 零警告编写)
#
# /Zc:preprocessor 是**必需**项,不是加固项(2026-10-10 四编译器矩阵实测):
# cl.exe 默认走传统预处理器,它在把 `__VA_ARGS__` 代入**嵌套宏调用**时
# 会把整个实参表当成**一个**实参(core_errors.hpp 的 NN_DETAIL_PICK2/3 实参
# 计数分发因此全部错位)。后果对两种语义都致命:
# · `NN_UNWRAP(cond, "ctx")` → 错选 NN_UNWRAP_1 → 展开成逗号表达式
# `check_value((cond, "ctx"), …)` → 条件恒为字符串指针(恒真)
# → **检查静默失效且编译通过**(实测:`NN_UNWRAP(false, "x")` 不 abort);
# · `NN_EXIT(cond, code, "ctx")` → 错选 NN_PICK_END → C3861 编译失败。
# 打开 /Zc:preprocessor 后两种形态均按 C++ 标准展开(实测 `NN_UNWRAP(false,…)`
# 正常 abort),本仓 500+ 处宏调用点的语义在 MSVC 下才与 clang/GCC 一致。
# 该开关同时也是**接口**选项(见下方 nn_core),否则库外消费者在 cl.exe 下
# 会拿到失效的 NN_UNWRAP。
if(MSVC)
# ⚠ 字符集只用 **/utf-8 一种写法**(= /source-charset:utf-8 +
# /execution-charset:utf-8)。此前这里写的是那对分开的开关,而
# `neuralnet_cpp` 的 **INTERFACE** 选项又给链接它的目标追加了 /utf-8 →
# 同一条命令行同时出现两者 ⇒ cl 直接报
# `D8016: "/source-charset:utf-8"和"/utf-8"命令行选项不兼容`(实测
# MSVC 19.51 / VS18:凡是链接本库的目标全数编译失败,MSVC 配置整体不可用)。
# 实测(D:\probe.bat):`/utf-8 /utf-8` 与「同一对开关重复两遍」都合法,
# 只有"混用两种写法"才报冲突——故统一成 /utf-8,目录级与接口级重复无害。
# /bigobj:Debug 下 text_train.cpp(header-only 全量展开)实测触发
# C1128「节数超过对象文件格式限制」(P1-5,MSVC 19.51 / VS18,2026-10-18);
# Release 未触发,但该限制与优化级别相关,统一打开以避免配置相关失败。
add_compile_options(/W4 /permissive- /Zc:preprocessor /utf-8 /bigobj)
# /STACK:MSVC 默认 1 MB 栈。Debug 下多个模型/序列化测试实测
# STATUS_STACK_OVERFLOW (0xC00000FD)(P1-5,2026-10-18:t1_58 / bitnet_struct /
# model_roles / batchnorm / model_spec_validation / text_train_modules /
# train_state);Linux 默认 8 MB。统一抬到 8 MB,与 Linux 口径对齐。
add_link_options(/STACK:8388608)
else()
add_compile_options(-fno-exceptions -Wall -Wextra -Wpedantic -Werror)
endif()
# Windows 上的 clang++(GNU-like 驱动)不是 CMake 的 MSVC 分支,但链接仍走
# link.exe,默认栈同样是 1 MB。实测(102 / clang 23.1 + MSVC STL / Release)
# t1_58_test、bitnet_struct_test、model_roles_test 在 ctest 中 SegFault;
# 与 cl.exe 的 0xC00000FD 同源。用 -Wl, 把 /STACK 透传给 link.exe。
if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT MSVC)
add_link_options(-Wl,/STACK:8388608)
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<F16>)无原生 x86 SIMD → 优化器无法向量化是预期行为
# (§7.2 兼容路径:f32 参考计算 + f16 舍入),抑制 -Wpass-failed 避免 -Werror 下编译失败
add_compile_options(-Wno-pass-failed)
endif()
# C++ 标准库由编译器默认决定(见文件开头的说明),此处不再显式指定 -stdlib。
#
# ── 编译器缓存(可选,强烈建议装了就用)──────────────────────────────────
# 自动探测 ccache / sccache(Windows 上 ccache 常缺席、sccache 更常见),也可用
# `-DNN_COMPILER_LAUNCHER=<path>` 显式指定(显式指定为空串 = 禁用)。
# 对本 header-only 工程收益很大:改动只影响单个 TU 时,未变 TU 直接命中缓存。
# ⚠ 调用方若已给 CMake 原生的 `-DCMAKE_CXX_COMPILER_LAUNCHER=...`(CI 就是这么
# 配的),这里**不再重复包一层** RULE_LAUNCH_COMPILE——两套机制叠加会变成
# `ccache ccache clang++`。
# ⚠ RULE_LAUNCH_* 是 **GLOBAL 属性**,会作用于整个构建树——嵌入(add_subdirectory)
# 消费方的构建时**自动探测一律不生效**(避免污染消费方);消费方想要就自己设
# CMAKE_CXX_COMPILER_LAUNCHER,或显式传 -DNN_COMPILER_LAUNCHER=<path>(显式 =
# 调用方主动要求,允许生效于整个构建树)。
set(NN_COMPILER_LAUNCHER "" CACHE STRING
"Compiler launcher prefix (e.g. ccache / sccache); empty = auto-detect (top-level only)")
set(NN_COMPILER_LAUNCHER_EXPLICIT "OFF" CACHE INTERNAL "NN_COMPILER_LAUNCHER passed by caller")
if(NN_COMPILER_LAUNCHER)
set(NN_COMPILER_LAUNCHER_EXPLICIT "ON")
endif()
if(NOT NN_COMPILER_LAUNCHER AND NOT CMAKE_CXX_COMPILER_LAUNCHER AND PROJECT_IS_TOP_LEVEL)
find_program(NN_CCACHE_PROGRAM ccache)
find_program(NN_SCCACHE_PROGRAM sccache)
if(NN_CCACHE_PROGRAM)
set(NN_COMPILER_LAUNCHER "${NN_CCACHE_PROGRAM}")
elseif(NN_SCCACHE_PROGRAM)
set(NN_COMPILER_LAUNCHER "${NN_SCCACHE_PROGRAM}")
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 影响不到它)。
if(NN_COMPILER_LAUNCHER MATCHES "ccache")
# ⚠ 必须写成**单个**字符串:写成两个相邻的带引号参数会变成 CMake 列表
# (分号连接)→ 命令行里被 `sh` 当成第二个命令,实测报
# `-E env ;CCACHE_SLOPPINESS=...`。
set(NN_COMPILER_LAUNCHER "${CMAKE_COMMAND} -E env CCACHE_SLOPPINESS=pch_defines,time_macros ${NN_COMPILER_LAUNCHER}")
endif()
message(STATUS "Compiler launcher: ${NN_COMPILER_LAUNCHER}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${NN_COMPILER_LAUNCHER}")
# 不挂 RULE_LAUNCH_LINK:本项目不做 LTO,ccache 对普通链接只有进程开销与
# "Called for linking" 统计噪声,没有收益。
elseif(CMAKE_CXX_COMPILER_LAUNCHER)
message(STATUS "Compiler launcher: ${CMAKE_CXX_COMPILER_LAUNCHER} "
"(来自 CMAKE_CXX_COMPILER_LAUNCHER,用 CMake 原生机制)")
else()
message(STATUS "Compiler launcher: (none found; install ccache or sccache for faster rebuilds)")
endif()
# ── 按构建类型设置优化标志 ───────────────────────────────────────────────
# Release: -O3 -funroll-loops -march=native(最大性能)
# Debug: -O0 -g(调试优先)
# 注意:不使用 -ffast-math,因为它会禁用 NaN/Infinity 支持,影响训练稳定性
# MSVC 对应 /fp:fast 同样禁用 NaN 语义(实测:base 子仓 precision_type_test 的
# "NaN != NaN" / "NaN 比较均为 false" 断言在 /fp:fast 下被编译器折叠为
# false → 假失败;/fp:precise 下全绿)→ 按同一铁律用 /fp:precise。
# Clang/GCC 用 -O3 + 向量化(自带 -fno-math-errno/-fno-trapping-math)。
#
# -march=native 会让二进制只能在编译机同代 CPU 上运行,分发/换机可能 SIGILL。
# 默认开启(本地追求最大性能);发布/CI 构建请用 -DNN_ENABLE_NATIVE=OFF 生成
# 通用可移植基线(见 .github/workflows 的 Configure 步骤)。
option(NN_ENABLE_NATIVE "Enable -march=native (max perf, non-portable binary)" ON)
if(MSVC)
set(RELEASE_FLAGS /O2 /fp:precise)
set(DEBUG_FLAGS /Od /Zi)
else()
if(NN_ENABLE_NATIVE)
set(RELEASE_FLAGS -O3 -fno-math-errno -fno-trapping-math -funroll-loops -march=native)
else()
set(RELEASE_FLAGS -O3 -fno-math-errno -fno-trapping-math -funroll-loops)
endif()
set(DEBUG_FLAGS -O0 -g)
endif()
add_compile_options(
"$<$<CONFIG:Release>:${RELEASE_FLAGS}>"
"$<$<CONFIG:Debug>:${DEBUG_FLAGS}>"
)
# ── 线程库(Linux 需要 pthread) ─────────────────────────────────────────
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# ── std::stacktrace 运行时库探测 (GCC & Clang) ──────────────────────────
# 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(保住既有行为),嵌入方降级为警告 +
# 留空——否则消费方会为了一个用不到的库在配置期被卡死。
set(NN_STACKTRACE_FATAL ${PROJECT_IS_TOP_LEVEL})
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# ── GCC 探测逻辑 ──────────────────────────────────────────────
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_LIBRARIES stdc++exp)
check_cxx_source_compiles("
#include <stacktrace>
int main() { return std::stacktrace::current().empty() ? 1 : 0; }
" NN_HAS_STDCXXEXP)
unset(CMAKE_REQUIRED_LIBRARIES)
if(NN_HAS_STDCXXEXP)
set(NN_STACKTRACE_LIB stdc++exp)
else()
set(CMAKE_REQUIRED_LIBRARIES stdc++_libbacktrace)
check_cxx_source_compiles("
#include <stacktrace>
int main() { return std::stacktrace::current().empty() ? 1 : 0; }
" NN_HAS_STDCXX_LIBBACKTRACE)
unset(CMAKE_REQUIRED_LIBRARIES)
if(NN_HAS_STDCXX_LIBBACKTRACE)
set(NN_STACKTRACE_LIB stdc++_libbacktrace)
elseif(NN_STACKTRACE_FATAL)
message(FATAL_ERROR "std::stacktrace 需要 stdc++exp 或 stdc++_libbacktrace")
else()
message(WARNING "std::stacktrace 库未找到(嵌入模式降级:仅 tools/scan_exprs 受影响)")
set(NN_STACKTRACE_LIB "")
endif()
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# ── Clang 探测逻辑 ─────────────────────────────────────────────
# Clang 可能链接 libstdc++ 或 libc++。我们通过尝试链接来探测。
# 如果 Clang 使用 libc++ 且其版本尚不支持 stacktrace,则此处会失败。
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_LIBRARIES stdc++exp)
check_cxx_source_compiles("
#include <stacktrace>
int main() { return std::stacktrace::current().empty() ? 1 : 0; }
" NN_CLANG_HAS_STDCXXEXP)
if(NN_CLANG_HAS_STDCXXEXP)
set(NN_STACKTRACE_LIB stdc++exp)
else()
# 回退尝试旧版 libbacktrace 库
set(CMAKE_REQUIRED_LIBRARIES stdc++_libbacktrace)
check_cxx_source_compiles("
#include <stacktrace>
int main() { return std::stacktrace::current().empty() ? 1 : 0; }
" NN_CLANG_HAS_STDCXX_LIBBACKTRACE)
unset(CMAKE_REQUIRED_LIBRARIES)
if(NN_CLANG_HAS_STDCXX_LIBBACKTRACE)
set(NN_STACKTRACE_LIB stdc++_libbacktrace)
else()
# Clang 可能在使用 libc++,其 stacktrace 支持可能不完整。
message(WARNING "Clang 无法链接 std::stacktrace。如果使用 libc++,请确保版本 >= 18。CI 构建可能会失败。")
set(NN_STACKTRACE_LIB "")
endif()
endif()
endif()
if(NN_STACKTRACE_LIB)
message(STATUS "std::stacktrace link library: ${NN_STACKTRACE_LIB}")
else()
message(STATUS "std::stacktrace link library: (none found,可能链接失败)")
endif()
# ── 公共 INTERFACE 库:所有目标共享 include 目录和链接库 ──────────────
# 通过 INTERFACE 库消除 7 个目标的重复设置
add_library(nn_core INTERFACE)
target_include_directories(nn_core INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
# ── 子仓 include path(拆仓集成)────────────────────────────────────────
# core/tokenizer/dataset/kvrecord/threadpool 的头已搬到各自子仓(extern/ 下
# submodule),门面经此把它们的 include/ 加进搜索路径。各子仓头统一在
# <repo>/include/neuralnet.cpp/ 下,文件名不冲突 → #include <neuralnet.cpp/xxx.hpp>
# 在门面子仓间透明解析,下游 include 路径零变化。
foreach(_nn_sub base threadpool kvrecord dataset tokenizer)
target_include_directories(nn_core INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/extern/${_nn_sub}/include>)
endforeach()
target_link_libraries(nn_core INTERFACE Threads::Threads)
if(NN_STACKTRACE_LIB)
target_link_libraries(nn_core INTERFACE ${NN_STACKTRACE_LIB})
endif()
# ── 诊断探针编译门(NN_ENABLE_PROBES,见下方 option;默认关)─────────────
# 定义放在 nn_core 的 **INTERFACE**:库头里的探针门(`nn_dbg_scan`、
# `NN_PREC_TRACE`、`env_flag` 等,core_config.hpp 的 NN_PROBES_ENABLED)在
# **所有 TU 上必须取值一致**,否则同一头文件两种语义(ODR 隐患);随 nn_core
# 传播可同时覆盖库内目标、PCH 目标、收集器与嵌入消费方。
if(NN_ENABLE_PROBES)
target_compile_definitions(nn_core INTERFACE NN_ENABLE_PROBES=1)
endif()
# ── C++ 标准(消费方使用需求,实测于 CMake 4.3)─────────────────────────────
# Clang/GNU(GNU 前端)认识 cxx_std_26 → 传 -std=c++26;
# **MSVC cl 不认识 cxx_std_26**(实测报 "not known to CXX compiler",其
# CMAKE_CXX23_STANDARD_COMPILE_OPTION 就映射为 /std:c++latest = C++26 预览
# 全开,与本文件顶部 MSVC 分支同策略)→ 降级请求 cxx_std_23;
# clang-cl 认识 cxx_std_26(映射 /std:c++latest)→ 走 26。
# 判据用「特征列表实测」而非 if(MSVC),因为 MSVC 变量对 clang-cl 也为真。
if("cxx_std_26" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
target_compile_features(nn_core INTERFACE cxx_std_26)
elseif("cxx_std_23" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
target_compile_features(nn_core INTERFACE cxx_std_23)
endif()
# ── 消费门面目标:下游只写 target_link_libraries(app PRIVATE neuralnet::nn) ──
# 嵌入(add_subdirectory / FetchContent)用法:
# add_subdirectory(3rd_party/neuralnet.cpp)
# target_link_libraries(app PRIVATE neuralnet::nn)
# #include <neuralnet.cpp/nn.hpp>
# 门面 = nn_core(include/标准/线程)+ **编译本库头文件所需**的选项(见下)
# + **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)。
add_library(neuralnet_cpp INTERFACE)
target_link_libraries(neuralnet_cpp INTERFACE nn_core)
# ── 编译本库头文件所需的消费方选项(目录级 add_compile_options 传不过去)──
# 实测缺口:下游 Release 构建库头文件时出现 6 个 `-Wpass-failed` 警告
# (`#pragma clang loop` 向量化未达成),库内靠 -Wno-pass-failed 压住;
# 消费方一旦开 -Werror 就直接编译失败——必须随目标传播。
target_compile_options(neuralnet_cpp INTERFACE
# Clang:库内同款(-fexperimental-library 与 -Wno-pass-failed 都在本文件
# 顶部的 Clang 分支加给库内目标;作用域限定 CXX 避免影响消费方 C 代码)
$<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>:-fexperimental-library>
$<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>:-Wno-pass-failed>
# MSVC:库头文件是 UTF-8 + 大量中文注释/字符串,不指定字符集时 cl 按
# 系统 codepage 读 → C4819/C4828 甚至误解析;/utf-8 = source+execution
# 都按 UTF-8(库内目录级选项**同款写法**,见上方公共标志处的 D8016 实测:
# 目录级与接口级必须同用 /utf-8,混用分开的两个开关会让 cl 直接拒绝命令行)。
# ⚠ 这同时要求消费方源码也是 UTF-8(现代工程默认如此)。
# /Zc:preprocessor 必须随接口传出去(见上方公共标志处的实测说明:
# 传统预处理器会让 NN_UNWRAP/NN_EXIT 的变参分发错位)。
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
# /bigobj 也随接口传:消费方在大 TU 里展开 nn.hpp 同样可能触发 C1128
# (P1-5 实测 text_train.cpp;对 ABI 无影响)。
$<$<CXX_COMPILER_ID:MSVC>:/bigobj>)
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。
# ⚠ **收集器目标不套 PCH**:`scan_exprs.cpp` 是在**源文件内**(include 之前)
# 自行 `#define NN_EXPR_SCAN` 的——PCH 会在该宏生效前把 nn.hpp 预编译进 AST,
# 导致 DSL 收集钩子整体失效(注册表空 → 构建硬失败)。故只有经
# `nn_add_executable` 建的应用/测试目标进入候选。
option(NN_ENABLE_PCH "Precompile nn.hpp once and reuse it across targets" ON)
# ── 便捷函数:统一添加可执行目标 ────────────────────────────────────────
# 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)
endif()
endfunction()
# ── 测试/验证目标开关(默认关闭) ──────────────────────────────────────
# 默认只构建应用与基准工具目标,避免生成大量验证程序;
# 需要时用 -DNN_ENABLE_TESTS=ON 开启全部 gradcheck / *_test / *_probe / *_debug。
option(NN_ENABLE_TESTS "Build test/verification executables" OFF)
# ── 诊断探针开关(默认关闭)─────────────────────────────────────────────
# "探针" = 会在**热路径**采样/打印/下载/扫描的诊断代码 + 只服务于排障的可执行
# 目标。打开后:① 库头里的探针门打开(`nn_dbg_scan`/`NN_F16_DEBUG` 中间量扫描、
# `NN_PREC_TRACE` 变体与边界 cast 归因、`text_train` 的 `NN_NAN_TRACE` /
# `NN_MEM_STATS`);② 创建探针可执行目标(mem_probe / f16_cpu_probe /
# gpu_stability_probe)。
# 关闭(默认)时这些代码**不参与编译**:没有额外分支、没有字符串、没有 host
# 下载路径,相关环境变量一律无效——探针只在排障时按需打开,不允许"设了环境
# 变量就悄悄拖慢正常训练/推理"。运行时**行为开关**(NN_VULKAN_DEVICE 设备选择、
# NN_VULKAN_* 设备/特性逃生阀、NN_POOL_* 池 A/B)不在本门内:它们读取一次并
# 缓存,不产生热路径开销(新增同类开关必须遵守"读一次 + 缓存")。
# ⚠ 打开会改变库头语义 → 探针构建的二进制/缓存与正常构建不可混用;
# 覆盖率/归因类验收(如 docs 里 `NN_PREC_TRACE` 的 V1 覆盖实测)需用本开关配置。
option(NN_ENABLE_PROBES "Compile diagnostic probes (hot-path trace hooks + probe executables)" OFF)
# 收集实际构建的目标,供 GPU 统一启用(避免引用未定义目标)
set(NN_APP_TARGETS "")
set(NN_TEST_TARGETS "")
set(NN_EXECUTABLE_TARGETS "") # 所有经 nn_add_executable 创建的可执行目标
set(NN_BENCH_TARGETS "") # 性能基准已移除(bench 工具不注册 ctest 且无法在 -Werror 下编译)
# ── 应用 / 探针 / 基准目标(NN_BUILD_APPS,默认 = 是否顶层工程)───────────
# 嵌入消费方(add_subdirectory)默认 OFF:只拿库目标(neuralnet::nn),
# 不被迫编译本仓 12 个可执行文件。需要复现应用时显式 -DNN_BUILD_APPS=ON。
# ⚠ f16_writeback_probe 同时是 ctest 用例(见 NN_ENABLE_TESTS 块),故它
# 单独按「任一开关」创建;其余 app 只看 NN_BUILD_APPS。
if(NN_BUILD_APPS)
# ── MNIST 目标(标准构建核心应用) ─────────────────────────────────────
nn_add_executable(mnist_train src/mnist_train.cpp)
nn_add_executable(mnist_infer src/mnist_infer.cpp)
list(APPEND NN_APP_TARGETS mnist_train mnist_infer)
# ── 文本目标(GPT 训练/推理 + BPE 分词器训练/推理,标准构建核心应用) ──
nn_add_executable(text_train src/text_train.cpp)
nn_add_executable(text_infer src/text_infer.cpp)
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_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)
# ── 层吞吐量基准(CPU/Vulkan GPU;--all 或 --layer <name> 指定) ────────
nn_add_executable(layer_bench src/layer_bench.cpp)
list(APPEND NN_APP_TARGETS layer_bench)
# ── 探针目标(诊断专用;需 -DNN_ENABLE_PROBES=ON,默认不构建)───────────
# 这三个只服务排障,不进任何正常流程:正常构建里连可执行文件都不存在,
# 文档里引用它们的复现步骤须先配置 NN_ENABLE_PROBES=ON。
if(NN_ENABLE_PROBES)
# 运行时显存分项探针(双池采样 + nvidia-smi + 逻辑分项归因)
nn_add_executable(mem_probe src/mem_probe.cpp)
list(APPEND NN_APP_TARGETS mem_probe)
# CPU f16 组件级诊断探针(A-I 阶段定位发散;NN_F16_DEBUG=1 开逐中间量扫描)
nn_add_executable(f16_cpu_probe src/f16_cpu_probe.cpp)
list(APPEND NN_APP_TARGETS f16_cpu_probe)
# CPU/GPU 训练 run-to-run 确定性探针(ComputeEngine Refresh P0 / D9 实测;
# 手动运行不注册 ctest,退出码 0=逐位一致 1=非确定 2=运行错误)
nn_add_executable(gpu_stability_probe src/gpu_stability_probe.cpp)
list(APPEND NN_APP_TARGETS gpu_stability_probe)
endif()
endif()
# ── GPU f16 参数写回探针(issue #13 P0-②:MSVC 下权重冻结定位)──────────────
# app 与 ctest 双身份 → 任一开关打开就创建(目标重复创建会报错,故统一在此)。
# ⚠ 刻意**不**并入 NN_ENABLE_PROBES:它是注册在 ctest 里的**回归用例**
# (断言 in-place f16 写回值正确 + 8 步 Adam 后参数必须移动),去掉会削弱
# 测试门禁;它自身不含热路径探针,不参与 NN_ENABLE_PROBES 的语义。
if(NN_BUILD_APPS OR NN_ENABLE_TESTS)
nn_add_executable(f16_writeback_probe src/f16_writeback_probe.cpp)
if(NN_BUILD_APPS)
list(APPEND NN_APP_TARGETS f16_writeback_probe)
endif()
endif()
# ── 测试 / 验证目标 ────────────────────────────────────────────────────
# 构建门 = (NN_BUILD_APPS OR NN_ENABLE_TESTS):默认(顶层工程 NN_BUILD_APPS=ON)
# 就把全部回归可执行文件编出来,方便本地**直接跑**(无需每次重配);
# `-DNN_ENABLE_TESTS=OFF -DNN_BUILD_APPS=OFF` 才是"纯库、零测试产物"。
# (ctest 注册另算,见下方 NN_ENABLE_TESTS 门——只编不注册 = 本地手跑。)
if(NN_BUILD_APPS OR NN_ENABLE_TESTS)
# ══════════════════════════════════════════════════════════════════════
# 测试目标(19 个,按功能域分组合并)
# 合并策略:wrapper .cpp 通过 #define main + #include 聚合原测试源文件
# ══════════════════════════════════════════════════════════════════════
# ── GPU 集成测试(需要 Vulkan)────────────────────────────────────────
nn_add_executable(gpu_test src/gpu_test.cpp)
list(APPEND NN_TEST_TARGETS gpu_test)
# ── GPU f16 参数写回回归(issue #13 P0-②:MSVC 下权重冻结)───────────
# 复用 app 目标 f16_writeback_probe(已在 NN_APP_TARGETS 构建):
# 断言 in-place f16 写回值正确 + 8 步 Adam 后参数必须移动(防"静默冻结")
list(APPEND NN_TEST_TARGETS f16_writeback_probe)
# ── GPU f16 能力检查 + cast/matmul 验证 ──────────────────────────────
nn_add_executable(gpu_f16_test src/gpu_f16_test.cpp)
list(APPEND NN_TEST_TARGETS gpu_f16_test)
# ── 多精度类型系统(f16 转换 / 舍入 / 张量精度 / f16 计算)──────────
nn_add_executable(precision_test src/precision_test.cpp)
list(APPEND NN_TEST_TARGETS precision_test)
# ── 多精度适配层(f16 存储真正生效:PrecisionEngine + DSL + 端到端)──
nn_add_executable(f16_precision_test src/f16_precision_test.cpp)
list(APPEND NN_TEST_TARGETS f16_precision_test)
# ── 三值(T1_58)精度 + BitLinear(层能力校验 / 量化规则 / STE / 端到端)──
nn_add_executable(t1_58_test src/t1_58_test.cpp)
list(APPEND NN_TEST_TARGETS t1_58_test)
# ── BitNet b1.58 2B4T 的结构开关(SubLN / GQA / tied embedding,docs 22)──
nn_add_executable(bitnet_struct_test src/bitnet_struct_test.cpp)
list(APPEND NN_TEST_TARGETS bitnet_struct_test)
# ── 写版本(write epoch)契约 + BitLinear 量化缓存(docs 21 §4.11)──
nn_add_executable(write_epoch_test src/write_epoch_test.cpp)
list(APPEND NN_TEST_TARGETS write_epoch_test)
# ── 三值打包权重 + 量化 GEMM(P2/F3,docs 21 §5 的 P2 验收)──
nn_add_executable(ternary_gemm_test src/ternary_gemm_test.cpp)
list(APPEND NN_TEST_TARGETS ternary_gemm_test)
# ── 模型文件角色(v7:.nnmodel / .nnckpt)+ 参数形态协议 ──
nn_add_executable(model_roles_test src/model_roles_test.cpp)
list(APPEND NN_TEST_TARGETS model_roles_test)
# ── 量化状态优化器 AdamWQuant(F5 第一步,docs 21 §4.13)──
nn_add_executable(adam_quant_test src/adam_quant_test.cpp)
list(APPEND NN_TEST_TARGETS adam_quant_test)
# ── Attention(梯度检查 + forward 一致性 + batch 行号回归)──────────
nn_add_executable(attn_test src/attn_test.cpp)
list(APPEND NN_TEST_TARGETS attn_test)
# ── 基础层数值梯度检查(SwiGLU / RMSNorm / Softmax)─────────────────
nn_add_executable(layer_gradcheck_test src/layer_gradcheck_test.cpp)
list(APPEND NN_TEST_TARGETS layer_gradcheck_test)
# ── BatchNorm(工厂不回落 + 训练/推理双态 fwd/bwd + running EMA
# + Model::set_training 转发 + save/load 往返逐位一致)────────────
nn_add_executable(batchnorm_test src/batchnorm_test.cpp)
list(APPEND NN_TEST_TARGETS batchnorm_test)
# ── CNN(MaxPool2D 独立参考比对 + 缓存/checkpoint 契约 + 端到端冒烟)──
nn_add_executable(cnn_test src/cnn_test.cpp)
list(APPEND NN_TEST_TARGETS cnn_test)
# ── GPT 模型(整链梯度检查 + 梯度检查点一致性)──────────────────────
nn_add_executable(gpt_test src/gpt_test.cpp)
list(APPEND NN_TEST_TARGETS gpt_test)
# ── 采样器(P0-5:top-k / top-p / 重复惩罚 / 种子;默认配置 ≡ 旧路径)──
nn_add_executable(sampling_test src/sampling_test.cpp)
list(APPEND NN_TEST_TARGETS sampling_test)
# ── RLA 扫描原语的**逐位**回归(CPU 参考 ↔ GPU;P4-2b 改写的前提)──────
# 无 GPU 时自身打印 77 = ctest SKIP(与 gpu_test 同约定)。
nn_add_executable(scan_bitwise_test src/scan_bitwise_test.cpp)
list(APPEND NN_TEST_TARGETS scan_bitwise_test)
# ── Activation Offload(原语往返 + GPT/RAPT 模型级一致性)──────────
# 模型级 offload 一致性(GPU-only:无 Vulkan 构建时自身返回 77 = SKIP)
nn_add_executable(gpt_offload_test src/gpt_offload_test.cpp)
list(APPEND NN_TEST_TARGETS gpt_offload_test)
nn_add_executable(rapt_offload_test src/rapt_offload_test.cpp)
list(APPEND NN_TEST_TARGETS rapt_offload_test)
# ── 文档感知注意力(端到端不变性 + 灵敏度/跨样本隔离对照;掩码构建
# 单测已随 build_attention_mask 删除)────────────────────────────────
nn_add_executable(doc_attn_test src/doc_attn_test.cpp)
list(APPEND NN_TEST_TARGETS doc_attn_test)
# ── ZiPT / AttnZip 已移除(保留在 legacy/zipt 分支)──
# 恢复前提见 docs/history/history.md「ZiPT 移除」条。
# ── RAPT / ReLU 线性注意力(梯度 + 冒烟 + 梯度检查点)──────────────
nn_add_executable(rapt_test src/rapt_test.cpp)
list(APPEND NN_TEST_TARGETS rapt_test)
# ── 表达式/IR 纯 CPU(DSL + 归约 + matmul + 优化 pass + 图 IR)────
nn_add_executable(expr_cpu_test src/expr_cpu_test.cpp)
list(APPEND NN_TEST_TARGETS expr_cpu_test)
# ── 图 IR + 融合分析(IR-C,纯 CPU:链融合/边界/确定性/matmul 参与)──
# 历史归档见 docs/history/history.md「IR-C 定位修正与恢复立项」
nn_add_executable(expr_graph_test src/expr_graph_test.cpp)
list(APPEND NN_TEST_TARGETS expr_graph_test)
# ── 表达式/IR GPU(图融合端到端 + AOT shader 验证 + 表达式求值)────
nn_add_executable(expr_gpu_test src/expr_gpu_test.cpp)
list(APPEND NN_TEST_TARGETS expr_gpu_test)
# ── DSL 无形状重载(dsl::compute / compute_reduce 的推导口径,P4-4)──
# 纯 CPU(推理规则与后端无关):推导形状 == 显式形状、逐位值一致、
# 显式 P 透传、负例(冲突 / 无网格贡献者 → 明确报错、不猜形状)
nn_add_executable(dsl_shape_infer_test src/dsl_shape_infer_test.cpp)
list(APPEND NN_TEST_TARGETS dsl_shape_infer_test)
# ── 融合 shader **逐形态** GPU 对拍(rope/swiglu/gelu/softmax/matmul/
# matmul+reduce/norm/reduce_consts/fold v1/fold attn/回退)──────────
# ⚠ 此前该文件**未被任何 CMake 目标引用**(未编译、未注册)——它是融合
# shader 逐形态的回归面,重新接入。
# 覆盖口径:**V0(全 f32)**逐形态对拍(该文件无 f16/精度引用)。
# V1(`key#x` 运行期精度分派)的覆盖在 `f16_precision_test`(NN_PREC_TRACE
# 实测 20 条 #x / 4 条 #a);本测试不覆盖 V1。
nn_add_executable(fused_gpu_test src/fused_gpu_test.cpp)
list(APPEND NN_TEST_TARGETS fused_gpu_test)
# ── ModelSpec 架构校验(load_model 文件头 vs 模型规格比对)──────────
nn_add_executable(model_spec_validation_test src/model_spec_validation_test.cpp)
list(APPEND NN_TEST_TARGETS model_spec_validation_test)
# ── 稀疏交叉熵融合(loss/grad vs 参考,CPU+GPU)─────────────────────
nn_add_executable(ce_fusion_test src/ce_fusion_test.cpp)
list(APPEND NN_TEST_TARGETS ce_fusion_test)
# ── RV-01:前置条件校验(NN_ASSERT_ALWAYS / NN_FATAL)在 NDEBUG 下也终止 ──
# 负例经子进程断言:越界 at / 未绑定 Tensor::engine() / 空 logits 采样
# 必须非零退出、打印 `file:line`、且 tag 是新宏(不是 NDEBUG 下为空的 NN_ASSERT)
nn_add_executable(assert_always_test src/assert_always_test.cpp)
list(APPEND NN_TEST_TARGETS assert_always_test)
# ── 生命周期 v3(设备域 / Layer 状态机 / 引擎先析构路径)────────────
# 覆盖:域模型与跨域拒绝、import 唯一入口、backend weak 缓存回收、
# init 失败回滚、跨引擎重绑拒绝、Tensor 活过引擎。
nn_add_executable(layer_lifetime_test src/layer_lifetime_test.cpp)
list(APPEND NN_TEST_TARGETS layer_lifetime_test)
# ── P0 生命周期契约负例(docs/todo/28 §5 P0)──────────────────────────
# 覆盖:ExprSegment 强引用(录制段内丢外部引擎句柄仍可 end)、
# ActivationOffloader 状态机(未 import 的重复 export = fail-fast;
# discard 作废通道)与精度门(slab 恒 f32 ⇒ 非 F32 激活拒绝)、
# optimizer 先于 Model 析构的正常顺序。
nn_add_executable(lifetime_contract_test src/lifetime_contract_test.cpp)
list(APPEND NN_TEST_TARGETS lifetime_contract_test)
# -- P2a: TrainingSession (Model + Optimizer 同生共死, docs/todo/29) --
nn_add_executable(training_session_test src/training_session_test.cpp)
list(APPEND NN_TEST_TARGETS training_session_test)
# ── RL 公共基础:有序 tensor schema / 具名张量 / 梯度包(docs/todo/31 §3)
nn_add_executable(tensor_schema_test src/tensor_schema_test.cpp)
list(APPEND NN_TEST_TARGETS tensor_schema_test)
# ── RL-0:named outputs / OutputBundleLayer / RowBandPacking / ModelSpec ─
nn_add_executable(rl_output_test src/rl_output_test.cpp)
list(APPEND NN_TEST_TARGETS rl_output_test)
# ── RL-1a:experience schema / GAE / return/group normalization ───────
nn_add_executable(rl_experience_test src/rl_experience_test.cpp)
list(APPEND NN_TEST_TARGETS rl_experience_test)
# ── RL-1b:categorical distribution / objective components / REINFORCE ─
nn_add_executable(rl_policy_test src/rl_policy_test.cpp)
list(APPEND NN_TEST_TARGETS rl_policy_test)
# ── RL-1c:PPO policy/value clip + 标准 preset 组合等价 ────────────────
nn_add_executable(rl_ppo_test src/rl_ppo_test.cpp)
list(APPEND NN_TEST_TARGETS rl_ppo_test)
# ── RL-1d:group advantage + GRPO preset ──────────────────────────────
nn_add_executable(rl_grpo_test src/rl_grpo_test.cpp)
list(APPEND NN_TEST_TARGETS rl_grpo_test)
# ── RL Vulkan smoke:AOT 闭合世界 + GPU 设备域(无卡时 77=SKIP)────────
nn_add_executable(rl_gpu_smoke_test src/rl_gpu_smoke_test.cpp)
list(APPEND NN_TEST_TARGETS rl_gpu_smoke_test)
# ── RL-2a:通用多模型 TrainingGroupSession ────────────────────────────
nn_add_executable(training_group_session_test
src/training_group_session_test.cpp)
list(APPEND NN_TEST_TARGETS training_group_session_test)
# ── RL-2b:rollout/buffer/sampler/metrics/checkpoint 编排 ─────────────
nn_add_executable(rl_pipeline_test src/rl_pipeline_test.cpp)
list(APPEND NN_TEST_TARGETS rl_pipeline_test)
# ── RL end-to-end:named PPO gradients → optimizer update ─────────────
nn_add_executable(rl_training_integration_test
src/rl_training_integration_test.cpp)
list(APPEND NN_TEST_TARGETS rl_training_integration_test)
# ── F1:危险精度组合启动期告警(仅警告,不拒绝;docs/todo/24 §2.3)────
# [1][2] 头文件函数组合矩阵(含 all=f16)单测;[3] 子进程跑 text_train/
# mnist_train,捕获 stderr 断言告警出现 + 退出码不变;二进制缺失 → 77 = SKIP
nn_add_executable(cli_precision_warning_test src/cli_precision_warning_test.cpp)
list(APPEND NN_TEST_TARGETS cli_precision_warning_test)
# ── P1-1:text_train 模块化(recovery / checkpoint 的恢复路径单测)──────
# [1] 断点路径派生;[2] --resume 角色门禁;[3] 断点往返(参数逐位一致);
# [4] 断点记账 + 收尾退出码语义(P1-1 第一刀回归锁);[5] 推理模型角色。
# 全 CPU、秒级、无外部依赖(临时目录自建自清)。
nn_add_executable(text_train_modules_test src/text_train_modules_test.cpp)
list(APPEND NN_TEST_TARGETS text_train_modules_test)
# ── P1-2 + P2-1:v8 训练状态段 + 整文件校验和(role 门禁 / 往返 / 截断)──
# [1] TrainState ⇄ kvrec 往返;[2] Checkpoint 往返(状态 + 优化器张量逐位);
# [3] role 门禁;[4] Model 角色状态段长度为 0;[5] 校验和(翻字节/截断报错);
# [6] load_model 跳过状态段后参数逐位一致;[7] 数据集身份差异说明;
# [8] 剥成 v7 后仍可加载(零破坏)。全 CPU、秒级、临时目录自建自清。
nn_add_executable(train_state_test src/train_state_test.cpp)
list(APPEND NN_TEST_TARGETS train_state_test)
# ── P0-5 尾项:多轮对话闭环(prompt 构建 + CLI --turn/--reply-out 接线)──
# [1] 对话模板结构;[2] ★ 上下文不丢(第 N 轮 prompt 以第 N-1 轮为前缀);
# [3] 助手轮 tokens 原样回灌;[4] 整轮截断(system + 末轮恒保留);
# [5] 纯文本词表回退;[6] 真实 text_infer 子进程端到端(prompt 与库逐 token
# 一致、--reply-out == 生成 token 解码);[7] CLI 负例(--turn 形状/互斥)。
# app 缺失 → 77 = SKIP(CLI 半边跳过)。
nn_add_executable(chat_prompt_test src/chat_prompt_test.cpp)
list(APPEND NN_TEST_TARGETS chat_prompt_test)
# ── P1-2:优化器断点状态(save_state/load_state + 重启轨迹连续)────────
# 覆盖 = kOptimizerTable 全表:状态存在性/非零 + 步计数器;中途存状态→重启续跑
# 与直跑参数**逐位一致**(含冷启动负例做判别力检查);张量数/形状不符报错。
nn_add_executable(optimizer_state_test src/optimizer_state_test.cpp)
list(APPEND NN_TEST_TARGETS optimizer_state_test)
# ── RV-04 优化器元数据表(唯一真源 ↔ 工厂)+ RV-03 同目标写序回归 ──────
# [1] 表内每个 name 都能被 create_optimizer 构造、名单文本与表一致;
# [2] AdamW(wd≠0)单步 vs 手写参考 + 顺序对调的负例;
# [3] 同目标两次写必须建顺序边、不合组,反序时 validate_dst_order 必须报错
nn_add_executable(optimizer_test src/optimizer_test.cpp)
list(APPEND NN_TEST_TARGETS optimizer_test)
# ── P2b:训练样本概念(数据集的训练单位 = 窗口样本;step 是派生量)───────
# [1] 切窗/样本视图;[2] make_batch 逐位约定(PAD/目标/掩码/doc_ids/
# batch-major 扁平化);[3] assistant 掩码按目标位置;[4] 缺 test 子集与
# 非法窗口参数报错;[5] 断点状态段旧键 dataset_windows 兼容读。
nn_add_executable(dataset_samples_test src/dataset_samples_test.cpp)
list(APPEND NN_TEST_TARGETS dataset_samples_test)
# ── P2b:训练工厂 TextTrainer(数据集直接接入 + 训练状态 + 断点)─────────
# [1] create(dataset) 建样本集/词表/数据身份;[2] fit 端到端(步数/参数移动/
# 输出锚);[3] max-steps/start_step;[4] 断点钩子 → TrainState → .nnckpt;
# [5] 前置条件(batch=0 / 裁剪配置不一致)。
nn_add_executable(text_trainer_test src/text_trainer_test.cpp)
list(APPEND NN_TEST_TARGETS text_trainer_test)
endif()
# ── 测试目标统一处理:输出到 <构建根>/test ──────────────────────────────
# 编译门(上面)与注册门(下方 NN_ENABLE_TESTS)解耦,故输出目录在此统一设置。
# 可执行文件位置不硬编码生成器差异:
# - 单配置生成器(Ninja / Make):默认可执行目录为 build 根目录 → build/test
# - 多配置生成器(MSVC):CMake 自动追加配置子目录 → build/test/Release(或 Debug)
# add_test 直接传目标名,CTest 会按当前配置自动解析出真实可执行文件路径。
foreach(TARGET ${NN_TEST_TARGETS})
set_target_properties(${TARGET} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test)
endforeach()
# ── ctest 注册(NN_ENABLE_TESTS 门;编译门见上方 NN_BUILD_APPS 块)──────────
# 注册门与编译门解耦:默认(NN_BUILD_APPS=ON)测试程序**已编译**,但只有
# NN_ENABLE_TESTS=ON 才把 NN_TEST_TARGETS 逐一 add_test —— 用于「本地直接跑、
# 不注册 ctest」的场景。输出目录已在上方统一设为 <构建根>/test。
if(NN_ENABLE_TESTS)
enable_testing()
foreach(TARGET ${NN_TEST_TARGETS})
add_test(NAME ${TARGET} COMMAND ${TARGET})
# 退出码 77 = 依约定"跳过"(如纯 CPU 构建下需要 Vulkan 的测试):
# 记 Skipped 而非 FAILED,否则 CPU-only 构建/CI 出现假红。
set_tests_properties(${TARGET} PROPERTIES SKIP_RETURN_CODE 77)
endforeach()
# CNN 的 GPU 变体:DSL 表达式校验只在 GPU/scan 路径发生(CPU 模板路径不校验),
# 默认的纯 CPU cnn_test 测不出"只在 GPU 触发"的表达式结构回归
# (如 MaxPool2D 裸视图根 → 空指令表)。无 Vulkan 构建下返回 77 = SKIP。
if(TARGET cnn_test)
add_test(NAME cnn_test_gpu COMMAND cnn_test --gpu)
set_tests_properties(cnn_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
# CNN / RAPT 的 f16 端到端变体(P2-3):Conv2D 权重/梯度按 param、融合算子
# 按 compute 走 f16 → 断言"loss 有限且下降 / 梯度无 NaN"。CPU 与 GPU
# 各一条(GPU 才暴露精度变体的闭合世界 miss 与 f16/f32 槽位错配)。
if(TARGET cnn_test)
add_test(NAME cnn_test_f16 COMMAND cnn_test --f16)
add_test(NAME cnn_test_f16_gpu COMMAND cnn_test --f16 --gpu)
set_tests_properties(cnn_test_f16 cnn_test_f16_gpu
PROPERTIES SKIP_RETURN_CODE 77)
endif()
if(TARGET rapt_test)
add_test(NAME rapt_test_f16 COMMAND rapt_test --f16)
add_test(NAME rapt_test_f16_gpu COMMAND rapt_test --f16 --gpu)
set_tests_properties(rapt_test_f16 rapt_test_f16_gpu
PROPERTIES SKIP_RETURN_CODE 77)
endif()
# BatchNorm 的 GPU 变体:推理态/EMA 表达式的 ExprSpec 校验只在 GPU
# 路径发生(CPU 模板路径不校验),且闭合世界 miss 只在 GPU 运行期暴露。
# 无 Vulkan 构建下返回 77 = SKIP。
if(TARGET batchnorm_test)
add_test(NAME batchnorm_test_gpu COMMAND batchnorm_test --gpu)
set_tests_properties(batchnorm_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
# 基准工具已移除(2026 清理:无法在 -Werror 下编译,且无 ctest 覆盖)
# 三值(T1_58)BitLinear 的 GPU 变体:BitLinear 的表达式必须命中 AOT 融合
# 注册表(闭合世界)+ 走一遍 f16 运行期精度分派;纯 CPU 构建/无设备 → 77。
if(TARGET t1_58_test)
add_test(NAME t1_58_test_gpu COMMAND t1_58_test --gpu)
set_tests_properties(t1_58_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
# BitNet 结构开关的 GPU 变体:SubLN 的 norm 表达式 / GQA 的 reshape+gather
# 必须命中 AOT 融合注册表(闭合世界)。无 Vulkan 构建/无设备 → 77。
if(TARGET bitnet_struct_test)
add_test(NAME bitnet_struct_test_gpu COMMAND bitnet_struct_test --gpu)
set_tests_properties(bitnet_struct_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
# 写版本 + BitLinear 量化缓存的 GPU 变体:写版本记在 GpuBuffer 上(别名
# 安全),且缓存命中路径不得绕过 AOT 闭合世界。无 Vulkan 构建/无设备 → 77。
if(TARGET write_epoch_test)
add_test(NAME write_epoch_test_gpu COMMAND write_epoch_test --gpu)
set_tests_properties(write_epoch_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
# 三值打包 GEMM 的 GPU 变体:打包/解码在设备侧(GpuEngine 接打包路径后生效),
# 未接的后端上 BitLinear 自动退回 f16 稠密缓冲(本用例会打印跳过)。无设备 → 77。
if(TARGET ternary_gemm_test)
add_test(NAME ternary_gemm_test_gpu COMMAND ternary_gemm_test --gpu)
set_tests_properties(ternary_gemm_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
# 量化状态优化器的 GPU 变体(状态读写全在设备侧;GPU 上表达式须命中闭合世界)。
if(TARGET adam_quant_test)
add_test(NAME adam_quant_test_gpu COMMAND adam_quant_test --gpu)
set_tests_properties(adam_quant_test_gpu PROPERTIES SKIP_RETURN_CODE 77)
endif()
foreach(TARGET ${NN_BENCH_TARGETS})
set_target_properties(${TARGET} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test)
endforeach()
endif()
# ── Vulkan GPU 加速(NN_ENABLE_GPU = AUTO/ON/OFF,见文件头) ───────────────
if(NN_ENABLE_GPU STREQUAL "OFF")
message(STATUS "Vulkan: disabled (NN_ENABLE_GPU=OFF); pure CPU build")
set(Vulkan_FOUND OFF)
elseif(NN_ENABLE_GPU STREQUAL "ON")
# 强制模式:缺 Vulkan 直接配置失败(fail-fast,不静默降级 CPU)
find_package(Vulkan REQUIRED)
else()
find_package(Vulkan QUIET)
endif()
if(Vulkan_FOUND)
message(STATUS "Vulkan found: ${Vulkan_LIBRARY}")
# 查找 SPIR-V 编译器(Vulkan SDK 自带)
find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE})
if(NOT GLSLC AND NN_ENABLE_GPU STREQUAL "ON")
# ON = 强制模式:缺 glslc 同样 fail-fast(否则 GPU 静默降级 CPU)
message(FATAL_ERROR "NN_ENABLE_GPU=ON but glslc not found. Install the Vulkan SDK "
"or use NN_ENABLE_GPU=AUTO/OFF.")
endif()
# ── 铁律:原语 shader 永不含任何算法 ──────────────────────────────
# 原语 shader 只做 op-level 计算(矩阵乘、逐元素、归约、广播、搬移)。
# ReLU/GeLU/Softmax/LayerNorm/Attention/RoPE/SwiGLU 等算法一律来自
# Layer(组合原语)或 DSL(nn::dsl,见下),绝不写进原语 shader。
set(GPU_SHADERS
matmul
matmul_tiled
batched_matmul
rearrange_3d
elementwise_v2
transpose
gather
scatter_add
im2col
col2im
group_reduce
scan_prefix_outer
scan_suffix_outer
scan_prefix_outer_gen
scan_suffix_outer_gen
outer_col
trit_pack
matmul_q
cast)
# 融合 shader:构建期从 Layer 内联表达式自动合成(**单步**)──────────
# 表达式**文本只出现在 Layer**(compute / compute_reduce / compute_into)。
# scan_exprs 同进程内完成:收集折叠出的 ExprSpec 结构(锚点自登记 + dry-run
# + 模型 pass + 显式登记,按 key 去重)→ glsl_gen → glslc → 内联 SPIR-V
# → fused_registry.hpp(key=expr_spec_key[#x|#a] → 融合 shader)。
# 不经 .bin 中间序列化(原独立工具 gen_fused 已并入 tools/fused_generate.hpp)。
# 运行时按 key 精确匹配 dispatch;未命中硬报错(闭合世界)。
set(GPU_SHADER_HPPS "")
# 便捷函数:glslc → .spv → embed_spirv.cmake → C++ 头文件
# 可选附加 glslc 参数走 ARGN(如 --target-env=...;不传则保持默认,
# 现有 shader 的编译参数完全不变)
# ⚠ 函数体内用 CMAKE_CURRENT_SOURCE_DIR/CMAKE_CURRENT_BINARY_DIR(= 本仓
# 源/构建根,add_subdirectory 嵌入时自动指向 3rd_party 子树),不能用
# CMAKE_SOURCE_DIR/CMAKE_BINARY_DIR(嵌入时会指到消费方工程的根)。
# 本函数只在本文件作用域内被调用,故两变量在定义/调用期同值。
function(nn_embed_shader SHADER_NAME SRC_FILE)
set(SPV ${CMAKE_CURRENT_BINARY_DIR}/${SHADER_NAME}.spv)
set(HPP ${CMAKE_CURRENT_BINARY_DIR}/generated/${SHADER_NAME}_spv.hpp)
set(FUNC "nn_${SHADER_NAME}_spirv_bytecode")
set(GPU_SHADER_SPVS ${GPU_SHADER_SPVS} ${SPV} PARENT_SCOPE)
set(GPU_SHADER_HPPS ${GPU_SHADER_HPPS} ${HPP} PARENT_SCOPE)
# 编译 GLSL → SPIR-V
# -I shaders:手写 shader 的 #include "nn_f16_rne.glsl"(f16 写出的
# RNE 预舍入,见该文件头注释);所有 shader 都依赖它 → 改动时全量重编。
add_custom_command(
OUTPUT ${SPV}
COMMAND ${GLSLC} -I ${CMAKE_CURRENT_SOURCE_DIR}/shaders ${ARGN} -o ${SPV} ${SRC_FILE}
DEPENDS ${SRC_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/shaders/nn_f16_rne.glsl
COMMENT "[GPU] Compiling ${SHADER_NAME}.comp → ${SHADER_NAME}.spv"
)
# 嵌入 SPIR-V → C++ 头文件
add_custom_command(
OUTPUT ${HPP}
COMMAND ${CMAKE_COMMAND}
-DSPV_INPUT=${SPV}
-DHPP_OUTPUT=${HPP}
-DFUNC_NAME=${FUNC}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_spirv.cmake
DEPENDS ${SPV} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_spirv.cmake
COMMENT "[GPU] Embedding ${SHADER_NAME} SPIR-V into C++ header"
)
endfunction()
# 1. 普通(手写)纯原语 shader
foreach(SHADER_NAME ${GPU_SHADERS})
nn_embed_shader(${SHADER_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/shaders/${SHADER_NAME}.comp)
endforeach()
# 1b. 小 N GEMV(matmul_gemv):用 subgroup 归约(GL_KHR_shader_subgroup_*)
# 需 SPIR-V ≥1.3 → 显式 --target-env=vulkan1.2(与 gen_fused 一致);
# 其余 shader 编译参数不变。
nn_embed_shader(matmul_gemv ${CMAKE_CURRENT_SOURCE_DIR}/shaders/matmul_gemv.comp --target-env=vulkan1.2)
# 1b'. 三值打包权重的窄 N GEMV(P2/F3):与 matmul_gemv 同骨(subgroup
# 归约)→ 同样需 SPIR-V ≥1.3 的 --target-env=vulkan1.2。
nn_embed_shader(matmul_q_gemv ${CMAKE_CURRENT_SOURCE_DIR}/shaders/matmul_q_gemv.comp --target-env=vulkan1.2)
# 1c. reduce:行列归约用 subgroup 蝶式(GL_KHR_shader_subgroup_*,与
# DSL 融合归约同配方)→ 同样需 SPIR-V ≥1.3 → --target-env=vulkan1.2
nn_embed_shader(reduce ${CMAKE_CURRENT_SOURCE_DIR}/shaders/reduce.comp --target-env=vulkan1.2)
# 1d. 精度变体(Phase 2 in-kernel f16):**同一份原语 shader** 用
# -DNN_SHADER_F16=1 编出第二份 SPIR-V(缓冲声明 float16_t + 别名槽
# uvec2/unpackHalf2x16,f32 累加,f16 写出)。后端按操作数精度选
# pipeline;设备无 storageBuffer16BitAccess 时不创建(回退 f32 边界
# cast,正确性不变)。f32 分支文本逐字未改 → 零回归。
nn_embed_shader(batched_matmul_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/batched_matmul.comp -DNN_SHADER_F16=1)
nn_embed_shader(matmul_tiled_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/matmul_tiled.comp -DNN_SHADER_F16=1)
# P0 wgrad 网格饥饿:同一份 matmul_tiled 用 -DNN_MM_FINE=1 编 16×16
# 输出块变体(4×4 线程)。后端在 64-tile 网格工作组数低于
# EXPR_MATMUL_FINE_MIN_WGS 时选它(逐输出元素 K 累加序不变 → 逐位一致)。
nn_embed_shader(matmul_tiled_fine ${CMAKE_CURRENT_SOURCE_DIR}/shaders/matmul_tiled.comp -DNN_MM_FINE=1)
# C2e:小 N GEMV 的 f16 变体(n_out≤8 推理热路径;subgroup → vulkan1.2)
nn_embed_shader(matmul_gemv_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/matmul_gemv.comp --target-env=vulkan1.2 -DNN_SHADER_F16=1)
# C2f:RLA 扫描原语的 f16 存储变体(输入输出均 half;输出 (·,seq) 全
# 尺寸——消灭适配层 scan 的全尺寸 f32 副本;scratch St 仍 f32)。
nn_embed_shader(scan_prefix_outer_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/scan_prefix_outer.comp -DNN_SHADER_F16=1)
nn_embed_shader(scan_suffix_outer_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/scan_suffix_outer.comp -DNN_SHADER_F16=1)
# P4-2b:前缀扫描的"窄共享"变体(dk ≤ 32)——同一份源码,只是把共享足迹宏
# 压到 32:A/B 从 33.3KB 降到 8.5KB ⇒ Maxwell 每 SM 驻留的 workgroup 从 1 个
# 提到 6 个。C++ 侧按 dk ≤ 32 选它,缺它时自动回落宽变体(同数值、只是慢)。
nn_embed_shader(scan_prefix_outer_d32 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/scan_prefix_outer.comp -DNN_SCAN_DK_MAX=32)
nn_embed_shader(scan_prefix_outer_d32_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/scan_prefix_outer.comp -DNN_SCAN_DK_MAX=32 -DNN_SHADER_F16=1)
nn_embed_shader(scan_prefix_outer_gen_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/scan_prefix_outer_gen.comp -DNN_SHADER_F16=1)
nn_embed_shader(scan_suffix_outer_gen_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/scan_suffix_outer_gen.comp -DNN_SHADER_F16=1)
nn_embed_shader(outer_col_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/outer_col.comp -DNN_SHADER_F16=1)
# 1e. Phase C1 op-level f16 变体(消灭边界 cast 的搬运大头):
# transpose / rearrange_3d / gather 的 f16 存储版(纯位拷贝零算术)。
# - gather:indices 保持 float(行号整数值 > f16 精度,禁转);
# - transpose:shared float16_t → SPIR-V Float16 capability →
# 运行时要求 shaderFloat16(否则后端跳过该 pipeline,回退边界 cast);
# - rearrange_3d:仅 StorageBuffer16BitAccess。
# 与 matmul 同款:f32 分支逐字未改 → 零回归。
nn_embed_shader(transpose_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/transpose.comp -DNN_SHADER_F16=1)
nn_embed_shader(rearrange_3d_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/rearrange_3d.comp -DNN_SHADER_F16=1)
nn_embed_shader(gather_f16 ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gather.comp -DNN_SHADER_F16=1)
# C1b:add_inplace/scale_inplace/accumulate 的 f16 存储变体
# (归因表 (256,8192)/(64,8192)/(2048,256) inplace 大头;仅需16位存储)