Skip to content

Commit a11a484

Browse files
authored
enable blockwise FP8 quantization on rocm (#609)
1 parent bc3766e commit a11a484

13 files changed

Lines changed: 479 additions & 20 deletions

ci/pytorch.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ run_test_config(){
5050
fi
5151
run 1 test_cuda_graphs.py
5252
run_default_fa 1 test_deferred_init.py
53-
run_default_fa 1 test_quantized_tensor.py
5453
run_default_fa 1 test_float8_current_scaling_exact.py
54+
run_default_fa 1 test_float8blockwisetensor.py
55+
run_default_fa 1 test_float8_blockwise_scaling_exact.py
56+
run_default_fa 1 test_quantized_tensor.py
5557
test $_fus_attn = auto -o $_fus_attn = ck && run 1 test_cpu_offloading.py
5658
test $_fus_attn = auto -o $_fus_attn = ck -o $_fus_attn = aotriton && NVTE_FLASH_ATTN=0 NVTE_CPU_OFFLOAD_V1=1 run 3 test_cpu_offloading_v1.py
5759
run_default_fa 1 test_fused_rope.py

tests/cpp/operator/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ add_executable(test_operator
1515
test_cast_mxfp8.cu
1616
test_cast_mxfp8_grouped.cu
1717
test_cast_nvfp4_transpose.cu
18-
test_cast_float8blockwise.cu #CUDA-only test
18+
test_cast_float8blockwise.cu
1919
test_dequantize_mxfp8.cu
2020
test_dequantize_mxfp8_grouped.cu
2121
test_transpose.cu
@@ -42,7 +42,6 @@ if(USE_ROCM)
4242
get_target_property(test_cuda_sources test_operator SOURCES)
4343
# Remove CUDA-only tests and add ROCm specific ones
4444
list(REMOVE_ITEM test_cuda_sources
45-
test_cast_float8blockwise.cu
4645
test_grouped_gemm.cu)
4746
list(APPEND test_cuda_sources
4847
test_dequantize_nvfp4.cu

tests/cpp/operator/test_cast_float8blockwise.cu

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/*************************************************************************
2+
* This file was modified for portability to AMDGPU
3+
* Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
24
* Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
35
*
46
* See LICENSE for license information.
@@ -227,17 +229,28 @@ void ref_quantize_onedimensional_blocks(const ProcessingMethod processing_method
227229
}
228230

229231
inline size_t scale_align_stride(size_t inner_elements) {
232+
#ifdef __HIP_PLATFORM_AMD__
233+
return inner_elements;
234+
#else
230235
return ((inner_elements + 4u - 1u) / 4u) * 4u;
236+
#endif
231237
};
232238

233239
void compare_scaling_factors(const std::string& name, const float* test, const float* ref,
234240
const size_t row_blocks, const size_t col_blocks,
235241
const size_t test_stride, const size_t ref_stride) {
242+
#ifdef __HIP_PLATFORM_AMD__
243+
const float atol = 1e-6f;
244+
#endif
236245
for (int i = 0; i < row_blocks; ++i) {
237246
for (int j = 0; j < col_blocks; ++j) {
238247
const int test_idx = i * test_stride + j;
239248
const int ref_idx = i * ref_stride + j;
249+
#ifdef __HIP_PLATFORM_AMD__
250+
ASSERT_FALSE(std::abs(test[test_idx] - ref[ref_idx]) > atol)
251+
#else
240252
ASSERT_FALSE(test[test_idx] != ref[ref_idx])
253+
#endif
241254
<< "Error in " << name << std::endl
242255
<< "Mismatch: " << test[test_idx] << " vs " << ref[ref_idx] << " at index " << test_idx
243256
<< "," << ref_idx;
@@ -248,12 +261,19 @@ void compare_scaling_factors(const std::string& name, const float* test, const f
248261
void compare_scaling_factors_one_dimensional_blocks(const std::string& name, const float* test,
249262
const float* ref, const size_t rows,
250263
const size_t col_blocks) {
264+
#ifdef __HIP_PLATFORM_AMD__
265+
const float atol = 1e-6f;
266+
#endif
251267
const size_t test_stride = scale_align_stride(rows);
252268
for (int i = 0; i < rows; ++i) {
253269
for (int j = 0; j < col_blocks; ++j) {
254270
const int test_idx = i + test_stride * j;
255271
const int ref_idx = i + rows * j;
272+
#ifdef __HIP_PLATFORM_AMD__
273+
ASSERT_FALSE(std::abs(test[test_idx] - ref[ref_idx]) > atol)
274+
#else
256275
ASSERT_FALSE(test[test_idx] != ref[ref_idx])
276+
#endif
257277
<< "Error in " << name << std::endl
258278
<< "Mismatch: " << test[test_idx] << " vs " << ref[ref_idx] << " at index " << test_idx
259279
<< "," << ref_idx;

tests/pytorch/references/blockwise_quantizer_reference.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ def quantize(
278278
assert quant_dtype in (
279279
torch.float8_e4m3fn,
280280
torch.float8_e5m2,
281+
torch.float8_e4m3fnuz,
282+
torch.float8_e5m2fnuz,
281283
), "Unsupported quant dtype."
282284

283285
assert quant_tile_shape in ((1, 128), (128, 128))

tests/pytorch/test_float8_blockwise_scaling_exact.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# This file was modified for portability to AMDGPU
2+
# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
13
# Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
24
#
35
# See LICENSE for license information.
@@ -15,6 +17,10 @@
1517
Float8BlockQuantizer,
1618
get_device_compute_capability,
1719
)
20+
from transformer_engine.pytorch.utils import (
21+
get_torch_float8_e4m3_type,
22+
get_torch_float8_e5m2_type,
23+
)
1824
from references.blockwise_quantizer_reference import (
1925
BlockwiseQuantizerReference,
2026
QuantizeResult,
@@ -24,12 +30,18 @@
2430
TestFP8RecipeLayerNormLinearBase,
2531
)
2632

33+
fp8_e4m3_type = get_torch_float8_e4m3_type()
34+
fp8_e5m2_type = get_torch_float8_e5m2_type()
35+
2736
# read env variable NVTE_TEST_FLOAT8_BLOCK_SCALING_EXACT_TENSOR_DUMP_DIR to override the default tensor dump directory
2837
TENSOR_DUMP_DIR = pathlib.Path(__file__).resolve().parent.parent.parent / "tensor_dumps"
2938
tensor_dump_dir_env = os.getenv("NVTE_TEST_BLOCK_CURRENT_SCALING_EXACT_TENSOR_DUMP_DIR")
3039
if tensor_dump_dir_env is not None:
3140
TENSOR_DUMP_DIR = pathlib.Path(tensor_dump_dir_env)
32-
recipe_available, reason_for_no_recipe = te.is_fp8_block_scaling_available(return_reason=True)
41+
recipe_available, reason_for_no_recipe = te.is_fp8_block_scaling_quantization_available(
42+
return_reason=True
43+
)
44+
gemm_available, reason_for_no_gemm = te.is_fp8_block_scaling_available(return_reason=True)
3345
recipe_emulated = get_device_compute_capability() >= (10, 0)
3446

3547

@@ -213,7 +225,7 @@ def check_quantization_block_tiling_versus_reference(
213225
],
214226
)
215227
@pytest.mark.parametrize("x_dtype", [torch.float32, torch.bfloat16], ids=str)
216-
@pytest.mark.parametrize("quant_dtype", [torch.float8_e4m3fn, torch.float8_e5m2], ids=str)
228+
@pytest.mark.parametrize("quant_dtype", [fp8_e4m3_type, fp8_e5m2_type], ids=str)
217229
@pytest.mark.parametrize("eps", [0], ids=["eps_0"])
218230
@pytest.mark.parametrize(
219231
"return_transpose", [True, False], ids=["quantize_transpose", "quantize_only"]
@@ -248,7 +260,7 @@ def test_quantization_block_tiling_versus_reference(
248260
],
249261
)
250262
@pytest.mark.parametrize("x_dtype", [torch.float32, torch.bfloat16], ids=str)
251-
@pytest.mark.parametrize("quant_dtype", [torch.float8_e4m3fn, torch.float8_e5m2], ids=str)
263+
@pytest.mark.parametrize("quant_dtype", [fp8_e4m3_type, fp8_e5m2_type], ids=str)
252264
@pytest.mark.parametrize("eps", [0], ids=["eps_0"])
253265
@pytest.mark.parametrize(
254266
"return_transpose", [True, False], ids=["quantize_transpose", "quantize_only"]
@@ -279,7 +291,7 @@ def test_quantization_block_tiling_versus_reference_fp32_scales(
279291
],
280292
)
281293
@pytest.mark.parametrize("x_dtype", [torch.float32, torch.bfloat16], ids=str)
282-
@pytest.mark.parametrize("quant_dtype", [torch.float8_e4m3fn, torch.float8_e5m2], ids=str)
294+
@pytest.mark.parametrize("quant_dtype", [fp8_e4m3_type, fp8_e5m2_type], ids=str)
283295
@pytest.mark.parametrize("eps", [0], ids=["eps_0"])
284296
@pytest.mark.parametrize("pow_2_scales", [True, False], ids=["pow2scales", "fp32scales"])
285297
@pytest.mark.parametrize("tile_size", [(128, 128)])
@@ -378,7 +390,7 @@ def test_quantization_block_tiling_extrema_versus_reference(
378390

379391

380392
# FP8 per tesnor current scaling
381-
@pytest.mark.skipif(not recipe_available, reason=reason_for_no_recipe)
393+
@pytest.mark.skipif(not gemm_available, reason=reason_for_no_gemm)
382394
class TestFP8BlockScalingRecipeLinear(TestFP8RecipeLinearBase):
383395

384396
@staticmethod
@@ -438,7 +450,7 @@ def test_fp8_current_scaling_with_linear_module(
438450
)
439451

440452

441-
@pytest.mark.skipif(not recipe_available, reason=reason_for_no_recipe)
453+
@pytest.mark.skipif(not gemm_available, reason=reason_for_no_gemm)
442454
class TestFP8BlockScalingRecipeLayerNormLinear(TestFP8RecipeLayerNormLinearBase):
443455

444456
@staticmethod

tests/pytorch/test_float8blockwisetensor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ def _to_list(x: Union[Iterable, Any]) -> List:
4545
DimsType = Union[Iterable[int], int]
4646

4747
# TODO replace with call to fp8.py when recipe added.
48-
recipe_available = not IS_HIP_EXTENSION and (get_device_compute_capability() >= (9, 0) and float(torch.version.cuda) >= 12.8)
48+
if IS_HIP_EXTENSION:
49+
recipe_available = get_device_compute_capability() >= (9, 4)
50+
else:
51+
recipe_available = get_device_compute_capability() >= (9, 0) and float(torch.version.cuda) >= 12.8
4952
reason_for_no_recipe = "Quantize kernels require TMA and are only relevant with GEMMS."
5053

5154

transformer_engine/common/CMakeLists.txt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ list(APPEND transformer_engine_cuda_sources
245245
transpose/cast_transpose_fusion.cu
246246
transpose/transpose_fusion.cu
247247
transpose/multi_cast_transpose.cu
248-
transpose/quantize_transpose_vector_blockwise.cu #CUDA-only
248+
transpose/quantize_transpose_vector_blockwise.cu
249249
transpose/swap_first_dims.cu
250250
dropout/dropout.cu
251251
fused_attn/flash_attn.cu
@@ -279,7 +279,6 @@ list(APPEND transformer_engine_cuda_sources
279279
comm_gemm_overlap/userbuffers/userbuffers.cu)
280280

281281
set(cuda_only_cuda_sources
282-
transpose/quantize_transpose_vector_blockwise.cu
283282
fused_attn/fused_attn_f16_max512_seqlen.cu
284283
fused_attn/fused_attn_f16_arbitrary_seqlen.cu
285284
fused_attn/fused_attn_fp8.cu
@@ -303,7 +302,7 @@ list(APPEND transformer_engine_cuda_arch_specific_sources
303302
multi_tensor/compute_scale.cu
304303
recipe/mxfp8_scaling.cu
305304
recipe/nvfp4.cu
306-
transpose/quantize_transpose_square_blockwise.cu #CUDA-only
305+
transpose/quantize_transpose_square_blockwise.cu
307306
transpose/quantize_transpose_vector_blockwise_fp4.cu)
308307

309308
set(cuda_only_cuda_arch_specific_sources
@@ -314,8 +313,7 @@ set(cuda_only_cuda_arch_specific_sources
314313
hadamard_transform/row_cast_col_hadamard_transform_cast_fusion.cu
315314
hadamard_transform/group_hadamard_transform_cast_fusion.cu
316315
hadamard_transform/group_row_cast_col_hadamard_transform_cast_fusion.cu
317-
hadamard_transform/graph_safe_group_row_cast_col_hadamard_transform_cast_fusion.cu
318-
transpose/quantize_transpose_square_blockwise.cu)
316+
hadamard_transform/graph_safe_group_row_cast_col_hadamard_transform_cast_fusion.cu)
319317

320318
# Compiling the files with the worst compilation time first to hopefully overlap
321319
# better with the faster-compiling cpp files

transformer_engine/common/cast/dispatch/quantize.cuh

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ void quantize_fwd_helper(const NVTETensor input, NVTETensor output,
164164
#endif
165165
break;
166166
}
167-
#ifndef __HIP_PLATFORM_AMD__
168167
case NVTE_BLOCK_SCALING_2D: {
169168
// TODO(kwyss): IS_ACT, ParamOP, OP parameters support.
170169
NVTE_CHECK(!IS_ACT, "IS_ACT is not implemented for FWD NVTE_BLOCK_SCALING_2D");
@@ -196,7 +195,6 @@ void quantize_fwd_helper(const NVTETensor input, NVTETensor output,
196195
columnwise_option, force_pow_2_scales, noop_tensor->data, stream);
197196
break;
198197
}
199-
#endif//#ifndef __HIP_PLATFORM_AMD__
200198
default:
201199
NVTE_ERROR("Not implemented scaling mode: " + to_string(output_tensor->scaling_mode) + ".");
202200
}
@@ -317,7 +315,6 @@ void quantize_bwd_helper(const NVTETensor grad, const NVTETensor input, NVTETens
317315
#endif
318316
break;
319317
}
320-
#ifndef __HIP_PLATFORM_AMD__
321318
case NVTE_BLOCK_SCALING_2D: {
322319
// TODO(kwyss): IS_BIAS, IS_DACT, ParamOP, OP parameters support.
323320
NVTE_CHECK((!IS_DBIAS && !IS_DACT),
@@ -351,7 +348,6 @@ void quantize_bwd_helper(const NVTETensor grad, const NVTETensor input, NVTETens
351348
columnwise_option, force_pow_2_scales, noop_tensor->data, stream);
352349
break;
353350
}
354-
#endif //#ifndef __HIP_PLATFORM_AMD__
355351
default:
356352
NVTE_ERROR("Not implemented scaling mode: " + to_string(output_tensor->scaling_mode) + ".");
357353
}

transformer_engine/common/recipe/recipe_common.cuh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/*************************************************************************
2+
* This file was modified for portability to AMDGPU
3+
* Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
24
* Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
35
*
46
* See LICENSE for license information.
@@ -61,10 +63,16 @@ __device__ __forceinline__ float compute_scale_from_amax(float amax, float max_f
6163
template <typename IType, typename OType>
6264
__device__ __forceinline__ float compute_scale_from_types(const float amax, const float eps,
6365
const float pow_2_scaling) {
66+
// On AMD host, TypeExtrema<fp8e4m3>::max is non-constexpr (runtime FNUZ detection)
67+
#if defined(__HIP_PLATFORM_AMD__) && !defined(__HIP_DEVICE_COMPILE__)
68+
const float fp8_max = detail::TypeExtrema<OType>::max;
69+
const float value_for_inf = detail::TypeExtrema<IType>::max;
70+
#else
6471
constexpr float fp8_max = TypeInfo<OType>::max_finite_value;
6572
// NOTE: We're relying on compute_scale_from_amax to have behavior where it
6673
// clips the mantissa of the max_finite_value if power of 2 scaling applies.
6774
constexpr float value_for_inf = TypeInfo<IType>::max_finite_value;
75+
#endif
6876
return compute_scale_from_amax(amax, fp8_max, pow_2_scaling, eps, value_for_inf);
6977
}
7078

transformer_engine/common/transpose/quantize_transpose_square_blockwise.cu

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/*************************************************************************
2+
* This file was modified for portability to AMDGPU
3+
* Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
24
* Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
35
*
46
* See LICENSE for license information.
@@ -17,6 +19,9 @@
1719
#include "common/util/cuda_runtime.h"
1820
#include "common/util/ptx.cuh"
1921
#include "common/utils.cuh"
22+
#ifdef __HIP_PLATFORM_AMD__
23+
#include "common/util/rocm_device_utils.cuh"
24+
#endif
2025

2126
#if (!defined(__CUDA_MINIMUM_ARCH__) && __CUDA_ARCH__ >= 900) || \
2227
(defined(__CUDA_MINIMUM_ARCH__) && __CUDA_MINIMUM_ARCH__ >= 900)
@@ -28,7 +33,11 @@ namespace {
2833

2934
// const values configuration
3035

36+
#if defined(__HIP_PLATFORM_AMD__) && !defined(__gfx1250__)
37+
constexpr size_t kThreadsPerWarp = 64;
38+
#else
3139
constexpr size_t kThreadsPerWarp = 32;
40+
#endif
3241
#ifdef TMA_HW_SUPPORTED
3342
constexpr size_t BLOCK_TILE_DIM = 128;
3443
constexpr size_t WARP_TILE_DIM_X = 32;
@@ -40,8 +49,12 @@ constexpr size_t BLOCK_TILE_DIM = 128;
4049
constexpr size_t WARP_TILE_DIM_X = 64;
4150
constexpr size_t WARP_TILE_DIM_Y = 32;
4251
constexpr size_t THREAD_TILE_DIM_X = 8;
52+
#if defined(__HIP_PLATFORM_AMD__) && !defined(__gfx1250__)
53+
constexpr size_t THREAD_TILE_DIM_Y = 4;
54+
#else
4355
constexpr size_t THREAD_TILE_DIM_Y = 8;
4456
#endif
57+
#endif
4558

4659
#ifdef TMA_HW_SUPPORTED
4760
constexpr size_t NUM_BYTES_PER_BANK = 4;
@@ -62,6 +75,7 @@ constexpr size_t NUM_THREADS_Y_IN_WARP = kThreadsPerWarp / NUM_THREADS_X_IN_WARP
6275

6376
#define MIN(a, b) (a < b ? a : b)
6477

78+
#ifndef __HIP_PLATFORM_AMD__
6579
template <bool kReturnTranspose, typename CType, typename IType, typename OType>
6680
__global__ void __launch_bounds__(THREADS_PER_BLOCK)
6781
block_scaled_cast_transpose_kernel(const IType* const input, OType* const output_c,
@@ -247,6 +261,7 @@ __global__ void __launch_bounds__(THREADS_PER_BLOCK)
247261
#endif
248262
}
249263
}
264+
#endif // __HIP_PLATFORM_AMD__
250265

251266
template <bool kReturnTranspose, typename CType, typename IType, typename OType>
252267
__global__ void __launch_bounds__(THREADS_PER_BLOCK) block_scaled_cast_transpose_kernel_notaligned(
@@ -357,10 +372,14 @@ __global__ void __launch_bounds__(THREADS_PER_BLOCK) block_scaled_cast_transpose
357372
}
358373
}
359374
// Reduce amax in the warp (32x32 tile)
375+
#ifdef __HIP_PLATFORM_AMD__
376+
warp_tile_amax = rocm_subwarp_allreduce<kThreadsPerWarp>(amax, rocm_op::max{});
377+
#else
360378
warp_tile_amax = warp_reduce_max<kThreadsPerWarp>(amax);
361379
// broadcast the amax to all threads in a warp from the lane 0
362380
constexpr int lane_zero = 0;
363381
warp_tile_amax = __shfl_sync(0xFFFFFFFF, warp_tile_amax, lane_zero);
382+
#endif
364383

365384
// reduce warp_tile_amax across multiple warps in a thread block using shared mem
366385
if (tid_in_warp == 0) {
@@ -456,6 +475,7 @@ __global__ void __launch_bounds__(THREADS_PER_BLOCK) block_scaled_cast_transpose
456475
}
457476
}
458477

478+
#ifndef __HIP_PLATFORM_AMD__
459479
template <typename OutputType>
460480
CUtensorMap get_tensor_map(const SimpleTensor& tensor, size_t global_dim_x, size_t global_dim_y) {
461481
CUtensorMapDataType dataType;
@@ -473,6 +493,7 @@ CUtensorMap get_tensor_map(const SimpleTensor& tensor, size_t global_dim_x, size
473493
/*stride_elems=*/global_dim_x, /*offset_elems=*/0, sizeof(OutputType) * 8);
474494
return tensor_map_output_trans;
475495
}
496+
#endif // __HIP_PLATFORM_AMD__
476497

477498
} // namespace
478499
} // namespace transformer_engine
@@ -543,6 +564,7 @@ void quantize_transpose_square_blockwise(const SimpleTensor& input, SimpleTensor
543564
return_transpose, kReturnTranspose,
544565

545566
dim3 grid(num_blocks_x, num_blocks_y, 1);
567+
#ifndef __HIP_PLATFORM_AMD__
546568
const bool full_tile =
547569
row_length % BLOCK_TILE_DIM == 0 && num_rows % BLOCK_TILE_DIM == 0;
548570

@@ -573,6 +595,20 @@ void quantize_transpose_square_blockwise(const SimpleTensor& input, SimpleTensor
573595
scale_stride_x, scale_stride_y, scale_t_stride_x, scale_t_stride_y, epsilon,
574596
pow_2_scale, noop_ptr);
575597
} // full-tile
598+
#else
599+
const size_t threads_per_block =
600+
(transformer_engine::cuda::sm_arch(transformer_engine::cuda::current_device()) == 125) ? 256 : 512;
601+
block_scaled_cast_transpose_kernel_notaligned<kReturnTranspose, float, InputType,
602+
OutputType>
603+
<<<grid, threads_per_block, 0, stream>>>(
604+
reinterpret_cast<const InputType*>(input.dptr),
605+
reinterpret_cast<OutputType*>(output.dptr),
606+
reinterpret_cast<OutputType*>(output_t.dptr),
607+
reinterpret_cast<float*>(scale_inv.dptr),
608+
reinterpret_cast<float*>(scale_inv_t.dptr), row_length, num_rows,
609+
scale_stride_x, scale_stride_y, scale_t_stride_x, scale_t_stride_y, epsilon,
610+
pow_2_scale, noop_ptr);
611+
#endif // __HIP_PLATFORM_AMD__
576612
) // return_transpose
577613
) // OutputType
578614
) // InputType

0 commit comments

Comments
 (0)