Skip to content

Latest commit

 

History

History
274 lines (204 loc) · 17.2 KB

CONTRIBUTING.md

File metadata and controls

274 lines (204 loc) · 17.2 KB

参与本项目(给非作者的入口说明)

本文写给不是本仓作者的人:想用库、报 bug、提 PR、加层/加原语、写文档、或把一份实验结果放进 research/。 只回答「我怎么参与」。项目自身的开发规范(14 条铁律、分层依赖、门禁口径、踩坑)在 AGENTS.md 与 docs/contributors/cpp-standards.md; 文档分册定位(哪篇写给谁看)在 docs/README.md。


0. 先读哪篇

你想做什么 从这里开始
用库写自己的模型/训练 docs/development/02-cpp-api-manual.md、docs/usage/
把库嵌进自己的 CMake 工程 docs/development/01-cmake-integration.md、docs/usage/05-consume-as-library.md
写自己的层 docs/development/03-write-a-custom-layer.md
扩展引擎原语 / 改 GPU 后端 docs/development/04-compute-engine-development.md + AGENTS.md §7
改本仓的代码 AGENTS.md(先读 §5 铁律与 §10 高频坑)
看"下一步该做什么" docs/internal/18-roadmap.md
提发布 / 定版本号 docs/contributors/release-policy.md(版本号必须由人提出,agent 不得擅自发布;dev 不写版本号,发布只在 main)+ docs/contributors/release-requirements.md
看"为什么当初这么改" docs/history/history.md(全仓唯一历史归档)

1. 环境与首次构建

需要 CMake ≥ 3.30、Ninja、一个支持 C++26 的编译器(CI 用 Clang;GCC 15 / MSVC 19.51 也验证过)。 Vulkan SDK(含 glslc)可选:找到就自动启用 GPU 后端,找不到就是纯 CPU 构建。 装了 ccache 会自动启用(详见 §5)。

# 最省事:用仓库自带的 preset(CMakePresets.json)
cmake --preset release      # Release + -march=native,纯跑应用
cmake --build --preset release

# 本仓日常开发(Release + 全部测试)
cmake --preset dev && cmake --build --preset dev && ctest --preset dev

# 也可以一条命令走完「配置 + 构建 + 测试」
cmake --workflow --preset dev

cmake --list-presets         # 看全部 preset:release/dev/debug/portable/cpu/probes

不想用 preset 也行,等价于:

cmake -B build -G Ninja && cmake --build build            # 默认 Release
cmake -B build -G Ninja -DNN_ENABLE_TESTS=ON && cmake --build build && ctest --test-dir build

常用开关(完整列表见 docs/development/01-cmake-integration.md):

开关 默认 用途
NN_ENABLE_NATIVE ON -march=native。分发/换机请设 OFF(否则可能 SIGILL)
NN_ENABLE_TESTS OFF 编测试与 gradcheck(在 build/test/ 下,注册 ctest)
NN_ENABLE_PROBES OFF 诊断探针(热路径扫描/归因 + mem_probe 等)。⚠ 会改变库头语义,不可与正常构建混用缓存/二进制
NN_ENABLE_GPU AUTO AUTO/ON(缺 Vulkan 直接配置失败)/OFF(纯 CPU 最短路径)
NN_BUILD_APPS 顶层=ON 嵌入本库当你自己工程的一部分时自动为 OFF

2. 跑测试

ctest --preset dev              # 或 ctest --test-dir build --output-on-failure
ctest --test-dir build -R gradcheck   # 只跑匹配的

本地必须跑:CI 目前只做「构建 + 打包可执行文件」(见 §6),不跑 ctest—— 那是 docs/internal/18-roadmap.md 里 P0-1 的待办。所以测试是你自己的门禁。

改到下面这些区域时,请把对应专项测试一起跑(都在 ctest 里):

