You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Choosing which target hidden-state layers to feed a drafter is a decision that
today is effectively a guess. In our Gemma-4-31B sweep the aux hidden-state layer
id (or set of ids) had a meaningful effect on acceptance length — the best single
layer beat the worst by a clear margin, and deep layers tended to beat shallow
ones — yet we have no principled, cheap procedure for finding a good selection.
We don't yet have enough evidence to claim how this ranks against other training
decisions in general, but it is clearly a knob worth choosing deliberately rather
than by default. Doing a full-data training run per candidate to find out does not
scale: an exhaustive layer search at full data cost is prohibitive.
Acceptance length as a function of the aux hidden-state layer id, at both data
scales. The choice of layer moves acceptance length substantially, and the shape
of the curve is the same at small and large data — deep layers win at both.
The key empirical observation motivating this RFC: a drafter's performance
under a small amount of training data is highly correlated with its performance
under a large amount of data. If cheap runs rank candidates the same way
expensive runs do, we can prune the search space cheaply and only spend
full-data compute on the finalists.
We measured this directly on Gemma-4-31B (google/gemma-4-31B-it), DFlash
drafter, 1 epoch on ShareGPT, evaluated by drafts-weighted mean acceptance
length across 9 held-out subsets, comparing n=4096 (small) vs n=65536
(16× larger) training samples per config.
Single aux layer, sweep of layers 0,4,…,56 (n=15 configs):
Metric
Value
Spearman ρ (rank corr, 4096 vs 65536)
0.914 (p=1.9e-06)
Kendall τ
0.790 (p=4.9e-06)
Top-1 preserved across scales
1 of 1 (layer 56 at both)
Top-2 preserved across scales
2 of 2 (52, 56)
Top-3 preserved across scales
3 of 3 (48, 52, 56)
Each point is one single-layer selection; x = acceptance length trained on 4096
samples, y = trained on 65536 samples (16× more data). The cheap-run score
reliably predicts the expensive-run score.
Five random aux layers, configs sampled by depth category (n=12 configs):
Metric
Value
Spearman ρ
0.916 (p=2.8e-05)
Kendall τ
0.758 (p=2.4e-04)
Top-1 preserved across scales
0 of 1 (#-1 and #-2 swap order)
Top-2 preserved across scales
2 of 2 (same 2 configs)
Top-3 preserved across scales
2 of 3
Same relationship holds for five-layer selections: small-data acceptance length tracks large-data acceptance length.
The practical takeaway matters more than the aggregate correlation: the top set is preserved even when exact order within it is not. For single layers,
the top-3 by small data are exactly the top-3 by large data (order included). For
five-layer configs, the top-2 by small data are exactly the top-2 by large data
as a set, but the #-1 and #-2 swap places — so small data does not reliably
pick the single winner there; it reliably picks a small shortlist that contains
it. This is precisely why the funnel carries a top-k (not top-1) from Stage 1
into Stage 2 to confirm, rather than trusting the cheap run's #-1 outright — while
still spending ~16× less per Stage 1 run.
Proposed Change.
Proposed Change
Add a staged (funnel) layer-selection pathway to speculators that turns
layer selection into an explicit, cheap-to-expensive search instead of a guess.
Three stages, each narrowing the candidate set while increasing the data spent
per candidate:
Stage 1 — broad search, small data. Train many candidate layer
selections on a small sample budget (e.g. ~4k samples, 1 epoch). Rank by
acceptance length. Cheap enough to cover a wide search.
Stage 2 — confirm, medium/large data. Re-train only the top-k candidates
from Stage 1 on a larger sample budget (e.g. ~64k). This filters out the
small handful of cases where small-data ranking jitters, at a fraction of the
cost of running everything at this scale.
Stage 3 — final run, full data. Train the single winning selection on the
full dataset with production hyperparameters.
Concretely, this would add to speculators:
A selection-strategy config describing the candidate space for Stage 1
(e.g. single-layer-sweep, random-k, depth-biased with late/uniform/early
weightings — the sampling scheme we used here) plus k per stage and the
sample budget per stage.
An orchestration entry point (script/CLI, e.g. scripts/select_layers.py)
that runs the three stages: fan out Stage 1 trainings, rank by benchmarked
acceptance length, carry the top-k into Stage 2, then emit the recommended
selection (and optionally kick off Stage 3).
A small results/leaderboard artifact (CSV/JSON) recording each candidate's
selection, stage, sample budget, and acceptance length, so the funnel is
auditable and re-rankable.
Reuse of the existing --target-layer-ids training path and the benchmark
harness — this is orchestration on top of what already exists, not a new
training mode.
Enabling infrastructure: runtime layer swap (depends on #996)
Stage 1 runs many candidate layer selections back-to-back. Today each candidate
requires relaunching the vLLM extraction server with a different --target-layer-ids, paying full model-load + startup cost per trial — which
dominates wall-clock when each training run is itself cheap (small data). To make
the sequential cheap experiments practical, we need to change which target
hidden layers are captured at runtime, without restarting the engine.
The draft PR vllm-project/speculators#996
("dynamic hidden-states layer swap for extract_hidden_states") provides exactly
this. It adds an opt-in vLLM plugin that swaps the captured aux-layer set live
over HTTP (POST /aux_hidden_state_layers) with no recompile and no --enforce-eager: it replaces the torch.compile-frozen layer_idx in aux_layers membership test with a masked accumulate over N fixed buffers,
where the selection is a one-hot mask in a registered buffer, so a swap is an
in-place mask.copy_(...) read live by both the compiled graph and captured CUDA
graphs. This is the mechanism a Stage 1 orchestrator would drive: launch the
extraction server once, then iterate candidates by POSTing new layer sets between
trials.
One constraint shapes the search design: #996 fixes the layer count at launch
(only the set is swappable). So a single extraction server can sweep candidates
of a fixed size k (e.g. all single-layer, or all 5-layer configs); sweeping a
different k requires a fresh launch. This fits the funnel naturally — pick k,
sweep sets of that size cheaply, confirm, commit — and is called out as a
dependency/assumption for Stage 1 below.
Any Other Things.
Part of a broader hyper-parameter optimization suite
Layer selection is the first concrete instance of a general pattern, and we
propose building it so the machinery generalizes. The same cheap-predicts-
expensive funnel — plus a shared candidate-config schema, staged orchestrator,
benchmark harness, and leaderboard artifact — likely applies to many knobs in speculators training.
Search within a fixed number of hidden states
One design rule falls out of the data: rank selections only against others with
the same number of hidden states — do not pool across different counts. The
correlation figures above each hold within a fixed count, but the absolute
acceptance-length scale shifts with the count, so a cross-count comparison is not
apples-to-apples.
Both populations plotted together with separate fits. The five-layer cloud sits
well above the single-layer cloud: at the same small-data acceptance length, a
five-layer selection reaches ~0.2 higher acceptance length at large data. The
extra hidden states add capacity, which shifts the whole curve — so a five-layer
config and a single-layer config with identical small-data scores are not
equivalent. The small-predicts-large property we rely on is a within-count statement.
This is why the funnel picks a number of hidden states k and searches sets of
that size, and it dovetails with #996's fixed-count-per-launch constraint: a
single extraction server sweeps one k, exactly the unit the ranking is valid
over.
Any Other Things
Evidence appendix (full per-config numbers) and caveats:
Data source. Gemma-4-31B DFlash, ShareGPT, 1 epoch, drafts-weighted mean
acceptance length over 9 subsets. Single-layer sweep = 15 configs complete;
five-layer sweep = 12 of 15 planned configs complete at drafting time (will be
refreshed to 15 before posting).
Coverage caveats. The single-layer sweep uses stride 4 and lands only on
sliding-window (local) layers — it does not probe the 10 global-attention
layers. Each (config, sample-size) point is a single run (1 epoch, one seed),
so run-to-run variance is unmeasured; this is exactly why Stage 2 confirmation
exists in the proposal rather than trusting Stage 1 outright.
What "small data predicts large data" does and does not claim. It reliably
preserves the top of the ranking (what the funnel needs). It does not
perfectly preserve mid-pack ordering — closely-scoring candidates reshuffle
between scales — which is expected and handled by carrying a top-k (not top-1)
into Stage 2.
Generality. Demonstrated on DFlash / Gemma here; the same funnel should
apply to EAGLE3 / P-EAGLE and other targets.
Dependency on feat(plugins): dynamic hidden-states layer swap for extract_hidden_states #996 and its limits. The efficient Stage 1 loop assumes the
runtime layer-swap plugin from #996. Its constraints
carry into the search: (a) the aux-layer count is fixed per server launch, so
each server sweeps one fixed k; (b) live swap without --enforce-eager covers
models that capture via EagleModelMixin._maybe_add_hidden_state (Qwen2/Qwen3,
Llama, generic dense/MoE) — models that inline the membership test (e.g. deepseek_v2, qwen3_next) still need --enforce-eager, or a modified version of feat(plugins): dynamic hidden-states layer swap for extract_hidden_states #996; (c) the extraction
connector stores no layer-set metadata, so the orchestrator must record its own {trial → layer set} mapping. If feat(plugins): dynamic hidden-states layer swap for extract_hidden_states #996 does not land, Stage 1 still works by
relaunching the server per candidate — just more slowly.
NB: I have more data for Qwen3-8B and Laguna-XS-2.1 with 1-layer and 2-layer that I will format and post below soon.
Motivation
Choosing which target hidden-state layers to feed a drafter is a decision that
today is effectively a guess. In our Gemma-4-31B sweep the aux hidden-state layer
id (or set of ids) had a meaningful effect on acceptance length — the best single
layer beat the worst by a clear margin, and deep layers tended to beat shallow
ones — yet we have no principled, cheap procedure for finding a good selection.
We don't yet have enough evidence to claim how this ranks against other training
decisions in general, but it is clearly a knob worth choosing deliberately rather
than by default. Doing a full-data training run per candidate to find out does not
scale: an exhaustive layer search at full data cost is prohibitive.
Acceptance length as a function of the aux hidden-state layer id, at both data
scales. The choice of layer moves acceptance length substantially, and the shape
of the curve is the same at small and large data — deep layers win at both.
The key empirical observation motivating this RFC: a drafter's performance
under a small amount of training data is highly correlated with its performance
under a large amount of data. If cheap runs rank candidates the same way
expensive runs do, we can prune the search space cheaply and only spend
full-data compute on the finalists.
We measured this directly on Gemma-4-31B (
google/gemma-4-31B-it), DFlashdrafter, 1 epoch on ShareGPT, evaluated by drafts-weighted mean acceptance
length across 9 held-out subsets, comparing n=4096 (small) vs n=65536
(16× larger) training samples per config.
Single aux layer, sweep of layers 0,4,…,56 (n=15 configs):
Each point is one single-layer selection; x = acceptance length trained on 4096
samples, y = trained on 65536 samples (16× more data). The cheap-run score
reliably predicts the expensive-run score.
Five random aux layers, configs sampled by depth category (n=12 configs):
Same relationship holds for five-layer selections: small-data acceptance length tracks large-data acceptance length.
The practical takeaway matters more than the aggregate correlation: the top
set is preserved even when exact order within it is not. For single layers,
the top-3 by small data are exactly the top-3 by large data (order included). For
five-layer configs, the top-2 by small data are exactly the top-2 by large data
as a set, but the #-1 and #-2 swap places — so small data does not reliably
pick the single winner there; it reliably picks a small shortlist that contains
it. This is precisely why the funnel carries a top-k (not top-1) from Stage 1
into Stage 2 to confirm, rather than trusting the cheap run's #-1 outright — while
still spending ~16× less per Stage 1 run.
Proposed Change.
Proposed Change
Add a staged (funnel) layer-selection pathway to speculators that turns
layer selection into an explicit, cheap-to-expensive search instead of a guess.
Three stages, each narrowing the candidate set while increasing the data spent
per candidate:
selections on a small sample budget (e.g. ~4k samples, 1 epoch). Rank by
acceptance length. Cheap enough to cover a wide search.
from Stage 1 on a larger sample budget (e.g. ~64k). This filters out the
small handful of cases where small-data ranking jitters, at a fraction of the
cost of running everything at this scale.
full dataset with production hyperparameters.
Concretely, this would add to speculators:
(e.g.
single-layer-sweep,random-k,depth-biasedwith late/uniform/earlyweightings — the sampling scheme we used here) plus
kper stage and thesample budget per stage.
scripts/select_layers.py)that runs the three stages: fan out Stage 1 trainings, rank by benchmarked
acceptance length, carry the top-k into Stage 2, then emit the recommended
selection (and optionally kick off Stage 3).
selection, stage, sample budget, and acceptance length, so the funnel is
auditable and re-rankable.
--target-layer-idstraining path and the benchmarkharness — this is orchestration on top of what already exists, not a new
training mode.
Enabling infrastructure: runtime layer swap (depends on #996)
Stage 1 runs many candidate layer selections back-to-back. Today each candidate
requires relaunching the vLLM extraction server with a different
--target-layer-ids, paying full model-load + startup cost per trial — whichdominates wall-clock when each training run is itself cheap (small data). To make
the sequential cheap experiments practical, we need to change which target
hidden layers are captured at runtime, without restarting the engine.
The draft PR vllm-project/speculators#996
("dynamic hidden-states layer swap for extract_hidden_states") provides exactly
this. It adds an opt-in vLLM plugin that swaps the captured aux-layer set live
over HTTP (
POST /aux_hidden_state_layers) with no recompile and no--enforce-eager: it replaces thetorch.compile-frozenlayer_idx in aux_layersmembership test with a masked accumulate overNfixed buffers,where the selection is a one-hot mask in a registered buffer, so a swap is an
in-place
mask.copy_(...)read live by both the compiled graph and captured CUDAgraphs. This is the mechanism a Stage 1 orchestrator would drive: launch the
extraction server once, then iterate candidates by POSTing new layer sets between
trials.
One constraint shapes the search design: #996 fixes the layer count at launch
(only the set is swappable). So a single extraction server can sweep candidates
of a fixed size
k(e.g. all single-layer, or all 5-layer configs); sweeping adifferent
krequires a fresh launch. This fits the funnel naturally — pickk,sweep sets of that size cheaply, confirm, commit — and is called out as a
dependency/assumption for Stage 1 below.
Any Other Things.
Part of a broader hyper-parameter optimization suite
Layer selection is the first concrete instance of a general pattern, and we
propose building it so the machinery generalizes. The same cheap-predicts-
expensive funnel — plus a shared candidate-config schema, staged orchestrator,
benchmark harness, and leaderboard artifact — likely applies to many knobs in speculators training.
Search within a fixed number of hidden states
One design rule falls out of the data: rank selections only against others with
the same number of hidden states — do not pool across different counts. The
correlation figures above each hold within a fixed count, but the absolute
acceptance-length scale shifts with the count, so a cross-count comparison is not
apples-to-apples.
Both populations plotted together with separate fits. The five-layer cloud sits
well above the single-layer cloud: at the same small-data acceptance length, a
five-layer selection reaches ~0.2 higher acceptance length at large data. The
extra hidden states add capacity, which shifts the whole curve — so a five-layer
config and a single-layer config with identical small-data scores are not
equivalent. The small-predicts-large property we rely on is a within-count statement.
This is why the funnel picks a number of hidden states
kand searches sets ofthat size, and it dovetails with #996's fixed-count-per-launch constraint: a
single extraction server sweeps one
k, exactly the unit the ranking is validover.
Any Other Things
Evidence appendix (full per-config numbers) and caveats:
acceptance length over 9 subsets. Single-layer sweep = 15 configs complete;
five-layer sweep = 12 of 15 planned configs complete at drafting time (will be
refreshed to 15 before posting).
sliding-window (local) layers — it does not probe the 10 global-attention
layers. Each (config, sample-size) point is a single run (1 epoch, one seed),
so run-to-run variance is unmeasured; this is exactly why Stage 2 confirmation
exists in the proposal rather than trusting Stage 1 outright.
preserves the top of the ranking (what the funnel needs). It does not
perfectly preserve mid-pack ordering — closely-scoring candidates reshuffle
between scales — which is expected and handled by carrying a top-k (not top-1)
into Stage 2.
apply to EAGLE3 / P-EAGLE and other targets.
runtime layer-swap plugin from
#996. Its constraints
carry into the search: (a) the aux-layer count is fixed per server launch, so
each server sweeps one fixed
k; (b) live swap without--enforce-eagercoversmodels that capture via
EagleModelMixin._maybe_add_hidden_state(Qwen2/Qwen3,Llama, generic dense/MoE) — models that inline the membership test (e.g.
deepseek_v2,qwen3_next) still need--enforce-eager, or a modified version of feat(plugins): dynamic hidden-states layer swap for extract_hidden_states #996; (c) the extractionconnector stores no layer-set metadata, so the orchestrator must record its own
{trial → layer set}mapping. If feat(plugins): dynamic hidden-states layer swap for extract_hidden_states #996 does not land, Stage 1 still works byrelaunching the server per candidate — just more slowly.
NB: I have more data for Qwen3-8B and Laguna-XS-2.1 with 1-layer and 2-layer that I will format and post below soon.