[Model][Qwen4Exp] Split the pinned-host PLE table between device and host (#479) - #528
Merged
Merged
Conversation
…host The pinned-host PLE path keeps the whole FP8 n-gram shard in pinned host memory and gathers every row over UVA. On a card that has room beside its layers that is the slow option, and on a box with little host RAM it does not boot at all: the Qwen3.8 Flash Next table is 51 GB, a TP2 rank pins 25 GB. Place the table by capacity instead: rows stay in device memory as long as the stage's weights, the KV cache of the requested max_model_len and a reserve for the activation peak and the graph pool still fit; only the remainder goes to pinned host memory. The budget is derived from the real headroom when the first checkpoint shard arrives (every other weight of the stage is placed by then), or fixed per rank with VLLM_QWEN4EXP_PLE_HOST_GIB. Both halves are gathered by the existing pinned-gather kernel; a row-less half is skipped without a device-to- host sync. The loader routes each shard into the half that owns its rows. This is how we serve Qwen3.8 Flash Next NVFP4 at TP2/PP2 on 2x RTX 8000 + 2x V100 with VLLM_QWEN4EXP_PLE_HOST_GIB=6 (the rest of each shard on the 48 GB cards): 51.9 tok/s with MTP k=4, coherence 8/8, measured 2026-08-28 on our fork and carried into our 1.5.0 deployment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Peuqui <peuqui@github.com>
_should_use_pinned_host_ple() asked current_platform for the capability of device 0 of the visible list. On a heterogeneous pipeline the stage that owns the PLE layers is not necessarily that device: with a Turing card first the Volta stage lost its pinned-host path and hit the fp16 materialization of the generic embedding, with a Volta card first a Turing PLE stage got it although it only qualifies with the split placement. Ask for the capability of the worker's current device instead. The split placement also serves every pre-Ampere card, not only exact SM70: the generic path dequantizes the whole FP8 table to fp16 inside the compiled graph (47.7 GiB for Qwen3.8 Flash Next), which neither Volta nor Turing can hold. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Peuqui <peuqui@github.com>
Peuqui
force-pushed
the
qwen4exp-ple-split-placement
branch
from
September 6, 2026 10:04
cfd8a5f to
b706970
Compare
Every tensor-parallel rank of the stage that owns the PLE table derives its host budget from MemAvailable, and all of them draw on the same host memory. Reading it per rank double-books it: on a 30 GB host with 20 GB available, two ranks each saw room for 7 GiB, pinned 14 GiB together and pushed the engine processes, the checkpoint loading and everything else into swap. The machine was unreachable for 40 minutes. cap_host_budget_bytes bounds a rank's budget by (available - reserve) / ranks_sharing_host. The reserve is a fraction of the host rather than a fixed size, so the rule means the same on every machine; the default is a quarter, overridable with VLLM_QWEN4EXP_PLE_HOST_RESERVE_GIB. What no longer spills stays on the device, and if the requested context then does not fit, the KV allocator reports the reachable max_model_len -- host memory is the hard limit, context the negotiable one. Measured on the reporting rig: 7.09 -> 3.18 GiB per rank, boot healthy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yangzhuxinyzx
added a commit
that referenced
this pull request
Sep 7, 2026
…907-030709 [Bugfix][PLE] Integrate split placement with padding and loading repairs (#528)
hubutui
pushed a commit
to hubutui/1Cat-vLLM
that referenced
this pull request
Sep 7, 2026
Integrate 1CatAI#528 safely on current main; validate memory budgets, preserve retryable allocation state, and test graph replay with in-place reload. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: yangzhuxinyzx <153831768+yangzhuxinyzx@users.noreply.github.com>
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.
Purpose
Third slice of the PP enablement offered in #479 (loader fix: #485, PLE gate on the partition: #516), and the placement we run Qwen3.8 Flash Next with.
The pinned-host PLE path (#345, #374) keeps the whole FP8 n-gram shard in pinned host memory and gathers every row over UVA. Where the card has room beside its layers that is the slow option, and on a box with little host RAM it does not boot at all: the Flash Next table is 51 GB, a TP2 rank pins 25 GB. #471 answers the memory side with a disk-backed table.
This PR adds the option in between: rows stay in device memory as long as the stage's weights, the KV cache of the requested
max_model_lenand a reserve for the activation peak and the graph pool still fit; only the remainder is pinned on the host. The budget is derived from the real headroom when the first checkpoint shard arrives (every other weight of the stage is placed by then), or fixed per rank withVLLM_QWEN4EXP_PLE_HOST_GIB. Both halves are gathered by the existingqwen4_exp_ple_pinned_gatherkernel; a row-less half is skipped without a device-to-host sync. The loader routes each shard into the half that owns its rows.Qwen4ExpPinnedHostEmbeddingtherefore starts with a row-less CPU placeholder and materializes its tables lazily (first shard, orprepare_accelerator_weight()for dummy weights).Second commit:
_should_use_pinned_host_ple()asked for the capability of device 0 of the visible list. On a heterogeneous pipeline the stage that owns the PLE layers is not necessarily that device. It now asks for the worker's own device, and every pre-Ampere card takes the split placement (the generic path dequantizes the whole FP8 table to fp16 inside the compiled graph, 47.7 GiB for Flash Next).This is how we serve Qwen3.8 Flash Next NVFP4 at TP2/PP2 on 2x RTX 8000 + 2x V100 with
VLLM_QWEN4EXP_PLE_HOST_GIB=6: 51.9 tok/s with MTP k=4, coherence 8/8 (measured 2026-08-28 on our fork, the same placement is in our 1.5.0 deployment). We cannot boot current main on this hardware, so the end-to-end evidence is from the fork; the unit tests below run against this branch.Known limits: the automatic budget estimates the activation peak and the graph pool through a reserve (8 % of the device, at most 4 GiB,
VLLM_QWEN4EXP_PLE_VRAM_RESERVE_GIB); the explicit host budget is the safe choice. The gather always reads both halves.Test Plan
Wheel venv (1Cat 1.5.0) with
PYTHONPATHon this checkout, V100 (SM70):Duplicate check / AI assistance
gh pr list --state all --search "PLE host" / "pinned host PLE" / "PLE placement" / "PLE_HOST": #345, #374 (whole shard in pinned host), #471 (disk-backed table) -- all merged, none splits the table between device and host; no open PR touches this path.gh issue view 479 --comments: the third-slice offer is in the issue.Written with AI assistance (Claude); every line was reviewed and the tests were run by me on 2x RTX 8000 + 3x V100.
🤖 Generated with Claude Code