项目文件夹

文件
2026-07-13 13:18:33 +08:00

107 行
3.9 KiB
C

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0
// DeepSpeed Team
// NOTE:
// This shared-memory implementation targets AArch64 CPUs.
// Minimum supported architecture is ARMv8-A with NEON (Advanced SIMD) support.
// Systems without NEON are not supported.
#include <arm_neon.h>
#include <stddef.h>
#include <stdint.h>
#include <cmath>
// 128 bits = 16 bytes -> fits 8 fp16/bf16 or 4 fp32 elements.
static int vector_length_in_bytes = 16;
// When widening fp16/bf16 -> fp32, 4 elements fit in one 128-bit register.
// Using 8 would require two 128-bit registers, so limit to 4.
static constexpr int full_precision_elements_in_fixed_vector = 4;
static inline float32x4_t cvt_bf16_to_fp32(const uint16x4_t input)
{
// Zero-extend 16-bit to 32-bit and shift left by 16 bits
// BF16 has the same exponent/sign bits as FP32, just missing lower mantissa bits
uint32x4_t result_32 = vshll_n_u16(input, 16);
return vreinterpretq_f32_u32(result_32);
}
static inline float32x4_t cvt_fp16_to_fp32(float16x4_t input)
{
// Converts 4 FP16 values to 4 FP32 values
return vcvt_f32_f16(input);
}
// While converting fp32 to fp16, before truncating lsb, it should be rounded to nearest even and
// Converts 4 float32 -> 4 bfloat16 with round-to-nearest-even (RNE) and NaN handling
static inline uint16x4_t cvt_fp32_to_bf16(float32x4_t src)
{
// Reinterpret float32 bits as uint32
uint32x4_t u32 = vreinterpretq_u32_f32(src);
const uint32x4_t ones = vdupq_n_u32(0x1);
const uint32x4_t vec_bias =
vdupq_n_u32(0x7FFF); // one less than half of the dropped bits range
const uint16x4_t nan_bf16 = vdup_n_u16(0xFFFF);
// RNE: lsb = (input >> 16) & 1
uint32x4_t lsb = vandq_u32(vshrq_n_u32(u32, 16), ones);
// rounding_bias = 0x7FFF + lsb, lsb can be 0 or 1.
uint32x4_t bias = vaddq_u32(vec_bias, lsb);
// input += rounding_bias
u32 = vaddq_u32(u32, bias);
// >> 16 to get bfloat16
// vshrq_n_u32 - keeps 32 bit width after shift
// vshrn_n_u32 - keeps 16 bits width after shift
uint16x4_t bf16 = vshrn_n_u32(u32, 16);
// vmvnq_u32 is bitwise NOT
// NaN mask: ~(src == src) -> 1 if NaN
// for normal num, ~(src == src) -> 0
uint32x4_t isnan = vmvnq_u32(vceqq_f32(src, src));
// Select nan_bf16 if isnan (use 16-bit mask)
uint16x4_t mask = vreinterpret_u16_u32(vget_low_u32(isnan));
return vbsl_u16(mask, nan_bf16, bf16);
}
// fp32 and fp16 are IEEE formats.
// converting fp32 to fp16 is handled by vcvt_f16_f32 internally without arbitrarily truncating the
// lsb but rounds to nearest.
static inline float16x4_t cvt_fp32_to_fp16(float32x4_t input)
{
// Converts 4 FP32 values to 4 FP16 values with rounding
return vcvt_f16_f32(input);
}
// Reduce functions down below use vectorized algorithm, the number of bytes processed each
// iteration depends on vector length. 128bit vector ==> 16 bytes. sticking to NEON 128 bit
void reduce_bf16_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers);
void reduce_fp16_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers);
void reduce_fp32_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers);
void parallel_memcpy(void* to, void* from, size_t n_bytes);
#define VLOAD_U8(X) vld1q_u8((uint8_t*)(X))
#define VLOAD_U16(X) vld1_u16((uint16_t*)(X))
#define VLOAD_F16(X) vld1_f16((float16_t*)(X))
#define VLOAD_F32(X) vld1q_f32((float32_t*)(X))
#define VSTORE_U8(A, B) vst1q_u8((uint8_t*)(A), B)
#define VSTORE_U16(A, B) vst1_u16((uint16_t*)(A), B)
#define VSTORE_F16(A, B) vst1_f16((float16_t*)(A), B) // fp16 supported from armv8.2-a+fp16
#define VSTORE_F32(A, B) vst1q_f32((float32_t*)(A), B)
#define VADD_F32(A, B) vaddq_f32(A, B)
#define VADD_F32_2VL(A, B) vaddq_f32(A, B)
#define CVT_BF16_TO_FP32(X) cvt_bf16_to_fp32(X)
#define CVT_FP16_TO_FP32(X) cvt_fp16_to_fp32(X)
#define CVT_FP32_TO_BF16(X) cvt_fp32_to_bf16(X)
#define CVT_FP32_TO_FP16(X) cvt_fp32_to_fp16(X)