改动区域 至少跑
引擎原语 / 张量 *_test(含 f16_precision_test、write_epoch_test)
训练/反向 各 gradcheck*、*_train_test
GPU 后端 / DSL 融合 带 --gpu 的变体(见 docs/internal/12-compute-engine-inventory.md 的清单)
三值 / BitLinear bitnet_struct_test、ternary_*、adam_quant_test
序列化 / 数据集 train_state_test、model_roles_test、kvrec_test、dataset_test、nnvocab_test、dataset_integration_test
基础件 / 并行 error_macro_test、assert_test、file_test、sha256_test、precision_type_test、threadpool_test、parallel_test、tokenizer_test
# GPU 测试要在能看见 /dev/nvidia* 的环境里跑(容器/沙箱内会静默退回 llvmpipe 软件光栅)
NN_VULKAN_DEVICE=850M ./build/test/bitnet_struct_test --gpu     # 显式选卡

3. 代码规范(怎么不被 review 打回)

3.1 先记住这 5 条(其余见 AGENTS.md §5 的 14 条铁律)

  1. 不许 throw / try / catch(-fno-exceptions 强制)。错误一律 Result<T> = std::expected<T, Error>, 手写错误检查用宏族:传播 NN_TRY/NN_TRY_MSG/NN_TRY_CHECK、终止 NN_UNWRAP/NN_EXIT、源头 NN_FAIL。 前置条件校验用 NN_ASSERT_ALWAYS / NN_FATAL(NDEBUG 下同样生效)。
  2. 不许 new/delete/裸指针所有权:std::vector / std::unique_ptr / std::span。
  3. 不穿透分层:Matrix(L1) 不写算法、Layer(L2) 不写底层计算、shader 里不写 ReLU/Softmax 这类算法。
  4. 张量存储不许绕过引擎:建/读/写/reshape 一律走 ComputeEngine(Tensor 的存储访问器是私有的, 绕过去编译不过)。
  5. 改动要可复现:涉及数值/性能的改动,请在 PR 描述里给出实测数字与跑法(本仓历史提交都这么做, 见 docs/history/history.md)。

3.2 代码风格

风格规则写在 docs/contributors/cpp-standards.md §8 (命名 / 缩进 4 空格 / include 顺序 / 注释语言),机器可读版本是同目录的 .clang-format。

# ✅ 推荐:只格式化**你这次改动**的行
git clang-format                 # 对暂存区相对 HEAD 的改动
git clang-format --diff          # 只看会改什么

# 单文件(新文件随意;旧文件整体跑通常已无 diff)
clang-format -i path/to/new_file.hpp

全仓已于 2026-10-31 按 .clang-format 统一格式化(提交 5854b98), 现有代码即基线;新增/改动代码用上面的增量方式保持一致即可。请不要在功能 PR 里顺手格式化无关文件。 缩进/行尾/编码这类字符级约定由 .editorconfig 与 .gitattributes 统一(全仓 LF)。

3.3 提交信息

沿用本仓既有写法:一行中文标题(可用「范围:结论」形式)+ 需要时补正文解释成因与验证方式。

修复合并引入的视图种类硬编码:nn::expr::trans() 写死 15

合并时 HeadGroup / Transpose 各占 15 → Transpose 顺延为 16,但 `nn::expr::trans()`
仍返回 {15, 0, stride} → 静默构造出 HeadGroup 视图。症状:...
改为 static_cast<std::uint8_t>(ExprViewKind::Transpose) 并加注释。
实测(...):修前 32/40(8 挂),修后见下一次全量 ctest。

要点:说清"为什么"和"怎么验证的";改了行为/数字就给出前后对比;没验证的部分明说没验证。


