top of page

Token Streaming

  • Writer: Pradeep P
    Pradeep P
  • 3 days ago
  • 4 min read

Layer 6 · Post 13 of 26

Layer 6 — Modern systems · Post 75 of 88

Token streaming is the protocol-level idea: the model emits pieces of text over time, and every layer from GPU to browser has to stay in sync with that flow.

What you'll learn

  • What a token is (and why it is not always a word) and how detokenization hits the stream

  • How SSE deltas, usage counters, and [DONE] line up with decode steps

  • Why UTF-8, tool-call JSON, and "partial tokens" break naive string concat in the UI

The idea in one minute

The model does not emit "English." It emits token IDs from a tokenizer (BPE/unigram). The inference server detokenizes incrementally and sends deltas — usually a small string — on each decode step (or every few steps). Every hop must preserve order, not drop a chunk, and not assume each chunk is a complete word or a valid UTF-8 character.

GPU decode --> token id | v [ Detokenizer: id --> incremental text ] | v [ SSE: data: {"choices":[{"delta":{"content":"Hel"}}]} ] | v [ Your API: pass-through / add request_id ] | v [ Client: append delta, render markdown cautiously ] | missing chunk? garbled words; double-apply? duplicated letters

Post 74 was "why stream." This is the wire format and the token boundary problem.

Why it matters

If you buffer until a newline, you destroy TTFT for tokens that are sub-word pieces. If you JSON.parse a tool call from incomplete deltas, you throw in the UI. If you count "words" for billing, you will disagree with the vendor's token invoice.

Interviews that go deep on LLM infra want you to separate tokens (model/billing) from characters (UI).

How it works

  1. Tokenizer. Hello might be one token; Supercalifragilistic might be several. Numbers and code tokenize differently from prose. max_tokens is this vocabulary, not whitespace words.

  2. Decode step. The GPU produces the next id given the KV cache. The server maps id → string piece. Some pieces are leading spaces (" world"). Some are incomplete UTF-8 until the next token arrives — a careful detokenizer holds a tail so you do not emit a broken character.

  3. Wire protocol. OpenAI-compatible streams: each event is data: \n\n. delta.content is a string fragment. Later, finish_reason (stop, length, tool_calls). data: [DONE]\n\n ends the stream. Usage (prompt_tokens, completion_tokens) may arrive at the end only — your live UI cannot show exact cost until then unless you count locally with the same tokenizer.

  4. Your API. Prefer pass-through of frames. If you coalesce (send every 50 ms), you trade smoothness for fewer syscalls — say so. Do not reorder. Do not UTF-8-split a delta in the middle of a codepoint when chopping for a max chunk size.

  5. Client. Maintain fullText += delta. Render markdown on a debounce so a half-open **bold does not flicker. For tool calls, accumulate delta.tool_calls[].function.arguments as a string until finish_reason; then JSON.parse.

  6. Failure. A dropped SSE event (rare on HTTP/2, possible with bad proxies) desyncs the text. There is usually no per-token replay. You reconnect with a new request or show "interrupted." Duplicate delivery of one event doubles characters — parsers must be dumb append, not "smart merge."

Clients are the browser parser. APIs are frame-preserving proxies. Stores might save the final concatenated string plus token counts. Failure is framing and partial JSON.

A simple example

The model emits ids that detokenize as "Hel", "lo", "!". Three SSE events; the UI shows Hello!. A later token is the start of an emoji that needs the next id to become a valid character; the server holds it for one step, then emits "🎉". Your naive server that flushed raw UTF-8 bytes on a 1-byte buffer would have sent a replacement character.

A tool call streams {"name":"get_weat then her","city":"Pune"}. You do not parse until the stream finishes that tool block. Billing uses 14 completion tokens, not 3 "words."

Common mistakes

Treating each SSE event as a word or a sentence. Tokens are subword. UI must append blindly.

Parsing JSON tool arguments on every delta. It will throw until the last chunk.

Re-encoding or logging with the wrong tokenizer and then arguing with finance about token counts.

Splitting chunks on byte length and breaking UTF-8 or combining characters.

Assuming usage is in the first event. Often it is only at the end; design the cost UI for that.

How this shows up in real systems

  • OpenAI, Azure OpenAI, Grok, Anthropic streaming APIs: delta events; details differ, the append model does not.

  • vLLM / TGI: --return-tokens vs text deltas; same decode loop underneath.

  • tiktoken, Hugging Face tokenizers: the local way to estimate what the server will bill.

Tokens on the wire still need a queue when GPUs are busy. That is the next post.

Recap

  • Streaming is ordered token-id → detokenize → delta frames.

  • The UI appends; it does not wait for word boundaries. Tool JSON waits for completion.

  • UTF-8, dropped frames, and usage-at-end are the sharp edges.

When more requests arrive than decode slots, you queue — next in the series.

Layer 6 · Post 13 of 26

Comments


About Me

DSC_7604.jpg

Hi, I am a software engineer from Bangalore, India. Love spending time on gaming and photography. This website is where I will ocassionally throw what comes to my mind. Hope it is useful or at least entertaining to you. :)

 

  • Instagram
  • Facebook
  • Twitter
  • LinkedIn
  • YouTube
  • 500px

© 2023 by Going Places. Proudly created with Wix.com

bottom of page