straylight: local Whisper ASR and Qwen3.8-Flash-Next uncensored

whisper.cpp large-v3-turbo on :11435 (CPU, OpenAI transcriptions path)
so ASR does not take GTT from llama-server. Flash-Next is the cygnal
IQ4_XS-NGQ4 GGUF (~98 GB, gfx1151), qwen4exp, mmproj pinned; unload
Laguna before loading.
This commit is contained in:
2026-09-16 07:18:28 -07:00
parent 927eff0c98
commit 1e94e6638e
2 changed files with 75 additions and 1 deletions
+74 -1
View File
@@ -76,6 +76,18 @@ let
[qwen3.8-27b-uncensored]
hf-repo = huihui-ai/Huihui-Qwen3.8-27B-abliterated-GGUF:Q4_K_L
dedup-cache-models = true
; Qwen3.8-Flash-Next (~177B / 6B active, qwen4exp), orcarouter-abliterated,
; cygnal IQ4_XS-NGQ4 GGUF (~98 GB) measured on gfx1151. Needs ~99 GB GTT
; plus KV: unload Laguna first. 32k ctx / 1 slot so it fits beside the
; 104 GiB TTM cap. mmproj is explicit; hf-repo alone misses it in router mode.
[qwen3.8-flash-next-uncensored]
hf-repo = cygnal/Qwen3.8-Flash-Next-Uncensored-IQ4XS-NGQ4-GGUF
hf-file = Qwen3.8-Flash-Next-Uncensored-IQ4XS-NGQ4.gguf
mmproj-url = https://huggingface.co/cygnal/Qwen3.8-Flash-Next-Uncensored-IQ4XS-NGQ4-GGUF/resolve/main/mmproj-Qwen3.8-Flash-Next-Uncensored-BF16.gguf
parallel = 1
ctx-size = 32768
dedup-cache-models = true
'';
# `llama-models` shows what the router has resident; `llama-unload` frees every loaded
# model (e.g. before a gaming session), or just the ones named on the command line.
@@ -112,6 +124,25 @@ let
'';
llamaModelsCmd = pkgs.writeShellScriptBin "llama-models" "exec ${pkgs.python3}/bin/python3 ${llamaCtl} list \"$@\"";
llamaUnloadCmd = pkgs.writeShellScriptBin "llama-unload" "exec ${pkgs.python3}/bin/python3 ${llamaCtl} unload \"$@\"";
# CPU whisper.cpp: keep GTT for llama-server. large-v3-turbo is ~1.5 GB.
whisperCpp = pkgsUnstable.whisper-cpp.override { withFFmpegSupport = true; };
whisperModel = "/var/lib/whisper-server/models/ggml-large-v3-turbo.bin";
whisperFetch = pkgs.writeShellScript "whisper-fetch-model" ''
set -eu
D=/var/lib/whisper-server/models
mkdir -p "$D"
F=${whisperModel}
if [ ! -s "$F" ]; then
echo "fetching whisper large-v3-turbo"
${pkgs.curl}/bin/curl -L --retry 20 --retry-delay 10 --retry-all-errors \
-o "$F.part" \
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin
mv "$F.part" "$F"
fi
'';
whisperTranscribe = pkgs.writeShellScriptBin "whisper-transcribe" ''
exec ${whisperCpp}/bin/whisper-cli -m ${whisperModel} --language auto -f "$@"
'';
in
{
imports = [
@@ -168,6 +199,43 @@ in
TimeoutStartSec = "600";
};
};
# Local ASR: OpenAI-compatible /v1/audio/transcriptions on 11435.
# CPU-only so it does not contend with llama-server for GTT.
users.users.whisper-server = {
isSystemUser = true;
group = "whisper-server";
home = "/var/lib/whisper-server";
};
users.groups.whisper-server = {};
systemd.services.whisper-server = {
description = "whisper.cpp transcription server";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
ExecStartPre = whisperFetch;
ExecStart = ''
${whisperCpp}/bin/whisper-server \
--model ${whisperModel} \
--host 0.0.0.0 \
--port 11435 \
--inference-path /v1/audio/transcriptions \
--language auto \
--convert \
--threads 8 \
--no-gpu
'';
User = "whisper-server";
Group = "whisper-server";
StateDirectory = "whisper-server";
WorkingDirectory = "/var/lib/whisper-server";
Restart = "on-failure";
RestartSec = "10s";
TimeoutStartSec = "600";
};
};
# straylight is the unikernel host. The shared mcp.nix locks the agent
# down with PrivateDevices=true, which hides /dev/kvm and /dev/net/tun.
# Relax that here (only on straylight) so the agent can boot Nanos
@@ -222,6 +290,8 @@ in
inputs.herdr.packages.x86_64-linux.default
llamaModelsCmd
llamaUnloadCmd
whisperTranscribe
whisperCpp
];
services.open-webui = {
@@ -236,6 +306,9 @@ in
OPENAI_API_BASE_URLS = "http://127.0.0.1:11434/v1";
OPENAI_API_KEYS = "none";
ENABLE_OLLAMA_API = "False";
AUDIO_STT_ENGINE = "openai";
AUDIO_STT_OPENAI_API_BASE_URL = "http://127.0.0.1:11435/v1";
AUDIO_STT_OPENAI_API_KEY = "none";
};
};
@@ -243,7 +316,7 @@ in
networking.firewall.allowedTCPPorts = [ 53 443 8080 8443 9443 9444 9555 ];
networking.firewall.allowedUDPPorts = [ 53 ];
# llama.cpp OpenAI-compatible API: tailnet only (localhost is always allowed).
networking.firewall.interfaces.tailscale0.allowedTCPPorts = [ 11434 ];
networking.firewall.interfaces.tailscale0.allowedTCPPorts = [ 11434 11435 ];
# DNS: MCNS for internal zones, public resolvers as fallback.
networking.nameservers = [