Repository navigation
Conversation
c537c01 to
f1b1689
Compare
| revoked and the hold is released. | ||
| RuntimeError: A registration may be live; the hold is kept. | ||
| """ | ||
| for index, region in enumerate(self.regions): |
There was a problem hiding this comment.
This registers every region the lender exposes and treats each one as host memory. Nothing checks that, so a region backed by device memory (a pool lent in place) would be registered with the transfer engine as host memory. Please register only host regions that have slots, keep their indices, refuse construction when none qualifies, and add a test that a device region is never registered.
There was a problem hiding this comment.
Done. RegionBinding.served_region_indices keeps only regions with slots in memory the driver can access: HOST, or DEVICE when the driver declares device_memory (Mooncake does not). With none, register() releases the hold and raises ConstructionRejected. Tests: test_region_binding_registers_the_regions_with_slots_the_driver_can_access, test_a_binding_with_no_region_to_register_is_rejected_and_lets_the_hold_go.
| f"{where}: addresses must be int64 [{rows}], got {addresses.dtype} " | ||
| f"{addresses.shape}" | ||
| ) | ||
| if not 0 <= group.region_index < len(self._regions): |
There was a problem hiding this comment.
A row whose region_index names a region this backend did not register is a row it does not recognize. Please reject it here with InvalidSubmission instead of passing the driver an address in memory it never registered.
There was a problem hiding this comment.
Done: _checked_group_rows raises InvalidSubmission for a region not in served_region_indices (test_rows_in_a_region_the_backend_did_not_register_are_rejected).
| # Submission, on the executor thread. | ||
|
|
||
| def _submit(self, view: View, hint: Hint, is_fetch: bool) -> BlobAttempt: | ||
| if hint is not None: |
There was a problem hiding this comment.
Rejecting every hint closes this class to drivers that declare hints. Could a fetch pass the hint through to driver.get(rows, hint) when the driver declares hints, and reject it otherwise? A publish has nowhere to put a hint, so rejecting a non-None destination there is right.
There was a problem hiding this comment.
Done: a fetch's hint goes to every get of a driver that declares hints; a fetch with a hint over any other driver raises InvalidSubmission before the driver is called, and a publish still takes only None (test_a_fetch_hint_reaches_every_get_of_a_driver_that_reads_hints, test_invalid_submissions_are_rejected_before_the_driver[hint]).
| key=(self._name, "observe", type(error)), | ||
| ) | ||
| return None, False, False | ||
| if count != len(batch): # outside the driver protocol: trust neither answer again |
There was a problem hiding this comment.
After a driver breaks its protocol (a wrong result count here, or a raise other than DriverRejected at line 549), the backend keeps accepting work. The holder then only sees attempts that never quiesce, and stops using the binding only when its retention limit trips. Please refuse every later submission with SubmissionRejected, with a fault text that names the violation.
There was a problem hiding this comment.
Done: a raise other than DriverRejected from put/get, a put/get that returns no handle, a query that raises, and a wrong result count are each recorded as a protocol violation. The call is held as unprovable (unless its access end was already recorded), and every later submission gets SubmissionRejected naming the violation. A submission being handed over when the violation is recorded hands no further call to the driver: the next call fails as a whole, the rest end CANCELLED, and none of them holds memory; calls already in the driver run on. Tests: test_any_other_raise_from_the_driver_holds_the_call_and_refuses_later_work, test_a_poll_with_the_wrong_row_count_faults_the_call_and_keeps_it_held, test_the_calls_after_a_protocol_violation_never_reach_the_driver, test_a_violation_told_during_the_hand_over_stops_the_rest_of_it, test_calls_cut_off_by_another_attempts_violation_end_their_access, test_a_protocol_violation_leaves_calls_already_in_the_driver_alone.
| deadline = time.monotonic() + max(0.0, timeout) | ||
| if self._close_and_force_outcomes(): | ||
| self._call_wake() | ||
| self._progress.join(max(0.0, deadline - time.monotonic())) |
There was a problem hiding this comment.
When the only work left is unprovable, the progress thread can never finish, so every shutdown waits out its whole budget. Could shutdown return as soon as no worker is inside the driver and every open batch is unprovable?
There was a problem hiding this comment.
Done: shutdown returns as soon as every attempt not yet quiesced holds a call whose access can never be proven to end, since from then on neither its label nor a region's can clear (_shutdown_answer_is_final). That also covers an attempt whose other calls are still running in the driver. Tests: test_shutdown_returns_once_every_open_attempt_has_an_unprovable_call, test_shutdown_returns_once_a_call_in_the_driver_turns_unprovable.
| return f"cannot import mooncake.store ({error}). {hint}" | ||
|
|
||
|
|
||
| def _warn_unless_pinned_version() -> None: |
There was a problem hiding this comment.
The code mapping and the access-end rules were read from 0.3.13. On another SDK they may not hold, and an early return from a put would let a revoke free space that a transfer is still writing. Please refuse other versions, with a test-only override if needed.
There was a problem hiding this comment.
Done: open_sdk refuses a mooncake.store not shipped at 0.3.13 by one of the pinned distributions (_require_pinned_builds, which also refuses when any installed build that lists the module's file is not one of those distributions at 0.3.13, as a second install over the same path can leave). No override was needed: the tests fake the installed distributions (test_only_pinned_sdk_builds_are_opened).
| return self.store.batch_put_from_multi_buffer_ranges(*args) | ||
|
|
||
|
|
||
| def test_real_concurrent_publishes_of_the_same_keys_from_one_client(clients, request) -> None: |
There was a problem hiding this comment.
This holds the first scatter for at most 5 s, well under the master's put discard timeout (30 s by default). The case that matters is a first put stuck past that timeout: the second put of the same key from this client must still be refused, and batch_is_exist must stay 0.
There was a problem hiding this comment.
Added test_real_put_stuck_past_the_discard_timeout_keeps_its_keys: the master runs with --put_start_discard_timeout_sec=2 and the first scatter is held past it. The second put of the same keys from this client is skipped without asking the SDK, and batch_is_exist stays 0 until the first put ends.
| SLOT_BYTES = 64 << 10 | ||
|
|
||
|
|
||
| def _require_mooncake() -> None: |
There was a problem hiding this comment.
Please check that mooncake_master comes from the same pinned distribution as the client, and record both versions in the run log. Beyond the current cases, the real-pool run should also cover a TRANSFER_FAIL get (access stays unproven), a lease-expired reread, a master kill, freeze and restart, a store-side freeze during a put, and process exit while a call is blocked.
There was a problem hiding this comment.
Version part done: the master must be shipped by one of the pinned distributions and report 0.3.13, every installed pinned client build must be 0.3.13, and both versions are printed and recorded as a test-suite property. Of the extra cases, the real pool covers a put stuck past the discard timeout. A TRANSFER_FAIL get and a lease-expired reread are covered only over the fake SDK (test_mooncake_driver.py). Master kill/freeze/restart, a store-side freeze during a put, and process exit during a blocked call are not in this PR.
| def make_driver(self) -> StoreDriver: | ||
| return MooncakeDriver(FakeMooncakeStore()) | ||
|
|
||
| @pytest.mark.xfail(strict=True, reason=SHORT_OBJECT_LANDS) |
There was a problem hiding this comment.
This strict xfail, and the one at test_mooncake_real.py:228, depend on the suite's "destination unchanged" assertion for short objects. batch_get_into writes a shorter object into the head of the slot, so that assertion does not fit this store; both xfails should go when the suite drops it.
There was a problem hiding this comment.
Done: for a smaller object the suite now checks only that the row is a FAULT and that the canaries outside the slot are intact (test_an_object_smaller_than_its_row_is_a_fault); both xfails are gone.
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def blob_memory_store(): |
There was a problem hiding this comment.
Loading the in-memory driver by file path ties this conftest to the test-tree layout. Once the driver suite and the in-memory driver are importable from the package, please import them instead.
There was a problem hiding this comment.
Done: the in-memory driver and the driver suite moved to tensorrt_llm/_torch/disaggregation/testing/. The conftest's path loading is gone, and the round-trip test imports them from the package.
| # Submission, on the executor thread. | ||
|
|
||
| def _submit(self, view: View, hint: Hint, is_fetch: bool) -> BlobAttempt: | ||
| if hint is not None: |
There was a problem hiding this comment.
This rejects every non-None hint, but blob/store.py in this PR says the backend passes a hint only to a driver that declares hints and rejects it before dispatch for the others (Capabilities.hints, StoreDriver.get). As written, a driver that fetches across instances by hint can't be used under this backend. Could a fetch's hint go to every get of a driver that declares hints (the same hint for each get when a submission is split into several), keeping the InvalidSubmission rejection for drivers that don't, with a test for both cases? Publish can keep rejecting non-None hints, since put takes none.
There was a problem hiding this comment.
Done, same change as the thread above: when a fetch is split, each get receives the same hint object; a fetch with a hint over a driver without hints raises InvalidSubmission before the driver is called, and publish keeps rejecting a non-None destination. Both cases are tested in test_a_fetch_hint_reaches_every_get_of_a_driver_that_reads_hints and test_invalid_submissions_are_rejected_before_the_driver[hint].
7f29395 to
f2d736b
Compare
f2d736b to
6b51a8c
Compare
… of NVIDIA#20473 The backend now hands each call to the driver as one RowBatch and learns of it through on_change, with no queue or worker threads of its own; the copied files match 15c9491 apart from batch registration, cutting a call at a repeated key, the extent check and the example's cleanup. The tests, the README and the KVCR-shaped driver follow. Signed-off-by: Tingfeng Xian <289617005+nv-xtf@users.noreply.github.com>
| """ | ||
| ... | ||
|
|
||
| def get(self, rows: Sequence[RowIO], hint: Hint = None) -> OpHandle: | ||
| """Fetch ``rows``, from where ``hint`` says when given; may return before any byte moved. | ||
| def get( |
There was a problem hiding this comment.
If a put-end RPC fails, this revoke call can make drain(0.0) wait for the SDK. The driver contract requires drain to return within its time budget. While it waits, the backend retains the region registration and lender hold, even after all data access has ended.
Maybe we should keep session cleanup within the remaining drain budget, and add a test with a blocked revoke call to verify that drain(0.0) returns promptly.
There was a problem hiding this comment.
Fixed in 282e8493d01 and a53e895d184: drain makes no SDK call any more, so drain(0.0) returns at once whatever the master does. The sessions left open hold no lender memory, so nothing needs them revoked before the regions and the hold go. close now tries the revocation once and then closes the SDK client, even if the revocation raised. Under main's rule close is the owner's last step, after shutdown returned no labels and release_hold(), and it already waits on the master for the unmount. A session whose revocation fails is freed by the master after put_start_release_timeout_sec. The ledger's FOR_DRAIN is now FOR_CLOSE. test_drain_returns_at_once_while_a_revocation_would_block holds a revoke inside the SDK and checks that drain returns without calling it and that close revokes afterwards. test_sessions_left_open_are_revoked_only_in_close covers a revocation that fails.
15c9491 to
47456bd
Compare
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Configuration and SDK setup tensorrt_llm/_torch/disaggregation/blob/mooncake_setup.py, tensorrt_llm/_torch/disaggregation/blob/__init__.py, tests/unittest/_torch/disaggregation/blob/test_mooncake_setup.py, tensorrt_llm/_torch/disaggregation/blob/README.md, tensorrt_llm/_torch/disaggregation/blob/store.py |
Adds configuration parsing and validation, SDK build and environment checks, master availability checks, address and RDMA-device selection, and driver setup. Updates row-status documentation and Mooncake test instructions. |
Driver lifecycle and scheduling tensorrt_llm/_torch/disaggregation/blob/mooncake.py |
Adds host-memory registration, row admission, worker scheduling, operation polling and cancellation, draining, and closing. |
Put and get transfer handling tensorrt_llm/_torch/disaggregation/blob/mooncake.py |
Adds put-session and scatter handling, get reads and lease-expired rereads, per-row result mapping, and access-safety tracking. |
Fake SDK and driver tests tests/unittest/_torch/disaggregation/blob/fake_mooncake.py, tests/unittest/_torch/disaggregation/blob/test_mooncake_driver.py |
Adds a scripted in-memory SDK fake and CPU-only tests for driver transfers, scheduling, session handling, lifecycle, and backend round trips. |
Real-pool and backend integration tests tests/test_common/mooncake_utils.py, tests/unittest/_torch/disaggregation/blob/test_mooncake_real.py, tests/unittest/_torch/executor/kv_cache/sharing/test_blob_backend_round_trip.py |
Adds an opt-in Mooncake segment provider and real-pool tests, plus full and partial KV-cache blob backend fetch tests. |
Priority:
Estimated code review effort: 4 (Complex) | ~60 minutes
Sequence Diagram(s)
sequenceDiagram
participant BlobBackend
participant MooncakeDriver
participant Worker
participant MooncakeStore
BlobBackend->>MooncakeDriver: submit put or get rows
MooncakeDriver->>Worker: queue a bounded part
Worker->>MooncakeStore: issue batch SDK calls
MooncakeStore-->>Worker: return per-key answers
Worker-->>MooncakeDriver: record row results and access state
BlobBackend->>MooncakeDriver: poll operation
MooncakeDriver-->>BlobBackend: return row results
Merge Risk: 
Minimal · up to a9f6a
-
This change adds a Mooncake blob driver that is not yet wired into the executor, so it has no effect on runtime behavior. Prior review concerns about session cleanup and row-status mapping appear to have been addressed. No unresolved merge-blocking issue was identified.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Description check
-
Explanation
The description explains the purpose, scope, design, testing, verification results, limitations, and checklist status. It provides the information required by the template, although it uses expanded section headings.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 35.44% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 158 functions across 10 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
|
/bot run |
|
PR_Github #81167 [ run ] triggered by Bot. Commit: |
|
PR_Github #81167 [ run ] completed with state Resource maintenance in LLM/main/L0_Test-x86_64-Single-GPU #9587: L40S-PyTorch-2 is temporarily paused due to Kubernetes agent provisioning and node issues.
|
MooncakeDriver implements the StoreDriver protocol over a set-up MooncakeDistributedStore from SDK 0.3.13, zero copy from and into the lender's registered host slots. A put starts a put session per key, writes every row in one scatter (batch_put_from_multi_buffer_ranges), ends the sessions of rows written whole and revokes the rest; a get is one batch_get_into. The module docstring cites the 0.3.13 sources each rule was read from. - put and get run on the executor loop's thread: a few comparisons whatever the batch's size (one span per row, at most 2 GiB a row, inside one live registration), then the operation is recorded and queued whole. Admitting one costs the same for any number of rows or parts. - A fixed pool of worker threads, started with the driver and ending when it drains, cuts parts off the queued operations, at most rows_per_call rows and bytes_per_call bytes each, and makes each part's SDK calls. Gets go first; once GETS_BEFORE_PUT_IS_DUE gets have gone ahead of a waiting put, that put goes next once a put may run, and puts take at most workers - 1 threads, so a get never waits behind puts alone. - on_change is called once an operation is done or turned unprovable, outside the driver's lock. A cancel ends the rows no worker took, and a drain those of every operation; the running parts run to their end. A worker an exception stops faults its part with access unproven and the others go on; once none is left, the rows no worker took fault and new operations are rejected. - Access end differs by call. The put scatter waits, without a timeout, until every task it posted finished, so its return ends access whatever it answers. batch_get_into can answer TRANSFER_FAIL for a future that gave up while its transfer still runs, so a get ends access only when no row answers TRANSFER_FAIL. A raise or a wrong answer count leaves access unproven. An operation whose access cannot be proven keeps its region from being unregistered and makes drain answer False at once. - Put sessions are per key and client. The driver never starts a session for a key one of its puts holds (that row is NOT_READY without asking the SDK) and ends only rows written whole. A session it cannot see ended or revoked stays in one ledger: left for drain to revoke (an end whose outcome is unknown, a start or put that raised or miscounted, a failed revoke), or left to the master when the target may still write into the object (TRANSFER_FAIL, a scatter that raised or miscounted). A key whose session is left open is never put again: it is READY when the master shows its object complete. - Codes become row results here and nowhere else: OBJECT_NOT_FOUND is a MISS; a key with no complete replica, a key held by this driver's put and an uncommitted object are NOT_READY; a full pool is DECLINED; an existing object is READY once the master confirms it; a read whose lease expired is read once more, READY or FAULT; the rest are FAULTs with their codes. Skipped rows are counted by reason in stats() and named in the part's warning. - Capabilities: absent_is_miss, host memory only, one span per row, cancel, no probe and no row limit of its own. close() closes the SDK client once a drain returned True and every region is unregistered. fake_mooncake.py implements only the SDK calls the driver may make, with 0.3.13's answers over real bytes; test_mooncake_driver.py pins the code mapping, sessions, access-end rules, scheduling, drain and close, a backend round trip, and that fetch and publish build no store key or RowIO per row and start no thread on the calling thread. The shared driver_conformance checks run over the fake SDK with every scenario it can stage. An exception escaping a thread fails the test; the tests that end a worker thread on purpose assert it ended by SystemExit. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
A deployment describes its store in a config file of its own, while the backend needs a MooncakeDriver over a set-up SDK client and a BlobConfig. mooncake_setup turns the one into the other. load_store_config reads the store config, a YAML file or the mapping parsed from one: a blob section for the caller's reader (BlobConfig for the blob backend), and a mooncake section read into MooncakeConfig, which also sets the driver's workers, rows_per_call and bytes_per_call (defaults measured on GB200 over RDMA). Unknown sections and keys, and values of the wrong type, are refused with their section; so are an unreadable file, a YAML error and a master address that is not one host:port. open_sdk refuses TCP's legacy framing, the local hot cache and the newer transfer engine (MC_USE_TENT, MC_USE_TEV1), whose behaviour the driver's rules were not read for; logs the Mooncake environment before setup; imports mooncake.store only when called, and refuses it unless every installed distribution that lists the module is a pinned 0.3.13 build; waits for the master; resolves the RDMA devices and the local address; and closes a client whose setup failed. open_mooncake_driver reads both sections before it opens the SDK, closes the client again when the driver cannot start its workers, and returns the driver with what the caller's reader returned. A client in service is never closed here. test_mooncake_setup.py covers config errors, setup arguments, device detection, a master that comes up late, the refused settings and SDK builds, a missing SDK, a half-open client, the opened driver working through its client, and the blob package's import layering. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
The fake SDK shows what the driver asks for; only a real master shows whether the SDK answers that way. test_mooncake_real.py runs the shared driver_conformance checks over a real pool, one fresh client per check, with the scenarios a real master can stage (a key another client is still writing, a get or put held inside the SDK), and runs the backend between real clients for what the fake models beyond them: a full pool that declines rows, a get that lands a shorter object's bytes and refuses a longer one, a second publish of keys whose sessions the same client still holds, also once the first put is stuck past the master's put_start_discard_timeout_sec, and four workers moving 2000 rows byte for byte. The clients give the pool no segment and keep no local buffer, so every object sits in another process's segment (running_segment_provider in test_common/mooncake_utils.py) and nothing falls back to the SDK's copying path. The file runs only with TRTLLM_MOONCAKE_REAL=1 and fails unless mooncake_master and the client are both a pinned 0.3.13 build; it records both versions as test-suite properties. An exception escaping a thread fails the test. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
… the blob backend The CPU and real-pool tests drive the backend with host slots of their own; none shows that what the staging lender lends is what the backend moves. test_blob_backend_round_trip.py does it end to end on a GPU: one real KV cache manager publishes a request's blocks through its lender's staging slots, another fetches them into a request of its own, and a test lease holder drives the lender as the runtime will (lend, wait for the view, submit it as it stands, wait for quiescence, mark what arrived, release). - Pages come back byte for byte and resume_bounds covers the prompt's three whole blocks, over the in-memory store and, with TRTLLM_MOONCAKE_REAL=1, a real Mooncake pool. Each driver moves every row separately, several at once: the in-memory driver takes one row per operation, each on a thread of its own; Mooncake cuts each operation into one-row parts on two workers. - A fetch whose later blocks fault marks only the delivered prefix, so resume_bounds stops at the first gap. Its in-memory driver ignores cancels: the rows run in any order, and the cancel a later row's fault forwards could otherwise end the first row before it started. The test builds each backend itself, as a factory will: it opens the driver, takes the lender's hold, registers the regions with RegionBinding, starts BlobBackend, and gives the hold back once the backend shut down. The lender's MemoryRegion names no memory kind, which SlotRegion requires, so the test registers host-memory SlotRegions made from the lender's regions, as a runtime must until the lender's regions name their memory kind. An exception escaping a thread fails the test. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
… makes no SDK call drain revoked the put sessions left open once it had drained, in one batch_put_session_revoke outside its budget. A slow or hung revoke then held up drain(0.0) on the backend's progress thread, and with it the revocation of the registrations and the lender's hold, although no access remained. close, the owner's last step under the blob backend's rule, now tries the revocation once before it closes the SDK client; close already waits on the master for the unmount. These sessions hold no lender memory, so nothing waits for them, and one whose revocation fails is freed by the master after put_start_release_timeout_sec. drain makes no SDK call and returns within its budget. The ledger's FOR_DRAIN becomes FOR_CLOSE, and the stats key put_sessions_left_for_close. close's docstring and open_mooncake_driver's say who closes: the caller that opened the driver, last, off the executor loop's thread. The setup and round-trip tests close their drivers that way, and the round trip keeps drivers rather than SDK clients for its fallback close. A new test holds a revoke inside the SDK and checks that drain returns at once without calling it; the session tests check that close revokes what drain left. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
…T_READY departs The blob backend cuts a group at a repeated object key, so no operation names a key twice; the driver keys a part's results by store key and relies on that. A test over the fake SDK publishes and fetches a view naming one key twice: two calls, no fault, both slots fetched. 0.3.13 cannot always confirm the hold that NOT_READY stands for: -703 also answers a complete replica it cannot read, batch_is_exist answers 0 for one too, and a session left open after a start that raised may not exist. The driver keeps NOT_READY for them, since the usual cause is a write in progress and a FAULT would stop every fetch that races a writer and count toward isolation; _SKIP_RESULTS now says so. Comments also say why a failed register_buffer certifies nothing was registered, and when the last worker's on_change may come after a drain returned True. The round trip's open_driver says how each driver moves its rows one by one. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
…, and pin the order What the review of the drain and close change found: - close closes the SDK client in a finally, so a BaseException from the revocation no longer leaves the client open behind a driver that already counts as closed. - Nothing pinned that close revokes before it closes the client. A closed 0.3.13 client answers every batch call with an empty list, which the driver would take as a failed revocation. The fake SDK now answers so too, and the session tests then fail if the order flips. - close's docstring: the revocation waits on the master without the GIL; only closing the client holds it through the unmount. - A put stopped by a raise is unprovable, so close never comes for its sessions; the class docstring says the master frees them. - The NOT_READY note names its one lasting case: a key whose left-open session the master freed stays NOT_READY, as it is never put again. - The worker-exit comment says when a drain returns True before the last worker's on_change, instead of when rows are left queued. - test_drain_leaves_the_session_of_an_unprovable_put_to_the_master is now test_an_unprovable_put_leaves_its_session_to_the_master, since drain revokes nothing. - The round trip's fallback close skips a driver a failed build never drained, so the clients after it still close, and its open_driver says Mooncake runs puts one at a time. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
…put of a key left open What the review of the NOT_READY departure settled: - RowStatus.NOT_READY now covers an object of the key that the store reports without a readable complete replica, as while a write has not finished or when no complete replica can be read, with the row memory of a get left untouched; and another put of the same driver, still running, that writes the key. RPC, transfer and integrity errors stay FAULT. README rule 4 says the same, and both say these statuses charge the row no fault rather than that nothing went wrong. - So Mooncake's first-read -703 and a put start's -705 with no complete object are NOT_READY under the rule itself, and the departure note in mooncake.py goes. - A put of a key whose session this driver left open, with no complete object behind it, is DECLINED with its own reason and counter (rows_declined_held_by_session_left_open), instead of NOT_READY under held_by_own_put. The driver never writes such a key again, and the put that left the session open already concluded FAULT, so a FAULT here would count that one failure again on every later put of the key. Beyond a store's own refusal, RowStatus.DECLINED covers only this case. - held_by_own_put keeps its name and counter but now means only another put of this driver writing the key now. - The probe docstring follows NOT_READY: a key a get would find NOT_READY is NOT_READY there too. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
36dce6d to
aaeeb4c
Compare
|
/bot run |
|
PR_Github #81317 [ run ] triggered by Bot. Commit: |
|
PR_Github #81317 [ run ] completed with state Resource maintenance in LLM/main/L0_Test-x86_64-Single-GPU #10112: L40S-PyTorch-2 is temporarily paused due to Kubernetes agent provisioning and node issues.
|
| refused = [] | ||
| for key, first in zip(keys, codes): | ||
| code = final[key] | ||
| if code == _Code.OK: |
There was a problem hiding this comment.
Does a successful put-end retry guarantee that the original timed-out RPC can no longer execute? Otherwise, could a late end prematurely commit a new object after eviction and reuse of the same key by this client? If so, should we retain the no-reuse guard even after retry success?
…nswered The master matches a session end or revoke to an object by key and client alone, and an end answered RPC_FAIL or RPC_TIMEOUT may still run later, after a retry on another connection ended the session. If the object is then evicted and this driver puts the key again, the late end marks the new, unwritten object complete. Reproduced on a real 0.3.13 master. - A reserved key is now resolved only when the SDK refused its start or all its session calls had a known outcome and left no session. An end whose first answer was RPC_FAIL or RPC_TIMEOUT leaves the key open for close even when the retry ends the session; the row stays READY. A later put of the key is READY while the master shows its object complete, DECLINED once it does not. - A start refused with RPC_FAIL or RPC_TIMEOUT still resolves its key; leaving such keys open would bar every key a master outage met from ever being put again. The class docstring says what a late start can do. - Renames: _sessions_left_open to _keys_left_open, the per-part closed set to resolved, the skip reason held_by_session_left_open to left_open_by_own_put (counter rows_declined_left_open_by_own_put), and the stats put_sessions_left_* to put_keys_left_*. - RowStatus.DECLINED: the driver will not write the key again, as an earlier put of its own may still hold the key or act on a new write of it. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
Summary
This PR adds the Mooncake driver for RI-08D's blob backend, which is on main through #20884.
MooncakeDrivermaps [None][feat] Add the blob store driver boundary and an in-memory test driver #20217's privateStoreDriverboundary onto the pinned Mooncake 0.3.13 SDK.open_mooncake_driveropens the driver from the store's own config file.The factory that RI-02's TransferManager calls is left to a follow-up on top of #20266, whose
BackendFactoryshape (cleanup dispatcher,FactoryRejected) is still in review. Nothing is wired into the executor yet, so the path stays off until a runtime assembles it.The initial profile follows the design: KVCM v2, TP1/DP1/PP1/CP1, one pool, host staging from the #19722 lender, and
Nonehints only. Names like RI-08D are items of the Runtime Integration design; M1–M5 are milestones of the Store Backend design. Both documents are linked below.What changed since the last review
The backend moved to main. #20884 (merged as
b351c407bba) copiedblob/backend.py,keys.py,store.py, thetestingpackage and their tests verbatim from this PR's head15c9491ba4a. It then added:RegistersRegionsTogether);MemoryDriverfixes and new conformance checks;This PR was rebuilt on main
592bd6fd8d9and now holds only the Mooncake driver, its setup and their tests. The review threads on the backend and the boundary have replies, and that code is unchanged on main in those respects.New in this PR since that head:
drainmakes no SDK call (thread from @chienchunhung). It used to revoke the put sessions left open, in a blocking RPC outside its budget, which could hold updrain(0.0)and with it the backend's revocation and the lender's hold.close, the owner's last step under main's rule, now does that revocation before it closes the SDK client. A test sets up a gate that would hold a revoke inside the SDK, and checks thatdrainreturns at once without reaching it.open_mooncake_driversays what main's README says: the caller that opened the driver closes it last.closeadds that this happens off the executor loop's thread. The tests close their drivers that way.RowStatus.NOT_READYnow covers an object of the key that the store reports without a readable complete replica, with a get's slot untouched, and another put of the same driver, still running, that writes the key; RPC, transfer and integrity errors stay FAULT. README rule 4 now defers toRowStatusfor these conditions. So Mooncake's first-read -703 and a put start's -705 with no complete object are NOT_READY under the rule, and the departure note is gone.rows_declined_left_open_by_own_put), when no complete object stands behind the key (thread from @nv-xtf, agreed). The driver never writes such a key again, and a FAULT here would count the earlier put's outcome again on every later put of the key. Beyond a store's own refusal,RowStatus.DECLINEDcovers only this case: the driver will not write the key again, as an earlier put of its own may still hold the key or act on a new write of it.put_start_discard_timeout_sec) or replace a put of the key unfinished past that timeout. The ledger and its names now say keys rather than sessions (_keys_left_open,put_keys_left_for_close,put_keys_left_to_master).The previous head is kept as
backup/pr20473-before-rebase-onto-20884in the fork.Design
BlobBackend(RegionBinding(regions, driver, hold), wake, config)from main, over the driveropen_mooncake_driverreturnsmooncakesection of the store config:workers(default 4),rows_per_call(256),bytes_per_call(128 MiB), each counted per SDK call. Gets go first; once four gets have gone ahead of a waiting put, that put goes next. Puts take at mostmax(1, workers − 1)threads, so with two workers or more a get never waits behind puts alonehints, so main's backend refuses a hinted fetch before the driver is calleddrainanswer Falsebatch_is_existsees it committedblob/mooncake.py; see "Mooncake mapping" belowtest_mooncake_real.py;test_blob_backend_round_trip.py, with a real lender on GPU; main'sdriver_conformancesuite run againstMooncakeDriverover the fake SDK and over the real poolkv_sharing.backends: [{type: mooncake, config: <path>}], with store parameters in the store's own fileblob/mooncake_setup.pyreads a YAML file withblob(key prefix) andmooncakesections; no LLM args are addedDepartures
Mooncake mapping
put calls the session API, one SDK call per step for a part of up to
rows_per_callrows:batch_put_session_start, thenbatch_put_from_multi_buffer_ranges(one scatter for the part), thenbatch_put_session_endfor the rows written whole. The rest getbatch_put_session_revoke, except a row the target may still write into, whose session is left open (see "Put sessions" below).wait()has no timeout. It returns only once every fragment has physically finished.batch_put_from, by contrast, a transfer that times out after 60 s is revoked at once while RDMA writes may still be in flight. A late write could then land in space the master has already given to another object.get is one
batch_get_intoper part.batch_get_into.Put sessions are per key and per client.
put_start_release_timeout_sec;closeto revoke: a session start that raised or miscounted, an end whose first answer was RPC_FAIL or RPC_TIMEOUT (even when the retry ended the session) or that raised or miscounted, a failed revoke, or a put stopped by a raise. They hold no lender memory, sodrainleaves them; one whose revocation fails is freed by the master. A put stopped by a raise is unprovable, soclosenever comes and the master frees its sessions too.Results:
batch_is_exist0)batch_is_exist0)batch_is_exist1Access-end evidence:
batch_get_intoanswers every row and no answer is TRANSFER_FAIL.access_unprovable: a TRANSFER_FAIL on a get, an exception, or a wrong answer count. It keeps its region from being unregistered and makesdrainanswer False. The backend keeps the attempt's slots for good.Capabilities:
get,putandcancel. Cancel ends the rows no worker has taken; the SDK cannot stop a running call.absent_is_miss: the master answers OBJECT_NOT_FOUND for a key it does not hold, before any transfer.device_memory=False). Main's backend refuses a row in any other region withInvalidSubmission, and a lender with no such region withConstructionRejected.multi_span=False):batch_get_intoreads each object into one buffer.max_rows_per_op=None), since the driver cuts parts itself.RegistersRegionsTogether.Shutdown:
draincancels the rows no worker took, ends the worker threads and waits for the running parts, and makes no SDK call.close, once a drain returned True and every region is unregistered, revokes the sessions left for it and then closes the SDK client, even if the revocation raised. The revocation waits on the master, and closing holds the GIL through the unmount, so the owner callscloseoff the executor loop's thread.Scope
961ee1277eeAdd the Mooncake store drivermooncake.pyec7bd8d3742Open a Mooncake store driver from a store configmooncake_setup.pyec265c74665Run the blob backend over a real Mooncake poolTRTLLM_MOONCAKE_REAL=1) againstmooncake_masterand a segment provider, plus the conformance suite on the real store4d311477bb9Round-trip KV blocks between two staging lenders through the blob backend840590063e7Revoke the Mooncake sessions left open in close, so drain makes no SDK calldrain,close321fc530e8dPin a repeated object key over Mooncake and say where NOT_READY departs83496c94f31Close the Mooncake client even when its revocation raises, and pin the ordercloseaaeeb4cdedeWiden NOT_READY to what the store reports, and decline a put of a key left openRowStatuswording (NOT_READY, DECLINED, the class and probe docstrings), README rule 4, the left-open reason inmooncake.pya9f6a74c3cbLeave a Mooncake key open when its first put end went unanswered_end_sessions(the first end answer decides the key), the ledger renamed to keys, main'sRowStatus.DECLINEDwordingThe first two commits add the Mooncake modules to
blob/__init__.py; the first three list them in main'sblob/README.md.Production code: +1497 / −12 lines, of which main's
store.pyis +15 / −11, plus +12 / −3 in the README. Tests: +3177 / −2 lines. Each commit passes the CPU tests present at it.Verification
a9f6a74c3cb, over main's backend:l0_cpu.drain/closetests were checked against mutants: revoking indrainagain, revoking after the client is closed, and closing without thefinallyeach fail them.a9f6a74c3cb: 28 passed, 20 skipped. The conformance suite on the real store now also runs main's extent check; the three batch-registration checks are skipped, as Mooncake registers one region at a time.f2d736b023d.kv_cache/sharing, inl0_a10), written against main's backend: passed in CI'sA10-PyTorch-2stage (L0 pipeline 66862), on the same diff before the rebase onto592bd6fd8d9. It moves each row on its own: the in-memory driver takes one row per operation on a thread of its own, and Mooncake cuts each operation into one-row parts for two workers.Objects of the wrong size. Both cases are checked.
real_client.cpp:5134-5141).real_client.cpp:5169-5180). The driver reports FAULT, its access ends normally, and nothing outside the row is touched.Performance
Driver-owned concurrency against the previous design, on CPU. These numbers were measured at
15c9491ba4a, before the rebase, againstf2d736b023d, and have not been re-run. The backend part of what they measure is now main's; main has since moved the repeated-key check into the call cut, and nothing else on the measured path.Mean submission to settle, torch-like executor:
The executor's own cost, pure-Python executor:
fetch/publish) p99Worker CPU per submission is less than half of what it was. Settle with the pure-Python executor stays as before for 1 × 256, improves for 1 × 4096 (p50 56.7–57.0 → 52.3–52.4 ms), and is slower for 4 × 256 (p50 20.3–20.6 → 24.8–25.0 ms); see "Known limitations".
Throughput on two GB200 nodes over RDMA, measured on
f2d736b023d, not yet on this branch.A row is one KV block (32 tokens) across all layers of a layer group, holding this rank's heads. With TP the rows shrink, so realistic rows are 0.25–2.5 MiB: Llama-3.1-8B at TP4 1 MiB, Llama-3.3-70B at TP4 2.5 MiB, DeepSeek-V3 (MLA) 2.1 MiB.
Please confirm
batch_get_into. SDK 0.3.13 answers TRANSFER_FAIL alike for a failed slice, a status-query error, a partly submitted batch and its 60 s timeout. In the last three cases the transfer may still be running, and the answer does not say which case it is (transfer_task.cpp:746-805, 813-905, 1239-1258).batch_get_intoand asks for a containment profile that is not executor-fatal for a store fetch, since the request can recompute instead.driver_call_limit(region)returns None. One driver call is then a layer group of a submission, cut only where it names an object key twice, and a holder bounds what one unprovable call holds by the size of its leases.a9f6a74c3cb); a start answered RPC_FAIL or RPC_TIMEOUT still frees its key. See the thread for why.Known limitations
list[int], so it converts an array element by element, measured 30 times slower thantolist().TLLM_NUMA_AWARE_WORKER_AFFINITY) does this.probe=False.batch_is_existcould back one later, but in 0.3.13 it answers 0 both for an absent key and for one still being written, takes no timeout, and renews the object's read lease on a hit.MemoryRegionaddsmemory_kind, whoever builds the backend handsRegionBindinghost-memorySlotRegions; the GPU round trip does this.MC_STORE_LOCAL_HOT_CACHE_SIZEother than empty or 0, TCP's legacy framing and the newer transfer engine are refused when the driver is opened.on_changefor the rows still queued after it stopped counting. A drain returns True before that call only once every worker died of a BaseException that left no operation unprovable, which only anon_changeraising against the protocol does; underBlobBackendthe owner cannot close the driver before that call is recorded.Notes
The base is main
592bd6fd8d9, which contains #20117, #20217 and #20884.🤖 Generated with Claude Code
Dev Engineer Review
MooncakeDriveradds a host-memory implementation of theStoreDriverboundary for Mooncake SDK 0.3.13. It supports batched transfers, session management, access tracking, cancellation, and shutdown.open_mooncake_drivervalidates configuration and opens the driver. The PR does not add a runtime factory or wire the driver into the executor.draindoes not call the SDK.closerevokes eligible remaining sessions before closing the client. The driver retains access slots when it cannot prove an operation has ended. This can prevent a successful drain and block executor progress. The supported setup profile is limited; verify its configuration checks and assumptions against deployment needs.QA Engineer Review
The changes add CPU-only fake-SDK driver and setup suites, opt-in real-Mooncake integration tests, and CUDA-gated blob-backend round trips. Driver tests cover status mapping, transfer limits and scheduling, cancellation, drain and close behavior, session cleanup, repeated keys, and unprovable access. Setup tests cover configuration, SDK selection, environment checks, networking, cleanup, and import boundaries. Real-pool tests cover declines, object-size handling, concurrent transfers, and duplicate publishes. Backend tests cover full and partial fetches.
Current test results are unavailable. The supplied author-reported results apply to earlier revisions and do not establish the current status. The changed tests were not found in the scanned CI or manual-QA lists. The Mooncake entries found in those lists refer to other tests. Coverage verdict: needs follow-up.
Per-File QA Perspective
tensorrt_llm/_torch/disaggregation/blob/README.md: Documents the driver, setup module, test requirements, and result classifications. Verify the documented verdict conditions againstRowStatusbehavior.tensorrt_llm/_torch/disaggregation/blob/__init__.py: Clarifies driver and setup responsibilities and lazy SDK imports. Verify that package imports do not loadmooncake.storeprematurely.tensorrt_llm/_torch/disaggregation/blob/mooncake.py: Adds driver transfer, scheduling, access-tracking, cancellation, and shutdown paths. Verify SDK result mapping and session cleanup, especially when access completion is unprovable.tensorrt_llm/_torch/disaggregation/blob/mooncake_setup.py: Adds configuration parsing, SDK and environment validation, master checks, and driver setup. Verify accepted settings and cleanup after failures.tests/test_common/mooncake_utils.py: Adds a subprocess-backed Mooncake segment provider with readiness and cleanup handling. No matching entry was found in the scanned CI or manual-QA lists.tests/unittest/_torch/disaggregation/blob/fake_mooncake.py: Adds a scripted fake SDK for driver tests. Verify that fake session and buffer behavior represents the SDK cases under test. No matching entry was found in the scanned lists.tests/unittest/_torch/disaggregation/blob/test_mooncake_driver.py: Covers driver conformance, transfer scheduling and limits, result mapping, access tracking, and lifecycle errors. No matching entry was found in the scanned lists.tests/unittest/_torch/disaggregation/blob/test_mooncake_real.py: Adds opt-in tests for real-pool transfers, concurrency, and duplicate publishes. No matching entry was found in the scanned lists.tests/unittest/_torch/disaggregation/blob/test_mooncake_setup.py: Covers configuration, SDK setup, networking, failure cleanup, and import boundaries. No matching entry was found in the scanned lists.tests/unittest/_torch/executor/kv_cache/sharing/test_blob_backend_round_trip.py: Adds CUDA-gated full and partial fetch tests. No matching entry was found in the scanned lists.tensorrt_llm/_torch/disaggregation/blob/store.py: ClarifiesNOT_READYandDECLINEDsemantics. Verify that callers handle concurrent or unresolved puts as documented.