โ† scrappylabs
๐Ÿซง Bubble-gum thinking

SPECVOICE: speculative execution at the dialog layer

2026-05-13 ยท Weekend prototype ยท github.com/scrappylabsai/specvoice

Bubble-gum thinking: ideas worth chewing on, not necessarily worth swallowing. This one โ€” generalize token-level speculative decoding (MTP, DFlash) from individual tokens to whole utterance chunks. Same shape as CPU branch prediction, just operating at the dialog layer instead of the instruction stream. 6.2ร— faster mouth-to-mouth, 16ร— less jitter on a commodity stack. Probably not novel; definitely measurable.

๐Ÿ”Š Hear the difference

Same question, same models, same hardware. First half = speculative (~56ms gap). Then 1.5s pause. Second half = baseline (~240ms gap). Listen for the silence after "today?":

The 184ms difference is right at the conscious-perception threshold for "is the system thinking?" Baseline reads as "the system is processing." Speculative reads as "responsive."

01The idea

Voice agent pipelines are serial: VAD โ†’ ASR โ†’ LLM โ†’ TTS. Each stage gates the next. Total mouth-to-mouth latency = sum of stages, and the LLM first-token step dominates (~200ms on a small model, more on bigger ones).

Token-level speculative decoding (Medusa, DFlash, MTP) works because a draft model proposes tokens in parallel that a target model verifies in one forward pass. The amortization beats serial autoregressive generation.

What if you do that at the dialog layer? The "draft tokens" become whole candidate utterances.

At partial-ASR time (before the user finishes speaking), fork the LLM into K parallel branches on different hypothesis completions. When ASR finalizes, pick the closest match via cosine similarity in embedding space and commit. The winning branch's tokens are already streaming โ€” downstream TTS starts immediately. Losers get cancelled.

02The pipeline

streaming ASR partials โ”€โ”ฌโ”€โ–บ LLM branch 1 (h1: "What's the weather") โ”€โ–บ Piper TTS render 1 โ”œโ”€โ–บ LLM branch 2 (h2: "...right now") โ”€โ–บ Piper TTS render 2 โ””โ”€โ–บ LLM branch 3 (h3: "...like today") โ”€โ–บ Piper TTS render 3 โ”‚ โ–ผ on final-ASR: embed final โ†’ cosine-sim โ†’ pick closest hypothesis โ”‚ โ–ผ audio out (winner's PCM already in memory)

03What the numbers say

6.2ร—
Mouth-to-mouth
16ร—
Jitter reduction
54 ms
Spec mean
ModeMeanMedianStdevRange
Speculative54.18ms51.65ms6.2ms49โ€“66ms
Baseline337.21ms293.66ms102ms239โ€“559ms

n=8 paired trials (4 utterances ร— 2 reps). End-to-end stacked benchmark, real chunked-input pacing, real streaming partials.

The jitter story is more interesting than the speedup

Voice UX is dominated by latency variance, not mean. A predictable 54ms beats a jittery 337ms even if both were "good enough." Baseline ranges 239โ€“559ms because ASR, LLM first-token, and TTS render each have independent jitter that compounds. Speculative absorbs all three by running them in parallel during the speech window.

04The stack

VAD
Silero (CPU)
ASR
faster-whisper small.en
LLM
Ollama qwen3.5:0.8b
TTS
Piper en_US-lessac-medium
Embedder
MiniLM-L6-v2 (CUDA)
Picker
Cosine sim, ~8ms

One consumer GPU (RTX 5090 Laptop) for whisper + embedder, one server GPU (RTX PRO 6000) for LLM. Nothing exotic. The interesting work is the orchestration, not the components.

05The unexpected finding

The cosine-sim picker doesn't always select the "right" hypothesis. In one demo run, partial-ASR transcribed "weather" as "way" mid-stream, so the picked hypothesis was the gibberish "What's the way? like today". But the LLM running on it produced:

"It is a lovely day!"

A perfectly fine answer to the actual question. Speculation tolerates bad hypotheses because adjacent-intent LLM responses still answer the user's actual intent. The picker selects the closest available; "closest-but-wrong" is usually still useful.

This is the kind of thing that makes a brittle-looking system surprisingly robust. The brittleness of the predictor stops mattering once the LLM is downstream.

06What's new, what's not

Probably not new

What I haven't seen written up cleanly

If you've published or built this combination, please open an issue with a pointer. Filed as bubble-gum thinking precisely because I don't know whether it's novel.

07Honest caveats

08Phase 1 bonus: TTS pivot speculation

๐Ÿ”Š Phase 1 interrupt artifact

Earlier sub-experiment: pre-render a "graceful pivot" utterance alongside the primary response. On interrupt, swap to the pre-rendered pivot PCM instantly (vs. 42ms baseline to render-on-interrupt).

Hard-cut click at the swap is intentional and audible โ€” prosody-aware crossfade is Phase 4 work, not done here. The point is the structural latency saving, not the audio polish.

09Why I built this

DFlash and MTP have always felt like they were operating at the wrong abstraction level. Tokens are too granular for a lot of what makes voice agents feel slow. The slowness lives at boundaries: ASR-final โ†’ LLM-start, LLM-done โ†’ TTS-start. Each boundary is a serial gate where speculation could amortize the waiting.

Whether or not anyone ships this exact stack, the framing โ€” "fork at uncertainty, commit at resolution, in parallel during the speech window" โ€” feels like a useful mental model for designing voice systems. Even if it turns out to be reinvention of prior art, it's reinvention I needed to do to understand the shape.

10What's filed

Open-source on GitHub. Reproduces in one session on any reasonable GPU setup. License: MIT.

โ†’ Code on GitHub โ†’ Download A/B WAV