Repository navigation
Conversation
ple_disk_gather_u8 advised every missing page of a request up front and then copied all rows. Read-ahead pages enter the oldest LRU generation untouched, so under memory pressure (a cgroup at memory.high with ~1.2 GB of page cache left) reclaim dropped them before the copy reached them, and the copy faulted them back one synchronous page at a time: all major faults in the PLE worker's main thread, ~3,500 pages/s, long-prefill TTFT spread 52-184 s per boot on PP4. The gather now copies the rows in address order and advises only the pages within ple_disk_prefetch_window_kib (KernelConfig, default 4 MiB) ahead of the copy; 0 copies without advice. Byte selection and output order are unchanged; the field is excluded from the compile hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YaG8VjXn5eN3Aq7eZrA3dp Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
…ode reads The packed GGUF embedding reads kernel_config while it is constructed (82b296a), and the n-gram layer's forward names its diagnostics by layer_name (906f8ff). The hand-built stand-ins in these tests lacked both, so all ten failed before reaching the code under test. With them, the CPU-owned reader that prepare_mapped_row_gather builds for packed GGUF tables runs in these tests again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YaG8VjXn5eN3Aq7eZrA3dp Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
Peuqui
force-pushed
the
pr-ple-prefetch-window
branch
from
October 28, 2026 06:06
a729ae2 to
d1c1591
Compare
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.
Update 2026-10-28: rebased onto main c59b3c4. The two commits are unchanged apart from the regenerated docs/design/architecture/runtime_reference.md (git range-diff). _C rebuilt from this tree; the Test Plan files pass on this head without a failure: 277 passed on a Tesla V100, 270 passed and 7 skipped on a Quadro RTX 8000 (the test fakes that #1214 repaired are on main now). pre-commit run --from-ref origin/main --to-ref HEAD on this head: every hook passes except check-layering, which now reports vllm/config/speculative.py (model coupling 142 -> 144) next to vllm/model_executor/layers/fused_moe/runner/moe_runner.py (platform coupling 37 -> 39); main c59b3c4 reports the same two counts, so this PR adds none. pre-commit run mypy-3.10 --hook-stage manual: passed.
Purpose
ple_disk_gather_u8 (from #885) first calls mincore and MADV_WILLNEED for every missing page of a request and then copies all rows. Pages brought in by read-ahead have not been accessed yet, so under memory pressure they are among the first pages reclaim takes. In a long prefill the copy can then reach pages that were already reclaimed and fault them back in one synchronous page at a time.
We hit this with Qwen3.8-Flash-Next-180B-A4B NVFP4 on PP4 (188,956,217 of 320,001,536 PLE rows on the disk tier), served from a systemd service with MemoryHigh=16G on a host with 30 GiB of RAM, the checkpoint on an NVMe SSD behind USB. In one boot the time to first token of a 184,134-token prompt was 164.7 s. While it served the long prompts, a per-thread sampler of the PLE offload worker counted 586,325 of the worker's 586,354 major faults in its main thread, up to 4,040 per second (10 s window), and py-spy showed that thread in MappedRowGatherKernel.apply. In the A/B below the same prompt took 131.8 and 183.0 s in the two base boots.
The first commit copies the rows in address order and advises only the pages that lie within ple_disk_prefetch_window_kib ahead of the copy (new KernelConfig field, default 4096 KiB; 0 copies without advice). As before, only pages that cover requested rows are considered. Byte selection and output order are unchanged. The field is listed with the other PLE I/O fields that do not enter the compile hash. Both callers of prepare_mapped_row_gather pass it: the n-gram disk tier in ple_layer.py and the CPU-owned packed GGUF table in gguf_embedding.py.
The second commit repairs the ten tests in tests/kernels/ple/test_gguf_packed_embedding.py, which fail on main before they reach the code under test: their hand-built stand-ins lack the kernel_config that the packed GGUF embedding reads while it is constructed (82b296a) and the layer_name that the n-gram forward uses for its diagnostics (906f8ff). With the repair these tests run the packed GGUF reader through this gather again (222 gather calls with the 4 MiB window in that file on the Tesla V100).
Not a duplicate: gh pr list --repo 1CatAI/1Cat-vLLM --state all --search with "prefetch window", "MADV_WILLNEED", "ple_disk_gather", "madvise PLE", "PLE page cache", "PLE disk prefetch", "mincore", "ple_disk_rows", "PLE major faults" and "PLE disk stall", and the same searches over issues, find no PR or issue about the prefetch of this gather. #885 (merged) introduced the gather. No open PR changes csrc/ple_disk_rows.cpp, vllm/model_executor/kernels/ple/disk_rows.py, tests/kernels/test_ple_disk_rows.py or tests/kernels/ple/test_gguf_packed_embedding.py (git diff against each open PR head). git merge-tree against all 122 open PR heads adds conflicts only in the generated docs/design/architecture/runtime_reference.md, for #903, #1210, #1211, #1215, #1216 and #1217: the line anchor of KernelConfig.compute_hash, which tools/generate_architecture_reference.py --write regenerates.
AI assistance was used for this change. I reviewed every line and ran the tests and measurements below.
Test Plan
The tests ran on main 6f3557c plus this PR (_C built from this tree, TORCH_CUDA_ARCH_LIST=7.0) and on main with its own build, once on a Tesla V100 and once on a Quadro RTX 8000.
Offline gather: the real FP8 PLE table of that checkpoint (128 shards, 320,001,536 rows of 160 B), 16 calls of 18,000 random row ids each (about the disk-tier share of a 2,048-token chunk), in a cgroup with memory.high = 3 GiB and 2.25 or 2.4 GiB of touched anonymous ballast, the table evicted from the page cache before each arm. The old operator (main's code) against this PR's operator with windows of 1, 4 and 16 MiB and with the whole request advised up front; SHA-256 over all output bytes.
Serving A/B on our fork (main 6f3557c plus our commits): fork commit cf50e1a (the two commits of this PR) against its parent 5841543, Flash-Next PP4 and TP2xPP2, torch.compile with CUDA graphs, MTP with 4 speculative tokens. Boots fix cold, base, fix, base, fix; the cold boot is not counted, and the PLE table is evicted from the page cache before every boot. The base arm runs the production tree, the fix arm the same entries with PYTHONPATH on the fork tree, its own compile cache and its own _C. Our fork also keeps a 0.5 GiB Python row cache in front of this gather and releases only the page tables of the rows it read; neither is on main. Per boot: four ~29k-token prompts, a decode benchmark (prose, code, edit), eight quality prompts at temperature 0, and prompts of 184,134 and 249,950 tokens. System-wide major faults and bytes read (/proc/vmstat) and file refaults of the service cgroup (memory.stat) are counted from the start to the end of each boot's measurements.
Path coverage
Acceleration and benchmark contract (required for performance changes)
Test Result
🤖 Generated with Claude Code
https://claude.ai/code/session_01YaG8VjXn5eN3Aq7eZrA3dp