Buckets:

abayb's picture
download
raw
22 kB
"""Patch vLLM Gemma4 MTP drafting with a CUDA graph loop replay.
This file is loaded by the vLLM child process through PYTHONPATH. It intentionally
does not patch PLE. Pupa's serve.py patches PLE textfast and scale-folds through
the installed vLLM source so the fold can be verified fail-closed at load time.
"""
from __future__ import annotations
import importlib.abc
import importlib.util
import os
import sys
from copy import copy
from typing import Any
LOOPGRAPH_TARGET = "vllm.v1.spec_decode.gemma4"
LOOPGRAPH_WARMUP_CALLS = int(os.environ.get("LOOPGRAPH_WARMUP_CALLS", "48"))
LOOPGRAPH_REQUIRE_CAPTURE = os.environ.get("LOOPGRAPH_REQUIRE_CAPTURE") == "1"
def _call_base_propose(base_propose: Any, self: Any, kwargs: dict[str, Any]) -> Any:
return base_propose(self, **kwargs)
def _build_static_buffers(self: Any, state: dict[str, Any], cad: Any) -> None:
import torch
device = self.device
token_count = self.num_speculative_tokens
state["out"] = torch.zeros((1, token_count), dtype=torch.int64, device=device)
state["seq_lens"] = torch.zeros_like(cad.seq_lens[:1])
state["block_tables"] = {}
static_cad = copy(cad)
static_cad.seq_lens = state["seq_lens"]
static_cad.num_actual_tokens = 1
static_cad.max_query_len = 1
static_cad.max_seq_len = self.max_model_len
static_cad.slot_mapping = self._slot_mapping_buffer[:1]
static_cad.query_start_loc = self.arange[:2]
per_layer_metadata = {}
for group in self.draft_attn_groups:
group_id = group.kv_cache_group_id
source = self._per_group_block_tables.get(group_id, cad.block_table_tensor)[:1]
block_size = group.get_metadata_builder().kv_cache_spec.block_size
width = max(source.shape[1], -(-self.max_model_len // block_size))
static_block_table = torch.zeros(
(1, width), dtype=source.dtype, device=device
)
state["block_tables"][group_id] = static_block_table
group_cad = copy(static_cad)
group_cad.block_table_tensor = static_block_table
metadata = group.get_metadata_builder().build_for_drafting(
common_attn_metadata=group_cad,
draft_index=1,
)
for layer_name in group.layer_names:
per_layer_metadata[layer_name] = metadata
state["metadata"] = per_layer_metadata
def _refresh_static_buffers(self: Any, state: dict[str, Any], cad: Any) -> None:
state["seq_lens"].copy_(cad.seq_lens[:1])
for group_id, static_block_table in state["block_tables"].items():
source = self._per_group_block_tables.get(group_id, cad.block_table_tensor)[:1]
width = min(source.shape[1], static_block_table.shape[1])
static_block_table[:, :width].copy_(source[:, :width])
def _run_graph_body(self: Any, state: dict[str, Any]) -> None:
from vllm.config import CUDAGraphMode
from vllm.forward_context import set_forward_context
token_count = self.num_speculative_tokens
output = state["out"]
with set_forward_context(
state["metadata"],
self.vllm_config,
num_tokens=1,
num_tokens_across_dp=None,
cudagraph_runtime_mode=CUDAGraphMode.NONE,
slot_mapping=self._get_slot_mapping(1),
):
for index in range(token_count - 1):
self.input_ids[:1].copy_(output[0, index : index + 1])
last_hidden, backbone_hidden = self.model(
input_ids=self.input_ids[:1],
positions=self._get_positions(1),
inputs_embeds=None,
hidden_states=self.hidden_states[:1],
)
self.hidden_states[:1].copy_(backbone_hidden[:1])
token = self.model.get_top_tokens(last_hidden[:1])
output[0, index + 1 : index + 2].copy_(token)
def _capture_graph(self: Any, state: dict[str, Any]) -> None:
import torch
for _ in range(2):
_run_graph_body(self, state)
torch.cuda.synchronize()
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
_run_graph_body(self, state)
state["graph"] = graph
def _is_loopgraph_eligible(self: Any, state: dict[str, Any], cad: Any) -> bool:
return (
not state["failed"]
and self.num_speculative_tokens > 1
and not self.parallel_drafting
and not self._enable_probabilistic_draft_probs
and not self.supports_mm_inputs
and not self.uses_mrope
and self.constant_draft_positions
and cad.batch_size() == 1
)
def _raise_or_fallback(exc: Exception) -> None:
if LOOPGRAPH_REQUIRE_CAPTURE:
raise RuntimeError("LOOPGRAPH_REQUIRE_CAPTURE=1 but capture failed") from exc
def _apply_loopgraph_patch(module: Any) -> None:
import torch
from vllm.forward_context import set_forward_context
proposer_cls = module.Gemma4Proposer
base_propose = proposer_cls.propose
def propose(
self: Any,
target_token_ids: Any,
target_positions: Any,
target_hidden_states: Any,
next_token_ids: Any,
token_indices_to_sample: Any,
common_attn_metadata: Any,
sampling_metadata: Any,
mm_embed_inputs: Any = None,
num_rejected_tokens_gpu: Any = None,
slot_mappings: Any = None,
) -> Any:
kwargs = {
"target_token_ids": target_token_ids,
"target_positions": target_positions,
"target_hidden_states": target_hidden_states,
"next_token_ids": next_token_ids,
"token_indices_to_sample": token_indices_to_sample,
"common_attn_metadata": common_attn_metadata,
"sampling_metadata": sampling_metadata,
"mm_embed_inputs": mm_embed_inputs,
"num_rejected_tokens_gpu": num_rejected_tokens_gpu,
"slot_mappings": slot_mappings,
}
state = self.__dict__.setdefault(
"_pupa_loopgraph",
{"calls": 0, "graph": None, "failed": False},
)
if not _is_loopgraph_eligible(self, state, common_attn_metadata):
return _call_base_propose(base_propose, self, kwargs)
state["calls"] += 1
if state["graph"] is None and state["calls"] <= LOOPGRAPH_WARMUP_CALLS:
return _call_base_propose(base_propose, self, kwargs)
self._last_draft_probs = None
token_count = self.num_speculative_tokens
num_tokens, token_indices_to_sample, cad = self.set_inputs_first_pass(
target_token_ids=target_token_ids,
next_token_ids=next_token_ids,
target_positions=target_positions,
target_hidden_states=target_hidden_states,
token_indices_to_sample=token_indices_to_sample,
cad=common_attn_metadata,
num_rejected_tokens_gpu=num_rejected_tokens_gpu,
)
_, per_layer_metadata = self.build_per_group_and_layer_attn_metadata(cad)
cg_mode, num_input_tokens, num_tokens_across_dp = (
self._determine_batch_execution_and_padding(num_tokens)
)
model_kwargs, slot_map_size = self.build_model_inputs_first_pass(
num_tokens,
num_input_tokens,
mm_embed_inputs,
)
with set_forward_context(
per_layer_metadata,
self.vllm_config,
num_tokens=num_input_tokens,
num_tokens_across_dp=num_tokens_across_dp,
cudagraph_runtime_mode=cg_mode,
slot_mapping=self._get_slot_mapping(slot_map_size, cad.slot_mapping),
):
last_hidden, hidden = self.model(**model_kwargs)
sample_hidden = last_hidden[token_indices_to_sample]
positions = self.positions[token_indices_to_sample]
first_hidden = hidden[token_indices_to_sample]
self.positions[:1] = positions
first_token, _ = self._sample_draft_tokens(sample_hidden, sampling_metadata)
cad.num_actual_tokens = 1
cad.max_query_len = 1
cad.query_start_loc = self.arange[:2]
cad.query_start_loc_cpu = torch.from_numpy(self.token_arange_np[:2]).clone()
if num_rejected_tokens_gpu is not None:
cad.seq_lens -= num_rejected_tokens_gpu
cad._seq_lens_cpu = None
cad._num_computed_tokens_cpu = None
if state["graph"] is None and not state["failed"]:
try:
_build_static_buffers(self, state, cad)
_refresh_static_buffers(self, state, cad)
state["out"][0, 0:1].copy_(first_token)
self.hidden_states[:1].copy_(first_hidden)
_capture_graph(self, state)
print(
f"[pupa-loopgraph] captured K-1={token_count - 1} graph "
f"at eligible call {state['calls']} (pid {os.getpid()})",
file=sys.stderr,
flush=True,
)
except Exception as exc:
state["failed"] = True
state["graph"] = None
print(
f"[pupa-loopgraph] capture failed: {exc!r}",
file=sys.stderr,
flush=True,
)
_raise_or_fallback(exc)
if state["graph"] is not None:
_refresh_static_buffers(self, state, cad)
state["out"][0, 0:1].copy_(first_token)
self.hidden_states[:1].copy_(first_hidden)
state["graph"].replay()
return state["out"].clone()
cg_mode, input_batch_size, batch_size_dp = (
self._determine_batch_execution_and_padding(1)
)
draft_tokens = [first_token]
hidden_current = first_hidden
loop_metadata = None
for index in range(token_count - 1):
input_ids = draft_tokens[-1].int()
if index == 0:
_, loop_metadata = self.build_per_group_and_layer_attn_metadata(
cad,
draft_index=1,
)
self.input_ids[:1] = input_ids
self.hidden_states[:1] = hidden_current
kwargs = {
"input_ids": self.input_ids[:input_batch_size],
"positions": self._get_positions(input_batch_size),
"inputs_embeds": None,
"hidden_states": self.hidden_states[:input_batch_size],
}
with set_forward_context(
loop_metadata,
self.vllm_config,
num_tokens=input_batch_size,
num_tokens_across_dp=batch_size_dp,
cudagraph_runtime_mode=cg_mode,
slot_mapping=self._get_slot_mapping(input_batch_size),
):
last_hidden, hidden = self.model(**kwargs)
hidden_current = hidden[:1]
token, _ = self._sample_draft_tokens(last_hidden[:1], sampling_metadata)
draft_tokens.append(token)
return torch.stack(draft_tokens, dim=1)
proposer_cls.propose = propose
print(
f"[pupa-loopgraph] patched Gemma4Proposer.propose in pid {os.getpid()} "
f"(warmup_calls={LOOPGRAPH_WARMUP_CALLS}, "
f"require_capture={LOOPGRAPH_REQUIRE_CAPTURE})",
file=sys.stderr,
flush=True,
)
class _PatchingLoader(importlib.abc.Loader):
def __init__(self, inner: importlib.abc.Loader, patch_fn: Any) -> None:
self._inner = inner
self._patch_fn = patch_fn
def create_module(self, spec: Any) -> Any:
return self._inner.create_module(spec)
def exec_module(self, module: Any) -> None:
self._inner.exec_module(module)
self._patch_fn(module)
class _TargetFinder(importlib.abc.MetaPathFinder):
def __init__(self, target: str, patch_fn: Any) -> None:
self._target = target
self._patch_fn = patch_fn
self._busy = False
def find_spec(self, fullname: str, path: Any = None, target: Any = None) -> Any:
if fullname != self._target or self._busy:
return None
self._busy = True
try:
spec = importlib.util.find_spec(fullname)
finally:
self._busy = False
if spec is None or spec.loader is None:
return None
spec.loader = _PatchingLoader(spec.loader, self._patch_fn)
return spec
ADAPTIVE_SPEC = os.environ.get("ADAPTIVE_SPEC") == "1"
ADAPTIVE_WARMUP_CALLS = int(os.environ.get("ADAPTIVE_WARMUP_CALLS", "96"))
ADAPTIVE_STOP_QUANTILE = float(os.environ.get("ADAPTIVE_STOP_QUANTILE", "0.30"))
ADAPTIVE_LOG_EVERY = int(os.environ.get("ADAPTIVE_LOG_EVERY", "2000"))
def _apply_adaptive_patch(module: Any) -> None:
"""abay: margin-gated adaptive speculation depth.
Derivation (board msg 20260610-002612): conditional acceptance RISES with
depth (0.68 -> 0.81), so steps are bimodal easy/hard. Fixed K both
truncates easy runs and burns full drafts on hard steps. Policy: roll the
AR drafter up to K_max=num_speculative_tokens, stop as soon as the
drafter's sparse-logit top1-top2 margin drops below a threshold
calibrated from its own warmup margin distribution. Token selection is
argmax over the SAME sparse logits as get_top_tokens, so proposals are
unchanged where drafted; early-stopped positions are padded with token 0,
which exact-match verification simply rejects. Output tokens cannot
change by construction; only drafting cost does.
"""
import torch
from vllm.forward_context import set_forward_context
proposer_cls = module.Gemma4Proposer
base_propose = proposer_cls.propose
def _margin_sample(self: Any, hidden: Any) -> tuple[Any, float]:
me = self.model.masked_embedding
weight = self.model._get_full_lm_head_weight()
logits, indices = me._select_and_score(hidden, weight)
top2 = logits.topk(2, dim=-1)
token = indices.gather(-1, top2.indices[:, :1]).squeeze(-1)
margin = float(top2.values[0, 0] - top2.values[0, 1])
return token, margin
def _is_adaptive_eligible(self: Any, cad: Any, sampling_metadata: Any) -> bool:
return (
self.num_speculative_tokens > 1
and not self.parallel_drafting
and not self._enable_probabilistic_draft_probs
and not self.supports_mm_inputs
and not self.uses_mrope
and self.constant_draft_positions
and sampling_metadata.all_greedy
and getattr(self.model, "masked_embedding", None) is not None
and cad.batch_size() == 1
)
def propose(
self: Any,
target_token_ids: Any,
target_positions: Any,
target_hidden_states: Any,
next_token_ids: Any,
token_indices_to_sample: Any,
common_attn_metadata: Any,
sampling_metadata: Any,
mm_embed_inputs: Any = None,
num_rejected_tokens_gpu: Any = None,
slot_mappings: Any = None,
) -> Any:
kwargs = {
"target_token_ids": target_token_ids,
"target_positions": target_positions,
"target_hidden_states": target_hidden_states,
"next_token_ids": next_token_ids,
"token_indices_to_sample": token_indices_to_sample,
"common_attn_metadata": common_attn_metadata,
"sampling_metadata": sampling_metadata,
"mm_embed_inputs": mm_embed_inputs,
"num_rejected_tokens_gpu": num_rejected_tokens_gpu,
"slot_mappings": slot_mappings,
}
state = self.__dict__.setdefault(
"_abay_adaptive",
{
"calls": 0,
"tau": None,
"pool": [],
"steps": 0,
"forwards": 0,
"stops": [0] * 32,
},
)
if not _is_adaptive_eligible(self, common_attn_metadata, sampling_metadata):
return _call_base_propose(base_propose, self, kwargs)
state["calls"] += 1
calibrated = state["tau"] is not None
self._last_draft_probs = None
token_count = self.num_speculative_tokens
num_tokens, token_indices_to_sample, cad = self.set_inputs_first_pass(
target_token_ids=target_token_ids,
next_token_ids=next_token_ids,
target_positions=target_positions,
target_hidden_states=target_hidden_states,
token_indices_to_sample=token_indices_to_sample,
cad=common_attn_metadata,
num_rejected_tokens_gpu=num_rejected_tokens_gpu,
)
_, per_layer_metadata = self.build_per_group_and_layer_attn_metadata(cad)
cg_mode, num_input_tokens, num_tokens_across_dp = (
self._determine_batch_execution_and_padding(num_tokens)
)
model_kwargs, slot_map_size = self.build_model_inputs_first_pass(
num_tokens,
num_input_tokens,
mm_embed_inputs,
)
with set_forward_context(
per_layer_metadata,
self.vllm_config,
num_tokens=num_input_tokens,
num_tokens_across_dp=num_tokens_across_dp,
cudagraph_runtime_mode=cg_mode,
slot_mapping=self._get_slot_mapping(slot_map_size, cad.slot_mapping),
):
last_hidden, hidden = self.model(**model_kwargs)
sample_hidden = last_hidden[token_indices_to_sample]
positions = self.positions[token_indices_to_sample]
first_hidden = hidden[token_indices_to_sample]
self.positions[:1] = positions
first_token, first_margin = _margin_sample(self, sample_hidden)
cad.num_actual_tokens = 1
cad.max_query_len = 1
cad.query_start_loc = self.arange[:2]
cad.query_start_loc_cpu = torch.from_numpy(self.token_arange_np[:2]).clone()
if num_rejected_tokens_gpu is not None:
cad.seq_lens -= num_rejected_tokens_gpu
cad._seq_lens_cpu = None
cad._num_computed_tokens_cpu = None
margins = [first_margin]
draft_tokens = [first_token]
state["steps"] += 1
state["forwards"] += 1
stop_now = calibrated and first_margin < state["tau"]
if not stop_now:
cg_mode, input_batch_size, batch_size_dp = (
self._determine_batch_execution_and_padding(1)
)
hidden_current = first_hidden
loop_metadata = None
for index in range(token_count - 1):
input_ids = draft_tokens[-1].int()
if index == 0:
_, loop_metadata = self.build_per_group_and_layer_attn_metadata(
cad,
draft_index=1,
)
self.input_ids[:1] = input_ids
self.hidden_states[:1] = hidden_current
fwd_kwargs = {
"input_ids": self.input_ids[:input_batch_size],
"positions": self._get_positions(input_batch_size),
"inputs_embeds": None,
"hidden_states": self.hidden_states[:input_batch_size],
}
with set_forward_context(
loop_metadata,
self.vllm_config,
num_tokens=input_batch_size,
num_tokens_across_dp=batch_size_dp,
cudagraph_runtime_mode=cg_mode,
slot_mapping=self._get_slot_mapping(input_batch_size),
):
last_hidden, hidden = self.model(**fwd_kwargs)
hidden_current = hidden[:1]
token, margin = _margin_sample(self, last_hidden[:1])
draft_tokens.append(token)
margins.append(margin)
state["forwards"] += 1
if calibrated and margin < state["tau"]:
break
n_drafted = len(draft_tokens)
state["stops"][min(n_drafted, 31)] += 1
if not calibrated:
state["pool"].extend(margins)
if state["calls"] >= ADAPTIVE_WARMUP_CALLS:
pool = sorted(state["pool"])
idx = max(0, min(len(pool) - 1, int(ADAPTIVE_STOP_QUANTILE * len(pool))))
state["tau"] = pool[idx]
qs = {
q: pool[int(q * (len(pool) - 1))]
for q in (0.1, 0.3, 0.5, 0.7, 0.9)
}
print(
f"[abay-adaptive] calibrated tau={state['tau']:.3f} "
f"(quantile {ADAPTIVE_STOP_QUANTILE}, pool={len(pool)}, "
f"quantiles={ {k: round(v, 2) for k, v in qs.items()} }, "
f"K_max={token_count}, pid {os.getpid()})",
file=sys.stderr,
flush=True,
)
if ADAPTIVE_LOG_EVERY and state["steps"] % ADAPTIVE_LOG_EVERY == 0:
mean_fwd = state["forwards"] / state["steps"]
print(
f"[abay-adaptive] steps={state['steps']} mean_drafts={mean_fwd:.2f} "
f"stop_hist={state['stops'][1:token_count + 1]}",
file=sys.stderr,
flush=True,
)
stacked = torch.stack(draft_tokens, dim=1)
if n_drafted < token_count:
out = torch.zeros(
(1, token_count), dtype=stacked.dtype, device=stacked.device
)
out[:, :n_drafted] = stacked
return out
return stacked
proposer_cls.propose = propose
print(
f"[abay-adaptive] patched Gemma4Proposer.propose in pid {os.getpid()} "
f"(warmup_calls={ADAPTIVE_WARMUP_CALLS}, "
f"stop_quantile={ADAPTIVE_STOP_QUANTILE})",
file=sys.stderr,
flush=True,
)
if ADAPTIVE_SPEC:
sys.meta_path.insert(0, _TargetFinder(LOOPGRAPH_TARGET, _apply_adaptive_patch))
else:
sys.meta_path.insert(0, _TargetFinder(LOOPGRAPH_TARGET, _apply_loopgraph_patch))

Xet Storage Details

Size:
22 kB
·
Xet hash:
9b79194d9fa8b399dbaee1ca8973b60b334c417f3327e0ee96d96c37e41e38ed

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.