trnblas 0.2.0__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.
- trnblas/__init__.py +35 -0
- trnblas/level1.py +45 -0
- trnblas/level2.py +86 -0
- trnblas/level3.py +203 -0
- trnblas/nki/__init__.py +5 -0
- trnblas/nki/dispatch.py +121 -0
- trnblas-0.2.0.dist-info/METADATA +139 -0
- trnblas-0.2.0.dist-info/RECORD +11 -0
- trnblas-0.2.0.dist-info/WHEEL +5 -0
- trnblas-0.2.0.dist-info/licenses/LICENSE +17 -0
- trnblas-0.2.0.dist-info/top_level.txt +1 -0
trnblas/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
trnblas — BLAS operations for AWS Trainium via NKI.
|
|
3
|
+
|
|
4
|
+
Provides Level 1 (vector), Level 2 (matrix-vector), and Level 3 (matrix-matrix)
|
|
5
|
+
BLAS operations with NKI kernel acceleration on Trainium hardware.
|
|
6
|
+
Part of the trn-* scientific computing suite by Playground Logic.
|
|
7
|
+
|
|
8
|
+
Target workloads: DF-MP2 tensor contractions, Fock matrix builds,
|
|
9
|
+
Cholesky-based density fitting, and general scientific linear algebra.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.2.0"
|
|
13
|
+
|
|
14
|
+
# Level 1 — Vector operations
|
|
15
|
+
from .level1 import axpy, dot, nrm2, scal, asum, iamax
|
|
16
|
+
|
|
17
|
+
# Level 2 — Matrix-vector operations
|
|
18
|
+
from .level2 import gemv, symv, trmv, ger
|
|
19
|
+
|
|
20
|
+
# Level 3 — Matrix-matrix operations
|
|
21
|
+
from .level3 import gemm, batched_gemm, symm, syrk, trsm, trmm
|
|
22
|
+
|
|
23
|
+
# Backend control
|
|
24
|
+
from .nki import HAS_NKI, set_backend, get_backend
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Level 1
|
|
28
|
+
"axpy", "dot", "nrm2", "scal", "asum", "iamax",
|
|
29
|
+
# Level 2
|
|
30
|
+
"gemv", "symv", "trmv", "ger",
|
|
31
|
+
# Level 3
|
|
32
|
+
"gemm", "batched_gemm", "symm", "syrk", "trsm", "trmm",
|
|
33
|
+
# Backend
|
|
34
|
+
"HAS_NKI", "set_backend", "get_backend",
|
|
35
|
+
]
|
trnblas/level1.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BLAS Level 1 — Vector operations.
|
|
3
|
+
|
|
4
|
+
axpy, dot, nrm2, scal, asum, iamax
|
|
5
|
+
All operate on torch tensors with NKI dispatch for Trainium acceleration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
import torch
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def axpy(alpha: float, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
|
|
16
|
+
"""y = alpha * x + y"""
|
|
17
|
+
return alpha * x + y
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def dot(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
|
|
21
|
+
"""Dot product: x^T y
|
|
22
|
+
|
|
23
|
+
Returns scalar tensor.
|
|
24
|
+
"""
|
|
25
|
+
return torch.dot(x.reshape(-1), y.reshape(-1))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def nrm2(x: torch.Tensor) -> torch.Tensor:
|
|
29
|
+
"""Euclidean norm: ||x||_2"""
|
|
30
|
+
return torch.linalg.norm(x.reshape(-1))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def scal(alpha: float, x: torch.Tensor) -> torch.Tensor:
|
|
34
|
+
"""x = alpha * x"""
|
|
35
|
+
return alpha * x
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def asum(x: torch.Tensor) -> torch.Tensor:
|
|
39
|
+
"""Sum of absolute values: sum(|x_i|)"""
|
|
40
|
+
return torch.abs(x).sum()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def iamax(x: torch.Tensor) -> int:
|
|
44
|
+
"""Index of element with maximum absolute value."""
|
|
45
|
+
return torch.argmax(torch.abs(x.reshape(-1))).item()
|
trnblas/level2.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BLAS Level 2 — Matrix-vector operations.
|
|
3
|
+
|
|
4
|
+
gemv, symv, trmv, ger
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def gemv(
|
|
14
|
+
alpha: float,
|
|
15
|
+
A: torch.Tensor,
|
|
16
|
+
x: torch.Tensor,
|
|
17
|
+
beta: float = 0.0,
|
|
18
|
+
y: Optional[torch.Tensor] = None,
|
|
19
|
+
trans: bool = False,
|
|
20
|
+
) -> torch.Tensor:
|
|
21
|
+
"""General matrix-vector multiply: y = alpha * op(A) * x + beta * y
|
|
22
|
+
|
|
23
|
+
op(A) = A if trans=False, A^T if trans=True.
|
|
24
|
+
"""
|
|
25
|
+
mat = A.T if trans else A
|
|
26
|
+
result = alpha * torch.mv(mat, x)
|
|
27
|
+
if y is not None and beta != 0.0:
|
|
28
|
+
result = result + beta * y
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def symv(
|
|
33
|
+
alpha: float,
|
|
34
|
+
A: torch.Tensor,
|
|
35
|
+
x: torch.Tensor,
|
|
36
|
+
beta: float = 0.0,
|
|
37
|
+
y: Optional[torch.Tensor] = None,
|
|
38
|
+
uplo: str = "upper",
|
|
39
|
+
) -> torch.Tensor:
|
|
40
|
+
"""Symmetric matrix-vector multiply: y = alpha * A * x + beta * y
|
|
41
|
+
|
|
42
|
+
A is symmetric; only the upper or lower triangle is referenced.
|
|
43
|
+
"""
|
|
44
|
+
if uplo == "upper":
|
|
45
|
+
sym = torch.triu(A) + torch.triu(A, diagonal=1).T
|
|
46
|
+
else:
|
|
47
|
+
sym = torch.tril(A) + torch.tril(A, diagonal=-1).T
|
|
48
|
+
result = alpha * torch.mv(sym, x)
|
|
49
|
+
if y is not None and beta != 0.0:
|
|
50
|
+
result = result + beta * y
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def trmv(
|
|
55
|
+
A: torch.Tensor,
|
|
56
|
+
x: torch.Tensor,
|
|
57
|
+
uplo: str = "upper",
|
|
58
|
+
trans: bool = False,
|
|
59
|
+
diag: str = "nonunit",
|
|
60
|
+
) -> torch.Tensor:
|
|
61
|
+
"""Triangular matrix-vector multiply: x = op(A) * x
|
|
62
|
+
|
|
63
|
+
uplo: 'upper' or 'lower'
|
|
64
|
+
diag: 'unit' or 'nonunit'
|
|
65
|
+
"""
|
|
66
|
+
if uplo == "upper":
|
|
67
|
+
tri = torch.triu(A)
|
|
68
|
+
else:
|
|
69
|
+
tri = torch.tril(A)
|
|
70
|
+
if diag == "unit":
|
|
71
|
+
tri = tri - torch.diag(torch.diag(tri)) + torch.eye(A.shape[0], dtype=A.dtype, device=A.device)
|
|
72
|
+
mat = tri.T if trans else tri
|
|
73
|
+
return torch.mv(mat, x)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def ger(
|
|
77
|
+
alpha: float,
|
|
78
|
+
x: torch.Tensor,
|
|
79
|
+
y: torch.Tensor,
|
|
80
|
+
A: Optional[torch.Tensor] = None,
|
|
81
|
+
) -> torch.Tensor:
|
|
82
|
+
"""Rank-1 update: A = alpha * x * y^T + A"""
|
|
83
|
+
result = alpha * torch.outer(x, y)
|
|
84
|
+
if A is not None:
|
|
85
|
+
result = result + A
|
|
86
|
+
return result
|
trnblas/level3.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BLAS Level 3 — Matrix-matrix operations.
|
|
3
|
+
|
|
4
|
+
gemm, symm, syrk, trsm, trmm
|
|
5
|
+
|
|
6
|
+
These are the critical path for scientific computing on Trainium:
|
|
7
|
+
- DF-MP2 tensor contractions → batched GEMM
|
|
8
|
+
- Fock matrix build → SYMM
|
|
9
|
+
- Cholesky-based density fitting → TRSM
|
|
10
|
+
- Metric contraction J^{-1/2} → SYRK
|
|
11
|
+
|
|
12
|
+
NKI dispatch for GEMM provides stationary tile reuse on the Tensor Engine.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import torch
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from .nki import get_backend, HAS_NKI
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def gemm(
|
|
24
|
+
alpha: float,
|
|
25
|
+
A: torch.Tensor,
|
|
26
|
+
B: torch.Tensor,
|
|
27
|
+
beta: float = 0.0,
|
|
28
|
+
C: Optional[torch.Tensor] = None,
|
|
29
|
+
transA: bool = False,
|
|
30
|
+
transB: bool = False,
|
|
31
|
+
) -> torch.Tensor:
|
|
32
|
+
"""General matrix multiply: C = alpha * op(A) @ op(B) + beta * C
|
|
33
|
+
|
|
34
|
+
op(X) = X if trans=False, X^T if trans=True.
|
|
35
|
+
|
|
36
|
+
This is the workhorse for DF-MP2:
|
|
37
|
+
(ia|P) = Σ_μν C_μi @ (μν|P) @ C_νa → two GEMMs per aux index
|
|
38
|
+
B_ia^P = (ia|Q) @ J^{-1/2}_{QP} → one GEMM
|
|
39
|
+
E_MP2 = Σ B_ia^P @ B_jb^P → batched GEMM
|
|
40
|
+
|
|
41
|
+
On Trainium, dispatches to NKI GEMM with stationary tile reuse.
|
|
42
|
+
"""
|
|
43
|
+
from .nki.dispatch import nki_gemm, _use_nki
|
|
44
|
+
|
|
45
|
+
a = A.T if transA else A
|
|
46
|
+
b = B.T if transB else B
|
|
47
|
+
|
|
48
|
+
if _use_nki() and a.dim() == 2 and b.dim() == 2:
|
|
49
|
+
result = alpha * nki_gemm(a, b)
|
|
50
|
+
else:
|
|
51
|
+
result = alpha * torch.matmul(a, b)
|
|
52
|
+
|
|
53
|
+
if C is not None and beta != 0.0:
|
|
54
|
+
result = result + beta * C
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def batched_gemm(
|
|
60
|
+
alpha: float,
|
|
61
|
+
A: torch.Tensor,
|
|
62
|
+
B: torch.Tensor,
|
|
63
|
+
beta: float = 0.0,
|
|
64
|
+
C: Optional[torch.Tensor] = None,
|
|
65
|
+
transA: bool = False,
|
|
66
|
+
transB: bool = False,
|
|
67
|
+
) -> torch.Tensor:
|
|
68
|
+
"""Batched GEMM: C[i] = alpha * op(A[i]) @ op(B[i]) + beta * C[i]
|
|
69
|
+
|
|
70
|
+
A, B have shape (batch, M, K) and (batch, K, N) respectively.
|
|
71
|
+
|
|
72
|
+
Critical for DF-MP2 energy evaluation where contractions are
|
|
73
|
+
independent across auxiliary basis indices.
|
|
74
|
+
"""
|
|
75
|
+
a = A.transpose(-2, -1) if transA else A
|
|
76
|
+
b = B.transpose(-2, -1) if transB else B
|
|
77
|
+
result = alpha * torch.bmm(a, b)
|
|
78
|
+
if C is not None and beta != 0.0:
|
|
79
|
+
result = result + beta * C
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def symm(
|
|
84
|
+
alpha: float,
|
|
85
|
+
A: torch.Tensor,
|
|
86
|
+
B: torch.Tensor,
|
|
87
|
+
beta: float = 0.0,
|
|
88
|
+
C: Optional[torch.Tensor] = None,
|
|
89
|
+
side: str = "left",
|
|
90
|
+
uplo: str = "upper",
|
|
91
|
+
) -> torch.Tensor:
|
|
92
|
+
"""Symmetric matrix multiply: C = alpha * A @ B + beta * C (side='left')
|
|
93
|
+
or C = alpha * B @ A + beta * C (side='right')
|
|
94
|
+
|
|
95
|
+
A is symmetric; only upper or lower triangle referenced.
|
|
96
|
+
Used in Fock matrix construction: F = H_core + J - K
|
|
97
|
+
where J and K involve symmetric density matrix contractions.
|
|
98
|
+
"""
|
|
99
|
+
if uplo == "upper":
|
|
100
|
+
sym = torch.triu(A) + torch.triu(A, diagonal=1).T
|
|
101
|
+
else:
|
|
102
|
+
sym = torch.tril(A) + torch.tril(A, diagonal=-1).T
|
|
103
|
+
|
|
104
|
+
if side == "left":
|
|
105
|
+
result = alpha * torch.matmul(sym, B)
|
|
106
|
+
else:
|
|
107
|
+
result = alpha * torch.matmul(B, sym)
|
|
108
|
+
|
|
109
|
+
if C is not None and beta != 0.0:
|
|
110
|
+
result = result + beta * C
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def syrk(
|
|
115
|
+
alpha: float,
|
|
116
|
+
A: torch.Tensor,
|
|
117
|
+
beta: float = 0.0,
|
|
118
|
+
C: Optional[torch.Tensor] = None,
|
|
119
|
+
trans: bool = False,
|
|
120
|
+
uplo: str = "upper",
|
|
121
|
+
) -> torch.Tensor:
|
|
122
|
+
"""Symmetric rank-k update: C = alpha * A @ A^T + beta * C (trans=False)
|
|
123
|
+
or C = alpha * A^T @ A + beta * C (trans=True)
|
|
124
|
+
|
|
125
|
+
Used in metric contraction for density fitting:
|
|
126
|
+
J_{PQ} = Σ_μν (P|μν)(μν|Q) which is A^T @ A form.
|
|
127
|
+
"""
|
|
128
|
+
if trans:
|
|
129
|
+
result = alpha * torch.matmul(A.T, A)
|
|
130
|
+
else:
|
|
131
|
+
result = alpha * torch.matmul(A, A.T)
|
|
132
|
+
|
|
133
|
+
if C is not None and beta != 0.0:
|
|
134
|
+
result = result + beta * C
|
|
135
|
+
|
|
136
|
+
# Symmetrize (numerical stability)
|
|
137
|
+
result = 0.5 * (result + result.T)
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def trsm(
|
|
142
|
+
alpha: float,
|
|
143
|
+
A: torch.Tensor,
|
|
144
|
+
B: torch.Tensor,
|
|
145
|
+
side: str = "left",
|
|
146
|
+
uplo: str = "upper",
|
|
147
|
+
trans: bool = False,
|
|
148
|
+
diag: str = "nonunit",
|
|
149
|
+
) -> torch.Tensor:
|
|
150
|
+
"""Triangular solve with multiple RHS: op(A) @ X = alpha * B (side='left')
|
|
151
|
+
or X @ op(A) = alpha * B (side='right')
|
|
152
|
+
|
|
153
|
+
Returns X.
|
|
154
|
+
|
|
155
|
+
Critical for Cholesky-based density fitting:
|
|
156
|
+
Given J = L @ L^T, solve L @ X = (μν|P) for X,
|
|
157
|
+
then B_ia^P = C^T @ X gives the DF coefficients.
|
|
158
|
+
"""
|
|
159
|
+
if uplo == "upper":
|
|
160
|
+
tri = torch.triu(A)
|
|
161
|
+
else:
|
|
162
|
+
tri = torch.tril(A)
|
|
163
|
+
|
|
164
|
+
if diag == "unit":
|
|
165
|
+
tri = tri - torch.diag(torch.diag(tri)) + torch.eye(A.shape[0], dtype=A.dtype, device=A.device)
|
|
166
|
+
|
|
167
|
+
mat = tri.T if trans else tri
|
|
168
|
+
|
|
169
|
+
if side == "left":
|
|
170
|
+
# Solve mat @ X = alpha * B
|
|
171
|
+
return alpha * torch.linalg.solve_triangular(mat, B, upper=(uplo == "upper" and not trans) or (uplo == "lower" and trans))
|
|
172
|
+
else:
|
|
173
|
+
# Solve X @ mat = alpha * B → mat^T @ X^T = alpha * B^T
|
|
174
|
+
upper_flag = (uplo == "lower" and not trans) or (uplo == "upper" and trans)
|
|
175
|
+
return alpha * torch.linalg.solve_triangular(mat.T, B.T, upper=upper_flag).T
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def trmm(
|
|
179
|
+
alpha: float,
|
|
180
|
+
A: torch.Tensor,
|
|
181
|
+
B: torch.Tensor,
|
|
182
|
+
side: str = "left",
|
|
183
|
+
uplo: str = "upper",
|
|
184
|
+
trans: bool = False,
|
|
185
|
+
diag: str = "nonunit",
|
|
186
|
+
) -> torch.Tensor:
|
|
187
|
+
"""Triangular matrix multiply: B = alpha * op(A) @ B (side='left')
|
|
188
|
+
or B = alpha * B @ op(A) (side='right')
|
|
189
|
+
"""
|
|
190
|
+
if uplo == "upper":
|
|
191
|
+
tri = torch.triu(A)
|
|
192
|
+
else:
|
|
193
|
+
tri = torch.tril(A)
|
|
194
|
+
|
|
195
|
+
if diag == "unit":
|
|
196
|
+
tri = tri - torch.diag(torch.diag(tri)) + torch.eye(A.shape[0], dtype=A.dtype, device=A.device)
|
|
197
|
+
|
|
198
|
+
mat = tri.T if trans else tri
|
|
199
|
+
|
|
200
|
+
if side == "left":
|
|
201
|
+
return alpha * torch.matmul(mat, B)
|
|
202
|
+
else:
|
|
203
|
+
return alpha * torch.matmul(B, mat)
|
trnblas/nki/__init__.py
ADDED
trnblas/nki/dispatch.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NKI dispatch for BLAS operations.
|
|
3
|
+
|
|
4
|
+
Backend selection mirrors trnfft: auto/pytorch/nki.
|
|
5
|
+
The GEMM kernel is the primary acceleration target — it uses stationary
|
|
6
|
+
tile reuse on the Tensor Engine for 2x fewer SBUF loads vs naive.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import neuronxcc.nki as nki
|
|
15
|
+
import neuronxcc.nki.language as nl
|
|
16
|
+
import neuronxcc.nki.isa as nisa
|
|
17
|
+
HAS_NKI = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
HAS_NKI = False
|
|
20
|
+
|
|
21
|
+
_backend = "auto"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def set_backend(backend: str):
|
|
25
|
+
global _backend
|
|
26
|
+
assert backend in ("auto", "pytorch", "nki")
|
|
27
|
+
if backend == "nki" and not HAS_NKI:
|
|
28
|
+
raise RuntimeError("NKI backend requires neuronxcc")
|
|
29
|
+
_backend = backend
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_backend() -> str:
|
|
33
|
+
return _backend
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _use_nki() -> bool:
|
|
37
|
+
if _backend == "nki":
|
|
38
|
+
return True
|
|
39
|
+
if _backend == "pytorch":
|
|
40
|
+
return False
|
|
41
|
+
return HAS_NKI
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def nki_gemm(A: torch.Tensor, B: torch.Tensor) -> torch.Tensor:
|
|
45
|
+
"""GEMM with NKI dispatch.
|
|
46
|
+
|
|
47
|
+
On NKI backend: uses tiled GEMM with stationary A reuse.
|
|
48
|
+
On PyTorch backend: torch.matmul.
|
|
49
|
+
|
|
50
|
+
NKI GEMM strategy (stationary tile reuse):
|
|
51
|
+
1. Load A tile to SBUF as stationary (stays in systolic array)
|
|
52
|
+
2. Stream B tiles as moving → accumulate in PSUM
|
|
53
|
+
3. One A load serves multiple B tiles
|
|
54
|
+
|
|
55
|
+
For DF-MP2 tensor contractions where A is the MO coefficient matrix
|
|
56
|
+
(reused across auxiliary basis indices), this cuts SBUF loads in half.
|
|
57
|
+
"""
|
|
58
|
+
if _use_nki():
|
|
59
|
+
return _nki_gemm_impl(A, B)
|
|
60
|
+
return torch.matmul(A, B)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _nki_gemm_impl(A: torch.Tensor, B: torch.Tensor) -> torch.Tensor:
|
|
64
|
+
"""NKI GEMM implementation.
|
|
65
|
+
|
|
66
|
+
TODO: Wire to actual NKI kernel with tiling once validated on trn1/trn2.
|
|
67
|
+
The kernel should:
|
|
68
|
+
- Tile A into 128×128 blocks (SBUF capacity)
|
|
69
|
+
- For each A tile, stream all corresponding B tiles through the systolic array
|
|
70
|
+
- Accumulate partial products in PSUM
|
|
71
|
+
- Handle edge tiles for non-128-aligned dimensions
|
|
72
|
+
|
|
73
|
+
See neuron-complex-ops kernels_optimized.py for the complex variant
|
|
74
|
+
of this pattern — real GEMM is simpler (no real/imag split needed).
|
|
75
|
+
"""
|
|
76
|
+
if not HAS_NKI:
|
|
77
|
+
raise RuntimeError("NKI not available")
|
|
78
|
+
# Fallback until kernel is validated on hardware
|
|
79
|
+
return torch.matmul(A, B)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if HAS_NKI:
|
|
83
|
+
|
|
84
|
+
@nki.jit
|
|
85
|
+
def gemm_kernel(A_ref, B_ref, C_ref, M: int, N: int, K: int):
|
|
86
|
+
"""Tiled GEMM kernel for Trainium NeuronCore.
|
|
87
|
+
|
|
88
|
+
C[M,N] = A[M,K] @ B[K,N]
|
|
89
|
+
|
|
90
|
+
Tiling: 128×128 tiles for SBUF, accumulate in PSUM.
|
|
91
|
+
A tiles are stationary (loaded once, reused across N tiles).
|
|
92
|
+
B tiles are streamed (moving operand in systolic array).
|
|
93
|
+
|
|
94
|
+
STUB: Scaffolded for on-hardware validation.
|
|
95
|
+
"""
|
|
96
|
+
TILE = 128
|
|
97
|
+
|
|
98
|
+
for m_tile in nl.affine_range(M // TILE):
|
|
99
|
+
for k_tile in nl.affine_range(K // TILE):
|
|
100
|
+
# Load A tile — stationary in systolic array
|
|
101
|
+
a_tile = nl.load(
|
|
102
|
+
A_ref[m_tile * TILE:(m_tile + 1) * TILE,
|
|
103
|
+
k_tile * TILE:(k_tile + 1) * TILE]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
for n_tile in nl.affine_range(N // TILE):
|
|
107
|
+
# Stream B tile — moving operand
|
|
108
|
+
b_tile = nl.load(
|
|
109
|
+
B_ref[k_tile * TILE:(k_tile + 1) * TILE,
|
|
110
|
+
n_tile * TILE:(n_tile + 1) * TILE]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Matmul → accumulate in PSUM
|
|
114
|
+
c_partial = nisa.nc_matmul(a_tile, b_tile)
|
|
115
|
+
|
|
116
|
+
# Store (would accumulate across k_tiles in practice)
|
|
117
|
+
nl.store(
|
|
118
|
+
C_ref[m_tile * TILE:(m_tile + 1) * TILE,
|
|
119
|
+
n_tile * TILE:(n_tile + 1) * TILE],
|
|
120
|
+
c_partial
|
|
121
|
+
)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trnblas
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: BLAS operations for AWS Trainium via NKI
|
|
5
|
+
Author-email: Scott Friedman <scott@playgroundlogic.co>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/scttfrdmn/trnblas
|
|
8
|
+
Project-URL: Repository, https://github.com/scttfrdmn/trnblas
|
|
9
|
+
Project-URL: Issues, https://github.com/scttfrdmn/trnblas/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: torch>=2.1
|
|
19
|
+
Requires-Dist: numpy>=1.24
|
|
20
|
+
Provides-Extra: neuron
|
|
21
|
+
Requires-Dist: neuronxcc>=2.15; extra == "neuron"
|
|
22
|
+
Requires-Dist: torch-neuronx>=2.1; extra == "neuron"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
26
|
+
Requires-Dist: scipy>=1.11; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# trnblas
|
|
30
|
+
|
|
31
|
+
[](https://github.com/scttfrdmn/trnblas/actions/workflows/ci.yml)
|
|
32
|
+
[](https://pypi.org/project/trnblas/)
|
|
33
|
+
[](https://pypi.org/project/trnblas/)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](https://scttfrdmn.github.io/trnblas/)
|
|
36
|
+
|
|
37
|
+
BLAS operations for AWS Trainium via NKI (Neuron Kernel Interface).
|
|
38
|
+
|
|
39
|
+
Trainium ships no BLAS library. `trnblas` provides Level 1-3 BLAS operations with NKI kernel acceleration on the Tensor Engine, targeting scientific computing workloads that are GEMM-dominated.
|
|
40
|
+
|
|
41
|
+
Part of the **trn-\*** scientific computing suite by [Playground Logic](https://playgroundlogic.co).
|
|
42
|
+
|
|
43
|
+
## Why
|
|
44
|
+
|
|
45
|
+
NVIDIA has cuBLAS with 152 optimized routines. Trainium has `torch.matmul`. That's fine for ML training but insufficient for scientific computing codes that need TRSM, SYRK, SYMM, and batched GEMM with specific transpose/scaling semantics.
|
|
46
|
+
|
|
47
|
+
trnblas closes this gap — same BLAS API surface, NKI-accelerated GEMM on Trainium, PyTorch fallback everywhere else.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install trnblas
|
|
53
|
+
|
|
54
|
+
# With Neuron hardware support
|
|
55
|
+
pip install trnblas[neuron]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import torch
|
|
62
|
+
import trnblas
|
|
63
|
+
|
|
64
|
+
# Level 3 — Matrix multiply (the hot path)
|
|
65
|
+
C = trnblas.gemm(alpha=1.0, A=A, B=B, beta=0.5, C=C_init, transA=True)
|
|
66
|
+
|
|
67
|
+
# Batched GEMM (DF-MP2 tensor contractions)
|
|
68
|
+
C = trnblas.batched_gemm(1.0, A_batch, B_batch)
|
|
69
|
+
|
|
70
|
+
# Symmetric matrix multiply (Fock builds)
|
|
71
|
+
F = trnblas.symm(1.0, density, H_core, side="left")
|
|
72
|
+
|
|
73
|
+
# Triangular solve (Cholesky-based density fitting)
|
|
74
|
+
X = trnblas.trsm(1.0, L, B, uplo="lower")
|
|
75
|
+
|
|
76
|
+
# Symmetric rank-k update (metric construction)
|
|
77
|
+
J = trnblas.syrk(1.0, integrals, trans=True)
|
|
78
|
+
|
|
79
|
+
# Level 2 — Matrix-vector
|
|
80
|
+
y = trnblas.gemv(1.0, A, x, beta=1.0, y=y)
|
|
81
|
+
|
|
82
|
+
# Level 1 — Vector operations
|
|
83
|
+
y = trnblas.axpy(alpha, x, y)
|
|
84
|
+
d = trnblas.dot(x, y)
|
|
85
|
+
n = trnblas.nrm2(x)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## DF-MP2 Example
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Run the density-fitted MP2 example (Janesko/TCU use case)
|
|
92
|
+
python examples/df_mp2.py --demo
|
|
93
|
+
python examples/df_mp2.py --nbasis 100 --nocc 20
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The example demonstrates all core BLAS operations in a realistic quantum chemistry workflow: Cholesky factorization, triangular solve, half-transform GEMMs, metric contraction, and energy evaluation.
|
|
97
|
+
|
|
98
|
+
## Operations
|
|
99
|
+
|
|
100
|
+
| Level | Operation | Description |
|
|
101
|
+
|-------|-----------|-------------|
|
|
102
|
+
| 1 | `axpy` | y = αx + y |
|
|
103
|
+
| 1 | `dot` | x^T y |
|
|
104
|
+
| 1 | `nrm2` | ‖x‖₂ |
|
|
105
|
+
| 1 | `scal` | x = αx |
|
|
106
|
+
| 1 | `asum` | Σ\|xᵢ\| |
|
|
107
|
+
| 1 | `iamax` | argmax \|xᵢ\| |
|
|
108
|
+
| 2 | `gemv` | y = α op(A) x + βy |
|
|
109
|
+
| 2 | `symv` | y = α A x + βy (A symmetric) |
|
|
110
|
+
| 2 | `trmv` | x = op(A) x (A triangular) |
|
|
111
|
+
| 2 | `ger` | A = α x yᵀ + A |
|
|
112
|
+
| 3 | `gemm` | C = α op(A) op(B) + βC |
|
|
113
|
+
| 3 | `batched_gemm` | Batched GEMM |
|
|
114
|
+
| 3 | `symm` | C = α A B + βC (A symmetric) |
|
|
115
|
+
| 3 | `syrk` | C = α A Aᵀ + βC |
|
|
116
|
+
| 3 | `trsm` | Solve op(A) X = αB |
|
|
117
|
+
| 3 | `trmm` | B = α op(A) B |
|
|
118
|
+
|
|
119
|
+
## Status
|
|
120
|
+
|
|
121
|
+
- [x] Level 1-3 BLAS with PyTorch backend
|
|
122
|
+
- [x] GEMM with NKI dispatch stub
|
|
123
|
+
- [x] DF-MP2 example (Janesko/TCU use case)
|
|
124
|
+
- [ ] NKI GEMM kernel validation on trn1/trn2
|
|
125
|
+
- [ ] NKI GEMM with stationary tile reuse
|
|
126
|
+
- [ ] Batched GEMM NKI kernel
|
|
127
|
+
- [ ] Double-double FP64 emulation
|
|
128
|
+
- [ ] Benchmarks vs cuBLAS
|
|
129
|
+
|
|
130
|
+
## Related Projects
|
|
131
|
+
|
|
132
|
+
| Project | What |
|
|
133
|
+
|---------|------|
|
|
134
|
+
| [trnfft](https://github.com/scttfrdmn/trnfft) | FFT + complex ops for Trainium (Williamson/OSU use case) |
|
|
135
|
+
| trnsolver *(planned)* | Linear solvers and eigendecomposition |
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
Apache 2.0 — Playground Logic LLC
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
trnblas/__init__.py,sha256=ZKCqCn3hyUm6zMdkdvjziyf6rdAte84ggrmzWp20WIc,1043
|
|
2
|
+
trnblas/level1.py,sha256=3XVIacU0ZJkF5lTYuN-j4AU-2p9ZqCV7uGZXLn4S-h0,1044
|
|
3
|
+
trnblas/level2.py,sha256=pbdxD1Dh37Whga7f1foVUIrmIq8lN_y3LKBX-VGh-c8,2059
|
|
4
|
+
trnblas/level3.py,sha256=k6dCFMsJCs8sj38GfWGmVBkMuaKkDYU03vfTCr3nBYQ,5782
|
|
5
|
+
trnblas/nki/__init__.py,sha256=IOVhXRydqjqRG2XHYGqqSA9wuyGQAN-IclX0mDPmVik,190
|
|
6
|
+
trnblas/nki/dispatch.py,sha256=qDdEdQ1MQZKkt1GvghNpFzRZWM3YcSkBeqaBTUdOVv8,3815
|
|
7
|
+
trnblas-0.2.0.dist-info/licenses/LICENSE,sha256=56AI7gGqh5OTpJf3y7nJVeWAY67dDH3l_CsDmdRYRog,749
|
|
8
|
+
trnblas-0.2.0.dist-info/METADATA,sha256=ua4gNkBef_tnxPvRqrGpQrr8tbgDbE-Xv02p6K0DGdw,4762
|
|
9
|
+
trnblas-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
trnblas-0.2.0.dist-info/top_level.txt,sha256=sUhvzYD9c6aMPJncvKmDoqdSqUy604F-qtzICWbj4SY,8
|
|
11
|
+
trnblas-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Playground Logic LLC
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
trnblas
|