59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
561 lines
19 KiB
Python
561 lines
19 KiB
Python
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
"""Cache-less multi-headed attention used by multimodal encoders.
|
|
|
|
Encode-only attention layer used by vision and audio towers, plus its backend
|
|
dispatch table. Backends are free functions (not
|
|
``AttentionBackend`` subclasses) because the vision encoder is single-shot
|
|
with no KV cache, no decode/extend split, and no graph capture protocol --
|
|
the ``AttentionBackend`` ABC's prefill/decode/extend lifecycle does not
|
|
apply. This file is therefore kept out of ``backends/`` (the home of
|
|
``AttentionBackend`` subclasses) and lives at the ``layers/attention/`` top
|
|
level alongside ``registry.py`` / ``utils.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import logging
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
from einops import rearrange
|
|
from tokenspeed_kernel.platform import current_platform
|
|
|
|
from tokenspeed.runtime.distributed import utils as dist_utils
|
|
from tokenspeed.runtime.distributed.mapping import Mapping
|
|
from tokenspeed.runtime.layers.linear import (
|
|
QKVParallelLinear,
|
|
RowParallelLinear,
|
|
)
|
|
from tokenspeed.runtime.layers.quantization import QuantizationConfig
|
|
from tokenspeed.runtime.layers.rotary_embedding import apply_rotary_pos_emb_native
|
|
from tokenspeed.runtime.utils import add_prefix, round_up
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_platform = current_platform()
|
|
_is_nvidia = _platform.is_nvidia
|
|
_is_amd = _platform.is_amd
|
|
|
|
if _is_nvidia:
|
|
from tokenspeed_kernel.ops.attention.flash_attn import flash_attn_varlen_func
|
|
from tokenspeed_kernel.ops.attention.flashinfer import (
|
|
cudnn_batch_prefill_with_kv_cache,
|
|
)
|
|
|
|
from tokenspeed_kernel.ops.attention.triton.context import context_attention_fwd
|
|
from tokenspeed_kernel.ops.attention.triton.qkv_rotary import (
|
|
packed_qkv_complex_rotary,
|
|
packed_qkv_neox_rotary,
|
|
)
|
|
|
|
# CUDA-graph bucketing for the cuDNN vision prefill backend: batch and max
|
|
# seqlen are quantized so a small set of captured graphs covers the request
|
|
# distribution. The consts are consumed by VLM tower models, not by
|
|
# ``MultimodalEncoderAttention`` itself.
|
|
VIT_CUDNN_WORKSPACE_BYTES = 128 * 1024 * 1024
|
|
VIT_CUDNN_BATCH_BUCKETS: tuple[int, ...] = (8, 16, 32, 64)
|
|
VIT_CUDNN_SEQLEN_BUCKETS: tuple[int, ...] = (4096, 8192, 16384, 32768, 65536, 131072)
|
|
|
|
|
|
def round_up_to_bucket(value: int, buckets: tuple[int, ...]) -> int:
|
|
"""Smallest bucket >= value; values past the last bucket round up to a
|
|
multiple of it. Used by vision tower code to pad batch size and max-seqlen
|
|
into a finite set of captured cuDNN graph shapes.
|
|
"""
|
|
if value <= 0:
|
|
return buckets[0]
|
|
for bucket in buckets:
|
|
if bucket >= value:
|
|
return bucket
|
|
return round_up(value, buckets[-1])
|
|
|
|
|
|
# === Backend dispatch ===
|
|
# The dispatcher always passes the full kwarg set (cu_seqlens / bsz / seq_len /
|
|
# softmax_scale / max_seqlen / sequence_lengths / workspace_buffer); each
|
|
# backend declares the subset it uses and absorbs the rest via ``**_``.
|
|
|
|
|
|
def _varlen_metadata(
|
|
cu_seqlens: torch.Tensor | None,
|
|
bsz: int,
|
|
seq_len: int,
|
|
*,
|
|
device: torch.device,
|
|
max_seqlen: int | None,
|
|
) -> tuple[torch.Tensor, torch.Tensor, int]:
|
|
"""Resolve cu_seqlens / seq_lens / max_seqlen shared by the varlen backends.
|
|
|
|
``max_seqlen`` is honored when the caller supplies it (the capture-safe
|
|
path); only the eager fallback derives it via ``.item()``, which forces a
|
|
GPU->CPU sync that is illegal inside a captured CUDA graph. Deriving it
|
|
once here keeps every varlen backend capture-safe instead of each kernel
|
|
wrapper re-deriving (and re-syncing) it.
|
|
"""
|
|
if cu_seqlens is None:
|
|
cu_seqlens = torch.arange(
|
|
0, (bsz + 1) * seq_len, step=seq_len, dtype=torch.int32, device=device
|
|
)
|
|
else:
|
|
cu_seqlens = cu_seqlens.to(dtype=torch.int32, device=device)
|
|
seq_lens = cu_seqlens[1:] - cu_seqlens[:-1]
|
|
if max_seqlen is None:
|
|
max_seqlen = int(seq_lens.max().item())
|
|
return cu_seqlens, seq_lens, int(max_seqlen)
|
|
|
|
|
|
def vision_attn_triton(
|
|
q: torch.Tensor,
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
*,
|
|
cu_seqlens: torch.Tensor | None,
|
|
bsz: int,
|
|
seq_len: int,
|
|
softmax_scale: float | None = None,
|
|
max_seqlen: int | None = None,
|
|
**_: Any,
|
|
) -> torch.Tensor:
|
|
"""Triton context attention without a causal mask."""
|
|
cu_seqlens, seq_lens, max_seqlen = _varlen_metadata(
|
|
cu_seqlens, bsz, seq_len, device=q.device, max_seqlen=max_seqlen
|
|
)
|
|
output = torch.empty_like(q)
|
|
context_attention_fwd(
|
|
q,
|
|
k,
|
|
v,
|
|
output,
|
|
cu_seqlens,
|
|
seq_lens,
|
|
max_seqlen,
|
|
is_causal=False,
|
|
sm_scale=softmax_scale,
|
|
)
|
|
return output
|
|
|
|
|
|
def vision_attn_fa3(
|
|
q: torch.Tensor,
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
*,
|
|
cu_seqlens: torch.Tensor | None,
|
|
bsz: int,
|
|
seq_len: int,
|
|
softmax_scale: float | None = None,
|
|
max_seqlen: int | None = None,
|
|
**_: Any,
|
|
) -> torch.Tensor:
|
|
cu_seqlens, _, max_seqlen = _varlen_metadata(
|
|
cu_seqlens, bsz, seq_len, device=q.device, max_seqlen=max_seqlen
|
|
)
|
|
return flash_attn_varlen_func(
|
|
q,
|
|
k,
|
|
v,
|
|
cu_seqlens_q=cu_seqlens,
|
|
cu_seqlens_k=cu_seqlens,
|
|
max_seqlen_q=max_seqlen,
|
|
max_seqlen_k=max_seqlen,
|
|
softmax_scale=softmax_scale,
|
|
)
|
|
|
|
|
|
def vision_attn_fa4(
|
|
q: torch.Tensor,
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
*,
|
|
cu_seqlens: torch.Tensor | None,
|
|
bsz: int,
|
|
seq_len: int,
|
|
softmax_scale: float | None = None,
|
|
max_seqlen: int | None = None,
|
|
**_: Any,
|
|
) -> torch.Tensor:
|
|
cu_seqlens, _, max_seqlen = _varlen_metadata(
|
|
cu_seqlens, bsz, seq_len, device=q.device, max_seqlen=max_seqlen
|
|
)
|
|
result = flash_attn_varlen_func(
|
|
q,
|
|
k,
|
|
v,
|
|
cu_seqlens_q=cu_seqlens,
|
|
cu_seqlens_k=cu_seqlens,
|
|
max_seqlen_q=max_seqlen,
|
|
max_seqlen_k=max_seqlen,
|
|
softmax_scale=softmax_scale,
|
|
)
|
|
# FA4 CUTE returns (output, lse) in newer builds and bare output in older
|
|
# ones; downstream callers only consume the tensor.
|
|
return result[0] if isinstance(result, tuple) else result
|
|
|
|
|
|
def vision_attn_flashinfer_cudnn(
|
|
q: torch.Tensor,
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
*,
|
|
cu_seqlens: torch.Tensor | None,
|
|
softmax_scale: float | None = None,
|
|
max_seqlen: Any = None,
|
|
sequence_lengths: torch.Tensor | None = None,
|
|
workspace_buffer: torch.Tensor | None = None,
|
|
**_: Any,
|
|
) -> torch.Tensor:
|
|
"""cuDNN prefill backend. The caller (vision tower with cuDNN graph capture)
|
|
prepares ``cu_seqlens`` as three concatenated element-offset indptrs
|
|
``[qk | v | o]`` of length ``batch+1`` each, plus ``sequence_lengths`` per
|
|
real (un-padded) sequence and ``max_seqlen`` as the bucketed budget.
|
|
"""
|
|
if (
|
|
sequence_lengths is None
|
|
or max_seqlen is None
|
|
or not isinstance(cu_seqlens, torch.Tensor)
|
|
):
|
|
raise ValueError(
|
|
"flashinfer_cudnn needs sequence_lengths, max_seqlen, and packed indptrs"
|
|
)
|
|
|
|
# cuDNN wants a python int for the seq budget.
|
|
max_seqlen = int(
|
|
max_seqlen.item() if isinstance(max_seqlen, torch.Tensor) else max_seqlen
|
|
)
|
|
|
|
# Flatten (b, s, h, d) -> (b*s, h, d) when the caller hands us 4-D.
|
|
in_4d = q.dim() == 4
|
|
if in_4d:
|
|
b4 = q.shape[0]
|
|
q, k, v = (rearrange(t, "b s ... -> (b s) ...") for t in (q, k, v))
|
|
|
|
seq_lens = sequence_lengths.view(-1).to(device=q.device, dtype=torch.int32)
|
|
batch = seq_lens.numel()
|
|
packed = cu_seqlens.view(-1).to(device=q.device, dtype=torch.int32)
|
|
expected_packed = 3 * (batch + 1)
|
|
if packed.numel() != expected_packed:
|
|
raise ValueError(
|
|
f"Expected packed indptr length {expected_packed}, got {packed.numel()}."
|
|
)
|
|
chunk = batch + 1
|
|
qk_off = packed[:chunk].view(chunk, 1, 1, 1)
|
|
v_off = packed[chunk : 2 * chunk].view(chunk, 1, 1, 1)
|
|
o_off = packed[2 * chunk :].view(chunk, 1, 1, 1)
|
|
seq_lens_4d = seq_lens.view(batch, 1, 1, 1)
|
|
|
|
head_size = q.shape[-1]
|
|
scale = softmax_scale if softmax_scale is not None else head_size**-0.5
|
|
|
|
output, _ = cudnn_batch_prefill_with_kv_cache(
|
|
q,
|
|
k,
|
|
v,
|
|
scale,
|
|
workspace_buffer,
|
|
max_token_per_sequence=max_seqlen,
|
|
max_sequence_kv=max_seqlen,
|
|
actual_seq_lens_q=seq_lens_4d,
|
|
actual_seq_lens_kv=seq_lens_4d,
|
|
causal=False,
|
|
return_lse=True,
|
|
batch_offsets_q=qk_off,
|
|
batch_offsets_k=qk_off,
|
|
batch_offsets_v=v_off,
|
|
batch_offsets_o=o_off,
|
|
is_cuda_graph_compatible=True,
|
|
)
|
|
if in_4d:
|
|
output = rearrange(output, "(b s) h d -> b s h d", b=b4)
|
|
return output
|
|
|
|
|
|
_BACKENDS: dict[str, Callable[..., torch.Tensor]] = {
|
|
"triton_attn": vision_attn_triton,
|
|
"fa3": vision_attn_fa3,
|
|
"fa4": vision_attn_fa4,
|
|
"flashinfer_cudnn": vision_attn_flashinfer_cudnn,
|
|
}
|
|
|
|
|
|
def _default_multimodal_encoder_attn_backend() -> str:
|
|
"""Platform default backend name."""
|
|
if _is_nvidia:
|
|
if _platform.arch_version.major == 9: # Hopper SM90
|
|
return "fa3"
|
|
if _platform.arch_version.major == 10: # Blackwell SM100
|
|
return "fa4"
|
|
return "triton_attn"
|
|
if _is_amd:
|
|
return "triton_attn"
|
|
raise RuntimeError(
|
|
f"No default multimodal encoder attention backend for platform {_platform}; "
|
|
"set --mm-attention-backend explicitly."
|
|
)
|
|
|
|
|
|
@functools.lru_cache(maxsize=None)
|
|
def _resolve_backend(name: str | None) -> Callable[..., torch.Tensor]:
|
|
"""Resolve a backend name to its dispatch function.
|
|
|
|
``None`` falls back to the platform default; an unknown or platform-
|
|
incompatible name raises ValueError listing the registered backends.
|
|
Cached so a process logs the chosen backend exactly once per name.
|
|
"""
|
|
explicit = name is not None
|
|
if name is None:
|
|
name = _default_multimodal_encoder_attn_backend()
|
|
fn = _BACKENDS.get(name)
|
|
if fn is None:
|
|
raise ValueError(
|
|
f"Unknown multimodal encoder attention backend {name!r} "
|
|
f"(check --mm-attention-backend); available: {sorted(_BACKENDS)}"
|
|
)
|
|
if name in ("fa3", "fa4", "flashinfer_cudnn") and not _is_nvidia:
|
|
raise ValueError(
|
|
f"multimodal encoder attention backend {name!r} is only available "
|
|
"on NVIDIA CUDA"
|
|
)
|
|
if name == "fa3" and _platform.is_blackwell:
|
|
raise ValueError("The 'fa3' backend is not supported on Blackwell GPUs")
|
|
logger.info(
|
|
f"multimodal encoder attention backend: {name} "
|
|
f"({'override' if explicit else 'auto'})"
|
|
)
|
|
return fn
|
|
|
|
|
|
class MultimodalEncoderAttention(nn.Module):
|
|
r"""Multi-headed attention without a KV cache for multimodal encoders."""
|
|
|
|
def __init__(
|
|
self,
|
|
embed_dim: int,
|
|
num_heads: int,
|
|
mapping: Mapping,
|
|
head_size: int | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
proj_bias: bool = True,
|
|
qkv_bias: bool = True,
|
|
customized_position_embedding_applier: Callable[
|
|
[torch.Tensor, torch.Tensor, Any, Any], tuple[torch.Tensor, torch.Tensor]
|
|
] = None,
|
|
position_embedding_mode: str | None = None,
|
|
workspace_buffer: torch.Tensor | None = None,
|
|
mm_attention_backend: str | None = None,
|
|
):
|
|
super().__init__()
|
|
self.vision = mapping.vision
|
|
self.tp_size = self.vision.tp_size
|
|
self.tp_rank = self.vision.tp_rank
|
|
self.tp_group = self.vision.tp_group
|
|
self.head_size = head_size if head_size is not None else embed_dim // num_heads
|
|
self.num_attention_heads_per_partition = dist_utils.divide(
|
|
num_heads, self.tp_size
|
|
)
|
|
self.num_attention_kv_heads_per_partition = dist_utils.divide(
|
|
num_heads, self.tp_size
|
|
)
|
|
|
|
self.q_size = self.num_attention_heads_per_partition * self.head_size
|
|
self.kv_size = self.num_attention_kv_heads_per_partition * self.head_size
|
|
|
|
self.customized_position_embedding_applier = (
|
|
customized_position_embedding_applier
|
|
)
|
|
if position_embedding_mode not in (None, "complex_rope"):
|
|
raise ValueError(
|
|
f"Unknown vision position embedding mode: {position_embedding_mode}"
|
|
)
|
|
self.position_embedding_mode = position_embedding_mode
|
|
self._backend_fn = _resolve_backend(mm_attention_backend)
|
|
self._use_packed_qkv_complex_rotary = (
|
|
self._backend_fn is vision_attn_fa4
|
|
and self.position_embedding_mode == "complex_rope"
|
|
)
|
|
self._use_packed_qkv_rotary = (
|
|
self._backend_fn is vision_attn_fa4
|
|
and self.customized_position_embedding_applier is None
|
|
)
|
|
self._copy_v_after_packed_qkv_rotary = False
|
|
self._workspace_buffer = workspace_buffer
|
|
|
|
self.qkv_proj = QKVParallelLinear(
|
|
hidden_size=embed_dim,
|
|
head_size=self.head_size,
|
|
total_num_heads=num_heads,
|
|
total_num_kv_heads=num_heads,
|
|
bias=qkv_bias,
|
|
quant_config=quant_config,
|
|
tp_rank=self.tp_rank,
|
|
tp_size=self.tp_size,
|
|
tp_group=self.tp_group,
|
|
prefix=add_prefix("qkv_proj", prefix),
|
|
)
|
|
self.proj = RowParallelLinear(
|
|
input_size=num_heads * self.head_size,
|
|
output_size=embed_dim,
|
|
bias=proj_bias,
|
|
quant_config=quant_config,
|
|
tp_rank=self.tp_rank,
|
|
tp_size=self.tp_size,
|
|
tp_group=self.tp_group,
|
|
prefix=add_prefix("proj", prefix),
|
|
reduce_results=True,
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
x: torch.Tensor,
|
|
cu_seqlens: torch.Tensor | None = None,
|
|
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
|
|
rotary_pos_emb_cos: torch.Tensor | None = None,
|
|
rotary_pos_emb_sin: torch.Tensor | None = None,
|
|
**kwargs,
|
|
) -> torch.Tensor:
|
|
r"""
|
|
Args:
|
|
x: [b, s, embed_dim]
|
|
cu_seqlens: [b]
|
|
Returns:
|
|
[b, s, head * head_size]
|
|
"""
|
|
if x.dim() == 2:
|
|
x = x.unsqueeze(0)
|
|
x_shape = x.shape
|
|
bsz, s, _ = x_shape
|
|
head = self.num_attention_heads_per_partition
|
|
kv_head = self.num_attention_kv_heads_per_partition
|
|
|
|
max_seqlen = kwargs["max_seqlen"] if "max_seqlen" in kwargs else None
|
|
sequence_lengths = (
|
|
kwargs["sequence_lengths"] if "sequence_lengths" in kwargs else None
|
|
)
|
|
|
|
qkv, _ = self.qkv_proj(x)
|
|
|
|
use_packed_qkv_rotary = (
|
|
self._use_packed_qkv_rotary
|
|
and position_embeddings is None
|
|
and rotary_pos_emb_cos is not None
|
|
and rotary_pos_emb_sin is not None
|
|
)
|
|
use_packed_qkv_complex_rotary = (
|
|
self._use_packed_qkv_complex_rotary and position_embeddings is not None
|
|
)
|
|
cos = rotary_pos_emb_cos if use_packed_qkv_rotary else None
|
|
sin = rotary_pos_emb_sin if use_packed_qkv_rotary else None
|
|
|
|
if use_packed_qkv_rotary:
|
|
if cos.size(-1) * 2 == self.head_size:
|
|
cos = torch.cat([cos, cos], dim=-1)
|
|
sin = torch.cat([sin, sin], dim=-1)
|
|
q, k, v = packed_qkv_neox_rotary(
|
|
qkv,
|
|
self.q_size,
|
|
self.kv_size,
|
|
head,
|
|
self.head_size,
|
|
cos,
|
|
sin,
|
|
copy_v=self._copy_v_after_packed_qkv_rotary,
|
|
)
|
|
elif use_packed_qkv_complex_rotary:
|
|
q, k, v = packed_qkv_complex_rotary(
|
|
qkv,
|
|
self.q_size,
|
|
self.kv_size,
|
|
head,
|
|
self.head_size,
|
|
position_embeddings,
|
|
copy_v=self._copy_v_after_packed_qkv_rotary,
|
|
)
|
|
else:
|
|
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
|
|
|
q = q.reshape(bsz * s, head, -1)
|
|
k = k.reshape(bsz * s, kv_head, -1)
|
|
v = v.reshape(bsz * s, kv_head, -1)
|
|
|
|
cos = None
|
|
sin = None
|
|
|
|
if position_embeddings is not None:
|
|
if self.customized_position_embedding_applier is not None:
|
|
q, k = self.customized_position_embedding_applier(
|
|
q, k, position_embeddings, x_shape
|
|
)
|
|
else:
|
|
cos, sin = position_embeddings
|
|
elif rotary_pos_emb_cos is not None and rotary_pos_emb_sin is not None:
|
|
cos = rotary_pos_emb_cos
|
|
sin = rotary_pos_emb_sin
|
|
|
|
if (
|
|
not use_packed_qkv_rotary
|
|
and not use_packed_qkv_complex_rotary
|
|
and cos is not None
|
|
and sin is not None
|
|
):
|
|
original_shape = q.shape
|
|
|
|
# [total_tokens, head, head_size]
|
|
q = q.view(-1, head, self.head_size)
|
|
k = k.view(-1, head, self.head_size)
|
|
|
|
if cos.size(-1) * 2 == self.head_size:
|
|
cos = torch.cat([cos, cos], dim=-1)
|
|
sin = torch.cat([sin, sin], dim=-1)
|
|
|
|
q, k = apply_rotary_pos_emb_native(q, k, cos, sin)
|
|
q = q.view(original_shape)
|
|
k = k.view(original_shape)
|
|
|
|
q, k, v = [
|
|
rearrange(t, "b s ... -> (b s) ...") if t.dim() == 4 else t
|
|
for t in (q, k, v)
|
|
]
|
|
|
|
output = self._backend_fn(
|
|
q,
|
|
k,
|
|
v,
|
|
cu_seqlens=cu_seqlens,
|
|
bsz=bsz,
|
|
seq_len=s,
|
|
max_seqlen=max_seqlen,
|
|
sequence_lengths=sequence_lengths,
|
|
workspace_buffer=self._workspace_buffer,
|
|
)
|
|
|
|
output = rearrange(output, "(b s) ... h d -> b s ... (h d)", b=bsz)
|
|
output, _ = self.proj(output)
|
|
|
|
return output
|
|
|
|
|
|
# Compatibility alias for existing vision tower implementations.
|
|
VisionAttention = MultimodalEncoderAttention
|