use std::marker::PhantomData;
use furiosa_mapping::*;
use furiosa_opt_macro::primitive;
use crate::backend::Backend;
use crate::cast::{FetchCast, FetchZeroPointSub, TableLookup};
use crate::constraints;
use crate::context::*;
use crate::engine::{CanApplyFetchCast, CanApplyFetchMask, CanApplyFetchTableLookup, CanApplyFetchZeroPointSub};
use crate::runtime::CurrentBackend;
use crate::scalar::*;
use crate::tensor::Tensor;
use crate::tensor::tu::{Position, TuTensor};
#[derive(Debug, Clone, Default)]
pub struct FetchMaskConfig {
pub last_axis: usize,
pub valid_count_dim: ValidCountDim,
pub rightmost_valid_count: [u8; 8],
}
#[derive(Debug, Clone, Copy, Default)]
pub enum ValidCountDim {
#[default]
Rightmost,
RightmostAndSecondRightmost,
Iterator(usize),
}
#[derive(Debug)]
pub struct PositionFetchMask;
impl Position for PositionFetchMask {}
pub type FetchMaskTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchMask, D, Chip, Cluster, Slice, Time, Packet, B>;
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchMaskTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
fn check_constraints() {
constraints::assert_cluster_size::<Cluster>();
constraints::assert_slice_size::<Slice>();
}
#[doc(hidden)]
pub(crate) fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
Self::check_constraints();
Self {
ctx,
inner,
_position: PhantomData,
}
}
}
#[derive(Debug)]
pub struct PositionFetchTableLookup;
impl Position for PositionFetchTableLookup {}
pub type FetchTableLookupTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchTableLookup, D, Chip, Cluster, Slice, Time, Packet, B>;
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchTableLookupTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
fn check_constraints() {
constraints::assert_cluster_size::<Cluster>();
constraints::assert_slice_size::<Slice>();
}
#[doc(hidden)]
pub(crate) fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
Self::check_constraints();
Self {
ctx,
inner,
_position: PhantomData,
}
}
}
#[derive(Debug)]
pub struct PositionFetchCast;
impl Position for PositionFetchCast {}
pub type FetchCastTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchCast, D, Chip, Cluster, Slice, Time, Packet, B>;
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchCastTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
fn check_constraints() {
constraints::assert_cluster_size::<Cluster>();
constraints::assert_slice_size::<Slice>();
}
#[doc(hidden)]
pub(crate) fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
Self::check_constraints();
Self {
ctx,
inner,
_position: PhantomData,
}
}
}
impl<'l, const T: Tu, P: CanApplyFetchMask, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
TuTensor<'l, T, P, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[primitive(TuTensor::fetch_mask)]
pub fn fetch_mask<OutTime: M, OutPacket: M>(
self,
) -> FetchMaskTensor<'l, T, D, Chip, Cluster, Slice, OutTime, OutPacket, B> {
verify_fetch_mask::<Time, Packet, OutTime, OutPacket>();
FetchMaskTensor::new(self.ctx, self.inner.transpose(true))
}
}
impl<
'l,
P: CanApplyFetchTableLookup,
D: MaterializableScalar,
Chip: M,
Cluster: M,
Slice: M,
Time: M,
Packet: M,
B: Backend,
> TuTensor<'l, { Tu::Main }, P, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[primitive(TuTensor::fetch_table_lookup)]
pub fn fetch_table_lookup<OutD: Scalar>(
self,
) -> FetchTableLookupTensor<'l, { Tu::Main }, OutD, Chip, Cluster, Slice, Time, Packet, B>
where
D: TableLookup<OutD>,
{
FetchTableLookupTensor::new(self.ctx, self.inner.map(|v| v.lookup()))
}
}
impl<
'l,
const T: Tu,
P: CanApplyFetchCast,
D: MaterializableScalar,
Chip: M,
Cluster: M,
Slice: M,
Time: M,
Packet: M,
B: Backend,
> TuTensor<'l, T, P, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[primitive(TuTensor::fetch_cast)]
pub fn fetch_cast<OutD: Scalar>(self) -> FetchCastTensor<'l, T, OutD, Chip, Cluster, Slice, Time, Packet, B>
where
D: FetchCast<OutD>,
{
FetchCastTensor::new(self.ctx, self.inner.map(|v| v.cast()))
}
}
#[derive(Debug)]
pub struct PositionFetchZeroPointSub;
impl Position for PositionFetchZeroPointSub {}
pub type FetchZeroPointSubTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchZeroPointSub, D, Chip, Cluster, Slice, Time, Packet, B>;
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchZeroPointSubTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
fn check_constraints() {
constraints::assert_cluster_size::<Cluster>();
constraints::assert_slice_size::<Slice>();
}
#[doc(hidden)]
pub(crate) fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
Self::check_constraints();
Self {
ctx,
inner,
_position: PhantomData,
}
}
}
impl<
'l,
const T: Tu,
P: CanApplyFetchZeroPointSub,
D: MaterializableScalar,
Chip: M,
Cluster: M,
Slice: M,
Time: M,
Packet: M,
B: Backend,
> TuTensor<'l, T, P, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[primitive(TuTensor::fetch_zero_point_sub)]
pub fn fetch_zero_point_sub<OutD: Scalar>(
self,
zero_point: i32,
) -> FetchZeroPointSubTensor<'l, T, OutD, Chip, Cluster, Slice, Time, Packet, B>
where
D: FetchZeroPointSub<OutD>,
{
let zero_point_range = <D as FetchZeroPointSub<OutD>>::ZERO_POINT_RANGE;
assert!(
zero_point_range.contains(&zero_point),
"zero_point {zero_point} is outside the source type's quantized range {zero_point_range:?}",
);
FetchZeroPointSubTensor::new(self.ctx, self.inner.map(|v| v.zero_point_sub(zero_point)))
}
}
#[allow(clippy::extra_unused_type_parameters)]
fn verify_fetch_mask<Time: M, Packet: M, OutTime: M, OutPacket: M>() {
todo!("fetch_mask is not yet implemented")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::Emulation;
use crate::cast::TableLookup;
use crate::scalar::{f4e2m1, f8e4m3};
use crate::tensor::Tensor;
axes![C = 16];
const E2M1_F32_ORACLE: [f32; 16] = [
0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0,
];
#[test]
fn table_lookup_decodes_e2m1_to_f8e4m3_matching_spec() {
for code in 0..16u8 {
let decoded: f8e4m3 = TableLookup::lookup(f4e2m1::from_bits(code));
let expected = E2M1_F32_ORACLE[code as usize];
assert_eq!(
decoded.to_f32().to_bits(),
expected.to_bits(),
"e2m1 code {code:#x} decoded to {} (0x{:08x}), expected {expected} (0x{:08x})",
decoded.to_f32(),
decoded.to_f32().to_bits(),
expected.to_bits(),
);
}
}
#[test]
fn table_lookup_tensor_map_matches_spec() {
let keys: Vec<f4e2m1> = (0..16u8).map(f4e2m1::from_bits).collect();
let input = Tensor::<f4e2m1, m![C], Emulation>::from_vec(keys);
let decoded: Tensor<f8e4m3, m![C], Emulation> = input.map(|v| v.lookup());
let got: Vec<u32> = decoded.into_vec().into_iter().map(|v| v.to_f32().to_bits()).collect();
let want: Vec<u32> = E2M1_F32_ORACLE.iter().map(|v| v.to_bits()).collect();
assert_eq!(got, want);
}
#[test]
fn e2m1_zero_law_holds_for_negative_zero() {
use num_traits::Zero;
let neg_zero = f4e2m1::from_bits(0x8);
assert!(neg_zero.is_zero());
assert_eq!(neg_zero, f4e2m1::zero());
assert_eq!(neg_zero.is_zero(), neg_zero == f4e2m1::zero());
}
}