| 1 | # HiSparse local KV offload architecture |
| 2 | |
| 3 | Status: experimental |
| 4 | |
| 5 | ## The short version |
| 6 | |
| 7 | There are three different jobs: |
| 8 | |
| 9 | 1. The normal KV cache system manages GPU block pools and tables. |
| 10 | 2. `HiSparseCoordinator` manages logical host blocks, source-prefix identity, and |
| 11 | host/GPU residency transitions. |
| 12 | 3. `HiSparseConnector` carries residency work between scheduler and worker; |
| 13 | `HiSparseWorker` coordinates transfers, while per-cache |
| 14 | `HiSparseRuntime` objects own host/hot views and GPU replacement state. |
| 15 | |
| 16 | Neither worker-side object allocates or frees logical blocks. HMA provides the |
| 17 | GPU allocation shared by the resident and hot groups; it does not manage CPU |
| 18 | memory or KV identity. |
| 19 | |
| 20 | ```text |
| 21 | request ────► HiSparseCoordinator ── source blocks + residency policy |
| 22 | │ |
| 23 | ├──► KV cache manager ── resident/hot GPU leases (HMA) |
| 24 | │ |
| 25 | └──► HiSparseConnector ── host bytes + copies + GPU LRU |
| 26 | ``` |
| 27 | |
| 28 | This is a local KV connector. When P/D or another offload connector is also |
| 29 | configured, `MultiConnector` composes it with `HiSparseConnector`. |
| 30 | |
| 31 | `host_pool_gib` is configured on `HiSparseConnector` and is the usable |
| 32 | host-cache capacity per data-parallel replica, not a node-wide memory budget. |
| 33 | Tensor-parallel ranks hold replicated views of that logical cache. Those views |
| 34 | may use private per-rank backing or one shared |
| 35 | physical allocation without changing the configured capacity. Physical host |
| 36 | memory consumption is therefore topology- and implementation-dependent. The |
| 37 | realized capacity may be slightly smaller because the budget is rounded down |
| 38 | to complete host blocks. |
| 39 | |
| 40 | ## Ownership |
| 41 | |
| 42 | | Thing | Owner | What “owner” means | |
| 43 | | --- | --- | --- | |
| 44 | | HiSparse source and prefix identity | `HiSparseCoordinator` | maps tokens to logical host blocks | |
| 45 | | Resident GPU block leases | normal KV cache manager | allocates and frees HMA blocks | |
| 46 | | Resident block tables | normal KV cache manager | tells attention where resident pages are | |
| 47 | | Residency transitions | `HiSparseCoordinator` | plans spill-before-free transactions | |
| 48 | | Logical host block allocation | `HiSparseCoordinator` | owns the separate CPU block pool and its lifecycle | |
| 49 | | Pinned host-pool lifecycle | `HiSparseWorker` | worker-wide backing and teardown | |
| 50 | | Per-cache host view and hot contents | `HiSparseRuntime` | binds host/hot storage and fills cache-manager-provided hot leases | |
| 51 | | Hot row map and LRU | `HiSparseRuntime` | resolves hits and chooses victims on GPU | |
| 52 | | Resident-cache route | `HiSparseCacheHandle` | exposes resident or host/hot resolution to attention | |
| 53 | | Sparse attention | attention backend | consumes a device cache and physical row IDs | |
| 54 | | HMA | allocator | provides GPU capacity; owns no KV meaning | |
| 55 | |
| 56 | The key distinction is logical allocation versus contents. `HiSparseCoordinator` |
| 57 | owns host block IDs and their request/prefix associations. `HiSparseWorker` and |
| 58 | its per-cache runtimes own the corresponding bytes. The normal cache manager |
| 59 | sees only device pools. |
| 60 | The source group has `block_pool_id=None`; device-pool consumers must narrow it |
| 61 | before indexing, so host ownership cannot masquerade as a numeric GPU pool. |
| 62 | |
| 63 | For single-node MP tensor parallelism, every TP worker maps the same pinned host |
| 64 | pool and uses the same block and layer offsets. MLA source KV is replicated |
| 65 | across TP ranks, so this stores one physical copy instead of one copy per rank. |
| 66 | TP rank 0 writes the shared host pool; peers wait on its IPC events before |
| 67 | reading it. Other executor and parallel layouts retain private per-rank pools. |
| 68 | |
| 69 | The shared layout backs the per-replica logical capacity with one physical pool; |
| 70 | the private layout allocates one physical pool per rank. Physical pool size |
| 71 | includes block-stride alignment. |
| 72 | |
| 73 | ## Code boundary |
| 74 | |
| 75 | ```text |
| 76 | scheduler process worker process |
| 77 | |
| 78 | HiSparseConnector HiSparseConnector |
| 79 | └─ HiSparseCoordinator └─ HiSparseWorker |
| 80 | │ │ |
| 81 | │ connector metadata ├─ host bytes |
| 82 | │ - page transfers ├─ copy scheduling |
| 83 | │ - block-table replacements └─ per-layer hot state |
| 84 | └───────────────────────────────────────────────►│ |
| 85 | ◄──────── connector worker metadata ─────────────┘ |
| 86 | enqueued and completed transfer IDs |
| 87 | ``` |
| 88 | |
| 89 | The command travels in `kv_connector_metadata`; transfer updates return in |
| 90 | `KVConnectorOutput.kv_connector_worker_meta`. The model runner does not |
| 91 | interpret page transfers. Enqueue acknowledgements let the scheduler release |
| 92 | source leases in stream order; completion acknowledgements publish the copied |
| 93 | host pages. |
| 94 | |
| 95 | ## Resident device pages |
| 96 | |
| 97 | Resident pages are intentionally outside `HiSparseRuntime`. |
| 98 | |
| 99 | KV-cache initialization binds cache-manager allocations to the attention-facing |
| 100 | `HiSparseCacheHandle` before constructing `HiSparseWorker`. That same |
| 101 | handle's runtime retains the resident source index needed by a transfer plan. |
| 102 | There is no second resident object or registration wrapper. |
| 103 | |
| 104 | ```text |
| 105 | KV cache setup |
| 106 | │ |
| 107 | ├─ bind resident allocation ──► HiSparseCacheHandle |
| 108 | │ cache + block table + slot mapping |
| 109 | │ |
| 110 | ├─ bind host/hot allocation ──► HiSparseRuntime |
| 111 | │ host + hot + GPU LRU |
| 112 | │ |
| 113 | └─ register cache handles ────► HiSparseWorker |
| 114 | step-level transfers |
| 115 | |
| 116 | HiSparseWorker registers the same HiSparseCacheHandle objects directly |
| 117 | ``` |
| 118 | |
| 119 | Attention construction links each layer to the most recent layer that actually |
| 120 | owns an indexer. This releases a follower's duplicate LRU tensors before GPU |
| 121 | memory profiling. Cache binding only attaches storage; it does not infer |
| 122 | semantic groups from the physical packed-tensor order. The construction cursor |
| 123 | is discarded with the worker's pinned state. |
| 124 | |
| 125 | Every HiSparse decode batch uses the same fused resolver. It checks resident |
| 126 | pages first, then hot rows, then pinned host memory. A resident hit exits inside |
| 127 | the kernel before hot-LRU lookup or host copying; there is no framework-level |
| 128 | residency route or separate CUDA graph. No CPU decision is added to the decode |
| 129 | path. The resolver consumes the existing graph-stable request mapping from |
| 130 | attention metadata; neither the worker nor individual cache handles keep a |
| 131 | duplicate mapping. |
| 132 | |
| 133 | Speculative decoding resolves all verification rows of a request in one pass: |
| 134 | one block resolves the union of the rows' top-k against the request's hot-cache |
| 135 | state, so rows that select the same host row share its hot row and no row |
| 136 | evicts a hot row another row of the step still reads. Draft layers write their |
| 137 | rows after the target forward, so their host mirror runs at the start of the |
| 138 | next step, after the drafter, and the step's page transfers are submitted |
| 139 | behind it. |
| 140 | |
| 141 | ## P/D import target |
| 142 | |
| 143 | The decoder chooses the landing target once per request from the normal cache |
| 144 | admission calculation. If the complete imported prefix fits the device pools, |
| 145 | NIXL transfers it directly into resident GPU pages. Otherwise, if the fixed |
| 146 | host-backed GPU footprint and host source blocks fit, the request imports into |
| 147 | the host tier. There is no context-length threshold or other heuristic, and a |
| 148 | request waiting for capacity retains its choice across admission retries. |
| 149 | |
| 150 | A host import reads through a bounded decoder-GPU staging pool before copying |
| 151 | into registered host memory. Pages needed immediately are mirrored into their |
| 152 | resident destinations during that copy. Both landing targets then use the same |
| 153 | fused decode resolver described above. |
| 154 | |
| 155 | ## Indexer KV offloading |
| 156 | |
| 157 | HiSparse does not keep a private CPU copy of indexer KV. The indexer remains a |
| 158 | normal prefix-cacheable GPU cache group. If `OffloadingConnector` is configured |
| 159 | with HiSparse, it stores and restores that group through the generic KV |
| 160 | offloading path; HiSparse continues to own only the sparse MLA host tier. |
| 161 | |
| 162 | The two prefix sources can have different hit lengths. When the HiSparse host |
| 163 | prefix extends beyond the GPU-resident indexer prefix, the scheduler asks |
| 164 | `OffloadingConnector` to restore only the missing indexer suffix, capped at the |
| 165 | host prefix boundary. If that suffix is unavailable, all groups fall back to |
| 166 | the shorter prefix they share. NIXL P/D transfers continue to place indexer KV |
| 167 | directly in its GPU group. |
| 168 | |
| 169 | ## Spill transaction |
| 170 | |
| 171 | A resident block cannot be reused until its contents have been handed to the |
| 172 | worker. |
| 173 | |
| 174 | ```text |
| 175 | HiSparseCoordinator HiSparseWorker |
| 176 | │ │ |
| 177 | │ pin source and destination leases │ |
| 178 | │── SparseKVPageTransfer ─────────────►│ |
| 179 | │ │ enqueue GPU-to-host copy |
| 180 | │◄── enqueued transfer ID ────────────│ |
| 181 | │ replace resident table entry │ |
| 182 | │ release resident lease to HMA │ |
| 183 | │ │ copy reaches its event |
| 184 | │◄── completed transfer ID ───────────│ |
| 185 | │ mark host page valid │ |
| 186 | │ release destination host lease │ |
| 187 | ``` |
| 188 | |
| 189 | “Enqueued” means the copy has entered the worker stream. Stream ordering makes |
| 190 | it safe to reuse the resident GPU block for later work, but the host page is |
| 191 | not yet published. “Completed” means the worker has observed the copy's event; |
| 192 | only then does the coordinator publish the host page for prefix reuse and |
| 193 | release its destination lease. A host-write event separately protects direct |
| 194 | CPU readers from writes already queued on the accelerator. |
| 195 | |
| 196 | The worker transfer contains only its transfer ID and physical copy |
| 197 | coordinates. Request identity and logical page state remain in the scheduler. |
| 198 | |
| 199 | ## Hot lookup and LRU |
| 200 | |
| 201 | The NVIDIA CUDA path keeps replacement entirely on the accelerator: |
| 202 | |
| 203 | ```text |
| 204 | top-K logical positions |
| 205 | │ |
| 206 | ▼ |
| 207 | resident page? ── yes ──► resident physical row |
| 208 | │ no |
| 209 | ▼ |
| 210 | hot row? ──────── yes ──► existing hot physical row + update GPU LRU |
| 211 | │ no |
| 212 | ▼ |
| 213 | choose GPU LRU victim ──► copy pinned host row ──► hot physical row |
| 214 | ``` |
| 215 | |
| 216 | ROCm is not currently supported because the fused HiSparse cache operations are |
| 217 | implemented only by CUDA kernels. A future platform-specific worker may provide |
| 218 | the same command, output, and cache-resolution boundaries. |
| 219 | |
| 220 | ## Main classes |
| 221 | |
| 222 | | Class | Inherits / implements | Responsibility | |
| 223 | | --- | --- | --- | |
| 224 | | `HiSparseCoordinator` | plain scheduler component | host allocation, source prefixes, resident leases, and spill state machine | |
| 225 | | `HiSparseConnector` | `KVConnectorBase_V1`, `SupportsHMA` | scheduler/worker metadata and lifecycle boundary | |
| 226 | | `HiSparseResidentManager` | `SingleTypeKVCacheManager` | normal block-pool bookkeeping with host-backed holes | |
| 227 | | `PagedCacheView` | immutable data object | shared resident/hot HMA tensor binding | |
| 228 | | `HiSparseWorker` | connector-owned worker component | worker-wide transfer scheduling and host-pool lifecycle | |
| 229 | | `HiSparseRuntime` | plain worker-owned component | per-cache host/hot tensors, GPU LRU, and fused resolution | |
| 230 | | `HiSparseCacheHandle` | plain attention component | resident view and fused cache resolution | |
| 231 | | `SparseKVOffloadCommand` | dataclass | opaque scheduler-to-worker work | |
| 232 | |
| 233 | ## Performance invariants |
| 234 | |
| 235 | - Resident hits bypass hot-LRU lookup and host copies inside the fused resolver. |
| 236 | - Hot lookup, victim selection, and LRU updates stay on the GPU. |
| 237 | - A hot miss still copies directly from registered pinned host memory. |
| 238 | - Top-K resolution stays inside the attention invocation and remains graph |
| 239 | capturable. |
| 240 | - Compatible layers still share one miss plan. |
| 241 | - Index-sharing followers release their private LRU state before memory sizing. |
| 242 | - Indexer KV is untouched by HiSparse unless a generic KV offloader is configured. |
| 243 | - Resident and hot leases can still share one packed HMA allocation. |
| 244 | - No device scalar readback or CPU/device metadata round trip is added. |
| 245 | - The abstraction wraps the fused kernel; it does not add another kernel |
| 246 | launch. |
| 247 | - When HiSparse is disabled, the scheduler does not construct an offload |
| 248 | command or empty update table. |
| 249 | |
| 250 | ## What remains platform-specific |
| 251 | |
| 252 | The command/result and attention-layer boundaries can be shared. The host |
| 253 | allocator, copy implementation, hot layout, and replacement policy should stay |
| 254 | platform-specific. NVIDIA uses the current accelerator LRU and fused host/hot |
| 255 | kernel. ROCm is not currently supported; AMD or other accelerator backends can |
| 256 | implement their own worker without forcing NVIDIA's policy into the shared |
| 257 | boundary. |