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
/*******************************************************************************
* Copyright 2021 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.
*******************************************************************************/

#ifndef CPU_X64_BRGEMM_JIT_BRDGMM_KERNEL_HPP
#define CPU_X64_BRGEMM_JIT_BRDGMM_KERNEL_HPP

#include "common/c_types_map.hpp"
#include "common/nstl.hpp"
#include "common/type_helpers.hpp"
#include "common/utils.hpp"

#include "cpu/x64/brgemm/brgemm_types.hpp"
#include "cpu/x64/cpu_barrier.hpp"
#include "cpu/x64/injectors/jit_uni_postops_injector.hpp"
#include "cpu/x64/jit_avx512_core_bf16cvt.hpp"
#include "cpu/x64/jit_generator.hpp"

namespace dnnl {
namespace impl {
namespace cpu {
namespace x64 {

template <typename Wmm>
struct jit_brdgmm_kernel_base_t : public jit_base_brgemm_kernel_t {
    jit_brdgmm_kernel_base_t(const brgemm_desc_t &abrd);

    DECLARE_CPU_JIT_AUX_FUNCTIONS(jit_brdgmm_kernel_base_t)

    brgemm_desc_t brg;

    static bool is_fast_vnni_int8(const brgemm_desc_t &brg) {
        return brg.is_dgmm && brg.is_int8 && brg.isa_impl == avx512_core_vnni
                && brg.ldb_tail /*n_vlen_tail*/ == 0;
    }

    struct vmm_allocator_helper_t {
        vmm_allocator_helper_t(const brgemm_desc_t &brg)
            : aux_vmm_count_(0)
            , vmm_tmp_count_(0)
            , compute_vmm_base_idx_(-1)
            , compute_vmm_count_((grouped_bs(brg) ? 0 : 1)
                      + (!is_fma_embd(brg))
                              * brg.ld_block2 /*n_block*/) // vmm_a + vmm_b
            , idx_vmm_a_(-1)
            , idx_vmm_b_(-1)
            , idx_vmm_permute_(-1)
            , idx_vmm_shift_(-1)
            , idx_vmm_zp_comp_(-1)
            , idx_vmm_bcast_(-1)
            , idx_vmm_s8s8_comp_(-1) {

            if (brg.with_sum || brg.with_wei_scales) vmm_tmp_count_ = 2;

            // assign aux vmms
            if (is_fast_vnni_int8(brg)) idx_vmm_permute_ = aux_vmm_count_++;
            if (brg.req_s8s8_compensation) {
                idx_vmm_shift_ = aux_vmm_count_;
                idx_vmm_s8s8_comp_ = aux_vmm_count_++;
                assert(idx_vmm_shift_ == idx_vmm_s8s8_comp_);
            }
            const bool compute_src_zp
                    = brg.zp_type_a != brgemm_broadcast_t::none;
            if (compute_src_zp) {
                idx_vmm_zp_comp_ = aux_vmm_count_++;
                if (!is_superset(brg.isa_impl, avx512_core))
                    idx_vmm_bcast_ = aux_vmm_count_++;
            } else if (brg.with_sum
                    && (!is_superset(brg.isa_impl, avx512_core))) {
                const bool p_sum_scale_reg_set = brg.sum_scale != 1.f;
                if (p_sum_scale_reg_set)
                    idx_vmm_bcast_
                            = aux_vmm_count_++; // need extra vmm for broadcast
            }
            compute_vmm_base_idx_ = aux_vmm_count_;

            // assign compute vmms
            idx_vmm_a_ = compute_vmm_base_idx_;
            const int max_m = brg.bd_block2 + brg.bs_group - 1;
            const int max_n = brg.ld_block2;
            idx_vmm_b_
                    = vmm_a_idx(brg, max_m - 1, max_n - 1) + !is_fma_embd(brg);
        }
        int vnni_substep(const brgemm_desc_t &brg) const {
            return brg.isa_impl == avx2_vnni_2 && brg.is_xf16() ? 2 : 1;
        }
        int vmm_a_idx(const brgemm_desc_t &brg, int m, int n) const {
            const auto idx_offset = grouped_bs(brg)
                    ? (m * brg.ld_block2 + n) * vnni_substep(brg)
                    : 0;
            return idx_vmm_a_ + idx_offset;
        }

