mirror of
https://github.com/ROCm/composable_kernel.git
synced 2026-07-15 03:30:11 +00:00
[CK] Add per-group zero-point support to wmma_cshuffle_v3_b_scale (asymmetric int4)
Adds an optional asymmetric int4 dequant path to the wmma_cshuffle_v3
b_scale GEMM device-op. The existing path computes (nibble - 8) * scale
(symmetric uint4b8); the new path computes (nibble - zp) * scale per
group (matches AWQ / per-group-zero-point checkpoints). Implemented
via the algebraic identity:
(nibble - zp) * scale == (nibble - 8) * scale - (zp - 8) * scale
Callers precompute scaled_zp = (zp - 8) * scale once at weight load and
pass the [N, K/G] tensor alongside the existing scale tensor. The hot
loop pays one extra fp16 vector subtract per dequant pack.
Changes are additive and gated on optional `p_b_zero_point` being non-null
at the device-op level (defaults to nullptr) and `BZeroPointStruct = Empty`
at the gridwise/blockwise level (defaults to Empty). Symmetric callers see
no signature change and compile bit-identically.
Files:
* element/unary_element_wise_operation.hpp
+ `i4_to_half4_zp_scale()`: pk_i4 -> half4 dequant with per-group
scaled_zp subtract (mirrors `i4_to_half4_scale`).
+ `DequantPack8WithZp`: element-op wrapping the above; mirrors
`DequantPack8`. `is_pack8_invocable = true`.
* thread/threadwise_tensor_slice_transfer.hpp
+ `ThreadwiseTensorSliceTransfer_v4::Run()` overload that takes
`scale + scaled_zp` instead of just `scale` and dispatches to
`DequantPack8WithZp` on the pk_i4 path. Non-pk_i4 SrcData falls
back to the plain dequant path with scaled_zp ignored.
* block/blockwise_gemm_pipeline_wmmaops_v1.hpp
+ `BZeroPointStruct = Empty` defaulted template arg on Run().
+ `if constexpr(HasBZp)` branch picks the new threadwise Run() overload
and runs an extra GlobalLoad cycle for the zero-point struct,
synchronized with the existing BScaleStruct cadence.
* block/blockwise_gemm_pipeline_wmmaops_v3.hpp
+ `BZeroPointStruct = Empty` defaulted template arg on Run() for
signature compatibility with v1. v3 (Intrawave) does not implement
the asymmetric path; passing a non-Empty BZeroPointStruct would
compile but silently ignore it. Caller is responsible for routing
asymmetric configs to v1 (Interwave).
* grid/gridwise_gemm_wmma_cshuffle_v3_common.hpp
+ Forward `b_zero_point_struct` (defaulted Empty) from Run() to the
blockwise pipeline's Run().
* grid/gridwise_gemm_wmma_cshuffle_v3_ab_scale.hpp
+ `p_b_zero_point_grid` field on Argument (defaulted nullptr).
+ Run() builds a BZeroPoint struct from `p_b_zero_point_grid` when
non-null (mirroring the BScale struct construction) and threads it
through to Base::Run; symmetric path stays bit-identical when
p_b_zero_point_grid is nullptr.
* device/impl/device_gemm_wmma_cshuffle_v3_b_scale.hpp
+ Optional `p_b_zero_point = nullptr` trailing arg on `MakeArgument`.
Stored on the Argument and propagated through the Run path. Existing
symmetric callers see no signature change.
Backward compatibility verified: with p_b_zero_point=nullptr / BZeroPointStruct=Empty
the gridwise + blockwise dispatch produces bit-identical results to the
symmetric pre-change path. Tested via the standalone
example_gemm_wmma_fp16_pk_i4_v3_b_scale runner (CPU-reference verify
mode) on M=2048 N=19456 K=2560 (gate_up_proj prefill shape) and on
M=2048 across qkv (N=6144), o_proj (N=2560 K=4096), and down_proj
(N=2560 K=9728) on Strix Halo (gfx1151) — all pass.
Use case: vLLM W4A16 prefill GEMM dispatch on Strix Halo (gfx1151)
for AWQ-quantized models (e.g. Qwen/Qwen3-4B-AWQ).
JIRA: AIESW-32176.
This commit is contained in:
@@ -719,6 +719,11 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
static constexpr index_t KRepeatPerNumScaleKBlock = 1;
|
||||
};
|
||||
|
||||
// AIESW-32176: BZeroPointStruct (defaulted to Empty) carries per-group
|
||||
// scaled_zp = (zp - 8) * scale precomputed at weight load. When non-Empty
|
||||
// the dispatch picks the new threadwise Run overload that subtracts
|
||||
// scaled_zp inside the dequant; symmetric callers (BZeroPointStruct=Empty
|
||||
// by default) compile bit-identically to the original.
|
||||
template <bool HasMainLoop,
|
||||
TailNumber TailNum,
|
||||
typename AGridDesc,
|
||||
@@ -736,6 +741,7 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
typename CThreadBuffer,
|
||||
typename AScaleStruct,
|
||||
typename BScaleStruct,
|
||||
typename BZeroPointStruct = Empty,
|
||||
typename enable_if<ck::is_same_v<AScaleStruct, Empty>, bool>::type = false>
|
||||
__device__ void Run(const AGridDesc& a_grid_desc,
|
||||
const ABlockDesc& a_block_desc,
|
||||
@@ -753,8 +759,11 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
AScaleStruct&,
|
||||
BScaleStruct& b_scale_struct,
|
||||
index_t num_loop,
|
||||
index_t num_loop_per_scale) const
|
||||
index_t num_loop_per_scale,
|
||||
BZeroPointStruct b_zero_point_struct = {}) const
|
||||
{
|
||||
constexpr bool HasBZp = !ck::is_same_v<BZeroPointStruct, Empty>;
|
||||
|
||||
constexpr index_t KPerWaveBlock = wmma_gemm.GetKPerWaveBlk();
|
||||
|
||||
auto a_thread_buf = make_static_buffer<AddressSpaceEnum::Vgpr, ComputeTypeA>(
|
||||
@@ -771,6 +780,10 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
|
||||
// Scales global load
|
||||
b_scale_struct.template GlobalLoad<0>(num_loop_per_scale == 1);
|
||||
if constexpr(HasBZp)
|
||||
{
|
||||
b_zero_point_struct.template GlobalLoad<0>(num_loop_per_scale == 1);
|
||||
}
|
||||
|
||||
// Local prefill 1
|
||||
a_blockwise_copy.RunWrite(a_block_desc, a_block_buf);
|
||||
@@ -802,7 +815,7 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
b_thread_buf);
|
||||
});
|
||||
}
|
||||
else
|
||||
else if constexpr(!HasBZp)
|
||||
{
|
||||
static_for<0, NRepeat, 1>{}([&](auto n0) {
|
||||
b_thread_copy_.Run(
|
||||
@@ -817,6 +830,30 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
b_thread_buf);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// AIESW-32176: scale + scaled_zp dispatch. The
|
||||
// BZeroPointStruct mirrors BScaleStruct exactly (same
|
||||
// [num_scale_k_block, num_scale_krepeat] shape and same
|
||||
// GlobalLoad cadence) — they're built from the same
|
||||
// BScale template type in the gridwise.
|
||||
static_for<0, NRepeat, 1>{}([&](auto n0) {
|
||||
b_thread_copy_.Run(
|
||||
b_block_desc_k0_n0_n1_n2_k1,
|
||||
make_tuple(I0, n0, k0_offset + k0_inner, I0, I0, I0, I0),
|
||||
b_block_buf,
|
||||
b_scale_struct.scale_thread_bufs(I0)[Number<
|
||||
n0 * BScaleStruct::num_scale_k_block +
|
||||
(k0_offset + k0_inner) / BScaleStruct::num_scale_krepeat>{}],
|
||||
b_zero_point_struct.scale_thread_bufs(
|
||||
I0)[Number<n0 * BZeroPointStruct::num_scale_k_block +
|
||||
(k0_offset + k0_inner) /
|
||||
BZeroPointStruct::num_scale_krepeat>{}],
|
||||
b_thread_desc_,
|
||||
make_tuple(I0, n0, k0_inner, I0, I0, I0, I0),
|
||||
b_thread_buf);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
__builtin_amdgcn_sched_barrier(0);
|
||||
@@ -919,6 +956,10 @@ struct BlockwiseGemmWmmaops_pipeline_v1<BlockGemmPipelineScheduler::Interwave,
|
||||
blockwise_gemm_func();
|
||||
|
||||
b_scale_struct.template GlobalLoad<0>((i + 2) % num_loop_per_scale == 0);
|
||||
if constexpr(HasBZp)
|
||||
{
|
||||
b_zero_point_struct.template GlobalLoad<0>((i + 2) % num_loop_per_scale == 0);
|
||||
}
|
||||
if constexpr(ck::is_same<BScaleStruct, Empty>::value == false)
|
||||
{
|
||||
block_sync_lds();
|
||||
|
||||
@@ -336,6 +336,10 @@ struct BlockwiseGemmWmmaops_pipeline_v3<BlockGemmPipelineScheduler::Intrawave,
|
||||
});
|
||||
}
|
||||
|
||||
// AIESW-32176: BZeroPointStruct is accepted as a defaulted-Empty trailing
|
||||
// arg for signature compatibility with the v1 pipeline. v3 doesn't
|
||||
// implement asymmetric dequant; only Empty is supported here. If a real
|
||||
// BZeroPointStruct is passed, this static_assert fires.
|
||||
template <bool HasMainLoop,
|
||||
TailNumber TailNum,
|
||||
typename AGridDesc,
|
||||
@@ -353,6 +357,7 @@ struct BlockwiseGemmWmmaops_pipeline_v3<BlockGemmPipelineScheduler::Intrawave,
|
||||
typename CThreadBuffer,
|
||||
typename AScaleStruct,
|
||||
typename BScaleStruct,
|
||||
typename BZeroPointStruct = Empty,
|
||||
typename enable_if<ck::is_same_v<AScaleStruct, Empty>, bool>::type = false>
|
||||
__device__ void Run(const AGridDesc& a_grid_desc,
|
||||
const ABlockDesc& a_block_desc,
|
||||
@@ -370,8 +375,12 @@ struct BlockwiseGemmWmmaops_pipeline_v3<BlockGemmPipelineScheduler::Intrawave,
|
||||
AScaleStruct&,
|
||||
BScaleStruct& b_scale_struct,
|
||||
index_t num_loop,
|
||||
index_t num_loop_per_scale) const
|
||||
index_t num_loop_per_scale,
|
||||
BZeroPointStruct = {}) const
|
||||
{
|
||||
// AIESW-32176: zero-point arg is ignored by v3 (asymmetric is only
|
||||
// implemented in the v1 Interwave path). Caller is responsible for
|
||||
// routing asymmetric configs to v1.
|
||||
__builtin_amdgcn_sched_barrier(0);
|
||||
|
||||
constexpr index_t KPerWaveBlock = wmma_gemm.GetKPerWaveBlk();
|
||||
|
||||
@@ -182,6 +182,9 @@ struct DeviceGemm_BScale_Wmma_CShuffleV3 : public DeviceGemmV2BScale<ALayout,
|
||||
|
||||
bool GetPermuteB() override { return PermuteB; }
|
||||
|
||||
// AIESW-32176: optional p_b_zero_point trailing arg (defaults to nullptr).
|
||||
// When non-null the gridwise/blockwise pipelines pick up the asymmetric
|
||||
// dequant path. Existing symmetric callers see no signature change.
|
||||
static auto MakeArgument(const ADataType* p_a,
|
||||
const BDataType* p_b,
|
||||
CDataType* p_c,
|
||||
@@ -196,7 +199,8 @@ struct DeviceGemm_BScale_Wmma_CShuffleV3 : public DeviceGemmV2BScale<ALayout,
|
||||
index_t KBatch,
|
||||
AElementwiseOperation a_element_op,
|
||||
BElementwiseOperation b_element_op,
|
||||
CElementwiseOperation cde_element_op)
|
||||
CElementwiseOperation cde_element_op,
|
||||
const BScaleDataType* p_b_zero_point = nullptr)
|
||||
{
|
||||
return Argument{std::array<const void*, 1>{p_a},
|
||||
std::array<const void*, 1>{p_b},
|
||||
@@ -216,7 +220,9 @@ struct DeviceGemm_BScale_Wmma_CShuffleV3 : public DeviceGemmV2BScale<ALayout,
|
||||
KBatch,
|
||||
a_element_op,
|
||||
b_element_op,
|
||||
cde_element_op};
|
||||
cde_element_op,
|
||||
/*is_reduce=*/false,
|
||||
p_b_zero_point};
|
||||
}
|
||||
|
||||
static auto MakeInvoker() { return Invoker{}; }
|
||||
|
||||
@@ -75,6 +75,43 @@ __device__ inline half4_t i4_to_half4_scale(int q, const ck::half2_t& scale)
|
||||
return res.template AsType<half4_t>()[Number<0>{}];
|
||||
}
|
||||
|
||||
// AIESW-32176: asymmetric int4 dequant.
|
||||
// With symmetric (uint4b8) the existing path computes (nibble - 8) * scale.
|
||||
// With asymmetric (zero point zp per group), the desired result is
|
||||
// (nibble - zp) * scale. Equivalently:
|
||||
// (nibble - 8) * scale - (zp - 8) * scale == result_sym - scaled_zp
|
||||
// so we precompute scaled_zp = (zp - 8) * scale once at load time and
|
||||
// subtract it after the existing scale multiply. One extra fp16 vector
|
||||
// subtract per dequant pack; everything else is identical to
|
||||
// i4_to_half4_scale, including the magic SUB/MUL/ADD constants.
|
||||
__device__ inline half4_t
|
||||
i4_to_half4_zp_scale(int q, const ck::half2_t& scale, const ck::half2_t& scaled_zp)
|
||||
{
|
||||
const int LO = 0x000f000f;
|
||||
const int HI = 0x00f000f0;
|
||||
const int EX = 0x64006400;
|
||||
|
||||
int lo = (q & LO) | EX;
|
||||
int hi = (q & HI) | EX;
|
||||
|
||||
const int SUB = 0xE408E408; // half2 {-1032, -1032}
|
||||
const int MUL = 0x2c002c00; // half2 {1 / 16, 1 / 16}
|
||||
const int ADD = 0xd480d480; // half2 {-72, -72}
|
||||
|
||||
vector_type<half_t, 4> res;
|
||||
|
||||
res.template AsType<half2_t>()(Number<0>{}) = bit_cast<half2_t>(lo) + bit_cast<half2_t>(SUB);
|
||||
res.template AsType<half2_t>()(Number<1>{}) =
|
||||
bit_cast<half2_t>(hi) * bit_cast<half2_t>(MUL) + bit_cast<half2_t>(ADD);
|
||||
|
||||
res.template AsType<half2_t>()(Number<0>{}) =
|
||||
res.template AsType<half2_t>()(Number<0>{}) * scale - scaled_zp;
|
||||
res.template AsType<half2_t>()(Number<1>{}) =
|
||||
res.template AsType<half2_t>()(Number<1>{}) * scale - scaled_zp;
|
||||
|
||||
return res.template AsType<half4_t>()[Number<0>{}];
|
||||
}
|
||||
|
||||
__device__ inline f8x4_t i4_to_f8x4(int q)
|
||||
{
|
||||
const int LO = 0x000f000f;
|
||||
@@ -299,6 +336,51 @@ struct DequantPack8
|
||||
constexpr const static bool is_pack8_invocable = true;
|
||||
};
|
||||
|
||||
// AIESW-32176: asymmetric variant of DequantPack8.
|
||||
// Same as DequantPack8 but takes an additional `scaled_zp` per-group
|
||||
// parameter (= (zp - 8) * scale precomputed at weight load) and
|
||||
// subtracts it from the result so the dequant matches AWQ's
|
||||
// (nibble - zp) * scale formula. See i4_to_half4_zp_scale.
|
||||
struct DequantPack8WithZp
|
||||
{
|
||||
static constexpr const char* name = "DequantPack8WithZp";
|
||||
|
||||
template <typename Y, typename X, typename S, typename ZP>
|
||||
__host__ __device__ void operator()(Y& y, const X& x, const S& s, const ZP& zp) const;
|
||||
|
||||
__host__ __device__ constexpr void operator()(ck::half8_t& y,
|
||||
const ck::pk_i4x4_t& x,
|
||||
const ck::half2_t& s,
|
||||
const ck::half2_t& scaled_zp) const
|
||||
{
|
||||
#if CK_USE_PK4_LAYOUT_SHUFFLE
|
||||
vector_type<half_t, 8> result;
|
||||
|
||||
result.template AsType<half4_t>()(Number<0>{}) =
|
||||
i4_to_half4_zp_scale(bit_cast<int>(x), s, scaled_zp);
|
||||
result.template AsType<half4_t>()(Number<1>{}) =
|
||||
i4_to_half4_zp_scale(bit_cast<int>(x) >> 8, s, scaled_zp);
|
||||
|
||||
y = result.template AsType<half8_t>()[Number<0>{}];
|
||||
#else
|
||||
// Reference (slow) path for the !CK_USE_PK4_LAYOUT_SHUFFLE config:
|
||||
// do the unscaled dequant via existing type_convert and then apply
|
||||
// (* scale) - scaled_zp manually.
|
||||
vector_type<half_t, 8> dst;
|
||||
vector_type<pk_i4_t, 4> src{x};
|
||||
|
||||
static_for<0, 4, 1>{}([&](auto i) {
|
||||
auto v = type_convert<half2_t>(src.template AsType<pk_i4_t>()[i]);
|
||||
dst.template AsType<half2_t>()(i) = v * s - scaled_zp;
|
||||
});
|
||||
|
||||
y = dst.template AsType<half8_t>()[Number<0>{}];
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr const static bool is_pack8_invocable = true;
|
||||
};
|
||||
|
||||
struct PassThroughPack2
|
||||
{
|
||||
static constexpr const char* name = "PassThroughPack2";
|
||||
|
||||
@@ -333,7 +333,8 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
AElementwiseOperation a_element_op_,
|
||||
BElementwiseOperation b_element_op_,
|
||||
CDEElementwiseOperation cde_element_op_,
|
||||
bool is_reduce_ = false)
|
||||
bool is_reduce_ = false,
|
||||
const BScaleType* p_b_zero_point_grid_ = nullptr)
|
||||
: Problem{M_,
|
||||
N_,
|
||||
K_,
|
||||
@@ -350,6 +351,7 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
p_e_grid{p_e_grid_},
|
||||
p_a_scale_grid{p_a_scale_grid_},
|
||||
p_b_scale_grid{p_b_scale_grid_},
|
||||
p_b_zero_point_grid{p_b_zero_point_grid_},
|
||||
a_element_op{a_element_op_},
|
||||
b_element_op{b_element_op_},
|
||||
cde_element_op{cde_element_op_},
|
||||
@@ -395,6 +397,9 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
|
||||
const AScaleType* p_a_scale_grid;
|
||||
const BScaleType* p_b_scale_grid;
|
||||
// AIESW-32176: optional per-group precomputed (zp - 8) * scale for
|
||||
// asymmetric int4. nullptr for symmetric — kernel skips zp subtract.
|
||||
const BScaleType* p_b_zero_point_grid;
|
||||
const AElementwiseOperation a_element_op;
|
||||
const BElementwiseOperation b_element_op;
|
||||
const CDEElementwiseOperation cde_element_op;
|
||||
@@ -707,6 +712,9 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
}
|
||||
}
|
||||
|
||||
// AIESW-32176: p_b_zero_point_grid (nullptr for symmetric callers) selects
|
||||
// the asymmetric dequant path. When non-null the caller has precomputed
|
||||
// (zp - 8) * scale per group at weight load.
|
||||
template <bool HasMainKBlockLoop,
|
||||
InMemoryDataOperationEnum EGlobalMemoryDataOperation,
|
||||
TailNumber TailNum,
|
||||
@@ -723,8 +731,9 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
BElementwiseOperation b_element_op,
|
||||
CDEElementwiseOperation cde_element_op,
|
||||
EpilogueArgument& epilogue_args,
|
||||
const index_t A_k_id = 0,
|
||||
const index_t B_k_id = 0)
|
||||
const index_t A_k_id = 0,
|
||||
const index_t B_k_id = 0,
|
||||
const BScaleType* p_b_zero_point_grid = nullptr)
|
||||
{
|
||||
const auto as_grid_desc_ak0_m_ak1 = MakeAsGridDescriptor_AK0_M_AK1(
|
||||
problem.M, problem.MPadded, problem.K, problem.KPadded, problem.StrideAs, problem.AK0);
|
||||
@@ -765,37 +774,82 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
// BScale struct
|
||||
auto b_scale_struct = MakeBScale<1>(problem, p_b_scale_grid, block_n_id);
|
||||
|
||||
// AIESW-32176: BZeroPoint struct (mirror of BScale; same shape, same
|
||||
// type, same load cadence). When p_b_zero_point_grid is nullptr we keep
|
||||
// BZeroPoint as Empty so the Base::Run path stays bit-identical to
|
||||
// symmetric.
|
||||
const index_t num_k_block_per_scale = GetKBlockPerScale();
|
||||
|
||||
Base::template Run<decltype(as_grid_desc_ak0_m_ak1),
|
||||
decltype(bs_grid_desc_bk0_n_bk1),
|
||||
decltype(ds_grid_desc_mblock_mperblock_nblock_nperblock),
|
||||
decltype(e_grid_desc_mblock_mperblock_nblock_nperblock),
|
||||
decltype(a_scale_struct),
|
||||
decltype(b_scale_struct),
|
||||
decltype(epilogue_args),
|
||||
HasMainKBlockLoop,
|
||||
EGlobalMemoryDataOperation,
|
||||
TailNum>(p_as_grid,
|
||||
p_bs_grid,
|
||||
p_ds_grid,
|
||||
p_e_grid,
|
||||
p_shared,
|
||||
as_grid_desc_ak0_m_ak1,
|
||||
bs_grid_desc_bk0_n_bk1,
|
||||
ds_grid_desc_mblock_mperblock_nblock_nperblock,
|
||||
e_grid_desc_mblock_mperblock_nblock_nperblock,
|
||||
a_element_op,
|
||||
b_element_op,
|
||||
cde_element_op,
|
||||
block_m_id,
|
||||
block_n_id,
|
||||
num_k_block_per_scale,
|
||||
a_scale_struct,
|
||||
b_scale_struct,
|
||||
epilogue_args,
|
||||
A_k_id,
|
||||
B_k_id);
|
||||
if(p_b_zero_point_grid == nullptr)
|
||||
{
|
||||
Base::template Run<decltype(as_grid_desc_ak0_m_ak1),
|
||||
decltype(bs_grid_desc_bk0_n_bk1),
|
||||
decltype(ds_grid_desc_mblock_mperblock_nblock_nperblock),
|
||||
decltype(e_grid_desc_mblock_mperblock_nblock_nperblock),
|
||||
decltype(a_scale_struct),
|
||||
decltype(b_scale_struct),
|
||||
decltype(epilogue_args),
|
||||
HasMainKBlockLoop,
|
||||
EGlobalMemoryDataOperation,
|
||||
TailNum>(p_as_grid,
|
||||
p_bs_grid,
|
||||
p_ds_grid,
|
||||
p_e_grid,
|
||||
p_shared,
|
||||
as_grid_desc_ak0_m_ak1,
|
||||
bs_grid_desc_bk0_n_bk1,
|
||||
ds_grid_desc_mblock_mperblock_nblock_nperblock,
|
||||
e_grid_desc_mblock_mperblock_nblock_nperblock,
|
||||
a_element_op,
|
||||
b_element_op,
|
||||
cde_element_op,
|
||||
block_m_id,
|
||||
block_n_id,
|
||||
num_k_block_per_scale,
|
||||
a_scale_struct,
|
||||
b_scale_struct,
|
||||
epilogue_args,
|
||||
A_k_id,
|
||||
B_k_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto b_zero_point_struct = MakeBScale<1>(problem, p_b_zero_point_grid, block_n_id);
|
||||
|
||||
Base::template Run<decltype(as_grid_desc_ak0_m_ak1),
|
||||
decltype(bs_grid_desc_bk0_n_bk1),
|
||||
decltype(ds_grid_desc_mblock_mperblock_nblock_nperblock),
|
||||
decltype(e_grid_desc_mblock_mperblock_nblock_nperblock),
|
||||
decltype(a_scale_struct),
|
||||
decltype(b_scale_struct),
|
||||
decltype(epilogue_args),
|
||||
HasMainKBlockLoop,
|
||||
EGlobalMemoryDataOperation,
|
||||
TailNum,
|
||||
decltype(b_zero_point_struct)>(
|
||||
p_as_grid,
|
||||
p_bs_grid,
|
||||
p_ds_grid,
|
||||
p_e_grid,
|
||||
p_shared,
|
||||
as_grid_desc_ak0_m_ak1,
|
||||
bs_grid_desc_bk0_n_bk1,
|
||||
ds_grid_desc_mblock_mperblock_nblock_nperblock,
|
||||
e_grid_desc_mblock_mperblock_nblock_nperblock,
|
||||
a_element_op,
|
||||
b_element_op,
|
||||
cde_element_op,
|
||||
block_m_id,
|
||||
block_n_id,
|
||||
num_k_block_per_scale,
|
||||
a_scale_struct,
|
||||
b_scale_struct,
|
||||
epilogue_args,
|
||||
A_k_id,
|
||||
B_k_id,
|
||||
/*k_batch=*/1,
|
||||
b_zero_point_struct);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Wrapper function to have __global__ function in common
|
||||
@@ -861,7 +915,11 @@ struct GridwiseGemm_wmma_cshuffle_v3_ab_scale
|
||||
karg.cde_element_op,
|
||||
epilogue_args,
|
||||
A_k_id,
|
||||
B_k_id);
|
||||
B_k_id,
|
||||
// AIESW-32176: optional zp pointer; nullptr keeps the symmetric path.
|
||||
// No splitk offset applied because zp shares the scale layout — the
|
||||
// splitk K offset is absorbed into the same scale stride.
|
||||
karg.p_b_zero_point_grid);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1364,6 +1364,8 @@ struct GridwiseGemm_wmma_cshuffle_v3_base
|
||||
|
||||
// Note: arguments k_batch and k_id should be set if splitk is used
|
||||
// with implicit gemm (no pointer shift but shift using tensor descriptors)
|
||||
// AIESW-32176: BZeroPointStruct (defaulted to Empty) is forwarded to the
|
||||
// blockwise pipeline's Run() to pick up the asymmetric dequant path.
|
||||
template <typename AGridDesc_AK0_M_K1,
|
||||
typename BGridDesc_BK0_N_K1,
|
||||
typename DsGridDesc_MBlock_MPerBlock_NBlock_NPerBlock,
|
||||
@@ -1373,7 +1375,8 @@ struct GridwiseGemm_wmma_cshuffle_v3_base
|
||||
typename EpilogueArgument,
|
||||
bool HasMainKBlockLoop,
|
||||
InMemoryDataOperationEnum EGlobalMemoryDataOperation,
|
||||
TailNumber TailNum = TailNumber::Odd>
|
||||
TailNumber TailNum = TailNumber::Odd,
|
||||
typename BZeroPointStruct = typename BlockwiseGemmPipe::Empty>
|
||||
__device__ static void Run(AsGridPointer p_as_grid,
|
||||
BsGridPointer p_bs_grid,
|
||||
DsGridPointer p_ds_grid,
|
||||
@@ -1394,9 +1397,10 @@ struct GridwiseGemm_wmma_cshuffle_v3_base
|
||||
AScaleStruct& a_scale_struct,
|
||||
BScaleStruct& b_scale_struct,
|
||||
EpilogueArgument& epilogue_args,
|
||||
const index_t A_k_id = 0,
|
||||
const index_t B_k_id = 0,
|
||||
const index_t k_batch = 1)
|
||||
const index_t A_k_id = 0,
|
||||
const index_t B_k_id = 0,
|
||||
const index_t k_batch = 1,
|
||||
BZeroPointStruct b_zero_point_struct = {})
|
||||
{
|
||||
const auto as_grid_buf = generate_tuple(
|
||||
[&](auto i) {
|
||||
@@ -1481,7 +1485,8 @@ struct GridwiseGemm_wmma_cshuffle_v3_base
|
||||
a_scale_struct,
|
||||
b_scale_struct,
|
||||
num_k_block_main_loop,
|
||||
num_k_block_per_scale);
|
||||
num_k_block_per_scale,
|
||||
b_zero_point_struct); // AIESW-32176: defaults to Empty; v1+v3 both accept
|
||||
|
||||
// Epilogue:
|
||||
// - CShuffle / direct store
|
||||
|
||||
@@ -1679,6 +1679,153 @@ struct ThreadwiseTensorSliceTransfer_v4
|
||||
});
|
||||
}
|
||||
|
||||
// AIESW-32176: scale + per-group zero point overload.
|
||||
// Same as the "Fuse scale" Run() above but additionally takes a scaled_zp
|
||||
// (= (zp - 8) * scale precomputed at weight load) and uses
|
||||
// DequantPack8WithZp on the pk_i4 path so the dequant matches AWQ's
|
||||
// (nibble - zp) * scale. All non-pk_i4 SrcData paths fall back to the
|
||||
// plain dequant + scale (scaled_zp ignored — only meaningful for pk_i4).
|
||||
template <typename SrcRefToOriginDisplacement,
|
||||
typename DstOriginIdx,
|
||||
typename SrcBuffer,
|
||||
typename DstBuffer>
|
||||
__device__ void Run(const SrcDesc&,
|
||||
const SrcRefToOriginDisplacement&,
|
||||
const SrcBuffer& src_buf,
|
||||
const DstData& scale,
|
||||
const DstData& scaled_zp,
|
||||
const DstDesc&,
|
||||
const DstOriginIdx&,
|
||||
DstBuffer& dst_buf) const
|
||||
{
|
||||
static_assert(SrcDesc::IsKnownAtCompileTime() && DstDesc::IsKnownAtCompileTime(),
|
||||
"wrong! SrcDesc and DstDesc need to known at compile-time");
|
||||
static_assert(
|
||||
is_same<remove_cvref_t<typename SrcBuffer::type>, remove_cvref_t<SrcData>>::value &&
|
||||
is_same<remove_cvref_t<typename DstBuffer::type>, remove_cvref_t<DstData>>::value,
|
||||
"wrong! SrcBuffer or DstBuffer data type is wrong");
|
||||
static_assert(DstBuffer::IsStaticBuffer(), "wrong! DstBuffer need to be StaticBuffer");
|
||||
static_assert(is_known_at_compile_time<remove_cvref_t<SrcRefToOriginDisplacement>>::value &&
|
||||
is_known_at_compile_time<remove_cvref_t<DstOriginIdx>>::value,
|
||||
"wrong! SrcOriginToRefDistance and DstOriginToRefDistance need to be known "
|
||||
"at compile-time");
|
||||
|
||||
constexpr auto src_desc = remove_cvref_t<SrcDesc>{};
|
||||
constexpr auto dst_desc = remove_cvref_t<DstDesc>{};
|
||||
|
||||
constexpr auto src_ref_to_origin_disp_idx = to_multi_index(SrcRefToOriginDisplacement{});
|
||||
constexpr auto dst_origin_idx = to_multi_index(DstOriginIdx{});
|
||||
|
||||
constexpr auto src_scalar_per_access = generate_sequence_v2(
|
||||
[&](auto i) constexpr {
|
||||
if constexpr(i == SrcVectorDim)
|
||||
return Number<SrcScalarPerVector>{};
|
||||
else
|
||||
return Number<1>{};
|
||||
},
|
||||
Number<nDim>{});
|
||||
|
||||
constexpr auto src_scalar_step_in_vector = generate_sequence_v2(
|
||||
[&](auto i) constexpr {
|
||||
if constexpr(i == SrcVectorDim)
|
||||
return Number<1>{};
|
||||
else
|
||||
return Number<0>{};
|
||||
},
|
||||
Number<nDim>{});
|
||||
|
||||
constexpr auto access_lengths = SliceLengths{} / src_scalar_per_access;
|
||||
constexpr auto dim_access_order = DimAccessOrder{};
|
||||
constexpr auto ordered_access_lengths =
|
||||
container_reorder_given_new2old(access_lengths, dim_access_order);
|
||||
|
||||
static_ford<decltype(ordered_access_lengths)>{}([&](auto ordered_access_idx) {
|
||||
constexpr auto data_to_origin_disp_idx =
|
||||
ordered_access_idx.ReorderGivenOld2New(dim_access_order) * src_scalar_per_access;
|
||||
|
||||
constexpr auto src_ref_to_data_disp_idx =
|
||||
src_ref_to_origin_disp_idx + data_to_origin_disp_idx;
|
||||
|
||||
constexpr auto src_ref_to_data_disp_coord_step =
|
||||
make_tensor_coordinate_step(src_desc, src_ref_to_data_disp_idx);
|
||||
|
||||
auto src_data_coord = src_ref_coord_;
|
||||
move_tensor_coordinate(src_desc, src_data_coord, src_ref_to_data_disp_coord_step);
|
||||
|
||||
vector_type_maker_t<SrcData, SrcScalarPerVector / PackedSize> src_tmp_vector;
|
||||
using src_vector_t = typename decltype(src_tmp_vector)::type;
|
||||
|
||||
const bool is_src_valid = coordinate_has_valid_offset_assuming_visible_index_is_valid(
|
||||
src_desc, src_data_coord);
|
||||
|
||||
if constexpr(SrcBuffer::IsDynamicBuffer())
|
||||
{
|
||||
src_tmp_vector.template AsType<src_vector_t>()(Number<0>{}) =
|
||||
src_buf.template Get<src_vector_t>(src_data_coord.GetOffset() / PackedSize,
|
||||
is_src_valid);
|
||||
}
|
||||
else if constexpr(SrcBuffer::IsStaticBuffer())
|
||||
{
|
||||
static_for<0, SrcScalarPerVector, 1>{}([&](auto i) {
|
||||
constexpr index_t src_offset = src_desc.CalculateOffset(
|
||||
src_ref_to_origin_disp_idx + data_to_origin_disp_idx +
|
||||
i * src_scalar_step_in_vector);
|
||||
src_tmp_vector.template AsType<SrcData>()(i) = src_buf[Number<src_offset>{}];
|
||||
});
|
||||
}
|
||||
|
||||
if constexpr(is_same<remove_cvref_t<SrcData>, pk_i4_t>::value)
|
||||
{
|
||||
vector_type_maker_t<DstData, SrcScalarPerVector> dst_tmp_vector;
|
||||
vector_type<DstData, 2> scale_vector;
|
||||
scale_vector.template AsType<DstData>()(Number<0>{}) = scale;
|
||||
scale_vector.template AsType<DstData>()(Number<1>{}) = scale;
|
||||
vector_type<DstData, 2> scaled_zp_vector;
|
||||
scaled_zp_vector.template AsType<DstData>()(Number<0>{}) = scaled_zp;
|
||||
scaled_zp_vector.template AsType<DstData>()(Number<1>{}) = scaled_zp;
|
||||
|
||||
constexpr index_t pack_size = 8;
|
||||
static_assert(SrcScalarPerVector % pack_size == 0, "");
|
||||
|
||||
using src_v_t = typename vector_type_maker_t<SrcData, pack_size / PackedSize>::type;
|
||||
using dst_v_t = typename vector_type_maker_t<DstData, pack_size>::type;
|
||||
using scale_v_t = typename vector_type_maker_t<DstData, 2>::type;
|
||||
|
||||
static_for<0, SrcScalarPerVector / pack_size, 1>{}([&](auto i) {
|
||||
ck::tensor_operation::element_wise::DequantPack8WithZp{}(
|
||||
dst_tmp_vector.template AsType<dst_v_t>()(i),
|
||||
src_tmp_vector.template AsType<src_v_t>()[i],
|
||||
scale_vector.template AsType<scale_v_t>()[Number<0>{}],
|
||||
scaled_zp_vector.template AsType<scale_v_t>()[Number<0>{}]);
|
||||
});
|
||||
|
||||
static_for<0, SrcScalarPerVector, 1>{}([&](auto i) {
|
||||
constexpr index_t dst_offset = dst_desc.CalculateOffset(
|
||||
dst_origin_idx + data_to_origin_disp_idx + i * src_scalar_step_in_vector);
|
||||
dst_buf(Number<dst_offset>{}) = dst_tmp_vector.template AsType<DstData>()[i];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-pk_i4 SrcData: scaled_zp has no defined meaning here;
|
||||
// fall back to plain type_convert + scale. Asserts on dtype
|
||||
// mismatch are deliberately omitted to keep this overload usable
|
||||
// wherever the scale-only overload is.
|
||||
vector_type_maker_t<DstData, SrcScalarPerVector> dst_tmp_vector;
|
||||
static_for<0, SrcScalarPerVector, 1>{}([&](auto i) {
|
||||
dst_tmp_vector.template AsType<DstData>()(i) =
|
||||
type_convert<DstData>(src_tmp_vector.template AsType<SrcData>()[i]) * scale;
|
||||
});
|
||||
|
||||
static_for<0, SrcScalarPerVector, 1>{}([&](auto i) {
|
||||
constexpr index_t dst_offset = dst_desc.CalculateOffset(
|
||||
dst_origin_idx + data_to_origin_disp_idx + i * src_scalar_step_in_vector);
|
||||
dst_buf(Number<dst_offset>{}) = dst_tmp_vector.template AsType<DstData>()[i];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename SrcSliceMoveStepIdx>
|
||||
__device__ void MoveSrcSliceWindow(const SrcDesc&,
|
||||
const SrcSliceMoveStepIdx& src_slice_move_step_idx)
|
||||
|
||||
Reference in New Issue
Block a user