Skip to content

[Model] Support disk-backed (mmap) PLE table for Qwen3.8-Flash-Next (VLLM_PLE_MMAP) - #54129

Open
Trosfy wants to merge 33 commits into
vllm-project:mainfrom
Trosfy:ple-mmap-upstream
Open

Trosfy wants to merge 33 commits into
vllm-project:mainfrom
Trosfy:ple-mmap-upstream

Conversation

@Trosfy

@Trosfy Trosfy commented Aug 28, 2026

Copy link
Copy Markdown

What this changes

Qwen3.8-Flash-Next has a 47.68 GiB FP8 PLE table. On machines with limited host memory, or one shared CPU and GPU memory pool, keeping that table resident can prevent the model from fitting.

VLLM_PLE_MMAP=1 reads the required rows from the checkpoint's safetensors files through read-only mmap. Discovery follows model.safetensors.index.json when present and rejects missing, duplicate, malformed, or unsupported shards and scales.

Model Runner V2 gathers PLE rows during input preparation and copies them into stable GPU buffers before the model runs. Captured code only reads those buffers, so FULL cudagraph modes work without the previous whole-forward splitting op.

The mapped files use Linux's normal file cache. Recently accessed pages may remain resident, but they are clean and file backed, so the kernel can reclaim them when memory is needed. Prewarm and readahead affect which pages enter the cache; they do not pin the complete table in RAM.

Configuration

Variable Default Purpose
VLLM_PLE_MMAP 0 Enable mmap-backed PLE
VLLM_PLE_MMAP_WORKERS 32 Gather worker count
VLLM_PLE_MMAP_CHUNK 2048 Rows per gather task
VLLM_PLE_MMAP_PREWARM 0 Warm a memory-bounded portion of the table
VLLM_PLE_MMAP_READAHEAD 0 Limit coalesced ranges sent to posix_fadvise; 0 disables it
VLLM_PLE_MMAP_PINNED 0 Use pinned host memory for CUDA H2D copies
VLLM_PLE_MMAP_SERIAL 0 Run gathers inline at or below this distinct-row count

The rate-limited timing line is PLE mmap input prep. Its ids_d2h_wait_ms field measures host time blocked at the IDs D2H boundary. This includes dependency-ordered accelerator work queued before the copy, so it is not pure transfer or exclusive PLE cost.

Validation

Current branch:

  • pytest tests/models/qwen4_exp/test_ple_mmap.py -q: 204 passed
  • pytest tests/v1/worker/ -q: 468 passed, 1 skipped
  • CUDA tests cover FP8 and BF16 FULL capture, repeated replay with new rows, padded rows, and dummy input preparation.
  • Pre-commit hooks on changed files: passed
  • pre-commit run mypy-3.12 --all-files --hook-stage manual: passed

Model evaluation:

  • On the 8x3090 tester's production port, a 50-item deterministic accuracy battery was byte-identical to the resident baseline: 27/50 with the same margins. This used the previous PIECEWISE split path. Results
  • The current input-preparation path has CUDA replay tests that compare FP8 and BF16 outputs across repeated staged batches.

External performance reports also used the previous PIECEWISE split path:

  • sm120, TP1: SERIAL reduced gather p50 from 0.73 to 0.22 ms and ITL from 11.92 to 11.45 ms. PINNED was neutral. Results
  • sm86, TP2/PP4 downstream port: resident FULL, resident PIECEWISE, and mmap PIECEWISE measured 15, 19, and 28 ms ITL. PP4 remains unsupported upstream. Results
  • Intel Arc Pro B70, TP1: moving the synchronization reduced reported PLE time from 93.21 to 0.80 ms without changing ITL. The original field was observing queued expert-offload work. Results

An earlier GB10 run measured 39.7% of the PLE shard pages resident in file cache and no PLE table bytes resident on the GPU.

Scope

This does not duplicate #53899. That implementation keeps the complete PLE table in host memory and uses an offload worker. This PR keeps the checkpoint files as backing storage and has no PLE worker.

Model Runner V2 is required. Pipeline parallelism greater than one is not supported. The implementation assumes Linux file-cache semantics.

AI assistance was used. I reviewed every changed line, verified the reported tests and external evaluation, and take responsibility for the contribution.

peakcrosser7 and others added 14 commits August 26, 2026 20:54
Co-authored-by: zjy0516 <riverclouds.zhu@qq.com>
Co-authored-by: KungYork <firelikest@163.com>
Co-authored-by: Thien Tran <gau.nernst@yahoo.com.sg>
Co-authored-by: Jiangyun Zhu <riverclouds.zhu@qq.com>
Co-authored-by: Yifan Qiao <yifanqiao@inferact.ai>
Co-authored-by: andyluo7 <43718156+andyluo7@users.noreply.github.com>
Co-authored-by: Isotr0py <mozf@inferact.ai>
Assisted-by: OpenAI Codex
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>
Signed-off-by: huanghaoyan.hhy <huanghaoyan.hhy@alibaba-inc.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@TSUMUGI-XE

