blob: cba067f09924902f91847f3c99a2553247fac213 [file] [edit]
// Copyright 2023 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.
$assert NR > 1
$assert KR > 1
#include <arm_neon.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "src/xnnpack/common.h"
#include "src/xnnpack/microparams.h"
#include "src/xnnpack/packw.h"
// convert a vector from packed nibbles to planar, and accumulate sum
static XNN_INTRINSIC
int8x16_t xnn_packed2planar(
int32x4_t *vacc,
const int8x16_t v,
const int8x16_t vmask,
const int8x16_t vones)
{
const int8x16_t vl = vandq_s8(v, vmask); // isolate lower int 4
const int8x16_t vh = vshlq_n_s8(v, 4);// isolate upper int 4
*vacc = vdotq_s32(*vacc, vh, vones);
*vacc = vdotq_s32(*vacc, vl, vones);
const int8x16_t v01 = vzip1q_s8(vh, vl);
const int8x16_t v23 = vzip2q_s8(vh, vl);
const uint8x16_t v02 = vreinterpretq_u8_s64(vuzp1q_s64(vreinterpretq_s64_s8(v01), vreinterpretq_s64_s8(v23)));
const uint8x16_t v13 = vreinterpretq_u8_s64(vuzp2q_s64(vreinterpretq_s64_s8(v01), vreinterpretq_s64_s8(v23)));
const uint8x16_t vl02 = vshrq_n_u8(v02, 4);
const uint8x16_t v0123 = vorrq_u8(vl02, v13);
return vreinterpretq_s8_u8(v0123);
}
void xnn_qb4_packw_gemm_goi_ukernel_x${NR}c${KR}__aarch64_neondot(
size_t g,
size_t nc,
size_t kc,
size_t nr,
size_t kr,
size_t sr,
size_t bl,
size_t n_stride,
const uint8_t* weights,
const int32_t* bias,
const void* scale,
int8_t* packed_weights,
size_t extra_bytes_bl,
size_t extra_bytes_n,
const void* params) XNN_OOB_READS
{
assert(g != 0);
assert(nc != 0);
assert(kc != 0);
assert(nr == ${NR});
assert(kr == ${KR});
assert(sr == 1);
assert(weights != NULL);
assert(packed_weights != NULL);
assert(extra_bytes_bl == nr * sizeof(uint16_t));
assert(extra_bytes_n == nr * sizeof(float));
assert(params != NULL);
assert(kc % bl == 0);
size_t num_blocks = kc / bl;
size_t weight_stride = (n_stride >> 1);
const int8x16_t vmask = vmovq_n_s8(INT8_C(0xF0));
const int8x16_t vones = vmovq_n_s8(UINT8_C(0x01));
uint8_t* out = (uint8_t*) packed_weights;
const int32_t* b = (const int32_t*) bias;
const float32x4_t vzeropoint = vmovq_n_f32((float) (((const struct xnn_qs8_qc4w_packing_params*) params)->input_zero_point + 0));
const float32x4_t vrecip_sixteen = vmovq_n_f32(1.0f/ 16.0f);
const int8_t kzp = (int8_t) (((const struct xnn_qs8_qc4w_packing_params*) params)->kernel_zero_point);
assert(kzp == 0 || kzp == 8);
const int8x16_t vkernel_zero_point = vmovq_n_s8(kzp * 0x11);
do {
// NC main loop multiple of ${NR}
const int8_t* w0 = (const int8_t*) weights;
const uint16_t* s0 = (const uint16_t*) scale;
int n = nc;
for (;n > 0; n -= ${NR}) {
float* packed_k_scaled_sum = (float*) out;
$for N in range(0, NR, 4):
float32x4_t packed_k_scaled_sums${N//4} = vdupq_n_f32(0.0f);
out += ${NR} * sizeof(float);
// KC/2 bytes is KC Nibbles
$for N in range(1, NR):
const int8_t* w${N} = w${N-1} + weight_stride;
// scales
$for N in range(1, NR):
const uint16_t* s${N} = s${N-1} + num_blocks;
if XNN_UNLIKELY(n < ${NR}){
$for N in range(1, NR):
$if $N % 2 == 0:
if XNN_UNPREDICTABLE(n <= ${N}) {
w${N} = w${N-1};
s${N} = s${N-1};
}
$else:
if XNN_UNPREDICTABLE(n < ${N+1}) {
w${N} = w${N-1};
s${N} = s${N-1};
}
}
size_t kb = kc;
// Process k by blocks (bl)
for (; kb >= bl; kb-=bl) {
// Initialize KSum as subtracting bl zero points (8)
$for N in range(0, NR, 2):
int32x4_t ksum${N//2} = vdupq_n_s32(0);
size_t k = bl;
// KC Main loop multiple of 16x32
for(; k >= 32; k-=32) {
$for N in range(0, NR):
const int64x2_t w${N}x01 = vreinterpretq_s64_s8(veorq_s8(vld1q_s8(w${N}), vkernel_zero_point)); w${N} += ${2 * KR};
$for N in range(0, NR, 2):
int8x16_t v${N}${N+1}_0 = vreinterpretq_s8_s64(vzip1q_s64(w${N}x01, w${N+1}x01));
int8x16_t v${N}${N+1}_1 = vreinterpretq_s8_s64(vzip2q_s64(w${N}x01, w${N+1}x01));
$for I in range(0, 2):
$for N in range(0, NR, 4):
v${N}${N+1}_${I} = xnn_packed2planar(&ksum${N//2}, v${N}${N+1}_${I}, vmask, vones);
v${N+2}${N+3}_${I} = xnn_packed2planar(&ksum${(N+2)//2}, v${N+2}${N+3}_${I}, vmask, vones);
$for I in range(0, 2):
$for N in range(0, NR, 2):
vst1q_s8((int8_t*) &out[${(I*NR + N)*KR}], v${N}${N+1}_${I});
out += ${NR*16};
}
$for N in range(0, NR, 8):
uint16x8_t bf_scales${N//8} = vdupq_n_u16(0);
$for N_I in range(0, 8):
bf_scales${N//8} = vsetq_lane_u16(s${N + N_I}[0], bf_scales${N//8}, ${N_I});
$for N in range(0, NR, 8):
float32x4_t f_scales${N//4} = vreinterpretq_f32_u32(vshll_n_u16(vget_low_u16(bf_scales${N//8}), 16));
float32x4_t f_scales${(N+4)//4} = vreinterpretq_f32_u32(vshll_n_u16(vget_high_u16(bf_scales${N//8}), 16));
$for N in range(0, NR):
s${N} += 1;
$for N in range(0, NR, 4):
f_scales${N//4} = vmulq_f32(f_scales${N//4}, vrecip_sixteen);
$for N in range(0, NR, 4):
float32x4_t f_ksum${N//4} = vcvtq_f32_s32(vpaddq_s32(ksum${N//2}, ksum${(N+2)//2}));
f_ksum${N//4} = vmulq_f32(f_ksum${N//4}, vzeropoint);
packed_k_scaled_sums${N//4} = vfmsq_f32(packed_k_scaled_sums${N//4}, f_ksum${N//4}, f_scales${N//4});
$for N in range(0, NR, 4):
vst1q_f32(&packed_k_scaled_sum[${N}], packed_k_scaled_sums${N//4});
$for N in range(0, NR, 4):
vst1_u16((uint16_t*)out+${N}, vreinterpret_u16_s16(vshrn_n_s32(vreinterpretq_s32_f32(f_scales${N//4}), 16)));
out += ${NR} * sizeof(uint16_t);
}
if XNN_LIKELY(b != NULL){
if (n >= ${NR}) {
$for N in range(0, NR, 4):
const int32x4_t b${N//4} = vld1q_s32(&b[${N}]);
vst1q_s32((int32_t*)out + ${N}, b${N//4});
b += ${NR};
} else {
memcpy(out, b, n * sizeof(int32_t));
memset((int32_t*) out + n, 0, (${NR} - n) * sizeof(int32_t));
b += n;
}
} else {
$for N in range(0, NR, 4):
vst1q_s32((int32_t*)out + ${N}, vdupq_n_s32(0));
}
out += ${NR} * sizeof(uint32_t);
w0 = w15 + weight_stride - (kc >> 1);
s0 = s15;
}
weights += nc * weight_stride;
} while (--g != 0);
}