Skip to content

serving: a caller gets the characters the model wrote, not replacement characters - #1732

Merged
anderdc merged 1 commit into
testfrom
serving/completion-from-token-bytes
Aug 29, 2026
Merged

serving: a caller gets the characters the model wrote, not replacement characters#1732
anderdc merged 1 commit into
testfrom
serving/completion-from-token-bytes

Conversation

@anderdc

@anderdc anderdc commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Found during soak 7, on the blessed pin, on a real 5090.

A runtime decodes each token to text independently, so a multibyte character split across two tokens reaches the stream as U+FFFD in both deltas. A 384-token completion came back as:

between **<U+FFFD>⌈m/2<U+FFFD>⌉** and **m** children

where the model wrote between **⌈m/2⌉** and **m** children. The gateway returns that assembled text to the caller, so this is what a paying user receives.

Reproduced directly against the pod: of 384 tokens, every one reported bytes, none were missing, and b''.join(token_bytes) decodes to the correct text — only the concatenated delta.content is damaged. So the information was never lost, just discarded.

StreamAssembler.text() now assembles the completion from the per-token bytes when every token reported them and they decode cleanly; a partial or undecodable byte list leaves the deltas exactly as sent. The audit already treats those bytes as ground truth (#1724 binds the miner's token ids to them), so this makes the text the caller sees agree with the bytes the validator verifies.

This also removes one source of the token ids do not spell the completion soft miss: that check has to skip whenever either side carries U+FFFD, which is precisely when a character was split.

Not fixed here: streaming callers still receive the runtime's deltas verbatim. Repairing those needs buffering across token boundaries so a split character is held until complete — a separate change with real latency implications.

Tests use the exact token shape captured from the live pod. CI green (ruff, format, pyright, vulture, 760 tests).

…t characters

A runtime decodes each token to text on its own, so a multibyte character split across
two tokens arrives as U+FFFD in both deltas. Found in soak 7 on the blessed pin: a 384
token completion came back reading "between **<U+FFFD>m/2<U+FFFD>**" where the model had
written the ceiling brackets, and the gateway handed that to the caller.

The per-token bytes are unaffected, and the audit already binds the miner's ids to them.
When every token reported its bytes and they decode cleanly, the completion is now
assembled from the bytes; a partial or undecodable byte list leaves the deltas as sent.

Streaming callers still receive the runtime's deltas verbatim - repairing those needs
buffering across token boundaries, which is a separate change.
@anderdc
anderdc merged commit 359ccef into test Aug 29, 2026
3 checks passed
@anderdc
anderdc deleted the serving/completion-from-token-bytes branch August 29, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant