代码结构与构建系统
1. 仓库目录布局
s2.cpp 是个”单仓库自包含”项目,除了 ggml 子模块外几乎不依赖外部代码:
s2.cpp/
├── CMakeLists.txt 构建入口(见本章第 3 节)
├── cmake/
│ └── apply_local_patches.cmake 补丁幂等应用脚本
├── patches/ 对 ggml 的两个本地 CUDA 修复
├── src/ 13 个 .cpp,约 7960 行核心实现
├── include/ 14 个公开头文件
├── third_party/
│ ├── json.hpp nlohmann/json(分词器)
│ ├── httplib.h cpp-httplib(HTTP 服务)
│ ├── dr_wav.h / dr_mp3.h 音频解码
│ └── filesystem.hpp C++17 之前的 filesystem 垫片
├── ggml/ git 子模块:ggml-org/ggml
├── tokenizer.json Qwen3 BPE 分词器定义
├── quantize/
│ └── unified_export_gguf.py 离线 GGUF 导出脚本(唯一的 Python)
├── openapi/
│ └── s2-openapi.yaml HTTP 接口规范
├── examples/{python,csharp,golang} 导出库调用示例
└── README.md / LICENSE.md
src/ 下 11 个核心源文件正好就是 CMake 里 S2_CORE_SOURCES 的清单(CMakeLists.txt:80-92),加上 CLI 入口 main.cpp 和导出层 s2_export_api.cpp。每个模块的依赖方向是单向的:
main.cpp / s2_server.cpp / s2_export_api.cpp
└──► Pipeline ──► {Tokenizer, SlowARModel, AudioCodec}
└──► generate() ──► {sampler, prompt}
2. 五个构建选项
全部构建开关定义在 CMakeLists.txt:12-16:
| 选项 | 默认 | 作用 |
|---|---|---|
S2_VULKAN | OFF | 启用 Vulkan 后端 |
S2_CUDA | OFF | 启用 CUDA 后端 |
S2_METAL | OFF | 启用 Metal 后端(仅 Apple,否则 FATAL_ERROR,:18-20) |
S2_BUILD_SHARED_LIBRARIES | OFF | 额外构建 s2_shared / s2_static |
S2_AUTO_APPLY_LOCAL_PATCHES | ON | 配置时自动给 ggml 打补丁 |
典型构建:
cmake -B build -DS2_CUDA=ON -DS2_BUILD_SHARED_LIBRARIES=ON
cmake --build build -j
# 产物:build/s2(CLI)、build/libs2.{so,dylib,dll}、build/libs2_static.a
3. CMake 如何编排 ggml
ggml 以 add_subdirectory(ggml) 直接编入(CMakeLists.txt:70),不找系统安装的版本。为此 CMake 做了三件约束性的事:
- 关掉 ggml 自带的测试和示例,避免拖慢构建、引入多余目标(:29-30);
- 强制后端缓存变量:无论用户环境里缓存了什么,
S2_CUDA等选项都以FORCE翻译成GGML_CUDA(:54-68),保证 s2 的开关是唯一事实来源; - 补丁先于编译:自定义目标
s2_apply_local_patches被挂为ggml、ggml-base、ggml-cpu、ggml-cuda、ggml-vulkan、ggml-metal各目标的依赖(:72-78),存在的目标才挂,保证补丁在任何 ggml 编译动作之前执行。
目标配置统一收口在函数 s2_configure_target()(CMakeLists.txt:94-121):头文件搜索路径包含 include/、third_party/ 以及 ggml 的内部目录(因为 codec 用到了 conv 等内部算子);链接 ggml;按后端加 GGML_USE_* 编译定义;Linux 上补 pthread m,Windows 上补 Winsock。
产物目标
| 目标 | 类型 | 备注 |
|---|---|---|
s2 | 可执行文件 | 11 核心源 + main.cpp,:123 |
s2_shared | 共享库 | OUTPUT_NAME s2,加 S2_LIBRARY 定义,:135-138 |
s2_static | 静态库 | 加 S2_LIBRARY S2_STATIC,:143-146 |
两个库目标比 CLI 多编入 s2_export_api.cpp(:130-133)。另一个 MSVC 专属兼容处理在 :25-27:强制把 MATH_LIBRARY 置空,防止某些 ggml 版本把 MATH_LIBRARY-NOTFOUND 写进 VS 链接器输入。
4. 为什么需要给 ggml 打补丁
patches/ 里有两个补丁,都针对 CUDA 上大维度卷积 的 kernel 启动缺陷——而 codec 的波形解码器恰好重度使用 conv-transpose-1d 和深度可分离 conv。上游 ggml 当时的 CUDA kernel 用固定 grid 且 block 数/输出索引使用 32 位整数,在 44.1 kHz、长音频的输出尺寸下会算错或溢出。
补丁应用脚本 apply_local_patches.cmake 保证幂等:对每个 patches/*.patch 先做 dry-run 正向检查,能干净打上就 apply;否则做 反向检查,若反向能打说明”已经打过”,跳过;两者都不行才 FATAL_ERROR。补丁工具在 Windows 上用 git apply、其他平台用 patch(CMakeLists.txt:34-38)。
补丁一:conv-transpose-1d
ggml-conv-transpose-1d-grid-fix-loop-fix.patch 重写 CUDA 转置卷积 1D kernel:
- 输出尺寸改用 int64 计算,block 数上限钳到 65535;
- 每个 block 用 grid-stride loop 处理多个输出位置,不再假设一个 block 只对应一个元素;
- 修正输入窗口
start_i / end_i的边界计算,与 CPU 实现对齐。
补丁二:depthwise conv2d
ggml-conv2d-dw-grid-fix.patch 对深度可分离 conv2d 做同类修复:grid-stride loop + block 数 65535 上限,解决大特征图下输出被截断的问题。
这类”钉一个上游版本 + configure 时补本地 patch”的做法,比直接 fork ggml 更易维护:上游可以正常升级,补丁冲突时在构建期就显式暴露,而不是带着错误静默运行。
5. 第三方库全部以源码内嵌
除 ggml 外,s2.cpp 不引入任何包管理器依赖,第三方库均为单头文件或单目录:
| 依赖 | 形态 | 用途 |
|---|---|---|
| nlohmann/json | third_party/json.hpp | 解析 tokenizer.json |
| cpp-httplib | third_party/httplib.h | HTTP 服务,无需 boost/asio |
| dr_libs | third_party/dr_wav.h、dr_mp3.h | WAV/MP3 参考音频读取 |
| ghc::filesystem | third_party/filesystem.hpp | 声音文件目录操作 |
这也是”构建完零外部运行时依赖”的工程基础:最终的 s2 二进制只动态链接系统库和(可选的)后端 SDK。
6. GGUF 从哪来:quantize/ 导出脚本
模型权重不手工制作,而由 unified_export_gguf.py 从官方 checkpoint 一次性导出:
- 架构字符串固定写入
fish-speech(DUAL_AR_GGUF_ARCH),引擎据此判断模型带 Fast-AR 解码器(s2_model.cpp:247-251); - 源 checkpoint 的
model_type必须是fish_qwen3_omni; - 张量名重映射(
remap_checkpoint_key)剥掉前缀,Fast-AR 相关张量统一加fast_,唯独codebook_embeddings例外; - 输出 dtype 仅支持 f16/f32;codec 张量保留
c.前缀,与引擎里 tprefix 逻辑对应(见第 08 章)。
7. 小结
构建系统的设计目标非常明确:一次 cmake 配置,产出零外部依赖的引擎,且对 ggml 的修改显式、幂等、可升级。理解了构建之后,第 04 章进入运行时的第一件大事——一个 GGUF 文件是如何被打开、元数据如何变成 hparams、权重张量如何被放到 CPU/GPU 的后端 buffer 上。
关键文件
| 文件 | 职责 |
|---|---|
| CMakeLists.txt | 全部构建逻辑 |
| cmake/apply_local_patches.cmake | 补丁幂等应用 |
| patches/ggml-conv-transpose-1d-grid-fix-loop-fix.patch | CUDA 转置卷积修复 |
| patches/ggml-conv2d-dw-grid-fix.patch | CUDA 深度卷积修复 |
| quantize/unified_export_gguf.py | 离线 GGUF 导出 |