[Mlir-commits] [mlir] 55c4d46 - [MLIR][XeGPU] Build float min/max neutral values the format can represent (#233471)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Nov 2 05:50:46 PST 2026
Author: Md Abdullah Shahneous Bari
Date: 2026-11-02T13:50:40Z
New Revision: 55c4d4682011fff5fef7a9f671aae1f6a973bc83
URL: https://github.com/llvm/llvm-project/commit/55c4d4682011fff5fef7a9f671aae1f6a973bc83
DIFF: https://github.com/llvm/llvm-project/commit/55c4d4682011fff5fef7a9f671aae1f6a973bc83.diff
LOG: [MLIR][XeGPU] Build float min/max neutral values the format can represent (#233471)
`createReductionNeutralValue` gives a float min/max reduction an
infinity (`maximumf`, `minimumf`) or a quiet NaN (the kinds that ignore
NaN). The WG-to-SG distribution and the peephole split of
`vector.multi_reduction` start their new reductions from this value.
Some float formats cannot represent these values:
- A format without infinity, such as `f8E4M3FN`: `APFloat::getInf`
returns NaN. So `maximumf` and `minimumf` start from NaN, and every
result is NaN.
- `f4E2M1FN` has neither infinity nor NaN, so building the value aborts.
- `f8E8M0FNU` has no negative values, so building -inf for `maximumf`
aborts.
This patch chooses the value by what the format supports:
- A kind that ignores NaN still uses a quiet NaN if the format has one.
- Otherwise it uses the infinity that never wins.
- A format without that infinity uses its lowest (for max) or highest
(for min) value, which never wins either.
- For an unsigned format the lowest value is zero, or its smallest value
if it has no zero.
IEEE formats get the same values as before.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply at anthropic.com>
Added:
Modified:
mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
mlir/test/Dialect/XeGPU/xegpu-wg-to-sg.mlir
Removed:
################################################################################
diff --git a/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp b/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
index ab9859938559ce..56c8b440701e16 100644
--- a/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
+++ b/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
@@ -725,6 +725,35 @@ Value xegpu::lowerCrossLaneReductionToShuffles(
return reductionResult;
}
+/// Returns the neutral value of the float min or max reduction `kind` over
+/// elements of `semantics`. Builds only values the format can represent:
+/// - A kind that ignores NaN operands uses a quiet NaN, if the format has one.
+/// - Otherwise it uses the infinity that never wins.
+/// - A format without that infinity uses its lowest (for max) or highest (for
+/// min) value. That value never wins either.
+static APFloat getMinMaxNeutral(const llvm::fltSemantics &semantics,
+ vector::CombiningKind kind) {
+ bool isMax = kind == vector::CombiningKind::MAXIMUMF ||
+ kind == vector::CombiningKind::MAXNUMF ||
+ kind == vector::CombiningKind::MAXIMUMNUMF;
+ bool ignoresNaN = kind != vector::CombiningKind::MINIMUMF &&
+ kind != vector::CombiningKind::MAXIMUMF;
+ if (ignoresNaN && APFloat::semanticsHasNaN(semantics))
+ return APFloat::getQNaN(semantics);
+ if (!isMax)
+ return APFloat::semanticsHasInf(semantics) ? APFloat::getInf(semantics)
+ : APFloat::getLargest(semantics);
+ // An unsigned format has no negative values. Its lowest value is zero, or
+ // its smallest positive value if it has no zero.
+ if (!APFloat::semanticsHasSignedRepr(semantics))
+ return APFloat::semanticsHasZero(semantics)
+ ? APFloat::getZero(semantics)
+ : APFloat::getSmallest(semantics);
+ return APFloat::semanticsHasInf(semantics)
+ ? APFloat::getInf(semantics, /*Negative=*/true)
+ : APFloat::getLargest(semantics, /*Negative=*/true);
+}
+
Value xegpu::createReductionNeutralValue(OpBuilder &builder, Location loc,
Type type,
vector::CombiningKind kind) {
@@ -776,25 +805,14 @@ Value xegpu::createReductionNeutralValue(OpBuilder &builder, Location loc,
return nullptr;
case vector::CombiningKind::MINIMUMF:
- if (auto floatTy = dyn_cast<FloatType>(elemTy))
- return makeConst(builder.getFloatAttr(
- elemTy, APFloat::getInf(floatTy.getFloatSemantics())));
- return nullptr;
-
case vector::CombiningKind::MAXIMUMF:
- if (auto floatTy = dyn_cast<FloatType>(elemTy))
- return makeConst(builder.getFloatAttr(
- elemTy,
- APFloat::getInf(floatTy.getFloatSemantics(), /*Negative=*/true)));
- return nullptr;
-
case vector::CombiningKind::MINNUMF:
case vector::CombiningKind::MINIMUMNUMF:
case vector::CombiningKind::MAXNUMF:
case vector::CombiningKind::MAXIMUMNUMF:
if (auto floatTy = dyn_cast<FloatType>(elemTy))
return makeConst(builder.getFloatAttr(
- elemTy, APFloat::getQNaN(floatTy.getFloatSemantics())));
+ elemTy, getMinMaxNeutral(floatTy.getFloatSemantics(), kind)));
return nullptr;
}
return nullptr;
diff --git a/mlir/test/Dialect/XeGPU/xegpu-wg-to-sg.mlir b/mlir/test/Dialect/XeGPU/xegpu-wg-to-sg.mlir
index 0c4bf0428cc5e9..0560c16591185c 100644
--- a/mlir/test/Dialect/XeGPU/xegpu-wg-to-sg.mlir
+++ b/mlir/test/Dialect/XeGPU/xegpu-wg-to-sg.mlir
@@ -933,6 +933,94 @@ gpu.module @test_distribution {
gpu.return
}
+ // f8E4M3FN has no infinity: maximumf/minimumf use the lowest/highest finite value.
+ // maxnumf still uses a quiet NaN.
+ // CHECK-LABEL: gpu.func @vector_reduce_no_inf_neutral
+ // CHECK: %[[N0:.*]] = arith.constant dense<-4.480000e+02> : vector<16xf8E4M3FN>
+ // CHECK: vector.multi_reduction <maximumf>, %{{.*}}, %[[N0]] [1] : vector<16x64xf8E4M3FN> to vector<16xf8E4M3FN>
+ // CHECK: %[[N1:.*]] = arith.constant dense<4.480000e+02> : vector<16xf8E4M3FN>
+ // CHECK: vector.multi_reduction <minimumf>, %{{.*}}, %[[N1]] [1] : vector<16x64xf8E4M3FN> to vector<16xf8E4M3FN>
+ // CHECK: %[[N2:.*]] = arith.constant dense<0x7F> : vector<16xf8E4M3FN>
+ // CHECK: vector.multi_reduction <maxnumf>, %{{.*}}, %[[N2]] [1] : vector<16x64xf8E4M3FN> to vector<16xf8E4M3FN>
+ gpu.func @vector_reduce_no_inf_neutral(%src: memref<256x64xf8E4M3FN>) {
+ %cst = arith.constant dense<1.0> : vector<256xf8E4M3FN>
+ %tdesc = xegpu.create_nd_tdesc %src : memref<256x64xf8E4M3FN>
+ -> !xegpu.tensor_desc<256x64xf8E4M3FN>
+ %load = xegpu.load_nd %tdesc[0, 0] <{layout = #xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>}>
+ : !xegpu.tensor_desc<256x64xf8E4M3FN>
+ -> vector<256x64xf8E4M3FN>
+ %r0 = vector.multi_reduction <maximumf>, %load, %cst [1]
+ : vector<256x64xf8E4M3FN> to vector<256xf8E4M3FN>
+ %a0 = xegpu.convert_layout %r0
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf8E4M3FN>
+ %r1 = vector.multi_reduction <minimumf>, %load, %cst [1]
+ : vector<256x64xf8E4M3FN> to vector<256xf8E4M3FN>
+ %a1 = xegpu.convert_layout %r1
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf8E4M3FN>
+ %r2 = vector.multi_reduction <maxnumf>, %load, %cst [1]
+ : vector<256x64xf8E4M3FN> to vector<256xf8E4M3FN>
+ %a2 = xegpu.convert_layout %r2
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf8E4M3FN>
+ gpu.return
+ }
+
+ // f4E2M1FN has neither NaN nor infinity: all min/max kinds use the lowest/highest
+ // finite value.
+ // CHECK-LABEL: gpu.func @vector_reduce_no_nan_no_inf_neutral
+ // CHECK: %[[N0:.*]] = arith.constant dense<-6.000000e+00> : vector<16xf4E2M1FN>
+ // CHECK: vector.multi_reduction <maxnumf>, %{{.*}}, %[[N0]] [1] : vector<16x64xf4E2M1FN> to vector<16xf4E2M1FN>
+ // CHECK: %[[N1:.*]] = arith.constant dense<6.000000e+00> : vector<16xf4E2M1FN>
+ // CHECK: vector.multi_reduction <minimumf>, %{{.*}}, %[[N1]] [1] : vector<16x64xf4E2M1FN> to vector<16xf4E2M1FN>
+ gpu.func @vector_reduce_no_nan_no_inf_neutral(%src: memref<256x64xf4E2M1FN>) {
+ %cst = arith.constant dense<1.0> : vector<256xf4E2M1FN>
+ %tdesc = xegpu.create_nd_tdesc %src : memref<256x64xf4E2M1FN>
+ -> !xegpu.tensor_desc<256x64xf4E2M1FN>
+ %load = xegpu.load_nd %tdesc[0, 0] <{layout = #xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>}>
+ : !xegpu.tensor_desc<256x64xf4E2M1FN>
+ -> vector<256x64xf4E2M1FN>
+ %r0 = vector.multi_reduction <maxnumf>, %load, %cst [1]
+ : vector<256x64xf4E2M1FN> to vector<256xf4E2M1FN>
+ %a0 = xegpu.convert_layout %r0
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf4E2M1FN>
+ %r1 = vector.multi_reduction <minimumf>, %load, %cst [1]
+ : vector<256x64xf4E2M1FN> to vector<256xf4E2M1FN>
+ %a1 = xegpu.convert_layout %r1
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf4E2M1FN>
+ gpu.return
+ }
+
+ // f8E8M0FNU is unsigned, with no zero and no infinity: maximumf uses the smallest
+ // value, minimumf the largest finite value.
+ // CHECK-LABEL: gpu.func @vector_reduce_unsigned_neutral
+ // CHECK: %[[N0:.*]] = arith.constant dense<5.877470e-39> : vector<16xf8E8M0FNU>
+ // CHECK: vector.multi_reduction <maximumf>, %{{.*}}, %[[N0]] [1] : vector<16x64xf8E8M0FNU> to vector<16xf8E8M0FNU>
+ // CHECK: %[[N1:.*]] = arith.constant dense<1.701410e+38> : vector<16xf8E8M0FNU>
+ // CHECK: vector.multi_reduction <minimumf>, %{{.*}}, %[[N1]] [1] : vector<16x64xf8E8M0FNU> to vector<16xf8E8M0FNU>
+ gpu.func @vector_reduce_unsigned_neutral(%src: memref<256x64xf8E8M0FNU>) {
+ %cst = arith.constant dense<1.0> : vector<256xf8E8M0FNU>
+ %tdesc = xegpu.create_nd_tdesc %src : memref<256x64xf8E8M0FNU>
+ -> !xegpu.tensor_desc<256x64xf8E8M0FNU>
+ %load = xegpu.load_nd %tdesc[0, 0] <{layout = #xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>}>
+ : !xegpu.tensor_desc<256x64xf8E8M0FNU>
+ -> vector<256x64xf8E8M0FNU>
+ %r0 = vector.multi_reduction <maximumf>, %load, %cst [1]
+ : vector<256x64xf8E8M0FNU> to vector<256xf8E8M0FNU>
+ %a0 = xegpu.convert_layout %r0
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf8E8M0FNU>
+ %r1 = vector.multi_reduction <minimumf>, %load, %cst [1]
+ : vector<256x64xf8E8M0FNU> to vector<256xf8E8M0FNU>
+ %a1 = xegpu.convert_layout %r1
+ <{target_layout = #xegpu.slice<#xegpu.layout<sg_layout = [16, 1], sg_data = [16, 64]>, dims = [1]>}>
+ : vector<256xf8E8M0FNU>
+ gpu.return
+ }
+
// NaN-propagating kinds have +/-inf as neutral element.
// CHECK-LABEL: gpu.func @vector_reduce_nan_propagating_neutral
gpu.func @vector_reduce_nan_propagating_neutral(%src: memref<256x64xf32>) {
More information about the Mlir-commits
mailing list