Copy link
Copy Markdown

Third topology, and a measurement that I think changes what PLE mmap forward p50 means.

Box: 2× Intel Arc Pro B70 (32 GB), both behind one PCIe switch sharing a single Gen4 x16
upstream, 128 GiB host RAM, TP=1, --cpu-offload-gb 47 --cpu-offload-params experts,
cudagraph_mode=PIECEWISE, this PR at 75f067130. Idle box, VM off, warmup discarded,
median of 10.

1. The p50 is not PLE's cost. forward() times a blocking D2H of the row ids. Putting an
explicit torch.xpu.synchronize() immediately before that timed region:

                     ITL p50     PLE fwd p50   sync_ms   explicit sync
default              111.39 ms      93.21        92.43        --
+ explicit sync      111.52 ms       0.80         0.17       92.01

The D2H itself is 0.17 ms. ITL does not change — the wait moved earlier, it did not go
away. The 92 ms is the device queue draining, and PLE is simply the first thing each step
that demands a synchronization, so it gets billed for it.

2. The drain tracks host streaming, not PLE. Dropping --cpu-offload-params experts
(so the offloader evicts in declaration order):

                     ITL p50     explicit sync   PLE fwd p50
experts              111.52 ms      92.01           0.80
declaration order    328.65 ms     308.53           0.86
delta                 +217.1        +216.5          +0.06

ITL and the drain move together, 1:1. PLE's real work does not move.

3. Demand paging is leaving 2.6× on the link. Same box, same card, 512 MiB H2D:

pinned bulk copy      26.60 GB/s   (link theoretical 31.5)
pageable bulk copy    17.08 GB/s
UVA fault-driven      10.10 GB/s   (0.93 GiB per token / 92 ms)

What I am unsure about is the fix. Batched prefetch after routing would start earlier
and transfer as one large copy, but on this stack UVA cannot place rows in pinned host USM,
so it means replacing demand paging rather than tuning it — and I do not know whether that
belongs here or somewhere else entirely.

Nothing above looks XPU-specific to me: the misattribution comes from where the sync happens,
and demand-paging efficiency is not a platform property. If it is useful, I am happy to run
whatever direction you take on this box — it seems to be the one where the effect is largest.

Prepare mmap-backed PLE rows in Qwen4ExpModelState and copy them
directly into stable, module-owned GPU buffers before compiled model
execution. Captured forwards now read those buffers without host work,
which removes the old whole-forward custom op and allows FULL cudagraph
modes under Model Runner V2. Model Runner V1 is rejected because it does
not prepare the required PLE inputs.

Keep profile and dummy batches on a zero-only path, account for staging
memory before KV-cache sizing, and validate mmap reloads before any model
mutation. Transaction-scoped approvals handle repeated nested loader
groups while preserving fail-closed shard and checkpoint validation.

Rename the timing line to PLE mmap input prep. The IDs D2H field is now
ids_d2h_wait_ms to state that it includes queued dependency work rather
than measuring pure copy latency.

Signed-off-by: Christopher Deodat <61226342+Trosfy@users.noreply.github.com>
Assisted-by: Claude Code (AI assistant)
PLE mmap discovery previously scanned every safetensors file in a
checkpoint directory. A consolidated or leftover export excluded by the
weight index could therefore supply rows and scales that the normal loader
never sees.

Filter discovery through the loader's index helper, reject duplicate
logical shards and scales, and key cached headers by the selected files'
identity and metadata so same-path checkpoint updates cannot retain stale
offsets. Reject unsupported scale dtypes during validation, before an
in-place reload can mutate model weights.

Signed-off-by: Christopher Deodat <61226342+Trosfy@users.noreply.github.com>
Assisted-by: Claude Code (AI assistant)
@mergify mergify Bot removed the needs-rebase label Sep 1, 2026
@Trosfy

Trosfy commented Sep 1, 2026

Copy link
Copy Markdown
Author

@tomylin890 Thanks for running both follow-ups. The matched resident PIECEWISE result changes the attribution in the useful direction.

Resident FULL at 15 ms versus resident PIECEWISE at 19 ms supports about 4 ms from graph mode. Mmap PIECEWISE at 28 ms leaves another 9 ms associated with the mmap path, but we cannot call all of that graph re-entry cost from this comparison alone.

We have now reconciled the branch with merged #53896 and moved PLE hashing and mmap gathering into input preparation. Rows are copied into a stable GPU buffer before the model runs, the old whole-forward split op is gone, and FULL graph modes are supported under Model Runner V2.

Upstream still supports PP1 only, so I am not asking you to port or rerun this on the TP2/PP4 stack yet. A future comparison needs to start from the reconciled implementation rather than the old split path.

