onednn-src 0.1.13

Source of oneAPI Deep Neural Network Library (oneDNN)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
/*******************************************************************************
* Copyright 2019 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/


#include "alloc_utils.hpp"
#include "gemmstone/generator.hpp"
#include "hw_utils.hpp"
#include "layout_utils.hpp"
#include "map.hpp"
#include "ngen_object_helpers.hpp"
#include "quantization.hpp"

GEMMSTONE_NAMESPACE_START

using namespace ngen;
using std::vector;


// Prepare 2D dequantization layouts.
template <HW hw>
bool Generator<hw>::gemmMake2DQuantizationLayouts(bool isA, const GEMMProblem &problem, GEMMStrategy &strategy, GEMMState &state)
{
    auto lateOffset = isA ? problem.needsBGroupSums() : problem.needsAGroupSums();
    int xoPtrDims = (isA ? problem.aoPtrDims : problem.boPtrDims);
    bool xo2D = isA ? problem.aOffset2D()       : problem.bOffset2D();
    bool xs2D = isA ? problem.aScale2D()        : problem.bScale2D();
    bool xg2D = isA ? problem.needsAGroupSums() : problem.needsBGroupSums();
    bool xoTo2D = !xo2D && (isA ? problem.aOffset == ABOffset::Calc && (problem.earlyDequantizeA() || lateOffset)
                                : problem.bOffset == ABOffset::Calc && (problem.earlyDequantizeB() || lateOffset));
    bool cColMajor = isRegisterColMajor(problem.Tc_ext, problem.C, strategy.C);

    if (!xo2D && !xoTo2D && !xs2D && !xg2D) return true;

    auto &X_strategy       = isA ? strategy.A             : strategy.B;
    auto &X_offsetStrategy = isA ? strategy.AO            : strategy.BO;
    auto &X_scaleStrategy  = isA ? strategy.A_scale       : strategy.B_scale;
    auto &Xg_strategy      = isA ? strategy.Ag            : strategy.Bg;
    auto &X_offsetLayout   = isA ? state.A_offsetLayout   : state.B_offsetLayout;
    auto &X_scaleLayout    = isA ? state.A_scaleLayout    : state.B_scaleLayout;
    auto &Xg_layout        = isA ? state.Ag_layout        : state.Bg_layout;
    auto &Xr_offsetLayout  = isA ? state.Ar_offsetLayout  : state.Br_offsetLayout;
    auto &Xr_scaleLayout   = isA ? state.Ar_scaleLayout   : state.Br_scaleLayout;
    auto &Xgr_layout       = isA ? state.Agr_layout       : state.Bgr_layout;

    auto &XO         = isA ? problem.AO         : problem.BO;
    auto &XS         = isA ? problem.A_scale    : problem.B_scale;
    auto &Xg         = isA ? problem.Ag         : problem.Bg;
    auto Tx_ext      = isA ? problem.Ta_ext     : problem.Tb_ext;
    auto Tx          = isA ? problem.Ta         : problem.Tb;
    auto Txo         = isA ? problem.Tao        : problem.Tbo;
    auto Txs         = isA ? problem.Ta_scale   : problem.Tb_scale;
    auto Txg         = isA ? problem.Tag        : problem.Tbg;
    auto xqGroupK    = isA ? problem.aqGroupK   : problem.bqGroupK;
    auto xqGroupMN   = isA ? problem.aqGroupM   : problem.bqGroupN;
    auto &Txo_int    = isA ? state.Tao_int      : state.Tbo_int;
    auto &Txs_int    = isA ? state.Ta_scaleInt  : state.Tb_scaleInt;
    auto &Txg_int    = isA ? state.Tag_int      : state.Tbg_int;
    auto &lateScale  = isA ? state.lateScale2DA : state.lateScale2DB;

    auto wgTileMN = strategy.wgTile(isA ? LoopM : LoopN);

    Txo_int = Txo.isInteger() ? Tx.asSignedInt() : Tx;
    Txs_int = Tx;
    if (Tx == Type::bf16)
        Txs_int = Type::f32;
    Txg_int = Txg;

    int cpoDiv = 1;
    if (Txo_int.isInt8()) Txo_int = Type::s16, cpoDiv = 2;
    // Use lateScale for cases of applying scale to inputs that will be natively dpas'd
    // but do not support add/mul.
    if (xs2D && ((Txs.paddedSize() > Tx.paddedSize() && Tx.isInteger()) || problem.forceLateQuant(hw, minOuterProductCount(hw, problem, strategy)) || state.useBDPAS)) {
        lateScale = true;
        Txs_int = problem.Tc;
    }

    bool int4SpecialPath = Tx_ext.isInt4() && one_of(Tx, {Type::f16, Type::f32});
    if (int4SpecialPath) {
        Txo_int = Type::f16;
        Txs_int = Tx;
        if (Tx == Type::bf16) Txs_int = Type::f16;
    }

    if (lateOffset && (Txo.isInt4() || Txo.isInt8()))
        Txo_int = Type::s32;

    if (Txs == Type::f8_e8m0 && state.useBDPAS)
	Txs_int = Type::f8_e8m0;
    // Get tile sizes, depending on whether A/B are copied to SLM.
    // For late scaling (after compute), scales are always applied to the whole tile.
    int r, c, k, rNoSLM, cNoSLM;
    int tileR = 0, tileC = 0;
    bool remR = false, remC = false;
    if (isA) {
        bool slmA = strategy.slmA;
        rNoSLM = strategy.unroll[LoopM];
        cNoSLM = strategy.ka_load;
        r = slmA ? state.ma_slm : rNoSLM;
        c = slmA ? state.ka_slm : cNoSLM;
        k = slmA ? strategy.unrollKSLM : cNoSLM;
        r = std::max(1, r / xqGroupMN);
        c = state.kaq = std::max(1, c % xqGroupK == 0 ? c / xqGroupK : 1);
        state.kaqStride = std::max(1, k % xqGroupK == 0 ? k /  xqGroupK : 1);
        rNoSLM = std::max(1, rNoSLM / xqGroupMN);
        cNoSLM = state.kaqLate = std::max(1, cNoSLM % xqGroupK == 0 ? cNoSLM /  xqGroupK: 1);
        remR = (strategy.remHandling[LoopM] != RemainderHandling::Ignore);
        if (xqGroupMN <= 1 && xqGroupK > 1) tileC = 1;
        if (xqGroupMN > 1 && (xqGroupMN % strategy.unroll[LoopM] && strategy.unroll[LoopM] % xqGroupMN))
            stub("Tile size not compatible with group size in m dimension");
    } else {
        bool slmB = strategy.slmB;
        cNoSLM = strategy.unroll[LoopN];
        rNoSLM = strategy.kb_load;
        c = slmB ? state.nb_slm : cNoSLM;
        r = slmB ? state.kb_slm : rNoSLM;
        k = slmB ? strategy.unrollKSLM : rNoSLM;
        c = std::max(1, c / xqGroupMN);
        r = state.kbq = std::max(1, r % xqGroupK == 0 ? r / xqGroupK : 1);
        state.kbqStride = std::max(1, k % xqGroupK == 0 ?  k / xqGroupK : 1);
        cNoSLM = std::max(1, cNoSLM / xqGroupMN);
        rNoSLM = state.kbqLate = std::max(1, rNoSLM % xqGroupK == 0 ?  rNoSLM / xqGroupK : 1);
        remC = (strategy.remHandling[LoopN] != RemainderHandling::Ignore);
        if (xqGroupMN <= 1 && xqGroupK > 1) tileR = 1;
        if (xqGroupMN > 1 && (xqGroupMN % strategy.unroll[LoopN] && strategy.unroll[LoopN] % xqGroupMN))
            stub("Tile size not compatible with group size in n dimension");
    }

    int ro = lateOffset ? rNoSLM : r;
    int co = lateOffset ? cNoSLM : c;
    int rs = lateScale  ? rNoSLM : r;
    int cs = lateScale  ? cNoSLM : c;

    if (X_strategy.padded) {
        X_offsetStrategy.padded = X_scaleStrategy.padded = Xg_strategy.padded = true;
        remR = remC = false;
    }

    bool wantCM = isA ^ (xqGroupMN > 1);
    auto chooseAccess = [=](const MatrixAddressing &Xq) {
        return (wantCM == isColMajor(Xq.layout)) ? AccessType::Block : AccessType::Scattered;
    };

    X_offsetStrategy.accessType = chooseAccess(XO);
    X_scaleStrategy.accessType  = chooseAccess(XS);
    Xg_strategy.accessType      = chooseAccess(Xg);

    if (xo2D && !(X_offsetLayout = RegisterLayout::tryCreate(hw, Txo, ro,     co,     XO, X_offsetStrategy, remR, remC))) return false;
    if (xs2D &&  !(X_scaleLayout = RegisterLayout::tryCreate(hw, Txs, rs,     cs,     XS, X_scaleStrategy,  remR, remC))) return false;
    if (xg2D &&      !(Xg_layout = RegisterLayout::tryCreate(hw, Txg, rNoSLM, cNoSLM, Xg, Xg_strategy,      remR, remC))) return false;

    // Adjust masks for m/n grouping.
    auto adjustMask = [=](MaskInfo &mask) {
        if (!mask || xqGroupMN <= 1) return;
        if (!is_zero_or_pow2(xqGroupMN)) {
            if (xqGroupMN < wgTileMN) stub();
            return;
        }
        if (mask.fixed.isFixed) stub();
        mask.variable.rshift += ilog2(xqGroupMN);
    };

    for (auto *Xq_layout: {&X_offsetLayout, &X_scaleLayout, &Xg_layout}) {
        for (auto &block: *Xq_layout) {
            adjustMask(block.rowMask);
            adjustMask(block.colMask);
        }
    }

    // Quantization parameters will be upconverted to the size of A/B and duplicated to match crosspack.
    auto &lsrc = isA ? (strategy.slmA ? state.Ao_layout : !state.Ar_layout.empty() ? state.Ar_layout : state.A_layout)
                     : (strategy.slmB ? state.Bo_layout : !state.Br_layout.empty() ? state.Br_layout : state.B_layout);
    if (lsrc.empty()) stub();
    int crosspack = lsrc[0].crosspack;
    if (xqGroupMN > 1)
        crosspack = 1;

    int cpo = lateOffset ? 1 : div_up(crosspack, cpoDiv);
    int cps = lateScale  ? 1 : crosspack;

    auto makeQRepack = [&, tileR, tileC](Type Txq, Type Txq_int, RegisterLayout &repack, const RegisterLayout &src,
                                         int m, int n, int cp, bool forceRepack) mutable {
        if (cp > 1 || (cColMajor && (cp != src[0].crosspack)) || Txq != Txq_int || forceRepack) {
            bool allowPartialRegs = false;
            // Native MXFP DPAS support
            if (state.useBDPAS) {
                allowPartialRegs = true;
                cp = 1;
                if (isA) {
                    tileR = problem.aqGroupK;
                } else {
                    tileC = problem.bqGroupK;
                }
            }
            repack = RegisterLayout(hw, Txq_int, m, n, wantCM, cp, tileR, tileC, allowPartialRegs);
        }
    };

    if (xo2D) makeQRepack(Txo, Txo_int, Xr_offsetLayout, X_offsetLayout, ro,     co,     cpo, false);
    if (xs2D) makeQRepack(Txs, Txs_int, Xr_scaleLayout,  X_scaleLayout,  rs,     cs,     cps, lateScale);
    if (xg2D) makeQRepack(Txg, Txg_int, Xgr_layout,      Xg_layout,      rNoSLM, cNoSLM, 1,   true);

    if (xoTo2D) {
        if (xoPtrDims <= 0)
            Xr_offsetLayout = RegisterLayout(hw, Txo_int, 1, 1, isA);
        else if (xoPtrDims == 1)
            Xr_offsetLayout = RegisterLayout(hw, Txo_int, ro, co, isA, cpo, tileR, tileC, false);
        else stub();
    }

    return true;
}

// Convert and repack 2D grouped quantization data in preparation for dequantization.
template <HW hw>
void Generator<hw>::gemmRepack2DQuantizationData(Type Ts, Type Td, const RegisterLayout &layoutSrc, const RegisterLayout &layoutDst,
                                                 const GRFMultirange &src, const GRFMultirange &dst,
                                                 const GEMMProblem &problem, const GEMMStrategy &strategy, GEMMState &state)
{
    if (layoutDst.empty()) return;

    // Copy, broadcasting 1D to 2D data as needed.
    for (int doffR = 0; doffR < layoutDst.rows(); doffR += layoutSrc.rows())
        for (int doffC = 0; doffC < layoutDst.cols(); doffC += layoutSrc.cols())
            copyRegisters(Ts, Td, layoutSrc, layoutDst, src, dst, doffR, doffC, false, strategy, state);

    int r = layoutDst.rows();
    int c = layoutDst.cols();
    // FP4 bdpas with group > 32 requires duplicating scales into upper half of registers.
    if(state.useBDPAS && r * c < minOuterProductCount(hw, problem, strategy)){
        int halfRegElems = elementsPerGRF(hw, Td) / 2;
        if(r * c > halfRegElems) stub();
        RegisterLayout offsetLayout(layoutDst);
        for( auto &b : offsetLayout){
            if(b.nr * b.nc > halfRegElems) stub();
            b.offsetBytes += (b.nr * b.nc);
        }
        copyRegisters(Td, Td, layoutDst, offsetLayout, dst, dst, 0, 0, false, strategy, state); 
    }

    // Duplicate data in padded region. TODO: do this as part of the copy.
    int cp = layoutDst[0].crosspack;
    int p0 = layoutDst[0].colMajor ? layoutDst[0].nc : layoutDst[0].nr;

    if (cp > 1) map(hw, Td, dst, layoutDst, strategy, [&](int simd, RegData r) {
        Subregister r0 = GRF(r.getBase()).sub(r.getOffset(), r.getType());
        moveToIntPipe(r0);
        auto r1 = r0;
        for (int i = p0; i < cp; i++) {
            r1.setOffset(r1.getOffset() + 1);
            mov(simd / cp, r1(cp), r0(cp));
        }
    });
}

template <HW hw>
void Generator<hw>::gemmRepack2DQuantizationData(const RegisterLayout &layoutSrc, const RegisterLayout &layoutDst,
                                                 const GRFMultirange &src, const GRFMultirange &dst,
                                                 const GEMMProblem &problem, const GEMMStrategy &strategy, GEMMState &state)
{
    gemmRepack2DQuantizationData(layoutSrc.type(), layoutDst.type(), layoutSrc, layoutDst,
                                 src, dst, problem, strategy, state);
}

template <HW hw>
void Generator<hw>::gemmRepack2DOffsetData(Type Text, const RegisterLayout &layoutSrc, const RegisterLayout &layoutDst,
                                           const GRFMultirange &src, const GRFMultirange &dst,
                                           const GEMMProblem &problem, const GEMMStrategy &strategy, GEMMState &state)
{
    auto Ts = layoutSrc.type(), Td = layoutDst.type();

    bool s4 = (Text == Type::s4);
    bool s8 = (Ts == Type::s8);
    bool u8 = (Ts == Type::u8);

    bool int4SpecialPath = Text.isInt4() && Td == Type::f16;
    auto tmpType = Td;

    if (int4SpecialPath) {
        if (u8) tmpType = Type::u16;
        if (s8) tmpType = Type::s16;
    }

    gemmRepack2DQuantizationData(Ts, tmpType, layoutSrc, layoutDst, src, dst, problem, strategy, state);

    if (int4SpecialPath) {
        if (s8 || u8) {
            int off = s4 ? 8 : 0;

            // Shift s8 -> u8 data.
            if (s8) {
                map(hw, Type::s16, dst, dst, strategy, [&](int esize, RegData r, RegData _) {
                    add(esize, r, r, 0x80);
                });
                off -= 0x80;
            }

            // Reinterpret as f16 and undo offsets.
            if (off != 0) map(hw, Type::f16, dst, dst, strategy, [&](int esize, RegData r, RegData _) {
                uint16_t offF16 = std::abs(off);
                if (off < 0) offF16 |= 0x8000;
                add(esize, r, r, Immediate::hf(offF16));
            });

            // Rescale into normal range. End result is 2^(-12) * intended offset.
            map(hw, Type::f16, dst, dst, strategy, [&](int esize, RegData r, RegData _) {
                mul(esize, r, r, Immediate::hf(0x6C00));
            });
        } else {
            map(hw, Type::f16, dst, dst, strategy, [&](int esize, RegData r, RegData _) {
                s4 ? mad(esize, r, Immediate::hf(0x1800), r, Immediate::hf(0x0C00))     // 0x1800 = 8 * 2^(-12)
                   : mul(esize, r,                        r, Immediate::hf(0x0C00));    // 0x0C00 = 2^(-12)
            });
        }
    }
}

// Apply a single 2D group dequantize operation (scale/multiply).
template <HW hw>
void Generator<hw>::gemmDequantizeOperation(bool doA, Type T, Type Tq, BinaryOp op,
                                            const RegisterLayout &layout, const RegisterLayout &qlayout,
                                            const GRFMultirange &regs, const GRFMultirange &qregs,
                                            int h, int kab_load, int kq_load, const GEMMProblem &problem, const CommonStrategy &strategy, CommonState &state)
{
    int xqGroupK  = doA ? problem.aqGroupK : problem.bqGroupK;
    int xqGroupMN = doA ? problem.aqGroupM : problem.bqGroupN;

    bool common = (qlayout.rows() * qlayout.cols()) == 1;
    bool colMajor = layout.colMajor();
    int xqGroupX = colMajor == doA ? xqGroupMN : xqGroupK;
    int xqGroupY = colMajor == doA ? xqGroupK : xqGroupMN;

    bool bfSpecialPath = (T == Type::bf16) && (Tq == Type::f32) && (xqGroupMN > 1) && layout.hasFullCrosspack(2) && (xqGroupY == 1);
    GRFMultirange qPairs;
    int npairs = 0;

    if (bfSpecialPath) {
        for (npairs = 8; npairs > 0; npairs >>= 1) {
            qPairs = tryChunkAlloc(npairs, 1, Bundle(), BundleGroup::AllBundles(), state);
            if (!qPairs.empty()) break;
        }
        if (qPairs.empty()) throw out_of_registers_exception();
    }

    for (auto &block: layout) {
        auto crosspack = block.crosspack;
        int nx = colMajor ? block.nr : block.nc;
        int ny = colMajor ? block.nc : block.nr;

        // If crosspack spans multiple groups, use a stride to restrict to one group
        bool qbroadcastY = (xqGroupY % crosspack == 0) || common || bfSpecialPath;
        int strided = 1;
        if (!qbroadcastY) {
            strided = crosspack;
        }

        bool qbroadcastX = xqGroupX > 1 || common;
        int strideq = qbroadcastX ? 0 : 1;
        if (bfSpecialPath)
            strideq = 1;

        // Unpack iteration # and block offsets into MNK indices
        auto h_block = align_down(h, kab_load) + (doA ? block.offsetC : block.offsetR);
        auto mn_block = doA ? block.offsetR : block.offsetC;

        for(int y0 = 0; y0 < ny; y0 += qbroadcastY ? crosspack : 1) {
        for(int x0 = 0; x0 < nx; ) {
            auto ii0 = colMajor ? x0 : y0;
            auto jj0 = colMajor ? y0 : x0;

            auto h_final = h_block + (doA ? jj0 : ii0);
            auto mn_final = mn_block + (doA ? ii0 : jj0);
            auto mnq = mn_final / xqGroupMN;
            auto hq = (h_final % kq_load) / xqGroupK;

            auto io0 = doA ? mnq : hq;
            auto jo0 = doA ? hq : mnq;

            // Common scales always load the first element
            if (common) io0 = jo0 = 0;

            int ne, neq;
            const RegisterBlock *qblock;
            auto data = block.find(T, ii0, jj0, regs, &ne);
            auto qdata = qlayout.find(io0, jo0, qregs, &neq, &qblock);

            if (!qbroadcastX) ne = std::min(ne, neq);

            // If a group lies along the X-direction, limit ne to the end of the current group
            if (xqGroupX > 1) ne = std::min(ne, xqGroupX - x0 % xqGroupX);

            // Broadcast pairs of scales, in case of stride-2 bfloat16 data.
            if (bfSpecialPath) {
                int npair = (qdata.getOffset() / 2) % npairs;
                if (x0 == 0 && npair == 0)
                    for (int p = 0; p < npairs; p++)
                        mov(elementsPerGRF(hw, Tq), qPairs[p].ud(), qdata.ud(2*p)(0,2,1));
                qdata = qPairs[npair].sub(0, Tq.ngen());
            }

            int maxSIMD = (op == BinaryOp::Sub && T.isInt8()) ? 64 : 32;
            if (Tq == Type::f32) maxSIMD = elementsPerGRF(hw, Tq);
            int simd = std::min({ne * crosspack / strided, 2 * elementsPerGRF(hw, T) / strided, maxSIMD});
            switch (op) {
                case BinaryOp::Sub:
                    if (T.isInt8() && strided == 1) {
                        add(simd / 2, data(2), data(2), -qdata(strideq * 2 / Tq));
                        data.setOffset(data.getOffset() + 1);
                        qdata.setOffset(qdata.getOffset() + strideq / Tq);
                        add(simd / 2, data(2), data(2), -qdata(strideq * 2 / Tq));
                    } else
                        add(simd, data(strided), data(strided), -qdata(strideq));
                    break;
                case BinaryOp::Mul:
                    emul(simd, data(strided), data(strided), qdata(strideq), strategy, state);
                    break;
                case BinaryOp::ScaleSub:
                    if (T != Type::f16) stub();
                    mad(simd, data(strided), -qdata(strideq), data(strided), Immediate::hf(0x6C00));  /* 0x6C00 = 2^12 */
                    break;
                default: stub();
            }
            x0 += simd * strided / crosspack;
        }
        }
    }

    safeReleaseRanges(qPairs, state);
}

