Save future agents (and humans) from re-deriving the repo's non-obvious constraints. If you plan to touch the build system, the ggml dependency, or the CPU/GPU backend wiring, read this first.
The project pins ggml v0.19.0 (cmake/Dependencies.cmake, URL + URL_HASH)
and builds the CI CPU package with GGML_NATIVE=ON (captures the runner
CPU — that is why that job is never cached). This is load-bearing:
- v0.20.x turned
GGML_CPU_ALL_VARIANTSinto dlopen MODULE plugins (GGML_BACKEND_DL), and DL mode does not link the CPU backend into the ggml umbrella target. - Our
src/backend.cppcalls the CPU backend API directly (ggml_backend_cpu_init,ggml_backend_cpu_set_n_threads,ggml_threadpool_new,ggml_backend_cpu_set_threadpool). With DL those becomeundefined referencelink errors. GGML_NATIVEandGGML_BACKEND_DLare mutually exclusive upstream (ggml-cpu CMakeFATAL_ERROR).
Therefore: do not bump the ggml tag to v0.20.x, and do not flip the
CI CPU job to the DL/ALL_VARIANTS portable baseline, until backend.cpp is
refactored to load the CPU backend via GGML_BACKEND_DL. Only after that
refactor is the gate eligible for review.
When the gate eventually lifts, re-verify:
- Both in-repo patches apply to the new tag (
git apply --checkagainst the fetched ggml source). - The CUDA arch list still passes ggml's CMake.
src/backend.cppggml_version_string()matches the new tag.- README/README_CN dependency table matches (it drifted before — see #4).
src/backend.cpp calls ggml CPU backend API directly
(ggml_backend_cpu_init, ggml_threadpool_new, ...). This couples us to a
build where the CPU backend is PUBLIC-linked into the ggml umbrella target
(non-DL). That is the root cause of pitfall #1. Any change that turns the
CPU backend into a dlopen module must come with the backend.cpp refactor.
Two ggml patches live in cmake/patches/ and are re-applied by
cmake/Dependencies.cmake game_ggml_apply_patch (idempotent; fails if the
diff stops applying):
ggml-metal-binary-archive.patch— Metal first-run <1s (MTLBinaryArchive PSO cache). Pairs withGGML_METAL_EMBED_LIBRARY OFF.ggml-vulkan-pipeline-cache.patch— Vulkan cold-start PSO persistence (disk-backedVkPipelineCache).
They are project-owned forks of upstream, not part of ggml main. Do not
delete them; keep their .md baseline per anchor tag.
Both READMEs previously said ggml v0.11.0 while the pin was v0.19.0 (then
v0.20.2). Always cross-check cmake/Dependencies.cmake against the docs;
never trust the README version row by itself. Keep both README dependency
tables in sync with the actual pin.
GGML_NATIVEadds-march=native(GCC/Clang) or MSVCFindSIMD.- It does NOT flip ggml's hand-written SIMD kernels — those key off
GGML_AVX*/GGML_AVX512*options. Real AVX-512 use requires enabling e.g.-DGGML_AVX512=ON -DGGML_AVX512_VNNI=ON. GGML_LLAMAFILE(project optionGAME_GGML_LLAMAFILE, default ON) routes CPUmul_matthrough llamafilesgemm(tinyBLAS) for Q8_0/F32/BF16 on AVX2+. Trade-off: it changes the FP summation order → CPU bit-exactness tests must be re-run when toggled, and the CI cpu job validates the combo.
The CI CUDA arch list 75;80;86;89;90;120-virtual requires CUDA 12.8+ for
Blackwell (sm_100/sm_120) targets; compute_120 PTX gives RTX 50-series
JIT. Older toolkits can't compile 120-virtual — trim the list rather than
"fixing" by touching NATIVE (the CUDA job already correctly uses
GGML_NATIVE=OFF; unrelated).
- CI
linux-x64-cpuisGGML_NATIVE=ON(runner-ISA capture) and must never be restored from the_depscache — runners rotate CPU generations and a stale cache ships Illegal-instruction binaries. The cache step already skips this job; keep it that way. macos-x64-metalcross-compile setsGGML_NATIVE=OFF— otherwise the CPU backend detects the ARM host (apple-m1) and fails.- NVCC/VS version coupling on Windows is documented in BUILDING.md; don't modernize blindly.
--nsteps > 1 engages DBCache (cross-step tail reuse). It is near-lossless
on purpose; the device-side decision metric exists specifically to avoid
host round-trips on GPU. Changing the threshold/defaults/robustness knobs
silently changes note output — measure frame-level metrics, not just note
count, before "improving" it. On Vulkan, Q8_0 can flip a boundary note vs
F32; bit-consistent users use F32.
src/mel.cpp uses its own small C++ thread pool + batched pocketfft; CPU
graph compute uses a persistent ggml threadpool (hybrid polling). They are
independent. Keep the mel pool ≤ 8 threads and guard by frame count (short
clips shouldn't spawn threads).
The git metadata of a checkout may live in a different directory than the
worktree (linked worktree; git rev-parse --git-dir finds it). Git index
writes (branch/stash/commit) write to that directory, not the worktree. If a
git write fails with "Permission denied" on index.lock, resolve the actual
git dir with git rev-parse --git-dir (--absolute-git-dir for a full path),
and it's almost always the harness sandbox — escalate once with
sandbox_permissions: danger-full-access + justification; do not retry in a
loop. Never hard-code a local absolute path in committed docs.
scripts/convert_pt_to_gguf.py, the quant config, and the
benchmark/alignment scripts back CI's prepare-model and reproducible
benches. Deleting them breaks release packaging. If a branch PR shows
script deletions, they are probably an accident unless the PR message says
otherwise.
- Prefer explicit
-D...=ON/OFFat configure over env/CMakeCachesurgery; re-define a FetchContent dependency → deletebuild/and reconfigure. - Any change to
cmake/Dependencies.cmake,cmake/patches/, or CI flags gets reviewed against the pitfalls above before pushing. - When in doubt, rebase a small branch onto
origin/mainand let CI (and CodeRabbit) review the exact delta — that is what this file is for.