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.
Files changed (37) hide show
  1. opengemm-0.1.0/LICENSE +21 -0
  2. opengemm-0.1.0/MANIFEST.in +4 -0
  3. opengemm-0.1.0/PKG-INFO +158 -0
  4. opengemm-0.1.0/README.md +114 -0
  5. opengemm-0.1.0/opengemm/__init__.py +17 -0
  6. opengemm-0.1.0/opengemm/python/__init__.py +2 -0
  7. opengemm-0.1.0/opengemm/python/api.py +132 -0
  8. opengemm-0.1.0/opengemm/python/bench.py +455 -0
  9. opengemm-0.1.0/opengemm/python/build.py +64 -0
  10. opengemm-0.1.0/opengemm/python/dtypes.py +330 -0
  11. opengemm-0.1.0/opengemm/python/emit.py +982 -0
  12. opengemm-0.1.0/opengemm/python/fold.py +822 -0
  13. opengemm-0.1.0/opengemm/python/log.py +7 -0
  14. opengemm-0.1.0/opengemm/python/run.py +129 -0
  15. opengemm-0.1.0/opengemm/python/tune.py +225 -0
  16. opengemm-0.1.0/opengemm/shapes.jsonc +77 -0
  17. opengemm-0.1.0/opengemm/src/mm/configs.json +18737 -0
  18. opengemm-0.1.0/opengemm/src/mm/device_utils.cuh +538 -0
  19. opengemm-0.1.0/opengemm/src/mm/host_utils.cuh +42 -0
  20. opengemm-0.1.0/opengemm/src/mm/mm.cu +426 -0
  21. opengemm-0.1.0/opengemm/src/mm/mm.cuh +714 -0
  22. opengemm-0.1.0/opengemm/src/mm/registry.cuh +139 -0
  23. opengemm-0.1.0/opengemm/src/mm/types.cuh +89 -0
  24. opengemm-0.1.0/opengemm/src/smm/configs.json +4266 -0
  25. opengemm-0.1.0/opengemm/src/smm/device_utils.cuh +399 -0
  26. opengemm-0.1.0/opengemm/src/smm/host_utils.cuh +80 -0
  27. opengemm-0.1.0/opengemm/src/smm/registry.cuh +67 -0
  28. opengemm-0.1.0/opengemm/src/smm/smm.cu +300 -0
  29. opengemm-0.1.0/opengemm/src/smm/smm.cuh +645 -0
  30. opengemm-0.1.0/opengemm/src/smm/types.cuh +192 -0
  31. opengemm-0.1.0/opengemm.egg-info/PKG-INFO +158 -0
  32. opengemm-0.1.0/opengemm.egg-info/SOURCES.txt +35 -0
  33. opengemm-0.1.0/opengemm.egg-info/dependency_links.txt +1 -0
  34. opengemm-0.1.0/opengemm.egg-info/requires.txt +1 -0
  35. opengemm-0.1.0/opengemm.egg-info/top_level.txt +1 -0
  36. opengemm-0.1.0/pyproject.toml +34 -0
  37. 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.
@@ -0,0 +1,4 @@
1
+ include LICENSE README.md
2
+ recursive-include opengemm *.cu *.cuh *.json *.jsonc
3
+ global-exclude *.pyc
4
+ global-exclude __pycache__/*
@@ -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
+
@@ -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,2 @@
1
+ """Python side of opengemm: the API, the tuner, the emitter and the
2
+ benchmark harness. `opengemm/__init__.py` re-exports the public names."""
@@ -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)