[Model] Fix KV cache layout and optimize Dots3 NOTE Omni encoders (#53460)
Signed-off-by: KurodaKanbei <mistergalahad@gmail.com>
范裕达 committed
Aug 24, 2026 at 01:02 UTC
185cada36bb25aa55f762d004d54c5ca1e3fc753
5 files changed
+211
-90
vllm/models/dots3_note/nvidia/attention.py
+8
@@ -37,6 +37,7 @@ from vllm.v1.attention.backends.mla.triton_mla import (
37
TritonMLAImpl,
38
TritonMLAMetadataBuilder,
39
)
40
+from vllm.v1.kv_cache_interface import KVCacheLayout
41
from vllm.v1.worker.workspace import (
42
current_workspace_manager,
43
is_workspace_manager_initialized,
@@ -677,6 +678,13 @@ class Dots3NoteTritonMLAImpl(TritonMLAImpl):
678
class Dots3NotePaddedSparseBackend(FlashAttnMLASparseBackend):
679
"""NOTE DSA backend for cache rows padded to the SWA latent width."""
680
681
+ @classmethod
682
+ def supported_kv_cache_layouts(cls) -> tuple[KVCacheLayout, ...]:
683
+ # NOTE packs the smaller DSA index pages beside the padded MLA/SWA pages.
684
+ # Keep the layer dimension inside the block dimension so mixed page sizes
685
+ # remain representable by the shared KV-cache allocation.
686
+ return (KVCacheLayout.BLHNC,)
687
+
688
@staticmethod
689
def get_name() -> str:
690
return "DOTS3_NOTE_PADDED_MLA_SPARSE"
vllm/models/dots3_note/nvidia/audio.py
+96
-14
@@ -21,6 +21,7 @@ DEFAULT_MERGE_FACTOR = 1
21
N_SAMPLES = DEFAULT_CHUNK_LENGTH_S * SAMPLE_RATE
22
STRIDE = HOP_LENGTH * DEFAULT_CONV_TEMPORAL_STRIDE * DEFAULT_MERGE_FACTOR
23
ENCODER_SEQ_LEN = N_SAMPLES // HOP_LENGTH // 2
24
+_AUDIO_FORWARD_MAX_SEGMENTS = 16
25
26
27
class Dots3NoteAudioConfig:
@@ -126,6 +127,17 @@ def log_mel_spectrogram(audio, n_mels=128):
127
return log_spec
128
129
130
+def batched_log_mel_spectrogram(audio, n_mels=128):
131
+ window = _hann_window(audio.device)
132
+ stft = torch.stft(audio, N_FFT, HOP_LENGTH, window=window, return_complex=True)
133
+ magnitudes = stft[..., :-1].abs() ** 2
134
+ filters = _mel_filters(audio.device, n_mels)
135
+ mel_spec = filters @ magnitudes
136
+ log_spec = torch.clamp(mel_spec, min=1e-10).log10()
137
+ log_spec = torch.maximum(log_spec, log_spec.amax(dim=(-2, -1), keepdim=True) - 8.0)
138
+ return (log_spec + 4.0) / 4.0
139
+
140
+
141
def compute_audio_token_length(
142
num_samples,
143
*,
@@ -181,14 +193,34 @@ class DotsEncoderWithMask(nn.Module):
193
input_seq_lens: torch.Tensor,
194
audio_sample_lens: list[int],
195
) -> torch.Tensor:
184
- """Run the eager speech encoder without server-side slicing/batching."""
196
mel_features = mel_features.to(dtype=torch.bfloat16, device=self.device)
186
- return self.speech_encoder(
187
- mel_features,
188
- return_dict=True,
189
- input_seq_lens=input_seq_lens,
190
- audio_sample_lens=audio_sample_lens,
191
- ).last_hidden_state
197
+ if mel_features.shape[0] <= _AUDIO_FORWARD_MAX_SEGMENTS:
198
+ return self.speech_encoder(
199
+ mel_features,
200
+ return_dict=True,
201
+ input_seq_lens=input_seq_lens,
202
+ audio_sample_lens=audio_sample_lens,
203
+ ).last_hidden_state
204
+
205
+ chunks = []
206
+ for start in range(0, mel_features.shape[0], _AUDIO_FORWARD_MAX_SEGMENTS):
207
+ end = start + _AUDIO_FORWARD_MAX_SEGMENTS
208
+ chunks.append(
209
+ self.speech_encoder(
210
+ mel_features[start:end],
211
+ return_dict=True,
212
+ input_seq_lens=input_seq_lens[start:end],
213
+ audio_sample_lens=audio_sample_lens[start:end],
214
+ ).last_hidden_state
215
+ )
216
+ max_seq_len = max(chunk.shape[1] for chunk in chunks)
217
+ chunks = [
218
+ F.pad(chunk, (0, 0, 0, max_seq_len - chunk.shape[1]))
219
+ if chunk.shape[1] < max_seq_len
220
+ else chunk
221
+ for chunk in chunks
222
+ ]
223
+ return torch.cat(chunks, dim=0)
224
225
def encode_waveform(self, audio_waveform: torch.Tensor) -> torch.Tensor:
226
segments = []
@@ -233,6 +265,53 @@ class DotsEncoderWithMask(nn.Module):
265
)
266
return torch.cat(chunk_embeddings, dim=0).unsqueeze(0)
267
268
+ def encode_waveforms(self, audio_waveforms) -> list[torch.Tensor]:
269
+ stride = HOP_LENGTH * self.conv_temporal_stride * self.merge_factor
270
+ segment_token_lengths = []
271
+ segment_sample_lengths = []
272
+ audio_segment_counts = []
273
+ padded_segments = []
274
+
275
+ for audio_waveform in audio_waveforms:
276
+ segment_count = 0
277
+ time_step = 0
278
+ while time_step * SAMPLE_RATE < audio_waveform.shape[0]:
279
+ segment = audio_waveform[
280
+ time_step * SAMPLE_RATE : (time_step + self.chunk_seconds)
281
+ * SAMPLE_RATE
282
+ ]
283
+ segment_length = segment.shape[0]
284
+ padded_segments.append(
285
+ pad_or_trim(segment.flatten(), length=self.chunk_samples)
286
+ )
287
+ segment_token_lengths.append((segment_length - 1) // stride + 1)
288
+ segment_sample_lengths.append(segment_length)
289
+ segment_count += 1
290
+ time_step += self.chunk_seconds
291
+ audio_segment_counts.append(segment_count)
292
+
293
+ mel_features = batched_log_mel_spectrogram(torch.stack(padded_segments))
294
+ assert mel_features.shape[-1] == self.chunk_mel_frames
295
+ input_seq_lens = (
296
+ torch.tensor(segment_token_lengths, dtype=torch.long) * self.merge_factor
297
+ )
298
+ audio_embedding = self._forward_speech_encoder(
299
+ mel_features, input_seq_lens, segment_sample_lengths
300
+ )
301
+
302
+ embeddings = []
303
+ segment_idx = 0
304
+ for segment_count in audio_segment_counts:
305
+ chunks = []
306
+ for _ in range(segment_count):
307
+ token_length = segment_token_lengths[segment_idx]
308
+ chunks.append(
309
+ audio_embedding[segment_idx, : token_length * self.merge_factor, :]
310
+ )
311
+ segment_idx += 1
312
+ embeddings.append(torch.cat(chunks).unsqueeze(0))
313
+ return embeddings
314
+
315
316
class AudioAdapter(nn.Module):
317
def __init__(self, in_dim, out_dim):
@@ -294,12 +373,15 @@ class Dots3NoteAudioModel(nn.Module):
373
audio_start += length
374
return waveforms
375
376
+ def _forward_batched(self, waveforms):
377
+ raw_embeddings = self.dots_encoder.encode_waveforms(waveforms)
378
+ merged = [
379
+ self._merge_embeddings(embedding).squeeze(0) for embedding in raw_embeddings
380
+ ]
381
+ token_lengths = [embedding.shape[0] for embedding in merged]
382
+ packed = self.audio_adapter(torch.cat(merged))
383
+ return packed, token_lengths
384
+
385
def forward(self, audio_inputs, lengths):
386
waveforms = self._split_waveforms(audio_inputs, lengths)
299
- all_embeddings = []
300
- token_lengths = []
301
- for waveform in waveforms:
302
- embedding = self._encode_single_audio(waveform)
303
- all_embeddings.append(embedding)
304
- token_lengths.append(embedding.shape[0])
305
- return torch.cat(all_embeddings, dim=0), token_lengths
387
+ return self._forward_batched(waveforms)
vllm/models/dots3_note/nvidia/multimodal.py
+22
-2
@@ -3,6 +3,7 @@
3
"""vLLM composition layer for Dots3Note image and audio encoders."""
4
5
from collections.abc import Iterable
6
+from contextlib import contextmanager
7
8
import torch
9
from torch import nn
@@ -41,6 +42,21 @@ from .model import Dots3NoteLanguageModelForCausalLM
42
from .vision import DotsMoEVitConfig, DotsMoEVitModel
43
44
45
+@contextmanager
46
+def _skip_linear_init():
47
+ """Skip initialization for VE Linear weights loaded immediately afterward."""
48
+ original_reset_parameters = nn.Linear.reset_parameters
49
+
50
+ def _noop_reset_parameters(_linear: nn.Linear) -> None:
51
+ return None
52
+
53
+ nn.Linear.reset_parameters = _noop_reset_parameters
54
+ try:
55
+ yield
56
+ finally:
57
+ nn.Linear.reset_parameters = original_reset_parameters
58
+
59
+
60
@MULTIMODAL_REGISTRY.register_processor(
61
Dots3NoteMultiModalProcessor,
62
info=Dots3NoteProcessingInfo,
@@ -113,7 +129,10 @@ class Dots3NoteForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
129
self.visual: DotsMoEVitModel | None
130
if image_enabled:
131
assert vision_config_dict is not None
116
- self.visual = DotsMoEVitModel(DotsMoEVitConfig(**vision_config_dict))
132
+ with _skip_linear_init():
133
+ self.visual = DotsMoEVitModel(
134
+ DotsMoEVitConfig(**vision_config_dict)
135
+ )
136
else:
137
self.visual = None
138
self.audio_tower: Dots3NoteAudioModel | None
@@ -158,9 +177,10 @@ class Dots3NoteForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
177
) -> tuple[torch.Tensor, ...]:
178
if self.visual is None:
179
return ()
161
- image_embeds = self.visual(pixel_values, image_grid_thw)
180
merge_size = self.visual.spatial_merge_size
181
sizes = (image_grid_thw.prod(-1) // merge_size**2).tolist()
182
+ meta = self.visual.prepare_meta(image_grid_thw)
183
+ image_embeds = self.visual(pixel_values, meta)
184
return image_embeds.split(sizes)
185
186
def _process_audio_input(
vllm/models/dots3_note/nvidia/vision.py
+56
-62
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
# ruff: noqa: E501
4
import math
5
+from dataclasses import dataclass
6
from typing import Any
7
8
import torch
@@ -15,10 +16,11 @@ from vllm.third_party.deep_gemm import per_block_cast_to_fp8
16
17
from .vision_attention import (
18
VisionRotaryEmbedding,
19
+ VisionRotaryPositionEmbedding,
20
apply_vision_attention_residual,
21
attn_uses_seqlens,
22
build_vision_attention,
21
- prepare_seqlens_for_attention,
23
+ prepare_rotary_pos_emb_vision,
24
resolve_attn_implementation,
25
)
26
from .vision_moe import note_vision_fused_moe_fp8
@@ -502,6 +504,15 @@ _ADAPTER_CLASSES: dict[str, type[nn.Module]] = {
504
}
505
506
507
+@dataclass(frozen=True)
508
+class VitForwardMeta:
509
+ cu_seqlens: torch.Tensor
510
+ max_seqlen: int
511
+ rotary_pos_emb: VisionRotaryPositionEmbedding
512
+ grid_thw_cpu: torch.Tensor
513
+ seqlens: list[int] | None = None
514
+
515
+
516
# ---- Full Model ----
517
518
@@ -562,7 +573,7 @@ class DotsMoEVitModel(PreTrainedModel):
573
block.norm_1 = torch.compile(block.norm_1, **compile_kwargs)
574
block.norm_2 = torch.compile(block.norm_2, **compile_kwargs)
575
565
- def get_pos_ids_by_grid(self, grid_thw):
576
+ def get_pos_ids_by_grid(self, grid_thw_cpu: torch.Tensor):
577
# Mirrors ``cybertron`` ``AIMv2NativeModel.rot_pos_emb``: when ``pre_pixel_shuffle``
578
# is set, RoPE positions follow the qwen ``merge_size`` grouped layout (default 2x2);
579
# otherwise positions are flat row-major regardless of ``spatial_merge_size``.
@@ -573,10 +584,8 @@ class DotsMoEVitModel(PreTrainedModel):
584
else:
585
rope_merge_size = 1
586
pos_ids = []
576
- for t, h, w in grid_thw:
577
- hpos_ids = (
578
- torch.arange(h, device=grid_thw.device).unsqueeze(1).expand(-1, w)
579
- )
587
+ for t, h, w in grid_thw_cpu:
588
+ hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
589
hpos_ids = hpos_ids.reshape(
590
h // rope_merge_size,
591
rope_merge_size,
@@ -585,9 +594,7 @@ class DotsMoEVitModel(PreTrainedModel):
594
)
595
hpos_ids = hpos_ids.permute(0, 2, 1, 3).flatten()
596
588
- wpos_ids = (
589
- torch.arange(w, device=grid_thw.device).unsqueeze(0).expand(h, -1)
590
- )
597
+ wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
598
wpos_ids = wpos_ids.reshape(
599
h // rope_merge_size,
600
rope_merge_size,
@@ -598,80 +605,67 @@ class DotsMoEVitModel(PreTrainedModel):
605
pos_ids.append(torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
606
return pos_ids
607
601
- def rot_pos_emb(self, grid_thw):
602
- pos_ids = self.get_pos_ids_by_grid(grid_thw)
603
- pos_ids = torch.cat(pos_ids, dim=0)
604
- max_grid_size = grid_thw[:, 1:].max()
608
+ def rot_pos_emb(self, grid_thw_cpu: torch.Tensor):
609
+ pos_ids = torch.cat(self.get_pos_ids_by_grid(grid_thw_cpu), dim=0)
610
+ pos_ids = pos_ids.to(self.device, non_blocking=True)
611
+ max_grid_size = int(grid_thw_cpu[:, 1:].max())
612
rotary_pos_emb_full = self.rotary_pos_emb(max_grid_size)
606
- rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
607
- return rotary_pos_emb
608
-
609
- def _build_cu_seqlens_from_grid(self, grid_thw: torch.Tensor):
610
- cu_seqlens = torch.repeat_interleave(
611
- grid_thw[:, 1] * grid_thw[:, 2], grid_thw[:, 0]
612
- ).cumsum(
613
- dim=0,
614
- dtype=grid_thw.dtype if torch.jit.is_tracing() else torch.int32,
613
+ return rotary_pos_emb_full[pos_ids].flatten(1)
614
+
615
+ def prepare_meta(self, grid_thw_cpu: torch.Tensor) -> VitForwardMeta:
616
+ if grid_thw_cpu.device.type != "cpu":
617
+ raise ValueError("NOTE vision grid_thw must remain on CPU")
618
+
619
+ seq_per_frame = grid_thw_cpu[:, 1] * grid_thw_cpu[:, 2]
620
+ frame_lengths = torch.repeat_interleave(seq_per_frame, grid_thw_cpu[:, 0])
621
+ cu_seqlens_cpu = F.pad(
622
+ frame_lengths.cumsum(dim=0, dtype=torch.int32), (1, 0), value=0
623
)
616
- cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0)
617
- # Same as (cu_seqlens[1:] - cu_seqlens[:-1]).max(); computed here to avoid D2H inside attention.
618
- max_seqlen = int((grid_thw[:, 1] * grid_thw[:, 2]).max().item())
619
- return cu_seqlens, max_seqlen
620
-
621
- def _build_single_temporal_cu_seqlens_from_grid(self, grid_thw: torch.Tensor):
622
- seq_lens = grid_thw[:, 1] * grid_thw[:, 2]
623
- cu_seqlens = torch.empty(
624
- (grid_thw.shape[0] + 1,), device=grid_thw.device, dtype=torch.int32
624
+ resolved_attn = resolve_attn_implementation(
625
+ self.config.attn_implementation, eager_fallback="eager_v2"
626
+ )
627
+ return VitForwardMeta(
628
+ cu_seqlens=cu_seqlens_cpu.to(self.device, non_blocking=True),
629
+ max_seqlen=int(seq_per_frame.max()),
630
+ rotary_pos_emb=prepare_rotary_pos_emb_vision(
631
+ self.rot_pos_emb(grid_thw_cpu)
632
+ ),
633
+ grid_thw_cpu=grid_thw_cpu,
634
+ seqlens=(
635
+ frame_lengths.tolist() if attn_uses_seqlens(resolved_attn) else None
636
+ ),
637
)
626
- cu_seqlens[0] = 0
627
- torch.cumsum(seq_lens, dim=0, dtype=torch.int32, out=cu_seqlens[1:])
628
- max_seqlen = int(seq_lens.max().item())
629
- return cu_seqlens, max_seqlen
638
639
def forward(
632
- self, hidden_states: torch.Tensor, grid_thw: torch.Tensor, bf16=True
640
+ self, hidden_states: torch.Tensor, meta: VitForwardMeta, bf16=True
641
) -> torch.Tensor:
634
- grid_thw = grid_thw.to(device=self.device)
642
if bf16:
636
- hidden_states = hidden_states.to(device=self.device, dtype=self.dtype)
637
- hidden_states = self.patch_embed(hidden_states)
638
-
639
- rotary_pos_emb = self.rot_pos_emb(grid_thw)
640
-
641
- if grid_thw[:, 0].sum().item() == grid_thw.shape[0]:
642
- cu_seqlens, max_seqlen = self._build_single_temporal_cu_seqlens_from_grid(
643
- grid_thw
643
+ hidden_states = hidden_states.to(
644
+ device=self.device, dtype=self.dtype, non_blocking=True
645
)
645
- else:
646
- cu_seqlens, max_seqlen = self._build_cu_seqlens_from_grid(grid_thw)
647
-
648
- seqlens = prepare_seqlens_for_attention(
649
- self.config.attn_implementation,
650
- cu_seqlens,
651
- eager_fallback="eager_v2",
652
- )
646
+ hidden_states = self.patch_embed(hidden_states)
647
648
for blk in self.blocks:
649
if self.gradient_checkpointing and self.training:
650
hidden_states = self._gradient_checkpointing_func(
651
blk.__call__,
652
hidden_states,
659
- cu_seqlens,
660
- rotary_pos_emb,
661
- max_seqlen,
662
- seqlens,
653
+ meta.cu_seqlens,
654
+ meta.rotary_pos_emb,
655
+ meta.max_seqlen,
656
+ meta.seqlens,
657
)
658
else:
659
hidden_states = blk(
660
hidden_states,
667
- cu_seqlens,
668
- rotary_pos_emb,
669
- max_seqlen,
670
- seqlens=seqlens,
661
+ meta.cu_seqlens,
662
+ meta.rotary_pos_emb,
663
+ meta.max_seqlen,
664
+ seqlens=meta.seqlens,
665
)
666
667
if self.config.post_norm:
668
hidden_states = self.post_trunk_norm(hidden_states)
669
676
- hidden_states = self.adapter(hidden_states, grid_thw)
670
+ hidden_states = self.adapter(hidden_states, meta.grid_thw_cpu)
671
return hidden_states
vllm/models/dots3_note/nvidia/vision_attention.py
+29
-12
@@ -36,15 +36,28 @@ def rotate_half(x: torch.Tensor) -> torch.Tensor:
36
return torch.cat((-x2, x1), dim=-1)
37
38
39
+VisionRotaryPositionEmbedding = tuple[torch.Tensor, torch.Tensor]
40
+
41
+
42
+def prepare_rotary_pos_emb_vision(
43
+ freqs: torch.Tensor,
44
+) -> VisionRotaryPositionEmbedding:
45
+ """Materialize vision RoPE cos/sin once for every encoder layer."""
46
+ cos = freqs.cos().unsqueeze(1).repeat(1, 1, 2).unsqueeze(0).float()
47
+ sin = freqs.sin().unsqueeze(1).repeat(1, 1, 2).unsqueeze(0).float()
48
+ return cos, sin
49
+
50
+
51
def apply_rotary_pos_emb_vision(
40
- tensor: torch.Tensor, freqs: torch.Tensor
52
+ tensor: torch.Tensor,
53
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding,
54
) -> torch.Tensor:
55
orig_dtype = tensor.dtype
56
tensor = tensor.float()
44
- cos = freqs.cos()
45
- sin = freqs.sin()
46
- cos = cos.unsqueeze(1).repeat(1, 1, 2).unsqueeze(0).float()
47
- sin = sin.unsqueeze(1).repeat(1, 1, 2).unsqueeze(0).float()
57
+ if isinstance(rotary_pos_emb, torch.Tensor):
58
+ cos, sin = prepare_rotary_pos_emb_vision(rotary_pos_emb)
59
+ else:
60
+ cos, sin = rotary_pos_emb
61
output = (tensor * cos) + (rotate_half(tensor) * sin)
62
return output.to(orig_dtype)
63
@@ -149,7 +162,7 @@ class _VisionAttentionBase(nn.Module):
162
def _qkv_with_rope(
163
self,
164
hidden_states: torch.Tensor,
152
- rotary_pos_emb: torch.Tensor | None,
165
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding | None,
166
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
167
seq_length = hidden_states.shape[0]
168
q, k, v = (
@@ -161,6 +174,8 @@ class _VisionAttentionBase(nn.Module):
174
if self.use_qk_norm:
175
q = self.q_norm(q)
176
k = self.k_norm(k)
177
+ if rotary_pos_emb is None:
178
+ raise ValueError("Vision rotary position embedding is required")
179
q = apply_rotary_pos_emb_vision(q.unsqueeze(0), rotary_pos_emb).squeeze(0)
180
k = apply_rotary_pos_emb_vision(k.unsqueeze(0), rotary_pos_emb).squeeze(0)
181
return q, k, v
@@ -174,7 +189,7 @@ class VisionAttention(_VisionAttentionBase):
189
hidden_states: torch.Tensor,
190
cu_seqlens: torch.Tensor,
191
max_seqlen: int,
177
- rotary_pos_emb: torch.Tensor | None = None,
192
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding | None = None,
193
seqlens: list[int] | None = None,
194
) -> torch.Tensor:
195
seq_length = hidden_states.shape[0]
@@ -212,7 +227,7 @@ class VisionAttentionV2(_VisionAttentionBase):
227
hidden_states: torch.Tensor,
228
cu_seqlens: torch.Tensor,
229
max_seqlen: int,
215
- rotary_pos_emb: torch.Tensor | None = None,
230
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding | None = None,
231
seqlens: list[int] | None = None,
232
) -> torch.Tensor:
233
seq_length = hidden_states.shape[0]
@@ -249,7 +264,7 @@ class VisionFlashAttention2(_VisionAttentionBase):
264
hidden_states: torch.Tensor,
265
cu_seqlens: torch.Tensor,
266
max_seqlen: int,
252
- rotary_pos_emb: torch.Tensor | None = None,
267
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding | None = None,
268
seqlens: list[int] | None = None,
269
) -> torch.Tensor:
270
seq_length = hidden_states.shape[0]
@@ -274,7 +289,7 @@ class VisionFlashAttention3(_VisionAttentionBase):
289
hidden_states: torch.Tensor,
290
cu_seqlens: torch.Tensor,
291
max_seqlen: int,
277
- rotary_pos_emb: torch.Tensor | None = None,
292
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding | None = None,
293
seqlens: list[int] | None = None,
294
) -> torch.Tensor:
295
seq_length = hidden_states.shape[0]
@@ -313,7 +328,7 @@ class VisionSdpaAttention(_VisionAttentionBase):
328
hidden_states: torch.Tensor,
329
cu_seqlens: torch.Tensor,
330
max_seqlen: int,
316
- rotary_pos_emb: torch.Tensor | None = None,
331
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding | None = None,
332
seqlens: list[int] | None = None,
333
) -> torch.Tensor:
334
seq_length = hidden_states.shape[0]
@@ -439,7 +454,7 @@ def apply_vision_attention_residual(
454
hidden_states: torch.Tensor,
455
cu_seqlens: torch.Tensor,
456
max_seqlen: int,
442
- rotary_pos_emb: torch.Tensor,
457
+ rotary_pos_emb: torch.Tensor | VisionRotaryPositionEmbedding,
458
*,
459
seqlens: list[int] | None = None,
460
uses_seqlens: bool = False,
@@ -465,11 +480,13 @@ __all__ = [
480
"VisionFlashAttention2",
481
"VisionFlashAttention3",
482
"VisionRotaryEmbedding",
483
+ "VisionRotaryPositionEmbedding",
484
"VisionSdpaAttention",
485
"apply_rotary_pos_emb_vision",
486
"apply_vision_attention_residual",
487
"attn_uses_seqlens",
488
"build_vision_attention",
489
+ "prepare_rotary_pos_emb_vision",
490
"prepare_seqlens_for_attention",
491
"resolve_attn_implementation",
492
"rotate_half",