// Shift s4 data by 8 to transfrom it into u4 data.
template <HW hw>
void Generator<hw>::dequantizeInt4Shift(Type Tsrc, GRFMultirange src, const CommonStrategy &strategy)
{
    if (Tsrc != Type::s4) return;
    map(hw, Type::u16, src, src, strategy, [&](int esize, RegData r, RegData _) {
        xor_(esize, r, r, 0x8888);
    });
}

// Optimized int4 -> f16/bf16/f32 dequantization sequence.
template <HW hw>
void Generator<hw>::dequantizeInt4(bool doA, const RegisterLayout &layoutSrc, const RegisterLayout &layoutDst,
                                   const RegisterLayout &layoutOffset, const RegisterLayout &layoutScale,
                                   const GRFMultirange &src, const GRFMultirange &dst, const GRFMultirange &offset, const GRFMultirange &scale,
                                   int offR, int offC, int h, int kab_load, int kq_load,
                                   const GEMMProblem *problem, const CommonStrategy &strategy, CommonState &state, bool s4Shift)
{
    auto Tsrc = layoutSrc.type(), Tdst = layoutDst.type();
    if (!canDequantizeInt4(layoutSrc, layoutDst, layoutOffset, layoutScale))
        stub("Cannot perform dequantizeInt4");

    bool s4 = Tsrc.isSigned();
    bool f32 = (Tdst == Type::f32);
    bool bf16 = (Tdst == Type::bf16);

    RegisterLayout layoutDstF16;
    const RegisterLayout *effLayoutDst = &layoutDst;
    GRFMultirange dstF16;
    const GRFMultirange *effDst = &dst;
    if (f32 || bf16) {
        layoutDstF16 = RegisterLayout(hw, Type::f16, layoutSrc.rows(), layoutSrc.cols(), layoutDst.colMajor(), layoutDst.crosspack(), 0, 0, (hw < HW::XeHP));
        for (auto &block: layoutDstF16) {
            block.offsetR += layoutDst[0].offsetR;
            block.offsetC += layoutDst[0].offsetC;
        }
        dstF16 = chunkAlloc(layoutDstF16.regs(), 2, state);
        effLayoutDst = &layoutDstF16;
        effDst = &dstF16;
    }

    // 1) Shift s4 data to u4 data by adding 8.
    if (s4 && s4Shift)
        dequantizeInt4Shift(Tsrc, src, strategy);

    // 2) Copy u4 -> u16 data.
    copyRegisters(Type::u4, Type::u16, layoutSrc, *effLayoutDst, src, *effDst, offR, offC, false, strategy, state);

    // 3) Reinterpret u16 data as denormal f16, scale into normal range and subtract (rescaled) offsets if available.
    //     The required rescaling factor (2^24) is necessarily outside f16 range,
    //     so two multiplications are needed.
    if (!layoutOffset.empty()) {
        if (!problem) stub();
        gemmDequantizeOperation(doA, Type::f16, Type::f16, BinaryOp::ScaleSub, *effLayoutDst, layoutOffset, *effDst, offset, h, kab_load, kq_load, *problem, strategy, state);
    } else {
        map(hw, Type::f16, *effDst, *effLayoutDst, strategy, [&](int esize, RegData r) {
            s4 ? mad(esize, r, Immediate::hf(0x9800), r, Immediate::hf(0x6C00)) /* 0x9800 = -8*2^(-12), 0x6C00 = 2^12 */
               : mul(esize, r, r, Immediate::hf(0x6C00));
        });
    }

    // 4) Finish rescaling -- remaining factor is 2^12.
    map(hw, Type::f16, *effDst, *effLayoutDst, strategy, [&](int esize, RegData r) {
        mul(esize, r, r, Immediate::hf(0x6C00));
    });

    // 5) Apply scales if present. If the scales are not too large (absolute value < 128),
    //      this could be merged into the previous multiplication.
    if (!f32 && !layoutScale.empty()) {
        if (!problem) stub();
        gemmDequantizeOperation(doA, Type::f16, Type::f16, BinaryOp::Mul, *effLayoutDst, layoutScale, *effDst, scale, h, kab_load, kq_load, *problem, strategy, state);
    }

    // 6) Convert to dst type if needed.
    if (f32 || bf16) {
        copyRegisters(Type::f16, Tdst, layoutDstF16, layoutDst, dstF16, dst, offR, offC, false, strategy, state);
        safeReleaseRanges(dstF16, state);
    }

    // 7) Apply scales for f32 after f16->f32 upconversion.
    if (f32 && !layoutScale.empty()) {
        if (!problem) stub();
        gemmDequantizeOperation(doA, Type::f32, Type::f32, BinaryOp::Mul, layoutDst, layoutScale, dst, scale, h, kab_load, kq_load, *problem, strategy, state);
    }
}

