NSIMD natively supports IEEE float16's. This means that NSIMD provides types and functions to deal with them. When the targeted architecture supports them then NSIMD will use approriate intrinsics otherwise emulation with float32's will be used.
When emulating, as float16's are not natively supported by neither C or C++ emulation is done with float32's.
Intel architectures do not support IEEE float16 arithmetic, they only
provide, as an extension, supports for convertion to/from float32. When
compiling NSIMD for Intel architectures use -DFP16
to activate the
conversion intrinsics if available on your machine. Note that AVX-512
has thoses natively.
Arm architectures can provide native float16 arithmetic. For 32-bits and
64-bits chips (ARMv7 and Aarch64) chips float16 support is optional. When
compiling with -DFP16
, NSIMD will use float16-related intrinsics. Note
that for SVE chips float16's are mandatory hence NSIMD will use appropriate
intrinsics with or without -DFP16
.
CUDA provides supports for converting float16's to/from float32's. These
are always used by NSIMD. But it is only since devices of compute
capabilities 5.3 and above that float16's arithmetic is provided. NSIMD will
always use CUDA float16's functions so there is no need to compile with
-DFP16
.
ROCm HIP supports float16's except for the first versions. For now NSIMD
assumes that it is always the case and use HIP float16 API. There is no
need for -DFP16
.
NSIMD provide the f16
type which represents a IEEE float16. Note that
depending on the targeted architecture and the presence of -DFP16
the float16
type can typedefs many different types. Therefore the two following functions
are provided and can be used to convert a float16 from/to a float32. These
functions preserve NaN's and infinities. When converting from a float32 to
a float16 saturation to infinities is performed when the float32 cannot be
represented as a float16.
Function signature | Availability |
---|---|
f16 nsimd_f32_to_f16(f32 a); |
C and C++ |
f32 nsimd_f16_to_f32(f16 a); |
C and C++ |
f16 nsimd::f32_to_f16(f32 a); |
C++ only |
f32 nsimd::f16_to_f32(f16 a); |
C++ only |
For loading/storing float16's NSIMD provides other conversion function to/from 16-bits unsigned integers. The integers will hold the IEEE binary representation of the float16's.
Function signature | Availability |
---|---|
u16 nsimd_f32_to_u16(f32 a); |
C and C++ |
f32 nsimd_u16_to_f32(u16 a); |
C and C++ |
u16 nsimd::f32_to_u16(f32 a); |
C++ only |
f32 nsimd::u16_to_f32(u16 a); |
C++ only |
The nsimd_*
functions listed above do not use the same linkage type depending
on the targeted architecture. When compiling for GPUs the corresponding symbols
names are mangled. They use C++ ABI because the float16 type is defined as a
C++ class and not as a C struct. We therefore inherit from the implementation
of CUDA and HIP/ROCm. Linkage types are listed below.
Function signature | CUDA/ROCm | Other architectures |
---|---|---|
f16 nsimd_f32_to_f16(f32 a); |
C++ linkage | C linkage |
f32 nsimd_f16_to_f32(f16 a); |
C++ linkage | C linkage |
f16 nsimd::f32_to_f16(f32 a); |
C++ linkage | C++ linkage |
f32 nsimd::f16_to_f32(f16 a); |
C++ linkage | C++ linkage |
u16 nsimd_f32_to_u16(f32 a); |
C++ linkage | C linkage |
f32 nsimd_u16_to_f32(u16 a); |
C++ linkage | C linkage |
u16 nsimd::f32_to_u16(f32 a); |
C++ linkage | C++ linkage |
f32 nsimd::u16_to_f32(u16 a); |
C++ linkage | C++ linkage |
It is possible to know at compile time in which situation we are. The
NSIMD_C_LINKAGE_FOR_F16
macro if defined means that C linkage is used for
nsimd_*
functions.