fix(fa3): no NaN embeddings with fa_skip_kv_cache under piecewise CUDA graph (#27343)
This commit is contained in:
@@ -152,6 +152,10 @@ class AlwaysHitShapeEnv:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.guards: list[Any] = []
|
||||
# Newer torch Inductor reads ``shape_env.var_to_hint_override`` during
|
||||
# compilation; provide an empty mapping so this dummy shape env stays
|
||||
# compatible across torch versions (older torch never accesses it).
|
||||
self.var_to_hint_override: dict[Any, Any] = {}
|
||||
|
||||
def evaluate_guards_expression(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
@@ -56,6 +56,10 @@ class FlashAttentionMetadata:
|
||||
cu_seqlens_q: torch.Tensor = None
|
||||
# Cumulative sequence lengths for key
|
||||
cu_seqlens_k: torch.Tensor = None
|
||||
# Dummy-tail varlen metadata for the fa_skip_kv_cache path under a piecewise
|
||||
# CUDA graph (built once per forward, reused across layers). See forward_extend.
|
||||
fa_skip_cu_seqlens_q: torch.Tensor = None
|
||||
fa_skip_max_seqlen_q: int = None
|
||||
# Window size (typically used by Gemma)
|
||||
window_size: tuple = (-1, -1)
|
||||
# Page table, the index of KV Cache Tables/Blocks
|
||||
@@ -979,14 +983,43 @@ class FlashAttentionBackend(AttentionBackend):
|
||||
"fa_skip_kv_cache uses raw K/V tensors, "
|
||||
"FP8 KV cache descaling is not supported in this mode"
|
||||
)
|
||||
# Piecewise CUDA graph pads the token dimension up to a captured
|
||||
# bucket size, so ``q`` has more rows than ``cu_seqlens_q`` covers.
|
||||
# ``flash_attn_varlen_func`` requires ``q.shape[0] == cu_seqlens_q[-1]``;
|
||||
# otherwise the boundary query block corrupts the last real token's
|
||||
# output, producing a NaN embedding (LAST pooling reads that token).
|
||||
# Append the padded tail as a dummy, self-attending segment so every
|
||||
# row is a valid sequence. Real tokens stay in their own segment and
|
||||
# are unaffected. Built once per forward and cached on the metadata
|
||||
# (reused across layers). ``extend_num_tokens`` is a python int equal
|
||||
# to ``cu_seqlens_q[-1]`` for extend/prefill forwards, so it needs no
|
||||
# device sync. With no padding -- or if the count is unavailable (e.g.
|
||||
# any non-piecewise path) -- cu_seqlens_q already covers q, so fall
|
||||
# through to using it as-is (no dummy segment).
|
||||
if metadata.fa_skip_cu_seqlens_q is None:
|
||||
num_real_tokens = forward_batch.extend_num_tokens
|
||||
num_padded_tokens = q.shape[0]
|
||||
if (
|
||||
num_real_tokens is not None
|
||||
and num_real_tokens < num_padded_tokens
|
||||
):
|
||||
metadata.fa_skip_cu_seqlens_q = torch.cat(
|
||||
[cu_seqlens_q, cu_seqlens_q.new_tensor([num_padded_tokens])]
|
||||
)
|
||||
metadata.fa_skip_max_seqlen_q = max(
|
||||
int(max_seqlen_q), num_padded_tokens - num_real_tokens
|
||||
)
|
||||
else:
|
||||
metadata.fa_skip_cu_seqlens_q = cu_seqlens_q
|
||||
metadata.fa_skip_max_seqlen_q = max_seqlen_q
|
||||
result = flash_attn_varlen_func(
|
||||
q=q.contiguous().view(-1, layer.tp_q_head_num, layer.head_dim),
|
||||
k=k.view(-1, layer.tp_k_head_num, layer.head_dim),
|
||||
v=v.view(-1, layer.tp_v_head_num, layer.v_head_dim),
|
||||
cu_seqlens_q=cu_seqlens_q,
|
||||
cu_seqlens_k=cu_seqlens_q,
|
||||
max_seqlen_q=max_seqlen_q,
|
||||
max_seqlen_k=max_seqlen_q,
|
||||
cu_seqlens_q=metadata.fa_skip_cu_seqlens_q,
|
||||
cu_seqlens_k=metadata.fa_skip_cu_seqlens_q,
|
||||
max_seqlen_q=metadata.fa_skip_max_seqlen_q,
|
||||
max_seqlen_k=metadata.fa_skip_max_seqlen_q,
|
||||
softmax_scale=layer.scaling,
|
||||
causal=causal,
|
||||
window_size=window_size,
|
||||
|
||||
Reference in New Issue
Block a user