aboutsummaryrefslogtreecommitdiff
path: root/app/backends
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-25 01:47:32 -0400
committerhistoria <historiavg@proton.me>2026-08-25 01:47:32 -0400
commita76896ec7ce60baaefcf5dd8062776be01dbc57a (patch)
tree126985294645ab412cc2bae72a64539663222e7b /app/backends
parentfe4b2b9eb7fb8aac81f65630720c9079d0a3121a (diff)
downloadtts-audiobook-generator-a76896ec7ce60baaefcf5dd8062776be01dbc57a.tar.gz
fix: automatic patch for top-k.cu to file ggml build error
Diffstat (limited to 'app/backends')
-rwxr-xr-xapp/backends/audiocpp.py109
-rw-r--r--app/backends/patches/ggml-top-k-cuda-iterator.patch10
2 files changed, 113 insertions, 6 deletions
diff --git a/app/backends/audiocpp.py b/app/backends/audiocpp.py
index 7a8eb75..9cb8e24 100755
--- a/app/backends/audiocpp.py
+++ b/app/backends/audiocpp.py
@@ -95,6 +95,12 @@ TASK_VDES = "vdes"
AUDIOCPP_DIR_NAME = "audio.cpp"
AUDIOCPP_GIT_URL = "https://github.com/0xShug0/audio.cpp"
+# ggml build patches shipped in this repo and applied to the (gitignored)
+# audio.cpp checkout before building, so a fresh clone survives known ggml
+# build bugs the audio.cpp fork has not re-vendored yet. See
+# apply_ggml_patches() below.
+PATCH_DIR = Path(__file__).resolve().parent / "patches"
+
# Sentinel returned by tui.confirm (via its cancel_value) when the user
# presses Esc on an overwrite prompt to go back to the wav-directory browser
# instead of aborting the wizard.
@@ -1358,18 +1364,24 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser
audiocpp_dir = find_local_checkout()
if audiocpp_dir is None:
target = APP_DIR / AUDIOCPP_DIR_NAME
- rc = taskview.run_steps(stdscr, "Clone audio.cpp", [taskview.TaskStep(
- f"Cloning audio.cpp into {target}",
- lambda emit, cancel: common.git_clone(
- AUDIOCPP_GIT_URL, target, emit=emit, cancel=cancel))])
+ rc = taskview.run_steps(stdscr, "Clone audio.cpp", [
+ taskview.TaskStep(
+ f"Cloning audio.cpp into {target}",
+ lambda emit, cancel: common.git_clone(
+ AUDIOCPP_GIT_URL, target, emit=emit, cancel=cancel)),
+ taskview.TaskStep(
+ "Apply ggml build patches",
+ lambda emit, cancel: apply_ggml_patches(
+ target, emit=emit, cancel=cancel)),
+ ])
if rc == 130:
# Cancelled from the task view: abort the wizard quietly.
return None
if rc != 0:
raise _TuiError(
- f"git clone failed (exit {rc}). Clone "
+ f"audio.cpp setup step failed (exit {rc}). Clone "
f"audio.cpp manually: git clone "
- f"{AUDIOCPP_GIT_URL} {target}")
+ f"{AUDIOCPP_GIT_URL} {target}, then re-run")
audiocpp_dir = target
resolve_checkout(audiocpp_dir)
first = _after_families()
@@ -1818,6 +1830,75 @@ def find_build_script(audiocpp_dir: Path) -> Optional[Path]:
return candidates[0] if candidates else None
+# Each entry pairs a shipped patch with the vendored file it touches and a
+# regex marker proving the fix is already present (so the patch is skipped
+# idempotently once applied, or once the fork re-vendors a fixed ggml).
+GGML_PATCHES = [
+ {
+ "file": "ggml-top-k-cuda-iterator.patch",
+ "target": "external/ggml/src/ggml-cuda/top-k.cu",
+ "marker": r"#\s*include\s*<cuda/iterator>",
+ "label": "top-k.cu: add #include <cuda/iterator> (CCCL 3.x build fix)",
+ },
+]
+
+
+def apply_ggml_patches(audiocpp_dir: Path, *, emit=None, cancel=None) -> int:
+ """Apply the shipped ggml build patches to an audio.cpp checkout.
+
+ Idempotent: a patch whose marker already matches its target is skipped
+ (it is either already applied, or the fork re-vendored a fixed ggml). A
+ patch that no longer applies because the vendored file changed shape is a
+ loud, non-interactive failure — the build is aborted so the user
+ re-evaluates the patch instead of hitting a known nvcc break minutes
+ later. Returns 0 when every patch is applied or already present, 1 on
+ drift, 130 when cancelled.
+ """
+ for patch in GGML_PATCHES:
+ if cancel is not None and cancel.is_set():
+ return 130
+ target = audiocpp_dir / patch["target"]
+ if not target.is_file():
+ print(f"[INFO] {patch['file']}: target {patch['target']} not "
+ f"present in this checkout; skipping")
+ continue
+ try:
+ text = target.read_text(encoding="utf-8", errors="ignore")
+ except OSError as exc:
+ print(f"[WARNING] {patch['file']}: could not read {target}: "
+ f"{exc}; skipping")
+ continue
+ if re.search(patch["marker"], text):
+ print(f"[OK] {patch['file']}: fix already present, skipping")
+ continue
+ patch_path = PATCH_DIR / patch["file"]
+ if not patch_path.is_file():
+ print(f"[ERROR] {patch['file']}: patch file not found at "
+ f"{patch_path}; cannot apply")
+ return 1
+ check_argv = ["git", "-C", str(audiocpp_dir), "apply", "--check",
+ "--whitespace=nowarn", str(patch_path)]
+ check_rc = common.run_console_subprocess(
+ check_argv, emit=emit, cancel=cancel)
+ if check_rc != 0:
+ print(f"[ERROR] {patch['file']}: no longer applies to "
+ f"{patch['target']} (git apply --check exit {check_rc}). "
+ f"The audio.cpp fork's vendored ggml changed shape and "
+ f"still lacks the fix. Re-evaluate {patch_path}: "
+ f"regenerate the patch, or drop this entry if the fork "
+ f"now ships the fix.")
+ return 1
+ apply_argv = ["git", "-C", str(audiocpp_dir), "apply",
+ "--whitespace=nowarn", str(patch_path)]
+ rc = common.run_console_subprocess(
+ apply_argv, emit=emit, cancel=cancel)
+ if rc != 0:
+ print(f"[ERROR] {patch['file']}: git apply failed (exit {rc})")
+ return rc
+ print(f"[OK] {patch['file']}: applied ({patch['label']})")
+ return 0
+
+
def build_audiocpp(audiocpp_dir: Path, backend: str, *,
emit=None, cancel=None) -> int:
"""Build audiocpp_server for BACKEND, streaming output.
@@ -1843,6 +1924,15 @@ def build_audiocpp(audiocpp_dir: Path, backend: str, *,
if emit is not None:
common.record_post_tui_notice(message)
return 1
+ patch_rc = apply_ggml_patches(audiocpp_dir, emit=emit, cancel=cancel)
+ if patch_rc != 0:
+ message = ("[ERROR] ggml build patches could not be applied; "
+ "aborting audiocpp_server build. See the messages above "
+ "and re-evaluate app/backends/patches/.")
+ print(message)
+ if emit is not None:
+ common.record_post_tui_notice(message)
+ return patch_rc
argv = ["sh", str(script), "--backend", backend, "--target",
"audiocpp_server"]
command = f"cd {audiocpp_dir} && {shlex.join(argv)}"
@@ -2159,6 +2249,13 @@ def _collect_from_flags(args: argparse.Namespace,
if rc != 0:
parser.error(f"git clone failed (exit {rc}); clone audio.cpp "
f"manually: git clone {AUDIOCPP_GIT_URL} {target}")
+ patch_rc = apply_ggml_patches(target)
+ if patch_rc != 0:
+ parser.error(
+ f"ggml build patches could not be applied to {target} "
+ f"(exit {patch_rc}); see messages above. The audio.cpp "
+ f"fork's vendored ggml may have changed — re-evaluate "
+ f"app/backends/patches/.")
audiocpp_dir = target
if audiocpp_dir is None:
parser.error(
diff --git a/app/backends/patches/ggml-top-k-cuda-iterator.patch b/app/backends/patches/ggml-top-k-cuda-iterator.patch
new file mode 100644
index 0000000..0eb89a5
--- /dev/null
+++ b/app/backends/patches/ggml-top-k-cuda-iterator.patch
@@ -0,0 +1,10 @@
+--- a/external/ggml/src/ggml-cuda/top-k.cu
++++ b/external/ggml/src/ggml-cuda/top-k.cu
+@@ -4,6 +4,7 @@
+ #ifdef GGML_CUDA_USE_CUB
+ # include <cub/cub.cuh>
+ # if (CCCL_MAJOR_VERSION >= 3 && CCCL_MINOR_VERSION >= 2)
+ # define CUB_TOP_K_AVAILABLE
++# include <cuda/iterator>
+ using namespace cub;
+ # endif // CCCL_MAJOR_VERSION >= 3 && CCCL_MINOR_VERSION >= 2