        int get_compute_vmm_count() { return compute_vmm_count_; }
        int get_aux_vmm_count() {
            return nstl::max(vmm_tmp_count_, aux_vmm_count_);
        }
        int get_idx_vmm_a() {
            assert(idx_vmm_a_ >= 0);
            return idx_vmm_a_;
        }
        int get_idx_vmm_b() {
            assert(idx_vmm_b_ >= 0);
            return idx_vmm_b_;
        }
        int get_idx_vmm_permute() {
            assert(idx_vmm_permute_ >= 0);
            return idx_vmm_permute_;
        }
        int get_idx_vmm_shift() {
            assert(idx_vmm_shift_ >= 0);
            return idx_vmm_shift_;
        }
        int get_idx_vmm_zp_comp() {
            assert(idx_vmm_zp_comp_ >= 0);
            return idx_vmm_zp_comp_;
        }
        int get_idx_vmm_bcast() {
            assert(idx_vmm_bcast_ >= 0);
            return idx_vmm_bcast_;
        }
        int get_idx_vmm_s8s8_comp() {
            assert(idx_vmm_s8s8_comp_ >= 0);
            return idx_vmm_s8s8_comp_;
        }

    private:
        int aux_vmm_count_;
        int vmm_tmp_count_;
        int compute_vmm_base_idx_;
        int compute_vmm_count_;
        int idx_vmm_a_;
        int idx_vmm_b_;
        int idx_vmm_permute_;
        int idx_vmm_shift_;
        int idx_vmm_zp_comp_;
        int idx_vmm_bcast_;
        int idx_vmm_s8s8_comp_;
    };

    static int get_aux_vmm_count(const brgemm_desc_t &brg) {
        auto vmm_alloc = vmm_allocator_helper_t(brg);
        return vmm_alloc.get_aux_vmm_count();
    }

    static int get_compute_vmm_count(const brgemm_desc_t &brg) {
        auto vmm_alloc = vmm_allocator_helper_t(brg);
        return vmm_alloc.get_compute_vmm_count();
    }

    const brgemm_desc_t &get_brg() const override { return brg; }

private:
    // note: this kernel doesn't yet support TMM's. We differentiate Wmm and Vmm
    // just to follow same template style as brgemm_kernel.
    using Vmm =
            typename utils::conditional<std::is_same<Wmm, Xbyak::Tmm>::value,
                    Xbyak::Zmm, Wmm>::type;
    using Vmm_low_t = typename vreg_traits_t<Vmm>::Vmm_lower_t;
    using po_injector_t = injector::jit_uni_postops_injector_base_t<Vmm>;
    std::unique_ptr<po_injector_t> postops_injector_;
    std::unique_ptr<bf16_emulation_t> bf16_emu_;

    Xbyak::Label permute_index_table;

    enum class compute_pad_kernel_t { s8s8_kernel, zero_point_kernel };

    injector_utils::registry_scratchpad_t regscratchpad_ {*this, brg.isa_impl};

    using reg64_t = const Xbyak::Reg64;
    // Register decomposition
    const reg64_t param1 = abi_param1;
    const reg64_t reg_A = abi_not_param1;
    const reg64_t reg_B = r8;
    // reg_aux_batch_addr changed in advance_A_B_matrices,
    // so need to be savable
    const injector_utils::reg64_savable_t reg_aux_batch_addr {
            regscratchpad_, r15};
    const reg64_t reg_BS = rsi;

    // loop variables
    const reg64_t reg_BS_loop = r12;
    const reg64_t reg_aux_M = r13;
    const reg64_t reg_aux_D = rbx;
    const injector_utils::reg64_savable_t reg_aux_C {
            regscratchpad_, rdx, rdx, may_use_rbp()};
    const reg64_t reg_aux_A = r10;
    const reg64_t reg_aux_B = abi_param1;
    const injector_utils::reg64_savable_t reg_aux1_A {
            regscratchpad_, abi_not_param1}; // brgemm_strd
    const injector_utils::reg64_savable_t reg_aux1_B {
            regscratchpad_, r8}; // brgemm_strd
    const reg64_t reg_a_offset = r9;
    const reg64_t reg_aux_N = r11;

    const reg64_t reg_aux_A_vpad_top = r14;
    const injector_utils::reg64_savable_t reg_aux_A_vpad_bottom {
            regscratchpad_, rdx, rbp, may_use_rbp()};

    const reg64_t reg_table_base = rax;
    const reg64_t reg_tmp = reg_table_base;
    const reg64_t reg_total_padding = reg_table_base;
    // reg_aux_bias changed in store_accumulators_apply_post_ops
    const injector_utils::reg64_savable_t reg_aux_bias {regscratchpad_, rax};
    const injector_utils::reg64_savable_t reg_aux_src_scales {
            regscratchpad_, rax};
    // reg_aux_wei_scales changed in store_accumulators_apply_post_ops,
    // so need to be savable
    const injector_utils::reg64_savable_t reg_aux_wei_scales {
            regscratchpad_, rax};
    const injector_utils::reg64_savable_t reg_aux_dst_scales {
            regscratchpad_, rax, r22};
    const injector_utils::reg64_savable_t reg_dst_zero_point {
            regscratchpad_, rax, r23};
    const injector_utils::reg64_savable_t reg_src_zero_point {
            regscratchpad_, rax, r24};
    const injector_utils::reg64_savable_t reg_zp_compensation {
            regscratchpad_, r12, r25};
    // abi_param1 is used in post-ops injector and by reg_aux_B,
    // so need to be savable or use other registers
    const injector_utils::reg64_savable_t reg_binary_params {
            regscratchpad_, abi_param1};
    const injector_utils::reg64_savable_t reg_ptr_sum_scale {
            regscratchpad_, r14, r27};
    const injector_utils::reg64_savable_t reg_ptr_sum_zp {
            regscratchpad_, rax, r28};
    const injector_utils::reg64_savable_t reg_s8s8_comp {
            regscratchpad_, r14, r29};

    const reg64_t reg_aux_src_zp = rax;
    const reg64_t reg_aux_zp_comp = r12;
    const reg64_t reg_aux_s8s8_comp = r14;

    Xbyak::Opmask k_mask = Xbyak::Opmask(2);
    Xbyak::Opmask k_tail_mask = Xbyak::Opmask(3);
    Xbyak::Opmask kblend_mask = Xbyak::Opmask(4);

    /* used for bfloat16 */
    reg64_t bf16_emu_scratch = reg_table_base;
    Xbyak::Zmm bf16_emu_reserv_1 = Xbyak::Zmm(0);
    Xbyak::Zmm bf16_emu_reserv_2 = Xbyak::Zmm(1);
    Xbyak::Zmm bf16_emu_reserv_3 = Xbyak::Zmm(2);
    Xbyak::Zmm bf16_emu_reserv_4 = Xbyak::Zmm(3);
    // note 1: zmm reserv_5 is not necessary since it's only used for
    // 'vdpbf16ps'
    // note 2: zmm0 collides with vmm_permute, hence need to write this value
    // before every loop.

    const int simd_w_;
    const int max_vmms_;
    const bool compute_dst_zp_, compute_src_zp_;
    const bool is_src_zp_bcast_;
    const bool compute_compensation_; // code-path for either s8s8 or src_zp
    const bool has_vpad_; // vertical padding w.r.t. M dimension
    const bool has_bpad_; // batch pad is computed for the overlap between the
            // weights and input height padding

