feat(frank-tts-engine): the arm A engine as a boot-persistent quadlet unit
Repo state only: nothing deployed, no host touched, no container started. - `frank-tts-engine` on Dankclaw: `qwentts.cpp` commit 5916bf7c79dbb4e66fc059cdc507a79a23a28bb4, resident `tts-server`, 1.7 B base talker Q4_K_M + Q8_0 tokenizer, container port 8880. It is deliberately NOT a fleet endpoint — `frank-tts` on 7772 is the endpoint consumers address. - Publishes NOTHING. A host-loopback publish would have been useless to the façade (netavark DNATs published ports only, so a bridge container cannot reach a host loopback bind — the shape that 502'd hermes) and would have been the one LAN-reachable surface of an unauthenticated synthesiser. Both voice containers sit on one committed quadlet network, `frank-tts-engine.network` pinned to 10.89.5.0/24 (next free /24 after open-notebook's 10.89.4.0/24), and peers address the engine by container name. Diagnostics and the deployer's health gate run inside the netns (`podman exec`, `wait_container_healthy`). - Cold-voice law honoured in the unit: the first request for a registered voice decodes its whole reference to snapshot codec state (6.2-8.0 s, first audio byte 5 530 ms), later ones restore in 0.5 ms, and the registry is memory-only. `ExecStartPost=-/usr/bin/podman exec frank-tts-engine /app/warm-voices.sh` with `TimeoutStartSec=900`; the `-` prefix keeps a failed warm from crash-looping the endpoint under `Restart=always`. It logs cold=/warm= per voice, so the under-a-second second request is proven on every boot. - Thread law recorded: `tts-server` has no `--threads`; src/backend.h uses hardware_concurrency()/2, so a cpuset pin silently changes the thread count. - Image builds from the pinned commit with the proven line (GGML_BLAS=OFF forced by this host, every GPU backend off, -j 8, no -DGGML_NATIVE because ggml defaults to -march=native for the building host). Two stages; the runtime stage COPYs both the entrypoint and the warmer, because a container that publishes nothing is reached with `podman exec`, not a port. - `scripts/download-frank-tts-engine-qwentts.sh` fetches the two GGUFs with byte and LFS-oid checks (talker 1 219 245 248 B, tokenizer 291 150 624 B); the deployer gates on --verify-only instead of pulling 1.4 GiB mid-deploy. - Registered in SERVICE_ORDER, DANKCLAW_SERVICES, the deploy-remote.sh dispatch, its pre-deploy requirement, deploy.sh's regex, the planner's path map (the whole `dankclaw/voiceservice/` tree mapped to nothing before this), and test-changed.sh ABOVE the `dankclaw/*.container` case whose `*` crosses `/`. - New seam tests/frank_tts_engine.bats (19): the Quadlet generator's own output for network/publish/boot/warm shape, entrypoint argv driven from the tracked env file, the warmer against a stub tts-server over a real socket (registration keys, ref_text absent, late engine, hung synthesis, name refusal), the fetcher's verifier, the pinned cmake line, and both routing tables. Suite: 422 passed. - Vault: [[Frank TTS Engine]] linked from the index; log.md entry for the day.
This commit is contained in:
1 parent
e13f875c94
commit
8d76854a1e
15 files changed
+1738
No files matched your search
@@ -2441,3 +2441,52 @@ every restart since the cutover.
|
||||
`~/.harbor/compose.opennotebook.yml` pins the deprecated `-single` image,
|
||||
carries no encryption key and injects the whole harbor env — left untouched,
|
||||
read-only, as the record of what not to copy.
|
||||
|
||||
## 2026-10-18 — frank-tts-engine: the arm A engine becomes deployable (nothing deployed)
|
||||
|
||||
- Goal: turn the engine the day's A/B picked into a **repo-owned, boot-persistent,
|
||||
self-healing container** — `frank-tts-engine`, `qwentts.cpp` commit
|
||||
`5916bf7c79dbb4e66fc059cdc507a79a23a28bb4`, resident `tts-server`, 1.7 B base
|
||||
talker Q4_K_M + Q8_0 tokenizer, container port 8880, and **not** a fleet
|
||||
endpoint: the endpoint stays `frank-tts` on 7772. Runbook: [[Frank TTS Engine]].
|
||||
- **No `PublishPort` at all, which is the shape the merged A/B states as law.**
|
||||
netavark DNATs published ports only, so a `127.0.0.1:8880` publish is not a path
|
||||
to a peer container — the façade could never have used it (the trap that 502'd
|
||||
hermes) — while being the one LAN-reachable surface of an unauthenticated
|
||||
synthesiser. Both voice containers therefore sit on one committed quadlet
|
||||
network, `frank-tts-engine.network` at **10.89.5.0/24** (the next free /24 after
|
||||
open-notebook's 10.89.4.0/24), and the façade addresses the engine by container
|
||||
name. Diagnostics moved to `podman exec`, including the deployer's health gate
|
||||
(`wait_container_healthy`, not `wait_for_health`: there is no URL to dial).
|
||||
- **The boot warmer is a unit hook, not an operator habit.** Cold-voice law: the
|
||||
first request for a registered voice decodes the whole reference to snapshot
|
||||
codec state (6.2–8.0 s, first byte 5 530 ms), later ones restore in 0.5 ms; the
|
||||
registry is memory-only and re-registering does not warm. So
|
||||
`ExecStartPost=-/usr/bin/podman exec frank-tts-engine /app/warm-voices.sh` with
|
||||
`TimeoutStartSec=900`, and the `-` prefix is what stops a failed warm from
|
||||
crash-looping the endpoint under `Restart=always`. It logs `cold=` and `warm=`
|
||||
per voice, so the under-a-second second request is proven on every boot.
|
||||
- **Thread law recorded before anyone tunes it away:** no `--threads` exists;
|
||||
`src/backend.h` uses `hardware_concurrency()/2`, so a cpuset pin silently
|
||||
changes the thread count. Nothing pins this container.
|
||||
- Image builds from the pinned commit with the **proven** line, verbatim —
|
||||
`GGML_BLAS=OFF` is forced (this host has `libopenblas.so.0`, no dev headers),
|
||||
every GPU backend off, `-j 8`, and no `-DGGML_NATIVE` because ggml's default is
|
||||
`-march=native` for the building host: the image is built where it runs. Base
|
||||
`ubuntu:24.04` pinned by index digest; two stages, because ggml builds shared
|
||||
and the RPATH names the build tree.
|
||||
- Weights fetched by `scripts/download-frank-tts-engine-qwentts.sh` with byte and
|
||||
LFS-oid checks (talker 1 219 245 248 B, tokenizer 291 150 624 B); the deployer
|
||||
gates on `--verify-only` rather than pulling 1.4 GiB inside a deploy.
|
||||
- Registered in all five places the open-notebook entry just named as law, plus
|
||||
the routing case above the `dankclaw/*.container` glob. New seam
|
||||
`tests/frank_tts_engine.bats` (18 tests): the Quadlet **generator's own output**
|
||||
for the network/publish/boot/warm shape, the entrypoint's argv driven from the
|
||||
tracked env file, the warmer against a stub `tts-server` over a real socket
|
||||
(registration keys, `ref_text` absent, late engine, hung synthesis, name
|
||||
refusal), the fetcher's verifier, and both routing tables.
|
||||
- Deliberately NOT done: the deploy (no host touched, no container started, no
|
||||
image built), any homepage card or Prometheus job for the engine — it publishes
|
||||
nothing and exposes no metrics, so there is nothing to probe, which is said out
|
||||
loud in the page rather than papered over. No ICL/`ref_text` work: the packs
|
||||
keep `reference.spk`/`reference.rvq` so it stays a per-voice opt-in.
|
||||
Reference in new issue
Block a user