Your disk-KV follow-up also narrows that result. Two roughly 41 GB restores competing for a 32 GiB pool explain the concurrency pressure, while healthy single restores and intermittent dual behavior do not establish mmap as the cause. We will keep that observation scoped accordingly.

Thanks for correcting the earlier attribution and closing both requested arms.

@Trosfy

Trosfy commented Sep 1, 2026

Copy link
Copy Markdown
Author

@TSUMUGI-XE Thanks for running this. The explicit sync arm caught a mistake in how we were reading our timing line.

We have renamed it to PLE mmap input prep and changed sync_ms to ids_d2h_wait_ms. The description now says that this is the host wait at the IDs D2H boundary, including earlier queued accelerator work. It is not pure copy time or exclusive PLE cost.

On your box, moving the 92 ms wait earlier left ITL unchanged and exposed the PLE path itself at under a millisecond. The experts versus declaration-order result puts the larger delay in generic parameter offload.

We are keeping #54129 scoped to PLE mmap rather than adding XPU and MoE offload work, so I do not think you need to run anything else for this PR. Thanks for adding the control. Without it, we would have kept charging someone else's queue to PLE.

@mergify

mergify Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Trosfy.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Sep 2, 2026
Reconcile the PLE mmap series with the fused PLE kernels merged by
vllm-project#54517 and the FP8 scale validation from vllm-project#54722. Keep upstream's new
ops.ple dispatch (ple_ngram_ids, ple_gate, ple_conv) and its removal of
the NVIDIA positions_buffer/padded_buffer registrations and the
max_total_tokens/max_num_reqs constructor parameters, then layer the
mmap delta onto those seams.

Resolves four hunks in ple_layer.py and model.py: union of the ple_mmap
and ops.ple imports, the mmap embedding branch without the deleted
buffers, the mmap short-circuit in forward without the duplicated
ngram_ids allocation upstream removed, and the _HC_WEIGHTS_MAPPER to
_EXTRA_WEIGHTS_MAPPER rename around the retained reload preflight.

Adapt test_ple_mmap.py to the new Qwen4ExpNGramEmbedding signature. The
CPU-driven equivalence test now copies compute_ngram_ids' result into the
op's output buffer itself: the fused CUDA kernel writes in place, but the
eager fallback returns a fresh tensor and leaves output untouched.

Signed-off-by: Christopher Deodat <61226342+Trosfy@users.noreply.github.com>
Assisted-by: Claude Code (AI assistant)
@mergify mergify Bot removed the needs-rebase label Sep 2, 2026
ToastyTheBot pushed a commit to ToastyTheBot/vllm that referenced this pull request Sep 2, 2026
All four failed on import/setup, so they asserted nothing from the moment they
landed. Commit b5fe6f4 described them as 'pre-existing and unrelated' --
they were inherited from the ported PR vllm-project#54129 suite, but the causes are the
port-adaptation gaps that same commit documented and did not carry into the
tests, and one of them guards this branch's own numerics.

* test_hash_ngram_ids_matches_golden_ids called module._hash_ngram_ids; main
  renamed it compute_ngram_ids. The compat shim in ple_mmap.py covers the
  module-level op, not a direct method call, so the golden-value pin on the
  trigram hashing never executed.

* test_env_on_off_forward_equivalence_fp8_and_dequantized died with 'Forward
  context is not set' on the stock arm. This is the ONLY test comparing mmap
  gather output against the stock table numerically -- the thing most worth
  having, and it had never run. Both arms now get a context.

* test_default_off_forward_never_calls_the_mmap_gather_op, same cause, plus
  the context must hold a layer-shaped object: the op resolves the PLE layer
  and then its .ple_embedding.

* test_default_off_load_weights_matches_the_stock_contract used a bare
  nn.Parameter, but the stock path calls embedding.weight.weight_loader(...).
  Added a double implementing the real contract -- intersect the shard's
  global row range with this rank's slice and copy the overlap -- so the test
  exercises the contract instead of raising AttributeError before its
  assertions.

138 passed, 6 skipped (GPU-only), up from 134 passed / 4 failed.

Co-Authored-By: Claude Mythos <noreply@anthropic.com>
yangzhuxinyzx added a commit to 1CatAI/1Cat-vLLM that referenced this pull request Sep 3, 2026
Retain checkpoint-native PLE shards as reclaimable safetensor mappings in the shared CPU offload worker. Deduplicate and sort row IDs, issue shard gathers through a bounded worker pool, and fan one exact FP8 result out to TP ranks.

The worker-pool design is informed by upstream vLLM PR #54129 while preserving 1Cat single-compute TP4 topology and MADV_RANDOM behavior.

Cold random TP4 prefill improves from 2382 to 4044 tok/s at 256K while avoiding the 47.7 GiB anonymous table and preserving output hashes.

Inspired-by: vllm-project/vllm#54129
Signed-off-by: yangzhuxinyzx <153831768+yangzhuxinyzx@users.noreply.github.com>
@mergify

mergify Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Trosfy.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants