[AMD] Fix DeepSeek-V4 fp8 KV path on gfx942 (e4m3fnuz) (#28455)
Co-authored-by: Raiden-Makoto <Raiden-Makoto@users.noreply.github.com>
This commit is contained in:
co-authored by
Raiden-Makoto
parent
c07811bfc6
commit
5e6d7c1615
@@ -202,7 +202,7 @@ INDEXER_KERNEL void fused_norm_rope_indexer(const __grid_constant__ FusedNormRop
|
||||
local_max = math::max(local_max, math::abs(data[i]));
|
||||
}
|
||||
const auto abs_max = warp::reduce_max(local_max);
|
||||
const auto scale = fmaxf(1e-4f, abs_max) / math::FP8_E4M3_MAX;
|
||||
const auto scale = fmaxf(1e-4f, abs_max) / kFP8E4M3Max;
|
||||
const auto inv_scale = 1.0f / scale;
|
||||
const int64_t page = out_loc >> kPageBits;
|
||||
const int64_t offset = out_loc & ((1 << kPageBits) - 1);
|
||||
@@ -488,7 +488,7 @@ FLASHMLA_KERNEL void fused_norm_rope_flashmla(const __grid_constant__ FusedNormR
|
||||
const auto x = cast<float>(cast<bf16_t>(data[0]));
|
||||
const auto y = cast<float>(cast<bf16_t>(data[1]));
|
||||
const auto abs_max = warp::reduce_max(fmaxf(fabs(x), fabs(y)));
|
||||
const auto scale_raw = fmaxf(1e-4f, abs_max) / math::FP8_E4M3_MAX;
|
||||
const auto scale_raw = fmaxf(1e-4f, abs_max) / kFP8E4M3Max;
|
||||
const auto scale_ue8m0 = cast_to_ue8m0(scale_raw);
|
||||
const auto inv_scale = inv_scale_ue8m0(scale_ue8m0);
|
||||
const auto result = pack_fp8(x * inv_scale, y * inv_scale);
|
||||
|
||||
@@ -52,7 +52,7 @@ __global__ void fused_store_flashmla_cache(const __grid_constant__ FusedStoreCac
|
||||
if (wid != 7) {
|
||||
const auto [x, y] = cast<fp32x2_t>(elems);
|
||||
const auto abs_max = warp::reduce_max(fmaxf(fabs(x), fabs(y)));
|
||||
const auto scale_raw = fmaxf(1e-4f, abs_max) / math::FP8_E4M3_MAX;
|
||||
const auto scale_raw = fmaxf(1e-4f, abs_max) / kFP8E4M3Max;
|
||||
const auto scale_ue8m0 = cast_to_ue8m0(scale_raw);
|
||||
const auto inv_scale = inv_scale_ue8m0(scale_ue8m0);
|
||||
const auto result = pack_fp8(x * inv_scale, y * inv_scale);
|
||||
@@ -104,7 +104,7 @@ __global__ void fused_store_indexer_cache(const __grid_constant__ FusedStoreCach
|
||||
const auto local_max = fmaxf(fmaxf(fabs(x0), fabs(x1)), fmaxf(fabs(y0), fabs(y1)));
|
||||
const auto abs_max = warp::reduce_max(local_max);
|
||||
// use normal fp32 scale
|
||||
const auto scale = fmaxf(1e-4f, abs_max) / math::FP8_E4M3_MAX;
|
||||
const auto scale = fmaxf(1e-4f, abs_max) / kFP8E4M3Max;
|
||||
const auto inv_scale = 1.0f / scale;
|
||||
const int32_t page = index >> kPageBits;
|
||||
const int32_t offset = index & ((1 << kPageBits) - 1);
|
||||
|
||||
@@ -26,7 +26,7 @@ struct FusedStoreCacheParam {
|
||||
[[maybe_unused]]
|
||||
SGL_DEVICE float fp8_e4m3_clip(float val) {
|
||||
namespace math = device::math;
|
||||
return math::max(math::min(val, math::FP8_E4M3_MAX), -math::FP8_E4M3_MAX);
|
||||
return math::max(math::min(val, kFP8E4M3Max), -kFP8E4M3Max);
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
@@ -63,7 +63,7 @@ __global__ void fused_store_indexer_cache(const __grid_constant__ FusedStoreCach
|
||||
const auto local_max = fmaxf(fmaxf(fabs(x0), fabs(x1)), fmaxf(fabs(y0), fabs(y1)));
|
||||
const auto abs_max = warp::reduce_max(local_max);
|
||||
// use normal fp32 scale
|
||||
const auto scale = fmaxf(1e-4f, abs_max) / math::FP8_E4M3_MAX;
|
||||
const auto scale = fmaxf(1e-4f, abs_max) / kFP8E4M3Max;
|
||||
const auto inv_scale = 1.0f / scale;
|
||||
const int32_t page = index >> kPageBits;
|
||||
const int32_t offset = index & ((1 << kPageBits) - 1);
|
||||
|
||||
@@ -74,7 +74,15 @@ SGL_DEVICE uint8_t cvt_float_to_fp8_e4m3(float val) {
|
||||
uint8_t mant3;
|
||||
|
||||
if (exp32 < kMinSubnormExp) {
|
||||
#if HIP_FP8_TYPE_FNUZ
|
||||
// E4M3FNUZ (gfx942) has no negative zero: byte 0x80 is NaN, not -0.0.
|
||||
// Returning `sign` (0x80) for an underflowing negative injects NaN into the
|
||||
// fp8 KV cache -> NaN attention/logits. Flush underflow to +0 instead.
|
||||
return 0;
|
||||
#else
|
||||
// E4M3FN (gfx950): 0x80 == -0.0, harmless.
|
||||
return sign;
|
||||
#endif
|
||||
} else if (exp32 < kMinNormExp) {
|
||||
// Subnormal range
|
||||
int32_t shift = -(kBias - 1) - exp32; // 1..3
|
||||
|
||||
@@ -17,7 +17,11 @@ inline constexpr float log2e = 1.44269504088896340736f;
|
||||
/// \brief Constant: ln(2)
|
||||
inline constexpr float loge2 = 0.693147180559945309417f;
|
||||
/// \brief Maximum representable value for FP8 E4M3 format.
|
||||
inline constexpr float FP8_E4M3_MAX = 448.0f;
|
||||
/// Arch-aware: 448 on CUDA / AMD OCP e4m3fn (gfx950), 224 on AMD e4m3fnuz
|
||||
/// (gfx942). Mirrors kFP8E4M3Max so fp8 quant scale divisors and clamps in
|
||||
/// the dsv4 compute path (indexer Q-quant, MoE silu+mul / dispatch quant,
|
||||
/// GEMM per-tensor quant) do not over-saturate fnuz hardware.
|
||||
inline constexpr float FP8_E4M3_MAX = ::kFP8E4M3Max;
|
||||
static_assert(log2e * loge2 == 1.0f, "log2e * loge2 must be 1");
|
||||
|
||||
/// \brief Returns the larger of `a` and `b`.
|
||||
|
||||
+13
-7
@@ -17,6 +17,12 @@ import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.utils.common import is_gfx942_supported
|
||||
|
||||
# gfx942/MI300/MI325 stores e4m3fnuz (bias 8);
|
||||
# gfx950/MI350 and CUDA store OCP e4m3fn (bias 7).
|
||||
_KV_FP8_TY = tl.float8e4b8 if is_gfx942_supported() else tl.float8e4nv
|
||||
|
||||
|
||||
def _bucket_total_tokens(total_tokens: int) -> int:
|
||||
"""Round total_tokens up to the nearest power of 2 for autotune key stability."""
|
||||
@@ -213,37 +219,37 @@ def _process_kv_block_aggressive(
|
||||
|
||||
qk = tl.zeros([BLOCK_H, BLOCK_N], dtype=tl.float32)
|
||||
|
||||
nope_fp8_0 = nope_uint8_0.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_0 = nope_uint8_0.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_0 = (nope_fp8_0.to(tl.bfloat16) * scale_bf16_0[:, None]).to(tl.bfloat16)
|
||||
kv_0 = tl.where(valid_2d, kv_0, 0.0)
|
||||
qk += tl.dot(q_0, tl.trans(kv_0)).to(tl.float32)
|
||||
|
||||
nope_fp8_1 = nope_uint8_1.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_1 = nope_uint8_1.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_1 = (nope_fp8_1.to(tl.bfloat16) * scale_bf16_1[:, None]).to(tl.bfloat16)
|
||||
kv_1 = tl.where(valid_2d, kv_1, 0.0)
|
||||
qk += tl.dot(q_1, tl.trans(kv_1)).to(tl.float32)
|
||||
|
||||
nope_fp8_2 = nope_uint8_2.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_2 = nope_uint8_2.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_2 = (nope_fp8_2.to(tl.bfloat16) * scale_bf16_2[:, None]).to(tl.bfloat16)
|
||||
kv_2 = tl.where(valid_2d, kv_2, 0.0)
|
||||
qk += tl.dot(q_2, tl.trans(kv_2)).to(tl.float32)
|
||||
|
||||
nope_fp8_3 = nope_uint8_3.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_3 = nope_uint8_3.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_3 = (nope_fp8_3.to(tl.bfloat16) * scale_bf16_3[:, None]).to(tl.bfloat16)
|
||||
kv_3 = tl.where(valid_2d, kv_3, 0.0)
|
||||
qk += tl.dot(q_3, tl.trans(kv_3)).to(tl.float32)
|
||||
|
||||
nope_fp8_4 = nope_uint8_4.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_4 = nope_uint8_4.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_4 = (nope_fp8_4.to(tl.bfloat16) * scale_bf16_4[:, None]).to(tl.bfloat16)
|
||||
kv_4 = tl.where(valid_2d, kv_4, 0.0)
|
||||
qk += tl.dot(q_4, tl.trans(kv_4)).to(tl.float32)
|
||||
|
||||
nope_fp8_5 = nope_uint8_5.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_5 = nope_uint8_5.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_5 = (nope_fp8_5.to(tl.bfloat16) * scale_bf16_5[:, None]).to(tl.bfloat16)
|
||||
kv_5 = tl.where(valid_2d, kv_5, 0.0)
|
||||
qk += tl.dot(q_5, tl.trans(kv_5)).to(tl.float32)
|
||||
|
||||
nope_fp8_6 = nope_uint8_6.to(tl.float8e4nv, bitcast=True)
|
||||
nope_fp8_6 = nope_uint8_6.to(_KV_FP8_TY, bitcast=True)
|
||||
kv_6 = (nope_fp8_6.to(tl.bfloat16) * scale_bf16_6[:, None]).to(tl.bfloat16)
|
||||
kv_6 = tl.where(valid_2d, kv_6, 0.0)
|
||||
qk += tl.dot(q_6, tl.trans(kv_6)).to(tl.float32)
|
||||
|
||||
@@ -96,6 +96,7 @@ def _fused_qk_norm_rope_store_kernel(
|
||||
BYTES_PER_TOKEN: tl.constexpr,
|
||||
SWA_PAGE_SIZE: tl.constexpr,
|
||||
BF16_STORE: tl.constexpr,
|
||||
IS_FNUZ: tl.constexpr,
|
||||
):
|
||||
pid_m = tl.program_id(0).to(tl.int64)
|
||||
pid_h = tl.program_id(1).to(tl.int64)
|
||||
@@ -259,7 +260,14 @@ def _fused_qk_norm_rope_store_kernel(
|
||||
x_scaled = tile_data * inv_scale[:, None]
|
||||
x_fp8 = tl.clamp(x_scaled, FP8_MIN, FP8_MAX)
|
||||
|
||||
x_fp8_cast = x_fp8.to(tl.float8e4nv)
|
||||
# Encode with the SAME fp8 type the decode bitcasts (read
|
||||
# _KV_FP8_TY = float8e4b8 for e4m3fnuz / float8e4nv otherwise).
|
||||
# An implicit/fn cast mis-encodes under fnuz (exponent bias 7 vs
|
||||
# fnuz bias 8), so the fnuz read decodes every element 2x too small.
|
||||
if IS_FNUZ:
|
||||
x_fp8_cast = x_fp8.to(tl.float8e4b8)
|
||||
else:
|
||||
x_fp8_cast = x_fp8.to(tl.float8e4nv)
|
||||
x_fp8_bytes = x_fp8_cast.to(tl.uint8, bitcast=True)
|
||||
fp8_byte_offs = value_base[:, None] + tile_start + nope_tile_offs[None, :]
|
||||
tl.store(
|
||||
@@ -396,6 +404,7 @@ def fused_qk_norm_rope_swa_store(
|
||||
BYTES_PER_TOKEN=bytes_per_token,
|
||||
SWA_PAGE_SIZE=swa_page_size,
|
||||
BF16_STORE=bf16_store,
|
||||
IS_FNUZ=_fp8_fnuz,
|
||||
num_warps=num_warps,
|
||||
)
|
||||
return q_out
|
||||
|
||||
@@ -129,6 +129,7 @@ from sglang.srt.utils import (
|
||||
add_prefix,
|
||||
get_bool_env_var,
|
||||
is_gfx95_supported,
|
||||
is_gfx942_supported,
|
||||
log_info_on_rank0,
|
||||
make_layers,
|
||||
)
|
||||
@@ -162,6 +163,7 @@ _use_aiter = get_bool_env_var("SGLANG_USE_AITER") and _is_hip
|
||||
# SGLANG_SHARED_EXPERT_TP1=1 (replicated shared expert). Default OFF.
|
||||
_SHARED_EXPERT_LOCAL = get_bool_env_var("SGLANG_DP_SHARED_EXPERT_LOCAL")
|
||||
_is_gfx95_supported = is_gfx95_supported()
|
||||
_is_gfx942_supported = is_gfx942_supported()
|
||||
|
||||
if _use_aiter:
|
||||
if _is_gfx95_supported:
|
||||
@@ -950,7 +952,14 @@ class MQALayer(nn.Module):
|
||||
# dispatch the cheaper decode::head64 variant; attn_sink is sliced to
|
||||
# this rank and padded to match.
|
||||
padded_num_heads = 64 if self.n_local_heads <= 64 else self.n_heads
|
||||
q_padded = x.new_empty(x.shape[0], padded_num_heads, self.head_dim)
|
||||
# Only [0:n_local_heads] is written below. Uninitialized padded TP
|
||||
# heads inject NaN into attention on gfx942 (fnuz), so zero-init
|
||||
# there; other archs tolerate new_empty and skip the per-forward
|
||||
# memset.
|
||||
if _is_gfx942_supported:
|
||||
q_padded = x.new_zeros(x.shape[0], padded_num_heads, self.head_dim)
|
||||
else:
|
||||
q_padded = x.new_empty(x.shape[0], padded_num_heads, self.head_dim)
|
||||
tp_slice = slice(0, self.n_local_heads)
|
||||
q_out = q_padded[:, tp_slice, :]
|
||||
if self._attn_sink_local is None:
|
||||
|
||||
Reference in New Issue
Block a user