Speech-to-text, the language model and text-to-speech run together on hardware you own and stream into each other, so a reply starts playing while it's still being generated. No per-minute API bills. No audio leaves your machine.
Speech runs in fusion's own process. The language model runs there too, or on vLLM or SGLang on the same GPU for several callers at once; frun up starts it either way.
A barge-in watcher runs alongside the pipeline: talk over the reply and generation and playback stop. The server decides that, because it hears clean audio — echo cancellation happens on the device, in the browser or in the terminal client, so it works on laptop speakers without headphones.
The ways a call breaks quietly, a frozen model server, a muted microphone, audio in the wrong format, a model running below strength, are noticed now, and either fixed or said out loud.
$ frun bench --audio order --callers 1,4,8,12,16 --target-ms 1500
1 caller(s): median 824 ms worst 832 ms
12 caller(s): median 1440 ms worst 3636 ms
Speech is where callers wait: stt went from 220 to 4553 ms from 1 to 16 caller(s),
while the LLM held (llm first token 49 ms).
Up to 12 caller(s) stayed under 1500 ms on this setup.
vLLM or SGLang that stays up but stops answering is restarted after 60 s, and warmed before callers reach it. /health says it's degraded while it's down.
No audio, or only digital silence, for 3 s and the caller is told. Silence never reaches speech-to-text, even if the voice detector failed to load.
frun bench runs real callers against your server and says how many stayed under your target, and which stage made the rest wait.
Tested on an RTX 3090 with vLLM and SGLang. Model server docs →
Name vLLM or SGLang in the agent, and frun up starts it before speech loads, restarts it if it exits, and stops it with you. Install the engine once, in its own environment.
agent = Agent(
prompt="You are the order line for ShopKart.",
greeting="Hi, this is ShopKart. Which order can I help you with?",
llm=LLM("vllm:hf:Qwen/Qwen2.5-7B-Instruct-AWQ"), # or "sglang:hf:..."
)
The server takes 60% of the GPU and speech the rest, so a 4-bit 7B model fits beside Whisper and Kokoro on 24 GB. If that much isn't free, frun up says what's holding it before anything loads.
A greeting plays the moment a caller connects, so nobody sits in silence wondering whether to talk. Talking over it stops it, like any reply.
A CPU image for laptops (Intel and Apple Silicon) and a GPU image that runs beside vLLM's own, each with a compose file. Logs come out as JSON for your log collector.
Measured on an RTX 4090: about 0.8–0.9 s from the end of a turn to the first audio, the LLM's first token in 23–55 ms. vLLM and SGLang docs →
A tool is a plain Python function. The model reads its name, docstring and type hints, calls it when it needs to, and answers with what it returned.
from fusion_runtime import Agent, LLM, tool
@tool
async def order_status(order_id: str) -> dict:
"""Look up where an order is and when it will arrive.
Args:
order_id: The order number, as the caller reads it out.
"""
return await orders.lookup(order_id)
agent = Agent(prompt="...", llm=LLM("vllm:hf:Qwen/Qwen2.5-7B-Instruct-AWQ"), tools=[order_status])
What the model says before a lookup ("Let me check.") is spoken while the tool runs. A second of silence on a call sounds broken.
Talk over the wait and the agent stops, but a lookup that lands keeps its result for the next answer. A tool that fails or times out tells the model what went wrong instead of ending the call.
Our first GPU test caught the agent inventing a second order's status: it remembered its answer but not that it came from a lookup. Lookups now stay in the conversation.
Tools need a model server that can call them: vLLM, SGLang, llama-server --jinja or a
hosted API. The in-process llama.cpp model can't yet, and frun up says so at startup.
Tools docs →
The production profile exactly as it ships — RTX 3090 at $0.50/hour, Qwen 7B, Whisper small and Kokoro all on the one card — through the browser client, 21 September 2026. A stopwatch from your last syllable reads 991 ms; the extra half second is the runtime waiting through silence to decide you've finished, which elapses while you're still finishing. Both numbers are below.
| Median | Range | |
|---|---|---|
| Processing — the turn ends, audio comes back | ~490 ms | 288–657 ms |
| Stopwatch from your last syllable | 991 ms | 858–1061 ms |
| ↳ of which: silence wait before the turn is judged over | ~500 ms | configurable |
| Speech-to-text | 119 ms | 58–329 ms |
| LLM first token | 27 ms | 20–70 ms |
| First token → first audio (a sentence gets written, then spoken) | 430 ms | 320–509 ms |
| Text-to-speech real-time factor | 0.09 | ~11× faster than real time |
| LLM tokens per second | 127 | 106–130 |
Interruptions fired on six of six attempts, and correctly ignored 192 ms of speech over the agent. The point isn't that a GPU beats a laptop — it's that a 7 B model answers as fast as a 0.5 B did on an 8 GB MacBook Air, because the pause you configure and the first sentence of speech dominate, not the model. Every figure comes from the runtime's own per-turn telemetry, so you can reproduce them on your own hardware.
Same 3090, real WebSocket sessions, three turns each, 26 September 2026. The server's own response time, turn end to first audio:
| Callers | Model in-process (llama.cpp) | vLLM | SGLang |
|---|---|---|---|
| 1 | ~460 ms | 398 ms | 410 ms |
| 4 | ~740 ms | 698 ms | 848 ms |
| 8 | ~4600 ms | 1086 ms | 1054 ms |
| 12 | ~7500 ms | 1083 ms | 1264 ms |
| 16 | — | 2064 ms | 1598 ms |
In-process, one llama.cpp context decodes one reply at a time, so four callers is the ceiling. vLLM and SGLang batch every caller together: the model's first token stayed between 36 and 72 ms from one caller to sixteen. What grows now is speech — Kokoro's first audio reaches 1.5–2 s at sixteen callers — so batching speech is what comes next. With every caller asking about an order, vLLM made the lookup in 122 of 123 turns; SGLang in 109, skipping only questions it had already looked up. Qwen2.5-7B-Instruct-AWQ, 60% of the card for the model.
The runtime serves the browser client it uses itself, so the page you demo with is the one your site embeds.
<script src="https://your-server/fusion-runtime.js"></script>
<button id="talk"></button>
<script>FusionRuntime.attach({ button: "#talk" });</script>
The browser's own echo canceller handles the speaker, the microphone is resampled off the
main thread, and replies are scheduled slightly ahead of real time so network jitter doesn't
leave gaps. The page never holds an API key — your backend mints it a token that works once.
Microphones need https://, so a deployment needs TLS and wss://.
Browser client docs →
Every stage runs in your process. Profile and fix any millisecond yourself instead of waiting on a vendor's queue or incident.
Audio and transcripts stay local by default. No third-party inference call in the default path — an architecture, not a config flag.
No API metering per stage. It runs on hardware you already pay for, so the marginal cost of another conversation is compute, not a bill.
Whisper tiny isn't Nova-3. Qwen 0.5B/7B isn't GPT-4o. Kokoro isn't ElevenLabs. That trade is right for some teams and not others.
Not everyone wants to run their own GPU. We're planning a hosted version of fusion-runtime —
no date yet, and it isn't built. If you'd rather not run infrastructure yourself, leave your
email and we'll tell you when it's real. This isn't a waitlist for the open-source runtime —
that's just pip install away.
No spam, one email when it ships. Unsubscribe anytime.