Skip to content

fix(frontend): offload prompt tokenization off the async event loop - #3

Merged
nv-yna merged 1 commit into
feat/deepseek_v4_aafrom
yna/dsv4-tokenize-offload
Jul 6, 2026
Merged

fix(frontend): offload prompt tokenization off the async event loop#3
nv-yna merged 1 commit into
feat/deepseek_v4_aafrom
yna/dsv4-tokenize-offload

Conversation

@nv-yna

@nv-yna nv-yna commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Root-cause fix for the DeepSeek-V4-Pro disagg SLO collapse. This is the clean, core-only subset of
nv-yna/dynamo#1: a single functional commit with no env-gated
diagnostic/instrumentation code (the DYN_ACK_TRACE / DYN_STALL_OP_TRACE / DYN_PREFILL_TRACE traces),
no request-plane msgpack codec, and no fault-inhibit hysteresis change. Just the fix.

The bug

The frontend generate path tokenized the prompt synchronously on the async event loop
(gather_tokensencode_with_timingtokenizer.encode), unlike the embedding path which already
offloads via spawn_blocking. For DeepSeek-V4's ~40k-token prompts at high concurrency this stalls the
frontend tokio runtime for multiple seconds (measured: 135 event-loop stalls, max 2.66s), starving the
request-plane I/O → 5s ACK timeout → CannotConnect → GEN worker inhibited → flap/cascade → throughput
collapse (105 → 14–30 req/s), while the GEN/CTX engines stay healthy.

The fix

Make encode_with_timing async and run tokenizer.encode on the bounded blocking pool via
tokio::task::spawn_blocking, mirroring the embedding path. Own the prompt + clone the tokenizer Arc
so the closure is 'static + Send. Propagate async up through gather_tokens and its two callers.
This frees the event loop so the request plane keeps getting polled → no false CannotConnect → no cascade.

Scope

1 file, +18/−11 (lib/llm/src/preprocessor.rs). Diff is exactly commit fbe21cc47718 from PR #1,
cherry-picked cleanly onto feat/deepseek_v4_aa.

ROOT CAUSE of the DSV4 disagg SLO collapse: the frontend generate path tokenized
the ~40k-token prompt SYNCHRONOUSLY on the async event loop (gather_tokens ->
encode_with_timing -> tokenizer.encode), unlike the embedding path which already
offloads via spawn_blocking. At high concurrency this stalls the frontend tokio
runtime multi-second (measured: 135 event-loop stalls, max 2.66s), starving the
request-plane I/O -> 5s ACK timeout -> CannotConnect -> GEN worker inhibited ->
flap/cascade -> throughput collapse (105 -> 14-30 req/s), while GEN/CTX engines
stay healthy. Evidence: iter4 C8diag2 stall logs + send->decode 14.6s arrival.

Fix: make encode_with_timing async and run tokenizer.encode on the bounded
blocking pool (spawn_blocking), mirroring the embedding path; propagate async
through gather_tokens and its two callers. Frees the event loop so request-plane
I/O is polled -> no false CannotConnect -> no cascade.

Signed-off-by: Yuewei Na <nv-yna@users.noreply.github.com>
@nv-yna
nv-yna merged commit 185f6ff into feat/deepseek_v4_aa Jul 6, 2026
13 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant