Repository navigation
refactor(conv): split conv_kernel into a package and fuse the backward fp16 casts - #11
Merged
Merged
Conversation
…ernel conv_kernel.py held three ~200-line CUDA/HIP templates plus their compilers, caches and launch planners in one 948-line file. It is now aether/custom_kernels/conv_kernel/: common.py ConvShapeMeta, ConvLaunchPlan, tile constants, vendor dicts forward.py conv_forward_wmma dweight.py conv_backward_dweight_wmma and its split-K sizing dinput.py conv_backward_dinput_wmma Code moved verbatim; only imports were added and two section headers dropped. __init__.py re-exports everything conv.py and the tests use, so the import path and every call site and mock are unchanged.
Conv2d._backward_gpu built its fp16 operands with four CuPy calls per conv: astype(fp32) (a no-op, dvalues already arrive in fp32), clip to +-65504, astype(fp16) on dvalues, and astype(fp16) on the saved inputs. On the CIFAR-10 benchmark that is 5 clips and 10 fp32->fp16 copies per step. conv_kernel/cast.py adds conv_backward_cast_fp16, which writes both operands in one launch: dvalues clipped to the fp16 range, inputs cast unclipped so dweight sees the same fp16 inputs the forward pass used. The clamp uses comparisons rather than fminf/fmaxf so NaN passes through and +-inf clamp, matching xp.clip. The layer passes contiguous fp32 (no-op today) and keeps the astype/clip chain as the fallback when the kernel fails to compile. The four conv kernels now share _COMPILE_OPTIONS in common.py. It drops the -I$ROCM_PATH/include the three WMMA kernels passed on ROCm: hiprtc already searches its own install's include dir, where rocWMMA lives, and the -I was searched first, so a stale ROCM_PATH could shadow it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
aether/custom_kernels/conv_kernel.pyhad grown to 948 lines: three CUDA/HIP templates plus their compilers, caches and launch planners. Adding another kernel there would have made it harder to follow. This PR splits it into a package with one module per kernel, then adds a fourth kernel that fuses the fp16 casts inConv2d._backward_gpu. All timings come from the CIFAR-10 model inbenchmarks/cifar10_breakdown.py(batch 256) on an RX 9070 XT (gfx1201).There are two commits:
refactor(conv): split conv_kernel.py into a package, one module per kernelis mechanical. Git won't show it as a rename because the file was split four ways, so review it by comparinggit show 848470c:aether/custom_kernels/conv_kernel.pywith the new modules.perf(conv): fuse the backward fp16 casts into one kernel.Package layout
aether/custom_kernels/conv_kernel/keeps the old import path. Its__init__.pyre-exports everythingconv.pyand the tests use, so every call site and everymock.patch.object(conv_kernel, ...)is unchanged.common.pyConvShapeMeta,ConvLaunchPlan, tile constants, CUDA/HIP vendor dicts,_COMPILE_OPTIONSforward.pyconv_forward_wmmadweight.pyconv_backward_dweight_wmmaand its split-K sizingdinput.pyconv_backward_dinput_wmmacast.pyconv_backward_cast_fp16(commit 2)Commit 1 moves code verbatim. The only additions are imports, and the only removals are two section headers that no longer apply.
Fused backward fp16 casts
_backward_gpubuilt its fp16 operands with four CuPy calls per conv:astype(fp32), which does nothing because a cast trace showed dvalues always arrive in fp32;astype(fp16)on dvalues;astype(fp16)on the saved inputs.On the benchmark that's 5 clips and 10 fp32→fp16 copies per step.
conv_backward_cast_fp16writes both operands in one launch:fminf/fmaxf, so NaN passes through and ±inf clamp, exactly likexp.clip.The layer passes both as contiguous fp32, which does nothing today, and keeps the old astype/clip chain as the fallback when the kernel fails to compile.
The four kernels now share
_COMPILE_OPTIONSinstead of a copy-pasted options block.Behavior changes
-I$ROCM_PATH/include(default/opt/rocm/include) to hiprtc.ROCM_PATHandROCM_HOMEunset.-Iwas searched before hiprtc's defaults, so a staleROCM_PATH, for example after a ROCm upgrade, could pull in mismatched headers.[aether]and conv falls back to the vectorized path.Testing
python3 -m unittest discover tests: 1113 tests pass on NumPy and CuPy (54 skipped), in the ROCm devcontainer. The suite also passes withROCM_PATH/ROCM_HOMEunset.New
tests/ops/test_conv_cast_kernel.py:New compile-failure case in
test_conv_kernel_compile_fallback.py: a failed compile warns and returnsNone, and the real compiled kernel is restored for later tests.New
test_conv_gpu_backward_parity_without_cast_kernelruns_backward_gpu's fallback branch with the kernel mocked out.I broke the kernel three ways and
test_conv_cast_kernelfailed each time:fminf/fmaxfinstead of comparisons;benchmarks/cifar10_breakdown.py --repeats 3:model.train()ms/stepEvery conv backward phase improved beyond run-to-run spread, and each conv's host time dropped by about 14 µs from the fewer launches.
Not verified:
cuda_fp16.h) hasn't been compiled or run.-Iwas tested only on ROCm 7.14.Follow-ups (out of scope)
Loss.regularization_loss()makes about 28 no-opastype(float32)calls per step and loops over every trainable layer. That goes in a separate losses PR.