opengemm 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.
- opengemm-0.1.0/LICENSE +21 -0
- opengemm-0.1.0/MANIFEST.in +4 -0
- opengemm-0.1.0/PKG-INFO +158 -0
- opengemm-0.1.0/README.md +114 -0
- opengemm-0.1.0/opengemm/__init__.py +17 -0
- opengemm-0.1.0/opengemm/python/__init__.py +2 -0
- opengemm-0.1.0/opengemm/python/api.py +132 -0
- opengemm-0.1.0/opengemm/python/bench.py +455 -0
- opengemm-0.1.0/opengemm/python/build.py +64 -0
- opengemm-0.1.0/opengemm/python/dtypes.py +330 -0
- opengemm-0.1.0/opengemm/python/emit.py +982 -0
- opengemm-0.1.0/opengemm/python/fold.py +822 -0
- opengemm-0.1.0/opengemm/python/log.py +7 -0
- opengemm-0.1.0/opengemm/python/run.py +129 -0
- opengemm-0.1.0/opengemm/python/tune.py +225 -0
- opengemm-0.1.0/opengemm/shapes.jsonc +77 -0
- opengemm-0.1.0/opengemm/src/mm/configs.json +18737 -0
- opengemm-0.1.0/opengemm/src/mm/device_utils.cuh +538 -0
- opengemm-0.1.0/opengemm/src/mm/host_utils.cuh +42 -0
- opengemm-0.1.0/opengemm/src/mm/mm.cu +426 -0
- opengemm-0.1.0/opengemm/src/mm/mm.cuh +714 -0
- opengemm-0.1.0/opengemm/src/mm/registry.cuh +139 -0
- opengemm-0.1.0/opengemm/src/mm/types.cuh +89 -0
- opengemm-0.1.0/opengemm/src/smm/configs.json +4266 -0
- opengemm-0.1.0/opengemm/src/smm/device_utils.cuh +399 -0
- opengemm-0.1.0/opengemm/src/smm/host_utils.cuh +80 -0
- opengemm-0.1.0/opengemm/src/smm/registry.cuh +67 -0
- opengemm-0.1.0/opengemm/src/smm/smm.cu +300 -0
- opengemm-0.1.0/opengemm/src/smm/smm.cuh +645 -0
- opengemm-0.1.0/opengemm/src/smm/types.cuh +192 -0
- opengemm-0.1.0/opengemm.egg-info/PKG-INFO +158 -0
- opengemm-0.1.0/opengemm.egg-info/SOURCES.txt +35 -0
- opengemm-0.1.0/opengemm.egg-info/dependency_links.txt +1 -0
- opengemm-0.1.0/opengemm.egg-info/requires.txt +1 -0
- opengemm-0.1.0/opengemm.egg-info/top_level.txt +1 -0
- opengemm-0.1.0/pyproject.toml +34 -0
- opengemm-0.1.0/setup.cfg +4 -0
opengemm-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya Ramesh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
opengemm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opengemm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open GEMM kernels for NVIDIA B200 (sm_100a) in CUDA
|
|
5
|
+
Author: Aditya Ramesh
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Aditya Ramesh
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/aramesh10/OpenGEMM
|
|
29
|
+
Project-URL: Repository, https://github.com/aramesh10/OpenGEMM
|
|
30
|
+
Project-URL: Issues, https://github.com/aramesh10/OpenGEMM/issues
|
|
31
|
+
Keywords: gemm,cuda,blackwell,sm_100a,kernels,agents
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: C++
|
|
37
|
+
Classifier: Environment :: GPU :: NVIDIA CUDA
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering
|
|
39
|
+
Requires-Python: >=3.10
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: torch>=2.8
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# OpenGEMM
|
|
46
|
+
|
|
47
|
+
GEMM kernels for NVIDIA B200 (sm_100a) in CUDA.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import opengemm as og
|
|
51
|
+
|
|
52
|
+
c = og.gemm(a, b) # C[M, N] = A[M, K] @ B[N, K].T
|
|
53
|
+
c = og.gemm(a, b, sfa, sfb) # block-scaled: nvfp4, mxfp8, mxfp4
|
|
54
|
+
|
|
55
|
+
og.emit_kernel(a, b, file="k.cu") # emits .cu/.cuh for this shape
|
|
56
|
+
c = og.run_kernel("k.cu", a, b) # compiles emitted kernel and runs it
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
From PyPI
|
|
62
|
+
```bash
|
|
63
|
+
pip install opengemm
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
From a clone:
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/aramesh10/OpenGEMM.git
|
|
69
|
+
cd OpenGEMM
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Requirements:
|
|
74
|
+
- sm_100a
|
|
75
|
+
- CUDA 12.9+ with `nvcc` on the path
|
|
76
|
+
- PyTorch 2.8+.
|
|
77
|
+
|
|
78
|
+
The first `gemm()` call builds the extension (a few minutes, then cached by torch).
|
|
79
|
+
|
|
80
|
+
## Agent Quickstart
|
|
81
|
+
|
|
82
|
+
Give your agent this prompt to use OpenGEMM as a tool:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
OpenGEMM emits standalone CUDA GEMM kernels for B200 (sm_100a), no GPU
|
|
86
|
+
needed to emit:
|
|
87
|
+
|
|
88
|
+
python -c "
|
|
89
|
+
import opengemm as og
|
|
90
|
+
S = dict(m=1024, n=1024, k=1024)
|
|
91
|
+
|
|
92
|
+
og.emit_kernel(**S, atype='bf16', file='k') # writes k.cu and k.cuh
|
|
93
|
+
og.emit_kernel(**S, atype='e4m3', btype='e5m2') # mixed, names itself
|
|
94
|
+
og.emit_kernel(**S, atype='e2m1', sftype='ue4m3') # block-scaled (nvfp4)
|
|
95
|
+
src, hdr = og.emit_kernel(**S, atype='bf16') # the text, always returned
|
|
96
|
+
print(src, hdr)
|
|
97
|
+
"
|
|
98
|
+
atype / btype: bf16 f16 tf32 s8 u8 e4m3 e5m2 e3m2 e2m3 e2m1
|
|
99
|
+
sftype (block-scaled): ue4m3 (nvfp4) or ue8m0 (mxfp8, mxfp4)
|
|
100
|
+
dtype (output): f32, s32 for s8/u8, bf16 when scaled — inferred, optional.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Dense and block-scaled
|
|
104
|
+
|
|
105
|
+
`C[M, N] = A[M, K] @ B[N, K].T`. Both operands are row-major with K innermost.
|
|
106
|
+
|
|
107
|
+
| GEMM | `atype` / `btype` | `sftype` | `dtype` | `torch.dtype` (in → out) |
|
|
108
|
+
| --- | --- | --- | --- | --- |
|
|
109
|
+
| bfloat16 | bf16 | — | f32 | `bfloat16` → `float32` |
|
|
110
|
+
| float16 | f16 | — | f32 | `float16` → `float32` |
|
|
111
|
+
| tf32 | tf32 | — | f32 | `float32` → `float32` |
|
|
112
|
+
| int8 | s8 | — | s32 | `int8` → `int32` |
|
|
113
|
+
| uint8 | u8 | — | s32 | `uint8` → `int32` |
|
|
114
|
+
| fp8 | e4m3 | — | f32 | `float8_e4m3fn` → `float32` |
|
|
115
|
+
| fp8 | e5m2 | — | f32 | `float8_e5m2` → `float32` |
|
|
116
|
+
| mixed fp8 | e4m3, e5m2 | — | f32 | `float8_e4m3fn`, `float8_e5m2` → `float32` |
|
|
117
|
+
| fp6 | e3m2 | — | f32 | `uint8` → `float32` |
|
|
118
|
+
| fp6 | e2m3 | — | f32 | `uint8` → `float32` |
|
|
119
|
+
| fp4 | e2m1 | — | f32 | `uint8` → `float32` |
|
|
120
|
+
| nvfp4 | e2m1 | ue4m3 (per 16) | bf16 | `float4_e2m1fn_x2`, `float8_e4m3fn` → `bfloat16` |
|
|
121
|
+
| mxfp8 | e4m3 | ue8m0 (per 32) | bf16 | `float8_e4m3fn`, `float8_e8m0fnu` → `bfloat16` |
|
|
122
|
+
| mxfp4 | e2m1 | ue8m0 (per 32) | bf16 | `float4_e2m1fn_x2`, `float8_e8m0fnu` → `bfloat16` |
|
|
123
|
+
|
|
124
|
+
Note: fp6 and fp4 have no torch dtype. They arrive densely packed in `uint8` and are named - `gemm(a, b, atype="e2m1")`
|
|
125
|
+
Use `btype=` when the two operands differ.
|
|
126
|
+
|
|
127
|
+
Input is `[M, K]` and `[N, K]` with column-major strides `(1, M)` and `(1, N)`
|
|
128
|
+
Output is `[M, N]` with column-major strides `(1, M)`
|
|
129
|
+
|
|
130
|
+
## Tuning and performance
|
|
131
|
+
|
|
132
|
+
There is no heursitic to choose the config. Optimized configs are stored in `configs.json`.
|
|
133
|
+
If a particular shape has not been optimized, the library autotunes and returns and saves the best config locally to `./opengemm-configs/tuned_configs.json` or to `OPENGEMM_CONFIGS` env variable.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
CUDA_VISIBLE_DEVICES=0 python scripts/tune.py --dtype f16 --shape 4096 4096 4096
|
|
137
|
+
CUDA_VISIBLE_DEVICES=0 python scripts/benchmark.py --dtype bf16 e4m3 # vs cuBLAS
|
|
138
|
+
CUDA_VISIBLE_DEVICES=0 python scripts/test.py # correctness
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`tune.py` ablates every compiled configuration for a shape and records the best performing config to `configs.json`
|
|
142
|
+
|
|
143
|
+
## Standalone kernels
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python scripts/emit_kernel.py --dtype e4m3 --shape 4096 4096 4096 --file emitted/e4m3_4k.cu
|
|
147
|
+
python scripts/run_kernel.py emitted/e4m3_4k.cu # correctness, then timing vs cuBLAS
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
OpenGEMM can also emit the optimized CUDA files for a kernel given a shape and dtype. It can be ran with `scripts/run_kernel.py` or built with `nvcc`:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
nvcc -O3 -std=c++20 -gencode=arch=compute_100a,code=sm_100a --expt-relaxed-constexpr -shared -Xcompiler -fPIC -lcuda <KERNEL_FILE>.cu -o <KERNEL_FILE>.so
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`emit_kernel` reads only shapes and dtypes, so meta tensors work:
|
|
157
|
+
`emit_kernel(torch.empty(4096, 4096, dtype=torch.bfloat16, device="meta"), ...)`.
|
|
158
|
+
|
opengemm-0.1.0/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# OpenGEMM
|
|
2
|
+
|
|
3
|
+
GEMM kernels for NVIDIA B200 (sm_100a) in CUDA.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import opengemm as og
|
|
7
|
+
|
|
8
|
+
c = og.gemm(a, b) # C[M, N] = A[M, K] @ B[N, K].T
|
|
9
|
+
c = og.gemm(a, b, sfa, sfb) # block-scaled: nvfp4, mxfp8, mxfp4
|
|
10
|
+
|
|
11
|
+
og.emit_kernel(a, b, file="k.cu") # emits .cu/.cuh for this shape
|
|
12
|
+
c = og.run_kernel("k.cu", a, b) # compiles emitted kernel and runs it
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
From PyPI
|
|
18
|
+
```bash
|
|
19
|
+
pip install opengemm
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
From a clone:
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/aramesh10/OpenGEMM.git
|
|
25
|
+
cd OpenGEMM
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requirements:
|
|
30
|
+
- sm_100a
|
|
31
|
+
- CUDA 12.9+ with `nvcc` on the path
|
|
32
|
+
- PyTorch 2.8+.
|
|
33
|
+
|
|
34
|
+
The first `gemm()` call builds the extension (a few minutes, then cached by torch).
|
|
35
|
+
|
|
36
|
+
## Agent Quickstart
|
|
37
|
+
|
|
38
|
+
Give your agent this prompt to use OpenGEMM as a tool:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
OpenGEMM emits standalone CUDA GEMM kernels for B200 (sm_100a), no GPU
|
|
42
|
+
needed to emit:
|
|
43
|
+
|
|
44
|
+
python -c "
|
|
45
|
+
import opengemm as og
|
|
46
|
+
S = dict(m=1024, n=1024, k=1024)
|
|
47
|
+
|
|
48
|
+
og.emit_kernel(**S, atype='bf16', file='k') # writes k.cu and k.cuh
|
|
49
|
+
og.emit_kernel(**S, atype='e4m3', btype='e5m2') # mixed, names itself
|
|
50
|
+
og.emit_kernel(**S, atype='e2m1', sftype='ue4m3') # block-scaled (nvfp4)
|
|
51
|
+
src, hdr = og.emit_kernel(**S, atype='bf16') # the text, always returned
|
|
52
|
+
print(src, hdr)
|
|
53
|
+
"
|
|
54
|
+
atype / btype: bf16 f16 tf32 s8 u8 e4m3 e5m2 e3m2 e2m3 e2m1
|
|
55
|
+
sftype (block-scaled): ue4m3 (nvfp4) or ue8m0 (mxfp8, mxfp4)
|
|
56
|
+
dtype (output): f32, s32 for s8/u8, bf16 when scaled — inferred, optional.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Dense and block-scaled
|
|
60
|
+
|
|
61
|
+
`C[M, N] = A[M, K] @ B[N, K].T`. Both operands are row-major with K innermost.
|
|
62
|
+
|
|
63
|
+
| GEMM | `atype` / `btype` | `sftype` | `dtype` | `torch.dtype` (in → out) |
|
|
64
|
+
| --- | --- | --- | --- | --- |
|
|
65
|
+
| bfloat16 | bf16 | — | f32 | `bfloat16` → `float32` |
|
|
66
|
+
| float16 | f16 | — | f32 | `float16` → `float32` |
|
|
67
|
+
| tf32 | tf32 | — | f32 | `float32` → `float32` |
|
|
68
|
+
| int8 | s8 | — | s32 | `int8` → `int32` |
|
|
69
|
+
| uint8 | u8 | — | s32 | `uint8` → `int32` |
|
|
70
|
+
| fp8 | e4m3 | — | f32 | `float8_e4m3fn` → `float32` |
|
|
71
|
+
| fp8 | e5m2 | — | f32 | `float8_e5m2` → `float32` |
|
|
72
|
+
| mixed fp8 | e4m3, e5m2 | — | f32 | `float8_e4m3fn`, `float8_e5m2` → `float32` |
|
|
73
|
+
| fp6 | e3m2 | — | f32 | `uint8` → `float32` |
|
|
74
|
+
| fp6 | e2m3 | — | f32 | `uint8` → `float32` |
|
|
75
|
+
| fp4 | e2m1 | — | f32 | `uint8` → `float32` |
|
|
76
|
+
| nvfp4 | e2m1 | ue4m3 (per 16) | bf16 | `float4_e2m1fn_x2`, `float8_e4m3fn` → `bfloat16` |
|
|
77
|
+
| mxfp8 | e4m3 | ue8m0 (per 32) | bf16 | `float8_e4m3fn`, `float8_e8m0fnu` → `bfloat16` |
|
|
78
|
+
| mxfp4 | e2m1 | ue8m0 (per 32) | bf16 | `float4_e2m1fn_x2`, `float8_e8m0fnu` → `bfloat16` |
|
|
79
|
+
|
|
80
|
+
Note: fp6 and fp4 have no torch dtype. They arrive densely packed in `uint8` and are named - `gemm(a, b, atype="e2m1")`
|
|
81
|
+
Use `btype=` when the two operands differ.
|
|
82
|
+
|
|
83
|
+
Input is `[M, K]` and `[N, K]` with column-major strides `(1, M)` and `(1, N)`
|
|
84
|
+
Output is `[M, N]` with column-major strides `(1, M)`
|
|
85
|
+
|
|
86
|
+
## Tuning and performance
|
|
87
|
+
|
|
88
|
+
There is no heursitic to choose the config. Optimized configs are stored in `configs.json`.
|
|
89
|
+
If a particular shape has not been optimized, the library autotunes and returns and saves the best config locally to `./opengemm-configs/tuned_configs.json` or to `OPENGEMM_CONFIGS` env variable.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
CUDA_VISIBLE_DEVICES=0 python scripts/tune.py --dtype f16 --shape 4096 4096 4096
|
|
93
|
+
CUDA_VISIBLE_DEVICES=0 python scripts/benchmark.py --dtype bf16 e4m3 # vs cuBLAS
|
|
94
|
+
CUDA_VISIBLE_DEVICES=0 python scripts/test.py # correctness
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`tune.py` ablates every compiled configuration for a shape and records the best performing config to `configs.json`
|
|
98
|
+
|
|
99
|
+
## Standalone kernels
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python scripts/emit_kernel.py --dtype e4m3 --shape 4096 4096 4096 --file emitted/e4m3_4k.cu
|
|
103
|
+
python scripts/run_kernel.py emitted/e4m3_4k.cu # correctness, then timing vs cuBLAS
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
OpenGEMM can also emit the optimized CUDA files for a kernel given a shape and dtype. It can be ran with `scripts/run_kernel.py` or built with `nvcc`:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
nvcc -O3 -std=c++20 -gencode=arch=compute_100a,code=sm_100a --expt-relaxed-constexpr -shared -Xcompiler -fPIC -lcuda <KERNEL_FILE>.cu -o <KERNEL_FILE>.so
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`emit_kernel` reads only shapes and dtypes, so meta tensors work:
|
|
113
|
+
`emit_kernel(torch.empty(4096, 4096, dtype=torch.bfloat16, device="meta"), ...)`.
|
|
114
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""opengemm: open GEMM kernels for NVIDIA B200 (sm_100a) in plain CUDA.
|
|
2
|
+
|
|
3
|
+
import opengemm as og
|
|
4
|
+
c = og.gemm(a, b) # dense
|
|
5
|
+
c = og.gemm(a, b, sfa, sfb) # block-scaled
|
|
6
|
+
og.emit_kernel(a, b, file="k.cu") # a standalone .cu/.cuh pair
|
|
7
|
+
og.run_kernel("k.cu", a, b) # compile and run it
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .python.api import dtype_name, gemm
|
|
11
|
+
from .python.dtypes import DTYPES, pack_e2m1, quantize, to_blocked, unpack_e2m1
|
|
12
|
+
from .python.emit import emit_kernel
|
|
13
|
+
from .python.run import run_kernel
|
|
14
|
+
|
|
15
|
+
__all__ = ["gemm", "emit_kernel", "run_kernel", "dtype_name", "DTYPES",
|
|
16
|
+
"quantize", "to_blocked", "pack_e2m1", "unpack_e2m1"]
|
|
17
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""C[M, N] = A[M, K] @ B[N, K].T on B200."""
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
|
|
5
|
+
from .bench import config_args
|
|
6
|
+
from .build import extension
|
|
7
|
+
from .dtypes import DENSE_OF_TORCH, DTYPES, ELEM_INDEX, SCALED_OF_TORCH
|
|
8
|
+
from .tune import resolve_config
|
|
9
|
+
|
|
10
|
+
# Keyed by (dtype name, M, N, K).
|
|
11
|
+
_launchers = {}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _dense_elem(t, name, which):
|
|
15
|
+
if name is not None:
|
|
16
|
+
if name not in ELEM_INDEX:
|
|
17
|
+
raise ValueError(f"{which}type={name!r}; have {list(ELEM_INDEX)}")
|
|
18
|
+
return name
|
|
19
|
+
elem = DENSE_OF_TORCH.get(t.dtype)
|
|
20
|
+
if elem is None:
|
|
21
|
+
raise TypeError(
|
|
22
|
+
f"{which} is {t.dtype}, which names no element: pass "
|
|
23
|
+
f"{which}type= (uint8 carries u8, e3m2, e2m3 and e2m1 alike; "
|
|
24
|
+
f"the rest are inferred)")
|
|
25
|
+
return elem
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def dtype_name(a, b, sfa=None, atype=None, btype=None):
|
|
29
|
+
"""Return the dtype name for these operands, as configs.json spells it.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
a: Left operand.
|
|
33
|
+
b: Right operand.
|
|
34
|
+
sfa: Scales of `a` for a block-scaled call, else None.
|
|
35
|
+
atype: Element name for a `uint8` operand: `"u8"`, `"e3m2"`, `"e2m3"`
|
|
36
|
+
or `"e2m1"`.
|
|
37
|
+
btype: Element name for `b`; defaults to `atype` when the dtypes
|
|
38
|
+
match.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A name such as `"bf16"`, `"e4m3xe5m2"` or `"nvfp4"`.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
TypeError: If the dtypes name no element or no block-scaled format.
|
|
45
|
+
"""
|
|
46
|
+
if sfa is not None:
|
|
47
|
+
if (a.dtype, sfa.dtype) not in SCALED_OF_TORCH:
|
|
48
|
+
raise TypeError(
|
|
49
|
+
f"{a.dtype} operands with {sfa.dtype} scales is not a "
|
|
50
|
+
f"format; have { {v: k for k, v in SCALED_OF_TORCH.items()} }")
|
|
51
|
+
return SCALED_OF_TORCH[(a.dtype, sfa.dtype)]
|
|
52
|
+
ea = _dense_elem(a, atype, "a")
|
|
53
|
+
eb = _dense_elem(b, btype or (atype if b.dtype == a.dtype else None), "b")
|
|
54
|
+
return ea if ea == eb else f"{ea}x{eb}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def launcher(dtype, m, n, k):
|
|
58
|
+
"""Return the launcher bound to the stored configuration for a shape.
|
|
59
|
+
|
|
60
|
+
A shape with no stored configuration is tuned first and the winner stored.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
dtype: Dtype name as configs.json spells it.
|
|
64
|
+
m: Rows of A and C.
|
|
65
|
+
n: Rows of B and columns of C.
|
|
66
|
+
k: Reduction length in elements, not bytes.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
A callable taking the operands and an optional `out`.
|
|
70
|
+
"""
|
|
71
|
+
key = (dtype, m, n, k)
|
|
72
|
+
launch = _launchers.get(key)
|
|
73
|
+
if launch is None:
|
|
74
|
+
d = DTYPES[dtype]
|
|
75
|
+
config = resolve_config(dtype, m, n, k)
|
|
76
|
+
args = config_args(config, d.impl)
|
|
77
|
+
if d.impl == "mm":
|
|
78
|
+
args += (ELEM_INDEX[d.elem_a], ELEM_INDEX[d.elem_b])
|
|
79
|
+
launch = _launchers[key] = extension(d.impl).launcher(*args)
|
|
80
|
+
return launch
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def gemm(a, b, sfa=None, sfb=None, out=None, atype=None, btype=None):
|
|
84
|
+
"""Compute C[M, N] = A[M, K] @ B[N, K].T.
|
|
85
|
+
|
|
86
|
+
Both operands are row-major with K innermost. Every call runs a measured
|
|
87
|
+
configuration from configs.json; a (dtype, shape) with no entry is tuned on
|
|
88
|
+
its first call, a few minutes on the GPU, and the winner is stored. The
|
|
89
|
+
first call builds the extension.
|
|
90
|
+
|
|
91
|
+
Dense: the element is inferred from the dtype (bfloat16, float16, float32
|
|
92
|
+
computed as tf32, int8, float8_e4m3fn, float8_e5m2). uint8 holds u8, e3m2,
|
|
93
|
+
e2m3 or e2m1, fp6 and fp4 packed densely along K with K a multiple of 128,
|
|
94
|
+
and is named with `atype` and `btype`. Mixed e4m3 x e5m2 is supported.
|
|
95
|
+
|
|
96
|
+
Block-scaled: nvfp4 is float4_e2m1fn_x2 operands with float8_e4m3fn scales
|
|
97
|
+
per 16, mxfp8 is float8_e4m3fn with float8_e8m0fnu per 32, mxfp4 is
|
|
98
|
+
float4_e2m1fn_x2 with float8_e8m0fnu per 32.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
a: `(M, K)` operand.
|
|
102
|
+
b: `(N, K)` operand.
|
|
103
|
+
sfa: Scales of `a` in the 128x4 blocked layout `torch._scaled_mm` takes
|
|
104
|
+
(`to_blocked` builds it); block-scaled calls only.
|
|
105
|
+
sfb: Scales of `b`, likewise.
|
|
106
|
+
out: Output to write into instead of allocating one.
|
|
107
|
+
atype: Element name for a `uint8` operand.
|
|
108
|
+
btype: Element name for `b`; defaults to `atype` when the dtypes
|
|
109
|
+
match.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Dense: `(M, N)` float32, or int32 for int8, with column-major strides
|
|
113
|
+
`(1, M)`, which is how the accumulator leaves tensor memory; call
|
|
114
|
+
`.contiguous()` for row-major. Block-scaled: `(M, N)` bfloat16,
|
|
115
|
+
row-major.
|
|
116
|
+
"""
|
|
117
|
+
if (sfa is None) != (sfb is None):
|
|
118
|
+
raise ValueError("sfa and sfb go together: both or neither")
|
|
119
|
+
dtype = dtype_name(a, b, sfa, atype, btype)
|
|
120
|
+
d = DTYPES[dtype]
|
|
121
|
+
key = (dtype, a.size(0), b.size(0), d.k_values(a.size(1)))
|
|
122
|
+
launch = _launchers.get(key)
|
|
123
|
+
if launch is None:
|
|
124
|
+
if not a.is_cuda:
|
|
125
|
+
raise TypeError("a and b must be CUDA tensors")
|
|
126
|
+
# A sweep allocates and times on the current device; make it the
|
|
127
|
+
# operands'.
|
|
128
|
+
with torch.cuda.device(a.device):
|
|
129
|
+
launch = launcher(*key)
|
|
130
|
+
if sfa is None:
|
|
131
|
+
return launch(a, b, out=out)
|
|
132
|
+
return launch(a, b, sfa, sfb, out=out)
|