[LLVMGPU] Add NVIDIA FP8 mma.sync intrinsics for sm_89 and sm_120 (#24659)

Part of #24654

## Summary
Adds NVIDIA FP8 `mma.sync` (m16n8k32) strategy-selection support for Ada
(sm_89) and Blackwell (sm_120). IREE previously had no NVIDIA FP8
support at all — FP8 matmuls silently fell back to SIMT tiling on both
targets.

## Not included
FP8 lowering pipeline support. The TileAndFuse LHS transpose is
hardcoded for element[K]=2 (F16); FP8's element[K]=4 causes a
vector.shape_cast shape mismatch. Will do this in the next PR.

## Test (on RTX PRO 6000 Blackwell, sm_120)

```
PATH=/workspace/iree-build/tools:/workspace/iree-build/llvm-project/bin:$PATH \
  ./llvm-project/bin/llvm-lit -v \
  /workspace/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm89.mlir \
  /workspace/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm120.mlir \
  /workspace/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/pipeline_tile_and_fuse_mma_sync.mlir
```

```
-- Testing: 3 tests, 3 workers --
PASS: IREE :: src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm89.mlir (1 of 3)
PASS: IREE :: src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm120.mlir (2 of 3)
PASS: IREE :: src/iree/compiler/Codegen/LLVMGPU/test/NVVM/pipeline_tile_and_fuse_mma_sync.mlir (3 of 3)

Testing Time: 0.20s

Total Discovered Tests: 3
  Passed: 3 (100.00%)
```

---------

Signed-off-by: weimin023 <tnwilly@gmail.com>
diff --git a/compiler/plugins/target/CUDA/test/target_device_features.mlir b/compiler/plugins/target/CUDA/test/target_device_features.mlir
index 158affd..2fb1072 100644
--- a/compiler/plugins/target/CUDA/test/target_device_features.mlir
+++ b/compiler/plugins/target/CUDA/test/target_device_features.mlir
@@ -14,7 +14,7 @@
 // SM89: target_info = #iree_gpu.target<arch = "sm_89", features = "+ptx78",
 // SM89-SAME: wgp = <compute = fp64|fp32|fp16|int64|int32|int16|int8, storage = b64|b32|b16|b8,
 // SM89-SAME:         subgroup = shuffle|arithmetic, dot = dp4xi8toi32,
-// SM89-SAME:         mma = [<NV_MMA_SYNC_F32_16x8x16_F16>, <NV_MMA_SYNC_F16_16x8x16_F16>, <NV_MMA_SYNC_F32_16x8x16_BF16>, <NV_WMMA_F32_16x16x16_F16>, <NV_WMMA_F16_16x16x16_F16>],
+// SM89-SAME:         mma = [<NV_MMA_SYNC_F32_16x8x16_F16>, <NV_MMA_SYNC_F16_16x8x16_F16>, <NV_MMA_SYNC_F32_16x8x16_BF16>, <NV_MMA_SYNC_F32_16x8x32_F8E4M3FN>, <NV_MMA_SYNC_F32_16x8x32_F8E5M2>, <NV_WMMA_F32_16x16x16_F16>, <NV_WMMA_F16_16x16x16_F16>],
 // SM89-SAME:         subgroup_size_choices = [32], max_workgroup_sizes = [1024, 1024, 1024],
 // SM89-SAME:         max_thread_count_per_workgroup = 1024, max_workgroup_memory_bytes = 101376,
 // SM89-SAME:         max_workgroup_counts = [2147483647, 65535, 65535]>
@@ -24,7 +24,7 @@
 // SM120: target_info = #iree_gpu.target<arch = "sm_120", features = "+ptx87",
 // SM120-SAME: wgp = <compute = fp64|fp32|fp16|int64|int32|int16|int8, storage = b64|b32|b16|b8,
 // SM120-SAME:         subgroup = shuffle|arithmetic, dot = dp4xi8toi32,
-// SM120-SAME:         mma = [<NV_MMA_SYNC_F32_16x8x16_F16>, <NV_MMA_SYNC_F16_16x8x16_F16>, <NV_MMA_SYNC_F32_16x8x16_BF16>, <NV_WMMA_F32_16x16x16_F16>, <NV_WMMA_F16_16x16x16_F16>],
+// SM120-SAME:         mma = [<NV_MMA_SYNC_F32_16x8x16_F16>, <NV_MMA_SYNC_F16_16x8x16_F16>, <NV_MMA_SYNC_F32_16x8x16_BF16>, <NV_MMA_SYNC_F32_16x8x32_F8E4M3FN>, <NV_MMA_SYNC_F32_16x8x32_F8E5M2>, <NV_WMMA_F32_16x16x16_F16>, <NV_WMMA_F16_16x16x16_F16>],
 // SM120-SAME:         subgroup_size_choices = [32], max_workgroup_sizes = [1024, 1024, 64],
 // SM120-SAME:         max_thread_count_per_workgroup = 1024, max_workgroup_memory_bytes = 101376,
 // SM120-SAME:         max_workgroup_counts = [2147483647, 65535, 65535]>
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUAttrs.cpp b/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUAttrs.cpp
index cc8f1dc..0bc2968 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUAttrs.cpp
+++ b/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUAttrs.cpp
@@ -340,6 +340,11 @@
   case MMAIntrinsic::NV_MMA_SYNC_F32_16x8x16_BF16:
   case MMAIntrinsic::WMMA_F32_16x16x32_BF16:
     return {bf16, bf16, f32};
+  // NVIDIA FP8 mma.sync (m16n8k32, sm_89+).
+  case MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E4M3FN:
+    return {f8E4M3FN, f8E4M3FN, f32};
+  case MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E5M2:
+    return {f8E5M2, f8E5M2, f32};
   case MMAIntrinsic::WMMAR3_BF16_16x16x16_BF16:
   case MMAIntrinsic::WMMAR4_BF16_16x16x16_BF16:
   case MMAIntrinsic::WMMA_BF16_16x16x32_BF16:
@@ -872,6 +877,26 @@
     default:
       return {};
     }
