CuDNNHLOOpt: give every fusion function an unused symbol name - #2987
CuDNNHLOOpt: give every fusion function an unused symbol name#2987AsafManela wants to merge 2 commits into
Conversation
The name of the outlined `__cudnn_fused_elementwise_dot_N` function came from
a `static int fusionCounter` inside `DotGeneralElementwiseToCuDNNFusion`. That
is a class template, so each `ElementwiseOpTy` instantiation has its own
counter and they all start at 0: as soon as one module contains, say, an
add-of-dot and a multiply-of-dot, both fusions are named
`__cudnn_fused_elementwise_dot_0` and the pass fails with
error: redefinition of symbol named '__cudnn_fused_elementwise_dot_0'
(hit by `cudnn_hlo_optimize=true` in Reactant.jl on a training-step graph.)
Look the candidate name up in the module's SymbolTable instead and take the
first one that is free, which is also robust against names already present in
the input module.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Update: this is now built and verified, so the "not built or run here" caveat in the I compiled the patch into a JLL and compared it against an otherwise identical build from Running the reproducer from the issue through A seeded fp32/bf16 smoke test gives bit-identical numerical results on both builds, so the The lit test in this PR has still not been executed -- I verified through the compiled |
|
Important caveat, found by using this fix rather than just testing it: this patch is With the fix in a built JLL, the pass gets past the symbol collision and emits real work: So the failure moves from MLIR to XLA. Before this patch the pass aborted at the third A hypothesis I have not confirmed, offered only as a lead: many of the 74 outlined bodies The patch here still stands on its own terms — duplicate symbols are invalid MLIR whatever Two things I want to be straight about: this measurement is not mine, it came from a Happy to open this as a separate issue with the full log if you'd rather track it apart |
Antipath1
left a comment
There was a problem hiding this comment.
Thank you for the PR it definitely is an improvement. CI is green, and I built the unpatched and patched version of this on the test file and could reproduce the error without this PR, and I made a suggestion that contains the actual symbol names instead of wildcards because I think wildcards are just plain wrong choice in this case. Also, I would feel a lot better about this if you ran enzymexlamlir-opt locally I mean you can build it locally already and running this could have produced a better test as well as running bazel test //test/lit_tests/... on your machine before CI. I also think the shape of the newly added test case is not great because it lackes all the information that the other tests have.
Here is the rest of the information of running enzymexlamlir-opt on my machine
module {
module {
func.func private @__cudnn_fused_elementwise_dot_0(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16> attributes {no_inline} {
%0 = stablehlo.dot_general %arg0, %arg1, batching_dims = [0] x [0], contracting_dims = [2] x [1], precision = [DEFAULT, DEFAULT] : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
%1 = stablehlo.add %0, %arg2 : tensor<4x16x16xbf16>
return %1 : tensor<4x16x16xbf16>
}
func.func @dense1(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16> {
%0 = stablehlo.custom_call @__cudnn$fusion(%arg0, %arg1, %arg2) {called_computations = [@__cudnn_fused_elementwise_dot_0]} : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
return %0 : tensor<4x16x16xbf16>
}
}
module {
func.func private @__cudnn_fused_elementwise_dot_0(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16> attributes {no_inline} {
%0 = stablehlo.dot_general %arg0, %arg1, batching_dims = [0] x [0], contracting_dims = [2] x [1], precision = [DEFAULT, DEFAULT] : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
%1 = stablehlo.subtract %arg2, %0 : tensor<4x16x16xbf16>
return %1 : tensor<4x16x16xbf16>
}
func.func @dense2(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16> {
%0 = stablehlo.custom_call @__cudnn$fusion(%arg0, %arg1, %arg2) {called_computations = [@__cudnn_fused_elementwise_dot_0]} : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
return %0 : tensor<4x16x16xbf16>
}
}
module {
func.func private @__cudnn_fused_elementwise_dot_1(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16> attributes {no_inline} {
%0 = stablehlo.dot_general %arg0, %arg1, batching_dims = [0] x [0], contracting_dims = [2] x [1], precision = [DEFAULT, DEFAULT] : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
%1 = stablehlo.add %0, %arg2 : tensor<4x16x16xbf16>
return %1 : tensor<4x16x16xbf16>
}
func.func private @__cudnn_fused_elementwise_dot_0(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16> attributes {no_inline} {
%0 = stablehlo.dot_general %arg0, %arg1, batching_dims = [0] x [0], contracting_dims = [2] x [1], precision = [DEFAULT, DEFAULT] : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
%1 = stablehlo.multiply %0, %arg2 : tensor<4x16x16xbf16>
return %1 : tensor<4x16x16xbf16>
}
func.func @dense3(%arg0: tensor<4x16x16xbf16>, %arg1: tensor<4x16x16xbf16>, %arg2: tensor<4x16x16xbf16>) -> (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) {
%0 = stablehlo.custom_call @__cudnn$fusion(%arg0, %arg1, %arg2) {called_computations = [@__cudnn_fused_elementwise_dot_1]} : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
%1 = stablehlo.custom_call @__cudnn$fusion(%arg0, %arg1, %arg2) {called_computations = [@__cudnn_fused_elementwise_dot_0]} : (tensor<4x16x16xbf16>, tensor<4x16x16xbf16>, tensor<4x16x16xbf16>) -> tensor<4x16x16xbf16>
return %0, %1 : tensor<4x16x16xbf16>, tensor<4x16x16xbf16>
}
}
}that should allow for writing a better test for you.
You can install Bazel via Bazelisk quite easily if you do not have it already
https://bazel.build/install/bazelisk
Happy to open this as a separate issue with the full log if you'd rather track it apart
Yeah sounds like a good idea
I have to say that I had to put your text body in Claude to turn it into something that I wanted to actually read. Most of what you wrote can be significantly condensed and far easier for a human to read. Maybe next time, prompt your LLM to be more terse, relevant and on-topic. I mean, I used an LLM to condense the information, so it is definitely possible.
Review feedback:
- Replace the hand-rolled "first free name" loop with
SymbolTable::generateSymbolName, which is what SymbolTable::insert itself
delegates to. It carries a uniquing counter rather than rescanning from 0 on
every match, and emits the same __cudnn_fused_elementwise_dot_0, _1, ...
names. insert() was the other candidate, but it uniquifies by appending to
the whole existing name, so the second fusion would come out as
..._dot_0_0 rather than ..._dot_1.
generateSymbolName appends "_<n>" itself, hence the drop_back() on the
prefix; trimming the underscore from kCuDNNFusionFuncPrefix instead would
break the starts_with() guard that stops the pass re-fusing its own output.
- Check the dense3 fusion bodies with CHECK-NEXT like the other cases in the
file, and name both called_computations explicitly instead of matching
{{[0-9]+}}.
Verified against a local build: //test/lit_tests/... is 1214/1214, and putting
the old static counter back makes cudnn/fusion.mlir fail with "redefinition of
symbol named '__cudnn_fused_elementwise_dot_0'".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
All three taken, and you were right that I should have built this locally first. Done now; new commit pushed. Wildcards + test shape.
Local runs (bazel 7.7.0,
The XLA verifier failure is unrelated to this patch; I'll track it separately rather than here. And point taken on length. |
|
Tracked: openxla/xla#48147. Root cause is in XLA, not here. So nothing further is needed on this PR. |
Antipath1
left a comment
There was a problem hiding this comment.
Thank you a lot. LGTM
Fixes #2985.
The outlined
__cudnn_fused_elementwise_dot_Nfunction got its name from astatic int fusionCounterinsideDotGeneralElementwiseToCuDNNFusion. That is a class template, soeach
ElementwiseOpTyinstantiation has its own counter and they all start at 0: as soon asone module contains, say, an add-of-dot and a multiply-of-dot, both fusions are named
__cudnn_fused_elementwise_dot_0and the pass fails withLook the candidate name up in the module's
SymbolTableand take the first free one. Thatfixes the collision, drops the process-global state (the old counter also made the names
depend on how many fusions had run earlier in the process), and makes the pass robust
against a name already present in the input module.
Lit test appended to
test/lit_tests/cudnn/fusion.mlir. The existing@dense1/@dense2cases are unaffected: they live in separate nested modules, so each still starts from
_0.Testing
The C++ is not built or run here -- no bazel in this environment. The reproducer and the
error message come from running the shipped pass (
Reactant_jllv0.0.405) on the module inthe issue. The lit test is written against the current sources but has not been executed;
please let CI have it, and tell me if the CHECK lines need adjusting.
🤖 Generated with Claude Code
https://claude.ai/code/session_012k6KZhhCNSCvZFKzErHaHH