feat(frank-tts-engine): synthesize on the gfx1151 iGPU via Vulkan

The NPU path is a dead end for this payload (verdict:
report/tts-npu-spike-verdict-2026-10-19.md); the iGPU is the accelerator that
exists on this box today, and it is already shared with frank-dragoon.

- Containerfile.engine: -DGGML_VULKAN=ON, plus the three packages the door
  needs to configure — libvulkan-dev, spirv-headers, and glslc, which Ubuntu's
  glslang-tools does NOT ship (find_package(Vulkan COMPONENTS glslc REQUIRED)
  fails without it) — and libvulkan1 + mesa-vulkan-drivers (the RADV ICD) at
  runtime. No NVIDIA ICD on purpose: one Vulkan device in the enumeration is
  what makes the NAME Vulkan0 safe to write down.
- tts-engine-start.sh: QWENTTS_DEVICE -> --device, default Vulkan0. A value
  that is set but EMPTY passes no flag at all, which is the CPU rollback; that
  is why the default is written as an -v test and not with :=, because := also
  fires on an empty value and would silently undo the rollback.
- quadlet: AddDevice=/dev/dri + GroupAdd=keep-groups (a rootless container that
  drops its supplementary groups enumerates no render device). The node is not
  named: this host is hybrid, renderD128 is the NVIDIA card and renderD129 the
  amdgpu iGPU.
- env: QWENTTS_DEVICE=Vulkan0 plus the device law in the header, including the
  rollback line.
- tests: the two CPU-only assertions now assert the Vulkan shape, and a new one
  pins the rollback (empty device => no --device in argv). 451/451 green.

Measured before writing any of it: the configure in the digest-pinned base
answers "Found Vulkan: ... found components: glslc" and "Including Vulkan
backend"; gate 1 (RADV GFX1151 visible to a rootless container off this base)
is recorded in the verdict report.
This commit is contained in:
Frank
2026-10-19 14:26:58 -07:00
parent 7601edfe28
commit 25df9d05a2
5 changed files with 179 additions and 38 deletions
+40 -7
View File
@@ -331,13 +331,40 @@ FAKE
[ "$(argv --port)" = "8880" ]
[ "$(argv --lang)" = "auto" ]
[ "$(argv --max-batch)" = "1" ]
# The two flags that must never appear: there is no thread count to set (it
# comes from hardware_concurrency()/2), and naming a device on an image with
# every GPU backend compiled out is only a new way to be wrong.
! grep -qx -- '--device' "$ARGS_FILE"
# The device the env names has to reach argv BY NAME: the image ships the
# Mesa ICD and no NVIDIA one, so Vulkan0 is the gfx1151 and cannot drift onto
# another card the way an index would.
[ "$(argv --device)" = "Vulkan0" ]
# The flag that must never appear: there is no thread count to set in this
# build (it comes from hardware_concurrency()/2), so naming one would only
# disagree with what the binary sized its pools from.
! grep -qE -- '^--?t$|^--threads$' "$ARGS_FILE"
}
@test "an empty device value is the CPU rollback: no --device reaches argv" {
# The same entrypoint with one key cleared, which is the rollback the env
# header documents. A script-level default that survived an explicitly empty
# value would leave the endpoint on the iGPU with no way back except a
# rebuild.
cat > "$TEST_TMP/fake-tts-server" <<'FAKE'
#!/usr/bin/env bash
printf '%s\n' "$@" > "$ARGS_FILE"
FAKE
chmod +x "$TEST_TMP/fake-tts-server"
mkdir -p "$TEST_TMP/models"
: > "$TEST_TMP/models/$TALKER"
: > "$TEST_TMP/models/$CODEC"
export QWENTTS_MODEL="$TEST_TMP/models/$TALKER"
export QWENTTS_CODEC="$TEST_TMP/models/$CODEC"
export QWENTTS_BIN="$TEST_TMP/fake-tts-server"
export ARGS_FILE="$TEST_TMP/argv.txt"
export QWENTTS_DEVICE=""
run bash "$START"
[ "$status" -eq 0 ] || { echo "$output"; false; }
! grep -qx -- '--device' "$ARGS_FILE"
}
@test "missing weights are a refusal that names the fetcher, before the port is bound" {
cat > "$TEST_TMP/fake-tts-server" <<'FAKE'
#!/usr/bin/env bash
@@ -627,17 +654,23 @@ PY
@test "the image build is the pinned commit with the proven cmake line" {
# GGML_BLAS=OFF is forced by the host (libopenblas.so.0 present, no dev
# headers) and upstream's own Dockerfile passes it ON, so this file is not a
# copy of upstream on purpose. -j 8 and the OFF-for-every-GPU-backend list
# are what the 2026-10-18 trial ran.
# copy of upstream on purpose. -j 8 is what the 2026-10-18 trial ran;
# GGML_VULKAN=ON is the 2026-10-19 iGPU cutover, and the two greps below are
# the packages that door needs at build time and at run time.
grep -qE '^ARG QWENTTS_COMMIT=[0-9a-f]{40}$' "$CF"
grep -qF '5916bf7c79dbb4e66fc059cdc507a79a23a28bb4' "$CF"
grep -qE 'git -C /src checkout --detach' "$CF"
grep -qE 'submodule update --init' "$CF" # ggml is a submodule; without it the build cannot configure
for flag in -DGGML_CCACHE=OFF -DGGML_BACKEND_DL=OFF -DGGML_CPU_ALL_VARIANTS=OFF \
-DGGML_OPENMP=ON -DGGML_BLAS=OFF -DGGML_CUDA=OFF -DGGML_HIP=OFF \
-DGGML_VULKAN=OFF -DGGML_METAL=OFF -DGGML_SYCL=OFF -DGGML_RPC=OFF; do
-DGGML_VULKAN=ON -DGGML_METAL=OFF -DGGML_SYCL=OFF -DGGML_RPC=OFF; do
grep -qF -- "$flag" "$CF" || { echo "build line lost $flag"; false; }
done
# glslc is not in glslang-tools on this distro, and the configure fails
# without it; an image with the loader and no RADV ICD enumerates zero
# devices and dies in device lookup after the weights load.
grep -qE 'libvulkan-dev glslc spirv-headers' "$CF"
grep -qE 'libvulkan1 mesa-vulkan-drivers' "$CF"
grep -qE 'cmake --build build -j 8' "$CF"
# OpenMP on means the runtime stage needs the OpenMP runtime, and the probe
# runs as /bin/sh -c, so the runtime stage needs curl.