Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
ENH: Vectorize FP16 math functions on Intel Sapphire Rapids
  • Loading branch information
Raghuveer Devulapalli
Raghuveer Devulapalli committed Jan 18, 2024
commit 55fee61d12559f338a08f05011a7c874d547bd18
24 changes: 24 additions & 0 deletions numpy/_core/src/common/npy_svml.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
#if NPY_SIMD && defined(NPY_HAVE_AVX512_SPR) && defined(NPY_CAN_LINK_SVML)
extern __m512h __svml_exps32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_exp2s32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_logs32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_log2s32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_log10s32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_expm1s32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_log1ps32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_cbrts32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_sins32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_coss32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_tans32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_asins32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_acoss32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_atans32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_atan2s32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_sinhs32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_coshs32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_tanhs32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_asinhs32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_acoshs32(npy_half*, npy_half*, npy_intp);
extern __m512h __svml_atanhs32(npy_half*, npy_half*, npy_intp);
#endif

#if NPY_SIMD && defined(NPY_HAVE_AVX512_SKX) && defined(NPY_CAN_LINK_SVML)
extern __m512 __svml_expf16(__m512 x);
extern __m512 __svml_exp2f16(__m512 x);
Expand Down
14 changes: 11 additions & 3 deletions numpy/_core/src/umath/loops_umath_fp.dispatch.c.src
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*@targets
** $maxopt baseline avx512_skx
** $maxopt baseline avx512_skx avx512_spr
*/
#include "numpy/npy_math.h"
#include "simd/simd.h"
Expand Down Expand Up @@ -156,7 +156,8 @@ avx512_@func@_f16(const npy_half *src, npy_half *dst, npy_intp len)
NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(HALF_@func@)
(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(data))
{
#if NPY_SIMD && defined(NPY_HAVE_AVX512_SKX) && defined(NPY_CAN_LINK_SVML)
#if defined(NPY_HAVE_AVX512_SPR) || defined(NPY_HAVE_AVX512_SKX)
#if NPY_SIMD && defined(NPY_CAN_LINK_SVML)
const npy_half *src = (npy_half*)args[0];
npy_half *dst = (npy_half*)args[1];
const int lsize = sizeof(src[0]);
Expand All @@ -166,10 +167,17 @@ NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(HALF_@func@)
if (!is_mem_overlap(src, steps[0], dst, steps[1], len) &&
(ssrc == 1) &&
(sdst == 1)) {
#if defined(NPY_HAVE_AVX512_SPR)
__svml_@intrin@s32(src, dst, len);
return;
#endif
#if defined(NPY_HAVE_AVX512_SKX)
avx512_@intrin@_f16(src, dst, len);
return;
}
#endif
}
#endif // NPY_SIMD && NPY_CAN_LINK_SVML
#endif // SPR or SKX
UNARY_LOOP {
const npy_float in1 = npy_half_to_float(*(npy_half *)ip1);
*((npy_half *)op1) = npy_float_to_half(npy_@intrin@f(in1));
Expand Down
41 changes: 41 additions & 0 deletions numpy/_core/tests/test_umath_accuracy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

IS_AVX = __cpu_features__.get('AVX512F', False) or \
(__cpu_features__.get('FMA3', False) and __cpu_features__.get('AVX2', False))

IS_AVX512FP16 = __cpu_features__.get('AVX512FP16', False)

# only run on linux with AVX, also avoid old glibc (numpy/numpy#20448).
runtest = (sys.platform.startswith('linux')
and IS_AVX and not _glibc_older_than("2.17"))
Expand Down Expand Up @@ -68,6 +71,8 @@ def test_validate_transcendentals(self):
maxulperr = data_subset['ulperr'].max()
assert_array_max_ulp(npfunc(inval), outval, maxulperr)

@pytest.mark.skipif(IS_AVX512FP16,
reason = "SVML FP16 have slightly higher ULP errors")
@pytest.mark.parametrize("ufunc", UNARY_OBJECT_UFUNCS)
def test_validate_fp16_transcendentals(self, ufunc):
with np.errstate(all='ignore'):
Expand All @@ -76,3 +81,39 @@ def test_validate_fp16_transcendentals(self, ufunc):
datafp32 = datafp16.astype(np.float32)
assert_array_max_ulp(ufunc(datafp16), ufunc(datafp32),
maxulp=1, dtype=np.float16)

@pytest.mark.skipif(not IS_AVX512FP16,
reason="lower ULP only apply for SVML FP16")
def test_validate_svml_fp16(self):
max_ulp_err = {
"arccos" : 2.54,
"arccosh": 2.09,
"arcsin" : 3.06,
"arcsinh": 1.51,
"arctan" : 2.61,
"arctanh": 1.88,
"cbrt" : 1.57,
"cos" : 1.43,
"cosh" : 1.33,
"exp2" : 1.33,
"exp" : 1.27,
"expm1" : 0.53,
"log" : 1.80,
"log10" : 1.27,
"log1p" : 1.88,
"log2" : 1.80,
"sin" : 1.88,
"sinh" : 2.05,
"tan" : 2.26,
"tanh" : 3.00,
}

with np.errstate(all='ignore'):
arr = np.arange(65536, dtype=np.int16)
datafp16 = np.frombuffer(arr.tobytes(), dtype=np.float16)
datafp32 = datafp16.astype(np.float32)
for func in max_ulp_err:
ufunc = getattr(np, func)
ulp = max_ulp_err[func]
assert_array_max_ulp(ufunc(datafp16), ufunc(datafp32),
maxulp=ulp, dtype=np.float16)