trainkernel 0.1.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.
trainkernel/runtime.py ADDED
@@ -0,0 +1,142 @@
1
+ """CUDA extension compilation and a checked, static execution boundary."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ import hashlib
6
+ from typing import Any
7
+
8
+ import torch
9
+ from torch.utils._pytree import tree_flatten
10
+
11
+ from .codegen import GeneratedKernel, generate
12
+ from .ir import Program
13
+
14
+
15
+ @dataclass
16
+ class ExecutionBuffers:
17
+ """Caller-owned storage. Reuse only when previous work on it is ordered."""
18
+
19
+ outputs: tuple[torch.Tensor, ...]
20
+ workspace: torch.Tensor
21
+
22
+
23
+ class CompiledProgram:
24
+ """One specialized kernel. Calls allocate independent buffers for stream safety.
25
+
26
+ The result has no autograd registration: compile a functional training step
27
+ containing explicit differentiation to compute gradients and parameter updates.
28
+ """
29
+
30
+ def __init__(self, program: Program, generated: GeneratedKernel, module: Any, device: torch.device, inplace_outputs: dict[int, int] | None = None):
31
+ self.program = program
32
+ self.generated = generated
33
+ self.source = generated.source
34
+ self.module = module
35
+ self.device = device
36
+ self.inplace_outputs = dict(inplace_outputs or {})
37
+
38
+ def resource_usage(self) -> dict[str, int]:
39
+ """Compiler resource attributes; these do not require GPU counters."""
40
+ with torch.cuda.device(self.device):
41
+ values = self.module.resource_usage()
42
+ return dict(zip(("registers_per_thread", "local_bytes_per_thread", "shared_bytes_per_block", "resident_blocks_per_sm"), values))
43
+
44
+ def _validate_inputs(self, inputs: tuple[torch.Tensor, ...]) -> None:
45
+ if len(inputs) != len(self.program.inputs):
46
+ raise ValueError(f"trainkernel: expected {len(self.program.inputs)} inputs, got {len(inputs)}")
47
+ for i, (tensor, spec) in enumerate(zip(inputs, self.program.inputs)):
48
+ if not isinstance(tensor, torch.Tensor):
49
+ raise TypeError(f"trainkernel: input {i} must be a tensor")
50
+ if tensor.is_neg() or tensor.is_conj():
51
+ raise ValueError("trainkernel: resolve lazy negative or conjugate input views before execution")
52
+ if tensor.device != self.device or tuple(tensor.shape) != spec.shape or tuple(tensor.stride()) != spec.strides or tensor.dtype != getattr(torch, spec.dtype):
53
+ raise ValueError(f"trainkernel: input {i} does not match compiled device, shape, stride, or dtype")
54
+ if any(inputs[i].requires_grad for i in self.inplace_outputs.values()):
55
+ raise ValueError("trainkernel: inplace targets must be detached from autograd")
56
+
57
+ def allocate(self, *inputs: torch.Tensor) -> ExecutionBuffers:
58
+ if self.inplace_outputs:
59
+ self._validate_inputs(inputs)
60
+ return ExecutionBuffers(
61
+ tuple(inputs[self.inplace_outputs[i]] if i in self.inplace_outputs else torch.empty(spec.shape, dtype=getattr(torch, spec.dtype), device=self.device) for i, spec in enumerate(self.program.outputs)),
62
+ torch.empty(self.generated.workspace_elements, dtype=torch.float32, device=self.device),
63
+ )
64
+
65
+ def run_into(self, *inputs: torch.Tensor, buffers: ExecutionBuffers) -> tuple[torch.Tensor, ...]:
66
+ self._validate_inputs(inputs)
67
+ if len(buffers.outputs) != len(self.program.outputs):
68
+ raise ValueError("trainkernel: incorrect output buffer count")
69
+ if buffers.workspace.device != self.device or buffers.workspace.dtype != torch.float32 or not buffers.workspace.is_contiguous() or buffers.workspace.numel() < self.generated.workspace_elements:
70
+ raise ValueError("trainkernel: workspace does not match compiled requirements")
71
+ # Caller buffers must be disjoint from each other and the inputs. A
72
+ # conservative storage-level check prevents writes clobbering dependencies.
73
+ output_storages = []
74
+ allowed_storages = set()
75
+ for slot, (out, spec) in enumerate(zip(buffers.outputs, self.program.outputs)):
76
+ if out.device != self.device or out.dtype != getattr(torch, spec.dtype) or tuple(out.shape) != spec.shape or not out.is_contiguous():
77
+ raise ValueError("trainkernel: output buffer does not match compiled requirements")
78
+ if out.numel():
79
+ output_storages.append(out.untyped_storage().data_ptr())
80
+ if slot in self.inplace_outputs:
81
+ target_index = self.inplace_outputs[slot]
82
+ target = inputs[target_index]
83
+ if out.data_ptr() != target.data_ptr() or tuple(out.stride()) != tuple(target.stride()):
84
+ raise ValueError("trainkernel: inplace output must bind its declared input")
85
+ if target.numel():
86
+ storage = target.untyped_storage().data_ptr()
87
+ if any(i != target_index and value.numel() and value.untyped_storage().data_ptr() == storage for i, value in enumerate(inputs)):
88
+ raise ValueError("trainkernel: inplace target must not alias another input")
89
+ allowed_storages.add(storage)
90
+ if buffers.workspace.numel():
91
+ output_storages.append(buffers.workspace.untyped_storage().data_ptr())
92
+ input_storages = {tensor.untyped_storage().data_ptr() for tensor in inputs if tensor.numel()}
93
+ if len(set(output_storages)) != len(output_storages) or input_storages.intersection(output_storages) - allowed_storages:
94
+ raise ValueError("trainkernel: execution buffers must not alias inputs or each other")
95
+ self.module.launch(list(inputs), list(buffers.outputs), buffers.workspace)
96
+ if self.inplace_outputs:
97
+ torch.autograd.graph.increment_version([inputs[i] for i in self.inplace_outputs.values()])
98
+ return buffers.outputs
99
+
100
+ def __call__(self, *inputs: torch.Tensor) -> tuple[torch.Tensor, ...]:
101
+ return self.run_into(*inputs, buffers=self.allocate(*inputs))
102
+
103
+
104
+ def compile_program(program: Program, example_inputs: tuple[Any, ...] | list[Any], *, blocks_per_sm: int = 1, max_blocks: int | None = None, fuse_pointwise: bool = True, fuse_epilogues: bool = True, gemm_k_tile: int = 32, gemm_tile_n: int = 32, shared_skew: int = 0, split_k_gemv: bool = False, threads_per_block: int = 128, inline_input_casts: bool = True, inplace_outputs: dict[int, int] | None = None, verbose: bool = False) -> CompiledProgram:
105
+ """Compile for the current input device, requiring Linux, CUDA and SM80+."""
106
+ flat, _ = tree_flatten(example_inputs)
107
+ if not flat or any(not isinstance(x, torch.Tensor) for x in flat):
108
+ raise TypeError("trainkernel: nonempty tensor inputs required")
109
+ if len(flat) != len(program.inputs):
110
+ raise ValueError("trainkernel: input count differs from the program signature")
111
+ if not torch.cuda.is_available() or any(x.device.type != "cuda" for x in flat):
112
+ raise RuntimeError("trainkernel: CUDA SM80+ GPU required; CPU IR inspection is available through capture()")
113
+ device = flat[0].device
114
+ if any(x.device != device for x in flat):
115
+ raise ValueError("trainkernel: all inputs must use the same CUDA device")
116
+ capability = torch.cuda.get_device_capability(device)
117
+ if capability < (8, 0):
118
+ raise RuntimeError("trainkernel: NVIDIA compute capability 8.0 or newer required")
119
+ generated = generate(program, blocks_per_sm=blocks_per_sm, max_blocks=max_blocks, fuse_pointwise=fuse_pointwise, fuse_epilogues=fuse_epilogues, gemm_k_tile=gemm_k_tile, gemm_tile_n=gemm_tile_n, shared_skew=shared_skew, split_k_gemv=split_k_gemv, threads_per_block=threads_per_block, inline_input_casts=inline_input_casts, inplace_outputs=inplace_outputs)
120
+ if any(flat[i].requires_grad for i in (inplace_outputs or {}).values()):
121
+ raise ValueError("trainkernel: inplace targets must be detached from autograd")
122
+ from torch.utils.cpp_extension import CUDA_HOME, load_inline
123
+ if CUDA_HOME is None:
124
+ raise RuntimeError("trainkernel: CUDA toolkit with nvcc is required to compile generated kernels")
125
+ name = "trainkernel_" + hashlib.sha256((generated.source + str(capability) + torch.__version__).encode()).hexdigest()[:20]
126
+ # Restrict compilation to the actual target rather than relying on a global
127
+ # TORCH_CUDA_ARCH_LIST, which may target a different GPU or unsupported SM.
128
+ arch = f"-gencode=arch=compute_{capability[0]}{capability[1]},code=sm_{capability[0]}{capability[1]}"
129
+ with torch.cuda.device(device):
130
+ module = load_inline(
131
+ name=name,
132
+ cpp_sources="void launch(std::vector<torch::Tensor>, std::vector<torch::Tensor>, torch::Tensor); std::vector<int64_t> resource_usage();",
133
+ cuda_sources=generated.source,
134
+ functions=["launch", "resource_usage"],
135
+ extra_cuda_cflags=["-O3", "-std=c++17", "--fmad=false", arch],
136
+ extra_cflags=["-O3"],
137
+ with_cuda=True,
138
+ verbose=verbose,
139
+ )
140
+ result = CompiledProgram(program, generated, module, device, inplace_outputs)
141
+ result._validate_inputs(tuple(flat))
142
+ return result
trainkernel/wmma.py ADDED
@@ -0,0 +1,130 @@
1
+ """Optional 32x64 WMMA GEMM source emitter.
2
+
3
+ This module deliberately has no dependency on the scheduler. The callbacks
4
+ are the small boundary needed to retain scheduler-specific addressing and
5
+ epilogue fusion while keeping this experimental tile independently testable.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Callable
10
+
11
+
12
+ Read = Callable[[str, str], str]
13
+ Store = Callable[[str, str], str]
14
+ Epilogue = Callable[[str, str], str]
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class WMMAEmission:
19
+ source: str
20
+ tile_m: int = 32
21
+ tile_n: int = 64
22
+ warps: int = 4
23
+ shared_a_elements: int = 0
24
+ shared_b_elements: int = 0
25
+ shared_c_elements: int = 2048
26
+
27
+ # A/B are FP16/BF16 (2 bytes each); C is FP32 (4 bytes). Keeping these
28
+ # separate avoids treating the mixed-width allocation as one element type.
29
+ @property
30
+ def shared_a_bytes(self) -> int:
31
+ return self.shared_a_elements * 2
32
+
33
+ @property
34
+ def shared_b_bytes(self) -> int:
35
+ return self.shared_b_elements * 2
36
+
37
+ @property
38
+ def shared_c_bytes(self) -> int:
39
+ return self.shared_c_elements * 4
40
+
41
+ @property
42
+ def shared_bytes(self) -> int:
43
+ return self.shared_a_bytes + self.shared_b_bytes + self.shared_c_bytes
44
+
45
+ @property
46
+ def shared_elements(self) -> int:
47
+ return self.shared_a_elements + self.shared_b_elements + self.shared_c_elements
48
+
49
+
50
+ def emit_wmma(
51
+ m: int,
52
+ k: int,
53
+ n: int,
54
+ read_a: Read,
55
+ read_b: Read,
56
+ store: Store,
57
+ epilogue: Epilogue | None = None,
58
+ *,
59
+ k_tile: int = 32,
60
+ value_type: str = "__nv_bfloat16",
61
+ cast: str = "__float2bfloat16_rn",
62
+ ) -> WMMAEmission:
63
+ """Emit one 32x64 low-precision GEMM work tile.
64
+
65
+ ``read_a(row, k)`` and ``read_b(k, col)`` return device expressions.
66
+ ``store(index, value)`` must return a complete CUDA statement. The
67
+ optional epilogue receives the logical output index and accumulator
68
+ expression, and may return a replacement expression.
69
+ """
70
+ if min(m, k, n) < 0 or k_tile not in (16, 32):
71
+ raise ValueError("WMMA dimensions must be non-negative and k_tile must be 16 or 32")
72
+ ep = epilogue or (lambda _index, value: value)
73
+ a_tile = 32 * k_tile
74
+ b_tile = k_tile * 64
75
+ chunks = (k + k_tile - 1) // k_tile
76
+ # Each warp owns one 16-row A slab and two 16-column output slabs.
77
+ lines = [
78
+ "{",
79
+ f" auto* wmma_a = reinterpret_cast<{value_type}*>(shared_a);",
80
+ f" auto* wmma_b = reinterpret_cast<{value_type}*>(shared_b);",
81
+ " const int wmma_warp = threadIdx.x >> 5;",
82
+ f" for (int64_t wtile = blockIdx.x; wtile < {((m + 31) // 32) * ((n + 63) // 64)}LL; wtile += gridDim.x) {{",
83
+ f" const int64_t wrow = (wtile / {max(1, (n + 63) // 64)}LL) * 32LL;",
84
+ f" const int64_t wcol = (wtile % {max(1, (n + 63) // 64)}LL) * 64LL;",
85
+ " nvcuda::wmma::fragment<nvcuda::wmma::accumulator,16,16,16,float> acc0, acc1;",
86
+ " nvcuda::wmma::fill_fragment(acc0, 0.0f);",
87
+ " nvcuda::wmma::fill_fragment(acc1, 0.0f);",
88
+ f" for (int64_t wkk = 0; wkk < {k}LL; wkk += {k_tile}) {{",
89
+ f" for (int p = threadIdx.x; p < {max(a_tile, b_tile)}; p += blockDim.x) {{",
90
+ f" if (p < {a_tile}) {{ const int64_t ar = wrow + p / {k_tile}, ak = wkk + p % {k_tile}; wmma_a[p] = {cast}((ar < {m}LL && ak < {k}LL) ? {read_a('ar', 'ak')} : 0.0f); }}",
91
+ f" if (p < {b_tile}) {{ const int64_t bk = wkk + p / 64, bc = wcol + p % 64; wmma_b[p] = {cast}((bk < {k}LL && bc < {n}LL) ? {read_b('bk', 'bc')} : 0.0f); }}",
92
+ " }",
93
+ " __syncthreads();",
94
+ " if (wmma_warp < 4) {",
95
+ f" const int arow = (wmma_warp >> 1) * 16;",
96
+ " nvcuda::wmma::fragment<nvcuda::wmma::matrix_a,16,16,16," + value_type + ",nvcuda::wmma::row_major> af;",
97
+ " nvcuda::wmma::fragment<nvcuda::wmma::matrix_b,16,16,16," + value_type + ",nvcuda::wmma::row_major> bf0, bf1;",
98
+ f" nvcuda::wmma::load_matrix_sync(af, wmma_a + arow * {k_tile}, {k_tile});",
99
+ " nvcuda::wmma::load_matrix_sync(bf0, wmma_b + (wmma_warp & 1) * 16, 64);",
100
+ " nvcuda::wmma::load_matrix_sync(bf1, wmma_b + (wmma_warp & 1) * 16 + 32, 64);",
101
+ " nvcuda::wmma::mma_sync(acc0, af, bf0, acc0);",
102
+ " nvcuda::wmma::mma_sync(acc1, af, bf1, acc1);",
103
+ ]
104
+ if k_tile == 32:
105
+ lines += [
106
+ " nvcuda::wmma::load_matrix_sync(af, wmma_a + arow * 32 + 16, 32);",
107
+ " nvcuda::wmma::load_matrix_sync(bf0, wmma_b + 16 * 64 + (wmma_warp & 1) * 16, 64);",
108
+ " nvcuda::wmma::load_matrix_sync(bf1, wmma_b + 16 * 64 + (wmma_warp & 1) * 16 + 32, 64);",
109
+ " nvcuda::wmma::mma_sync(acc0, af, bf0, acc0);",
110
+ " nvcuda::wmma::mma_sync(acc1, af, bf1, acc1);",
111
+ ]
112
+ lines += [
113
+ " }",
114
+ " __syncthreads();",
115
+ " }",
116
+ " if (wmma_warp < 4) {",
117
+ " nvcuda::wmma::store_matrix_sync(shared_c + (wmma_warp >> 1) * 16 * 64 + (wmma_warp & 1) * 16, acc0, 64, nvcuda::wmma::mem_row_major);",
118
+ " nvcuda::wmma::store_matrix_sync(shared_c + (wmma_warp >> 1) * 16 * 64 + (wmma_warp & 1) * 16 + 32, acc1, 64, nvcuda::wmma::mem_row_major);",
119
+ " }",
120
+ " __syncthreads();",
121
+ " for (int p = threadIdx.x; p < 2048; p += blockDim.x) {",
122
+ " const int64_t rr = wrow + p / 64, cc = wcol + p % 64;",
123
+ f" if (rr < {m}LL && cc < {n}LL) {{ const int64_t oi = rr * {n}LL + cc; {store('oi', ep('oi', 'shared_c[p]'))} }}",
124
+ " }",
125
+ " __syncthreads();",
126
+ " }",
127
+ "}",
128
+ ]
129
+ return WMMAEmission("\n".join(lines), shared_a_elements=a_tile, shared_b_elements=b_tile)
130
+
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: trainkernel
3
+ Version: 0.1.0
4
+ Summary: Compile a complete static PyTorch training step into one CUDA kernel
5
+ Author: Arya Somu
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/arpjw/trainkernel
8
+ Project-URL: Documentation, https://github.com/arpjw/trainkernel#readme
9
+ Project-URL: Issues, https://github.com/arpjw/trainkernel/issues
10
+ Project-URL: Source, https://github.com/arpjw/trainkernel
11
+ Keywords: pytorch,cuda,compiler,training,gpu
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: GPU :: NVIDIA CUDA
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ License-File: NOTICE
26
+ Requires-Dist: torch>=2.8
27
+ Requires-Dist: ninja>=1.11
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == "dev"
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Requires-Dist: twine>=5; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # TrainKernel
35
+
36
+ TrainKernel compiles a supported static PyTorch training step into one
37
+ cooperative CUDA megakernel. The generated kernel can include the forward pass,
38
+ loss, gradients, and SGD update, so a captured step has no per-operator Python
39
+ launch overhead.
40
+
41
+ The current backend schedules operators in topological order, uses grid barriers for dependencies, inlines single-consumer pointwise operations, and reuses temporary storage after its last reader. BF16 and FP16 matrix multiplications use WMMA tensor cores. FP32 uses a tiled SIMT implementation for accuracy.
42
+
43
+ This is a scoped v0.1 research release for static functional training on NVIDIA
44
+ SM80 GPUs. It is not a general PyTorch compiler and does not claim universal
45
+ speedups.
46
+
47
+ | A100 workload | TrainKernel | Fastest measured Inductor | TrainKernel / Inductor |
48
+ | --- | ---: | ---: | ---: |
49
+ | tiny online MLP | 15.28 μs | 22.85 μs | **0.669, 1.49x faster** |
50
+ | online MLP | 20.32 μs | 24.24 μs | **0.838, 1.19x faster** |
51
+ | small MLP | 66.04 μs | 22.92 μs | 2.894, slower |
52
+ | medium MLP | 255.32 μs | 42.19 μs | 6.052, slower |
53
+
54
+ These are complete forward, backward, and update steps captured as CUDA Graphs.
55
+ The [held-out report](https://github.com/arpjw/trainkernel/blob/main/results/a100-optimization/README.md) publishes all seeds,
56
+ raw timings, correctness checks, baseline failures, and the two regressions.
57
+
58
+ An independent release run adds a one-hot least-squares classification head.
59
+ Across three seeds, TrainKernel's in-place path measured 13.07 us versus
60
+ 18.63 us for the fastest measured Inductor configuration at the median, or
61
+ 1.43x faster. The [release evidence](https://github.com/arpjw/trainkernel/blob/main/results/release-validation/README.md)
62
+ retains all raw samples and correctness failures. Its cold-cache setup took
63
+ 53.62 seconds and required an estimated 7.82 million updates to amortize, so
64
+ this is a steady-state small-workload result rather than a claim about
65
+ short-lived jobs.
66
+
67
+ ## Install
68
+
69
+ Linux, Python 3.10+, PyTorch 2.8+, an NVIDIA SM80+ GPU, a CUDA toolkit with `nvcc`, and a C++ compiler are required for generated kernel execution. CPU capture and IR checks run without CUDA.
70
+
71
+ ```bash
72
+ python -m pip install trainkernel
73
+ ```
74
+
75
+ For development:
76
+
77
+ ```bash
78
+ python -m venv --system-site-packages .venv
79
+ source .venv/bin/activate
80
+ python -m pip install -e '.[dev]'
81
+ python -m pytest -q
82
+ python -m examples.online_training
83
+ ```
84
+
85
+ The backend targets SM80+, including A100 and H100. Release evidence is A100-only; H100 has not been tested and Hopper TMA/WGMMA paths are not implemented.
86
+
87
+ ## Compile a training step
88
+
89
+ ```python
90
+ import torch
91
+ import trainkernel
92
+
93
+ def train_step(weights, x, target):
94
+ def loss(w):
95
+ return ((torch.relu(x @ w) - target) ** 2).mean()
96
+ gradients, value = torch.func.grad_and_value(loss)(weights)
97
+ return value, gradients, weights - 1e-3 * gradients
98
+
99
+ compiled = trainkernel.compile_training(train_step, (weights, x, target))
100
+ loss, gradients, weights = compiled(weights, x, target)
101
+ ```
102
+
103
+ `compile_training` returns a typed `CompiledTrainingStep`. It verifies that
104
+ gradients match state structure and derives the update mapping, including nested
105
+ state trees. The supplied state must be detached from autograd because the
106
+ training function computes its own gradients. `compiled.capture(...)` creates a
107
+ CUDA Graph after warmup, restores the supplied state after warmup and capture,
108
+ and returns a reusable `CapturedProgram`; replay outputs borrow captured buffers
109
+ with fixed shapes and addresses. See the [100-update example](https://github.com/arpjw/trainkernel/blob/main/examples/online_training.py).
110
+
111
+ Parameters may be nested dictionaries or tuples. Inputs and operations must stay on one device; resolve lazy negative or conjugate input views before compiling. Updated parameters are returned explicitly. The compiler proves direct last-read state commits where safe and stages all other updates transactionally; input mutation and arbitrary aliasing remain unsupported. Different shapes, strides, devices, or dtypes require another compilation.
112
+
113
+ Inspect the compiler without a GPU:
114
+
115
+ ```python
116
+ program, output_tree = trainkernel.capture(train_step, cpu_examples)
117
+ from trainkernel.codegen import generate
118
+ cuda_source = generate(program).source
119
+ ```
120
+
121
+ ## Benchmark
122
+
123
+ ```bash
124
+ bash scripts/run_train_loop_suite.sh results/a100-optimization/heldout
125
+ ```
126
+
127
+ For a clean release-candidate check, including the useful classification-head
128
+ workload and package build:
129
+
130
+ ```bash
131
+ bash scripts/run_release_validation.sh results/release-validation
132
+ ```
133
+
134
+ The [training-loop harness](https://github.com/arpjw/trainkernel/blob/main/benchmarks/train_loop.py) uses FP32 master weights, BF16 compute, FP32 loss, and FP32 SGD updates. It checks every loss, gradient, and updated parameter for 100 steps and separately checks a captured 20-step replay. Timed graphs include state feedback over four resident batches. They compare pure and in-place megakernels, captured eager PyTorch, and default and eager-rounding Inductor variants. CUDA event timings exclude compilation, capture, reset, and warmup, and retain raw samples. Numerical failures remain visible for all baselines. The [methodology](https://github.com/arpjw/trainkernel/blob/main/docs/a100-methodology.md) explains the held-out seeds and fixed tolerance policy; the [result](https://github.com/arpjw/trainkernel/blob/main/results/a100-optimization/README.md) includes both wins and regressions. The older [native-BF16 fixed-step harness](https://github.com/arpjw/trainkernel/blob/main/benchmarks/train_mlp.py) remains available for the historical report.
135
+
136
+ ## Supported subset
137
+
138
+ - FP32, BF16, and FP16 strided tensors with static shapes.
139
+ - Matrix multiplication and `addmm`, broadcast arithmetic, ReLU and its backward, scalar powers, sums and means, dtype casts, fills, and supported storage views.
140
+ - Pure tensor functions, including explicit differentiation through `torch.func.grad_and_value`.
141
+
142
+ Unsupported operations produce an error. There is no silent eager fallback. Attention, dropout/RNG, convolutions, dynamic shapes, arbitrary input mutation, distributed collectives, and asynchronous task-queue scheduling are not implemented.
143
+
144
+ Temporary values use FP32 workspace storage with explicit rounding at low-precision boundaries. Fusion reduces materialization but does not keep an entire model on-chip. The tested larger GEMMs run faster in Inductor; the report keeps those regressions visible.
145
+
146
+ ## Design and evidence
147
+
148
+ See [compiler design](https://github.com/arpjw/trainkernel/blob/main/docs/design.md), [A100 optimization evidence](https://github.com/arpjw/trainkernel/blob/main/results/a100-optimization/README.md), [independent release validation](https://github.com/arpjw/trainkernel/blob/main/results/release-validation/README.md), and [GPU access record](https://github.com/arpjw/trainkernel/blob/main/docs/infrastructure.md). CPU tests validate IR semantics, views, reductions, diagnostics, and arena lifetimes. The independent release suite passed 116 tests that compile and execute generated CUDA, compare training outputs across precisions, and exercise CUDA Graph replay.
149
+
150
+ ## Related work and roadmap
151
+
152
+ Most adjacent public megakernel systems currently focus on inference:
153
+ [HazyResearch Megakernels](https://github.com/HazyResearch/Megakernels) targets
154
+ low-latency Llama inference, [MegaBake](https://github.com/megabake/megabake)
155
+ captures PyTorch inference workloads for SM90, [AutoMegaKernel](https://github.com/RightNow-AI/AutoMegaKernel)
156
+ generates Llama-family inference kernels, and [Mirage Persistent Kernel](https://arxiv.org/abs/2512.22219)
157
+ targets multi-GPU model inference. TrainKernel instead explores compiling the
158
+ stateful forward, loss, backward, and optimizer update of a small training step
159
+ on A100 into one kernel. It does not claim priority for megakernel compilation
160
+ in general.
161
+
162
+ The next engineering priorities are a measured shape-aware dispatcher that can
163
+ delegate GEMM-heavy steps to Inductor, an experimental `torch.compile` backend
164
+ after AOTAutograd, AdamW, and additional real online-learning workloads. These
165
+ are roadmap items, not v0.1 capabilities.
166
+
167
+ ## License
168
+
169
+ Apache License 2.0. See [LICENSE](https://github.com/arpjw/trainkernel/blob/main/LICENSE) and [NOTICE](https://github.com/arpjw/trainkernel/blob/main/NOTICE).
@@ -0,0 +1,16 @@
1
+ trainkernel/__init__.py,sha256=q3Rn3ug1CeCDHN3nxOiYwFBxSj38VssMqdH-1XGYCwk,407
2
+ trainkernel/codegen.py,sha256=mzIFB9agOqBaZvN4d6yyECPSs7mGnV5ghcQLC-iEGfs,52881
3
+ trainkernel/frontend.py,sha256=zeC06BmpclBf5eAL7kisVbffkUGGejmmVAUzXp5zhEU,15438
4
+ trainkernel/gemv.py,sha256=T466_07Jj3sH9RGj_XKD8ZYr_AEni1QJAh6UEaign3c,2049
5
+ trainkernel/ir.py,sha256=ghIch3x1XXni4C5xo9fKyhBcw3s-FKblKVGXaxMcLg8,2525
6
+ trainkernel/passes.py,sha256=pw3VtNeI3CSxUAMFpgOL1cZy3llVFha4M8yHvDYi9Ks,3382
7
+ trainkernel/py.typed,sha256=1F5juyAFRQIScl2WbVd2VzUvkxbW-SYhkQnmeXncq_4,60
8
+ trainkernel/reference.py,sha256=8qZTyq6PYx_VaCn6wBZz_V3AEy1rpnd0saAUzyG8a3M,2468
9
+ trainkernel/runtime.py,sha256=CDhAW4ar5BGGsO9v9P96IqE1JjCuNX_gVzBc0O3s6qo,9053
10
+ trainkernel/wmma.py,sha256=TVjFn4OMUBEc_1C_7BcRkXvvjPmmpLFUsmnMs-i_PRY,5796
11
+ trainkernel-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
+ trainkernel-0.1.0.dist-info/licenses/NOTICE,sha256=K2yZZQoC774cNzyge_5x1Dr2wE132L3Lf_Yegg8cVxg,174
13
+ trainkernel-0.1.0.dist-info/METADATA,sha256=FEuQCQaLUWGDFwHXwppMYAnOzvjM1T33SZ6P6jOf5VI,9638
14
+ trainkernel-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
15
+ trainkernel-0.1.0.dist-info/top_level.txt,sha256=-EBMWciAioOLO1Pbf9uC9AUqPPTJCbPsfjY_zRE9nEs,12
16
+ trainkernel-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+