transformations: (x86) fold vector loads into FMA memory operands - #6360
transformations: (x86) fold vector loads into FMA memory operands#6360manishpaulish wants to merge 3 commits into
Conversation
…width
RSM_Vfmadd231pdOp and RSM_Vfmadd231psOp hardcoded their broadcast
modifier to 1to8 and 1to16 respectively. Those values are only correct
for zmm operands. AVX512VL allows EVEX broadcast on ymm and xmm too,
where the same instruction broadcasts to fewer lanes, so the hardcoded
modifier emits assembly the assembler rejects:
vfmadd231pd ymm0, ymm1, [rdx]{1to8}
error: invalid operand for instruction
The lane count is the register width divided by the element width.
Replace broadcast_modifier() with an abstract element_bitwidth() and
compute the modifier from the type of register_in, adding bitwidth() to
the vector register types.
This was not caught before because nothing generated these operations;
the existing emission tests only cover zmm, where the hardcoded values
happen to be right.
Verified against the assembler for all six pd/ps x xmm/ymm/zmm forms.
x86 FMA instructions can take a multiply operand straight from memory, and on AVX-512 can additionally apply an embedded broadcast to it. The RSM forms of vfmadd231pd/ps were defined and asm-tested but no pass ever generated them, so every FMA paid for a separate load or broadcast plus the vector register to hold it. Add x86-fold-memory-operands, which folds a single-use load or broadcast-load into the FMA that consumes it. The fold is rejected when the loaded value has other uses, when a memory write sits between the load and the FMA, and - for the broadcast form - when the target is not AVX-512. Both multiply operands are considered, since multiplication commutes. On the libxsmm matmul integration test at avx512 this removes all eight broadcasts: 28 instructions down to 20, and eight fewer live vector values going into register allocation. The generated assembly still assembles, links and matches the naive reference in main.c.
Raising DiagnosticException from inside 'except KeyError' chained the dict lookup onto the traceback, so a vector too wide for the target printed 'KeyError: 512' above the real explanation. Suppress the chain and list the sizes the target does support.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6360 +/- ##
==========================================
+ Coverage 86.93% 86.94% +0.01%
==========================================
Files 439 440 +1
Lines 65902 65988 +86
Branches 7490 7502 +12
==========================================
+ Hits 57289 57372 +83
+ Misses 7036 7034 -2
- Partials 1577 1582 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
This is cool but also touches on some tricky things, where technically this is not always an optimisation, and is an instruction selection and scheduling concern. In that sense it does feel like it might be worth having the rewrite pattern somewhere, but I'm not 100% sure that it would be pulling its weight as for the time being I'd expect this kind of choice to be made before the IR is in x86. What motivated you to make this change? If there's a reason to add it I'd be open to it, then it would be good to discuss what the objective of the pass should be. Also yes, please open a new PR for the KeyError fix, with a dedicated test. |
|
Fair points, and I think you are right on the architecture. On what motivated it: I was measuring the libxsmm matmul pipeline and noticed the On "not always an optimisation", you are right that the pass has no basis for the claim. Two things worth separating: The regression I would have expected, a load hoisted out of a loop getting sunk back in and reloading every iteration, does not actually happen, because The case I have no answer for is scheduling. Whether the fused form wins depends on whether the kernel is load-port bound or register bound, and the pass has no cost model to reason about that at all. It just always folds. So I would not argue for this landing as an unconditional pass. The two coherent homes seem to be either in lowering, where Happy to close this if you would rather not have it sitting open. Otherwise I will leave it as a draft and come back once the arch plumbing exists and there is something to make the decision with. KeyError fix is split out with a dedicated test, opening that separately. |
Depends on #6359, which is the first commit here. Opening as a draft until that lands, then I will rebase so this is a clean single-commit diff.
x86 FMA instructions can take a multiply operand straight from memory, and on AVX-512 can apply an embedded broadcast to it. The
RSMforms ofvfmadd231pdandvfmadd231psare defined and have emission tests, but nothing generates them, so every FMA currently pays for a separate load or broadcast plus the vector register to hold the result.This adds
x86-fold-memory-operands, which folds a single-use load or broadcast-load into the FMA that consumes it.The fold is rejected when:
Both multiply operands are considered, as multiplication commutes.
On
tests/filecheck/projects/libxsmmatarch=avx512this removes all eight broadcasts:and eight fewer live vector values reach the register allocator. The generated assembly still assembles, links against the existing
main.cand matches the naive reference, checked on hardware with avx512f, avx512vl and fma.Run it before
x86-allocate-registersso the freed registers are available to the allocator.The last commit is unrelated housekeeping I hit while debugging this: raising
DiagnosticExceptionfrom insideexcept KeyErrorchained the dict lookup onto the traceback, so a vector too wide for the target printedKeyError: 512above the actual explanation. Happy to split it out if you would rather.