4. 提 PR

  1. Fork(或在本仓开分支)。分支模型:

    分支 定位 流转
    main 主分支:只合并其他分支或直接更新文档,不直接开发 发布基线,tag 打在这里
    dev 集成分支:hotfix + new feature 的汇合点;从不定档版本号(版本号只在经审核合入 main 时确定) 定期合入 main
    feature/<feature_name> 功能分支:只实现一个功能 完成后合入 dev,随即删除
    hotfix/<bug_name> 热修复分支:只修复一个 bug 完成后合入 dev,随即删除

    其他历史分支命名(feat/、fix/、docs/ 等)已废止。文档类改动可直接在 main 提 PR。

    版本号与发布规则(正文与真源表见 docs/contributors/release-policy.md): ① 版本号格式 v1.<API 变化>.<漏洞修复>(位定义见 docs/release-notes/README.md §1),版本权威 = git tag; ② dev 及其上的功能分支从不写版本号——不 bump project(VERSION ...)、不建 release note 册、不打 tag;定档与发布只发生在 main; ③ 发布版本号必须由人提出并明确给出,agent(AI 编码助手)不得自行决定版本号或执行发布动作(git tag / gh release 等)。

  2. 一次 PR 一件事。大改动请先在 issue/讨论里对齐设计——本仓有「先写设计稿进 docs/internal/, 裁定后再实施」的习惯(docs/internal/ 就是项目开发者的工作区)。

  3. 不再拆分子仓库(2026-11-01 治理裁定)。此前拆出的 core / threadpool / kvrecord / dataset / tokenizer 五个子仓已合并回本仓单仓库(头文件在 include/neuralnet.cpp/、 测试在 src/),根因是嵌套 submodule 依赖导致"B 被存好几遍"、一次底层改动要在 多仓级联 bump。如未来确需再拆,前置条件是先出现真实的外部独立消费方,且在 issue 里对齐供货方式(不再用嵌套 submodule)后再动手。

  4. 提交前自查:

    cmake --preset dev && cmake --build --preset dev && ctest --preset dev   # 测试必须绿(当前口径:无 Vulkan 62 项 / 开 GPU 64 项)
    python bench/gui_cli_audit.py         # 改了 gui.py / cli_controllers.py / 任一 CLI 选项后必跑(七节:声明式字段表 ↔ Controller 闭包、断点续训禁列、多轮对话闭环;当前 exit 0)
    python bench/gui_draw_logic_audit.py    # 改了画板绘制路径后必跑(桩替换 customtkinter,无需 GUI)
    python bench/gui_draw_runtime_audit.py  # 同上(真机:功能闭环 + 拖动开销对照;无显示时 77 = SKIP)
    python scripts/audit_modern_style.py  # 现代 C++ 风格(裸指针 / new / delete / malloc);⚠ 见下
    git clang-format --diff               # 风格:只格式化**你改动的行**
    ./build/layer_bench --op scan_prefix --gpu --batch 8 --heads 4 --dk 32 --seq 128 --hash
                                          # 改了状态扫描原语(或任何 GPU kernel)后必跑:配对 A/B 的 ms +
                                          # 位级哈希(逐位不变的改写必须哈希相同)。改完只需 `ninja layer_bench`
                                          # (PCH + 1 个 TU ≈2 min),全量重建留到最后一次

    ⚠ audit_modern_style.py 对本仓当前是 FAIL(59 E / 147 W)——E 几乎全部落在 Vulkan C API 边界(reinterpret_cast<VkBaseInStructure*> 的 pNext 串链、void* mapped、std::free)与一处 刻意的进程级单例 new(compute_vk_backend.hpp 的 static GpuBackend* backend)。所以它是 基线对照工具:看你改动的那几行有没有新增 E,别指望整仓绿。要让它成为可执行门禁,得先给 这些 C 边界补 // nn-allow: <原因> 豁免(脚本支持该机制,当前全仓 0 处豁免)——那是独立的一批改动, 不在功能 PR 里顺手做。 改了文档必跑 python scripts/doc_audit.py(文档 ↔ 代码对齐 + 文档规范审计,跨平台; --report 另生成代码示例人工复核清单)。Windows 侧另有 pwsh -File bench/doc_inventory.ps1 (引擎接口盘点 + L2 分层门禁,需要 PowerShell 7),改了引擎接口时跑。

  5. PR 描述里写:动机 → 改了什么 → 怎么验证的 → 影响面/兼容性(模型文件格式、CLI 参数、 数值结果是否有变化)。破坏性变更请附迁移说明。


5. 关于构建速度(ccache 是本仓的默认路径)

本仓是 header-only,一次全量 Release + 测试构建在本机 8 核上约 13 分钟(大头是 LLVM 后端优化, 单个 TU 约 30s)。仓库已经做了这些事,你不需要额外配置:

  • PCH:nn.hpp 只预编译一次,被 40+ 个目标共享(NN_ENABLE_PCH,默认开)。
  • ccache 自动启用:顶层构建会自己探测 ccache/sccache;装了就用。 仓库会把 ccache 需要的 PCH 兼容设置(sloppiness=pch_defines,time_macros + -fno-pch-timestamp)钉进编译命令,因此"用了 PCH 的 TU"也能命中缓存—— 二次构建(含 ninja -t clean 之后)本机实测 58.8s(全量 13m26s → 58.8s),剩余时间是 shader 生成与链接、不是编译。
  • 想指定/禁用它:-DNN_COMPILER_LAUNCHER=<path>(空串 = 禁用)。

