use std::mem::MaybeUninit;
use rten_simd::ops::{BitOps, Extend, NarrowSaturate};
use rten_simd::{Isa, SimdOp, SliceWriter, f16};
pub struct F16ToF32<'s, 'd> {
src: &'s [f16],
dest: &'d mut [MaybeUninit<f32>],
}
impl<'s, 'd> F16ToF32<'s, 'd> {
pub fn new(src: &'s [f16], dest: &'d mut [MaybeUninit<f32>]) -> Self {
assert_eq!(src.len(), dest.len());
F16ToF32 { src, dest }
}
}
impl<'d> SimdOp for F16ToF32<'_, 'd> {
type Output = &'d mut [f32];
#[inline(always)]
fn eval<I: Isa>(self, isa: I) -> Self::Output {
let f16_ops = isa.f16();
let f32_ops = isa.f32();
let f16_v_len = f16_ops.len();
let mut dest_writer = SliceWriter::new(self.dest);
let mut chunks = self.src.chunks_exact(f16_v_len * 2);
for chunk in chunks.by_ref() {
let xs = f16_ops.load_many::<2>(chunk);
let lo0 = f16_ops.extend_low(xs[0]);
let hi0 = f16_ops.extend_high(xs[0]);
let lo1 = f16_ops.extend_low(xs[1]);
let hi1 = f16_ops.extend_high(xs[1]);
dest_writer.write_vecs(f32_ops, [lo0, hi0, lo1, hi1]);
}
let mut chunks = chunks.remainder().chunks_exact(f16_v_len);
for chunk in chunks.by_ref() {
let x = f16_ops.load(chunk);
let low = f16_ops.extend_low(x);
let high = f16_ops.extend_high(x);
dest_writer.write_vec(f32_ops, low);
dest_writer.write_vec(f32_ops, high);
}
for &x in chunks.remainder() {
dest_writer.write_scalar(x.to_f32());
}
dest_writer.into_mut_slice()
}
}
pub struct F32ToF16<'s, 'd> {
src: &'s [f32],
dest: &'d mut [MaybeUninit<f16>],
}
impl<'s, 'd> F32ToF16<'s, 'd> {
pub fn new(src: &'s [f32], dest: &'d mut [MaybeUninit<f16>]) -> Self {
assert_eq!(src.len(), dest.len());
F32ToF16 { src, dest }
}
}
impl<'d> SimdOp for F32ToF16<'_, 'd> {
type Output = &'d mut [f16];
#[inline(always)]
fn eval<I: Isa>(self, isa: I) -> Self::Output {
let f32_ops = isa.f32();
let f16_ops = isa.f16();
let f32_v_len = f32_ops.len();
let mut src_chunks = self.src.chunks_exact(f32_v_len * 2);
let mut dest_writer = SliceWriter::new(self.dest);
for src_chunk in src_chunks.by_ref() {
let xs = f32_ops.load_many::<2>(src_chunk);
let half = f32_ops.narrow_saturate(xs[0], xs[1]);
dest_writer.write_vec(f16_ops, half);
}
for &x in src_chunks.remainder() {
dest_writer.write_scalar(f16::from_f32(x));
}
dest_writer.into_mut_slice()
}
}
#[cfg(test)]
mod tests {
use rten_simd::ops::BitOps;
use rten_simd::{Isa, SimdOp, f16};
use super::{F16ToF32, F32ToF16};
fn f16_vec_len() -> usize {
struct F16VecLen {}
impl SimdOp for F16VecLen {
type Output = usize;
fn eval<I: Isa>(self, isa: I) -> usize {
isa.f16().len()
}
}
F16VecLen {}.dispatch()
}
#[test]
fn test_f16_to_f32() {
let len = f16_vec_len() * 3 + 1;
let src: Vec<f16> = (0..len)
.map(|i| f16::from_f32(i as f32 * 0.5 - 3.0))
.collect();
let expected: Vec<f32> = src.iter().map(|x| x.to_f32()).collect();
let mut buf = Vec::with_capacity(src.len());
let actual = F16ToF32::new(&src, buf.spare_capacity_mut()).dispatch();
assert_eq!(actual, expected);
}
#[test]
fn test_f16_to_f32_empty() {
let src: Vec<f16> = Vec::new();
let mut buf: Vec<f32> = Vec::new();
let actual = F16ToF32::new(&src, buf.spare_capacity_mut()).dispatch();
assert!(actual.is_empty());
}
#[test]
fn test_f32_to_f16() {
let len = f16_vec_len() + 1;
let src: Vec<f32> = (0..len).map(|i| i as f32 * 0.5 - 3.0).collect();
let expected: Vec<f16> = src.iter().map(|&x| f16::from_f32(x)).collect();
let mut buf = Vec::with_capacity(src.len());
let actual = F32ToF16::new(&src, buf.spare_capacity_mut()).dispatch();
assert_eq!(actual, expected);
}
#[test]
fn test_f32_to_f16_empty() {
let src: Vec<f32> = Vec::new();
let mut buf: Vec<f16> = Vec::new();
let actual = F32ToF16::new(&src, buf.spare_capacity_mut()).dispatch();
assert!(actual.is_empty());
}
#[test]
fn test_roundtrip() {
let len = f16_vec_len() * 2 + 3;
let src: Vec<f32> = (0..len).map(|i| i as f32 * 0.25 - 5.0).collect();
let mut half_buf = Vec::with_capacity(len);
let half = F32ToF16::new(&src, half_buf.spare_capacity_mut()).dispatch();
let half: Vec<f16> = half.to_vec();
let mut back_buf = Vec::with_capacity(len);
let back = F16ToF32::new(&half, back_buf.spare_capacity_mut()).dispatch();
let expected: Vec<f32> = src.iter().map(|&x| f16::from_f32(x).to_f32()).collect();
assert_eq!(back, expected);
}
}