+  // FP8 mma.sync (m16n8k32, sm_89+). Same thread arrangement as the f16
+  // m16n8k16 variants; K doubles so element[K] doubles from 2 to 4 (4 FP8
+  // values packed per 32-bit register vs 2 FP16). The accumulator shape is
+  // unchanged (same M×N output). Both supported FP8 types share the same
+  // register layout.
+  case MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E4M3FN:
+  case MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E5M2:
+    switch (operandIndex) {
+    case kMMAOperandLhs: // A [M=16, K=32]: 16 elements/thread
+      return {/*outer=*/{2, 2}, /*thread=*/{8, 4}, /*strides=*/{4, 1},
+              /*element=*/{1, 4}};
+    case kMMAOperandRhs: // B [K=32, N=8]: 8 elements/thread
+      return {/*outer=*/{2, 1}, /*thread=*/{4, 8}, /*strides=*/{1, 4},
+              /*element=*/{4, 1}};
+    case kMMAOperandAcc: // C [M=16, N=8]: 4 elements/thread (same as f16)
+      return {/*outer=*/{2, 1}, /*thread=*/{8, 4}, /*strides=*/{4, 1},
+              /*element=*/{1, 2}};
+    default:
+      return {};
+    }
   case MMAIntrinsic::NV_WMMA_F32_16x16x16_F16:
   case MMAIntrinsic::NV_WMMA_F16_16x16x16_F16:
     return {};
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUEnums.td b/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUEnums.td
index 19a64bf..80f6311 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUEnums.td
+++ b/compiler/src/iree/compiler/Codegen/Dialect/GPU/IR/IREEGPUEnums.td
@@ -274,6 +274,10 @@
 def NV_MMA_SYNC_F16_16x8x16_F16 : I32EnumAttrCase<"NV_MMA_SYNC_F16_16x8x16_F16", 0x2021>;
 def NV_MMA_SYNC_F32_16x8x16_BF16 : I32EnumAttrCase<"NV_MMA_SYNC_F32_16x8x16_BF16", 0x2022>;
 
+// NV FP8
+def NV_MMA_SYNC_F32_16x8x32_F8E4M3FN : I32EnumAttrCase<"NV_MMA_SYNC_F32_16x8x32_F8E4M3FN", 0x2023>;
+def NV_MMA_SYNC_F32_16x8x32_F8E5M2 : I32EnumAttrCase<"NV_MMA_SYNC_F32_16x8x32_F8E5M2", 0x2024>;
+
 // opaque NV intrinsics
 def NV_WMMA_F32_16x16x16_F16 : I32EnumAttrCase<"NV_WMMA_F32_16x16x16_F16", 0x2120>;
 def NV_WMMA_F16_16x16x16_F16 : I32EnumAttrCase<"NV_WMMA_F16_16x16x16_F16", 0x2121>;
