[DRAFT] feat(selection): cache select() booking inputs so create_reservation replays by id - #2
[DRAFT] feat(selection): cache select() booking inputs so create_reservation replays by id#2jeffreywang88 wants to merge 4 commits into
Conversation
0951a9c to
b7436c7
Compare
| "selection_id": "select-123", | ||
| "reservation_id": "request-123", |
There was a problem hiding this comment.
can selection_id and reservation_id be different? what are they used for respectively?
There was a problem hiding this comment.
[claude] Yes, they may differ. selection_id names the cached selection to replay (the id given to select); reservation_id is the id the booking is registered under and what the lifecycle endpoints use. Ray passing the same value for both also works.
There was a problem hiding this comment.
reservation_id is the id the booking is registered under and what the lifecycle endpoints use
explain this mechanism
There was a problem hiding this comment.
[claude] create_reservation registers the booking in the per-model scheduler under request_id = reservation_id (finalize_reservation calls scheduler.add_request), which tracks its active and prefill load. The lifecycle endpoints (/reservations/{id}/prefill_complete, /output_block, DELETE) look the booking up by that same id to clear prefill load, grow decode blocks, and free it. selection_id never reaches the scheduler; it only keys the cache lookup.
| - **Single-use**: The first successful booking consumes the entry; a repeat | ||
| replay returns `404` (`no pending selection`). | ||
| - **Retryable on failure**: A booking that fails before landing (worker no | ||
| longer schedulable, service not ready) re-arms the entry, so the same call |
There was a problem hiding this comment.
re-arms the entry
how is this done?
There was a problem hiding this comment.
[claude] On a pre-booking failure (entry not ready, cached worker unschedulable, invalid prefill override), reserve_from_cached_selection re-inserts the taken entry into the cache before returning the error, so the identical replay call works once the fault clears. Failures after the scheduler booking landed do not re-arm.
| - **Retryable on failure**: A booking that fails before landing (worker no | ||
| longer schedulable, service not ready) re-arms the entry, so the same call | ||
| can be retried once the condition clears. | ||
| - **Bounded window**: Entries expire after 120 seconds, and each selector |
There was a problem hiding this comment.
tell me the reason why 120 and 4096 are fixed numbers
There was a problem hiding this comment.
[claude] They are backstops, not measured values: the expected select-to-reserve gap is sub-second, so 120s is generous headroom for a decide window plus retries, and 4096 bounds worst-case memory under mass abandonment (about 256MB if every entry held a 128k-token prompt's hashes). If operators need tuning we can promote both to KvRouterConfig, which has precedent (router_ttl_secs).
| - **Bounded window**: Entries expire after 120 seconds, and each selector | ||
| retains at most 4096 pending selections, evicting oldest first. | ||
| - **Replica-local**: The cache lives in the selector process that served the | ||
| `/select`. With multiple selector replicas, route the reservation to the |
There was a problem hiding this comment.
explain "multiple selector replicas". when can this happen?
There was a problem hiding this comment.
[claude] The selector can run as several processes for availability, synchronized via --replica-sync-port/--replica-sync-peers (Peer Planes section). Behind a load balancer, select and /reservations can land on different processes, and the cache is process-local, so the replay only hits on the replica that served the select.
| - **Overrides**: Only `effective_prefill_tokens` and `expected_output_tokens` | ||
| are honored; `dp_rank` and prompt fields (`token_ids`, `block_hashes`, | ||
| `sequence_hashes`, `isl_tokens`, `lora_name`) are ignored in favor of the | ||
| cached selection. Supplying `worker_id` switches to the explicit form. |
There was a problem hiding this comment.
if minimal form is used, all field except for expected_output_tokens should be ignored because it's not passed in to select.
confirm this statement: expected_output_tokens isn't passed in to select
There was a problem hiding this comment.
[claude] Not quite: select does accept expected_output_tokens (optional on SelectRequest) and the entry caches it; the reservation's value, when present, overrides it. effective_prefill_tokens is also honored because it is an output of select the caller may refine, validated against the cached ISL. Everything else is ignored, as documented.
There was a problem hiding this comment.
we should ignore expected_output_tokens and effective_prefill_tokens. the ones passed in from select should be honored.
There was a problem hiding this comment.
[claude] Done. The replay is now pure: effective_prefill_tokens, expected_output_tokens, and the prefill-tracking decision are captured at select time and booked as-is; the reservation-side values are ignored (worker and prompt already were). The override-above-ISL 400 no longer exists, and the tests assert the cached values win.
| The self-contained form carries the worker identity and prompt and needs no | ||
| cached selection; it wins whenever `worker_id` is present. It also discards | ||
| any cached selection for `selection_id` (or, when absent, `reservation_id`), | ||
| so a delayed replay cannot book stale state: |
There was a problem hiding this comment.
are we preserving the semantics prior to this PR?
There was a problem hiding this comment.
[claude] For the explicit form, yes: same fields, normalization, validation, and booking. Two deltas: a request missing worker_id used to fail deserialization with 422 and now returns 400 (no ids) or replays (selection_id present), and an explicit booking discards any cached entry for its id, a no-op for clients that never pass selection_id.
| semantics are unchanged; no idempotency ledger is added. | ||
| cross-replica admission, and the pending-selection cache behind the minimal | ||
| reservation form is local to the selector that served the `/select`. Use | ||
| `/select_and_reserve` for atomic local booking. |
There was a problem hiding this comment.
is the logic for select_and_reserve the same as the state prior to this PR?
There was a problem hiding this comment.
[claude] Yes. Caching is gated on !book (cached_inputs in schedule_selection), so select_and_reserve never reads or writes the selection cache; its booking path is unchanged.
| }; | ||
| // `select` (book == false) with a selection_id caches the booking inputs | ||
| // so a follow-up `create_reservation` can replay them by that id. | ||
| // Capture the to-be-moved values before they go to the scheduler. |
There was a problem hiding this comment.
remove this line of comment
b7436c7 to
90f0c84
Compare
90f0c84 to
73a2e10
Compare
73a2e10 to
65455d3
Compare
65455d3 to
12a13a8
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jeffrey Wang <jeffreywang@anyscale.com>
…lection_id Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jeffrey Wang <jeffreywang@anyscale.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jeffrey Wang <jeffreywang@anyscale.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jeffrey Wang <jeffreywang@anyscale.com>
12a13a8 to
6588b18
Compare
What
POST /selectwith aselection_idnow caches the computed booking inputs — the chosen worker, the normalized sequence hashes, andeffective_prefill_tokens— in an in-processSelectionCachekeyed by(model, tenant, selection_id). A follow-upPOST /reservationscarrying thatselection_id(plusmodel_name,tenant_id, and its ownreservation_id) replays the cached selection, so the (potentially very long) prompt is not re-sent or re-hashed.worker_idselects the explicit, self-contained form — it wins when both are present and discards any cached selection for the id. A request with neitherselection_idnorworker_idis rejected outright.When it is used
The Ray select-then-reserve flow: Ray calls
select, dispatches the request to the returned worker, then books admission with a second call carryingselection_id,reservation_id, andexpected_output_tokens. The two calls cannot be fused (select_and_reserve) because Ray decides between them, and under faults aselectmay never be followed by a reservation. The replay is keyed byselection_id; the booking is registered underreservation_id, so the two ids may differ. The explicit form remains fully supported and takes precedence wheneverworker_idis present.On the cached path only
effective_prefill_tokensandexpected_output_tokensare honored as overrides;dp_rankand prompt fields are ignored in favor of the cached selection (documented in the API docs,ReservationRequest, and thePython stubs).
Failure modes and client errors
Every failure returns a specific, actionable message — never an opaque error:
selection_idnorworker_idsupplied400include selection_id to replay a cached selection, or worker_id with the prompt for a self-contained booking404no pending selection {selection_id} for {key} (expired, already used, or never selected); call select again or book with worker_id and the prompt404schedulable worker {id} not found for {key}503no schedulable workers for {key}effective_prefill_tokensoverride exceeds the cached prompt length400... must not exceed isl_tokens409Request {id} already exists (assigned to worker {worker})503selection service is shutting downFailures that occur before the booking lands (the worker/readiness/override rows above) re-insert the cached selection, so the identical replay call can be retried once the condition clears — the client never has to re-send the prompt because of a transient fault.
How memory leaks are avoided
The cache needs no background task and no async runtime (it works identically in the HTTP server and the in-process Python
SelectionService):takere-checks the TTL on read.Related Issues
🔗 This PR is linked to an issue:
🚫 This PR is NOT linked to an issue: