Repository navigation
Commit c6b26f0
authored
Optimize convolution and groupnorm (#98)
* Add a channels-last-native Triton GroupNorm
ScaFFold's GroupNorm was the only operator forcing a layout change: ATen's
kernel launches one workgroup per (batch, group) row -- 8 of them at the
benchmark's defaults -- so on a 228-CU MI300A it ran at a small fraction of
achievable bandwidth, and it was the sole reason channels-last broke.
This is an NDHWC-native kernel: Welford statistics, fp32/bf16/fp16 with
torch's autocast contract, an int64 tile-base path for tensors past 2^31
elements, and a fused ReLU that is bit-exact against the unfused form.
Registered as a custom op with fake kernels and register_autograd, so it
traces under torch.compile(fullgraph=True) and composes with DistConv's
DCTensor.
Determinism is a contract here, not an accident: no float atomics, and the
grid, split, tile and reduction order are pure functions of the shape.
Verified run-to-run and across interpreters.
Nothing imports this yet -- the wiring is the next commit.
* Route the UNet GroupNorm through the Triton kernel
FastGroupNorm becomes a three-rung ladder -- the native Triton kernel, a
torch.compile'd functional, and stock eager -- with the routing decision
made per call and the rejections tested in order: an explicit opt-out, a
rung that has already failed in this process, an active torch.func
transform, a non-CUDA tensor, a tensor subclass other than DistConv's
DCTensor, a GPU the launch tables were not tuned on, and anything the
kernel's own is_supported rejects.
The ladder primitives live in _rungs.py, which the convolution ladder will
share. The hardware guard is a preference rather than a correctness
condition -- the kernel is correct anywhere Triton lowers it, and what is
unknown elsewhere is only its speed -- so an explicit opt-in overrides it
and nothing else on that list can be overridden at all.
DoubleConv now asks the GroupNorm for its ReLU: the Triton kernel folds the
activation into its forward store, removing a streaming pass worth 38% of
the forward at the shapes that dominate the step. The nn.ReLU slots stay
occupied by nn.Identity so nn.Sequential does not renumber its children and
existing checkpoints keep loading.
* Add a Triton 3-D convolution package
A self-contained NDHWC implicit-GEMM convolution for MI300A / gfx942, built
to replace MIOpen in this benchmark and to be upstreamable to DistConv on
its own terms. It imports nothing from ScaFFold.
Four kernels serve seven operator-directions. Backward-data is not a kernel
at all -- it is the forward contraction on a permuted weight -- and the
transposed operator's backward directions are its own forward kernel and
backward-weight with the operands swapped. That reuse is the main
structural result.
The corpus is recorded from real ScaFFold calls, and it records the *shape
form*: the logical convolution, the halo'd unpadded one DistConv hands a
backend, and the padded one this package's adapter actually issues. Those
are different problems, and conflating them has been the most expensive
mistake in this work, so ConvProblem names which is which and the benchmark
driver takes --form.
Backward-weight is deterministic by default: split-K with a reduction tree
whose split count, tile and order are pure functions of the shape.
Correctness is checked against fp64 references under a three-tier tolerance
policy, with a bitwise corpus for the exactly-representable cases.
Also here: the benchmark harness, which times kernels through CUDA-graph
replay with 95% intervals and an online iteration count, and which can run
without the MIOpen control -- 98% of a two-arm capture's wall clock was
MIOpen's find, not measurement.
* Route every convolution through the Triton rung
FastConv3d and FastConvTranspose3d mirror FastGroupNorm: drop-in nn.Conv3d
and nn.ConvTranspose3d, same parameters under the same names, no buffers, a
rung ladder sharing _rungs.py, and MIOpen underneath everything the kernel
declines.
The transposed operator gets a factory of its own rather than a flag,
because it is a different operator -- the weight's channel axes are the
other way round and a different set of kernels sits behind it.
The adapter performs the halo exchange itself, above autograd, rather than
leaving it to the one DistConv does below. That has a consequence for the
shape the kernel sees, and it is the thing to know when reading any number
from this work: only the split axis is halo'd, so padding=1 survives on the
other two and every k=3 convolution in the network is padded at every
configuration -- unsharded there is nothing to halo at all. The benchmark
corpus calls that the adapter form, and it is what production issues.
* Cite findings rather than the scratch tree that produced them
Every comment and docstring in the committed tree stood on its own path into
the untracked work/ scratch directory: 70 references across 20 files, all of
them dangling for anyone who clones this branch. The measurement each one
supported is kept and stated as a result -- the number, the direction of the
effect, and the "this was tried and it loses" warnings that stop a closed
question being re-litigated -- while the path, the capture filename, the
section number of an unshipped document and the blow-by-blow methodology go.
Three pointers were dead even with work/ present and are simply gone: the
transposed benchmark driver the conv_bench docstring narrated a refactor away
from, a tuned table's named source capture, and the review commit SHA in the
GroupNorm wiring tests.
The corpus JSON keeps model-analysis/unet_shapes.py as its provenance; nothing
parses either file's "source" field.
* Add helper script to run tests.
* Satisfy the style checks CI actually runs
The style workflow runs `ruff format --diff .` and `ruff check .`, and both
were failing: 22 files would be reformatted and there were 19 lint errors,
all of the latter in the Triton package. Most of this is whitespace.
Three fixes are not mechanical:
`gemm_probe` built two closures over `a` and `b` in functions whose
`finally` deletes both names, so each was correct only because the harness
happens to call it before the cleanup runs. They now bind the tensors as
default arguments, which captures at definition time and does not depend
on call order. This is also what ruff was reporting as F821.
`baseline._callable` assigned two lambdas to names, now plain functions.
Six imports were unused and are gone.
`ScaFFold/viz/standard_viz.py` is not part of this branch's work -- it
fails the format check on round2-fixes too, and is reformatted here only
so the check can pass.
Both suites re-run afterwards, since several tests read their subject's
source: triton_conv3d 1425 passed / 16 skipped, ScaFFold 742 passed /
8 skipped / 1 xfailed. Reformatting changes Triton's JIT cache key, so
the first run after this recompiles every kernel and takes ~5x longer.
* Say which kernel each accelerated module ended up using
A run gives no sign of whether the Triton rungs actually served it. Print
one line per ladder on rank 0 at startup, naming Triton against everything
PyTorch does -- compiled and eager together, since from outside the ladder
those are the same answer.
Placement is the whole of the problem. ``_triton_ok`` is a latch set when a
rung first answers a call, so reporting at construction time would say
"Native" about modules that have not run yet. Reporting at the end of
warmup is right when there is a warmup, but ``warmup_batches <= 0`` returns
before running anything, which is what the benchmark drivers configure -- so
a single call site would drop the line on exactly the runs most likely to
want it. Both sites call it behind a one-shot flag.
The reporter is duck-typed on the latch rather than on isinstance: _rungs is
imported by the modules it would otherwise have to import back, and a ladder
added later is then reported without touching this code. Labels come from a
_rung_label class attribute and fall back to the class name, so a ladder that
does not declare one still appears.
Deliberately one rank's answer and not a collective: ranks latch
independently and a rung can still fall back later, so this is informational
rather than a contract, and gathering it would put a barrier on a path with
no other reason for one.
* Tighten comments and drop measured figures from them
Reviewers asked that the comments on this branch be more concise and that
specific performance numbers be removed from comments describing changes,
since those numbers go stale. Comments and docstrings only: every file's
AST is unchanged apart from docstrings, ruff format and check are clean,
and the test runner's shell script differs only in comment lines.
Gone: timings, speedups, roofline percentages, memory figures, cell
tallies, dated history of what the code used to do, and pointers to the
untracked scratch tree. Kept: contracts, traps, and the hardware and
algorithmic constants the code depends on (buffer-op and int32 limits,
tile geometry, tolerances, pinned test shapes). Where a rationale would
be empty without its figure, the comment names the bench tool that
regenerates it.
Across the 30 files: 11.9k comment and docstring lines become 10.6k, and
703 lines carrying a measurement, date, or scratch-tree pointer become 65,
each of which is a constraint rather than a measurement.
* Install the triton_conv3d package alongside ScaFFold
ScaFFold.unet.conv3d imports the sibling package, but package discovery
only listed ScaFFold*, so a wheel shipped an adapter with nothing behind
it. Discovery is namespace-aware, so the test tree (and the recorded
corpora it holds) is excluded by name; the suite runs from a checkout.
Verified by building a wheel, installing it into an empty target and
importing both packages from there.
* Keep the recorded convolution corpora with the tests
Nothing on the runtime path reads scaffold_corpus.json or
scaffold_census.json; only bench/ and the test suite do, and both run
from a checkout. Move them under triton_conv3d/tests/data, point the
loaders in shapes.py there, and say what each file is a record of: the
corpus joins model-analysis shape dumps with MIOpen profiles of the same
configurations, the census is an instrumented training run.
* Say why triton_group_norm's activation defaults to None
ScaFFold always fuses the ReLU; the default exists because the function
is a stand-alone replacement for F.group_norm.
* Never take the Triton conv rung without a handler for its failures
_triton_kernel_failures() resolved to () when triton.errors could not be
imported, and `except ()` catches nothing, so on a Triton without that
module every handler in the ladder became a bare try and the first
CompilationError or OutOfResources took the rank down -- the inverse of
the ladder's contract, silently. Resolve the 2.x roots
(CompilationError, OutOfResources) when triton.errors is absent, and have
_routing_declines refuse the rung, with one warning, when nothing at all
resolved.
* Agree the halo exchange across the mesh instead of deciding it per rank
Every input to the Triton rung's routing verdict is a fact about one
rank -- the thin-shard check in _halo_plan, the device, the layout, the
shape gates, the latch -- while the halo exchange is a collective. A rank
declining alone went through DistConv's exchange while its neighbour
posted receives against this module's, and the mesh hung. Unreachable
with the shipped shapes, where D divides evenly, but a hang.
The global extent is not on a DCTensor, so the check cannot be derived
locally; instead, for any call some rank may exchange on, whether to
exchange through this module is the MIN of every rank's verdict, agreed
once per module instance and cached. The latch, and any later change in
a rank's own verdict, only choose the kernel run on the exchanged tensor,
which communicates nothing. A shard that later becomes too thin for a
plan on an agreed module is an error rather than a lone trip to MIOpen.
Adds the first test of the real exchange: two ranks under torchrun, the
sharded output checked against F.conv3d on the whole volume, then a local
decline, a latch and a thin shard, each ending with both ranks on the
same side of the exchange.
* Commit the generator behind scaffold_corpus.json
make_corpus.py traces the three profiled configurations with
model-analysis/unet_shapes.py, the pure-stdlib shape calculator the corpus
already cited as its source, and joins profile_points.json, the MIOpen
profile record of the same runs reduced to its convolution kernels. Both
were in the untracked scratch tree; the corpus was not regenerable from
the repository. `--check` confirms the committed file is reproduced
byte for byte.
* Commit the instrument and generator behind scaffold_census.json
conv_census.py wraps FastConv3d, the two autograd nodes and the six kernel
entry points around a real `scaffold benchmark` run and writes one capture
per rank; make_census.py folds the four captures under census/ into the
committed file. Both were in the untracked scratch tree, where the
instrument ran through a scratch training driver. Verified by running
the new instrument at configuration A: its capture records the same
modules, autograd nodes and kernel calls as the committed one on every
field except the resolver's answers on padded backward-weight problems,
which the tuning tables have moved since and the census does not read.
`--check` confirms the committed census is reproduced from the captures.
The census's stale `tuned_declined` field is dropped from the instrument:
the decline it described no longer exists. A README in tests/data says
what each file is and how to regenerate it.1 parent c48b7ba commit c6b26f0
47 files changed
Lines changed: 70312 additions & 166 deletions
File tree
- ScaFFold
- unet
- utils
- viz
- model-analysis
- scripts
- tests
- helpers/rank_scripts
- triton_conv3d
- bench
- tests
- data
- census
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
31 | | - | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
36 | | - | |
37 | | - | |
38 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
39 | 121 | | |
40 | 122 | | |
41 | 123 | | |
42 | | - | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
43 | 140 | | |
44 | 141 | | |
45 | 142 | | |
46 | 143 | | |
47 | 144 | | |
48 | 145 | | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
55 | 152 | | |
56 | 153 | | |
57 | 154 | | |
| |||
75 | 172 | | |
76 | 173 | | |
77 | 174 | | |
78 | | - | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
79 | 185 | | |
80 | 186 | | |
81 | 187 | | |
| |||
90 | 196 | | |
91 | 197 | | |
92 | 198 | | |
93 | | - | |
| 199 | + | |
94 | 200 | | |
95 | 201 | | |
96 | 202 | | |
| |||
118 | 224 | | |
119 | 225 | | |
120 | 226 | | |
121 | | - | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
122 | 230 | | |
123 | 231 | | |
124 | 232 | | |
125 | 233 | | |
126 | 234 | | |
127 | 235 | | |
128 | | - | |
| 236 | + | |
129 | 237 | | |
130 | 238 | | |
131 | 239 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| |||
90 | 91 | | |
91 | 92 | | |
92 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
93 | 97 | | |
94 | 98 | | |
95 | 99 | | |
| |||
786 | 790 | | |
787 | 791 | | |
788 | 792 | | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
789 | 817 | | |
790 | 818 | | |
791 | 819 | | |
| |||
847 | 875 | | |
848 | 876 | | |
849 | 877 | | |
| 878 | + | |
850 | 879 | | |
851 | 880 | | |
852 | 881 | | |
| |||
953 | 982 | | |
954 | 983 | | |
955 | 984 | | |
| 985 | + | |
| 986 | + | |
956 | 987 | | |
957 | 988 | | |
958 | 989 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
| |||
64 | 66 | | |
65 | 67 | | |
66 | 68 | | |
67 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
68 | 72 | | |
69 | 73 | | |
70 | 74 | | |
| |||
76 | 80 | | |
77 | 81 | | |
78 | 82 | | |
79 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
80 | 86 | | |
81 | 87 | | |
82 | 88 | | |
| |||
89 | 95 | | |
90 | 96 | | |
91 | 97 | | |
92 | | - | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
93 | 101 | | |
94 | 102 | | |
95 | 103 | | |
| |||
0 commit comments