Skip to content

Commit 0662b18

Browse files
[rocm-libraries] ROCm/rocm-libraries#11721 (commit d8dab35)
=?UTF-8?q?feat(ck=5Ftile):=20make=20weight-preshuffled=20?= =?UTF-8?q?B=20usable=20with=20RowCol=20(per-token=20=C3=97=20per-channel)?= =?UTF-8?q?=20scales=20(#11721)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUE ID : #3766 Replaces my #3770, which I withdrew because its evidence was vacuous — the `-preshuffleb` flag was not part of the rowcol LUT key, so both arms of that "before/after" ran the same non-preshuffle kernel. This PR fixes that (among other things) and the test now genuinely toggles. Two commits: the first is #3769 (the rowcol BQ-extent fix), included because the enablement cannot be evaluated without it — rowcol fails its own verification until `BQN = N`. If #3769 merges first this rebases cleanly. ## The defect `WeightPreshufflePipelineAGmemBGmemCRegV2` never announced `PreshuffleB`. `QuantGemmKernel` detects a preshuffled B by SFINAE on `GemmPipeline::PreshuffleB` (`gemm_quant_kernel.hpp:104-106`) and **defaults to false when the member is absent** — so pairing the quant kernel with the weight-preshuffle pipeline made it read preshuffled weights as a plain B. No error, just wrong numbers. Measured by removing the announcement again on an otherwise-identical build (gfx950): | | result | |---|---| | rowcol + preshuffleb, **without** the announcement | **fail — 39.47% wrong, max err 386.78** | | rowcol + preshuffleb, **with** it | correct | | rowcol, preshuffleb=0 (control) | correct in both builds | ## Making the combination reachable - rowcol routes to the weight-preshuffle pipeline when `PreshuffleB` is set (it was pinned to `GemmPipelineAgBgCrCompV3` unconditionally); - new `GemmConfigPreshuffleB_RowCol_Prefill`; - `-preshuffleb` joins the rowcol LUT key — without it the flag selected the same kernel either way, so the configuration could not be tested at all; - `RowColQuant` leaves the `PreshuffleB` rejection list. ## Results in this example (gfx950 / MI350X, fp8, verification on) | shape | preshuffleb=0 | preshuffleb=1 | |---|---|---| | 512³ | correct | correct | | 1024×2688×6144 | 0.1014 ms, 333 TF | **0.0471 ms, 718 TF** | | 2048×6144×12288 | 0.7211 ms, 429 TF | **0.2102 ms, 1471 TF** | **Caveat**: the two arms differ in tile shape as well (the existing rowcol config is decode-shaped 16×64, the new one prefill-shaped 128×128), so read these as *the new preshuffled config vs the only rowcol config the example had at these shapes*, not as preshuffle in isolation. ## Independent measurement, stated carefully Driving this same composition through AITER's tuner on MI350X — 1754 measurements across 6 GLM-shaped GEMMs, full candidate space (259 flatmm tiles + 44 rowcol tiles), **errRatio 0.0000 on every one**: | shape (M,N,K) | best rowcol | best flatmm | winner | |---|---|---|---| | 1, 2688, 6144 | — (no divisible tile) | 11.008 µs | flatmm | | 64, 2688, 6144 | 12.640 µs (rank 5) | 11.325 µs | flatmm, −11.6% | | 256, 2688, 6144 | 19.190 µs (rank 10) | 17.590 µs | flatmm, −9.1% | | 1024, 2688, 6144 | 41.226 µs (rank 35) | 35.491 µs | flatmm, −16.2% | | **2048, 6144, 12288** | **158.661 µs (rank 1)** | 175.943 µs | **rowcol, +9.8%** | | **16384, 6144, 12288** | **1165.842 µs (rank 1)** | 1258.569 µs | **rowcol, +7.4%** | So it is a genuine per-shape winner on large-M / prefill-shaped GEMMs and **not** a global replacement; across 184 matched (tile, BlockPerCu) pairs the median is −0.9%, with the advantage concentrated in wide-N tiles and growing with M. Also note **M=1 cannot dispatch to this route** — no MTile divides 1 and it is instantiated unpadded; that needs `kPadM` wired through separately. (Earlier revisions of this description quoted "+8-11% over flatmm" from a restricted candidate set; the table above supersedes it. See the comments for the full correction history.) Per-token × per-channel scales on preshuffled weights is the standard shape for FP8 LLM inference with online activation quantization, so having it reachable — and not silently mis-typed as plain B — matters beyond this example. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent cea77f7 commit 0662b18

7 files changed

Lines changed: 121 additions & 11 deletions

File tree

‎example/ck_tile/38_block_scale_gemm/gemm_quant.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ auto gen_lut_key(const ck_tile::ArgParser& arg_parser)
8585
arg_parser.get_bool("preshufflequant") ? "preshufflequant" : "non-preshufflequant";
8686
params.push_back(preshufflequant);
8787
}
88+
if(quant_mode == "rowcol")
89+
{
90+
// RowCol scales are independent of the B layout, so the preshuffle
91+
// choice selects a different kernel and must be part of the key.
92+
params.push_back(arg_parser.get_bool("preshuffleb") ? "preshuffleb" : "non-preshuffleb");
93+
}
8894
if(quant_mode != "rowcol" && quant_mode != "tensor")
8995
{
9096
// NOTE: rowcol and tensor pipeline do not use group size

‎example/ck_tile/38_block_scale_gemm/gemm_quant_rowcol.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
template <typename T>
77
using GemmConfig = GemmConfigQuantDecode<T>;
88

9+
template <typename T>
10+
using GemmConfigPreshuffle = GemmConfigPreshuffleB_RowCol_Prefill<T>;
11+
912
static auto _ = []() {
1013
auto& lut = get_kernel_lut();
1114
// NOTE: QuantGroupSize is a place holder. rowcol pipeline does not use QuantGroupSize
@@ -26,5 +29,18 @@ static auto _ = []() {
2629
QuantGroupSize,
2730
ck_tile::QuantType::RowColQuant>(arg_parser);
2831
};
32+
lut[hash_multiple_strings({"fp8", "rowcol", "preshuffleb"})] = [](const ck_tile::ArgParser&
33+
arg_parser) {
34+
using TypeConfig =
35+
decltype(GemmQuantTypeConfig<ck_tile::fp8_t, ck_tile::fp8_t, ck_tile::half_t, float>{});
36+
return run_gemm_example_prec_type<GemmConfigPreshuffle<ck_tile::fp8_t>,
37+
TypeConfig,
38+
QuantGroupSize,
39+
ck_tile::QuantType::RowColQuant>(arg_parser);
40+
};
41+
lut[hash_multiple_strings({"fp8", "rowcol", "non-preshuffleb"})] =
42+
lut[hash_multiple_strings({"fp8", "rowcol"})];
43+
lut[hash_multiple_strings({"bf8", "rowcol", "non-preshuffleb"})] =
44+
lut[hash_multiple_strings({"bf8", "rowcol"})];
2945
return 0;
3046
}();

‎example/ck_tile/38_block_scale_gemm/gemm_utils.hpp‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,31 @@ struct GemmConfigPreshuffleB_BQuant_Prefill : public GemmConfigBase
216216
static constexpr int kBlockPerCu = 2;
217217
};
218218

219+
// RowCol (per-token x per-channel) scales on a weight-preshuffled B. The scales
220+
// are applied by the epilogue, so the quant mode and the B layout are
221+
// independent axes; this is the shape FP8 LLM inference uses when activations
222+
// are quantized online.
223+
template <typename PrecType>
224+
struct GemmConfigPreshuffleB_RowCol_Prefill : public GemmConfigBase
225+
{
226+
static constexpr ck_tile::index_t M_Tile = 128;
227+
static constexpr ck_tile::index_t N_Tile = 128;
228+
static constexpr ck_tile::index_t K_Tile = 128 / sizeof(PrecType);
229+
230+
static constexpr ck_tile::index_t M_Warp = 1;
231+
static constexpr ck_tile::index_t N_Warp = 4;
232+
static constexpr ck_tile::index_t K_Warp = 1;
233+
234+
static constexpr ck_tile::index_t M_Warp_Tile = 16;
235+
static constexpr ck_tile::index_t N_Warp_Tile = 16;
236+
static constexpr ck_tile::index_t K_Warp_Tile =
237+
ck_tile::get_k_warp_tile<PrecType, M_Warp_Tile, true>();
238+
239+
static constexpr bool PreshuffleB = true;
240+
static constexpr bool DoubleSmemBuffer = true;
241+
static constexpr int kBlockPerCu = 2;
242+
};
243+
219244
template <typename PrecType>
220245
struct GemmConfigPreshuffleB_PreshuffleBQuant_Prefill
221246
: public GemmConfigPreshuffleB_BQuant_Prefill<PrecType>

