"""Packed fast path: whole requests concatenated into one padding-free sequence per forward pass. Each packed unit is a whole request (or, for over-length requests, a chunk of its questions) with one readout per question marker. Requests stay independent: - full-attention layers: varlen FlashAttention (``torch.nn.attention.varlen``) with window (-1, -1), i.e. noncausal within each request, which is what the checkpoint's padding-masked noncausal SDPA computes; - linear-attention layers: ``fla``'s chunked gated delta rule with ``cu_seqlens`` (state reset per request) and a depthwise causal convolution that does not read across request boundaries; - positions restart at 0 for every request; image requests get their own M-RoPE positions (``get_rope_index``); - readouts are gathered at the marker positions only. Optional speedups (``FastOptions``): torch.compile of sub-modules or whole layers, static pack sizes with CUDA graphs (filler sequences reach the bucket size; request boundaries reach the eager kernels through a side channel), last-layer pruning at marker rows, and FP8 matmuls. The decoder layers are re-expressed here with the checkpoint's own modules and weights; the Transformers model is not patched, so the reference path of the same ``Decider`` stays exact. """ from __future__ import annotations import queue import threading from collections.abc import Iterator from typing import TYPE_CHECKING, Any import torch import torch.nn.functional as F from transformers.activations import ACT2FN from transformers.models.qwen3_5.modeling_qwen3_5 import apply_rotary_pos_emb from .protocol import Encoded from .types import Request if TYPE_CHECKING: from .model import Decider, FastOptions FILLER_MAX = 1024 # filler sequences that reach a static size are split into chunks of at most this many tokens _PACK: dict[str, Any] = {} # boundaries of the current pack; only read by the eager kernels below def _require_kernels() -> tuple[Any, Any]: try: from fla.ops.gated_delta_rule import chunk_gated_delta_rule except ImportError as error: # pragma: no cover - environment dependent raise ImportError( "The fast path needs flash-linear-attention (pip install flash-linear-attention); " "use fast=False for the reference path" ) from error try: from torch.nn.attention.varlen import varlen_attn except ImportError as error: # pragma: no cover raise ImportError("The fast path needs torch>=2.10 with torch.nn.attention.varlen") from error return chunk_gated_delta_rule, varlen_attn _chunk_rule, _varlen = None, None @torch.compiler.disable def _gated_delta(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, g: torch.Tensor, beta: torch.Tensor) -> Any: out, _ = _chunk_rule(q, k, v, g=g, beta=beta, initial_state=None, output_final_state=False, use_qk_l2norm_in_kernel=True, cu_seqlens=_PACK["cu_long"], cu_seqlens_cpu=_PACK["cu_cpu"]) return out @torch.compiler.disable def _attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, scale: float) -> torch.Tensor: # (1, H, T, D) -> (T, H, D); noncausal inside each request, nothing across requests q, k, v = (x[0].transpose(0, 1) for x in (q, k, v)) cu, mx = _PACK["cu"], _PACK["max"] out = _varlen(q, k, v, cu, cu, mx, mx, scale=scale, window_size=(-1, -1), enable_gqa=k.shape[1] != q.shape[1]) return out.unsqueeze(0) # (1, T, H, D) def packed_conv(x: torch.Tensor, weight: torch.Tensor, seqpos: torch.Tensor) -> torch.Tensor: """Depthwise causal conv over (1, C, T) that never reads taps before the start of a request (fp32 sums).""" taps = weight.shape[-1] xf = x.float() out = xf * weight[:, taps - 1, None].float() for shift in range(1, taps): shifted = F.pad(xf, (shift, 0))[..., : x.shape[-1]] out = out + shifted * (seqpos >= shift).float() * weight[:, taps - 1 - shift, None].float() return out.to(x.dtype) def linear_attention(mod: torch.nn.Module, x: torch.Tensor, seqpos: torch.Tensor, conv: Any) -> torch.Tensor: """Qwen3_5GatedDeltaNet.forward without cache or padding, on a packed (1, T, D) sequence.""" _, length, _ = x.shape mixed = mod.in_proj_qkv(x).transpose(1, 2) z = mod.in_proj_z(x).reshape(1, length, -1, mod.head_v_dim) b = mod.in_proj_b(x) a = mod.in_proj_a(x) if mod.conv1d.bias is not None: raise ValueError("Unexpected convolution bias") mixed = ACT2FN[mod.activation](conv(mixed, mod.conv1d.weight.squeeze(1), seqpos)).transpose(1, 2) query, key, value = torch.split(mixed, [mod.key_dim, mod.key_dim, mod.value_dim], dim=-1) query = query.reshape(1, length, -1, mod.head_k_dim) key = key.reshape(1, length, -1, mod.head_k_dim) value = value.reshape(1, length, -1, mod.head_v_dim) beta = b.sigmoid() g = -mod.A_log.float().exp() * F.softplus(a.float() + mod.dt_bias) if mod.num_v_heads // mod.num_k_heads > 1: query = query.repeat_interleave(mod.num_v_heads // mod.num_k_heads, dim=2) key = key.repeat_interleave(mod.num_v_heads // mod.num_k_heads, dim=2) core = _gated_delta(query, key, value, g, beta) core = mod.norm(core.reshape(-1, mod.head_v_dim), z.reshape(-1, mod.head_v_dim)) return mod.out_proj(core.reshape(1, length, -1)) def full_attention(attn: torch.nn.Module, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: _, length, _ = x.shape shape = (1, length, -1, attn.head_dim) query, gate = torch.chunk(attn.q_proj(x).view(1, length, -1, attn.head_dim * 2), 2, dim=-1) gate = gate.reshape(1, length, -1) query = attn.q_norm(query.view(shape)).transpose(1, 2) key = attn.k_norm(attn.k_proj(x).view(shape)).transpose(1, 2) value = attn.v_proj(x).view(shape).transpose(1, 2) query, key = apply_rotary_pos_emb(query, key, cos, sin) out = _attention(query, key, value, attn.scaling) return attn.o_proj(out.reshape(1, length, -1).contiguous() * torch.sigmoid(gate)) def decoder_layer( layer: torch.nn.Module, h: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, seqpos: torch.Tensor, conv: Any = packed_conv, ) -> torch.Tensor: x = layer.input_layernorm(h) if layer.block_type == "linear_attention": x = linear_attention(layer.linear_attn, x, seqpos, conv) else: x = full_attention(layer.self_attn, x, cos, sin) h = h + x return h + layer.mlp(layer.post_attention_layernorm(h)) def last_layer_at( layer: torch.nn.Module, h: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, sel: torch.Tensor, cu_q: torch.Tensor, max_q: int, cu_k: torch.Tensor, max_k: int, ) -> torch.Tensor: """Last (full-attention) layer with K/V for every token but queries, output and MLP only at rows ``sel``.""" attn = layer.self_attn x = layer.input_layernorm(h) length, hd, n = x.shape[1], attn.head_dim, sel.shape[0] key = attn.k_norm(attn.k_proj(x).view(1, length, -1, hd)).transpose(1, 2) value = attn.v_proj(x).view(1, length, -1, hd).transpose(1, 2) query, gate = torch.chunk(attn.q_proj(x[:, sel]).view(1, n, -1, hd * 2), 2, dim=-1) gate = gate.reshape(1, n, -1) query = attn.q_norm(query).transpose(1, 2) query, _ = apply_rotary_pos_emb(query, query, cos[:, sel], sin[:, sel]) _, key = apply_rotary_pos_emb(key, key, cos, sin) q, k, v = (t[0].transpose(0, 1).contiguous() for t in (query, key, value)) out = _varlen(q, k, v, cu_q, cu_k, max_q, max_k, scale=attn.scaling, window_size=(-1, -1), enable_gqa=k.shape[1] != q.shape[1]) out = attn.o_proj(out.reshape(1, n, -1) * torch.sigmoid(gate)) hs = h[:, sel] + out return hs + layer.mlp(layer.post_attention_layernorm(hs)) class FP8Linear(torch.nn.Module): """Rowwise-scaled float8_e4m3fn matmul: per-output-channel weight scales, per-token activation scales.""" def __init__(self, linear: torch.nn.Linear) -> None: super().__init__() weight = linear.weight.data.float() scale = weight.abs().amax(dim=1, keepdim=True).clamp(min=1e-12) / 448.0 self.register_buffer("wq", (weight / scale).to(torch.float8_e4m3fn)) self.register_buffer("w_scale", scale.t().contiguous()) self.bias = linear.bias self.out_features = linear.out_features def forward(self, x: torch.Tensor) -> torch.Tensor: shape = x.shape flat = x.reshape(-1, shape[-1]) a_scale = flat.abs().amax(dim=-1, keepdim=True).float().clamp(min=1e-12) / 448.0 xq = (flat.float() / a_scale).to(torch.float8_e4m3fn) out = torch._scaled_mm(xq, self.wq.t(), scale_a=a_scale, scale_b=self.w_scale, bias=self.bias, out_dtype=torch.bfloat16) return out.reshape(*shape[:-1], self.out_features) def to_fp8(module: torch.nn.Module) -> None: for name, child in list(module.named_children()): if isinstance(child, torch.nn.Linear) and child.in_features % 16 == 0 and child.out_features % 16 == 0 \ and child.out_features >= 256: setattr(module, name, FP8Linear(child)) else: to_fp8(child) class FastPath: def __init__(self, decider: Decider, options: FastOptions) -> None: global _chunk_rule, _varlen if decider.device.type != "cuda": raise ValueError("The fast path needs a CUDA device") _chunk_rule, _varlen = _require_kernels() if options.cuda_graphs and options.compile != "layer": raise ValueError("cuda_graphs=True needs compile='layer'") if options.budget() < 1: raise ValueError("The packing budget must be positive") self.decider = decider self.options = options self.lm = decider.backbone.language_model self.layers = list(self.lm.layers) if self.layers[-1].block_type != "full_attention": raise ValueError("Last-layer pruning expects a full-attention last layer") self.image_token_id = decider.backbone.config.image_token_id if options.fp8: to_fp8(self.lm.layers) torch.cuda.empty_cache() torch._dynamo.config.cache_size_limit = max(torch._dynamo.config.cache_size_limit, 256) torch._dynamo.config.accumulated_cache_size_limit = max( torch._dynamo.config.accumulated_cache_size_limit, 4096) self.conv = packed_conv self.layer_fn: Any = decoder_layer self.graph_fn: Any = None if options.compile == "sub": self.conv = torch.compile(packed_conv, dynamic=True) for layer in self.layers: mods = [layer.mlp, layer.input_layernorm, layer.post_attention_layernorm] mods += [layer.linear_attn.norm] if layer.block_type == "linear_attention" else \ [layer.self_attn.q_norm, layer.self_attn.k_norm] for mod in mods: mod.forward = torch.compile(mod.forward, dynamic=True) elif options.compile == "layer": if options.cuda_graphs: self.graph_fn = torch.compile(decoder_layer, mode="reduce-overhead", dynamic=False) self.layer_fn = torch.compile(decoder_layer, dynamic=True) self.last_fn: Any = last_layer_at if options.compile is None else torch.compile(last_layer_at, dynamic=True) self.buckets = tuple(sorted(options.buckets)) if options.cuda_graphs else () # -------------------------------------------------------------- scheduling def run(self, requests: list[Request]) -> Iterator[tuple[Request, dict[str, list[float]], list[Encoded]]]: """Yield (request, logits per question, chunks) in input order.""" encoder, max_length = self.decider.encoder, self.decider.max_length for request in requests: encoder.validate(request) budget = self.options.budget() def plans() -> Iterator[tuple[int, list[Encoded]]]: for index, request in enumerate(requests): yield index, encoder.plan(request, max_length) source = _prefetch(plans()) if self.options.prefetch and len(requests) > 1 else plans() pending: dict[int, list[Encoded]] = {} results: dict[int, dict[str, list[float]]] = {} remaining: dict[int, int] = {} queue_: list[tuple[int, Encoded]] = [] queued_tokens = 0 next_out = 0 def flush(force: bool) -> None: nonlocal queue_, queued_tokens while queue_ and (force or queued_tokens >= budget): pack, used = [], 0 for item in queue_: if pack and used + item[1].length > budget: break pack.append(item) used += item[1].length for (index, chunk), rows in zip(pack, self.forward([c for _, c in pack]), strict=True): results.setdefault(index, {}).update(zip(chunk.keys, rows, strict=True)) remaining[index] -= 1 queue_ = queue_[len(pack):] queued_tokens -= used if not force and queued_tokens < budget: break for index, plan in source: pending[index] = plan remaining[index] = len(plan) for chunk in plan: queue_.append((index, chunk)) queued_tokens += chunk.length flush(force=False) while next_out in pending and remaining[next_out] == 0: yield requests[next_out], results.pop(next_out), pending.pop(next_out) next_out += 1 flush(force=True) while next_out in pending: yield requests[next_out], results.pop(next_out), pending.pop(next_out) next_out += 1 # -------------------------------------------------------------- forward def _positions(self, chunk: Encoded) -> torch.Tensor: if chunk.image_grid_thw is None: return torch.arange(chunk.length).expand(3, -1) positions, _ = self.decider.backbone.get_rope_index( chunk.input_ids[None], chunk.mm_token_type_ids[None], image_grid_thw=chunk.image_grid_thw) return positions[:, 0] @torch.inference_mode() def forward(self, chunks: list[Encoded]) -> list[list[list[float]]]: device = self.decider.device lengths = [chunk.length for chunk in chunks] total = sum(lengths) bucket = next((b for b in self.buckets if b >= total), None) ids = [chunk.input_ids for chunk in chunks] positions = [self._positions(chunk) for chunk in chunks] if bucket is not None: # filler sequences up to the static size; never read out fill = bucket - total while fill > 0: size = min(fill, FILLER_MAX) lengths.append(size) ids.append(torch.full((size,), self.decider.encoder.pad_id, dtype=torch.long)) positions.append(torch.arange(size).expand(3, -1)) fill -= size cu_cpu = F.pad(torch.tensor(lengths).cumsum(0), (1, 0)) seqpos = torch.cat([torch.arange(n) for n in lengths]).to(device, non_blocking=True) input_ids = torch.cat(ids).to(device, non_blocking=True) rope = torch.cat(positions, dim=1)[:, None, :].to(device, non_blocking=True) cu = cu_cpu.to(device, torch.int32, non_blocking=True) _PACK.update(cu=cu, cu_long=cu.long(), cu_cpu=cu_cpu, max=max(lengths)) h = self.lm.embed_tokens(input_ids)[None] pixels = [chunk.pixel_values for chunk in chunks if chunk.pixel_values is not None] if pixels: grids = torch.cat([c.image_grid_thw for c in chunks if c.image_grid_thw is not None]).to(device) features = self.decider.backbone.get_image_features(torch.cat(pixels).to(device), grids, return_dict=True).pooler_output features = torch.cat(features).to(h.dtype) mask = (input_ids == self.image_token_id)[None, :, None].expand_as(h) h = h.masked_scatter(mask, features) cos, sin = self.lm.rotary_emb(h, rope) starts = cu_cpu[: len(chunks)].tolist() sel = torch.cat([chunk.markers + start for chunk, start in zip(chunks, starts, strict=True)]).to(device) static = bucket is not None and self.graph_fn is not None # A chunk longer than the budget (max_length > budget) runs alone and uncompiled. fn = self.graph_fn if static else self.layer_fn if total <= self.options.budget() else decoder_layer layers = self.layers[:-1] if self.options.prune_last else self.layers if static: torch.compiler.cudagraph_mark_step_begin() for layer in layers: h = fn(layer, h, cos, sin, seqpos, self.conv) if static: h = h.clone() # CUDA-graph outputs are overwritten by the next replay if self.options.prune_last: per_seq = torch.tensor([0] + [len(c.keys) for c in chunks]).cumsum(0) cu_q = per_seq.to(device, torch.int32) h = self.last_fn(self.layers[-1], h, cos, sin, sel, cu_q, max(len(c.keys) for c in chunks), cu[: len(chunks) + 1], max(c.length for c in chunks)) else: h = h[:, sel] logits = self.decider.readout(self.lm.norm(h)[0]).float().cpu() out, cursor = [], 0 for chunk in chunks: out.append([logits[cursor + j, :count].tolist() for j, count in enumerate(chunk.counts)]) cursor += len(chunk.keys) return out def warmup(self) -> None: """Run one dummy pack per static bucket (or two sizes when dynamic) so compilation happens up front.""" request = {"state": "warmup", "questions": {"q": {"type": "noul", "instructions": "Warm up?"}}} chunk = self.decider.encoder.plan(request, None)[0] sizes = self.buckets or (512, 4096) for size in sizes: count = max(1, min(size // chunk.length, 64)) if not self.buckets else 1 for _ in range(3 if self.buckets else 2): self.forward([chunk] * count) torch.cuda.synchronize() def _prefetch(source: Iterator[Any], depth: int = 64) -> Iterator[Any]: """Run ``source`` (tokenization and image preprocessing) in a background thread; re-raise its errors.""" items: queue.Queue[Any] = queue.Queue(maxsize=depth) done = object() def produce() -> None: try: for item in source: items.put(item) except BaseException as error: # forwarded to the consumer items.put(error) items.put(done) threading.Thread(target=produce, daemon=True).start() while (item := items.get()) is not done: if isinstance(item, BaseException): raise item yield item