    vmm_allocator_helper_t vmm_alloc;

    bool with_binary_non_scalar_bcast_ = false;

    inline int M() { return brg.bcast_dim; }
    inline int N() { return brg.load_dim; }
    inline int m_block2() { return brg.bd_block2; }
    inline int nb_m_block2() { return brg.bdb2; }
    inline int m_block2_tail() { return brg.bdb2_tail; }

    inline int n_block1() { return brg.ld_block; }
    inline int nb_n_block1() { return brg.ldb; }
    inline int n_block1_tail() { return brg.ldb_tail; }
    inline int n_block2() { return brg.ld_block2; }
    inline int nb_n_block2() { return brg.ldb2; }
    inline int n_block2_tail() { return brg.ldb2_tail; }
    int tail_length() { return n_block1_tail() % simd_w_; }

    inline int bs_group() const { return brg.bs_group; }
    static bool grouped_bs(const brgemm_desc_t &brg) {
        return brg.bs_group > 1;
    }
    inline bool grouped_bs() const { return grouped_bs(brg); }
    static bool is_fma_embd(const brgemm_desc_t &brg) {
        return grouped_bs(brg)
                ? false
                : brg.is_f32 && is_superset(brg.isa_impl, avx512_core);
    }
    bool is_fma_embd() { return is_fma_embd(brg); }
    bool is_fast_vnni_int8() { return is_fast_vnni_int8(brg); }
    bool is_slow_bf16_vnni() {
        // On avx512_core_amx machines, the bf16 vnni is found to be slower.
        // TODO: Check the above limitations on new cpus
        return brg.is_bf16 && mayiuse(avx512_core_amx);
    }

    bool req_vmm_reload() { return brg.is_bf16_emu; }
    bool assign_data_vmm_once() { return !req_vmm_reload(); }

    int vnni_substep() { return vmm_alloc.vnni_substep(brg); }

    int get_substep_simd(int n_i, int v_i, bool has_n_tail) {
        const int last_n_block_sz
                = n_block2_tail() > 0 ? n_block2_tail() : n_block2();
        if (has_n_tail && n_i + 1 == last_n_block_sz) {
            return nstl::min(simd_w_, n_block1_tail() - v_i * simd_w_);
        } else {
            return simd_w_;
        }
    }
    Vmm vmm_a(int m, int n) {
        const auto idx_a = vmm_alloc.vmm_a_idx(brg, m, n);
        assert(idx_a < (vmm_alloc.get_idx_vmm_b() + is_fma_embd()));
        return Vmm(idx_a);
    }
    Vmm vmm_b(int bi = 0) { return Vmm(vmm_alloc.get_idx_vmm_b() + bi); }
    Vmm accm(int m_blocks, int n_blocks, int m, int n, int vnni_idx) {
        assert(m_blocks <= m_block2() && m < m_blocks);
        assert(n_blocks <= n_block2() && n < n_blocks);
        const int accm_start = max_vmms_ - m_blocks * n_blocks * vnni_substep();
        const int accm_rel_idx
                = m * n_blocks * vnni_substep() + n * vnni_substep() + vnni_idx;
        const int idx = accm_start + accm_rel_idx;
        assert(idx < max_vmms_ && idx > vmm_b(0).getIdx());
        return Vmm(idx);
    }
    Vmm vmm_permute() { return Vmm(vmm_alloc.get_idx_vmm_permute()); }
    Vmm vmm_shift() { // -128's
        return Vmm(vmm_alloc.get_idx_vmm_shift());
    }
    Vmm vmm_s8s8_comp() { return Vmm(vmm_alloc.get_idx_vmm_s8s8_comp()); }
    Vmm vmm_zp_comp() { return Vmm(vmm_alloc.get_idx_vmm_zp_comp()); }
    Vmm vmm_bcast() { return Vmm(vmm_alloc.get_idx_vmm_bcast()); }
    Vmm vmm_tmp(int i) {
        const int idx
                = max_vmms_ - m_block2() * n_block2() * vnni_substep() - 1 - i;
        assert(idx > (is_fast_vnni_int8() - 1));
        return Vmm(idx);
    }

