Skip to content
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
25d5b4b
Prototype NVFP4 with UE5M3 scales
timmoon10 Aug 7, 2026
dede7c7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 7, 2026
343c4bd
[PyTorch] Enable e5m3 fused GEMM kernels from cuDNN (#2)
kainzhong Aug 13, 2026
8feb5e9
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Aug 13, 2026
ab3a9b3
Use custom recipe for NVFP4-UE5M3 tests
timmoon10 Aug 14, 2026
92b1063
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2026
3d25d20
Add grouped MLP kernel for GGEMM+SwiGLU+RHT+quant
timmoon10 Aug 14, 2026
f030e40
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2026
535ea4f
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Aug 14, 2026
e366e88
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2026
252ae94
Debug integration with GGEMM+GLU+RHT+quant
timmoon10 Aug 15, 2026
f2b9c98
Remove scale max helper functions from NVFP4 cast utils
timmoon10 Aug 18, 2026
8685556
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 18, 2026
803dd24
Fix compile error
timmoon10 Aug 18, 2026
27f29d0
Fix NVFP4 scale dtype ABI
tdophung Aug 18, 2026
c9dad3e
remove redundant output alloc
kainzhong Aug 19, 2026
2bf42eb
no need to pad N now
kainzhong Aug 19, 2026
b6913b2
fix linting errors
kainzhong Aug 19, 2026
f2c26bd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 19, 2026
030e9af
Disable cuDNN GGEMM+GLU+RHT+quant kernel
timmoon10 Aug 20, 2026
9162e98
Guard NVFP4 alpha scaling by scaling mode
tdophung Aug 20, 2026
db0957b
Restore UE5M3 NVFP4 cast support
tdophung Aug 20, 2026
982be7b
Localize NVFP4 4over6 scale policy
tdophung Aug 21, 2026
01f4edc
Tweak arg order in C API functions
timmoon10 Aug 21, 2026
3a63623
Rename cuDNN GGEMM helper functions for general_gemm
timmoon10 Aug 21, 2026
26db2ee
Merge branch 'main' into HEAD
timmoon10 Aug 21, 2026
8e229f1
Fix compilation error in C++ test
timmoon10 Aug 21, 2026
c8e6ced
Treat nvfp4_e4m3_max=0 as unset value
timmoon10 Aug 21, 2026
6e84816
Debug torch.compile test failure
timmoon10 Aug 21, 2026
0c2de5e
Debug test failures
timmoon10 Aug 21, 2026
dd90509
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 21, 2026
d42ccbe
Revert accidental CUTLASS commit change
timmoon10 Aug 21, 2026
b47e1f8
Enable cuDNN GGEMM+GLU+RHT+quant kernel
timmoon10 Aug 21, 2026
ae3020f
Remove incorrect scale_inv_dtype arg to NVFP4Tensor constructor
timmoon10 Aug 22, 2026
1d0d56f
Fix bug when selecting cuDNN GGEMM+GLU+RHT+quant kernel
timmoon10 Aug 22, 2026
b8fa24d
Restore GGEMM+GLU+RHT+amax kernel with RHT sign mask
timmoon10 Aug 24, 2026
30b8af8
Debug minor test failures
timmoon10 Aug 24, 2026
d9b633d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 24, 2026
75bdfe4
Fix incorrect scale dtypes in grouped tensor builder method
timmoon10 Aug 25, 2026
58dfc41
Avoid redundant amax ptr check in row-scaled NVFP4 quantize
timmoon10 Aug 25, 2026
b1a4483
Clean up C++ unit tests
timmoon10 Aug 28, 2026
93cbd0a
Address some review comments from @ptrendx
timmoon10 Aug 28, 2026
d00cbeb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2026
3a62970
Respect no-tensor-scaling in grouped tensor helper function
timmoon10 Aug 28, 2026
e404db8
Debug C++ test compilation error
timmoon10 Aug 29, 2026
adef2dc
Make sure GEMM alpha/beta scales are on GPU, even without amaxes
timmoon10 Aug 30, 2026
a971552
Create new Mcore DDP integration functions rather than breaking backw…
timmoon10 Aug 31, 2026
bb59631
Remove unrelated tests from NVFP4 dequantize C++ unit tests
timmoon10 Aug 31, 2026
85cf34f
Add reference impl and test for NVFP4-UE5M3
timmoon10 Sep 1, 2026
d73bb04
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Sep 1, 2026
bdc1d97
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 1, 2026
dc40106
Make NVFP4-UE5M3 grouped GEMM CUDA-graph safe (#4)
michal2409 Sep 1, 2026
4be085f
Fix JAX NVFP4 GEMM device scalar handling
tdophung Sep 1, 2026
1bf887c
Replace NVFP4 amax heuristic with explicit alpha/beta residency flag
tdophung Sep 2, 2026
9ca3313
Document bugs with `alpha_beta_on_device` GEMM config
timmoon10 Sep 2, 2026
608ce6b
[PyTorch] Do not swizzle operand scales in place in the NVFP4-UE5M3 G…
michal2409 Sep 2, 2026
7a6c88b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 2, 2026
e4fa0f5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 3, 2026
2b4b054
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Sep 3, 2026
1f448ac
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Sep 4, 2026
57d394a
Return the swizzled scales the UE5M3 GEMM helper actually produced (#6)
michal2409 Sep 8, 2026
87edaff
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Sep 8, 2026
41033e8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 8, 2026
9752cc3
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Sep 9, 2026
e91f9ff
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Sep 17, 2026
b024a8c
Merge branch 'main' into nvfp4-ue5m3-prototype
timmoon10 Oct 1, 2026
bba7365
Debug compilation errors
timmoon10 Oct 1, 2026
5abb2e5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 1, 2026
dd5ca67
Debug compile error in NVRTC NVFP4 4over6 kernel
timmoon10 Oct 1, 2026
1bb34dc
Debug NVFP4 UE5M3 in grouped MLP tests
timmoon10 Oct 1, 2026
7b1230a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 1, 2026
1a30247
Fix typo
timmoon10 Oct 2, 2026
bc2dc89
Debug NVFP4 4over6 tests
timmoon10 Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
447 changes: 324 additions & 123 deletions tests/cpp/operator/test_cast_nvfp4_transpose.cu

Large diffs are not rendered by default.

217 changes: 177 additions & 40 deletions tests/cpp/operator/test_dequantize_nvfp4.cu

Large diffs are not rendered by default.

61 changes: 40 additions & 21 deletions tests/cpp/test_common.cu
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const std::string &typeName(DType type) {
{DType::kBFloat16, "bfloat16"},
{DType::kFloat8E4M3, "float8e4m3"},
{DType::kFloat8E5M2, "float8e5m2"},
{DType::kFloat8UE5M3, "float8ue5m3"},
{DType::kFloat8E8M0, "float8e8m0"},
{DType::kFloat4E2M1, "float4e2m1"}};
return name_map.at(type);
Expand Down Expand Up @@ -132,12 +133,16 @@ NVTEShape convertShape(const std::vector<size_t>& s) {
}

std::pair<scale_inv_meta, scale_inv_meta> get_scales(const NVTEShape& shape,
const NVTEScalingMode scaling_mode) {
const NVTEScalingMode scaling_mode,
std::optional<DType> scale_type) {
if (scaling_mode == NVTE_DELAYED_TENSOR_SCALING) {
scale_inv_meta ret;
ret.shape = {1};
ret.type = DType::kFloat32;
ret.type_size_bits = typeToNumBits(DType::kFloat32);
if (!scale_type) {
scale_type = DType::kFloat32;
}
ret.type = *scale_type;
ret.type_size_bits = typeToNumBits(*scale_type);
return {ret, ret};
}
if (scaling_mode == NVTE_MXFP8_1D_SCALING) {
Expand All @@ -160,10 +165,13 @@ std::pair<scale_inv_meta, scale_inv_meta> get_scales(const NVTEShape& shape,
size_t scale_dim_X_colwise = DIVUP_TO_MULTIPLE(last_dim, scale_tensor_alignment_X_colwise);
ret_colwise.shape = {scale_dim_Y_colwise, scale_dim_X_colwise};

ret_rowwise.type = DType::kFloat8E8M0;
ret_rowwise.type_size_bits = typeToNumBits(DType::kFloat8E8M0);
ret_colwise.type = DType::kFloat8E8M0;
ret_colwise.type_size_bits = typeToNumBits(DType::kFloat8E8M0);
if (!scale_type) {
scale_type = DType::kFloat8E8M0;
}
ret_rowwise.type = *scale_type;
ret_rowwise.type_size_bits = typeToNumBits(*scale_type);
ret_colwise.type = *scale_type;
ret_colwise.type_size_bits = typeToNumBits(*scale_type);

return {ret_rowwise, ret_colwise};
}
Expand All @@ -188,10 +196,13 @@ std::pair<scale_inv_meta, scale_inv_meta> get_scales(const NVTEShape& shape,
size_t scale_dim_X_t = DIVUP_TO_MULTIPLE(DIVUP(first_dim, 16lu), scale_tensor_alignment_X_rowwise);
ret_colwise.shape = {scale_dim_Y_t, scale_dim_X_t};

ret_rowwise.type = DType::kFloat8E4M3;
ret_rowwise.type_size_bits = typeToNumBits(DType::kFloat8E4M3);
ret_colwise.type = DType::kFloat8E4M3;
ret_colwise.type_size_bits = typeToNumBits(DType::kFloat8E4M3);
if (!scale_type) {
scale_type = DType::kFloat8E4M3;
}
ret_rowwise.type = *scale_type;
ret_rowwise.type_size_bits = typeToNumBits(*scale_type);
ret_colwise.type = *scale_type;
ret_colwise.type_size_bits = typeToNumBits(*scale_type);

return {ret_rowwise, ret_colwise};
}
Expand All @@ -215,10 +226,14 @@ std::pair<scale_inv_meta, scale_inv_meta> get_scales(const NVTEShape& shape,
size_t scale_dim_1 = DIVUP(DIVUP(first_dim, 128lu), 4) * 4;
ret_colwise.shape = {scale_dim_0, scale_dim_1};
}
ret_rowwise.type = DType::kFloat32;
ret_colwise.type = DType::kFloat32;
ret_rowwise.type_size_bits = typeToNumBits(DType::kFloat32);
ret_colwise.type_size_bits = typeToNumBits(DType::kFloat32);

if (!scale_type) {
scale_type = DType::kFloat32;
}
ret_rowwise.type = *scale_type;
ret_colwise.type = *scale_type;
ret_rowwise.type_size_bits = typeToNumBits(*scale_type);
ret_colwise.type_size_bits = typeToNumBits(*scale_type);

return {ret_rowwise, ret_colwise};
}
Expand All @@ -241,10 +256,13 @@ std::pair<scale_inv_meta, scale_inv_meta> get_scales(const NVTEShape& shape,
size_t scale_dim_1 = DIVUP(last_dim, 4) * 4;
ret_colwise.shape = {scale_dim_0, scale_dim_1};
}
ret_rowwise.type = DType::kFloat32;
ret_colwise.type = DType::kFloat32;
ret_rowwise.type_size_bits = typeToNumBits(DType::kFloat32);
ret_colwise.type_size_bits = typeToNumBits(DType::kFloat32);
if (!scale_type) {
scale_type = DType::kFloat32;
}
ret_rowwise.type = *scale_type;
ret_colwise.type = *scale_type;
ret_rowwise.type_size_bits = typeToNumBits(*scale_type);
ret_colwise.type_size_bits = typeToNumBits(*scale_type);
return {ret_rowwise, ret_colwise};
}

Expand Down Expand Up @@ -278,7 +296,8 @@ void Tensor::Buffer::from_cpu() {
Tensor::Tensor(const std::string& name,
const NVTEShape &shape, const DType type,
const bool rowwise, const bool columnwise,
const NVTEScalingMode &scaling_mode)
const NVTEScalingMode &scaling_mode,
const std::optional<DType> scale_type)
: tensor_(scaling_mode), rowwise_{rowwise}, columnwise_{columnwise}, name_{name} {
// Initialize RNG
const size_t seed = create_seed_from_tensor_name(name);
Expand Down Expand Up @@ -373,7 +392,7 @@ Tensor::Tensor(const std::string& name,
case NVTE_NVFP4_1D_SCALING:
{
// Block scaling factors
auto [rowwise_scale_meta, colwise_scale_meta] = get_scales(flattened_shape, tensor_.scaling_mode());
auto [rowwise_scale_meta, colwise_scale_meta] = get_scales(flattened_shape, tensor_.scaling_mode(), scale_type);
if (rowwise) {
const auto scale_shape = rowwise_scale_meta.shape;
const auto scale_dtype = rowwise_scale_meta.type;
Expand Down
32 changes: 27 additions & 5 deletions tests/cpp/test_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ using bf16 = nv_bfloat16;
using fp8e4m3 = __nv_fp8_e4m3;
using fp8e5m2 = __nv_fp8_e5m2;
using fp8e8m0 = uint8_t;
#if CUDA_VERSION >= 13040
using fp8ue5m3 = __nv_fp8_ue5m3;
#endif
#if FP4_TYPE_SUPPORTED
using fp4e2m1 = __nv_fp4_e2m1;
using fp4e2m1x2 = __nv_fp4x2_e2m1;
Expand All @@ -91,7 +94,12 @@ struct BitsNumber {
template <typename T>
struct TypeInfo {
#if FP4_TYPE_SUPPORTED
using types = std::tuple<byte, int16, int32, int64, fp32, fp16, bf16, fp8e4m3, fp8e5m2, fp8e8m0, fp4e2m1>;
using types = std::tuple<byte, int16, int32, int64, fp32, fp16, bf16, fp8e4m3,
fp8e5m2, fp8e8m0, fp4e2m1
#if CUDA_VERSION >= 13040
, fp8ue5m3
#endif
>;
#else
using types = std::tuple<byte, int16, int32, int64, fp32, fp16, bf16, fp8e4m3, fp8e5m2, fp8e8m0>;
#endif
Expand Down Expand Up @@ -151,15 +159,18 @@ class Tensor {
const NVTEShape &shape, const DType type,
const bool rowwise = true,
const bool columnwise = false,
const NVTEScalingMode &mode = NVTE_DELAYED_TENSOR_SCALING);
const NVTEScalingMode &mode = NVTE_DELAYED_TENSOR_SCALING,
const std::optional<DType> scale_dtype = std::nullopt);

Tensor(const std::string& name,
const std::vector<size_t> &shape,
const DType type,
const bool rowwise = true,
const bool columnwise = false,
const NVTEScalingMode &mode = NVTE_DELAYED_TENSOR_SCALING) :
Tensor(name, nvte_make_shape(shape.data(), shape.size()), type, rowwise, columnwise, mode) {}
const NVTEScalingMode &mode = NVTE_DELAYED_TENSOR_SCALING,
const std::optional<DType> scale_type = std::nullopt) :
Tensor(name, nvte_make_shape(shape.data(), shape.size()), type, rowwise, columnwise, mode,
scale_type) {}

Tensor() = default;

Expand Down Expand Up @@ -635,6 +646,16 @@ GroupedBuffers build_grouped_tensor(const std::vector<Tensor*>& tensors,
#define SWITCH_FP4_TYPE_HANDLE(type, ...) // do nothing
#endif

#if CUDA_VERSION >= 13040
#define SWITCH_UE5M3_TYPE_HANDLE(type, ...) \
case DType::kFloat8UE5M3: { \
using type = fp8ue5m3; \
{ __VA_ARGS__ } \
} break;
#else
#define SWITCH_UE5M3_TYPE_HANDLE(type, ...) // do nothing
#endif

#define TRANSFORMER_ENGINE_TYPE_SWITCH_ALL(dtype, type, ...) \
switch (dtype) { \
using namespace transformer_engine; \
Expand Down Expand Up @@ -693,7 +714,8 @@ GroupedBuffers build_grouped_tensor(const std::vector<Tensor*>& tensors,
} \
break; \
SWITCH_FP4_TYPE_HANDLE(type, __VA_ARGS__) \
default: \
SWITCH_UE5M3_TYPE_HANDLE(type, __VA_ARGS__) \
default: \
printf("dtype: %d\n", static_cast<int>(dtype)); \
NVTE_ERROR("Invalid type."); \
}
Expand Down
158 changes: 158 additions & 0 deletions tests/pytorch/nvfp4/test_nvfp4_gemm_exact.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,55 @@
recipe_available, reason_for_no_recipe = te.is_nvfp4_available(return_reason=True)


@pytest.mark.skipif(not recipe_available, reason=reason_for_no_recipe)
@pytest.mark.parametrize(
"disable_x, disable_w",
[(True, False), (False, True), (True, True)],
ids=["x_unit_global_scale", "w_unit_global_scale", "both_unit_global_scale"],
)
def test_gemm_with_missing_nvfp4_amax(disable_x: bool, disable_w: bool) -> None:
"""A null amax contributes a unit global scale to GEMM alpha."""
torch.manual_seed(0)
x = torch.randn((128, 128), dtype=torch.bfloat16, device="cuda")
w = torch.randn((128, 128), dtype=torch.bfloat16, device="cuda")
unit_scale_amax = 448.0 * 6.0
x[0, 0] = unit_scale_amax
w[0, 0] = unit_scale_amax

def quantize(tensor: torch.Tensor, disable_second_level_scale: bool):
return NVFP4Quantizer(
rowwise=True,
columnwise=True,
disable_second_level_scale=disable_second_level_scale,
)(tensor)

x_ref, w_ref = quantize(x, False), quantize(w, False)
x_test, w_test = quantize(x, disable_x), quantize(w, disable_w)

def gemm(w_q, x_q):
workspace = torch.empty(4, dtype=torch.uint8, device="cuda")
return tex.generic_gemm(
w_q,
True,
x_q,
False,
None,
None,
TE_DType[torch.bfloat16],
None,
TE_DType[torch.bfloat16],
False,
None,
False,
workspace,
workspace.numel(),
False,
False,
)[0]

torch.testing.assert_close(gemm(w_test, x_test), gemm(w_ref, x_ref), atol=0, rtol=0)


def check_nvfp4_gemm_versus_reference(
x_dtype: torch.dtype,
w_dtype: torch.dtype,
Expand Down Expand Up @@ -641,3 +690,112 @@ def test_nvfp4_row_scaled_gemm_matches_emulated(
use_4over6=use_4over6,
nvfp4_4over6_err_mode=nvfp4_4over6_err_mode,
)


def _check_ue5m3_gemm_versus_dequantized(
M, K, N, x_columnwise, w_columnwise, disable_second_level_scale
):
"""Run an NVFP4/UE5M3 GEMM and compare against a dequantized FP32 reference."""
torch.manual_seed(0)
device, dtype, out_dtype = "cuda", torch.bfloat16, torch.bfloat16
x_shape = (K, M) if x_columnwise else (M, K)
w_shape = (K, N) if w_columnwise else (N, K)
x = torch.randn(x_shape, dtype=dtype, device=device)
w = torch.randn(w_shape, dtype=dtype, device=device)

common = dict(
fp4_dtype=tex.DType.kFloat4E2M1,
scale_dtype=tex.DType.kFloat8UE5M3,
rowwise=True,
columnwise=True,
with_amax_reduction=False,
amax_reduction_group=None,
with_rht=False,
with_post_rht_amax=False,
)
# disable_second_level_scale is given per operand, as (x, w).
xq = NVFP4Quantizer(**common, disable_second_level_scale=disable_second_level_scale[0])
wq = NVFP4Quantizer(**common, disable_second_level_scale=disable_second_level_scale[1])
x_q = xq.update_quantized(x, xq.make_empty(x_shape, dtype=dtype, device=device))
w_q = wq.update_quantized(w, wq.make_empty(w_shape, dtype=dtype, device=device))

if disable_second_level_scale[0]:
assert x_q._amax_rowwise is None, "disable_second_level_scale should drop the amax"
if disable_second_level_scale[1]:
assert w_q._amax_rowwise is None, "disable_second_level_scale should drop the amax"

# Reference: dequantize the orientation each operand is actually read in.
x_ref = _dequantize_nvfp4_usage(x_q, columnwise=x_columnwise)
w_ref = _dequantize_nvfp4_usage(w_q, columnwise=w_columnwise)
# _dequantize_nvfp4_usage returns each operand canonically as (rows, K), so
# the reference is the same expression for every layout.
ref = x_ref @ w_ref.t()

if x_columnwise:
x_q.update_usage(rowwise_usage=False)
if w_columnwise:
w_q.update_usage(rowwise_usage=False)
transa, transb = not w_columnwise, x_columnwise
layout = ("T" if transa else "N") + ("T" if transb else "N")
y = general_gemm(w_q, x_q, out_dtype=out_dtype, layout=layout)[0]

# Both sides see identically quantized operands, so quantization error cancels and
# only accumulation order and the bf16 output rounding differ. One bf16 ulp is
# already ~4e-3 relative, which no elementwise tolerance survives, so compare the
# whole result instead.
rel_err = (y.float() - ref).norm() / ref.norm()
assert rel_err < 5e-3, f"relative error {rel_err:.2e} is too large"


ue5m3_available, reason_for_no_ue5m3 = te.is_fp8_ue5m3_available(return_reason=True)


@pytest.mark.skipif(not recipe_available, reason=reason_for_no_recipe)
@pytest.mark.skipif(not ue5m3_available, reason=reason_for_no_ue5m3)
@pytest.mark.parametrize(
"M, K, N",
[
(256, 128, 256),
(256, 256, 256),
(256, 1024, 256),
(1024, 1024, 1024),
(4096, 512, 3072),
(112, 128, 96),
(304, 640, 304),
(1008, 3072, 992),
(256, 64, 256),
(128, 128, 112),
],
)
@pytest.mark.parametrize(
"x_columnwise, w_columnwise",
[
(False, False), # TN -- w rowwise, x rowwise (fprop)
(False, True), # NN -- w colwise, x rowwise (dgrad)
(True, True), # NT -- w colwise, x colwise (wgrad)
],
ids=["FF", "FT", "TT"],
)
@pytest.mark.parametrize(
"disable_second_level_scale",
[
(True, False),
],
ids=["TF"],
)
def test_nvfp4_ue5m3_gemm_versus_reference(
M: int,
K: int,
N: int,
x_columnwise: bool,
w_columnwise: bool,
disable_second_level_scale: bool,
):
"""NVFP4 GEMM with UE5M3 block scales, with and without second-level scaling.

UE5M3's wider range is what makes dropping the per-tensor global scale
viable, so both configurations must match the dequantized reference.
"""
_check_ue5m3_gemm_versus_dequantized(
M, K, N, x_columnwise, w_columnwise, disable_second_level_scale
)
Loading
Loading