feat(plugins): dynamic hidden-states layer swap for extract_hidden_states - #996
feat(plugins): dynamic hidden-states layer swap for extract_hidden_states#996reed-meyerson wants to merge 1 commit into
Conversation
…ates Add a vLLM plugin (vllm_plugins/dynamic_hidden_states) that swaps *which* target-model hidden layers are captured during speculator training-data generation (method="extract_hidden_states") at runtime over HTTP, without restarting the engine. Components: - graph_patch.install (vllm.general_plugins): a pre-compile monkeypatch that replaces the compiled-in `layer_idx in aux_hidden_state_layers` membership test with a masked accumulate into N fixed buffers weighted by a one-hot selection mask held in a registered buffer. The compiled graph becomes selection-agnostic, so a swap is an in-place mask.copy_(...) — same storage address, read live by both the compiled graph and captured CUDA graphs. This makes swaps take effect with no recompile and no --enforce-eager. Memory stays O(N); the cost is a small multiply-add over every candidate layer's residual. - AuxLayerWorkerExtension (--worker-extension-cls): engine-side RPC (get/set_aux_hidden_state_layers_rpc) callable via collective_rpc. - AuxLayerEndpoint (vllm.endpoint_plugins): GET/POST /aux_hidden_state_layers on the OpenAI server; the layer count is fixed at launch (fail closed). Verified on Qwen3-8B (1xH100) under default torch.compile + FULL_AND_PIECEWISE CUDA graphs: swapping layer 2->6 changes only column 0 (max|delta|=21.125) while unchanged layers 18/34 stay bit-identical, reproducing the eager baked path's values exactly, with no recompilation. See vllm_plugins/README.md. Scope: covers models capturing through EagleModelMixin._maybe_add_hidden_state (Qwen2/Qwen3, Llama, generic dense/MoE); models that inline the membership test (deepseek_v2, qwen3_next) still require --enforce-eager for a live swap. Signed-off-by: Reed Meyerson <31574681+reed-meyerson@users.noreply.github.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merge Protections🔴 1 of 1 protections blocking · waiting on 👀 reviews
🔴 Require approval from approved reviewers listWaiting for any of
This rule is failing.All pull requests must have at least one approving review from a member of the approved reviewers list before merging.
|
Summary
Adds a self-contained vLLM plugin (
vllm_plugins/dynamic_hidden_states) thatlets you swap which target-model hidden layers are captured during
speculator training-data generation (
method="extract_hidden_states") atruntime over HTTP, without restarting the engine. Only the set of layer
indices changes; the count is fixed at launch (proposer buffers, dummy-proposer
KV-cache shape and on-disk safetensors layout depend on it), so count changes are
rejected (fail closed).
This is opt-in and touches no vLLM core files — it wires in entirely through
public plugin entry points and
--worker-extension-cls.How it works
graph_patch.install(vllm.general_plugins) — a pre-compilemonkeypatch of
EagleModelMixin._maybe_add_hidden_state/_set_aux_hidden_state_layers. The stock capture uses a Python membership test(
if layer_idx in self.aux_hidden_state_layers) thattorch.compileconstant-folds into the compiled forward, freezing the selection — so a naive
attribute swap is a silent no-op unless the engine runs
--enforce-eager.Instead we keep
Nfixed accumulator buffers and fold every candidate layer'sresidual into them weighted by a one-hot selection mask held in a registered
buffer. The graph becomes selection-agnostic, so a swap is an in-place
mask.copy_(...)— same storage address, read live by both the compiled graphand captured CUDA graphs. No recompile, no
--enforce-eager. Memory staysO(N); the cost is a small multiply-add over every candidate layer's residual
(~0.006% of forward FLOPs).
AuxLayerWorkerExtension(--worker-extension-cls) — engine-side RPC(
get/set_aux_hidden_state_layers_rpc) callable throughcollective_rpc.AuxLayerEndpoint(vllm.endpoint_plugins) — registersGET/POST /aux_hidden_state_layerson the OpenAI-compatible server.Testing
Verified on Qwen3-8B (1×H100) under the default
torch.compile+FULL_AND_PIECEWISECUDA graphs (no--enforce-eager):(
max|Δ|=21.125) while columns for unchanged layers 18/34 staybit-identical (
Δ=0).400.An end-to-end integration script is included at
vllm_plugins/tests/test_swap_e2e.py(run manually against a live server; seeits docstring and
vllm_plugins/README.md).Scope / limitations
EagleModelMixin._maybe_add_hidden_state(Qwen2/Qwen3, Llama, generic dense/MoE). Models that inline the membership test
in their own forward (e.g.
deepseek_v2,qwen3_next) are not covered bythe compile patch and still need
--enforce-eagerfor a live swap.controller that swaps mid-run must record its own
{time/req-range → layer set}mapping.
Notes
Opening as a draft for discussion — feedback welcome on whether this belongs
here as a plugin vs. upstreaming the masked-accumulate capture into vLLM core.