Repository navigation
feat(runner): implement chunked prefill to eliminate decode blocking during continuous batching - #2473
Open
presentyourlove wants to merge 1 commit into
Conversation
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.
Description
This PR resolves item 7 in
TODO.md: "Solve the problem of in continuous batching when a new prompt comes in, it will block decode of the current batch until the prefill is complete."Background & Problem
In continuous batching with
BatchGeneratorandExoBatchGenerator, whenever a new request arrived while existing requests were actively decoding, the batch loop synchronously executed the entire prefill phase for the new request before advancing the generation loop:_gen.step()was blocked, completely freezing token emission for all currently active streams (causing noticeable jitter, stutter, and degraded perceived latency for active users).Solution: Chunked Prefill Continuous Batching
This PR introduces Chunked Prefill to interleave prompt prefilling in configurable chunk budgets (default: 512 tokens) with active decode steps.
Non-blocking Multi-step Prefill Session (
src/exo/worker/engines/mlx/generator/batch_generate.py):PrefillSessiondataclass tracking sliced prompt tokens, KV cache accumulator, media regions, progress callbacks, and completion status.prepare_prefill(...) -> PrefillSession: sets up prompt tokenization, checks prefix KV cache hits, and creates a prefill session without executing forward computation.step_prefill(session, chunk_size=512) -> tuple[bool, int | None]: evaluates one chunk of tokens against the model, updates rotating KV cache, notifies real-time prefill progress (PrefillProgressChunk), and registers the generation task once prefill is complete.submit(...)by wrappingprepare_prefill+step_prefill.Interleaved Decode and Prefill Loop (
src/exo/worker/runner/llm_inference/batch_generator.py):_prefilling_tasktracking inBatchGenerator._gen.step()so active streams emit tokens every step (~30ms) without any perceptible stutter._prefilling_taskby one chunk (default: 512 tokens when active decode streams exist; opportunistic fast-path 4,096 tokens when idle)._active_tasks._apply_cancellations): promptly drops prefilling sessions if cancelled before completing prefill.Cross-Platform Compatibility (
src/exo/utils/channels.py,src/exo/worker/runner/bootstrap.py):_MemoryObjectStreamStatefallback inchannels.pyfor varying AnyIO environments.resourceimport optional inbootstrap.pyfor cross-platform robustness.Comprehensive Unit Tests (
src/exo/worker/tests/unittests/test_runner/test_chunked_prefill.py):test_chunked_prefill_does_not_block_active_decode_batch: Verifies that Task A decodes a token at every single step while Task B prefills across multiple 512-token chunks, after which both decode concurrently.test_cancellation_during_chunked_prefill: Verifies that a task cancelled during intermediate prefilling chunks cleanly terminates without corrupting the batch or stalling existing tasks.test_legacy_engine_fallback_without_chunking: Verifies backward compatibility for legacy engines lacking chunked prefill support.Verification
.venv/Scripts/python.exe -m pytest src/exo/worker/tests/unittests/test_runner/test_chunked_prefill.py -v # 3 passed in 0.57s.venv/Scripts/python.exe -m ruff check src/exo/utils/channels.py src/exo/worker/engines/mlx/generator/batch_generate.py src/exo/worker/runner/bootstrap.py src/exo/worker/runner/llm_inference/batch_generator.py src/exo/worker/tests/unittests/test_runner/test_chunked_prefill.py .venv/Scripts/python.exe -m ruff format src/exo/utils/channels.py src/exo/worker/engines/mlx/generator/batch_generate.py src/exo/worker/runner/bootstrap.py src/exo/worker/runner/llm_inference/batch_generator.py src/exo/worker/tests/unittests/test_runner/test_chunked_prefill.py # All checks passed!Related Issues
TODO.md("Solve the problem of in continuous batching when a new prompt comes in, it will block decode of the current batch until the prefill is complete.")