The Message Protocol That Runs a Local LLM Inside a Browser Worker
Running an LLM locally in the browser demands a messaging architecture that handles streaming, cancellation, and state synchronization across threads. Getting the protocol right determines whether the UI stays responsive and whether multi-turn conversations run at acceptable speed.
A full inference engine for DeepSeek-R1-Distill-Qwen-1.5B executes entirely in a browser Web Worker, communicating with the main thread through a 13-message protocol. The design routes every lifecycle event—WebGPU detection, model download progress, token-by-token streaming, interruption, and context reset—through typed postMessage calls. A dual-callback TextStreamer separates raw token-ID processing for performance stats and state detection from decoded text delivery for UI rendering. KV-cache reuse cuts multi-turn conversation latency by avoiding repeated attention computation, while an InterruptableStoppingCriteria flag lets users halt generation mid-stream without losing already-displayed tokens. A two-state machine tracks DeepSeek's thinking/answering phases by matching exact token IDs, giving the UI a reliable signal to style reasoning output differently from final answers.
Evolving a message protocol from 5 to 13 types by adding messages whenever the main thread lacked state visibility is a practical counterpoint to upfront UML design—the protocol emerged from runtime gaps, not a whiteboard.
Using token IDs instead of decoded text for state detection (thinking vs. answering) eliminates an entire class of parsing bugs; a token ID match is exact and costs nothing, while regex on strings is fragile and adds overhead.
The cooperative interruption pattern—checking a flag rather than killing a thread—is the only viable approach inside a Worker, and it naturally preserves partial results that have already been pushed to the UI.
Storing the Worker reference in useRef rather than useState is a concrete React pattern that prevents memory leaks from abandoned Worker threads, yet many tutorials still get this wrong.