feat: gpu-check probe + resident TTS daemon (serve)

Add two subcommands and the deps they need, shipped as madcat-say 0.1.0.

gpu-check (Sources/GpuCheck.swift)
  Queries the Metal device and runs a small MLX compute probe to verify
  the GPU pipeline + bundled mlx.metallib resolve before synth.

serve (Sources/Serve.swift)
  Loopback HTTP daemon (default 127.0.0.1:8765, Hummingbird). Loads
  VoxCPM2 once behind an actor (serializes the single GPU), warms the
  pipeline at boot, caches the last reference voice. Routes:
    GET  /health            -> {status,model,ready,uptime_s}
    POST /v1/audio/speech   {input|text, voice?, language?, timesteps?,
                             cfg?, prepad?} -> audio/wav
  Cuts warm synth to ~3.2s vs ~6.3s cold (in-process model load).

speak (Sources/MadcatSay.swift)
  Probes the daemon /health (0.6s) and forwards over HTTP when up;
  falls back to in-process load otherwise. New flags --daemon-port,
  --no-daemon.

Package.swift: add mlx-swift (GPU probe) and hummingbird 2.5..<2.17
  (HTTP only, no WebSocket — avoids the swift-websocket pin).

Makefile: resolve the metallib via `swift build --show-bin-path` on
  both packages instead of the triple-prefixed/symlink path, which is
  not always present in speech-swift.
This commit is contained in:
madcat-core
2026-06-10 21:04:19 +02:00
parent 67be877f7f
commit da04416ea4
5 changed files with 520 additions and 11 deletions
+12 -7
View File
@@ -6,7 +6,6 @@
SPEECH_SWIFT ?= ../speech-swift
CONFIG ?= release
METALLIB = $(SPEECH_SWIFT)/.build/$(CONFIG)/mlx.metallib
.PHONY: build run clean metallib install
@@ -14,15 +13,21 @@ build:
swift build -c $(CONFIG)
@$(MAKE) --no-print-directory metallib
# Copy the prebuilt metallib next to our binary. If speech-swift hasn't built
# it yet, fall back to its build script.
# Copy the prebuilt metallib next to our binary. SwiftPM's real bin dir is
# triple-prefixed (e.g. .build/arm64-apple-macosx/release) and the
# .build/<config> symlink is not always present in speech-swift, so resolve
# both ends with `swift build --show-bin-path` instead of hardcoding the path.
# If speech-swift hasn't built the metallib yet, fall back to its build script.
metallib:
@if [ ! -f "$(METALLIB)" ]; then \
@ss_bin="$$(cd "$(SPEECH_SWIFT)" && swift build -c $(CONFIG) --show-bin-path)"; \
if [ ! -f "$$ss_bin/mlx.metallib" ]; then \
echo "metallib missing in speech-swift — building it there..."; \
( cd "$(SPEECH_SWIFT)" && swift build -c $(CONFIG) && ./scripts/build_mlx_metallib.sh $(CONFIG) ); \
fi
@cp "$(METALLIB)" ".build/$(CONFIG)/mlx.metallib"
@echo "metallib in place: .build/$(CONFIG)/mlx.metallib"
ss_bin="$$(cd "$(SPEECH_SWIFT)" && swift build -c $(CONFIG) --show-bin-path)"; \
fi; \
our_bin="$$(swift build -c $(CONFIG) --show-bin-path)"; \
cp "$$ss_bin/mlx.metallib" "$$our_bin/mlx.metallib"; \
echo "metallib in place: $$our_bin/mlx.metallib"
# Quick smoke test (default voice).
run: build