| // Copyright 2024 Google LLC |
| // |
| // This source code is licensed under the BSD-style license found in the |
| // LICENSE file in the root directory of this source tree. |
| |
| $ABC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" |
| $assert NR % 16 == 0 |
| $assert 16 <= NR <= 64 |
| $assert 1 <= MR <= 16 |
| $assert REQUANTIZATION == "FP32" or not REQUANTIZATION |
| $assert DATATYPE in ["QD8_F32", "QC8"] |
| #include <assert.h> |
| // Disable gcc amxintrin header |
| #define _AMXTILEINTRIN_H_INCLUDED |
| |
| #include <immintrin.h> |
| |
| #include <xnnpack/gemm.h> |
| #include <xnnpack/intrinsics-polyfill.h> |
| #include <xnnpack/math.h> |
| #include <xnnpack/unaligned.h> |
| $if PREFETCH: |
| #include <xnnpack/prefetch.h> |
| |
| $DATATYPE_SPEC = {"QC8": "qs8_qc8w", "QD8_F32": "qd8_f32_qc8w", "QC4_F32": "qd8_f32_qc4w"}[DATATYPE] |
| $REQUANTIZATION_SPEC = "" if DATATYPE in ["QD8_F32", "QC4_F32"] else "_" + REQUANTIZATION.lower() |
| $PARAMS_STRUCT = REQUANTIZATION.lower() + "_avx512" |
| $PARAMS_UNION = {"QC8": "xnn_qs8_qc8w_conv_minmax_params", "QD8_F32": "xnn_f32_minmax_params", "QC4_F32": "xnn_f32_qc4w_minmax_params"}[DATATYPE] |
| $OUT_T = "float" if DATATYPE in ["QD8_F32", "QC4_F32"] else "int8_t" |
| $_MM_MAX_EPX8 = "_mm_max_epi8" |
| $_MM512_CVTXEPI32_EPI8 = "_mm512_cvtsepi32_epi8" |
| void xnn_${DATATYPE_SPEC}_gemm_minmax${REQUANTIZATION_SPEC}_ukernel_${MR}x${NR}c4__avx512amx${"_prfm" if PREFETCH else ""}( |
| size_t mr, |
| size_t nc, |
| size_t kc, |
| const int8_t* restrict a, |
| size_t a_stride, |
| const void* restrict w, |
| ${OUT_T}* restrict c, |
| size_t cm_stride, |
| size_t cn_stride, |
| $if DATATYPE in ["QD8_F32", "QC4_F32"]: |
| const union ${PARAMS_UNION} params[restrict XNN_MIN_ELEMENTS(1)], |
| const struct xnn_qd8_quantization_params quantization_params[restrict XNN_MIN_ELEMENTS(1)]) |
| $else: |
| const union ${PARAMS_UNION} params[restrict XNN_MIN_ELEMENTS(1)]) |
| { |
| assert(mr != 0); |
| assert(mr <= ${MR}); |
| assert(nc != 0); |
| assert(kc != 0); |
| assert(kc % sizeof(int8_t) == 0); |
| assert(a != NULL); |
| assert(w != NULL); |
| assert(c != NULL); |
| |
| // TODO: amxintrin.h only provide intrinsics for __x86_64__ |
| // amxintrin.h doesnt build with gcc sandbox enabled |
| // Update if amxintrin changes |
| #if defined(__x86_64__) && defined(__clang__) |
| $for N in range(0, NR, 16): |
| __attribute__((aligned(64))) int32_t res${N // 16}[${MR} * 16]; |
| |
| kc = round_up_po2(kc, 4 * sizeof(int8_t)); |
| size_t kremainder = kc & 63; |
| if (kremainder == 0) { // zero is invalid config |
| kremainder = 64; |
| } |
| |
| // Define tile config data structure |
| struct __tile_config { |
| uint8_t palette_id; |
| uint8_t start_row; |
| uint8_t reserved_0[14]; |
| uint16_t colsb[8]; |
| uint16_t reserved_1[8]; |
| uint8_t rows[8]; |
| uint8_t reserved_2[8]; |
| }; |
| |
| // Load tile configuration |
| __attribute__((aligned(64))) struct __tile_config tile_data = {0}; |
| tile_data.palette_id = 1; |
| tile_data.rows[0] = mr; // tmm0 = res 0 |
| tile_data.rows[1] = mr; // tmm1 = res 1 |
| tile_data.rows[2] = mr; // tmm2 = res 2 |
| tile_data.rows[3] = mr; // tmm3 = res 3 |
| tile_data.rows[4] = mr; // tmm4 = input |
| tile_data.rows[5] = 16; // tmm5 = weights |
| tile_data.rows[6] = mr; // tmm6 = input remainder |
| tile_data.rows[7] = kremainder >> 2; // tmm7 = weights remainder |
| |
| tile_data.colsb[0] = 64; // tmm0 = res 0 |
| tile_data.colsb[1] = 64; // tmm1 = res 1 |
| tile_data.colsb[2] = 64; // tmm2 = res 1 |
| tile_data.colsb[3] = 64; // tmm3 = res 1 |
| tile_data.colsb[4] = 64; // tmm4 = input |
| tile_data.colsb[5] = 64; // tmm5 = weights |
| tile_data.colsb[6] = kremainder; // tmm6 = input remainder |
| tile_data.colsb[7] = 64; // tmm7 = weights remainder |
| |
| //_tile_loadconfig(&tile_data); |
| __asm__ volatile ("ldtilecfg %0" :: "m" (tile_data)); |
| |
| ${OUT_T}* c0 = c; |
| $for M in range(1, MR): |
| ${OUT_T}* c${M} = (${OUT_T}*) ((uintptr_t) c${M-1} + cm_stride); |
| $if M % 2 == 0: |
| if XNN_UNPREDICTABLE(mr <= ${M}) { |
| c${M} = c${M-1}; |
| } |
| $elif M + 1 == MR: |
| if XNN_UNPREDICTABLE(mr != ${M+1}) { |
| c${M} = c${M-1}; |
| } |
| $else: |
| if XNN_UNPREDICTABLE(mr < ${M+1}) { |
| c${M} = c${M-1}; |
| } |
| |
| $if DATATYPE in ["QD8_F16", "QD8_F32", "QC4_F16", "QC4_F32"]: |
| const __m512 voutput_min = _mm512_set1_ps(params->scalar.min); |
| const __m512 voutput_max = _mm512_set1_ps(params->scalar.max); |
| $if DATATYPE in ["QC4_F16", "QC4_F32"]: |
| const __m256i vmask = _mm256_set1_epi8(params->scalar.mask); |
| $else: |
| const __m512 voutput_max_less_zero_point = _mm512_set1_ps(params->${PARAMS_STRUCT}.output_max_less_zero_point); |
| const __m512i voutput_zero_point = _mm512_set1_epi32(params->${PARAMS_STRUCT}.output_zero_point); |
| const __m128i voutput_min = _mm_load_si128((const __m128i*) params->${PARAMS_STRUCT}.output_min); |
| |
| do { |
| $for N in range(0, NR, 16): |
| const __m512i vksum${ABC[N:N+16]} = _mm512_load_epi32((const int32_t*) w + ${N}); |
| w = (const int32_t*) w + ${NR}; |
| |
| // Zero tile accumulator |
| __asm__ volatile ( |
| $for N in range(0, NR, 16): |
| "tilezero %%tmm${N // 16}\\n" |
| ::); |
| |
| size_t k = kc; |
| while (k >= 64 * sizeof(int8_t)) { |
| _tile_loadd(4, a, a_stride); |
| a += 64; |
| $for N in range(0, NR, 16): |
| _tile_loadd(5, (const int8_t*) w + ${N * 4}, ${NR * 4}); |
| _tile_dpbssd(${N // 16}, 4, 5); |
| $if PREFETCH: |
| $for P in range(4096, 5120 + NR // 16 * 1024, 64): |
| xnn_prefetch_to_l1((const int8_t*) w + ${P}); |
| |
| w = (const int8_t*) w + ${NR // 16 * 1024}; |
| k -= 64 * sizeof(int8_t); |
| } |
| |
| if XNN_UNLIKELY(k != 0) { |
| _tile_loadd(6, a, a_stride); |
| a += kremainder; |
| $for N in range(0, NR, 16): |
| _tile_loadd(7, (const int8_t*) w + ${N * 4}, ${NR * 4}); |
| _tile_dpbssd(${N // 16}, 6, 7); |
| |
| w = (const int8_t*) w + kremainder * ${NR}; |
| k -= kremainder * sizeof(int8_t); |
| } |
| |
| // Add tile to bias |
| $for N in range(0, NR, 16): |
| _tile_stored(${N // 16}, res${N // 16}, 64); |
| |
| $if DATATYPE in ["QD8_F16", "QD8_F32", "QC4_F16", "QC4_F32"]: |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| __m512i vacc${M}x${ABC[N:N+16]} = _mm512_mullo_epi32(vksum${ABC[N:N+16]}, _mm512_set1_epi32((int) quantization_params[${M}].zero_point)); |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vacc${M}x${ABC[N:N+16]} = _mm512_add_epi32(vacc${M}x${ABC[N:N+16]}, _mm512_load_epi32(res${N // 16} + ${M * 16})); |
| $else: |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| __m512i vacc${M}x${ABC[N:N+16]} = _mm512_add_epi32(vksum${ABC[N:N+16]}, _mm512_load_epi32(res${N // 16} + ${M * 16})); |
| |
| $if DATATYPE == "QC4_F32": |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vacc${M}x${ABC[N:N+16]} = _mm512_srai_epi32(vacc${M}x${ABC[N:N+16]}, 4); |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| __m512 vscaled${M}x${ABC[N:N+16]} = _mm512_cvtepi32_ps(vacc${M}x${ABC[N:N+16]}); |
| |
| $if DATATYPE in ["QD8_F32", "QC4_F32"]: |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_mul_ps(vscaled${M}x${ABC[N:N+16]}, _mm512_set1_ps(quantization_params[${M}].inv_scale)); |
| |
| $for N in range(0, NR, 16): |
| const __m512 vfilter_output_scale${ABC[N:N+16]} = _mm512_load_ps((const float*) w); |
| w = (const float*) w + 16; |
| $for N in range(0, NR, 16): |
| const __m512 vbias${ABC[N:N+16]} = _mm512_load_ps((const float*) w); |
| w = (const float*) w + 16; |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_fmadd_ps(vscaled${M}x${ABC[N:N+16]}, vfilter_output_scale${ABC[N:N+16]}, vbias${ABC[N:N+16]}); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_max_ps(vscaled${M}x${ABC[N:N+16]}, voutput_min); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_min_ps(vscaled${M}x${ABC[N:N+16]}, voutput_max); |
| |
| if XNN_LIKELY(nc >= ${NR}) { |
| $for M in reversed(range(MR)): |
| $for N in range(0, NR, 16): |
| _mm512_storeu_ps(c${M} + ${N}, vscaled${M}x${ABC[N:N+16]}); |
| |
| $for M in reversed(range(MR)): |
| c${M} = (float*) ((uintptr_t) c${M} + cn_stride); |
| |
| a -= kc; |
| nc -= ${NR}; |
| } else { |
| // Prepare mask for valid 32-bit elements (depends on nc). |
| $for N in range(0, NR, 16): |
| const __mmask16 vmask${N // 16} = _cvtu32_mask16((uint32_t) ((((UINT64_C(1) << nc) - 1) >> ${N}) & 0xFFFF)); |
| $for M in reversed(range(MR)): |
| $for N in range(0, NR, 16): |
| _mm512_mask_storeu_ps(c${M} + ${N}, vmask${N // 16}, vscaled${M}x${ABC[N:N+16]}); |
| nc = 0; |
| } |
| $elif DATATYPE in ["QS8", "QC8"]: |
| $if DATATYPE == "QC8": |
| $for N in range(0, NR, 16): |
| const __m512 vscale${ABC[N:N+16]} = _mm512_load_ps(w); |
| w = (const float*) w + 16; |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_mul_ps(vscaled${M}x${ABC[N:N+16]}, vscale${ABC[N:N+16]}); |
| $else: |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_mul_ps(vscaled${M}x${ABC[N:N+16]}, vscale); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vscaled${M}x${ABC[N:N+16]} = _mm512_min_ps(vscaled${M}x${ABC[N:N+16]}, voutput_max_less_zero_point); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vacc${M}x${ABC[N:N+16]} = _mm512_cvtps_epi32(vscaled${M}x${ABC[N:N+16]}); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vacc${M}x${ABC[N:N+16]} = _mm512_add_epi32(vacc${M}x${ABC[N:N+16]}, voutput_zero_point); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| __m128i vout${M}x${ABC[N:N+16]} = ${_MM512_CVTXEPI32_EPI8}(vacc${M}x${ABC[N:N+16]}); |
| |
| $for M in range(MR): |
| $for N in range(0, NR, 16): |
| vout${M}x${ABC[N:N+16]} = ${_MM_MAX_EPX8}(vout${M}x${ABC[N:N+16]}, voutput_min); |
| |
| if XNN_LIKELY(nc >= ${NR}) { |
| $for M in reversed(range(MR)): |
| $for N in range(0, NR, 16): |
| _mm_storeu_si128((__m128i*) (c${M} + ${N}), vout${M}x${ABC[N:N+16]}); |
| c${M} = (${OUT_T}*) ((uintptr_t) c${M} + cn_stride); |
| a -= kc; |
| nc -= ${NR}; |
| } else { |
| // Prepare mask for valid 8-bit elements (depends on nc). |
| $for N in range(0, NR, 16): |
| const __mmask16 vmask${N // 16} = _cvtu32_mask16((uint32_t) ((((UINT64_C(1) << nc) - 1) >> ${N}) & 0xFFFF)); |
| |
| $for M in reversed(range(MR)): |
| $for N in range(0, NR, 16): |
| _mm_mask_storeu_epi8(c${M} + ${N}, vmask${N // 16}, vout${M}x${ABC[N:N+16]}); |
| nc = 0; |
| } |
| } while (nc != 0); |
| // Release tile config |
| // _tile_release(); |
| __asm__ volatile ("tilerelease" ::); |
| #endif // defined(__x86_64__) && defined(__clang__) |
| } |