Skip to content

Commit bd6db6c

Browse files
authored
[None][fix] LTX-2: re-enable Ulysses for v2a cross-attention (#15303)
Signed-off-by: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com>
1 parent 660aede commit bd6db6c

3 files changed

Lines changed: 31 additions & 7 deletions

File tree

tensorrt_llm/_torch/visual_gen/attention_backend/parallel.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,7 @@ def wrap_parallel_attention(
812812
*,
813813
visual_gen_mapping: Optional["VisualGenMapping"] = None,
814814
enable_sequence_parallel: bool = True,
815+
use_ulysses: bool = True,
815816
async_ulysses: bool = False,
816817
) -> AttentionBackend:
817818
"""Wrap a compute backend with the configured parallelism strategy.
@@ -822,6 +823,12 @@ def wrap_parallel_attention(
822823
823824
When ``enable_sequence_parallel`` is False, no wrappers are applied (callers
824825
use this for cross-attention paths that cannot use Ulysses/Ring/Attention2D).
826+
827+
``use_ulysses`` gates the Ulysses head-sharding wrap independently of
828+
``ulysses_size``: pass False to skip it even when ``ulysses_size > 1`` (e.g. a
829+
SEPARATE_QKV cross-attn that falls back to all-gather and built its inner
830+
backend with the full, un-sharded head count). This keeps the wrap consistent
831+
with the caller's inner head-count decision.
825832
"""
826833
if not enable_sequence_parallel or visual_gen_mapping is None:
827834
return attn
@@ -840,7 +847,7 @@ def wrap_parallel_attention(
840847
elif ring_size > 1:
841848
attn = RingAttention(attn, process_group=vgm.ring_group)
842849

843-
if ulysses_size > 1:
850+
if ulysses_size > 1 and use_ulysses:
844851
attn = UlyssesAttention(
845852
attn,
846853
process_group=vgm.ulysses_group,

tensorrt_llm/_torch/visual_gen/models/ltx2/transformer_ltx2.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,14 @@ def __init__(
170170
# (sharded inner backend + UlyssesAttention) for both self-attn and
171171
# cross-attn paths.
172172

173-
# For audio self-attention that may need a runtime Ulysses toggle
174-
# (sequence length not always divisible by ulysses_size), create a
175-
# plain backend as fallback. The base class already set self.attn
176-
# to UlyssesAttention(inner_backend=sharded_backend).
177-
if use_ulysses and not self._is_cross_attn and ulysses_size > 1:
173+
# Build a Ulysses/plain dual-attn pair so set_ulysses_active() can toggle
174+
# at runtime. Needed for self-attn (audio seq not always divisible by
175+
# ulysses_size) and for v2a cross-attn under pure Ulysses (cp_size == 1),
176+
# where it lets the block forward use Ulysses a2a instead of all-gathering
177+
# the full video K/V. Combined ring/attn2d + Ulysses (cp_size > 1) keeps
178+
# cross-attn on the all-gather fallback. The base class already set
179+
# self.attn to UlyssesAttention(inner_backend=sharded_backend).
180+
if use_ulysses and (cp_size == 1 or not self._is_cross_attn) and ulysses_size > 1:
178181
self._ulysses_attn = self.attn
179182
self._plain_attn = create_attention(
180183
backend=self.attn_backend,
@@ -684,6 +687,7 @@ def _init_av_cross_modules(self, v_cfg, a_cfg, rope_type, eps, model_config, idx
684687
layer_idx=idx,
685688
module_name=f"transformer_blocks.{idx}.video_to_audio_attn",
686689
enable_sequence_parallel=True,
690+
use_ulysses=True,
687691
)
688692
self.scale_shift_table_a2v_ca_audio = nn.Parameter(torch.empty(5, a_cfg.dim))
689693
self.scale_shift_table_a2v_ca_video = nn.Parameter(torch.empty(5, v_cfg.dim))

tensorrt_llm/_torch/visual_gen/modules/attention.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import torch
55
import torch.nn as nn
66

7+
from tensorrt_llm.logger import logger
78
from tensorrt_llm.visual_gen.sparse_attention import SkipSoftmaxAttentionConfig
89

910
from ...modules.linear import Linear, TensorParallelMode, WeightMode, WeightsLoadingConfig
@@ -182,11 +183,22 @@ def __init__(
182183
# The async-ulysses path uses SEPARATE_QKV for stream-pipelined
183184
# V/Q/K projections AND still needs the head-sharding wrap — opt in
184185
# via async_ulysses=True.
186+
cp_size = vgm.cp_size if vgm else 1
185187
use_ulysses = (
186188
ulysses_size > 1
187189
and enable_sequence_parallel
188-
and (self.qkv_mode != QKVMode.SEPARATE_QKV or async_ulysses)
190+
and (self.qkv_mode != QKVMode.SEPARATE_QKV or async_ulysses or cp_size == 1)
189191
)
192+
if ulysses_size > 1 and enable_sequence_parallel and not use_ulysses:
193+
# Ulysses was requested (ulysses_size > 1, SP on) but disabled: this is a
194+
# SEPARATE_QKV cross-attention that is neither async nor pure-Ulysses
195+
# (cp_size > 1), so it falls back to the all-gather K/V path.
196+
logger.debug(
197+
f"Attention(layer={layer_idx}): Ulysses disabled despite ulysses_size="
198+
f"{ulysses_size} — qkv_mode={self.qkv_mode.value}, "
199+
f"async_ulysses={async_ulysses}, cp_size={cp_size} "
200+
f"(SEPARATE_QKV cross-attn needs async_ulysses or cp_size==1)."
201+
)
190202

191203
# Compute head counts for the backend
192204
# Ulysses shards heads across workers; inner backend sees sharded count
@@ -234,6 +246,7 @@ def __init__(
234246
self.attn,
235247
visual_gen_mapping=vgm,
236248
enable_sequence_parallel=enable_sequence_parallel,
249+
use_ulysses=use_ulysses,
237250
async_ulysses=use_ulysses and async_ulysses,
238251
)
239252

0 commit comments

Comments
 (0)