stream-cqsa 2.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stream_cqsa/__init__.py +90 -0
- stream_cqsa/adapters.py +127 -0
- stream_cqsa/api.py +363 -0
- stream_cqsa/attention_kernel/Custom.py +97 -0
- stream_cqsa/attention_kernel/FA.py +92 -0
- stream_cqsa/attention_kernel/__init__.py +7 -0
- stream_cqsa/autoconfig.py +607 -0
- stream_cqsa/autograd_op.py +596 -0
- stream_cqsa/backends/__init__.py +2 -0
- stream_cqsa/backends/exact/__init__.py +25 -0
- stream_cqsa/backends/exact/online_softmax.py +279 -0
- stream_cqsa/backends/exact/rectangular_ooc_backend.py +68 -0
- stream_cqsa/backends/exact/rectangular_ooc_inner_kernel.py +89 -0
- stream_cqsa/backends/exact/sdpa_backend.py +88 -0
- stream_cqsa/backends/exact/sdpa_inner_kernel.py +573 -0
- stream_cqsa/baselines/__init__.py +2 -0
- stream_cqsa/baselines/longnet/__init__.py +21 -0
- stream_cqsa/baselines/longnet/longnet_backend.py +100 -0
- stream_cqsa/baselines/longnet/longnet_inner_kernel.py +294 -0
- stream_cqsa/baselines/longnet/longnet_mask.py +244 -0
- stream_cqsa/config.py +19 -0
- stream_cqsa/cqs_attention.py +158 -0
- stream_cqsa/cqs_attention_stream.py +1949 -0
- stream_cqsa/cqs_attention_wrapper.py +760 -0
- stream_cqsa/cqs_mask.py +647 -0
- stream_cqsa/cqsa_probe_capacity.py +534 -0
- stream_cqsa/devkit.py +413 -0
- stream_cqsa/distributed.py +181 -0
- stream_cqsa/doctor.py +125 -0
- stream_cqsa/interface.py +444 -0
- stream_cqsa/memory_fitting.py +498 -0
- stream_cqsa/native_autograd.py +211 -0
- stream_cqsa/native_wave.py +569 -0
- stream_cqsa/offload.py +105 -0
- stream_cqsa/oom_fallback.py +346 -0
- stream_cqsa/progress.py +129 -0
- stream_cqsa/reference.py +707 -0
- stream_cqsa/stable_stream.py +2521 -0
- stream_cqsa/streamed_fwd_bwd.py +2169 -0
- stream_cqsa/triton_kernel.py +594 -0
- stream_cqsa-2.1.2.dist-info/METADATA +22 -0
- stream_cqsa-2.1.2.dist-info/RECORD +46 -0
- stream_cqsa-2.1.2.dist-info/WHEEL +5 -0
- stream_cqsa-2.1.2.dist-info/entry_points.txt +2 -0
- stream_cqsa-2.1.2.dist-info/licenses/LICENSE +29 -0
- stream_cqsa-2.1.2.dist-info/top_level.txt +1 -0
stream_cqsa/__init__.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
__version__ = "2.1.2"
|
|
2
|
+
|
|
3
|
+
from .stable_stream import (
|
|
4
|
+
stream_cqsa_forward,
|
|
5
|
+
stream_cqsa_backward,
|
|
6
|
+
TraceRecorder,
|
|
7
|
+
)
|
|
8
|
+
from .oom_fallback import (stream_cqsa_auto, attention_oom_safe,
|
|
9
|
+
ESCALATION, ESCALATION_FAST)
|
|
10
|
+
from .native_autograd import stream_cqsa_attn, StreamCQSAAttention
|
|
11
|
+
from .api import attention, estimate, patch_sdpa, unpatch_sdpa, patched_sdpa, kernels_available
|
|
12
|
+
from .doctor import doctor
|
|
13
|
+
from .progress import verbose_enabled
|
|
14
|
+
|
|
15
|
+
from .interface import (
|
|
16
|
+
flash_attn_func,
|
|
17
|
+
flash_attn_func_cqs,
|
|
18
|
+
flash_attn_func_cqs_group_bits,
|
|
19
|
+
flash_attn_bwd_cqs_group_bits,
|
|
20
|
+
flash_attn_func_cqsa,
|
|
21
|
+
)
|
|
22
|
+
from .config import (
|
|
23
|
+
DEFAULT_PROBING_HISTORY,
|
|
24
|
+
DEFAULT_PROBING_HISTORY_DIR,
|
|
25
|
+
GLOBAL_STREAM_SEED,
|
|
26
|
+
MEM_BUDGET_COE,
|
|
27
|
+
)
|
|
28
|
+
from .cqsa_probe_capacity import CQSAProbe
|
|
29
|
+
from .cqs_attention_stream import (
|
|
30
|
+
CQSAStreamRunner,
|
|
31
|
+
SubsequenceAttentionFn,
|
|
32
|
+
compare_O,
|
|
33
|
+
run_scqsa_stream,
|
|
34
|
+
stream_cqsa,
|
|
35
|
+
)
|
|
36
|
+
from .attention_kernel import (
|
|
37
|
+
default_subsequence_attention,
|
|
38
|
+
custom_attn,
|
|
39
|
+
)
|
|
40
|
+
from .cqs_mask import CQS_mask
|
|
41
|
+
from .memory_fitting import fitting_mem, pred_mem, best_seq_length, estimate_memory_time_from_model
|
|
42
|
+
from .autograd_op import stream_cqsa_autograd
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# --- the one-call API -------------------------------------------------
|
|
46
|
+
"attention", # SDPA signature; monolithic when it fits, exact decomposition when it does not
|
|
47
|
+
"estimate", # dry run: what a call would cost here
|
|
48
|
+
"patch_sdpa", "unpatch_sdpa", "patched_sdpa",
|
|
49
|
+
"doctor", "kernels_available",
|
|
50
|
+
# --- primary entry points -------------------------------------------
|
|
51
|
+
"stream_cqsa_auto", # "just run it": escalates until it fits
|
|
52
|
+
"stream_cqsa_forward", # explicit forward, returns (out, lse)
|
|
53
|
+
"stream_cqsa_backward", # explicit backward, needs the global lse
|
|
54
|
+
"stream_cqsa_attn", # autograd-aware; supports .backward()
|
|
55
|
+
"StreamCQSAAttention", # the same, as an nn.Module
|
|
56
|
+
"attention_oom_safe",
|
|
57
|
+
"ESCALATION",
|
|
58
|
+
"ESCALATION_FAST",
|
|
59
|
+
"TraceRecorder",
|
|
60
|
+
# --- everything else --------------------------------------------------
|
|
61
|
+
"flash_attn_func",
|
|
62
|
+
"flash_attn_func_cqs",
|
|
63
|
+
"flash_attn_func_cqs_group_bits",
|
|
64
|
+
"flash_attn_bwd_cqs_group_bits",
|
|
65
|
+
"flash_attn_func_cqsa",
|
|
66
|
+
"CQSAProbe",
|
|
67
|
+
"CQSAStreamRunner",
|
|
68
|
+
"SubsequenceAttentionFn",
|
|
69
|
+
"DEFAULT_PROBING_HISTORY",
|
|
70
|
+
"DEFAULT_PROBING_HISTORY_DIR",
|
|
71
|
+
"GLOBAL_STREAM_SEED",
|
|
72
|
+
"MEM_BUDGET_COE",
|
|
73
|
+
"run_scqsa_stream",
|
|
74
|
+
"stream_cqsa",
|
|
75
|
+
"compare_O",
|
|
76
|
+
"default_subsequence_attention",
|
|
77
|
+
"custom_attn",
|
|
78
|
+
"CQS_mask",
|
|
79
|
+
"fitting_mem",
|
|
80
|
+
"pred_mem",
|
|
81
|
+
"best_seq_length",
|
|
82
|
+
"estimate_memory_time_from_model",
|
|
83
|
+
"stream_cqsa_autograd",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# v2 additions
|
|
87
|
+
from .autoconfig import (detect_hardware, hardware_from_dict, plan, calibrate, autotune, # noqa: E402
|
|
88
|
+
auto_attention, QUORUM_SETS, CostModel, Plan)
|
|
89
|
+
from .devkit import compare_kernels, quick_bench, Config, run_config # noqa: E402
|
|
90
|
+
from .adapters import flex_inner, dense_inner # noqa: E402
|
stream_cqsa/adapters.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapters that turn a monolithic attention implementation into a Stream-CQSA
|
|
3
|
+
inner kernel without writing CUDA.
|
|
4
|
+
|
|
5
|
+
What an inner kernel must provide (see devkit.compare_kernels):
|
|
6
|
+
|
|
7
|
+
inner(q_i, k_i, v_i, group_bits, *, causal, scale, **_) -> (out_i, lse_i)
|
|
8
|
+
|
|
9
|
+
i.e. attention restricted to the pair set {(r, c): bits[r] & bits[c] == 0,
|
|
10
|
+
(c <= r if causal)}, returning the normalised output ``out_i [B, L, H, D]``
|
|
11
|
+
AND the per-row log-sum-exp of the kept scores ``lse_i [B, L, H]`` (both
|
|
12
|
+
token-major, float32). Any implementation that (a) accepts an
|
|
13
|
+
arbitrary pairwise mask and (b) returns the log-sum-exp can therefore be
|
|
14
|
+
plugged in mechanically. Two such families exist in PyTorch today:
|
|
15
|
+
|
|
16
|
+
* ``flex_inner(score_mod=None)`` -- torch's FlexAttention. The CQS pair set is
|
|
17
|
+
expressed as a ``mask_mod`` (a block mask is built per subproblem, so the
|
|
18
|
+
masked tiles are skipped by the compiled kernel too), and ``return_lse=True``
|
|
19
|
+
gives the lse. Any attention that FlexAttention can express -- ALiBi,
|
|
20
|
+
soft-capping, sliding windows, document masks, relative biases -- becomes a
|
|
21
|
+
Stream-CQSA inner kernel by passing its ``score_mod``/extra mask, with no
|
|
22
|
+
other change. That is the "automatic conversion" for the score-modification
|
|
23
|
+
family.
|
|
24
|
+
|
|
25
|
+
* ``dense_inner(mono_masked)`` -- for small subproblems, any callable that
|
|
26
|
+
takes a dense boolean mask and returns (out, lse) (or just out, in which
|
|
27
|
+
case the lse is recomputed densely). This is the differential-testing route.
|
|
28
|
+
|
|
29
|
+
Kernels that return NO log-sum-exp cannot be adapted losslessly: the merge of
|
|
30
|
+
subproblems needs the row statistics, and recovering them from the output
|
|
31
|
+
alone is impossible (the output is normalised). For those the only exact
|
|
32
|
+
option is a second pass that computes the lse (``lse_from_scores``), which
|
|
33
|
+
costs another QK^T; the devkit reports that overhead honestly.
|
|
34
|
+
"""
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from typing import Callable
|
|
38
|
+
|
|
39
|
+
import torch
|
|
40
|
+
|
|
41
|
+
_flex_cache: dict = {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _bits_pair_mask(bits: torch.Tensor, causal: bool):
|
|
45
|
+
def mask_mod(b, h, q_idx, kv_idx):
|
|
46
|
+
keep = (bits[q_idx] & bits[kv_idx]) == 0
|
|
47
|
+
if causal:
|
|
48
|
+
keep = keep & (kv_idx <= q_idx)
|
|
49
|
+
return keep
|
|
50
|
+
return mask_mod
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def flex_inner(score_mod: Callable | None = None, *, extra_mask_mod: Callable | None = None,
|
|
54
|
+
block_size: int = 128, compile: bool = True, global_positions: bool = True) -> Callable:
|
|
55
|
+
"""
|
|
56
|
+
Build a Stream-CQSA inner kernel from torch FlexAttention.
|
|
57
|
+
|
|
58
|
+
`score_mod(score, b, h, q_idx, kv_idx)` and `extra_mask_mod(b, h, q_idx, kv_idx)`
|
|
59
|
+
are written against GLOBAL token positions (as in the monolithic call): a
|
|
60
|
+
subproblem sees a gathered subsequence, so the engine hands the inner kernel
|
|
61
|
+
the gather index (`token_ids`) and the adapter remaps local indices through
|
|
62
|
+
it before calling the user's functions. That is the one thing an automatic
|
|
63
|
+
conversion must get right for position-dependent attention (ALiBi, sliding
|
|
64
|
+
windows, document masks); with `global_positions=False` the raw local
|
|
65
|
+
indices are passed instead (only correct for position-free score_mods).
|
|
66
|
+
"""
|
|
67
|
+
from torch.nn.attention.flex_attention import flex_attention, create_block_mask
|
|
68
|
+
fa = torch.compile(flex_attention, dynamic=False) if compile else flex_attention
|
|
69
|
+
|
|
70
|
+
def inner(q_i, k_i, v_i, group_bits, *, causal: bool, scale: float, token_ids=None, **_):
|
|
71
|
+
B, L, H, D = q_i.shape
|
|
72
|
+
dev = q_i.device
|
|
73
|
+
bits = group_bits.to(dev, torch.int64)
|
|
74
|
+
mm = _bits_pair_mask(bits, causal)
|
|
75
|
+
ids = token_ids.to(dev, torch.int64) if (token_ids is not None and global_positions) else None
|
|
76
|
+
if extra_mask_mod is not None:
|
|
77
|
+
base = mm
|
|
78
|
+
if ids is not None:
|
|
79
|
+
mm = lambda b, h, qi, ki: base(b, h, qi, ki) & extra_mask_mod(b, h, ids[qi], ids[ki])
|
|
80
|
+
else:
|
|
81
|
+
mm = lambda b, h, qi, ki: base(b, h, qi, ki) & extra_mask_mod(b, h, qi, ki)
|
|
82
|
+
sm = score_mod
|
|
83
|
+
if score_mod is not None and ids is not None:
|
|
84
|
+
sm = lambda score, b, h, qi, ki: score_mod(score, b, h, ids[qi], ids[ki])
|
|
85
|
+
bm = create_block_mask(mm, B=None, H=None, Q_LEN=L, KV_LEN=L, device=dev, BLOCK_SIZE=block_size)
|
|
86
|
+
qb, kb, vb = (t.transpose(1, 2) for t in (q_i, k_i, v_i)) # [B,H,L,D]
|
|
87
|
+
out, lse = fa(qb, kb, vb, score_mod=sm, block_mask=bm, scale=float(scale), return_lse=True)
|
|
88
|
+
# rows with no kept pair: flex returns lse=-inf and out=0, which is the contract.
|
|
89
|
+
# Engine contract: out_i [B,L,H,D], lse_i [B,L,H] (token-major, like the CUDA kernel's return).
|
|
90
|
+
return out.transpose(1, 2).float(), lse.float().transpose(1, 2)
|
|
91
|
+
|
|
92
|
+
return inner
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def lse_from_scores(q_i, k_i, v_i, keep: torch.Tensor, scale: float) -> torch.Tensor:
|
|
96
|
+
"""Dense fp32 per-row lse over kept pairs (second pass for kernels without lse). [B,H,L]"""
|
|
97
|
+
s = torch.matmul(q_i.transpose(1, 2).float(), k_i.transpose(1, 2).float().transpose(-1, -2)) * float(scale)
|
|
98
|
+
s = s.masked_fill(~keep[None, None], float("-inf"))
|
|
99
|
+
return torch.logsumexp(s, dim=-1)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def dense_inner(mono_masked: Callable, *, returns_lse: bool = True) -> Callable:
|
|
103
|
+
"""
|
|
104
|
+
Wrap ``mono_masked(q, k, v, mask, scale) -> (out [B,H,L,D], lse [B,H,L])`` (or
|
|
105
|
+
just out if returns_lse=False) where mask is a dense bool [L, L] (True = keep).
|
|
106
|
+
Only for small L (the mask is O(L^2)).
|
|
107
|
+
"""
|
|
108
|
+
def inner(q_i, k_i, v_i, group_bits, *, causal: bool, scale: float, **_):
|
|
109
|
+
L = q_i.shape[1]
|
|
110
|
+
bits = group_bits.to(q_i.device, torch.int64)
|
|
111
|
+
keep = (bits[:, None] & bits[None, :]) == 0
|
|
112
|
+
if causal:
|
|
113
|
+
keep = keep & torch.ones(L, L, dtype=torch.bool, device=q_i.device).tril()
|
|
114
|
+
qb, kb, vb = (t.transpose(1, 2) for t in (q_i, k_i, v_i))
|
|
115
|
+
r = mono_masked(qb, kb, vb, keep, float(scale))
|
|
116
|
+
if returns_lse:
|
|
117
|
+
out, lse = r
|
|
118
|
+
else:
|
|
119
|
+
out, lse = r, lse_from_scores(q_i, k_i, v_i, keep, scale)
|
|
120
|
+
return out.transpose(1, 2).float(), lse.float().transpose(1, 2) # [B,L,H,D], [B,L,H]
|
|
121
|
+
return inner
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def sdpa_masked(qb, kb, vb, keep, scale):
|
|
125
|
+
"""Reference monolithic-with-mask kernel (torch SDPA, no lse): use with dense_inner(..., returns_lse=False)."""
|
|
126
|
+
import torch.nn.functional as F
|
|
127
|
+
return F.scaled_dot_product_attention(qb, kb, vb, attn_mask=keep, scale=scale)
|
stream_cqsa/api.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The one entry point: ``stream_cqsa.attention`` with the signature of
|
|
3
|
+
``torch.nn.functional.scaled_dot_product_attention``.
|
|
4
|
+
|
|
5
|
+
out = stream_cqsa.attention(q, k, v, is_causal=True)
|
|
6
|
+
|
|
7
|
+
* below the memory boundary it IS the monolithic call (SDPA / FlashAttention);
|
|
8
|
+
* above it, the planner picks the decomposition (depth, quorum set, accumulator
|
|
9
|
+
placement, host streaming, concurrency) from the free memory and the call
|
|
10
|
+
runs exactly, on the best kernel available (native wave kernel, CUDA
|
|
11
|
+
extension, or the Triton kernels when nothing is compiled);
|
|
12
|
+
* gradients flow when the inputs require them;
|
|
13
|
+
* ``verbose=True`` (or ``CQSA_VERBOSE=1``) shows what is happening.
|
|
14
|
+
|
|
15
|
+
``patch_sdpa()`` swaps this in for ``F.scaled_dot_product_attention`` so an
|
|
16
|
+
existing model runs unchanged; ``estimate(N, ...)`` is the dry run that says
|
|
17
|
+
what a call would cost before it is made.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import contextlib
|
|
22
|
+
import os
|
|
23
|
+
import warnings
|
|
24
|
+
from typing import Any, Optional
|
|
25
|
+
|
|
26
|
+
import torch
|
|
27
|
+
import torch.nn.functional as F
|
|
28
|
+
|
|
29
|
+
from .progress import verbose_enabled
|
|
30
|
+
|
|
31
|
+
__all__ = ["attention", "estimate", "patch_sdpa", "unpatch_sdpa", "patched_sdpa", "kernels_available"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def kernels_available() -> dict[str, bool]:
|
|
35
|
+
"""Which attention kernels this environment can run."""
|
|
36
|
+
from . import interface as I
|
|
37
|
+
out = {"cuda_extension": I.cqsa_cuda is not None, "cuda_extension_noncausal": I.cqsa_cuda_noncausal is not None}
|
|
38
|
+
try:
|
|
39
|
+
from .native_wave import native_available
|
|
40
|
+
out["native_wave"] = native_available()
|
|
41
|
+
except Exception:
|
|
42
|
+
out["native_wave"] = False
|
|
43
|
+
try:
|
|
44
|
+
import triton # noqa: F401
|
|
45
|
+
from . import triton_kernel # noqa: F401
|
|
46
|
+
out["triton"] = True
|
|
47
|
+
except Exception:
|
|
48
|
+
out["triton"] = False
|
|
49
|
+
try:
|
|
50
|
+
import flash_attn # noqa: F401
|
|
51
|
+
out["flash_attn"] = True
|
|
52
|
+
except Exception:
|
|
53
|
+
out["flash_attn"] = False
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _check_inputs(q, k, v, attn_mask, dropout_p, enable_gqa):
|
|
58
|
+
if q.dim() != 4 or k.dim() != 4 or v.dim() != 4:
|
|
59
|
+
raise ValueError(f"stream_cqsa.attention expects q/k/v as [B, H, N, D]; got {tuple(q.shape)}, {tuple(k.shape)}, {tuple(v.shape)}")
|
|
60
|
+
if attn_mask is not None:
|
|
61
|
+
raise NotImplementedError("stream_cqsa.attention supports the plain and the causal (is_causal=True) cases; an explicit "
|
|
62
|
+
"attn_mask is not supported. For FlexAttention-style masks use stream_cqsa.adapters.flex_inner.")
|
|
63
|
+
if dropout_p:
|
|
64
|
+
raise NotImplementedError("stream_cqsa.attention does not support dropout (dropout_p must be 0).")
|
|
65
|
+
if enable_gqa or k.shape[1] != q.shape[1]:
|
|
66
|
+
raise NotImplementedError("stream_cqsa.attention needs the same number of heads for q and k/v (no GQA); repeat k/v heads first.")
|
|
67
|
+
if q.shape != k.shape or q.shape != v.shape:
|
|
68
|
+
raise ValueError("q, k and v must have the same shape [B, H, N, D] (cross-attention with N_q != N_k is not supported)")
|
|
69
|
+
if q.dtype not in (torch.float16, torch.bfloat16):
|
|
70
|
+
raise TypeError(f"stream_cqsa.attention needs fp16 or bf16 inputs (got {q.dtype}); cast with .half() or .bfloat16(). "
|
|
71
|
+
"Accumulation is fp32 internally regardless.")
|
|
72
|
+
if not (q.dtype == k.dtype == v.dtype):
|
|
73
|
+
raise TypeError("q, k and v must share a dtype")
|
|
74
|
+
if not (q.device == k.device == v.device):
|
|
75
|
+
raise ValueError("q, k and v must be on the same device")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _is_oom(exc: BaseException) -> bool:
|
|
79
|
+
return isinstance(exc, torch.cuda.OutOfMemoryError) or "out of memory" in str(exc).lower()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, attn_mask=None, dropout_p: float = 0.0,
|
|
83
|
+
is_causal: bool = False, scale: Optional[float] = None, enable_gqa: bool = False, *,
|
|
84
|
+
verbose: Optional[bool] = None, hardware=None, kernel: str = "auto", plan_only: bool = False,
|
|
85
|
+
return_plan: bool = False, **overrides) -> torch.Tensor:
|
|
86
|
+
"""
|
|
87
|
+
Exact attention that always fits. Drop-in for ``F.scaled_dot_product_attention``
|
|
88
|
+
(same positional signature; masks, dropout and GQA are rejected with a message).
|
|
89
|
+
|
|
90
|
+
q/k/v ``[B, H, N, D]`` fp16/bf16, on a device or in host memory. Returns the
|
|
91
|
+
output in the inputs' dtype and device. Differentiable when the inputs require
|
|
92
|
+
gradients. ``kernel``: "auto" (native wave kernel when built and applicable,
|
|
93
|
+
else the CUDA extension, else Triton), "wave", "cuda", "triton".
|
|
94
|
+
``overrides`` (itr=, c=, interest_set=, max_parallel=, ...) pin engine settings.
|
|
95
|
+
``plan_only=True`` returns the plan without computing anything.
|
|
96
|
+
"""
|
|
97
|
+
_INSIDE[0] += 1
|
|
98
|
+
try:
|
|
99
|
+
return _attention(q, k, v, attn_mask, dropout_p, is_causal, scale, enable_gqa, verbose=verbose, hardware=hardware,
|
|
100
|
+
kernel=kernel, plan_only=plan_only, return_plan=return_plan, **overrides)
|
|
101
|
+
finally:
|
|
102
|
+
_INSIDE[0] -= 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _attention(q, k, v, attn_mask, dropout_p, is_causal, scale, enable_gqa, *, verbose, hardware, kernel, plan_only,
|
|
106
|
+
return_plan, **overrides):
|
|
107
|
+
from .autoconfig import plan as _plan, hardware_from_dict, detect_hardware
|
|
108
|
+
_check_inputs(q, k, v, attn_mask, dropout_p, enable_gqa)
|
|
109
|
+
B, H, N, D = q.shape
|
|
110
|
+
if scale is None:
|
|
111
|
+
scale = float(D) ** -0.5
|
|
112
|
+
on_cuda = q.device.type == "cuda"
|
|
113
|
+
out_device = q.device
|
|
114
|
+
hw = hardware_from_dict(hardware) if isinstance(hardware, dict) else (hardware or detect_hardware())
|
|
115
|
+
direction = "bwd" if torch.is_grad_enabled() and (q.requires_grad or k.requires_grad or v.requires_grad) else "fwd"
|
|
116
|
+
p = _plan(N=N, B=B, H=H, D=D, dtype=q.dtype, causal=bool(is_causal), hardware=hw, direction=direction,
|
|
117
|
+
allow_distributed=False)
|
|
118
|
+
if plan_only:
|
|
119
|
+
return p
|
|
120
|
+
vb = verbose_enabled(verbose)
|
|
121
|
+
|
|
122
|
+
# ---- monolithic: the call fits, so it IS the normal kernel ------------------
|
|
123
|
+
if p.mode == "mono" and not overrides:
|
|
124
|
+
if vb:
|
|
125
|
+
print(f"Stream-CQSA: N={N} fits ({p.est_peak_gib:.1f} GiB) -- monolithic call, no decomposition", flush=True)
|
|
126
|
+
qd, kd, vd = ((t if on_cuda else t.to("cuda", non_blocking=True)) for t in (q, k, v))
|
|
127
|
+
try:
|
|
128
|
+
out = _TRUE_SDPA(qd, kd, vd, is_causal=bool(is_causal), scale=scale)
|
|
129
|
+
out = out if on_cuda else out.to(q.device)
|
|
130
|
+
return (out, p) if return_plan else out
|
|
131
|
+
except Exception as exc: # noqa: BLE001
|
|
132
|
+
if not _is_oom(exc):
|
|
133
|
+
raise
|
|
134
|
+
if vb:
|
|
135
|
+
print("Stream-CQSA: the monolithic call ran out of memory after all; decomposing", flush=True)
|
|
136
|
+
del qd, kd, vd
|
|
137
|
+
torch.cuda.empty_cache()
|
|
138
|
+
p = _plan(N=N, B=B, H=H, D=D, dtype=q.dtype, causal=bool(is_causal), hardware=detect_hardware(),
|
|
139
|
+
direction=direction, allow_distributed=False)
|
|
140
|
+
if p.mode == "mono": # the planner still thinks it fits: force a decomposition
|
|
141
|
+
p.mode, p.itr, p.c, p.interest_set = "cqsa", 1, 7, (0, 1, 3)
|
|
142
|
+
|
|
143
|
+
# ---- decomposed ------------------------------------------------------------
|
|
144
|
+
kw = p.engine_kwargs() if p.mode != "mono" else dict(itr=1, c=7, interest_set=(0, 1, 3))
|
|
145
|
+
if on_cuda and kw.get("stream_from_host"):
|
|
146
|
+
# The inputs are on the device. Prefer the fastest configuration that leaves them
|
|
147
|
+
# there; stream from the host only if nothing device-resident fits (then a pinned
|
|
148
|
+
# host copy is made -- the caller's device tensors stay where they are).
|
|
149
|
+
dev_ok = [c for c in p.candidates if c["ok"] and c["mode"] == "cqsa" and not c["stream_from_host"]]
|
|
150
|
+
if dev_ok:
|
|
151
|
+
c0 = min(dev_ok, key=lambda c: c["time"])
|
|
152
|
+
kw = dict(itr=int(c0["itr"]), c=int(c0["c"]), interest_set=tuple(c0["interest_set"]),
|
|
153
|
+
low_memory=(c0["acc"] == "cpu"), accumulate_on_gpu=(c0["acc"] == "gpu"),
|
|
154
|
+
stream_from_host=False, max_parallel=int(c0["n_par"]), shared_chunks=False)
|
|
155
|
+
elif direction == "bwd":
|
|
156
|
+
# Copying the inputs to the host would sever the autograd graph. Keep them on the
|
|
157
|
+
# device, park the accumulator in host memory, and let the engine escalate the
|
|
158
|
+
# depth on its own if the device is still short.
|
|
159
|
+
if vb:
|
|
160
|
+
print("Stream-CQSA: no device-resident configuration fits the estimate; keeping Q/K/V on the device with a "
|
|
161
|
+
"host accumulator and escalating on demand", flush=True)
|
|
162
|
+
kw.update(stream_from_host=False, low_memory=True, accumulate_on_gpu=False, shared_chunks=False)
|
|
163
|
+
else:
|
|
164
|
+
if vb:
|
|
165
|
+
print("Stream-CQSA: no device-resident configuration fits; copying Q/K/V to pinned host memory and streaming", flush=True)
|
|
166
|
+
if direction == "bwd":
|
|
167
|
+
q, k, v = (t.to("cpu") for t in (q, k, v)) # differentiable copies: the graph stays intact
|
|
168
|
+
else:
|
|
169
|
+
q, k, v = (t.detach().to("cpu").pin_memory() for t in (q, k, v))
|
|
170
|
+
on_cuda = False
|
|
171
|
+
kw.update(overrides)
|
|
172
|
+
if not kw.get("stream_from_host"):
|
|
173
|
+
kw["shared_chunks"] = False
|
|
174
|
+
use_wave = _pick_wave(kernel, kw, is_causal, q, direction)
|
|
175
|
+
if vb:
|
|
176
|
+
print(f"Stream-CQSA: {p.reason}", flush=True)
|
|
177
|
+
if use_wave:
|
|
178
|
+
from .native_wave import wave_attention, wave_forward
|
|
179
|
+
wkw = dict(itr=int(kw.get("itr", 1)), c=int(kw.get("c", 7)), interest_set=tuple(kw.get("interest_set", (0, 1, 3))))
|
|
180
|
+
try:
|
|
181
|
+
if direction == "bwd":
|
|
182
|
+
out = wave_attention(q, k, v, causal=bool(is_causal), scale=scale, verbose=verbose, **wkw)
|
|
183
|
+
else:
|
|
184
|
+
out, _ = wave_forward(q, k, v, causal=bool(is_causal), scale=scale, verbose=verbose, **wkw)
|
|
185
|
+
except Exception as exc: # noqa: BLE001
|
|
186
|
+
if not _is_oom(exc) or kernel == "wave":
|
|
187
|
+
raise
|
|
188
|
+
# the wave engine keeps its accumulator on the device; the classic engine can offload it
|
|
189
|
+
if vb:
|
|
190
|
+
print("Stream-CQSA: the wave engine ran out of memory; retrying on the streaming engine", flush=True)
|
|
191
|
+
torch.cuda.empty_cache()
|
|
192
|
+
use_wave = False
|
|
193
|
+
if not use_wave:
|
|
194
|
+
from .native_autograd import stream_cqsa_attn
|
|
195
|
+
from .stable_stream import stream_cqsa_forward
|
|
196
|
+
if kernel == "triton":
|
|
197
|
+
os.environ["CQSA_FORWARD"] = "triton"; os.environ["CQSA_BACKWARD"] = "triton"
|
|
198
|
+
host = not on_cuda
|
|
199
|
+
if direction == "bwd":
|
|
200
|
+
out = stream_cqsa_attn(q, k, v, causal=bool(is_causal), scale=scale, verbose=verbose,
|
|
201
|
+
itr=kw.get("itr", "auto"), c=kw.get("c", 7), interest_set=kw.get("interest_set", (0, 1, 3)),
|
|
202
|
+
stream_from_host=bool(kw.get("stream_from_host", host)),
|
|
203
|
+
accumulate_on_gpu=bool(kw.get("accumulate_on_gpu", True)),
|
|
204
|
+
max_parallel=kw.get("max_parallel"))
|
|
205
|
+
else:
|
|
206
|
+
kw.setdefault("stream_from_host", host)
|
|
207
|
+
# leave the fp32 result where the accumulator is; _deliver casts and moves it
|
|
208
|
+
out, _ = stream_cqsa_forward(q, k, v, causal=bool(is_causal), scale=scale, verbose=verbose, out_device="acc", **kw)
|
|
209
|
+
out = _deliver(out, out_device, q.dtype, vb)
|
|
210
|
+
return (out, p) if return_plan else out
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _deliver(out: torch.Tensor, device, dtype, vb: bool) -> torch.Tensor:
|
|
214
|
+
"""Hand the result over in the caller's dtype and on the caller's device.
|
|
215
|
+
|
|
216
|
+
The engines return fp32; the cast needs room for a second copy. When the device
|
|
217
|
+
cannot take it, the cast is done in host memory and the result moved back; if
|
|
218
|
+
even the half-size result does not fit next to what the caller keeps on the
|
|
219
|
+
device, it is returned in host memory with a warning rather than failing after
|
|
220
|
+
the work is done."""
|
|
221
|
+
if out.dtype == dtype and out.device == device:
|
|
222
|
+
return out
|
|
223
|
+
try:
|
|
224
|
+
return out.to(device=device, dtype=dtype)
|
|
225
|
+
except Exception as exc: # noqa: BLE001
|
|
226
|
+
if not _is_oom(exc):
|
|
227
|
+
raise
|
|
228
|
+
host = out.to("cpu") if out.device.type != "cpu" else out
|
|
229
|
+
del out
|
|
230
|
+
torch.cuda.empty_cache()
|
|
231
|
+
host = host.to(dtype)
|
|
232
|
+
if device.type == "cpu":
|
|
233
|
+
return host
|
|
234
|
+
try:
|
|
235
|
+
return host.to(device)
|
|
236
|
+
except Exception as exc: # noqa: BLE001
|
|
237
|
+
if not _is_oom(exc):
|
|
238
|
+
raise
|
|
239
|
+
warnings.warn(f"Stream-CQSA: the result ({host.numel() * host.element_size() / 2**30:.1f} GiB) does not fit on {device} "
|
|
240
|
+
f"next to what is already there; returning it in host memory.", RuntimeWarning, stacklevel=3)
|
|
241
|
+
return host
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _pick_wave(kernel: str, kw: dict, is_causal: bool, q: torch.Tensor, direction: str) -> bool:
|
|
245
|
+
if kernel == "wave":
|
|
246
|
+
return True
|
|
247
|
+
if kernel in ("cuda", "triton"):
|
|
248
|
+
return False
|
|
249
|
+
if kernel != "auto":
|
|
250
|
+
raise ValueError(f"kernel must be 'auto', 'wave', 'cuda' or 'triton' (got {kernel!r})")
|
|
251
|
+
try:
|
|
252
|
+
from .native_wave import native_available
|
|
253
|
+
if not native_available():
|
|
254
|
+
return False
|
|
255
|
+
except Exception:
|
|
256
|
+
return False
|
|
257
|
+
# the wave engine keeps its accumulator (and, in the backward, the fp32 gradients) on the device
|
|
258
|
+
if kw.get("low_memory") or kw.get("accumulate_on_gpu") is False:
|
|
259
|
+
return False
|
|
260
|
+
from .native_wave import native_supports
|
|
261
|
+
return native_supports(q.dtype, int(q.shape[-1])) # depends on the kernel set the extension was built with
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
# dry run
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
def estimate(N: int, B: int = 1, H: int = 8, D: int = 64, dtype=torch.float16, causal: bool = True,
|
|
268
|
+
hardware=None, direction: str = "both", print_table: bool = True) -> dict[str, Any]:
|
|
269
|
+
"""
|
|
270
|
+
What would ``attention`` do for this shape on this machine, and what would it
|
|
271
|
+
cost? Prints (and returns) the monolithic estimate, the chosen decomposition
|
|
272
|
+
and the runner-up configurations, for the forward and the backward.
|
|
273
|
+
"""
|
|
274
|
+
from .autoconfig import plan as _plan, hardware_from_dict, detect_hardware, _cname, GIB
|
|
275
|
+
hw = hardware_from_dict(hardware) if isinstance(hardware, dict) else (hardware or detect_hardware())
|
|
276
|
+
res: dict[str, Any] = {"N": N, "B": B, "H": H, "D": D, "dtype": str(dtype), "causal": causal, "hardware": hw.summary()}
|
|
277
|
+
dirs = ("fwd", "bwd") if direction == "both" else (direction,)
|
|
278
|
+
lines = [f"Stream-CQSA estimate for N={N:,} B={B} H={H} D={D} {str(dtype).replace('torch.', '')} {'causal' if causal else 'non-causal'}",
|
|
279
|
+
f" hardware: {hw.summary()}"]
|
|
280
|
+
itemsize = torch.tensor([], dtype=dtype).element_size()
|
|
281
|
+
for d in dirs:
|
|
282
|
+
p = _plan(N=N, B=B, H=H, D=D, dtype=dtype, causal=causal, hardware=hw, direction=d, allow_distributed=False)
|
|
283
|
+
mono = p.candidates[0]
|
|
284
|
+
feas = sorted([c for c in p.candidates if c["ok"] and c["mode"] != "mono"], key=lambda c: c["time"])[:3]
|
|
285
|
+
r = dict(plan=p.name(), est_time_s=p.est_time_s, est_peak_gib=p.est_peak_gib, est_host_gib=p.est_host_gib,
|
|
286
|
+
mono_fits=bool(mono["ok"]), mono_peak_gib=mono["peak"], mono_time_s=mono["time"], reason=p.reason,
|
|
287
|
+
runners_up=[dict(config=_cname(c), time_s=c["time"], peak_gib=c["peak"], host_gib=c["host"]) for c in feas],
|
|
288
|
+
feasible=any(c["ok"] for c in p.candidates))
|
|
289
|
+
res[d] = r
|
|
290
|
+
label = "forward" if d == "fwd" else "backward (fwd+bwd step)"
|
|
291
|
+
lines.append(f" {label}:")
|
|
292
|
+
lines.append(f" monolithic FlashAttention: {'fits' if mono['ok'] else 'does NOT fit'} ({mono['peak']:.1f} GiB), ~{mono['time']:.2f} s")
|
|
293
|
+
if p.mode == "mono":
|
|
294
|
+
lines.append(f" -> monolithic call")
|
|
295
|
+
elif r["feasible"]:
|
|
296
|
+
lines.append(f" -> Stream-CQSA {p.name()}: ~{p.est_time_s:.1f} s, {p.est_peak_gib:.1f} GiB on the device"
|
|
297
|
+
+ (f", {p.est_host_gib:.0f} GiB host" if p.est_host_gib else ""))
|
|
298
|
+
for c in feas[1:]:
|
|
299
|
+
lines.append(f" also: {_cname(c)} ~{c['time']:.1f} s, {c['peak']:.1f} GiB")
|
|
300
|
+
else:
|
|
301
|
+
floor = (4 + 4) * N * B * H * D / GIB
|
|
302
|
+
lines.append(f" -> nothing fits: the fp32 output + accumulator alone are {floor:.1f} GiB on the device; "
|
|
303
|
+
f"reduce B/H/D, add host memory, or use more devices")
|
|
304
|
+
if print_table:
|
|
305
|
+
print("\n".join(lines), flush=True)
|
|
306
|
+
res["text"] = "\n".join(lines)
|
|
307
|
+
return res
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ---------------------------------------------------------------------------
|
|
311
|
+
# monkeypatch F.scaled_dot_product_attention
|
|
312
|
+
# ---------------------------------------------------------------------------
|
|
313
|
+
_ORIGINAL_SDPA = None
|
|
314
|
+
_PATCH_MIN_TOKENS = 65536
|
|
315
|
+
_TRUE_SDPA = F.scaled_dot_product_attention # captured at import, before any patch
|
|
316
|
+
_INSIDE = [0] # re-entrancy: calls made by the engines go to the original
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _patched_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, enable_gqa=False):
|
|
320
|
+
orig = _ORIGINAL_SDPA or _TRUE_SDPA
|
|
321
|
+
supported = (attn_mask is None and not dropout_p and not enable_gqa and query.dim() == 4
|
|
322
|
+
and query.dtype in (torch.float16, torch.bfloat16) and key.shape == query.shape == value.shape
|
|
323
|
+
and query.shape[-2] >= _PATCH_MIN_TOKENS and _INSIDE[0] == 0)
|
|
324
|
+
if not supported:
|
|
325
|
+
return orig(query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal, scale=scale,
|
|
326
|
+
**({"enable_gqa": enable_gqa} if enable_gqa else {}))
|
|
327
|
+
return attention(query, key, value, is_causal=is_causal, scale=scale)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def patch_sdpa(min_tokens: int = 65536) -> None:
|
|
331
|
+
"""
|
|
332
|
+
Route ``torch.nn.functional.scaled_dot_product_attention`` through
|
|
333
|
+
``stream_cqsa.attention`` for the calls it handles (4-D fp16/bf16, no mask,
|
|
334
|
+
no dropout, no GQA, N >= ``min_tokens``); everything else goes to the original.
|
|
335
|
+
Below the memory boundary the routed call is still the monolithic kernel, so
|
|
336
|
+
the patch changes nothing until a call would have run out of memory.
|
|
337
|
+
"""
|
|
338
|
+
global _ORIGINAL_SDPA, _PATCH_MIN_TOKENS
|
|
339
|
+
_PATCH_MIN_TOKENS = int(min_tokens)
|
|
340
|
+
if _ORIGINAL_SDPA is None:
|
|
341
|
+
_ORIGINAL_SDPA = F.scaled_dot_product_attention
|
|
342
|
+
F.scaled_dot_product_attention = _patched_sdpa
|
|
343
|
+
torch.nn.functional.scaled_dot_product_attention = _patched_sdpa
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def unpatch_sdpa() -> None:
|
|
347
|
+
global _ORIGINAL_SDPA
|
|
348
|
+
if _ORIGINAL_SDPA is not None:
|
|
349
|
+
F.scaled_dot_product_attention = _ORIGINAL_SDPA
|
|
350
|
+
torch.nn.functional.scaled_dot_product_attention = _ORIGINAL_SDPA
|
|
351
|
+
_ORIGINAL_SDPA = None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@contextlib.contextmanager
|
|
355
|
+
def patched_sdpa(min_tokens: int = 65536):
|
|
356
|
+
"""``with stream_cqsa.patched_sdpa(): model(...)``"""
|
|
357
|
+
was = _ORIGINAL_SDPA is not None
|
|
358
|
+
patch_sdpa(min_tokens)
|
|
359
|
+
try:
|
|
360
|
+
yield
|
|
361
|
+
finally:
|
|
362
|
+
if not was:
|
|
363
|
+
unpatch_sdpa()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Sequence, Tuple
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _group_bits_to_dense_mask(
|
|
9
|
+
*,
|
|
10
|
+
group_bits: torch.Tensor,
|
|
11
|
+
device: torch.device,
|
|
12
|
+
) -> torch.Tensor:
|
|
13
|
+
"""
|
|
14
|
+
Build dense local mask [L, L] from per-token int64 group bits.
|
|
15
|
+
True means masked.
|
|
16
|
+
"""
|
|
17
|
+
bits = group_bits.to(device=device, dtype=torch.int64).contiguous()
|
|
18
|
+
bits_row = bits.view(bits.numel(), 1)
|
|
19
|
+
bits_col = bits.view(1, bits.numel())
|
|
20
|
+
return torch.bitwise_and(bits_row, bits_col).ne(0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _group_runs_to_dense_mask(
|
|
24
|
+
*,
|
|
25
|
+
local_size: int,
|
|
26
|
+
group_runs: Sequence[Sequence[Tuple[int, int]]],
|
|
27
|
+
device: torch.device,
|
|
28
|
+
) -> torch.Tensor:
|
|
29
|
+
"""
|
|
30
|
+
Build dense local mask [L, L] where True means masked.
|
|
31
|
+
"""
|
|
32
|
+
mask = torch.zeros((int(local_size), int(local_size)), dtype=torch.bool, device=device)
|
|
33
|
+
for runs in group_runs:
|
|
34
|
+
idx_parts: List[torch.Tensor] = []
|
|
35
|
+
for s, e in runs:
|
|
36
|
+
si, ei = int(s), int(e)
|
|
37
|
+
if ei > si:
|
|
38
|
+
idx_parts.append(torch.arange(si, ei, device=device, dtype=torch.long))
|
|
39
|
+
if len(idx_parts) == 0:
|
|
40
|
+
continue
|
|
41
|
+
idx = idx_parts[0] if len(idx_parts) == 1 else torch.cat(idx_parts, dim=0)
|
|
42
|
+
mask[idx.unsqueeze(1), idx.unsqueeze(0)] = True
|
|
43
|
+
return mask
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def custom_attn(
|
|
47
|
+
*,
|
|
48
|
+
q: torch.Tensor,
|
|
49
|
+
k: torch.Tensor,
|
|
50
|
+
v: torch.Tensor,
|
|
51
|
+
cqs_mask: Dict[str, Any],
|
|
52
|
+
softmax_scale: float,
|
|
53
|
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
54
|
+
"""
|
|
55
|
+
Naive reference subsequence attention plugin.
|
|
56
|
+
|
|
57
|
+
Steps:
|
|
58
|
+
1) R_i = (Q @ K^T) * scale
|
|
59
|
+
2) apply CQS mask M_i on R_i (masked positions -> -inf)
|
|
60
|
+
3) P_i = exp(R_i) (no softmax normalization)
|
|
61
|
+
4) Den_i = row_sum(P_i)
|
|
62
|
+
5) Num_i = P_i @ V
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
- Num_i: [B, L, H, D] float32
|
|
66
|
+
- Den_i: [B, H, L] float32
|
|
67
|
+
"""
|
|
68
|
+
local_size = int(cqs_mask["local_size"])
|
|
69
|
+
group_bits_cpu = cqs_mask.get("group_bits_cpu", None)
|
|
70
|
+
if isinstance(group_bits_cpu, torch.Tensor):
|
|
71
|
+
M_i = _group_bits_to_dense_mask(
|
|
72
|
+
group_bits=group_bits_cpu,
|
|
73
|
+
device=q.device,
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
M_i = _group_runs_to_dense_mask(
|
|
77
|
+
local_size=local_size,
|
|
78
|
+
group_runs=cqs_mask["group_runs"],
|
|
79
|
+
device=q.device,
|
|
80
|
+
) # [L, L], True means masked
|
|
81
|
+
|
|
82
|
+
q_bhld = q.transpose(1, 2).float() # [B, H, L, D]
|
|
83
|
+
k_bhld = k.transpose(1, 2).float() # [B, H, L, D]
|
|
84
|
+
v_bhld = v.transpose(1, 2).float() # [B, H, L, D]
|
|
85
|
+
|
|
86
|
+
R_i = torch.matmul(q_bhld, k_bhld.transpose(-2, -1)) * float(softmax_scale) # [B, H, L, L]
|
|
87
|
+
if bool(M_i.any().item()):
|
|
88
|
+
R_i = R_i.masked_fill(M_i.unsqueeze(0).unsqueeze(0), float("-inf"))
|
|
89
|
+
|
|
90
|
+
P_i = torch.exp(R_i) # [B, H, L, L]
|
|
91
|
+
P_i = torch.nan_to_num(P_i, nan=0.0, posinf=0.0, neginf=0.0)
|
|
92
|
+
|
|
93
|
+
Den_i = P_i.sum(dim=-1) # [B, H, L]
|
|
94
|
+
Num_i = torch.matmul(P_i, v_bhld).transpose(1, 2).contiguous() # [B, L, H, D]
|
|
95
|
+
Num_i = torch.nan_to_num(Num_i, nan=0.0, posinf=0.0, neginf=0.0)
|
|
96
|
+
Den_i = torch.nan_to_num(Den_i, nan=0.0, posinf=0.0, neginf=0.0)
|
|
97
|
+
return Num_i, Den_i
|