‎example/ck_tile/38_block_scale_gemm/run_gemm_quant_example.inc‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,18 @@ float gemm_calc_quant(const ck_tile::QuantGemmHostArgs& args, const ck_tile::str
195195
ck_tile::WPABQuantBPipelineAgBgCrV2<PipelineProblem>,
196196
ck_tile::ABQuantGemmPipelineAgBgCrCompV3<PipelineProblem>>>;
197197

198+
// RowCol/Tensor scales are applied by the epilogue, so the quant mode
199+
// and the B layout are independent axes: a preshuffled B just needs the
200+
// weight-preshuffle pipeline underneath.
201+
using RowColPipeline =
202+
std::conditional_t<GemmConfig::PreshuffleB,
203+
ck_tile::WeightPreshufflePipelineAGmemBGmemCRegV2<PipelineProblem>,
204+
ck_tile::GemmPipelineAgBgCrCompV3<PipelineProblem>>;
205+
198206
using GemmPipeline = std::conditional_t<
199207
QuantMode == ck_tile::QuantType::RowColQuant ||
200208
QuantMode == ck_tile::QuantType::TensorQuant,
201-
ck_tile::GemmPipelineAgBgCrCompV3<PipelineProblem>,
209+
RowColPipeline,
202210
std::conditional_t<QuantMode == ck_tile::QuantType::AQuantGrouped,
203211
AQuantPipeline,
204212
std::conditional_t<QuantMode == ck_tile::QuantType::ABQuantGrouped,
@@ -494,11 +502,18 @@ int run_gemm_example_with_layouts(const ck_tile::ArgParser& arg_parser,
494502
K, BQuantGroupSize::kK); // Group quantization: BQK = K / GroupSize
495503
BQN = ck_tile::integer_divide_ceil(N, BQuantGroupSize::kN);
496504
}
497-
else if constexpr(QuantMode == ck_tile::QuantType::RowColQuant ||
498-
QuantMode == ck_tile::QuantType::TensorQuant)
505+
else if constexpr(QuantMode == ck_tile::QuantType::RowColQuant)
506+
{
507+
AQK = 1; // Row quantization: AQ tensor shape [M, 1]
508+
BQK = 1; // Column quantization: BQ tensor shape [1, N] -- one scale per
509+
BQN = N; // output column, so the extent is N, not 1. stride_BQ below is
510+
// already computed for (1, N); allocating (1, 1) here left the
511+
// kernel reading N scales out of a single-element buffer.
512+
}
513+
else if constexpr(QuantMode == ck_tile::QuantType::TensorQuant)
499514
{
500-
AQK = 1; // Row quantization: tensor shape [M, 1] or [1]
501-
BQK = 1; // Column quantization: tensor shape [1, N] or [1]
515+
AQK = 1; // Tensor quantization: AQ tensor shape [1]
516+
BQK = 1; // Tensor quantization: BQ tensor shape [1]
502517
BQN = 1;
503518
}
504519
else
@@ -992,12 +1007,11 @@ int run_gemm_example_prec_type(const ck_tile::ArgParser& arg_parser)
9921007
using Col = ck_tile::tensor_layout::gemm::ColumnMajor;
9931008

9941009
if((QuantMode == ck_tile::QuantType::AQuantGrouped ||
995-
QuantMode == ck_tile::QuantType::RowColQuant ||
9961010
std::is_same_v<typename TypeConfig::BDataType, ck_tile::pk_fp4_t>) &&
9971011
GemmConfig::PreshuffleB)
9981012
{
9991013
throw std::runtime_error(
1000-
"Preshuffling weight matrix is not supported for AQuant, RowColQuant or bf16_fp4_gemm");
1014+
"Preshuffling weight matrix is not supported for AQuant or bf16_fp4_gemm");
10011015
}
10021016

10031017
if constexpr(std::is_same_v<typename TypeConfig::ADataType, ck_tile::pk_int4_t> ||

‎include/ck_tile/ops/gemm/pipeline/wp_pipeline_agmem_bgmem_creg_v2.hpp‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ struct WeightPreshufflePipelineAGmemBGmemCRegV2
7676
using Base = BaseWeightPreshufflePipelineAGmemBGmemCRegV2<Problem>;
7777
using PipelineImplBase = GemmPipelineAgBgCrImplBase<Problem, PipelinePolicy>;
7878

79+
// This pipeline consumes a preshuffled B. Kernels that have to know --
80+
// QuantGemmKernel picks its B window and its N-step from it -- detect that
81+
// by SFINAE on GemmPipeline::PreshuffleB and default to *false* when the
82+
// member is absent (gemm_quant_kernel.hpp is_preshuffleB_enabled), which
83+
// silently makes them treat preshuffled weights as a plain B.
84+
static constexpr bool PreshuffleB = true;
85+
7986
using AsDataType = remove_cvref_t<typename Problem::AsDataTypeTuple>;
8087
using BsDataType = remove_cvref_t<typename Problem::BsDataTypeTuple>;
8188
using CDataType = remove_cvref_t<typename Problem::CDataType>;

‎test/ck_tile/gemm_block_scale/test_gemm_quant_fixtures.hpp‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,9 +1626,11 @@ class TestCkTileGemmRowColQuant
16261626
using typename Base::ComputeDataType;
16271627
using typename Base::DsDataType;
16281628
using typename Base::DsLayout;
1629+
using typename Base::GemmConfig;
16291630
using typename Base::QDataType;
16301631

1631-
static constexpr auto QuantType = Base::QuantType;
1632+
static constexpr auto QuantType = Base::QuantType;
1633+
static constexpr auto PreshuffleB = Base::PreshuffleB;
16321634

16331635
static constexpr ck_tile::index_t NumDTensor = DsDataType::size();
16341636

@@ -1676,7 +1678,15 @@ class TestCkTileGemmRowColQuant
16761678

16771679
// Copy to device
16781680
a_m_k_dev_buf.ToDevice(a_m_k.data());
1679-
b_k_n_dev_buf.ToDevice(b_k_n.data());
1681+
if constexpr(PreshuffleB)
1682+
{
1683+
auto b_k_n_shuffled = ck_tile::shuffle_b_v0<GemmConfig>(b_k_n);
1684+
b_k_n_dev_buf.ToDevice(b_k_n_shuffled.data());
1685+
}
1686+
else
1687+
{
1688+
b_k_n_dev_buf.ToDevice(b_k_n.data());
1689+
}
16801690
row_scales_dev_buf.ToDevice(row_scales_m.data());
16811691
col_scales_dev_buf.ToDevice(col_scales_n.data());
16821692

@@ -2127,7 +2137,10 @@ class TestCkTileGemmRowColQuant
21272137
ComputeDataType,
21282138
ComputeDataType>;
21292139

2130-
using BaseGemmPipeline = ck_tile::BaseGemmPipelineAgBgCrCompV3<GemmPipelineProblem>;
2140+
using BaseGemmPipeline = std::conditional_t<
2141+
PreshuffleB,
2142+
ck_tile::BaseWeightPreshufflePipelineAGmemBGmemCRegV2<GemmPipelineProblem>,
2143+
ck_tile::BaseGemmPipelineAgBgCrCompV3<GemmPipelineProblem>>;
21312144

21322145
constexpr auto K1 = CodegenGemmShape::WarpTile::at(ck_tile::number<2>{});
21332146
const ck_tile::index_t K_split =
@@ -2155,7 +2168,10 @@ class TestCkTileGemmRowColQuant
21552168
has_hot_loop_v,
21562169
tail_number_v>;
21572170

2158-
using GemmPipeline = ck_tile::GemmPipelineAgBgCrCompV3<PipelineProblem>;
2171+
using GemmPipeline = std::conditional_t<
2172+
PreshuffleB,
2173+
ck_tile::WeightPreshufflePipelineAGmemBGmemCRegV2<PipelineProblem>,
2174+
ck_tile::GemmPipelineAgBgCrCompV3<PipelineProblem>>;
21592175
using GemmEpilogue = ck_tile::CShuffleEpilogue<
21602176
ck_tile::CShuffleEpilogueProblem<ADataType,
21612177
BDataType,

‎test/ck_tile/gemm_block_scale/test_gemm_quant_rowcol.cpp‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@ TYPED_TEST(TestCkTileGemmRowColQuant, RowColQuantTest)
2727
this->run_test_with_validation(1024, 1024, 1024);
2828
}
2929

30+
#ifdef CK_GFX950_SUPPORT
31+
using RowColQuantPreshuffleTypes = ::testing::Types<std::tuple<RowMajor,
32+
ColumnMajor,
33+
RowMajor,
34+
RowMajor,
35+
FP8,
36+
FP8,
37+
float,
38+
Half,
39+
RowColQuant,
40+
GemmConfigPreshuffleBPrefill,
41+
GroupSize1D_128>>;
42+
43+
template <typename Tuple>
44+
class TestCkTileGemmRowColQuantPreshuffle : public TestCkTileGemmRowColQuant<Tuple>
45+
{
46+
};
47+
48+
TYPED_TEST_SUITE(TestCkTileGemmRowColQuantPreshuffle, RowColQuantPreshuffleTypes);
49+
50+
TYPED_TEST(TestCkTileGemmRowColQuantPreshuffle, PreshuffledBTest)
51+
{
52+
this->run_test_with_validation(1024, 1024, 1024);
53+
}
54+
#endif
55+
3056
template <typename Tuple>
3157
class TestCkTileGemmRowColQuantMultiD : public TestCkTileGemmRowColQuant<Tuple>
3258
{

0 commit comments

Comments
 (0)