代码结构、构建与 CI

1. 为什么用 blade-build

nuthatch 用 blade-build 构建——这本身是个决定。blade 是仓库作者自己的构建系统,拿这个新项目dogfood 它,一举两得:既检验 blade 在一个真实 C++ 项目(带第三方依赖、跨平台、多目标)上的体验,又给 blade 攒真实反馈(docs/blade-feedback.md)。

blade 的目标声明很简洁。一个库 + 它的测试长这样:

cc_library(
    name = 'kv_cache',
    srcs = ['kv_cache.cc'],
    hdrs = ['kv_cache.h'],
    deps = [':olmoe_model', '//thirdparty/ggml:ggml'],
    visibility = ['PUBLIC'],
)
cc_test(name = 'kv_cache_test', srcs = ['kv_cache_test.cc'], deps = [':kv_cache', ...])

cc_test_config 自动注入 gtest_main;check_undefined_severity='error' 会在链接期抓出”用了却没声明的依赖”——这一条后来救了几次场(见下)。

2. 经 vcpkg 引入 ggml

三个第三方依赖全走 vcpkg,且都引入前先讨论过:ggml(推理内核)、gtest(测试)、pcre2(分词器正则)。BLADE_ROOTvcpkg_config 钉了 baseline commit,thirdparty/ 下是薄封装:

# thirdparty/ggml/BUILD —— 把 vcpkg 的 ggml 暴露成一个 blade target
cc_library(name = 'ggml', hdrs = [], deps = ['vcpkg#ggml:ggml-cpu', 'vcpkg#ggml:ggml-base', 'vcpkg#ggml:ggml'], visibility = ['PUBLIC'])

这里踩过一个坑:umbrella 的 libggml.a 是空的,符号在 libggml-base.a / libggml-cpu.a 里,漏了就链接失败。blade 的 check_undefined 把它当警告抓到了,而 CI 的链接器却更宽松——于是把严重度提到 error,让这类问题在本地就暴露。

3. 模块布局

src/
  util/       version
  core/       ggml 冒烟(最早验证 ggml 能编能链)
  gguf/       GGUF 元数据读取器
  io/         定位读:pread / Windows ReadFile,跨平台
  moe/        MoE 融合专家张量布局
  trace/      路由 trace 二进制 + 文本读写
  cache/      cache_policy · lru · os_page · learned_pin · trace_sweep/replay
  tokenizer/  byte-level BPE:解码 + PCRE2 预分词 + 编码
  model/      引擎主体(见第 5、10 章)
data/         跨架构验证工件(granite-moe 路由 trace)
docs/         ROADMAP · 依赖账本 · blade 反馈 · 跨架构提取法 · 本分析
tools/        跨平台工具(如 Windows I/O 冒烟,MSVC 单独编)

依赖只向”更基础”的方向连:model 依赖 io/gguf/trace/cache,反之不成立。

4. 每步一个 PR

一条硬约束贯穿始终:每个步骤一个独立 PR,带 CI workflow + 单测 + 详细说明。 32 个 PR 从脚手架一路到跨架构验证,每个都能独立 review。这不是形式主义——它逼着每一步都”可验证、可回退、可讲清楚为什么”,也让整个仓库天然成了一份按学习顺序排列的推理引擎教程。

5. CI 是真防线

CI 在 ubuntu-latest + macos-latest 上跑 blade test //...(Windows 见第 3 章的单独 I/O job)。它反复证明了自己的价值——Linux gcc 的 -Werror 抓到了好几个 macOS clang 放过的跨平台坑:

  • INFINITY 在 Linux 上要显式 #include <cmath>(macOS 间接引入了);
  • std::memcpy#include <cstring>;
  • for (const std::string& x : {"a", "b"}) 每次迭代从 const char* 构造临时量,触发 -Werror=range-loop-construct(改成 for (const char*))。

这些都是”在我机器上好好的”的经典陷阱。一个诚实的多平台 CI,比任何本地自测都靠谱。

下一章:读 GGUF 与定位读


This site uses Just the Docs, a documentation theme for Jekyll.