Repository navigation
Conversation
ce10e69 to
9349d78
Compare
9349d78 to
bbe189c
Compare
Every per-layer CPU cache of a rank is now a 4 KiB-aligned view into one
host mapping. A new opt-in key only selects the page source:
"kv_connector_extra_config": {
"cpu_bytes_to_use_per_rank": ...,
"cpu_hugepage_block_size": "1GB", # or "2MB"; default: ordinary pages
"cpu_numa_strict": false
}
For either source the mapping is anonymous MAP_PRIVATE with
MADV_DONTFORK, placed on the GPU's NUMA node (preferred, or bound with
cpu_numa_strict), populated up front with MADV_POPULATE_WRITE across up
to 16 threads (first-touch fallback for ordinary pages on pre-5.14
kernels), and pinned with a single cudaHostRegister, so every block copy
stays inside one registration. A single registration of >= 512 GiB fails
on CUDA (vllm-project#51081); per-rank buffers must stay below that.
- HugeTLB fails closed: a short host pool fails at mmap, and a short
hugetlb cgroup limit or node pool fails at populate, instead of
falling back to 4 KiB pages or raising SIGBUS later. No hugetlbfs mount
is needed; under Kubernetes only the hugepages-<size> limit applies.
The key name matches the OffloadingConnector proposal (vllm-project#45693).
- MADV_DONTFORK: fork() neither copies pinned pages nor leaves them
copy-on-write under a GPU mapping.
- The block count still matches the scheduler; the mapping rounds up to
whole pages.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Summer Yang <girasoleyang@gmail.com>
bbe189c to
5c5a882
Compare
YannikHinteregger
left a comment
There was a problem hiding this comment.
Thanks for the contribution! I left some inline comments and hope I provided enough context.
Could you please also include benchmarks showing total engine startup time before and after this change
| try: | ||
| # Fault every page now: a cgroup or pool shortfall becomes an | ||
| # error here instead of SIGBUS or an OOM kill while serving. | ||
| self._mmap.madvise(_MADV_POPULATE_WRITE) |
There was a problem hiding this comment.
Could we keep one buffer but register it in chunks? A single registration of 512 GiB or more fails on CUDA (#51081) so large per-rank buffers would fail at startup.
We’re also working on moving CPU pinning into a background thread. That path needs smaller chunks with a short yield between calls because a long registration can block other CUDA calls and delay inference (CUDA docs)
There was a problem hiding this comment.
addressed in 76e2461
Currently left this in main thread, but can definitely move to a background thread if needed.
| flags, policy = mapping_info(buf.tensor.data_ptr()) | ||
| assert "dc" in flags # MADV_DONTFORK sets VM_DONTCOPY | ||
| assert ("ht" in flags) == hugepages | ||
| assert policy == "prefer:0" |
There was a problem hiding this comment.
Could you please mark this test to skip on systems where NUMA placement is unsupported or blocked
| flags |= _MAP_HUGETLB | (hugepage_size.bit_length() - 1) << _MAP_HUGE_SHIFT | ||
| try: | ||
| # HugeTLB reserves every page from the host pool here. | ||
| self._mmap = mmap.mmap(-1, self.nbytes, flags=flags) |
There was a problem hiding this comment.
Switching from torch.zeros to mmap bypasses PyTorch’s allocator, which honors THP_MEM_ALLOC_ENABLE=1. Could we apply MADV_HUGEPAGE ourselves when this flag is enabled and explicit HugeTLB pages are disabled?
|
This pull request has merge conflicts that must be resolved before it can be |
…ies on SDMA From HIP 7.16, hipMemcpyBatchAsync without hipMemcpyFlagExtPreferCE runs the batch as a blit kernel on the CUs (__amd_rocclr_copyBufferBatch), which slows concurrent decode like the Triton copier does. Pass the flag in the batch copy attributes on 7.16+, so the copies stay on the SDMA engines at the same bandwidth. CUDA is unchanged (flags stay 0). Co-authored-by: Taoyu Zhu <zhutaoyu97@gmail.com> Co-authored-by: v <v@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Update the PR onto main while preserving its published history. Register host memory in bounded chunks, roll back partial pinning on failure, preserve transparent hugepage advice, and skip NUMA placement checks when placement is unsupported or blocked. Document HugeTLB configuration. The resulting tree matches the locally tested rebased revision. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Summer Yang <girasoleyang@gmail.com>
|
Documentation preview: https://vllm--60130.org.readthedocs.build/en/60130/ |
|
/ci run |
…aries Allocate only the resolved CPU cache size and inherit the worker's NUMA memory policy. Keep pin/unpin paired per range and reuse HiSparse block-safe registration boundaries for chunked CPU offload pinning. Remove the independent HIP copy-engine optimization, tracked in vllm-project#65559. Validation: pin/unpin regression tests passed (2 cases); original registration leak reproduced; Ruff and diff checks passed. Registration-boundary test collection was blocked by missing FlashAttention CUDA extensions. GPU and end-to-end tests were not run. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Move pin_tensor, unpin_tensor, and host_registration_ranges into vllm.utils.host_memory. Update HiSparse, CPU offload, and disk offload to import the shared helpers directly. The three function ASTs are unchanged. The existing registration/teardown regression test passed both cases; Ruff and diff checks passed. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
|
This pull request has merge conflicts that must be resolved before it can be |
CUDA on GB200 rejects a HugeTLB registration ending inside a backing page under NUMA binding. Pass the buffer page size to the shared boundary calculation, preserving the OS-page default for HiSparse. Use a real worker test config, cover both HugeTLB pool sizes, and stabilize the NUMA integration test's metric label for cached model paths. CUDA validation on Slurm job 45182: 16 targeted cases passed, one unsupported 1 GiB-page case skipped. Four HugeTLB ranks ran for 120 seconds with zero mismatches. The 150 GiB ordinary-page soak completed 18490 round trips and 36980 forks. TP=2 Qwen3-0.6B outputs matched offload-disabled results; worker/engine kills, SIGTERM, restart and buffer-level exits recovered resources within 5 seconds. HugeTLB settings were restored and the allocation released. Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Reuse HOST_REGISTER_CHUNK_BYTES (256 GiB) as the shared registration chunk limit. With a 1 GiB cap, 1 GiB HugeTLB pages failed at startup for any block size that does not divide 1 GiB (for example MLA layouts); the larger cap leaves room for a boundary at lcm(block, page). HiSparse keeps the same limit. Replace HostBuffer with alloc_host_buffer(): torch.frombuffer keeps the mapping alive. Reuse the existing MADV_POPULATE_WRITE helper and its pre-5.14 fallback, accept only the documented page sizes, and drop the unused timing log, pin yield and stale connector comment. Shorten the docs. Tests: check page rounding, pool consumption and fail-closed behavior instead of smaps flags and kernel zeroing; make the worker test a store/load round trip without private attributes; move hugepage helpers to conftest; drop the single-fix pin/unpin test and the unrelated integration-test change. Validation: pre-commit (ruff, mypy) passed; test_host_buffer.py 7 passed, 2 skipped (no free hugepages). test_worker.py and tests/v1/worker/test_utils.py could not be collected locally (missing FlashAttention CUDA extensions). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
ivanium
left a comment
There was a problem hiding this comment.
Thanks for the PR! @GirasoleY I made a bunch of small simplifications; please help double check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>

Overview
Functionality
SimpleCPUOffloadConnectorallocates each rank's CPU KV caches as views into one host mapping sized to the resolved KV blocks, rounded up to the selected page size.cpu_bytes_to_use_per_rankremains the cache budget. Registration is split into chunks of at most 256 GiB, the limit HiSparse already uses, without bisecting a KV block or backing page.cpu_hugepage_block_sizekey ("1GB"or"2MB") backs that mapping with explicit HugeTLB pages; without it, ordinary pages are used.fork()(MADV_DONTFORK), so child processes can't make pinned pages copy-on-write.{"kv_connector": "SimpleCPUOffloadConnector", "kv_role": "kv_both", "kv_connector_extra_config": {"cpu_bytes_to_use_per_rank": 137438953472, "cpu_hugepage_block_size": "1GB"}}Performance evidence from the earlier revision
vllm serve(TP=2, 64 GiB per rank, ordinary pages), engine init takes 19.0 s vs 20.2 s before.Known limitations
pin_tensor,unpin_tensor, and the registration boundary calculation live invllm/utils/host_memory.py, shared by HiSparse and CPU/disk offload as applicable.ceil(resolved cache bytes / page size)HugeTLB pages, reserved on the host and, on Kubernetes, requested ashugepages-<size>.Related: #45693 (same key name for
OffloadingConnector), #58852, #59430, #51081.Validation
The AMD measurements below describe the earlier revision. Current CUDA validation is recorded separately below. NUMA placement now follows the worker policy.
MI350X (gfx950), ROCm 7.2.3, Linux 6.8,
vllm/vllm-openai-rocm:nightly-92044241a0with this PR's three changed files overlaid; the image's copies were byte-identical to this PR's base.pytest tests/v1/simple_kv_offload/test_host_buffer.py tests/v1/simple_kv_offload/test_worker.pymmapwith a clear errorfork()svllm serve(TP=2, 32 GiB of 1 GiB pages per rank):SIGKILLa worker,SIGKILLthe engine core,SIGTERM, then restart; plusSIGKILL/SIGSEGV/exception at buffer level, including mid-populate, mid-pin, and with forked children still aliveThe 8-rank, soak and pool rows ran on the previous revision, which populated pages with 16 threads; this revision populates with one
madvisecall and was re-checked with the unit tests and thevllm servecrash/restart row.The earlier AMD validation did not include CUDA or an offload-on/off output comparison. The subsequent CUDA runs below include a small-model output smoke comparison; a full model-accuracy evaluation remains outstanding. AMD CI already runs
tests/v1/simple_kv_offload; the HugeTLB tests skip on hosts without a pool.Review fixes (
76365e976b,de6a707b43)These AI-assisted fixes allocate only the resolved cache bytes, inherit NUMA policy, and preserve one registration per matching unregister. Shared helpers moved to
vllm/utils/host_memory.py; their function ASTs are unchanged by the move. The independent HIP SDMA optimization is handled by #65559.pin_tensorleft one registered range after HiSparse teardown for a tensor larger than 1 GiB.pin_tensornow registers exactly one range again.tests/v1/worker/test_utils.py::test_hisparse_registration_chunks_end_between_host_blocks: collection blocked by missing FlashAttention CUDA extensions. The environment was not modified.CUDA validation and alignment fix (
26bcba935b, 2026-11-05)Validated on an exclusive Slurm node with 4 NVIDIA GB200 GPUs, driver 580.126.09, CUDA 13.0 / PyTorch 2.14.0, and Linux 6.17 with 64 KiB OS pages. The isolated environment used this PR's Python source and native/generated dependencies from
vllm/vllm-openai:nightly-40941f2c4ba96fba0a7e282a5355e89cd74d9ac5.The first large HugeTLB run exposed a real bug: under NUMA binding, a registration ending inside a 2 MiB backing page returned
cudaErrorInvalidValue. For example, registering1 GiB - 64 KiBfailed while a whole1 GiBsucceeded on the same mapping. The shared boundary helper now receives the actual backing-page size; HiSparse keeps its OS-page default. The previously failing four-rank scenario passed after this correction.The ordinary-page soak started before the HugeTLB alignment correction; its OS-page arithmetic is unchanged, and ordinary-page focused tests passed again afterward. The CUDA run uses four ranks rather than the earlier eight-rank AMD setup. NUMA node 1 could reserve only about 25.9 GiB of 2 MiB pages, so its two ranks used 12 GiB each. This validates 2 MiB HugeTLB on GB200, not 1 GiB pages or all CUDA platforms. The output check is a three-prompt serving smoke test, not a full accuracy evaluation. No speedup is claimed from these runs.
This commit also extended the existing boundary test to HugeTLB page sizes. Pre-commit checks, including mypy, passed.
Simplification (
2a6867aa5c)HOST_REGISTER_CHUNK_BYTES(256 GiB) instead of 1 GiB. With a 1 GiB limit and 1 GiB pages, every boundary had to be a 1 GiB multiple that is also a block boundary, so any block size that does not divide 1 GiB (for example MLA: 576 × 2 × 64 = 73728 B) failed at startup. With the larger limit, a boundary exists at lcm(block, page), which is 9 GiB in that example. HiSparse behavior is unchanged.HostBufferis now a function,alloc_host_buffer(), becausetorch.frombufferkeeps the mapping alive. It reuses the existingMADV_POPULATE_WRITEhelper and its pre-5.14 fallback fromshared_offload_region.py.cpu_hugepage_block_sizeaccepts only the documented2MB,1GBandnone.served_model_namechange to the integration test were removed.tests/v1/simple_kv_offload/test_host_buffer.py: 7 passed, 2 skipped (no free hugepages on the host).test_worker.pyandtests/v1/worker/test_utils.pycould not be collected locally because the FlashAttention CUDA extensions are missing; they rely on CI. The GB200 runs above predate this commit.Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.
Not a duplicate: the related PRs target
OffloadingConnectorand HiSparse.🤖 Generated with Claude Code