-
Notifications
You must be signed in to change notification settings - Fork 56
Separate mm encoder scheduling #1015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gkumbhat
merged 73 commits into
torch-spyre:main
from
gkumbhat:separate_mm_encoder_scheduling
Jul 13, 2026
Merged
Changes from all commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
d5ff401
:sparkles: Deduplicate MM vision encoder TP ranks
gkumbhat 8257f13
:bug: Add inference mode context around MM encoding invocation
gkumbhat cf94898
wip improvements
gkumbhat 8afe3d0
Merge branch 'main' into remove_redundant_computation
gkumbhat f8bf367
:construction: rebase with main and update runner
gkumbhat 5e2a761
:bulb: Remove unnecessary commentary
gkumbhat 7def4ff
:art: Fix ty checks
gkumbhat f2afc5a
:white_check_mark: Add unit tests for mm shared memory
gkumbhat 960f572
:art: Fix ruff check failure
gkumbhat d89fa06
:construction: Add schedular level MM batch processing
gkumbhat c9dc099
:construction: Change mistral batching to loop operation
gkumbhat 7caa3ee
:construction: WIP for phase 2 - separate process for vision encoder
gkumbhat 4041d95
:bug: Fix SHM conflict between async encoder
gkumbhat 5c1dc8f
Merge branch 'main' into separate_mm_encoder_scheduling
gkumbhat 232505d
:bug: Fix encoder process termination
gkumbhat 510ff1d
:truck: Rename encode to execute model
gkumbhat 566de61
:bug: Fix nnpa registration
gkumbhat 75cb0c4
:bug: fix casting of mm_dtype
gkumbhat 31e22da
:bug: Fix the dtype conversion between vision encoder and spyre runner
gkumbhat 39859ed
:bug: Fix mm dtype for NNPA
gkumbhat 3f83962
:bug: Fix mm dtype for NNPA
gkumbhat 67b645a
:bug: Fix NNPA slow performance caused by use of fused weights and cl…
gkumbhat 3d9a9b4
:loud_sound: Logging improvements
gkumbhat 5c39e29
Merge branch 'main' into separate_mm_encoder_scheduling
gkumbhat e8dd6c2
Cleanup old implementation and documentations related to that
gkumbhat a888b51
:art: Fix formatting
gkumbhat 883d20a
:wastebasket: Remove architecture diagram section from readme
gkumbhat b0caf82
:art::bulb: Cleanup and remove unnecessary code
gkumbhat 1386652
:art: Fix formatting
gkumbhat 074023a
:white_check_mark: Add unit tests for mm encoder and spyre executor
gkumbhat 70e6179
:bug: Fix issue with potential hang if encoder process fails
gkumbhat f0de732
:bug::white_check_mark: Fix text-only scheduling in mix of MM encoding
gkumbhat 2a53aea
:bug::white_check_mark: Fix MM embedding cleanup after abortion
gkumbhat 0a3a2c3
:bug::wastebasket: Fix logging statement and cleanup unused broadcast…
gkumbhat 8bca0f4
:bug: Fix MM encoding wrongly getting removed from pending requests
gkumbhat 94809ac
:bug: Fix aborted request from encoding memory
gkumbhat 5f2c530
:art: Fix formatting
gkumbhat a22f395
:art: Fix ruff check failures
gkumbhat 2005940
Merge branch 'main' into separate_mm_encoder_scheduling
gkumbhat 395a86d
:bug: Fix adjust_compute_token usage as implemented by PR 993
gkumbhat 4a44bef
:memo: Update doc
gkumbhat 2378b8b
:art: Fix markdown formatting
gkumbhat acc6b8a
:zap: Improve performance by allowing separate thread handling
gkumbhat b3ed4fb
Merge branch 'main' into separate_mm_encoder_scheduling
gkumbhat e00a55c
:recycle: Configure encoder threading to include ppc64le config
gkumbhat ee8454d
:art: Fix formatting
gkumbhat 0755ca5
Add compatibility to 1022
gkumbhat f991a35
:bug: Fix test by handling set num interop in try / except
gkumbhat 18cccb1
:coffin::memo::truck: Cleanups
gkumbhat acdc4c7
:test_tube: add some claude-drafted tests for edge cases
joerunde 27997a8
:alembic: try some e2e tests
joerunde efce073
:thread: Handle encoder job cancellation
gkumbhat 1f85989
:children_crossing: Handle encoder process crash by killing server
gkumbhat 2e43627
:bug: fixup the e2e tests to run on mac
joerunde af988ab
:thread: Handle DOS and encoder startup failure scenario
gkumbhat d352d72
:shield: Handle some edge case scenario around failures of encode reqs
gkumbhat b4f831b
:package: Update ibm-fms to 1.12.1 with granite-vision fix
gkumbhat d030638
Merge branch 'separate_mm_encoder_scheduling' into some-mm-edge-cases
joerunde 8d3a777
:fire: rip out other unit tests
joerunde 63ebf1e
:construction: try to add granite vision
joerunde 2e82e36
:recycle: rename
joerunde 819918a
:bento: use nano gc model
joerunde 188e3fa
Merge pull request #3 from joerunde/some-mm-edge-cases
gkumbhat f83dfd7
:bug: Fix encoder e2e test failure
gkumbhat 9832d6c
:art: Fix formatting and typing errors
gkumbhat dcb3a11
Merge branch 'main' into separate_mm_encoder_scheduling
gkumbhat 967a841
:white_check_mark: Fix schedular MM test to declare request pausing vars
gkumbhat c412334
:art: Fix formatting and remove old test file
gkumbhat a5926a9
:triangular_flag_on_post: Enable MM encoding feature by default
gkumbhat b1eec4f
Merge branch 'main' into separate_mm_encoder_scheduling
gkumbhat 6c01349
:triangular_flag_on_post: Enable inference async MM encoder by default
gkumbhat bdecefd
:test_tube: Fix two test failures after vLLM v0.24.0 bump
gkumbhat cfe1eaf
:triangular_flag_on_post: Disable separate encoding only for power
gkumbhat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # Parallel Vision Encoder Execution In Single Instance vLLM | ||
|
|
||
| ## Background | ||
|
|
||
| Multimodal models on Spyre compute the vision encoder on CPU (rank 0 only) and broadcast embeddings to other ranks via POSIX shared memory. Today this encoding runs serially, once per request, at the start of that request's first prefill step. MM encoding is expensive operation and in current implementation its blocking, so no other operation like prefill and decode of other requests can run in parallel affecting overall performance. | ||
|
|
||
| ## Goal | ||
|
|
||
| Overlap CPU / NNPA vision encoding with AIU prefill/decode by running the encoder in a separate subprocess. Embeddings are written to POSIX shared memory and all TP workers read them independently — no rank-0 broadcast of large tensors. The scheduler gates MM request prefill on encoding readiness, so a request only enters prefill once its embedding is available. | ||
|
|
||
| ## Evolution Path | ||
|
|
||
| **Phase 1 and 2:** Combined in current implementation. Vision encoding runs in a dedicated non-daemon subprocess (`mm-encoder`) managed by `SpyreMultiprocExecutor`. The encoder subprocess loads only the vision model via `get_model(..., vision_only=True)`. | ||
| The scheduler submits MM requests for encoding on every step and gates prefill on encoding. | ||
|
|
||
| **Phase 3 (future):** Enable vision encoder batching within the encoder subprocess. This will further improve the performance by handling all pending MM requests in single batch. This requires FMS changes to stack same-resolution images instead of | ||
| concatenating. See [Phase 3](#phase-3-add-vision-encoder-batching) below. | ||
|
|
||
| ### Current Flow | ||
|
|
||
| ```text | ||
| Scheduler picks 1 MM request | ||
| → execute_model(): | ||
| encode(request) # CPU, rank 0, single request | ||
| broadcast embeddings # SHM + dist.broadcast | ||
| prefill chunk on Spyre | ||
| (repeat for each chunk) | ||
| → decode steps | ||
| ``` | ||
|
|
||
| ### Implemented Flow | ||
|
|
||
| ```text | ||
| Encoder subprocess starts AFTER warmup completes | ||
| (SpyreMultiprocExecutor hooks on collective_rpc("compile_or_warm_up_model")) | ||
|
|
||
| Scheduler emits unsubmitted waiting MM requests on EVERY step (prefill and decode). | ||
| Scheduler gates MM prefill on _mm_encoding_ready | ||
| (only applies when SENDNN_INFERENCE_ASYNC_MM_ENCODER=1). | ||
|
|
||
| SpyreMultiprocExecutor.execute_model() on every step: | ||
| 1. Submit new _spyre_mm_encode_requests → job_queue # non-blocking put_nowait | ||
| 2. Drain result_queue (non-blocking) # collect completed encodings | ||
| if results: | ||
| collective_rpc("store_mm_embeddings") # all TP workers read SHM | ||
| cleanup SHM blocks | ||
| set scheduler_output._spyre_newly_encoded_req_ids | ||
| 3. super().execute_model() → workers run AIU forward # concurrent with encoder subprocess encoder process runs in parallel | ||
|
|
||
| scheduler.update_from_output(): | ||
| _mm_encoding_ready.update(_spyre_newly_encoded_req_ids) | ||
|
|
||
| Next schedule() call: request now in _mm_encoding_ready → scheduled for prefill | ||
| add_new_request(): cached_mm_embeddings = pending_mm_embeddings.pop(req_id) | ||
| _prepare_chunked_prefill(): uses cached embeddings, skips inline encoding | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Changes Summary | ||
|
|
||
| | File | Change | | ||
| |---|---| | ||
| | `sendnn_inference/platform.py` | Register `SpyreMultiprocExecutor` when `SENDNN_INFERENCE_ASYNC_MM_ENCODER=1` and TP > 1 | | ||
| | `sendnn_inference/v1/executor/spyre_executor.py` | `SpyreMultiprocExecutor`: override `execute_model` to submit encode jobs, collect results, call `store_mm_embeddings` on workers | | ||
| | `sendnn_inference/v1/worker/mm_encoder_process.py` | `VisionEncoderRunner` + `encoder_process_main`: load vision-only model, serve encode jobs, write embeddings to SHM | | ||
| | `sendnn_inference/v1/worker/spyre_worker.py` | Add `store_mm_embeddings` — delegates to model runner | | ||
| | `sendnn_inference/v1/worker/spyre_model_runner.py` | Add `pending_mm_embeddings` dict, `store_mm_embeddings` (reads from SHM), `_compute_and_cache_mm_embeddings` as inline fallback for warmup; consume in `add_new_request` | | ||
| | `sendnn_inference/v1/core/scheduler.py` | Add `MMEncodeRequest` dataclass; emit encode jobs every step; track `_mm_encoding_submitted` / `_mm_encoding_ready`; gate MM prefill on encoding readiness (async mode only); update state in `update_from_output` and `finish_requests` | | ||
| | `sendnn_inference/model_executor/model_loader/spyre.py` | Extract `cast_params_for_spyre` as module-level function reusable by encoder subprocess | | ||
| | `sendnn_inference/envs.py` | Add `SENDNN_INFERENCE_ASYNC_MM_ENCODER` env var (default 0) | | ||
|
|
||
| Non-MM requests, the warmup path, chunked prefill logic, and TP broadcast are unaffected. | ||
|
|
||
| ## Alternatives considered | ||
|
|
||
| ### Threading (abandoned) | ||
|
|
||
| **What we tried:** Start a `threading.Thread` in the worker model runner. The thread uses the already-loaded `fms_model` directly (no copy) and encodes waiting MM requests in the background while the AIU runs. | ||
|
|
||
| **Why it failed:** Spyre operations and vision encoding both are blocking operations. The background thread cannot make any progress during AIU execution. Encoding only runs in tiny Python gaps between AIU calls and prefill / decode gets impacted by encoding operations. | ||
|
|
||
| **Verdict:** No benefit. Reverted. | ||
|
|
||
| ### Subprocess from worker (abandoned) | ||
|
|
||
| **What we tried:** Start a `multiprocessing.Process` from inside the worker's `load_model` or | ||
| `complete_warmup`. | ||
|
|
||
| **Why it failed:** vLLM spawns worker processes as **daemon processes** | ||
| (`multiprocessing.Process(daemon=True)`). Python forbids daemon processes from spawning children (`AssertionError: daemonic processes are not allowed to have children`). | ||
|
|
||
| **Verdict:** Architecturally impossible from a worker process. | ||
|
|
||
| ### Subprocess from MultiprocExecutor (**implemented**) | ||
|
|
||
| **The idea:** vLLM's `MultiprocExecutor` runs in the **main (non-daemon) process**. Any process it spawns is also non-daemon. By subclassing `MultiprocExecutor` as `SpyreMultiprocExecutor`, we can start the encoder process at the executor level. | ||
|
|
||
| **Model weight loading:** FMS now supports `get_model(..., vision_only=True)`, which loads only vision tower + projector + text embedding from the checkpoint, skipping the LLM decoder. The encoder subprocess calls this directly. | ||
|
|
||
| **SHM-based result delivery:** The encoder process writes completed embeddings to POSIX SHM and puts only `(req_id, shape, dtype)` metadata on the result queue (no large tensors in the queue). The executor calls `collective_rpc("store_mm_embeddings", metadata)` so all TP workers read from SHM independently — no rank-0 to others tensor broadcast. | ||
|
|
||
| **Scheduler-level encoding readiness gate:** The scheduler tracks `_mm_encoding_submitted` and `_mm_encoding_ready` sets. MM requests are only eligible for prefill when their encoding is confirmed complete. Text-only requests are completely unaffected. The scheduler submits encoding jobs on every step (prefill AND decode) so the encoder stays ahead of the prefill queue. | ||
|
|
||
| ## Phase 3: Add Vision Encoder Batching | ||
|
|
||
| For N same-resolution images, the vision transformer can runs once on `[N, P, D]` instead of N times on `[1, P, D]`. CPU / NNPA matmul efficiently scales with batch size, so the single batched call should be significantly faster than N sequential calls — particularly for large images where the `P²` self-attention dominates. |
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taken out this function for reusability across encoder and decoder processes