colabfold-kernels 0.1.0__tar.gz
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.
- colabfold_kernels-0.1.0/LICENSE +26 -0
- colabfold_kernels-0.1.0/PKG-INFO +34 -0
- colabfold_kernels-0.1.0/README.md +16 -0
- colabfold_kernels-0.1.0/pyproject.toml +28 -0
- colabfold_kernels-0.1.0/setup.cfg +4 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels/__init__.py +9 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels/device.py +29 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels/fused_ops.py +72 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels/tri_flash.py +167 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels/tri_mul.py +213 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels/volta.py +169 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels.egg-info/PKG-INFO +34 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels.egg-info/SOURCES.txt +13 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels.egg-info/dependency_links.txt +1 -0
- colabfold_kernels-0.1.0/src/colabfold_kernels.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © 2026 The ColabFold Development Team
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
26
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: colabfold-kernels
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast fused kernels for ColabFold
|
|
5
|
+
Author-email: Milot Mirdita <milot@mirdita.de>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mirditalab/colabfold-kernels
|
|
8
|
+
Project-URL: Issues, https://github.com/mirditalab/colabfold-kernels/issues
|
|
9
|
+
Keywords: alphafold,colabfold,pallas,triton,jax,attention,bioinformatics
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# colabfold-kernels
|
|
20
|
+
|
|
21
|
+
Fast fused kernels for ColabFold, written in
|
|
22
|
+
[Pallas](https://docs.jax.dev/en/latest/pallas/index.html) with native CUDA-fallback
|
|
23
|
+
for Volta- and Turing-generation GPUs.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import jax.numpy as jnp
|
|
27
|
+
from colabfold_kernels import attention, dispatch
|
|
28
|
+
|
|
29
|
+
kernel = attention(dispatch(), jnp.bfloat16, key_dim=96, value_dim=96, layout="seq")
|
|
30
|
+
if kernel is not None:
|
|
31
|
+
out = kernel(q, k, v, mask_bias, nonbatched_bias, scale)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`attention`, `layer_norm` and `gated_dual_proj` each return `None` if not supported.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# colabfold-kernels
|
|
2
|
+
|
|
3
|
+
Fast fused kernels for ColabFold, written in
|
|
4
|
+
[Pallas](https://docs.jax.dev/en/latest/pallas/index.html) with native CUDA-fallback
|
|
5
|
+
for Volta- and Turing-generation GPUs.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import jax.numpy as jnp
|
|
9
|
+
from colabfold_kernels import attention, dispatch
|
|
10
|
+
|
|
11
|
+
kernel = attention(dispatch(), jnp.bfloat16, key_dim=96, value_dim=96, layout="seq")
|
|
12
|
+
if kernel is not None:
|
|
13
|
+
out = kernel(q, k, v, mask_bias, nonbatched_bias, scale)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`attention`, `layer_norm` and `gated_dual_proj` each return `None` if not supported.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "colabfold-kernels"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Fast fused kernels for ColabFold"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Milot Mirdita", email = "milot@mirdita.de" }]
|
|
13
|
+
keywords = ["alphafold", "colabfold", "pallas", "triton", "jax", "attention", "bioinformatics"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Intended Audience :: Science/Research",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: POSIX :: Linux",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
19
|
+
]
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/mirditalab/colabfold-kernels"
|
|
22
|
+
Issues = "https://github.com/mirditalab/colabfold-kernels/issues"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools]
|
|
25
|
+
package-dir = { "" = "src" }
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Fused Pallas/Triton kernels for ColabFold's AlphaFold2 and AlphaFold3 backends."""
|
|
2
|
+
|
|
3
|
+
from colabfold_kernels.device import compute_capability, dispatch, shared_memory_limit
|
|
4
|
+
from colabfold_kernels.fused_ops import attention, gated_dual_proj, layer_norm
|
|
5
|
+
|
|
6
|
+
__all__ = ["attention", "compute_capability", "dispatch", "gated_dual_proj",
|
|
7
|
+
"layer_norm", "shared_memory_limit", "__version__"]
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""What the GPUs here can run, so a caller need not work it out itself."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def compute_capability():
|
|
5
|
+
"""The NVIDIA compute capability as an integer, e.g. 75, or None."""
|
|
6
|
+
try:
|
|
7
|
+
import jax
|
|
8
|
+
cc = str(jax.devices()[0].compute_capability)
|
|
9
|
+
return int(round(float(cc) * 10)) if "." in cc else int(cc)
|
|
10
|
+
except Exception:
|
|
11
|
+
return None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def shared_memory_limit():
|
|
15
|
+
"""The smallest shared memory budget across the GPUs here, or None."""
|
|
16
|
+
import jax
|
|
17
|
+
limits = [getattr(d, "shared_memory_per_block_optin", 0) for d in jax.local_devices()
|
|
18
|
+
if d.platform == "gpu"]
|
|
19
|
+
limits = [limit for limit in limits if limit]
|
|
20
|
+
return min(limits) if limits else None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def dispatch(backend="auto"):
|
|
24
|
+
"""The mapping the fused_ops selectors read. 'auto' picks what this GPU supports."""
|
|
25
|
+
cc = compute_capability()
|
|
26
|
+
if backend == "auto":
|
|
27
|
+
# XLA gates Pallas/Triton to sm_80+, so older NVIDIA parts take the CUDA kernels
|
|
28
|
+
backend = "cuda_legacy" if cc is not None and cc < 80 else "pallas"
|
|
29
|
+
return {"use_pallas": True, "kernel_backend": backend, "compute_capability": cc}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Pick the fused kernel (volta, Pallas, or None for XLA) for each op."""
|
|
2
|
+
import functools
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _legacy_cc(global_config):
|
|
6
|
+
"""Give the compute capability if the CUDA kernels were asked for, else None."""
|
|
7
|
+
if global_config.get('kernel_backend', 'pallas') != 'cuda_legacy':
|
|
8
|
+
return None
|
|
9
|
+
return global_config.get('compute_capability')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _volta():
|
|
13
|
+
from colabfold_kernels import volta
|
|
14
|
+
return volta
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def attention(global_config, dtype, key_dim, value_dim, layout='heads'):
|
|
18
|
+
"""-> f(q, k, v, mask_bias, nonbatched_bias, scale) or None.
|
|
19
|
+
|
|
20
|
+
layout 'heads' takes q/k/v as [b, h, S, c], 'seq' as [b, S, h, c].
|
|
21
|
+
"""
|
|
22
|
+
import jax.numpy as jnp
|
|
23
|
+
if not global_config.get('use_pallas', False):
|
|
24
|
+
return None
|
|
25
|
+
cc = _legacy_cc(global_config)
|
|
26
|
+
if dtype == jnp.float16 and cc is not None and layout == 'heads':
|
|
27
|
+
va = _volta()
|
|
28
|
+
# The kernels only handle a fixed set of head dims, and need key == value.
|
|
29
|
+
if (va.available(cc) and key_dim == value_dim
|
|
30
|
+
and va.supports(int(key_dim), cc)):
|
|
31
|
+
return functools.partial(va.volta_attention, cc=cc)
|
|
32
|
+
if (global_config.get('kernel_backend', 'pallas') == 'pallas'
|
|
33
|
+
and dtype in (jnp.bfloat16, jnp.float16)):
|
|
34
|
+
from colabfold_kernels.tri_flash import pallas_attention
|
|
35
|
+
if layout == 'seq':
|
|
36
|
+
return functools.partial(pallas_attention, seq_major=True)
|
|
37
|
+
return pallas_attention
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def layer_norm(global_config, dtype):
|
|
42
|
+
"""-> f(x, scale, offset, *, eps) or None. Last-axis, scale+offset only."""
|
|
43
|
+
import jax.numpy as jnp
|
|
44
|
+
if not global_config.get('use_pallas', False):
|
|
45
|
+
return None
|
|
46
|
+
cc = _legacy_cc(global_config)
|
|
47
|
+
if dtype == jnp.float16 and cc is not None:
|
|
48
|
+
va = _volta()
|
|
49
|
+
if va.ops_available(cc):
|
|
50
|
+
return va.volta_layer_norm
|
|
51
|
+
if (global_config.get('kernel_backend', 'pallas') == 'pallas'
|
|
52
|
+
and dtype in (jnp.bfloat16, jnp.float32)):
|
|
53
|
+
from colabfold_kernels.tri_mul import pallas_layer_norm
|
|
54
|
+
return pallas_layer_norm
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def gated_dual_proj(global_config, dtype):
|
|
59
|
+
"""-> f(x, wp, bp, wg, bg, mask, *, split) or None."""
|
|
60
|
+
import jax.numpy as jnp
|
|
61
|
+
if not global_config.get('use_pallas', False):
|
|
62
|
+
return None
|
|
63
|
+
cc = _legacy_cc(global_config)
|
|
64
|
+
if dtype == jnp.float16 and cc is not None:
|
|
65
|
+
va = _volta()
|
|
66
|
+
if va.ops_available(cc):
|
|
67
|
+
return functools.partial(va.volta_gated_dual_proj, cc=cc)
|
|
68
|
+
if (global_config.get('kernel_backend', 'pallas') == 'pallas'
|
|
69
|
+
and dtype in (jnp.bfloat16, jnp.float16)):
|
|
70
|
+
from colabfold_kernels.tri_mul import gated_dual_proj as gdp
|
|
71
|
+
return gdp
|
|
72
|
+
return None
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Pallas/Triton flash attention for the AF2 Evoformer (MSA + triangle).
|
|
2
|
+
|
|
3
|
+
Inference (forward) only, no backward pass
|
|
4
|
+
|
|
5
|
+
Computes, per (batch n, head h):
|
|
6
|
+
softmax_k( scale * Q_q . K_k + bias_{h,q,k} if mask_{n,k} else -inf ) . V_k
|
|
7
|
+
Layout: q/k/v [N, H, S, D]; bias [H, Sq, Sk] (shared across N); kmask [N, Sk] bool.
|
|
8
|
+
"""
|
|
9
|
+
import functools, math
|
|
10
|
+
import numpy as np
|
|
11
|
+
import jax, jax.numpy as jnp
|
|
12
|
+
from jax.experimental import pallas as pl
|
|
13
|
+
from jax.experimental.pallas import triton as plgpu
|
|
14
|
+
|
|
15
|
+
# jnp.dot is stable across jax versions, contrary to pl.dot/plgpu.dot
|
|
16
|
+
_dot = functools.partial(jnp.dot, preferred_element_type=jnp.float32)
|
|
17
|
+
|
|
18
|
+
NEG = -0.7 * float(np.finfo(np.float32).max)
|
|
19
|
+
LOG2E = math.log2(math.e)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _pow2_tile(d):
|
|
23
|
+
"""Triton tiles are powers of two, and its dot needs at least 16 columns."""
|
|
24
|
+
return max(16, 1 << (int(d) - 1).bit_length())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _chunks(d):
|
|
28
|
+
"""d as power-of-two dot widths: narrow dots beat one wider tile of padding."""
|
|
29
|
+
out, off, rem = [], 0, int(d)
|
|
30
|
+
while rem > 0:
|
|
31
|
+
w = 1 << (rem.bit_length() - 1) if rem >= 16 else 16
|
|
32
|
+
out.append((off, w))
|
|
33
|
+
off, rem = off + w, rem - w
|
|
34
|
+
tile = _pow2_tile(d)
|
|
35
|
+
return tuple(out) if off < tile else ((0, tile),)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _kernel(q_ref, k_ref, v_ref, bias_ref, kmask_ref, o_ref, *,
|
|
39
|
+
sm_scale, block_q, block_k, sq, d, chunks):
|
|
40
|
+
# q_ref [block_q, W]; k_ref/v_ref [Sk, W]; bias_ref [block_q, Sk]; kmask_ref [Sk],
|
|
41
|
+
# W being d split into power-of-two dot widths. Sq/Sk are NOT padded to block
|
|
42
|
+
# multiples; boundary blocks are handled with masked loads (offs < Sk) and a
|
|
43
|
+
# masked output store (offs_q < Sq) so no jnp.pad copy of q/k/v is needed.
|
|
44
|
+
sk = k_ref.shape[0]
|
|
45
|
+
# only a last chunk reaching past d needs a lane mask, and those lanes read zero
|
|
46
|
+
cols = [None if off + w <= d else (off + jnp.arange(w)) < d for off, w in chunks]
|
|
47
|
+
qs = [q_ref[:, pl.dslice(off, w)] if c is None else
|
|
48
|
+
plgpu.load(q_ref.at[:, pl.dslice(off, w)], mask=c[None, :], other=0.0)
|
|
49
|
+
for (off, w), c in zip(chunks, cols)]
|
|
50
|
+
m_i = jnp.full(block_q, -float('inf'), jnp.float32)
|
|
51
|
+
l_i = jnp.zeros(block_q, jnp.float32)
|
|
52
|
+
os = [jnp.zeros((block_q, w), jnp.float32) for _, w in chunks]
|
|
53
|
+
|
|
54
|
+
def body(j, carry):
|
|
55
|
+
o_prev, m_prev, l_prev = carry
|
|
56
|
+
start = j * block_k
|
|
57
|
+
kb = (start + jnp.arange(block_k)) < sk # [block_k] in-bounds keys
|
|
58
|
+
sl = pl.dslice(start, block_k)
|
|
59
|
+
qk = None
|
|
60
|
+
for qc, (off, w), c in zip(qs, chunks, cols):
|
|
61
|
+
m = kb[:, None] if c is None else kb[:, None] & c[None, :]
|
|
62
|
+
kc = plgpu.load(k_ref.at[sl, pl.dslice(off, w)], mask=m, other=0.0)
|
|
63
|
+
part = _dot(qc, kc.T) # [block_q, block_k]
|
|
64
|
+
qk = part if qk is None else qk + part
|
|
65
|
+
bias = plgpu.load(bias_ref.at[:, sl], mask=kb[None, :], other=0.0)
|
|
66
|
+
qk = (qk * sm_scale + bias) * LOG2E
|
|
67
|
+
km = plgpu.load(kmask_ref.at[sl], mask=kb, other=False) # OOB keys -> masked
|
|
68
|
+
qk = jnp.where(km[None, :], qk, NEG)
|
|
69
|
+
m_curr = jnp.max(qk, axis=-1)
|
|
70
|
+
m_next = jnp.maximum(m_prev, m_curr)
|
|
71
|
+
corr = jnp.exp2(m_prev - m_next)
|
|
72
|
+
s = jnp.exp2(qk - m_next[:, None])
|
|
73
|
+
l_next = corr * l_prev + s.sum(axis=-1)
|
|
74
|
+
o_next = []
|
|
75
|
+
for oc, (off, w), c in zip(o_prev, chunks, cols):
|
|
76
|
+
m = kb[:, None] if c is None else kb[:, None] & c[None, :]
|
|
77
|
+
vc = plgpu.load(v_ref.at[sl, pl.dslice(off, w)], mask=m, other=0.0)
|
|
78
|
+
o_next.append(corr[:, None] * oc + _dot(s.astype(vc.dtype), vc))
|
|
79
|
+
return o_next, m_next, l_next
|
|
80
|
+
|
|
81
|
+
os, m_i, l_i = jax.lax.fori_loop(0, pl.cdiv(sk, block_k), body, (os, m_i, l_i))
|
|
82
|
+
q_valid = (pl.program_id(0) * block_q + jnp.arange(block_q)) < sq
|
|
83
|
+
for oc, (off, w), c in zip(os, chunks, cols):
|
|
84
|
+
m = q_valid[:, None] if c is None else q_valid[:, None] & c[None, :]
|
|
85
|
+
plgpu.store(o_ref.at[:, pl.dslice(off, w)],
|
|
86
|
+
(oc / l_i[:, None]).astype(o_ref.dtype), mask=m)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@functools.partial(jax.jit, static_argnames=(
|
|
90
|
+
"sm_scale", "block_q", "block_k", "num_warps", "num_stages", "seq_major"))
|
|
91
|
+
def tri_flash(q, k, v, bias, kmask, *, sm_scale, block_q=64, block_k=64,
|
|
92
|
+
num_warps=4, num_stages=2, seq_major=False):
|
|
93
|
+
# q/k/v [N,H,Sq,D], or [N,Sq,H,D] when seq_major; bias [H,Sq,Sk]; kmask [N,Sk]
|
|
94
|
+
# bool. No padding: Sq/Sk need not be block multiples; the kernel masks the
|
|
95
|
+
# partial q-block (output) and the partial k-block (loads), so the big q/k/v
|
|
96
|
+
# tensors are never copied.
|
|
97
|
+
if seq_major:
|
|
98
|
+
N, Sq, H, D = q.shape
|
|
99
|
+
Sk = k.shape[1]
|
|
100
|
+
else:
|
|
101
|
+
N, H, Sq, D = q.shape
|
|
102
|
+
Sk = k.shape[2]
|
|
103
|
+
chunks = _chunks(D)
|
|
104
|
+
dk = chunks[-1][0] + chunks[-1][1] # the tile the chunks span
|
|
105
|
+
# grid (q_block, head, row): row N is innermost so the shared bias[h,q_block,:]
|
|
106
|
+
# stays hot in L2 while sweeping rows (the bias is re-read for every row).
|
|
107
|
+
grid = (pl.cdiv(Sq, block_q), H, N)
|
|
108
|
+
if seq_major:
|
|
109
|
+
bs_qo = pl.BlockSpec((None, block_q, None, dk), lambda i, h, j: (j, i, h, 0))
|
|
110
|
+
bs_kv = pl.BlockSpec((None, Sk, None, dk), lambda i, h, j: (j, 0, h, 0))
|
|
111
|
+
out_shape = jax.ShapeDtypeStruct((N, Sq, H, D), q.dtype)
|
|
112
|
+
else:
|
|
113
|
+
bs_qo = pl.BlockSpec((None, None, block_q, dk), lambda i, h, j: (j, h, i, 0))
|
|
114
|
+
bs_kv = pl.BlockSpec((None, None, Sk, dk), lambda i, h, j: (j, h, 0, 0))
|
|
115
|
+
out_shape = jax.ShapeDtypeStruct((N, H, Sq, D), q.dtype)
|
|
116
|
+
bs_bias = pl.BlockSpec((None, block_q, Sk), lambda i, h, j: (h, i, 0))
|
|
117
|
+
bs_mask = pl.BlockSpec((None, Sk), lambda i, h, j: (j, 0))
|
|
118
|
+
return pl.pallas_call(
|
|
119
|
+
functools.partial(_kernel, sm_scale=sm_scale, block_q=block_q,
|
|
120
|
+
block_k=block_k, sq=Sq, d=D, chunks=chunks),
|
|
121
|
+
grid=grid,
|
|
122
|
+
in_specs=[bs_qo, bs_kv, bs_kv, bs_bias, bs_mask],
|
|
123
|
+
out_specs=bs_qo,
|
|
124
|
+
out_shape=out_shape,
|
|
125
|
+
compiler_params=plgpu.CompilerParams(num_warps=num_warps, num_stages=num_stages),
|
|
126
|
+
name="tri_flash_fwd",
|
|
127
|
+
)(q, k, v, bias, kmask)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def pallas_attention(q, k, v, mask_bias, nonbatched_bias, scale, *, seq_major=False):
|
|
131
|
+
"""AF2 attention via the Pallas flash kernel.
|
|
132
|
+
|
|
133
|
+
q/k/v heads-major [b, h, S, c], or seq-major [b, S, h, c] when seq_major.
|
|
134
|
+
mask_bias additive [b,1,1,S_kv] (~-1e9 = invalid); nonbatched_bias
|
|
135
|
+
[h,S_qo,S_kv] shared, or None.
|
|
136
|
+
Returns q's layout.
|
|
137
|
+
"""
|
|
138
|
+
if seq_major:
|
|
139
|
+
b, sq, h, c = q.shape
|
|
140
|
+
sk = k.shape[1]
|
|
141
|
+
else:
|
|
142
|
+
b, h, sq, c = q.shape
|
|
143
|
+
sk = k.shape[2]
|
|
144
|
+
# The kernel reuses q's channel dim for v (one BlockSpec), so key_dim must equal value_dim
|
|
145
|
+
# True for AF2's attention
|
|
146
|
+
assert q.shape[-1] == v.shape[-1], (
|
|
147
|
+
f'tri_flash needs key_dim == value_dim, got {q.shape[-1]} vs {v.shape[-1]}')
|
|
148
|
+
# mask_bias is 0 or big_neg(dtype): -1e9, or -1e4 in float16. Template pointwise
|
|
149
|
+
# attention shares one row across the batch, which the row grid index cannot index.
|
|
150
|
+
kmask = jnp.broadcast_to(mask_bias[:, 0, 0, :] > -1e3, (b, sk)) # [b, S_kv] bool
|
|
151
|
+
bias = (jnp.zeros((h, sq, sk), q.dtype)
|
|
152
|
+
if nonbatched_bias is None else nonbatched_bias.astype(q.dtype))
|
|
153
|
+
# Key tile matches the head: a wider tile stages smem the kernel never reads.
|
|
154
|
+
block_k = _pow2_tile(min(64, c))
|
|
155
|
+
# a head dim that is not a power of two, or below 16, is tiled wider and masked
|
|
156
|
+
out = tri_flash(q, k, v, bias, kmask, sm_scale=float(scale),
|
|
157
|
+
block_q=64, block_k=block_k, seq_major=seq_major)
|
|
158
|
+
return out.astype(q.dtype)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def ref_attn(q, k, v, bias, kmask, sm_scale):
|
|
162
|
+
logits = jnp.einsum('nhqd,nhkd->nhqk', q, k) * sm_scale + bias[None]
|
|
163
|
+
logits = jnp.where(kmask[:, None, None, :], logits, NEG)
|
|
164
|
+
w = jax.nn.softmax(logits.astype(jnp.float32), axis=-1).astype(v.dtype)
|
|
165
|
+
return jnp.einsum('nhqk,nhkd->nhqd', w, v)
|
|
166
|
+
|
|
167
|
+
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Pallas/Triton fused gated dual projection for AF2 triangle-multiplication.
|
|
2
|
+
|
|
3
|
+
Inference (forward) only, no backward pass
|
|
4
|
+
|
|
5
|
+
Computes the masked, sigmoid-gated dual projection in one kernel so the [M, 2c]
|
|
6
|
+
projection/gate intermediates are never materialized. The triangle matmul itself
|
|
7
|
+
stays a native XLA batched GEMM.
|
|
8
|
+
|
|
9
|
+
out[m, p] = mask[m] * (x[m]·wp[:,p] + bp[p]) * sigmoid(x[m]·wg[:,p] + bg[p])
|
|
10
|
+
where x = layer-normed pair [M=N*N, K=c_z], wp/wg [K, P=2*c_i].
|
|
11
|
+
"""
|
|
12
|
+
import functools
|
|
13
|
+
import jax, jax.numpy as jnp
|
|
14
|
+
from jax.experimental import pallas as pl
|
|
15
|
+
from jax.experimental.pallas import triton as plgpu
|
|
16
|
+
|
|
17
|
+
# jnp.dot is stable across jax versions, contrary to pl.dot/plgpu.dot
|
|
18
|
+
_dot = functools.partial(jnp.dot, preferred_element_type=jnp.float32)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _gdp_kernel(x_ref, wp_ref, bp_ref, wg_ref, bg_ref, mask_ref, o_ref, *,
|
|
22
|
+
activation=jax.nn.sigmoid):
|
|
23
|
+
x = x_ref[...] # [BM, K] bf16
|
|
24
|
+
proj = _dot(x, wp_ref[...]) + bp_ref[...][None, :].astype(jnp.float32)
|
|
25
|
+
gate = _dot(x, wg_ref[...]) + bg_ref[...][None, :].astype(jnp.float32)
|
|
26
|
+
o = mask_ref[...][:, None].astype(jnp.float32) * proj * activation(gate)
|
|
27
|
+
o_ref[...] = o.astype(o_ref.dtype)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _half(x, wp, bp, wg, bg, mask):
|
|
31
|
+
proj = _dot(x, wp) + bp[None, :].astype(jnp.float32)
|
|
32
|
+
gate = _dot(x, wg) + bg[None, :].astype(jnp.float32)
|
|
33
|
+
return mask[:, None].astype(jnp.float32) * proj * jax.nn.sigmoid(gate)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _gdp_kernel_split_cm(x_ref, wpl_ref, bpl_ref, wgl_ref, bgl_ref,
|
|
37
|
+
wpr_ref, bpr_ref, wgr_ref, bgr_ref, mask_ref,
|
|
38
|
+
ol_ref, or_ref):
|
|
39
|
+
# _gdp_kernel_split, stored channel-major [ci, BM] for the triangle einsum.
|
|
40
|
+
x = x_ref[...]
|
|
41
|
+
m = mask_ref[...]
|
|
42
|
+
ol_ref[...] = _half(x, wpl_ref[...], bpl_ref[...], wgl_ref[...], bgl_ref[...],
|
|
43
|
+
m).T.astype(ol_ref.dtype)
|
|
44
|
+
or_ref[...] = _half(x, wpr_ref[...], bpr_ref[...], wgr_ref[...], bgr_ref[...],
|
|
45
|
+
m).T.astype(or_ref.dtype)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _gdp_kernel_split(x_ref, wpl_ref, bpl_ref, wgl_ref, bgl_ref,
|
|
49
|
+
wpr_ref, bpr_ref, wgr_ref, bgr_ref, mask_ref,
|
|
50
|
+
ol_ref, or_ref):
|
|
51
|
+
# Computes the left [BM, ci] and right [BM, ci] gated projections as two
|
|
52
|
+
# separate dots (weights pre-split on the host) and writes them to two
|
|
53
|
+
# contiguous outputs -- so the triangle einsum consumes them directly with no
|
|
54
|
+
# post-kernel strided slice/copy of the combined [N,N,2ci] tensor.
|
|
55
|
+
x = x_ref[...]
|
|
56
|
+
m = mask_ref[...]
|
|
57
|
+
ol_ref[...] = _half(x, wpl_ref[...], bpl_ref[...], wgl_ref[...], bgl_ref[...],
|
|
58
|
+
m).astype(ol_ref.dtype)
|
|
59
|
+
or_ref[...] = _half(x, wpr_ref[...], bpr_ref[...], wgr_ref[...], bgr_ref[...],
|
|
60
|
+
m).astype(or_ref.dtype)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@functools.partial(jax.jit,
|
|
64
|
+
static_argnames=("block_m", "split", "channel_major", "activation"))
|
|
65
|
+
def gated_dual_proj(x, wp, bp, wg, bg, mask, *, block_m=64, split=False,
|
|
66
|
+
channel_major=False, activation=jax.nn.sigmoid):
|
|
67
|
+
# x [M,K]; wp/wg [K,P]; bp/bg [P]; mask [M].
|
|
68
|
+
# split=False -> gated [M,P]; split=True -> (left [M,P/2], right [M,P/2]),
|
|
69
|
+
# the two halves written contiguously so the caller needs no slice.
|
|
70
|
+
M, K = x.shape
|
|
71
|
+
P = wp.shape[1]
|
|
72
|
+
Mp = -(-M // block_m) * block_m
|
|
73
|
+
x = jnp.pad(x, [(0, Mp - M), (0, 0)])
|
|
74
|
+
mask = jnp.pad(mask, [(0, Mp - M)])
|
|
75
|
+
in_specs = [
|
|
76
|
+
pl.BlockSpec((block_m, K), lambda i: (i, 0)),
|
|
77
|
+
pl.BlockSpec((K, P), lambda i: (0, 0)),
|
|
78
|
+
pl.BlockSpec((P,), lambda i: (0,)),
|
|
79
|
+
pl.BlockSpec((K, P), lambda i: (0, 0)),
|
|
80
|
+
pl.BlockSpec((P,), lambda i: (0,)),
|
|
81
|
+
pl.BlockSpec((block_m,), lambda i: (i,)),
|
|
82
|
+
]
|
|
83
|
+
cp = plgpu.CompilerParams(num_warps=4, num_stages=1)
|
|
84
|
+
if split:
|
|
85
|
+
ci = P // 2
|
|
86
|
+
wpl, wpr = wp[:, :ci], wp[:, ci:]
|
|
87
|
+
wgl, wgr = wg[:, :ci], wg[:, ci:]
|
|
88
|
+
bpl, bpr = bp[:ci], bp[ci:]
|
|
89
|
+
bgl, bgr = bg[:ci], bg[ci:]
|
|
90
|
+
wspec = pl.BlockSpec((K, ci), lambda i: (0, 0))
|
|
91
|
+
bspec = pl.BlockSpec((ci,), lambda i: (0,))
|
|
92
|
+
split_in = [in_specs[0], wspec, bspec, wspec, bspec, wspec, bspec,
|
|
93
|
+
wspec, bspec, in_specs[5]]
|
|
94
|
+
if channel_major:
|
|
95
|
+
bs = pl.BlockSpec((ci, block_m), lambda i: (0, i))
|
|
96
|
+
left, right = pl.pallas_call(
|
|
97
|
+
_gdp_kernel_split_cm,
|
|
98
|
+
grid=(Mp // block_m,), in_specs=split_in, out_specs=[bs, bs],
|
|
99
|
+
out_shape=[jax.ShapeDtypeStruct((ci, Mp), x.dtype),
|
|
100
|
+
jax.ShapeDtypeStruct((ci, Mp), x.dtype)],
|
|
101
|
+
compiler_params=cp, name="gated_dual_proj_split_cm",
|
|
102
|
+
)(x, wpl, bpl, wgl, bgl, wpr, bpr, wgr, bgr, mask)
|
|
103
|
+
return left[:, :M], right[:, :M]
|
|
104
|
+
bs = pl.BlockSpec((block_m, ci), lambda i: (i, 0))
|
|
105
|
+
left, right = pl.pallas_call(
|
|
106
|
+
_gdp_kernel_split,
|
|
107
|
+
grid=(Mp // block_m,), in_specs=split_in, out_specs=[bs, bs],
|
|
108
|
+
out_shape=[jax.ShapeDtypeStruct((Mp, ci), x.dtype),
|
|
109
|
+
jax.ShapeDtypeStruct((Mp, ci), x.dtype)],
|
|
110
|
+
compiler_params=cp, name="gated_dual_proj_split",
|
|
111
|
+
)(x, wpl, bpl, wgl, bgl, wpr, bpr, wgr, bgr, mask)
|
|
112
|
+
return left[:M], right[:M]
|
|
113
|
+
out = pl.pallas_call(
|
|
114
|
+
functools.partial(_gdp_kernel, activation=activation),
|
|
115
|
+
grid=(Mp // block_m,), in_specs=in_specs,
|
|
116
|
+
out_specs=pl.BlockSpec((block_m, P), lambda i: (i, 0)),
|
|
117
|
+
out_shape=jax.ShapeDtypeStruct((Mp, P), x.dtype),
|
|
118
|
+
compiler_params=cp, name="gated_dual_proj",
|
|
119
|
+
)(x, wp, bp, wg, bg, mask)
|
|
120
|
+
return out[:M]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def ref_gdp(x, wp, bp, wg, bg, mask):
|
|
124
|
+
proj = x @ wp + bp
|
|
125
|
+
gate = x @ wg + bg
|
|
126
|
+
return (mask[:, None] * proj * jax.nn.sigmoid(gate))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Fused LayerNorm: bf16 in/out, fp32 internal. One kernel replaces the
|
|
130
|
+
# bf16->fp32 upcast + layernorm + fp32->bf16 downcast (kills the convert ops
|
|
131
|
+
# and never materializes the fp32 tensor), while keeping fp32 accuracy.
|
|
132
|
+
def _ln_kernel(x_ref, scale_ref, offset_ref, o_ref, *, eps, m, c, block_m):
|
|
133
|
+
bc = x_ref.shape[-1]
|
|
134
|
+
rows = (pl.program_id(0) * block_m + jnp.arange(block_m)) < m # [block_m]
|
|
135
|
+
if bc == c:
|
|
136
|
+
x = plgpu.load(x_ref, mask=rows[:, None], other=0.0).astype(jnp.float32)
|
|
137
|
+
mean = jnp.mean(x, axis=-1, keepdims=True)
|
|
138
|
+
d = x - mean
|
|
139
|
+
var = jnp.mean(d * d, axis=-1, keepdims=True)
|
|
140
|
+
scale, offset = scale_ref[...], offset_ref[...]
|
|
141
|
+
valid = rows[:, None]
|
|
142
|
+
else:
|
|
143
|
+
# c is not a power of two: tile to the next one, mask the lanes past c
|
|
144
|
+
cols = jnp.arange(bc) < c
|
|
145
|
+
valid = rows[:, None] & cols[None, :]
|
|
146
|
+
x = plgpu.load(x_ref, mask=valid, other=0.0).astype(jnp.float32)
|
|
147
|
+
mean = jnp.sum(x, axis=-1, keepdims=True) / c
|
|
148
|
+
d = jnp.where(cols[None, :], x - mean, 0.0)
|
|
149
|
+
var = jnp.sum(d * d, axis=-1, keepdims=True) / c
|
|
150
|
+
scale = plgpu.load(scale_ref, mask=cols, other=0.0)
|
|
151
|
+
offset = plgpu.load(offset_ref, mask=cols, other=0.0)
|
|
152
|
+
y = d * jax.lax.rsqrt(var + eps)
|
|
153
|
+
y = y * scale[None, :].astype(jnp.float32) + offset[None, :].astype(jnp.float32)
|
|
154
|
+
plgpu.store(o_ref, y.astype(o_ref.dtype), mask=valid)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Triton keeps the tile in registers, so a wider one only spills and compiles slower
|
|
158
|
+
_TILE_BYTES = 32 * 1024
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@functools.partial(jax.jit, static_argnames=("eps", "block_m"))
|
|
162
|
+
def pallas_layer_norm(x, scale, offset, *, eps=1e-5, block_m=64):
|
|
163
|
+
# x [..., c]; normalize over last axis; scale/offset [c]. Returns x's shape/dtype.
|
|
164
|
+
c = x.shape[-1]
|
|
165
|
+
bc = max(16, 1 << (c - 1).bit_length()) # Triton tiles are powers of two
|
|
166
|
+
xf = x.reshape(-1, c)
|
|
167
|
+
m = xf.shape[0]
|
|
168
|
+
rows = max(1, _TILE_BYTES // (bc * x.dtype.itemsize))
|
|
169
|
+
block_m = min(block_m, 1 << (rows.bit_length() - 1))
|
|
170
|
+
out = pl.pallas_call(
|
|
171
|
+
functools.partial(_ln_kernel, eps=eps, m=m, c=c, block_m=block_m),
|
|
172
|
+
grid=(pl.cdiv(m, block_m),),
|
|
173
|
+
in_specs=[pl.BlockSpec((block_m, bc), lambda i: (i, 0)),
|
|
174
|
+
pl.BlockSpec((bc,), lambda i: (0,)),
|
|
175
|
+
pl.BlockSpec((bc,), lambda i: (0,))],
|
|
176
|
+
out_specs=pl.BlockSpec((block_m, bc), lambda i: (i, 0)),
|
|
177
|
+
out_shape=jax.ShapeDtypeStruct((m, c), x.dtype),
|
|
178
|
+
compiler_params=plgpu.CompilerParams(num_warps=4, num_stages=2),
|
|
179
|
+
name="pallas_layer_norm",
|
|
180
|
+
)(xf, scale, offset)
|
|
181
|
+
return out.reshape(x.shape)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def ref_layer_norm(x, scale, offset, eps=1e-5):
|
|
185
|
+
xf = x.astype(jnp.float32)
|
|
186
|
+
mean = jnp.mean(xf, -1, keepdims=True)
|
|
187
|
+
var = jnp.mean((xf - mean) ** 2, -1, keepdims=True)
|
|
188
|
+
y = (xf - mean) * jax.lax.rsqrt(var + eps) * scale + offset
|
|
189
|
+
return y.astype(x.dtype)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
import time
|
|
194
|
+
key = jax.random.PRNGKey(0)
|
|
195
|
+
M, K, P = 1000 * 1000, 128, 256
|
|
196
|
+
x = (jax.random.normal(key, (M, K)) * 0.3).astype(jnp.bfloat16)
|
|
197
|
+
wp = (jax.random.normal(key, (K, P)) * 0.1).astype(jnp.bfloat16)
|
|
198
|
+
wg = (jax.random.normal(key, (K, P)) * 0.1).astype(jnp.bfloat16)
|
|
199
|
+
bp = (jax.random.normal(key, (P,)) * 0.1).astype(jnp.bfloat16)
|
|
200
|
+
bg = (jax.random.normal(key, (P,)) * 0.1).astype(jnp.bfloat16)
|
|
201
|
+
mask = (jax.random.uniform(key, (M,)) > 0.1).astype(jnp.bfloat16)
|
|
202
|
+
o = gated_dual_proj(x, wp, bp, wg, bg, mask)
|
|
203
|
+
r = ref_gdp(x.astype(jnp.float32), wp.astype(jnp.float32), bp.astype(jnp.float32),
|
|
204
|
+
wg.astype(jnp.float32), bg.astype(jnp.float32), mask.astype(jnp.float32))
|
|
205
|
+
err = float(jnp.max(jnp.abs(o.astype(jnp.float32) - r)))
|
|
206
|
+
print(f"gated_dual_proj bf16 err={err:.3e}")
|
|
207
|
+
f = jax.jit(gated_dual_proj)
|
|
208
|
+
o = f(x, wp, bp, wg, bg, mask); o.block_until_ready()
|
|
209
|
+
t = time.time()
|
|
210
|
+
for _ in range(50): o = f(x, wp, bp, wg, bg, mask)
|
|
211
|
+
o.block_until_ready()
|
|
212
|
+
print(f"per-call {(time.time()-t)/50*1000:.2f} ms")
|
|
213
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""sm_70/75 CUDA kernels (colabfold-legacy-kernels) used instead of Pallas."""
|
|
2
|
+
import ctypes
|
|
3
|
+
import functools
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import jax
|
|
7
|
+
import jax.numpy as jnp
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
# Head dims each kernel instantiates: mma.sync (sm_75+) and wmma (sm_70).
|
|
11
|
+
_HEAD_DIMS_MMA = (8, 16, 32, 64)
|
|
12
|
+
_HEAD_DIMS_WMMA = (8, 16, 32, 64)
|
|
13
|
+
|
|
14
|
+
_LOADED = {}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _package_path(kernel, cc):
|
|
18
|
+
"""Give the library path from colabfold_legacy_kernels, or None."""
|
|
19
|
+
try:
|
|
20
|
+
import colabfold_legacy_kernels as clk
|
|
21
|
+
except ImportError:
|
|
22
|
+
return None
|
|
23
|
+
try:
|
|
24
|
+
return clk.library_path(kernel, cc)
|
|
25
|
+
except (FileNotFoundError, KeyError):
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load(kernel, cc, symbols):
|
|
30
|
+
"""dlopen the library for one kernel and register its FFI targets."""
|
|
31
|
+
key = (kernel, cc, tuple(symbols))
|
|
32
|
+
if key in _LOADED:
|
|
33
|
+
return _LOADED[key]
|
|
34
|
+
path = _package_path(kernel, cc)
|
|
35
|
+
if not path or not os.path.exists(path):
|
|
36
|
+
_LOADED[key] = False
|
|
37
|
+
return False
|
|
38
|
+
lib = ctypes.cdll.LoadLibrary(path)
|
|
39
|
+
for sym in symbols:
|
|
40
|
+
jax.ffi.register_ffi_target(
|
|
41
|
+
sym, jax.ffi.pycapsule(getattr(lib, sym)), platform="CUDA")
|
|
42
|
+
_LOADED[key] = True
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _attn_symbol(cc):
|
|
47
|
+
"""sm_75+ gets the CUTLASS kernel; sm_70 the wmma one."""
|
|
48
|
+
return "VoltaMma" if cc >= 75 else "VoltaWmma"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def available(cc):
|
|
52
|
+
"""True if the attention library for this device loads."""
|
|
53
|
+
return _load("attention", cc, (_attn_symbol(cc),))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _native_dims(cc):
|
|
57
|
+
return _HEAD_DIMS_MMA if cc >= 75 else _HEAD_DIMS_WMMA
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _pad_target(head_dim, cc):
|
|
61
|
+
"""Smallest supported head dim >= head_dim, or None."""
|
|
62
|
+
for d in sorted(_native_dims(cc)):
|
|
63
|
+
if d >= head_dim:
|
|
64
|
+
return d
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def supports(head_dim, cc):
|
|
69
|
+
if head_dim in _native_dims(cc):
|
|
70
|
+
return True
|
|
71
|
+
# wmma: zero-pad to the next supported head dim (exact, beats XLA on V100).
|
|
72
|
+
if cc < 75:
|
|
73
|
+
return _pad_target(head_dim, cc) is not None
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def ops_available(cc):
|
|
78
|
+
"""LayerNorm is Volta-capable; the CUTLASS gdp needs sm_75+."""
|
|
79
|
+
syms = ("VoltaLayerNorm", "VoltaGdp") if cc >= 75 else ("VoltaLayerNorm",)
|
|
80
|
+
ok = _load("layer_norm", cc, syms)
|
|
81
|
+
if ok and cc < 75:
|
|
82
|
+
ok = _load("gated_dual_proj", cc, ("VoltaGdpWmma",))
|
|
83
|
+
return ok
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@functools.partial(jax.jit, static_argnames=("sym", "sm_scale", "block_q", "block_k"))
|
|
87
|
+
def _attn_call(q, k, v, bias, kmask, *, sym, sm_scale, block_q, block_k):
|
|
88
|
+
n, h, sq, d = q.shape
|
|
89
|
+
# sequential: the template stack vmaps over templates, and the handlers take a fixed rank.
|
|
90
|
+
return jax.ffi.ffi_call(sym, jax.ShapeDtypeStruct((n, h, sq, d), jnp.float16),
|
|
91
|
+
vmap_method="sequential")(
|
|
92
|
+
q, k, v, bias, kmask,
|
|
93
|
+
scale=np.float32(sm_scale),
|
|
94
|
+
block_q=np.int64(block_q), block_k=np.int64(block_k))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def volta_attention(q, k, v, mask_bias, nonbatched_bias, scale, cc,
|
|
98
|
+
block_q=64, block_k=32):
|
|
99
|
+
"""Drop-in for tri_flash.pallas_attention. q/k/v [b,h,S,c] float16."""
|
|
100
|
+
in_dtype = q.dtype
|
|
101
|
+
b, h, sq, c = q.shape
|
|
102
|
+
sk = k.shape[2]
|
|
103
|
+
assert c == v.shape[-1], "kernel needs key_dim == value_dim"
|
|
104
|
+
pad_to = None
|
|
105
|
+
if c not in _native_dims(cc):
|
|
106
|
+
pad_to = _pad_target(c, cc)
|
|
107
|
+
assert pad_to is not None, f"unsupported head_dim {c}"
|
|
108
|
+
pad = [(0, 0)] * 3 + [(0, pad_to - c)]
|
|
109
|
+
q, k, v = (jnp.pad(t.astype(jnp.float16), pad) for t in (q, k, v))
|
|
110
|
+
if cc >= 75 and (pad_to or c) == 64 and (block_q, block_k) == (64, 32):
|
|
111
|
+
block_k = 64 # the mma kernel has no (64, 64, 32) instantiation
|
|
112
|
+
# Template pointwise attention shares one mask row across the batch; the kernel
|
|
113
|
+
# indexes the mask per row, so broadcast instead of reading past its end.
|
|
114
|
+
kmask = jnp.broadcast_to(mask_bias[:, 0, 0, :] > -1e3, (b, sk)).astype(jnp.uint8)
|
|
115
|
+
bias = (jnp.zeros((h, sq, sk), jnp.float16) if nonbatched_bias is None
|
|
116
|
+
else nonbatched_bias.astype(jnp.float16))
|
|
117
|
+
out = _attn_call(q.astype(jnp.float16), k.astype(jnp.float16),
|
|
118
|
+
v.astype(jnp.float16), bias, kmask,
|
|
119
|
+
sym=_attn_symbol(cc), sm_scale=float(scale),
|
|
120
|
+
block_q=block_q, block_k=block_k)
|
|
121
|
+
if pad_to is not None:
|
|
122
|
+
out = out[..., :c]
|
|
123
|
+
return out.astype(in_dtype)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@functools.partial(jax.jit, static_argnames=("eps",))
|
|
127
|
+
def _ln_call(x, scale, offset, *, eps):
|
|
128
|
+
m, c = x.shape
|
|
129
|
+
return jax.ffi.ffi_call(
|
|
130
|
+
"VoltaLayerNorm", jax.ShapeDtypeStruct((m, c), jnp.float16),
|
|
131
|
+
vmap_method="sequential")(
|
|
132
|
+
x, scale, offset, eps=np.float32(eps))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def volta_layer_norm(x, scale, offset, *, eps=1e-5):
|
|
136
|
+
"""Drop-in for tri_mul.pallas_layer_norm. Normalises the last axis."""
|
|
137
|
+
c = x.shape[-1]
|
|
138
|
+
out = _ln_call(x.reshape(-1, c).astype(jnp.float16),
|
|
139
|
+
scale.astype(jnp.float32), offset.astype(jnp.float32), eps=eps)
|
|
140
|
+
return out.reshape(x.shape).astype(x.dtype)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@functools.partial(jax.jit, static_argnames=("sym",))
|
|
144
|
+
def _gdp_call(x, wp, bp, wg, bg, mask, *, sym):
|
|
145
|
+
m = x.shape[0]
|
|
146
|
+
n = wp.shape[1]
|
|
147
|
+
return jax.ffi.ffi_call(sym, jax.ShapeDtypeStruct((m, n), jnp.float16),
|
|
148
|
+
vmap_method="sequential")(
|
|
149
|
+
x, wp, bp, wg, bg, mask)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def volta_gated_dual_proj(x, wp, bp, wg, bg, mask, cc, *, split=True,
|
|
153
|
+
channel_major=False):
|
|
154
|
+
"""Drop-in for tri_mul.gated_dual_proj. x [M,K]; wp/wg [K,P]; mask [M]."""
|
|
155
|
+
sym = "VoltaGdp" if cc >= 75 else "VoltaGdpWmma"
|
|
156
|
+
f16 = lambda t: t.astype(jnp.float16)
|
|
157
|
+
if not split:
|
|
158
|
+
return _gdp_call(f16(x), f16(wp), f16(bp), f16(wg), f16(bg),
|
|
159
|
+
f16(mask), sym=sym).astype(x.dtype)
|
|
160
|
+
ci = wp.shape[1] // 2
|
|
161
|
+
halves = []
|
|
162
|
+
for lo, hi in ((0, ci), (ci, 2 * ci)):
|
|
163
|
+
halves.append(_gdp_call(f16(x), f16(wp[:, lo:hi]), f16(bp[lo:hi]),
|
|
164
|
+
f16(wg[:, lo:hi]), f16(bg[lo:hi]), f16(mask),
|
|
165
|
+
sym=sym).astype(x.dtype))
|
|
166
|
+
if channel_major:
|
|
167
|
+
# XLA would transpose row-major halves for the einsum anyway.
|
|
168
|
+
halves = [h.T for h in halves]
|
|
169
|
+
return halves[0], halves[1]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: colabfold-kernels
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast fused kernels for ColabFold
|
|
5
|
+
Author-email: Milot Mirdita <milot@mirdita.de>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mirditalab/colabfold-kernels
|
|
8
|
+
Project-URL: Issues, https://github.com/mirditalab/colabfold-kernels/issues
|
|
9
|
+
Keywords: alphafold,colabfold,pallas,triton,jax,attention,bioinformatics
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# colabfold-kernels
|
|
20
|
+
|
|
21
|
+
Fast fused kernels for ColabFold, written in
|
|
22
|
+
[Pallas](https://docs.jax.dev/en/latest/pallas/index.html) with native CUDA-fallback
|
|
23
|
+
for Volta- and Turing-generation GPUs.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import jax.numpy as jnp
|
|
27
|
+
from colabfold_kernels import attention, dispatch
|
|
28
|
+
|
|
29
|
+
kernel = attention(dispatch(), jnp.bfloat16, key_dim=96, value_dim=96, layout="seq")
|
|
30
|
+
if kernel is not None:
|
|
31
|
+
out = kernel(q, k, v, mask_bias, nonbatched_bias, scale)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`attention`, `layer_norm` and `gated_dual_proj` each return `None` if not supported.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/colabfold_kernels/__init__.py
|
|
5
|
+
src/colabfold_kernels/device.py
|
|
6
|
+
src/colabfold_kernels/fused_ops.py
|
|
7
|
+
src/colabfold_kernels/tri_flash.py
|
|
8
|
+
src/colabfold_kernels/tri_mul.py
|
|
9
|
+
src/colabfold_kernels/volta.py
|
|
10
|
+
src/colabfold_kernels.egg-info/PKG-INFO
|
|
11
|
+
src/colabfold_kernels.egg-info/SOURCES.txt
|
|
12
|
+
src/colabfold_kernels.egg-info/dependency_links.txt
|
|
13
|
+
src/colabfold_kernels.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
colabfold_kernels
|