代码结构与构建系统

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 做了三件约束性的事:

  1. 关掉 ggml 自带的测试和示例,避免拖慢构建、引入多余目标(:29-30);
  2. 强制后端缓存变量:无论用户环境里缓存了什么,S2_CUDA 等选项都以 FORCE 翻译成 GGML_CUDA(:54-68),保证 s2 的开关是唯一事实来源;
  3. 补丁先于编译:自定义目标 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 导出

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