Repository navigation
[TRTLLM-16186][feat] Define backend contract for KV cache transfers - #20117
Shixiaowei02 merged 3 commits into
Conversation
chienchunhung
left a comment
There was a problem hiding this comment.
Notes:
- View metadata: Keep original group and row metadata stable until result consumption, including after early lease release. Keep this lifetime separate from payload protection.
- Hint ownership: State whether the backend copies required hint fields or the caller keeps them stable until use ends.
|
From the Store Backend integration perspective, the current contract supports our example backend. Overall LGTM. |
|
/bot run --disable-fail-fast |
|
/bot kill |
|
PR_Github #78104 [ run ] triggered by Bot. Commit: |
|
PR_Github #78106 [ kill ] triggered by Bot. Commit: |
|
PR_Github #78104 [ run ] completed with state |
|
PR_Github #78739 [ run ] triggered by Bot. Commit: |
|
PR_Github #78739 [ run ] completed with state
|
5a67a46 to
2d05d32
Compare
Define, in tensorrt_llm/_torch/disaggregation/base, the contract between a lease holder, which holds leases from the KV cache lender, and a backend that moves the lent rows to or from a peer or a store. base exports Backend, Capabilities, Attempt, Outcome, View, LayerGroupRows, SlotRegion, MemoryKind, Hint, WakeFn, InvalidSubmission and SubmissionRejected, plus TokenRange and CacheKind, which are not part of the contract. Backend replaces Fetches and Publishes. - MemoryKind, in base/memory.py, is the kind of memory a region lies in: HOST is CPU-addressable and DEVICE is the GPU holding the cache. A backend registers only the regions whose SlotRegion.memory_kind it can access, and rows in a region it has not registered are rows it does not recognize. A row given by an address occupies one slot. - A view's groups and their arrays are an immutable snapshot that stays valid after the lease is returned; the lease bounds only the payload's protection. A hint describes only the other end and is an immutable snapshot; which rows this end reads or writes comes from the view alone. - fetch and publish state what the lease holder guarantees about the memory the view locates until the submission's quiesced() is true, and no two rows of a fetch locate overlapping memory. A raise other than the two rejections may have let access to the memory escape, and the lease holder then keeps the lease until shutdown() returns an empty set. quiesced() covers the whole submission, so the leases lent for a backend that may leave a row not proven ended are kept small. - Conclusion and quiescence do not depend on calls to poll() or quiesced(): a backend advances its own work. A backend calls WakeFn only after every change it reports is visible: an outcome or an access end through poll() or quiesced(), a change an optional protocol names where that protocol says. - An instance serves the submissions of one lender's domain, and a process may hold several. A backend's methods and its attempts' belong to one call sequence. - A backend given rows it does not recognize rejects the submission with InvalidSubmission and never skips them. Delivery masks are bool arrays; anything else raises TypeError. Native's per-request types, Chunk, CacheExtent, Delivered, Failed, Cancelled and its own Attempt and Outcome, move unchanged from base/backend.py to native/extent.py; native and transceiver.py change only their imports, and base/transfer.py imports Chunk from native/extent.py for type checking only. The contract tests import nothing of the lender, and they pin that base imports native only for type checking. Signed-off-by: Xiaowei Shi <39303645+Shixiaowei02@users.noreply.github.com>
Delivery masks are of dtype bool; anything else raises TypeError. Outcome keeps a read-only bool mask whose type is exactly numpy.ndarray as it is, and its giver writes neither it nor memory it shares afterwards; any other mask is copied. A read-only subclass of numpy.ndarray, such as a masked array, is copied into a plain array, since all() on a masked array skips masked rows. The contract tests add a kept read-only mask beside a copied writable one, a read-only masked array, and read-only int and float masks. Signed-off-by: Xiaowei Shi <39303645+Shixiaowei02@users.noreply.github.com>
A backend can then mark the masks it is given read-only and hand them to Outcome, which keeps a read-only bool mask without copying it. Signed-off-by: Xiaowei Shi <39303645+Shixiaowei02@users.noreply.github.com>
2d05d32 to
8ac96cf
Compare
|
/bot run --disable-fail-fast |
|
PR_Github #79201 [ run ] triggered by Bot. Commit: |
|
PR_Github #79201 [ run ] completed with state
|
… 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, over the in-memory store and, with TRTLLM_MOONCAKE_REAL=1, a real Mooncake pool. - A fetch whose later blocks fault marks only the delivered prefix, so resume_bounds stops at the first gap. The in-memory store and driver come from NVIDIA#20217's testing package. 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 NVIDIA#20117's 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. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
|
/bot run --disable-fail-fast |
|
PR_Github #79469 [ run ] triggered by Bot. Commit: |
|
PR_Github #79469 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #79589 [ run ] triggered by Bot. Commit: |
|
PR_Github #79589 [ run ] completed with state |
… 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. - A fetch whose later blocks fault marks only the delivered prefix, so resume_bounds stops at the first gap. The in-memory store and driver come from NVIDIA#20217's testing package. 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 NVIDIA#20117's 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. 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. - A fetch whose later blocks fault marks only the delivered prefix, so resume_bounds stops at the first gap. The in-memory store and driver come from NVIDIA#20217's testing package. 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 NVIDIA#20117's 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. 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. - A fetch whose later blocks fault marks only the delivered prefix, so resume_bounds stops at the first gap. The in-memory store and driver come from NVIDIA#20217's testing package. 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 NVIDIA#20117's 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. Signed-off-by: Chuang Zhu <111838961+chuangz0@users.noreply.github.com>
Description
This pull request refactors the KV cache transfer backend by separating and clarifying the contract between the backend and the native implementation. It introduces a new
extent.pymodule for native transfer types, movesMemoryKindto its own module, and updates imports throughout the codebase and tests to use the new structure. The changes improve modularity and make the separation between backend contract and native implementation clearer.Backend contract and native separation:
native/extent.pyto define native-specific transfer types (Chunk,CacheExtent,Delivered,Failed,Cancelled,Attempt,Outcome) and removed these from the backend contract (base/__init__.py). Imports in native modules and tests are updated to usenative.extentfor these types. [1] [2]base/__init__.py) to only export types that are part of the contract, and clarified the module docstring to reflect the new structure.Memory kind modularization:
MemoryKindto a newbase/memory.pymodule, with its own docstring and enum definition, and updated the backend contract to import it from there. [1] [2]Import and usage updates across codebase:
Chunk,CacheExtent, and related types fromnative.extentinstead of the backend contract, ensuring clear separation of contract and implementation. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14]Test and contract validation updates:
Attemptinstance instead ofPublishes, aligning with the new contract.These changes collectively improve code organization, clarify the backend-native interface, and ensure consistency across the codebase.