    template <typename U>
    U maybe_mask(const U umm_in, bool mask_flag, bool store);
    void init_masks();
    void read_params();
    void load_permute_vmm();
    void load_accumulators(int m_blocks, int n_blocks);
    void restore_A_B_matrices();
    void set_A_B_matrices();
    void advance_A_B_matrices();
    void load_a(Vmm vmma, int m_i, int n_i, int v_i, bool has_n_tail);
    void load_b(
            Vmm vmmb, int n_i, int v_i, bool has_n_tail, bool wei_zp = false);
    void comp_dot_product(compute_pad_kernel_t kernel_type, Vmm vmm_acc,
            Vmm vmmb, int n,
            bool is_tail_block); // int8 compensation dot_product (zp and s8s8)
    void pad_comp_kernel(compute_pad_kernel_t kernel_type, int m_blocks,
            int n_blocks, int padding, const Xbyak::Reg64 reg_pad,
            const std::function<int(int)> &get_mi, bool has_tail = false);
    void vertical_pad_kernel(int m_blocks, int n_blocks, bool has_tail);
    void batch_pad_kernel(int m_blocks, int n_blocks, bool has_tail = false);
    void brdgmm_microkernel(int m_blocks, int n_blocks, bool has_top_padding,
            bool has_bottom_padding, bool has_tail, int shift_a);
    void compute_loop();
    void get_batch_padding_info();
    void get_vertical_padding_info(const int m_blocks);
    void call_brdgmm_microkernel(const int m_blocks, const int n_blocks,
            bool has_n_tail, int shift_a);
    void batch_loop(const int m_blocks, const int n_blocks, bool has_n_tail);
    void cvt2ps(data_type_t type_in, const Vmm vmm_in, const Xbyak::Operand &op,
            bool mask_flag, bool store);
    void apply_post_ops(int m_blocks, int n_blocks, bool has_n_tail);
    void maybe_transpose_interleaved_vnni_to_plain(
            int m_blocks, int n_blocks, bool has_n_tail);
    void load_src_zp();
    void compute_int8_compensation(int m_blocks, int n_blocks, bool has_n_tail);
    void store_accumulators(int m_blocks, int n_blocks, bool has_n_tail);
    void store_accumulators_without_post_ops(
            int m_blocks, int n_blocks, bool has_n_tail);
    void store_accumulators_apply_post_ops(
            int m_blocks, int n_blocks, bool has_n_tail);
    bool check_effective_padding() { return has_vpad_ && M() > m_block2(); }
    int oc_logical_offset(int n) { return n * n_block1(); }
    int A_offset(int m, int n) {
        return brg.typesize_A * (m * brg.LDA + n * n_block1());
    }
    int B_offset(int n) { return brg.typesize_B * n * n_block1(); }
    int C_offset(int m, int n, int v) {
        return brg.typesize_C * (m * brg.LDC + n * n_block1() + v * simd_w_);
    }
    int D_offset(int m, int n, int v) {
        return brg.typesize_D * (m * brg.LDD + n * n_block1() + v * simd_w_);
    }
    int bias_offset(int n, int v) {
        return brg.typesize_bias * (n * n_block1() + v * simd_w_);
    }
    int wei_scales_offset(int n, int v) {
        return sizeof(float) * brg.is_oc_scale * (n * n_block1() + v * simd_w_);
    }
    size_t comp_offset(int n) { return sizeof(int32_t) * n * n_block1(); }

    void generate() override;
};

} // namespace x64
} // namespace cpu
} // namespace impl
} // namespace dnnl

#endif