Files
vllm-omni/vllm_omni/diffusion/models/helios/cross_attn_cache.py
T
2026-09-30 16:44:51 +08:00

101 lines
3.9 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Weakref-guarded LRU cache for cross-attention key/value reuse.
``HeliosTransformer3DModel`` projects the text-encoder output into
cross-attention K/V once per request and reuses the projection across the
~50 denoise steps of that request. The cache is keyed by a storage
fingerprint of the source tensor (``data_ptr`` / shape / stride / dtype /
device / ``_version``). That fingerprint is **not** unique across requests:
once the source tensor is freed, PyTorch's caching allocator may hand its
address to an unrelated tensor of the same shape, producing a false cache
hit and cross-prompt contamination (request B silently reuses request A's
projected K/V).
``SourceTensorLRUCache`` stores a :mod:`weakref` to the source tensor next
to the cached value and verifies the source is still alive on every lookup.
An address reused by a *different* tensor still matches the fingerprint,
but the original source's weakref is dead by then, so the lookup becomes a
miss instead of a silent wrong hit. Within a single request the source
tensor is held alive across all denoise steps, so legitimate intra-request
reuse still hits.
"""
from __future__ import annotations
import weakref
from collections import OrderedDict
import torch
class SourceTensorLRUCache:
"""Bounded LRU cache keyed by a source-tensor fingerprint + liveness.
The key is derived from the source tensor's storage fingerprint
(``data_ptr`` / shape / stride / dtype / device / ``_version``); a
weakref to the source is stored alongside the value and checked on
lookup so that an allocator address reused after the source is freed
never yields a false hit. Stale entries (dead weakref) are evicted
lazily on lookup and proactively on insert.
"""
def __init__(self, max_size: int = 2) -> None:
self._cache: OrderedDict[tuple, tuple[weakref.ReferenceType, object]] = OrderedDict()
self._max_size = max_size
@staticmethod
def _key(tensor: torch.Tensor) -> tuple:
try:
version = tensor._version
except RuntimeError:
version = None
return (
tensor.data_ptr(),
tuple(tensor.shape),
tuple(tensor.stride()),
tensor.dtype,
tensor.device.type,
tensor.device.index,
version,
)
def get(self, source: torch.Tensor):
"""Return the cached value for ``source`` or ``None`` on miss.
A miss is also returned when the source tensor that originally
populated a matching entry has been freed (its weakref is dead),
even if the fingerprint now matches a new tensor reusing the
address; the stale entry is evicted in that case.
"""
key = self._key(source)
entry = self._cache.get(key)
if entry is None:
return None
src_ref, value = entry
if src_ref() is None:
# Source freed: the address may have been reused by an unrelated
# tensor, so the fingerprint match is a coincidence -> miss.
self._cache.pop(key, None)
return None
self._cache.move_to_end(key)
return value
def put(self, source: torch.Tensor, value) -> None:
key = self._key(source)
# Proactively drop entries whose source has been freed so cached
# values do not linger until the LRU bound evicts them.
if self._cache:
for stale in [k for k, v in self._cache.items() if v[0]() is None]:
del self._cache[stale]
self._cache[key] = (weakref.ref(source), value)
self._cache.move_to_end(key)
while len(self._cache) > self._max_size:
self._cache.popitem(last=False)
def clear(self) -> None:
self._cache.clear()
def __len__(self) -> int:
return len(self._cache)