日常改一个文件的循环:cmake --build --preset dev 会自动只重编受影响的目标; 只是想验证某个改动不影响别处时,优先跑对应测试而不是整仓重建。


6. CI 会做什么

.github/workflows/cmake-single-platform.yml:

  • Linux + Windows,Clang,Release,NN_ENABLE_NATIVE=OFF(可移植基线),带 Vulkan SDK(GPU 编译);
  • -DNN_ENABLE_TESTS=ON 构建并真跑 ctest(需 Vulkan 的用例在无 GPU 的 runner 上按退出码 77 记 skip,非失败);
  • 设备域检查是默认入口检查(v3 起每个操作数必须属于本引擎域),不再需要绑定调试开关的第二轮 ctest;
  • 构建 8 个应用入口(mnist_train/infer、text_train/infer、tokenizer_train/infer、dataset_gen、dataset_convert)并打包成 .7z 产物;
  • push 到 main 时创建/更新 nightly 预发布。

本地复现 CI 门禁:ctest --preset dev(应全绿;需 Vulkan 的用例无设备时按 77 记 skip)。

大版本发布(版本号 1.x.y → 1.(x+1).0)在 CI 门禁之上还有一份必做清单——端到端模型训练测试 (这类测试不进默认 ctest)、发布文档、完整 ctest + Debug 构建、多编译器 × 标准库矩阵—— 见 docs/contributors/release-requirements.md。


7. 文档贡献

规范正文在 docs/contributors/documentation-standard.md (分册职责 / 编号制度 / 单篇模板 / 代码示例标注 / 历史性记载禁令 / 审查口径),写任何文档前先读。 要点速览:

目录 读者 收什么
docs/introduction/ docs/usage/ 调用者 总览、教程(编号 = 阅读顺序,连续不留洞)
docs/development/ 调用者(扩展库的人) 接入、API 手册、写自定义层、精度契约
docs/contributors/ 贡献者 / 维护者 / agent 长期规范与治理(无编号主题名)
docs/internal/ 项目开发者 设计稿、路线图、盘点、故障、整改(编号 = 稳定 ID,新增续最大号)
docs/history/history.md 项目开发者 全仓唯一历史归档(演进/勘误/被否决方案/A-B 数字)
  • 头部模板强制:每篇必须有 > **读者**: / > **一句话结论**: / > **状态**: (词表:现行 / 冻结 / TODO / 转发页 / 已归档)。
  • 历史性记载禁令:history/ 之外(含代码注释)不得写"xxx 已在 commit xxx 修复"式叙事, 一律改写为当前事实。
  • 登记义务:新增/删除文档必须同步分册 README + docs/README.md 两处索引。

改完文档必跑 python scripts/doc_audit.py(文件/链接、CLI 参数、nn:: 符号、头部模板、 索引登记、历史性记载、代码示例清单八类检查;--report 生成代码示例人工复核清单)。


8. 实验与结论(research/)

research/ 是可引用的实测结论归口:一次研究一个子目录(报告 + 脚本/探针 + 汇总数据), 结论要能被人不读源码地引用(给出结论、证据、复现命令、适用边界)。见 research/README.md。 不要把中间日志/权重仓进来(.gitignore 已忽略 *.log / *.bin,报告里给出重跑命令即可)。


9. 数据与第三方内容

代码是 LGPL-3.0(LGPLv3,含 GPL-3.0 条款,见 LICENSE)。但 scripts/ 下的下载脚本拉取的数据集各有自己的许可证 (TinyStories / OpenWebText / 中文 wiki 等),不随本项目 LGPL 分发——请自行遵守其条款, 也不要把数据集或训练产物提交进仓库。


10. 行为准则

参与本项目即表示同意遵守 CODE_OF_CONDUCT.md。

11. 许可证

提交贡献即表示你同意以本项目的 LGPL-3.0 License 授权你的贡献。