@@ -394,6 +398,9 @@
       NV_MMA_SYNC_F32_16x8x16_F16,
       NV_MMA_SYNC_F16_16x8x16_F16,
       NV_MMA_SYNC_F32_16x8x16_BF16,
+      // NV FP8 intrinsics (sm_89+).
+      NV_MMA_SYNC_F32_16x8x32_F8E4M3FN,
+      NV_MMA_SYNC_F32_16x8x32_F8E5M2,
       // opaque NV intrinsics.
       NV_WMMA_F32_16x16x16_F16,
       NV_WMMA_F16_16x16x16_F16,
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/GPU/TargetUtils/KnownTargets.cpp b/compiler/src/iree/compiler/Codegen/Dialect/GPU/TargetUtils/KnownTargets.cpp
index 39724bf..65edff2 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/GPU/TargetUtils/KnownTargets.cpp
+++ b/compiler/src/iree/compiler/Codegen/Dialect/GPU/TargetUtils/KnownTargets.cpp
@@ -1020,12 +1020,14 @@
 }
 
 const WgpDetails *getAdaWgpDetails() {
-  // Ada (sm_89) exposes the same tensor-core instructions as Ampere-class
-  // hardware.
+  // Ada (sm_89) adds FP8 mma.sync on top of Ampere-class tensor-core
+  // instructions.
   static const MMAIntrinsic mmaOps[] = {
       MMAIntrinsic::NV_MMA_SYNC_F32_16x8x16_F16,
       MMAIntrinsic::NV_MMA_SYNC_F16_16x8x16_F16,
       MMAIntrinsic::NV_MMA_SYNC_F32_16x8x16_BF16,
+      MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E4M3FN,
+      MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E5M2,
       MMAIntrinsic::NV_WMMA_F32_16x16x16_F16,
       MMAIntrinsic::NV_WMMA_F16_16x16x16_F16,
   };
@@ -1080,6 +1082,8 @@
       MMAIntrinsic::NV_MMA_SYNC_F32_16x8x16_F16,
       MMAIntrinsic::NV_MMA_SYNC_F16_16x8x16_F16,
       MMAIntrinsic::NV_MMA_SYNC_F32_16x8x16_BF16,
+      MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E4M3FN,
+      MMAIntrinsic::NV_MMA_SYNC_F32_16x8x32_F8E5M2,
       MMAIntrinsic::NV_WMMA_F32_16x16x16_F16,
       MMAIntrinsic::NV_WMMA_F16_16x16x16_F16,
   };
