frontend: add /tokenize and /detokenize endpoints - #32
Open
Pernekhan wants to merge 1 commit into
Open
Conversation
Pernekhan
force-pushed
the
pernekhan/tokenize-di
branch
from
August 27, 2026 03:33
b9ab1a1 to
efa957b
Compare
Pernekhan
had a problem deploying
to
external_collaborator
August 27, 2026 03:33 — with
GitHub Actions
Failure
Pernekhan
force-pushed
the
pernekhan/tokenize-di
branch
from
August 27, 2026 16:53
efa957b to
b1f8f15
Compare
Pernekhan
had a problem deploying
to
external_collaborator
August 27, 2026 16:53 — with
GitHub Actions
Failure
Pernekhan
commented
Aug 27, 2026
Comment on lines
+147
to
+150
| /// The preprocessors behind `chat_engine` / `completions_engine`. The pipelines own | ||
| /// them for generation; they are retained here so read-only callers (the `/tokenize` | ||
| /// and `/detokenize` endpoints) run the model's *own* template and tokenizer rather | ||
| /// than rebuilding a second copy that can drift from it. |
Collaborator
Author
There was a problem hiding this comment.
too much commenting here
Ports the upstream PR (ai-dynamo#9012) to the branch our frontend image is built from, so the endpoints can be exercised on our own stack. Same design: the endpoints run the model's own OpenAIPreprocessor, retained on WorkerSet by the watcher, rather than rebuilding a template and tokenizer of their own. 1.4.1 has no RenderedPrompt, so tokenize_chat encodes the rendered string directly instead of branching on segments; the catalog holds plain cards rather than Arc handles. Everything else is the upstream change verbatim. The 1.5.0 form of this change targets upstream separately.
Pernekhan
force-pushed
the
pernekhan/tokenize-di
branch
from
August 27, 2026 20:57
b1f8f15 to
fcf313c
Compare
Pernekhan
had a problem deploying
to
external_collaborator
August 27, 2026 20:58 — with
GitHub Actions
Failure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
POST /tokenizeandPOST /detokenizeto the frontend, so a caller can ask what a prompt costs without running inference./tokenizetakes either{"prompt": …}or{"messages": […]}; the latter renders the model's chat template first./detokenizeis the inverse. Both are system routes, appear in/openapi.json, and their paths are overridable viaDYN_HTTP_SVC_TOKENIZE_PATH/DYN_HTTP_SVC_DETOKENIZE_PATH.The number has to be the served number
The endpoints run the model's own
OpenAIPreprocessor— the instance its generate pipeline was built with — rather than re-deriving tokenization. The watcher already constructed one per pipeline and moved it intobuild_pipeline;WorkerSetnow retains it,ModelandModelManagerexpose it, andOpenAIPreprocessorgrows the read-only entry points that stop before the worker hop:tokenize_chat— normalize, template, encodetokenize_completion— encode a bare prompt, which is all/v1/completionsdoesdetokenize/token_stringsnormalize_chat_requestisgenerate()'s own pre-template prologue hoisted into one function both callers share (default thinking mode, thinking aliases, named-tool-choice fixup). All of them mutatechat_template_argsortool_choice, so templating without them renders a different prompt.An earlier revision re-derived that sequence instead and was wrong four separate times —
rendervsrender_prompt, plainencodevsencode_segments, the skipped prologue, and NUL-byte stripping. Each was found by reading the code, not by a failing test, which is why the shared-instance approach is the one here.Behaviour worth knowing
add_special_tokensdefaults tofalse./v1/completionsapplies no template and tokenizes without special tokens, so the prompt reaches the engine with no BOS; reporting anything else would report a count this deployment never produces. An explicittruereturns 501.These return 501 rather than an approximate answer: multimodal message parts,
continue_final_message, a per-requestchat_template,media_io_kwargs/mm_processor_kwargs,add_special_tokens: true, and a model with no Rust pipeline. Multimodal matters most — a chat template emits one placeholder where inference expands hundreds of tokens.token_strsreturns decoded text (" world") rather than vocabulary spellings ("Ġworld"). The latter is only reachable from anEncoding::Hf, and the prefix cache normalizes every encode toEncoding::Sp, so it would vary withDYN_TOKENIZER_CACHE.Verification
Measured against the endpoints themselves —
usage.prompt_tokensfrom the same frontend, modelgoogle/gemma-4-E4B-it:/tokenizevs live/v1/completions/tokenizevs live/v1/chat/completionsdeep-tokenizerserviceParity models: gemma-4-E4B-it, DeepSeek V3.1 / V3.2 / V4-Pro, GLM-4.6 / GLM-5, Qwen3-Coder-480B, Qwen3-235B-Thinking, Llama-3.1-8B-Instruct, Mistral-Nemo — both request forms,
add_generation_prompton and off,token_strs, and detokenize round-trips including CJK, emoji and accented text.On this branch:
cargo test -p dynamo-llm --no-fail-fast→ 38 suites, 0 failures;clippy -D warnings,fmt --checkand rustdoc all clean.Three guards, each verified by injecting the drift it exists to catch:
tokenize_chat_matches_the_generate_path_token_idstokenize_completion_matches_the_completions_pipelinetokenize_completion_strips_null_bytes_like_the_generate_pathAlso exercised end to end against a live
python -m dynamo.frontendwith etcd/NATS discovery and a mocker worker, on an image built the waydynamo-frontend:dyn141-v1is built.