// Dequantize A/B, given 2D grouped quantization data.
template <HW hw>
void Generator<hw>::gemmDequantizeAB(bool doA, const RegisterLayout &layoutSrc, const RegisterLayout &layoutDst0,
                                     const GRFMultirange &src, const GRFMultirange &dst0, int h, int kab_load, int kab_repack, int kq_load,
                                     const GEMMProblem &problem, const GEMMStrategy &strategy, GEMMState &state,
                                     bool s4Shift)
{
    auto Tsrc = layoutSrc.type(), Tdst = layoutDst0.type();
    auto Txo_int     = doA ? state.Tao_int             : state.Tbo_int;
    auto Txs_int     = doA ? state.Ta_scaleInt         : state.Tb_scaleInt;
    auto &oiLayout   = doA ? state.A_offsetLayout      : state.B_offsetLayout;
    auto &orLayout   = doA ? state.Ar_offsetLayout     : state.Br_offsetLayout;
    auto &oiRegs     = doA ? state.A_offsetRegs        : state.B_offsetRegs;
    auto &orRegs     = doA ? state.Ar_offsetRegs       : state.Br_offsetRegs;
    auto &siLayout   = doA ? state.A_scaleLayout       : state.B_scaleLayout;
    auto &srLayout   = doA ? state.Ar_scaleLayout      : state.Br_scaleLayout;
    auto &siRegs     = doA ? state.A_scaleRegs         : state.B_scaleRegs;
    auto &srRegs     = doA ? state.Ar_scaleRegs        : state.Br_scaleRegs;
    bool lateOffset  = doA ? problem.needsBGroupSums() : problem.needsAGroupSums();
    bool lateScale   = doA ? state.lateScale2DA        : state.lateScale2DB;

    auto &oLayout = orLayout.empty() ? oiLayout : orLayout;
    auto &oRegs   = orLayout.empty() ? oiRegs   : orRegs;
    auto &sLayout = srLayout.empty() ? siLayout : srLayout;
    auto &sRegs   = srLayout.empty() ? siRegs   : srRegs;

    bool xo2D = !oLayout.empty() && !lateOffset;
    bool xs2D = !sLayout.empty() && !lateScale;

    bool copy = !layoutDst0.empty();
    auto layoutDst = copy ? layoutDst0 : layoutSrc;
    auto dst       = copy ? dst0 : src;

    auto Tx_int = xo2D ? Txo_int : Tdst;

    if (xo2D && !xs2D && (Txo_int.bits() > Tdst.bits()))
        Tx_int = Tdst;

    int offR = doA ? 0 : h % kab_repack;
    int offC = doA ? h % kab_repack : 0;
    int offR0 = offR, offC0 = offC;

    int ms = layoutSrc.rows(), ns = layoutSrc.cols();
    int md = layoutDst.rows(), nd = layoutDst.cols();

    if (ms < md || ns < nd) {
        if (!copy) stub();
        layoutDst = RegisterLayout(hw, Tdst, ms, ns, layoutDst0.colMajor(), layoutDst[0].crosspack);
        dst = chunkAlloc(layoutDst.regs(), 2, state);
        offR = offC = 0;
    }

    if (canDequantizeInt4(layoutSrc, layoutDst, oLayout, sLayout)) {
        dequantizeInt4(doA, layoutSrc, layoutDst, oLayout, sLayout,
                       src, dst, oRegs, sRegs, offR, offC, h, kab_load, kq_load, &problem,
                       strategy, state, s4Shift);
    } else {
        if (copy)
            copyRegisters(Tsrc, Tx_int, layoutSrc, layoutDst, src, dst, offR, offC, false, strategy, state);
        else if (Tsrc.asSigned() != Tx_int.asSigned())
            convert(src, Tsrc, Tx_int, strategy, state);

        if (xo2D) {
            if (!state.useBDPAS)
            {
            gemmDequantizeOperation(doA, Tx_int, Txo_int, BinaryOp::Sub, layoutDst, oLayout, dst, oRegs, h, kab_load, kq_load, problem, strategy, state);
            convert(dst, Tx_int, Tdst, strategy, state);
            }
        }

        if (xs2D)
            if (!state.useBDPAS)
            {
            gemmDequantizeOperation(doA, Tdst, Txs_int, BinaryOp::Mul, layoutDst, sLayout, dst, sRegs, h, kab_load, kq_load, problem, strategy, state);
	    }
    }

    if (ms < md || ns < nd) {
        copyRegisters(Tdst, Tdst, layoutDst, layoutDst0, dst, dst0, offR0, offC0, false, strategy, state);
        safeReleaseRanges(dst, state);
    }
}

GEMMSTONE_NAMESPACE_END