[LingBot World] Eleven bit-identical removals of repeated work in the realtime path (#7648)
Signed-off-by: tzhouam <tzhouam@connect.ust.hk> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9e2117f5e
commit
86fcfe95f1
23 files changed
+1957
-200
No files matched your search
@@ -15,7 +15,8 @@ from __future__ import annotations
|
||||
import inspect
|
||||
import os
|
||||
from collections.abc import Collection, Iterable, Sequence
|
||||
from typing import cast
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
import torch
|
||||
from vllm.logger import init_logger
|
||||
@@ -29,7 +30,11 @@ from vllm.v1.request import RequestStatus
|
||||
|
||||
from vllm_omni.diffusion.diffusion_kv.layout import build_kv_cache_tensor
|
||||
from vllm_omni.experimental.ar_diffusion.capability import ARDiffusionKVBranchSpec
|
||||
from vllm_omni.experimental.ar_diffusion.kv_cache.config import ARDiffusionKVConfig
|
||||
from vllm_omni.experimental.ar_diffusion.kv_cache.config import (
|
||||
KV_GATHER_ENV,
|
||||
ARDiffusionKVConfig,
|
||||
contiguous_kv_gather_enabled,
|
||||
)
|
||||
from vllm_omni.experimental.ar_diffusion.kv_cache.paged import (
|
||||
ChunkWindowManager,
|
||||
ChunkWindowSpec,
|
||||
@@ -158,6 +163,18 @@ def build_kv_manager(
|
||||
return KVCacheManager(config, **kwargs)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HistoryStagingState:
|
||||
"""What the staged history window currently holds: whose session and which visible blocks.
|
||||
|
||||
Owned by the cache next to the buffers themselves; ``ARDiffusionPagedForwardContext`` reads it to decide
|
||||
whether a forward may keep the staged history and writes back what it staged.
|
||||
"""
|
||||
|
||||
adapter: Any | None = None # weakref to the session adapter the window was staged for
|
||||
signature: tuple[Any, ...] | None = None
|
||||
|
||||
|
||||
class ARDiffusionKVCache:
|
||||
"""Own the paged KV pool and KV-branch-local storage for one model.
|
||||
|
||||
@@ -180,6 +197,7 @@ class ARDiffusionKVCache:
|
||||
kv_branches: tuple[ARDiffusionKVBranchSpec, ...],
|
||||
session_capacity: int,
|
||||
cross_attention_lengths: dict[str, int] | None = None,
|
||||
cross_attention_kv_heads: dict[str, int] | None = None,
|
||||
device: torch.device | None = None,
|
||||
frames_per_block: int = 1,
|
||||
max_scratch_tokens_per_branch: int = 0,
|
||||
@@ -225,6 +243,18 @@ class ARDiffusionKVCache:
|
||||
self.head_size = head_size
|
||||
self.dtype = dtype
|
||||
self.cross_attention_lengths = dict(cross_attention_lengths or {})
|
||||
# A cross-attention cache may hold more heads than the self-attention share: a model that keeps every
|
||||
# local head on every rank, rather than sharding heads across the sequence-parallel group, stores the
|
||||
# full local set here. Absent means the self-attention head count, which is what every caller did before.
|
||||
self.cross_attention_kv_heads = {
|
||||
name: int((cross_attention_kv_heads or {}).get(name, num_kv_heads)) for name in self.cross_attention_lengths
|
||||
}
|
||||
invalid_heads = {n: h for n, h in self.cross_attention_kv_heads.items() if h <= 0}
|
||||
if invalid_heads:
|
||||
raise ValueError(f"cross_attention_kv_heads must be positive, got {invalid_heads}")
|
||||
unknown_heads = set(cross_attention_kv_heads or {}) - set(self.cross_attention_lengths)
|
||||
if unknown_heads:
|
||||
raise ValueError(f"cross_attention_kv_heads names unknown caches: {sorted(unknown_heads)}")
|
||||
invalid_cross = {name: length for name, length in self.cross_attention_lengths.items() if length <= 0}
|
||||
if invalid_cross:
|
||||
raise ValueError(f"cross_attention_lengths must be positive, got {invalid_cross}")
|
||||
@@ -274,20 +304,46 @@ class ARDiffusionKVCache:
|
||||
self.scratch_reserved_bytes = self.scratch_num_blocks * page_size_bytes
|
||||
self.model_owned_state_bytes_per_session = model_owned_state_bytes_per_session
|
||||
|
||||
def _cross_pool_bytes(length: int) -> int:
|
||||
return int(2 * len(self.kv_branches) * length * num_kv_heads * head_size * dtype.itemsize * num_layers)
|
||||
def _cross_pool_bytes(length: int, heads: int) -> int:
|
||||
return int(2 * len(self.kv_branches) * length * heads * head_size * dtype.itemsize * num_layers)
|
||||
|
||||
self.cross_attention_bytes_per_session = sum(
|
||||
_cross_pool_bytes(length) for length in self.cross_attention_lengths.values()
|
||||
_cross_pool_bytes(length, self.cross_attention_kv_heads[name])
|
||||
for name, length in self.cross_attention_lengths.items()
|
||||
)
|
||||
|
||||
def _required_managed_blocks(capacity: int) -> int:
|
||||
resident_per_session = config.sink_chunks + config.window_chunks
|
||||
return self.num_local_kv_branches * (capacity * resident_per_session + self.frames_per_block) + 2
|
||||
|
||||
# reuse_history_staging keeps one contiguous K and V buffer per layer,
|
||||
# sized to the padded visible window (sink + window plus the
|
||||
# action-capacity block build_block_table always reserves), for the
|
||||
# whole worker. It is allocated lazily on the first prepared forward,
|
||||
# which is after admission, so it has to be reserved here or admission
|
||||
# can succeed and that first forward run out of memory. Only the
|
||||
# contiguous-gather attention path consumes it.
|
||||
self.history_staging_reserved_bytes = 0
|
||||
self.history_staging_tokens = 0
|
||||
if config.reuse_history_staging:
|
||||
if contiguous_kv_gather_enabled():
|
||||
self.history_staging_tokens = (
|
||||
self.spec.sliding_window + config.sink_chunks * config.chunk_size + block_size
|
||||
)
|
||||
self.history_staging_reserved_bytes = int(
|
||||
2 * num_layers * self.history_staging_tokens * num_kv_heads * head_size * dtype.itemsize
|
||||
)
|
||||
else:
|
||||
_log.warning(
|
||||
"reuse_history_staging is set but the contiguous K/V gather path (%s=1) is off: "
|
||||
"staging has no consumer and is neither budgeted nor allocated.",
|
||||
KV_GATHER_ENV,
|
||||
)
|
||||
|
||||
def _required_bytes(capacity: int) -> int:
|
||||
return (
|
||||
self.scratch_reserved_bytes
|
||||
+ self.history_staging_reserved_bytes
|
||||
+ capacity * self.cross_attention_bytes_per_session
|
||||
+ capacity * self.model_owned_state_bytes_per_session
|
||||
+ _required_managed_blocks(capacity) * page_size_bytes
|
||||
@@ -298,7 +354,7 @@ class ARDiffusionKVCache:
|
||||
raise ValueError(
|
||||
"AR-Diffusion available device memory cannot fit one session: "
|
||||
f"available={available_bytes} bytes, required={one_session_bytes} bytes "
|
||||
"(managed self-attention + cross-attention + scratch + model-owned state)."
|
||||
"(managed self-attention + cross-attention + scratch + model-owned state + K/V staging)."
|
||||
)
|
||||
self.memory_budget_bytes = max(self.configured_memory_budget_bytes, one_session_bytes)
|
||||
if self.memory_budget_bytes > self.configured_memory_budget_bytes:
|
||||
@@ -325,6 +381,7 @@ class ARDiffusionKVCache:
|
||||
self_attn_budget_bytes = (
|
||||
self.memory_budget_bytes
|
||||
- self.scratch_reserved_bytes
|
||||
- self.history_staging_reserved_bytes
|
||||
- self.cross_attention_reserved_bytes
|
||||
- self.model_owned_state_reserved_bytes
|
||||
)
|
||||
@@ -374,6 +431,20 @@ class ARDiffusionKVCache:
|
||||
dtype,
|
||||
device,
|
||||
)
|
||||
# reuse_history_staging: one contiguous (K, V) pair per layer, sized to the padded visible window this
|
||||
# cache admits and budgeted above, owned here next to the pools it stages from. Allocated outside any
|
||||
# capture and marked static, so compiled regions treat it as a stable input rather than a new tensor.
|
||||
self.history_staging: list[tuple[torch.Tensor, torch.Tensor]] = []
|
||||
self.history_staging_state = HistoryStagingState()
|
||||
if self.history_staging_tokens and device is not None:
|
||||
for _ in range(num_layers):
|
||||
pair = (
|
||||
torch.empty((self.history_staging_tokens, num_kv_heads, head_size), device=device, dtype=dtype),
|
||||
torch.empty((self.history_staging_tokens, num_kv_heads, head_size), device=device, dtype=dtype),
|
||||
)
|
||||
for buffer in pair:
|
||||
torch._dynamo.mark_static_address(buffer)
|
||||
self.history_staging.append(pair)
|
||||
|
||||
# -- cross-attention pool access -------------------------------------------
|
||||
# Cross-attn KV is static once populated — write once (from text encoder),
|
||||
@@ -455,7 +526,7 @@ class ARDiffusionKVCache:
|
||||
if not self._allocate_tensors:
|
||||
raise RuntimeError("AR-Diffusion cross-attention tensors require a configured pool device")
|
||||
|
||||
shape = (length, self.num_kv_heads, self.head_size)
|
||||
shape = (length, self.cross_attention_kv_heads[cache_name], self.head_size)
|
||||
expected_input_shape = (1, *shape)
|
||||
k_pool = [torch.empty(shape, dtype=self.dtype, device=self.device) for _ in range(self.num_layers)]
|
||||
v_pool = [torch.empty(shape, dtype=self.dtype, device=self.device) for _ in range(self.num_layers)]
|
||||
|
||||
Reference in new issue
Block a user