[perf][tinker] Don't deserialize request payloads the scheduler discards - #1992
Draft
avigyabb wants to merge 3 commits into
Draft
[perf][tinker] Don't deserialize request payloads the scheduler discards#1992avigyabb wants to merge 3 commits into
avigyabb wants to merge 3 commits into
Conversation
The engine's scheduling queries selected whole `FutureDB` rows, so every pending request's `request_data` was deserialized on every poll -- including requests parked behind an optim_step/load_weights barrier, which are then filtered out and discarded. A forward_backward with 4x512 tokens is ~76 KiB of JSON, so a backlog of a few hundred parked requests meant tens of MB of wasted decode per 100ms tick, keeping the engine CPU-bound and slow to dispatch. The three finders now select ids only, filter as before, and fetch `request_data` just for the requests they return. Sample batching needs one payload field, so `checkpoint_id` is extracted in the database instead of loading whole prompts to reach it. Measured on 512 pending rows of ~76 KiB, the cost of the old full-row query breaks down as: Python JSON deserialization 499ms (83%) ORM object construction 73ms (12%) reading payload text off disk 30ms (5%) the id-only query itself 1.6ms (0.3%) So a scan that dispatches nothing drops from ~285ms to ~2ms. A scan where everything is dispatchable is roughly unchanged, since those payloads are genuinely needed -- the saving is proportional to how much of the queue is blocked, which for multi-LoRA is most of it. Scheduling semantics are unchanged: same barrier logic, same request_id ordering, same return shapes. The existing barrier regression tests cover this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Avi Basnet <avigyabb@stanford.edu>
pcmoritz
reviewed
Aug 5, 2026
Signed-off-by: Avi Basnet <avigyabb@stanford.edu>
Signed-off-by: Avi Basnet <avigyabb@stanford.edu>
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.
What
The engine's scheduling queries selected whole
FutureDBrows, so every pending request'srequest_datawas deserialized on every poll — including requests parked behind anoptim_step/load_weightsbarrier, which are then filtered out and thrown away.A
forward_backwardwith 4×512 tokens is ~76 KiB of JSON. A backlog of a few hundred parked requests therefore meant tens of MB of wasted decode per 100 ms tick, which keeps the engine CPU-bound and slow to dispatch. That backlog is the multi-LoRA steady state: some adapters stepping while others have passes stacked up behind a barrier.The change
The three finders now select ids only, filter exactly as before, and fetch
request_datafor just the requests they return. Sample batching needs one field from the payload, socheckpoint_idis extracted in the database rather than loading whole prompts to reach it.Per finder the change is two lines — the column list, and a payload fetch after the filter:
Plus one small helper,
_load_request_payloads, so that fetch isn't written three times.Scheduling semantics are unchanged: same barrier logic, same
request_idordering, same return shapes. The existing barrier regression tests pass untouched.Where the cost was
Measured on 512 pending rows of ~76 KiB, breaking down what the old full-row query was paying for:
Note how little of it is I/O — only 30 ms of 602 ms is SQLite reading text. The rest is Python turning that text into dicts and lists of numbers. That's why this showed up as engine CPU rather than disk, and why it starves other processes on a shared box.
A scan that can dispatch nothing drops from ~285 ms to ~2 ms. A scan where everything is dispatchable is roughly unchanged, because those payloads are genuinely needed. The saving is proportional to how much of the queue is blocked — which for multi-LoRA is most of it.
Deliberately kept small
An earlier version of this (#1976) also replaced the per-finder queries with one shared metadata scan, moved barrier computation from SQL into Python, and added a covering index. Measuring the pieces separately showed that was poor value: the shared scan is worth about 1 ms on top of this (0.4% of the win), and the covering index is below noise once payloads are skipped. All of it is left out here, and
_find_destructive_barrierskeeps its existingGROUP BYquery untouched.I'd close #1976 and #1977 in favour of this if it looks right.
One behaviour note
json_extractreturnsNULLfor a missing key, where the previousop.request_data["checkpoint_id"]would have raisedKeyError.checkpoint_idis required onSampleInputand always present inmodel_dump(), so this is unreachable today; a missing value is now treated like the base-model case (always batch-compatible) rather than crashing the scan.Testing
Three tests added to
tests/tinker/test_engine.py, covering what previously had none:find_batchable_model_passesholds back passes behind a barrier while another model's pass still dispatches, and the returned payload still parsesfind_batchable_samplekeeps onecheckpoint_idper model, exercising thejson_extractpath🤖 Generated with Claude Code