perf(hermes): tune Piper for Jetson CPU bursts

This commit is contained in:
jenkins 2026-08-10 01:11:04 -03:00
parent a2220f7ae9
commit 8cdf38f781
3 changed files with 28 additions and 6 deletions

View File

@ -11,7 +11,8 @@ import wave
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path from pathlib import Path
from piper import PiperVoice, SynthesisConfig import onnxruntime
from piper import PiperConfig, PiperVoice, SynthesisConfig
HOST = os.getenv("HERMES_TTS_HOST", "0.0.0.0") HOST = os.getenv("HERMES_TTS_HOST", "0.0.0.0")
@ -19,6 +20,7 @@ PORT = int(os.getenv("HERMES_TTS_PORT", "9001"))
VOICE_NAME = os.getenv("HERMES_TTS_VOICE", "en_US-lessac-high") VOICE_NAME = os.getenv("HERMES_TTS_VOICE", "en_US-lessac-high")
CACHE_DIR = Path(os.getenv("HERMES_TTS_CACHE", "/cache/piper")) CACHE_DIR = Path(os.getenv("HERMES_TTS_CACHE", "/cache/piper"))
MAX_TEXT_CHARS = 5000 MAX_TEXT_CHARS = 5000
ONNX_THREADS = max(1, int(os.getenv("HERMES_TTS_ONNX_THREADS", "4")))
VOICE_LOCK = threading.Lock() VOICE_LOCK = threading.Lock()
@ -96,8 +98,21 @@ def main() -> None:
config_path = CACHE_DIR / f"{VOICE_NAME}.onnx.json" config_path = CACHE_DIR / f"{VOICE_NAME}.onnx.json"
if not model_path.exists() or not config_path.exists(): if not model_path.exists() or not config_path.exists():
raise RuntimeError(f"baked Piper voice is missing: {VOICE_NAME}") raise RuntimeError(f"baked Piper voice is missing: {VOICE_NAME}")
print(f"[tts] loading Piper voice {VOICE_NAME} on CPU", flush=True) with config_path.open("r", encoding="utf-8") as config_file:
voice = PiperVoice.load(model_path, config_path, use_cuda=False, download_dir=CACHE_DIR) config = PiperConfig.from_dict(json.load(config_file))
session_options = onnxruntime.SessionOptions()
session_options.intra_op_num_threads = ONNX_THREADS
session_options.inter_op_num_threads = 1
session = onnxruntime.InferenceSession(
str(model_path),
sess_options=session_options,
providers=["CPUExecutionProvider"],
)
voice = PiperVoice(session=session, config=config, download_dir=CACHE_DIR)
print(
f"[tts] loaded Piper voice {VOICE_NAME} on CPU with {ONNX_THREADS} ONNX threads",
flush=True,
)
server = ThreadingHTTPServer((HOST, PORT), SpeechHandler) server = ThreadingHTTPServer((HOST, PORT), SpeechHandler)
server.voice = voice # type: ignore[attr-defined] server.voice = voice # type: ignore[attr-defined]
print(f"[tts] ready on {HOST}:{PORT}", flush=True) print(f"[tts] ready on {HOST}:{PORT}", flush=True)

View File

@ -123,7 +123,7 @@ spec:
kubernetes.io/hostname: titan-20 kubernetes.io/hostname: titan-20
containers: containers:
- name: tts - name: tts
image: registry.bstein.dev/bstein/hermes-jetson-tts@sha256:1020a73e8941932dd071e5dcc66b5583ebb9376f9ee2fd1711b50e026eb88467 image: registry.bstein.dev/bstein/hermes-jetson-tts@sha256:82fbed67bd871e821e4fd745a49c91a3749dc6ad45ea4841dc3fb7cfbcd53e61
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- {name: http, containerPort: 9001, protocol: TCP} - {name: http, containerPort: 9001, protocol: TCP}
@ -133,6 +133,7 @@ spec:
- {name: HERMES_TTS_PORT, value: "9001"} - {name: HERMES_TTS_PORT, value: "9001"}
- {name: HERMES_TTS_VOICE, value: en_US-lessac-high} - {name: HERMES_TTS_VOICE, value: en_US-lessac-high}
- {name: HERMES_TTS_CACHE, value: /opt/models/piper} - {name: HERMES_TTS_CACHE, value: /opt/models/piper}
- {name: HERMES_TTS_ONNX_THREADS, value: "4"}
startupProbe: startupProbe:
httpGet: {path: /health, port: http} httpGet: {path: /health, port: http}
periodSeconds: 5 periodSeconds: 5
@ -159,8 +160,8 @@ spec:
volumeMounts: volumeMounts:
- {name: tmp, mountPath: /tmp} - {name: tmp, mountPath: /tmp}
resources: resources:
requests: {cpu: 500m, memory: 512Mi} requests: {cpu: "1", memory: 512Mi}
limits: {cpu: "2", memory: 2Gi} limits: {cpu: "4", memory: 2Gi}
volumes: volumes:
- name: tmp - name: tmp
emptyDir: emptyDir:

View File

@ -200,6 +200,7 @@ def test_voice_models_are_baked_and_runtime_has_no_public_egress():
tts_server = (ROOT / "dockerfiles" / "hermes-jetson-tts-server.py").read_text() tts_server = (ROOT / "dockerfiles" / "hermes-jetson-tts-server.py").read_text()
assert "download_voice" not in tts_server assert "download_voice" not in tts_server
assert "baked Piper voice is missing" in tts_server assert "baked Piper voice is missing" in tts_server
assert "session_options.intra_op_num_threads = ONNX_THREADS" in tts_server
policies = _documents(HERMES / "networkpolicy.yaml") policies = _documents(HERMES / "networkpolicy.yaml")
voice_policy = next( voice_policy = next(
@ -237,6 +238,11 @@ def test_voice_workloads_have_deliberate_xavier_placement():
assert stt_resources["requests"]["nvidia.com/gpu.shared"] == 1 assert stt_resources["requests"]["nvidia.com/gpu.shared"] == 1
assert stt_resources["limits"]["nvidia.com/gpu.shared"] == 1 assert stt_resources["limits"]["nvidia.com/gpu.shared"] == 1
assert "nvidia.com/gpu.shared" not in tts["containers"][0]["resources"]["requests"] assert "nvidia.com/gpu.shared" not in tts["containers"][0]["resources"]["requests"]
tts_env = {
item["name"]: item["value"] for item in tts["containers"][0]["env"]
}
assert tts_env["HERMES_TTS_ONNX_THREADS"] == "4"
assert tts["containers"][0]["resources"]["limits"]["cpu"] == "4"
assert all("hostPath" not in volume for volume in stt["volumes"]) assert all("hostPath" not in volume for volume in stt["volumes"])
assert all("hostPath" not in volume for volume in tts["volumes"]) assert all("hostPath" not in volume for volume in tts["volumes"])