diff --git a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/BUILD.bazel b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/BUILD.bazel
index ae991c2..5bc165f 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/BUILD.bazel
+++ b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/BUILD.bazel
@@ -16,6 +16,7 @@
     name = "lit",
     srcs = enforce_glob(
         [
+            "config_tile_and_fuse_sm89.mlir",
             "config_tile_and_fuse_sm120.mlir",
             "config_tile_and_fuse_sm80.mlir",
             "config_vector_distribute_sm80.mlir",
diff --git a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/CMakeLists.txt b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/CMakeLists.txt
index b293f5d..6ca9b6d 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/CMakeLists.txt
@@ -16,6 +16,7 @@
   SRCS
     "config_tile_and_fuse_sm120.mlir"
     "config_tile_and_fuse_sm80.mlir"
+    "config_tile_and_fuse_sm89.mlir"
     "config_vector_distribute_sm80.mlir"
     "pipeline_full_smoketests.mlir"
     "pipeline_tile_and_fuse_mma_sync.mlir"
diff --git a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm120.mlir b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm120.mlir
index 684c23a..aa6fed1 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm120.mlir
+++ b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm120.mlir
@@ -162,3 +162,63 @@
 //  CHECK-SAME:     reduction = [0, 0, 8]
 //  CHECK-SAME:     subgroup = [2, 4, 0]
 //  CHECK-SAME:     workgroup = [64, 64, 0]
+
+// -----
+
+// Test that FP8 matmul selects NV_MMA_SYNC_F32_16x8x32_F8E4M3FN with TileAndFuse.
+
+#pipeline_layout_fp8 = #hal.pipeline.layout<bindings = [
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>
+]>
+func.func @matmul_256x256x256_f8e4m3fn_f32() {
+  %cst = arith.constant 0.000000e+00 : f32
+  %c0 = arith.constant 0 : index
+  %0 = hal.interface.binding.subspan layout(#pipeline_layout_fp8) binding(0) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>>
+  %1 = hal.interface.binding.subspan layout(#pipeline_layout_fp8) binding(1) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>>
+  %2 = hal.interface.binding.subspan layout(#pipeline_layout_fp8) binding(2) alignment(64) offset(%c0) : !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  %3 = iree_tensor_ext.dispatch.tensor.load %0, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>> -> tensor<256x256xf8E4M3FN>
+  %4 = iree_tensor_ext.dispatch.tensor.load %1, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>> -> tensor<256x256xf8E4M3FN>
+  %5 = tensor.empty() : tensor<256x256xf32>
+  %6 = linalg.fill ins(%cst : f32) outs(%5 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  %7 = linalg.matmul ins(%3, %4 : tensor<256x256xf8E4M3FN>, tensor<256x256xf8E4M3FN>) outs(%6 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  iree_tensor_ext.dispatch.tensor.store %7, %2, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : tensor<256x256xf32> -> !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  return
+}
+
+// CHECK-LABEL: func.func @matmul_256x256x256_f8e4m3fn_f32(
+//  CHECK-SAME:   #iree_codegen.translation_info<pipeline = #iree_gpu.pipeline<TileAndFuse> workgroup_size = [128, 1, 1] subgroup_size = 32
+//       CHECK:   linalg.matmul {{.*}}lowering_config = #iree_gpu.lowering_config
+//  CHECK-SAME:     mma_kind = #iree_gpu.mma_layout<NV_MMA_SYNC_F32_16x8x32_F8E4M3FN>
+//  CHECK-SAME:     promote_operands = [0, 1]
+
+// -----
+
+// Test that F8E5M2 matmul selects NV_MMA_SYNC_F32_16x8x32_F8E5M2.
+
+#pipeline_layout_fp8e5m2 = #hal.pipeline.layout<bindings = [
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>
+]>
+func.func @matmul_256x256x256_f8e5m2_f32() {
+  %cst = arith.constant 0.000000e+00 : f32
+  %c0 = arith.constant 0 : index
+  %0 = hal.interface.binding.subspan layout(#pipeline_layout_fp8e5m2) binding(0) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>>
+  %1 = hal.interface.binding.subspan layout(#pipeline_layout_fp8e5m2) binding(1) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>>
+  %2 = hal.interface.binding.subspan layout(#pipeline_layout_fp8e5m2) binding(2) alignment(64) offset(%c0) : !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  %3 = iree_tensor_ext.dispatch.tensor.load %0, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>> -> tensor<256x256xf8E5M2>
+  %4 = iree_tensor_ext.dispatch.tensor.load %1, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>> -> tensor<256x256xf8E5M2>
+  %5 = tensor.empty() : tensor<256x256xf32>
+  %6 = linalg.fill ins(%cst : f32) outs(%5 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  %7 = linalg.matmul ins(%3, %4 : tensor<256x256xf8E5M2>, tensor<256x256xf8E5M2>) outs(%6 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  iree_tensor_ext.dispatch.tensor.store %7, %2, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : tensor<256x256xf32> -> !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  return
+}
+
+// CHECK-LABEL: func.func @matmul_256x256x256_f8e5m2_f32(
+//  CHECK-SAME:   #iree_codegen.translation_info<pipeline = #iree_gpu.pipeline<TileAndFuse> workgroup_size = [128, 1, 1] subgroup_size = 32
+//       CHECK:   linalg.matmul {{.*}}lowering_config = #iree_gpu.lowering_config
+//  CHECK-SAME:     mma_kind = #iree_gpu.mma_layout<NV_MMA_SYNC_F32_16x8x32_F8E5M2>
+//  CHECK-SAME:     promote_operands = [0, 1]
diff --git a/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm89.mlir b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm89.mlir
new file mode 100644
index 0000000..4b4fd09
--- /dev/null
+++ b/compiler/src/iree/compiler/Codegen/LLVMGPU/test/NVVM/config_tile_and_fuse_sm89.mlir
@@ -0,0 +1,66 @@
+// RUN: iree-opt --mlir-print-local-scope --split-input-file --iree-gpu-test-target=sm_89 \
+// RUN:   --pass-pipeline="builtin.module(iree-llvmgpu-select-lowering-strategy)" %s \
+// RUN:   | FileCheck %s
+
+// Tests for Ada (sm_89) FP8 mma.sync strategy selection. F16/BF16 coverage
+// is shared with sm_80 via config_tile_and_fuse_sm80.mlir.
+
+// -----
+
+// Test that F8E4M3FN matmul selects NV_MMA_SYNC_F32_16x8x32_F8E4M3FN.
+
+#pipeline_layout = #hal.pipeline.layout<bindings = [
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>
+]>
+func.func @matmul_256x256x256_f8e4m3fn_f32() {
+  %cst = arith.constant 0.000000e+00 : f32
+  %c0 = arith.constant 0 : index
+  %0 = hal.interface.binding.subspan layout(#pipeline_layout) binding(0) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>>
+  %1 = hal.interface.binding.subspan layout(#pipeline_layout) binding(1) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>>
+  %2 = hal.interface.binding.subspan layout(#pipeline_layout) binding(2) alignment(64) offset(%c0) : !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  %3 = iree_tensor_ext.dispatch.tensor.load %0, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>> -> tensor<256x256xf8E4M3FN>
+  %4 = iree_tensor_ext.dispatch.tensor.load %1, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E4M3FN>> -> tensor<256x256xf8E4M3FN>
+  %5 = tensor.empty() : tensor<256x256xf32>
+  %6 = linalg.fill ins(%cst : f32) outs(%5 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  %7 = linalg.matmul ins(%3, %4 : tensor<256x256xf8E4M3FN>, tensor<256x256xf8E4M3FN>) outs(%6 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  iree_tensor_ext.dispatch.tensor.store %7, %2, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : tensor<256x256xf32> -> !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  return
+}
+
+// CHECK-LABEL: func.func @matmul_256x256x256_f8e4m3fn_f32(
+//  CHECK-SAME:   #iree_codegen.translation_info<pipeline = #iree_gpu.pipeline<TileAndFuse> workgroup_size = [128, 1, 1] subgroup_size = 32
+//       CHECK:   linalg.matmul {{.*}}lowering_config = #iree_gpu.lowering_config
+//  CHECK-SAME:     mma_kind = #iree_gpu.mma_layout<NV_MMA_SYNC_F32_16x8x32_F8E4M3FN>
+//  CHECK-SAME:     promote_operands = [0, 1]
+
+// -----
+
+// Test that F8E5M2 matmul selects NV_MMA_SYNC_F32_16x8x32_F8E5M2.
+
+#pipeline_layout_e5m2 = #hal.pipeline.layout<bindings = [
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>,
+  #hal.pipeline.binding<storage_buffer>
+]>
+func.func @matmul_256x256x256_f8e5m2_f32() {
+  %cst = arith.constant 0.000000e+00 : f32
+  %c0 = arith.constant 0 : index
+  %0 = hal.interface.binding.subspan layout(#pipeline_layout_e5m2) binding(0) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>>
+  %1 = hal.interface.binding.subspan layout(#pipeline_layout_e5m2) binding(1) alignment(64) offset(%c0) flags(ReadOnly) : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>>
+  %2 = hal.interface.binding.subspan layout(#pipeline_layout_e5m2) binding(2) alignment(64) offset(%c0) : !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  %3 = iree_tensor_ext.dispatch.tensor.load %0, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>> -> tensor<256x256xf8E5M2>
+  %4 = iree_tensor_ext.dispatch.tensor.load %1, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : !iree_tensor_ext.dispatch.tensor<readonly:tensor<256x256xf8E5M2>> -> tensor<256x256xf8E5M2>
+  %5 = tensor.empty() : tensor<256x256xf32>
+  %6 = linalg.fill ins(%cst : f32) outs(%5 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  %7 = linalg.matmul ins(%3, %4 : tensor<256x256xf8E5M2>, tensor<256x256xf8E5M2>) outs(%6 : tensor<256x256xf32>) -> tensor<256x256xf32>
+  iree_tensor_ext.dispatch.tensor.store %7, %2, offsets = [0, 0], sizes = [256, 256], strides = [1, 1] : tensor<256x256xf32> -> !iree_tensor_ext.dispatch.tensor<writeonly:tensor<256x256xf32>>
+  return
+}
+
+// CHECK-LABEL: func.func @matmul_256x256x256_f8e5m2_f32(
+//  CHECK-SAME:   #iree_codegen.translation_info<pipeline = #iree_gpu.pipeline<TileAndFuse> workgroup_size = [128, 1, 1] subgroup_size = 32
+//       CHECK:   linalg.matmul {{.*}}lowering_config = #iree_gpu.lowering_config
+//  CHECK-SAME:     mma_kind = #iree_gpu.mma_layout<NV_MMA_SYNC_F32_16x8x32_F8E5M2>
+//  CHECK-SAME:     promote_operands = [0, 1]