Skip to content

fix(server): a re-sent chat prompt encodes only the pieces it has not sent before - #48

Open
imD-5 wants to merge 1 commit into
Elevatormusic:deepseek-v41-tp2from
imD-5:fix/chat-prompt-pieces
Open

imD-5 wants to merge 1 commit into
Elevatormusic:deepseek-v41-tp2from
imD-5:fix/chat-prompt-pieces

Conversation

@imD-5

@imD-5 imD-5 commented Oct 28, 2026

Copy link
Copy Markdown

Effect

  • One agent turn of a 94K-token chat: tokenizer work 2.67 M allocations / 694 MiB to 4.2 K / 4.2 MiB.
  • Rank 0 growth over 30 such turns: 14.2 MiB a turn to 4.7-8.3 MiB.
  • Replies unchanged: greedy token_sha identical between the two images, and --alone --serial equal in every cell.

Reproduce

python3 tools/bench_concurrent.py http://<head>:8100 <MODEL> --alone --serial   # concurrent equals solo, drafted equals serial
python3 -m pytest -q tests/test_prompt_pieces.py                                 # ids equal the tokenizer's
  • Two images, one session, same pair: a token_sha per reply, the same conversation re-sent with a new last turn each time.

Change

  • Adds server/prompt_pieces.py and one call in cuda/server.py: the rendered prompt is cut after each added token and only unseen pieces are encoded.

Why

  • The tokenizer splits its input at the added tokens it matches before normalization, so a piece that ends in one encodes alone to the ids it gets inside the whole text.

Type

  • type: fix: a bug fix, or a fix after review findings

Tests

  • 9 new tests, failing before the change; the suite's 41 pre-existing failures are unchanged.

Checklist

  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.
AI disclosure

AI: Claude Opus 5.5 via Hermes Agent
Checked: by author

… sent before

A chat client re-sends its whole conversation every turn, and the CUDA server
tokenized all of it each time. For a long agent conversation that is millions
of small native allocations a request, made on the request's own thread; glibc
keeps much of that memory in the thread's arena after it is freed, so rank 0
grows by GiBs over a session until the node runs out of memory.

The tokenizer splits its input at the added tokens it matches before
normalization and encodes each part on its own. PromptPieces cuts the rendered
prompt after each such token, encodes only the pieces it has not seen and
reuses the ids of the rest, within a token budget. The ids are the tokenizer's
own: a tokenizer whose added tokens could move a split (rstrip, single_word,
special tokens encoded as text) keeps the full encode.
@imD-5
imD-5 requested a review from Elevatormusic as a code owner October 28, 2026 06:08
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