Repository navigation
feat(remote_cxl): read hints, so the memory device can prefetch KV pages before the load reads them - #124
Closed
youngrok-XCENA wants to merge 9 commits into
Closed
feat(remote_cxl): read hints, so the memory device can prefetch KV pages before the load reads them#124youngrok-XCENA wants to merge 9 commits into
youngrok-XCENA wants to merge 9 commits into
Conversation
…mory Some memory answers every address but not at one speed: a device keeps a small fast cache in front of slower media, so data is brought in before it is read and written back after it is written. Maru stays device-neutral and lets a separately installed package control only when its transfers run: - An access policy (entry-point group maru.access_policies), selected by name in MaruConfig.access_policy, runs the READs of a load in order by calling the transfer Maru hands it, prepares and finishes WRITEs, and says when a page given back may be reused. - A region extension (maru.region_extensions), selected with the remote CXL agent's --region-extension, sees the regions the agent maps, may refuse one, and may keep one out of use after the NIC lost it until its own work on it ended; the sweep and close ask it again. - Policies reach their extension over the connection that mapped a region. The agent checks every range against what that connection holds, the handle, the region bounds and the epoch, and refuses a write prepared with a read handle, before the extension sees the message. A policy that fails gives an outcome Maru already has: a miss, a key not stored, a page or region kept out of use. Without a policy nothing changes. The vLLM connector passes maru_access_policy(_config) to the worker only.
…acts - A late WRITE's staging slots go back once NIXL released it; only its pages wait for the policy (_late_pages), so a policy that cannot take them yet no longer keeps the slots or the unconfirmed-transfer state. - After a transfer raised, the policy cannot transfer into the same slots again, nor after read returned. - A worker with a policy refuses to message an agent without a region extension (hello says whether it has one). - Pages given back are offered to the policy at most every 10 ms, unless a store found no free page; a policy that keeps raising is logged once. - The agent refuses a non-boolean write flag. - The contracts say which hooks run under locks, that extension hooks must be thread-safe, that retire may name a region mapped never saw, and how a prepared but unwritten page ends.
Report the extension and the reason, give the node lock back and exit 1 instead of leaving a traceback with the lock held.
…s bytes early An access policy hears of a read only when the worker starts it: with memory that keeps a fast cache in front of slower media, the bytes are not ready and the READs follow the device's fill chunk by chunk. The engine knows which keys a request will load much earlier. - The scheduler connector gives the backend a read hint once per request: at the first lookup that finds an asynchronous load, or, with maru_read_hint_on_arrival, as soon as vLLM's engine receives the request (EngineCore.preprocess_add_request). Its end follows request_finished. - The backend queues the hint for one thread per client: a probe that also returns where each hit is (with a read handle, no lease), then the policy's optional read_hint / read_hint_end hooks with a channel to each region's remote CXL agent. Callers never wait. Without such a policy nothing runs. - The remote CXL agent accepts a hint for a region it maps from a connection that holds none (the scheduler has no data path), never as a write. - The worker names the request its reads load for (set_read_request), and the policy's read hook receives it. - The scheduler's handler gets the access policy too (it only hears hints), and a prompt's chunk keys are hashed once for the lookup, the hint and the load.
…takes hints - Never drop an end; only hints are capped (with a warning when dropped). - Ignore hints after close; close hint connections only after the thread ended. - Pass request_id to policy.read only for policies with read hints. - Refuse hints for regions no connection holds; document hints as read-only. - Scheduler: no hint state or thread unless storage takes hints; reuse an arrival hint's chunk keys instead of an LRU cache over whole prompts; turn maru_read_hint_on_arrival off without maru_async_load.
# Conflicts: # maru_vllm/connector.py
…the Maru/package boundary Name the contracts at the boundary (Maru to policy, policy to Maru through the channel, agent to extension), draw who calls whom, use prefetch for what the device does, and state that the need is wider than remote_cxl.
1 of 4 tasks
Collaborator
Author
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.
🤔 Background & Motivation (Why)
With this PR, Maru sends the memory node's device a read hint: where a request's KV pages are, looked up before the load starts. The device can prefetch those pages (on a CXL-SSD, from the SSD into DRAM), and the load waits until each page is ready before its RDMA READ. Writes use the same hooks to have pages held in DRAM and written back.
This PR lets a device package learn about Maru's transfers in advance and hold each one until its pages are ready. It adds two vendor-neutral extension points: an access policy on the GPU node that decides when each transfer runs, and a region extension in the memory node's agent that drives the device. Any memory that needs pages prepared before access can use them; CXL-SSD is the case that motivated them, and the measurements below cover its read side.
With an out-of-tree package for one CXL-SSD, the extra time to first token of a reused prefix, compared with the same device holding everything in its cache, was:
The rows come from different stages of a separate prototype, not this branch, so they are not one controlled comparison.
🏗️ Design Changes
Grey boxes are Maru; orange boxes are the installed package, selected by name. Every message between the package's two halves passes through Maru.
flowchart TB subgraph gpu["GPU node"] direction LR pol["access policy<br/>(maru.access_policies)"] mc["Maru in vLLM<br/>(scheduler, workers)"] end subgraph mem["memory node"] direction LR ms["maru-server"] ag["remote CXL agent"] ext["region extension<br/>(maru.region_extensions)"] dev[("CXL-SSD<br/>DRAM cache + SSD")] end mc -->|"① pages to be read or written<br/>(read hint, then transfer)"| pol pol -->|"② when to transfer,<br/>messages for the extension"| mc mc -->|"lookup, lease"| ms mc -->|"carries messages"| ag ag -->|"③ after checking the pages"| ext ext -.->|"prefetch, hold,<br/>write back"| dev mc ==>|"RDMA READ / WRITE"| dev classDef maru fill:#e8e8e8,stroke:#555,color:#111 classDef pkg fill:#fbe3cc,stroke:#c06000,color:#111 class mc,ms,ag maru class pol,ext pkgremote_cxl: a CXL-SSD attached to the GPU node's own host has the same problem, and engines other than vLLM also know their requests early. This PR coversremote_cxlfirst because there nothing else can help: the GPU node cannot reach the device software at all, and Maru's storage client itself issues every RDMA transfer, so it can hold one back. Read hints are sent only by the vLLM connector; LMCache and SGLang onremote_cxlget the policy's timing but no hints. On the local path the engine's connector, not Maru, copies the bytes, and the existing handler plugins see a batch only after its lookup; bringing that path under the same policy is left for a follow-up.remote_cxlloads do not reach them.📝 Implementation Details
Hook contracts, locks and failure outcomes are documented in the module docstrings and
docs/source/design_doc/access_policies.md.maru_handler/storage/access_policy.py, entry-point groupmaru.access_policies, selected bymaru_access_policy/maru_access_policy_config(remote_cxlonly). Hooks:read(channel, ranges, transfer),write_batch_limit,prepare_write,written,completed_write,reusable, optionalread_hint/read_hint_end. A policy callstransfer(start, end)in order until every range is read. It sends messages withAccessChannel.request(action, ranges, ...);actionis opaque to Maru, and a message may be delivered twice. With a policy, READs are grouped per region, and WRITEs are batched bywrite_batch_limit.maru_remote_cxl_agent/extension.py, entry-point groupmaru.region_extensions, selected by--region-extension. Hooks:validate_accessandmappedwhen a region is first mapped,request,retire,close.requestandretirerun under the agent's lock. A region the extension still holds stays out of use untilretireanswers True. The agent exits if the named extension cannot load.service.py): every range must match its region's handle and fit in it, and a read handle cannot prepare a write.hint: trueis accepted from any connection for a mapped region that some connection holds, and never withwrite, so the extension must treat every hint as read-only.remote_cxl/hints.py,client.py,router.py,handler.py):MaruHandler.hint_read,end_read_hint,set_read_request. Amaru-read-hintsthread per storage client probes maru-server and calls the policy. It opens its own authenticated connections to the agents, so the scheduler needs the agent key file. Above 1024 queued hints, new hints are dropped; ends are never dropped.replica_directory.py):probewithlocations: truealso returns each remote CXL hit's location and its region's read handle, without a lease.maru_vllm/connector.py): hints at the first asynchronous hit and ends the hint inrequest_finished. Hints needmaru_async_load.maru_read_hint_on_arrivalmonkeypatchesEngineCore.preprocess_add_request(vLLM 0.22.1 and 0.32.0); even when storage takes no hints, it installs the wrapper and starts one idlemaru-arrival-hintthread. The worker bracketsbatch_retrievewithset_read_request.✅ Tests
tests/unit/test_remote_cxl_access_policy.py,tests/unit/test_remote_cxl_read_hints.py(real directory, agent and clients with a fake policy and extension: leading hits per region, once per request, end routing, agent checks, probe locations),tests/unit/test_vllm_read_hints.py(scheduler hint points, arrival hook, no hint after the end). Also: ends kept and dropped hints warned when too many wait, hints after close ignored, a hint for a region nobody holds refused, a three-argument policyread, no hint work when storage takes none, the arrival setting without asynchronous loading. Full unit suite: 1770 passed, 4 skipped.🔗 Related Issues (optional)
🌿 Related PRs (optional)
remote_cxlbackend (feat: add the remote_cxl storage backend (CXL memory of memory nodes over RDMA) #107).🌿 Related Branches (optional)
📦 Release Note (for auto-generation / write in English)
NEW
CHANGED
FIXED
IMPORTANT NOTES