use furiosa_mapping::*;
use furiosa_opt_macro::primitive;
use std::marker::PhantomData;
use crate::backend::Backend;
use crate::constraints;
use crate::context::*;
use crate::engine::{CanApplyFetch, CanApplyFetchChipLift, CanApplyFetchClusterLift, CanApplyFetchSliceLift};
use crate::runtime::CurrentBackend;
use crate::scalar::*;
use crate::tensor::Tensor;
use crate::tensor::tu::{Position, TuTensor};
use furiosa_opt_lower::{
FetchDimensionsInput, FetchInput, FetchLiftDimension, FetchLiftInput, config_fetch, config_fetch_dimensions,
config_fetch_lift,
};
#[derive(Debug)]
pub struct PositionFetch;
impl Position for PositionFetch {}
pub type FetchTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetch, D, Chip, Cluster, Slice, Time, Packet, B>;
#[derive(Debug)]
pub struct PositionFetchChipLift;
impl Position for PositionFetchChipLift {}
#[derive(Debug)]
pub struct PositionFetchClusterLift;
impl Position for PositionFetchClusterLift {}
#[derive(Debug)]
pub struct PositionFetchSliceLift;
impl Position for PositionFetchSliceLift {}
pub type FetchChipLiftTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchChipLift, D, Chip, Cluster, Slice, Time, Packet, B>;
pub type FetchClusterLiftTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchClusterLift, D, Chip, Cluster, Slice, Time, Packet, B>;
pub type FetchSliceLiftTensor<'l, const T: Tu, D, Chip, Cluster, Slice, Time, Packet, B = CurrentBackend> =
TuTensor<'l, { T }, PositionFetchSliceLift, D, Chip, Cluster, Slice, Time, Packet, B>;
fn new_fetch_lift_tensor<
'l,
const T: Tu,
P: Position,
D: Scalar,
Chip: M,
Cluster: M,
Slice: M,
Time: M,
Packet: M,
B: Backend,
>(
ctx: &'l mut TuContext<{ T }>,
inner: Tensor<D, Pair<Chip, Pair<Cluster, Pair<Slice, Pair<Time, Packet>>>>, B>,
) -> TuTensor<'l, T, P, D, Chip, Cluster, Slice, Time, Packet, B> {
TuTensor {
ctx,
inner,
_position: PhantomData,
}
}
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchChipLiftTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[doc(hidden)]
pub fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
new_fetch_lift_tensor(ctx, inner)
}
}
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchClusterLiftTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[doc(hidden)]
pub fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
new_fetch_lift_tensor(ctx, inner)
}
}
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchSliceLiftTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[doc(hidden)]
pub fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
new_fetch_lift_tensor(ctx, inner)
}
}
impl<'l, const T: Tu, D: Scalar, Chip: M, Cluster: M, Slice: M, Time: M, Packet: M, B: Backend>
FetchTensor<'l, T, D, Chip, Cluster, Slice, Time, Packet, B>
{
#[doc(hidden)]
pub fn new(ctx: &'l mut TuContext<{ T }>, inner: Tensor<D, Self::Mapping, B>) -> Self {
Self {
ctx,
inner,
_position: PhantomData,
}
}
}
impl<'l, const T: Tu, P: CanApplyFetch, 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)]
pub fn fetch<OutTime: M, OutPacket: M>(self) -> FetchTensor<'l, T, D, Chip, Cluster, Slice, OutTime, OutPacket, B> {
verify_fetch::<Cluster, Slice, Time, Packet, OutTime, OutPacket>();
FetchTensor::new(self.ctx, self.inner.transpose(true))
}
}
impl<'l, const T: Tu, P: Position, 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_chip_lift)]
pub fn fetch_chip_lift<OutChip: M, OutTime: M>(
self,
) -> FetchChipLiftTensor<'l, T, D, OutChip, Cluster, Slice, OutTime, Packet, B>
where
P: CanApplyFetchChipLift,
{
constraints::assert_chip_preserved::<Chip, OutChip>();
constraints::assert_lift_factor::<Chip, Time, OutTime>();
verify_fetch_lift::<Chip, OutChip, Time, Packet, OutTime>(FetchLiftDimension::Chip);
FetchChipLiftTensor::new(self.ctx, self.inner.transpose(true))
}
}
impl<'l, const T: Tu, P: Position, 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_cluster_lift)]
pub fn fetch_cluster_lift<OutCluster: M, OutTime: M>(
self,
) -> FetchClusterLiftTensor<'l, T, D, Chip, OutCluster, Slice, OutTime, Packet, B>
where
P: CanApplyFetchClusterLift,
{
constraints::assert_cluster_preserved::<Cluster, OutCluster>();
constraints::assert_lift_factor::<Cluster, Time, OutTime>();
verify_fetch_lift::<Cluster, OutCluster, Time, Packet, OutTime>(FetchLiftDimension::Cluster);
FetchClusterLiftTensor::new(self.ctx, self.inner.transpose(true))
}
}
impl<'l, const T: Tu, P: Position, 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_slice_lift)]
pub fn fetch_slice_lift<OutSlice: M, OutTime: M>(
self,
) -> FetchSliceLiftTensor<'l, T, D, Chip, Cluster, OutSlice, OutTime, Packet, B>
where
P: CanApplyFetchSliceLift,
{
constraints::assert_slice_preserved::<Slice, OutSlice>();
constraints::assert_lift_factor::<Slice, Time, OutTime>();
verify_fetch_lift::<Slice, OutSlice, Time, Packet, OutTime>(FetchLiftDimension::Slice);
FetchSliceLiftTensor::new(self.ctx, self.inner.transpose(true))
}
}
fn verify_fetch<Cluster: M, Slice: M, Time: M, Packet: M, OutTime: M, OutPacket: M>() {
config_fetch_dimensions(FetchDimensionsInput {
cluster_size: Cluster::SIZE,
slice_size: Slice::SIZE,
})
.unwrap_or_else(|e| panic!("{e}"));
config_fetch(FetchInput {
in_time: Time::to_value(),
in_packet: Packet::to_value(),
out_time: OutTime::to_value(),
out_packet: OutPacket::to_value(),
lifted: None,
})
.unwrap_or_else(|e| panic!("{e}"));
}
fn verify_fetch_lift<Placement: M, OutPlacement: M, Time: M, Packet: M, OutTime: M>(dimension: FetchLiftDimension) {
let lifted = config_fetch_lift(FetchLiftInput {
dimension,
in_placement: Placement::to_value(),
out_placement: OutPlacement::to_value(),
})
.unwrap_or_else(|e| panic!("{e}"));
let packet = Packet::to_value();
let _ = config_fetch(FetchInput {
in_time: Time::to_value(),
in_packet: packet.clone(),
out_time: OutTime::to_value(),
out_packet: packet,
lifted: Some(lifted),
})
.unwrap_or_else(|e| panic!("{dimension} lift: {e}"));
}
#[cfg(test)]
mod tests {
use super::*;
use furiosa_opt_lower::{FetchError, FetchLiftError};
axes![A = 8, B = 8, S = 64, X = 8, L = 16];
#[test]
fn valid_read() {
verify_fetch::<m![1], m![S], m![1], m![A, B], m![A], m![B]>();
}
#[test]
fn transposed_read_rejects() {
assert_eq!(
config_fetch(FetchInput {
in_time: <m![1]>::to_value(),
in_packet: <m![A, B]>::to_value(),
out_time: <m![B]>::to_value(),
out_packet: <m![A]>::to_value(),
lifted: None,
}),
Err(FetchError::NonContiguousPacket {
innermost: <m![A]>::to_value(),
memory_stride: B::SIZE,
})
);
}
#[test]
fn innermost_packet_broadcast_rejects() {
assert_eq!(
config_fetch(FetchInput {
in_time: <m![1]>::to_value(),
in_packet: <m![B]>::to_value(),
out_time: <m![1]>::to_value(),
out_packet: <m![B, X]>::to_value(),
lifted: None,
}),
Err(FetchError::NonContiguousPacket {
innermost: <m![X]>::to_value(),
memory_stride: 0,
})
);
}
#[test]
fn split_axis_across_packet_and_time() {
verify_fetch::<m![1], m![S], m![1], m![L], m![L / 8], m![L % 8]>();
}
#[test]
fn broadcast_time_read() {
verify_fetch::<m![1], m![S], m![1], m![A], m![4], m![A]>();
}
#[test]
fn live_input_time_reads() {
verify_fetch::<m![1], m![S], m![A], m![B], m![A], m![B]>();
}
#[test]
fn unreadable_axis_rejects() {
assert!(matches!(
config_fetch(FetchInput {
in_time: <m![1]>::to_value(),
in_packet: <m![A, B]>::to_value(),
out_time: <m![A]>::to_value(),
out_packet: <m![X]>::to_value(),
lifted: None,
}),
Err(FetchError::Unread { .. })
));
}
#[test]
fn cluster_size_rejects() {
assert_eq!(
config_fetch_dimensions(FetchDimensionsInput {
cluster_size: 3,
slice_size: S::SIZE,
}),
Err(FetchError::ClusterSize(3))
);
}
#[test]
fn slice_size_rejects() {
assert_eq!(
config_fetch_dimensions(FetchDimensionsInput {
cluster_size: 1,
slice_size: 100,
}),
Err(FetchError::SliceSize(100))
);
}
#[test]
fn verify_fetch_checks_dimensions() {
assert!(
std::panic::catch_unwind(|| { verify_fetch::<m![3], m![S], m![1], m![A, B], m![A], m![B]>() }).is_err()
);
assert!(
std::panic::catch_unwind(|| { verify_fetch::<m![1], m![100], m![1], m![A, B], m![A], m![B]>() }).is_err()
);
}
#[test]
fn unaligned_output_packet_allowed() {
verify_fetch::<m![1], m![S], m![1], m![L / 4], m![1], m![L / 4]>();
}
#[test]
fn valid_lift() {
verify_fetch_lift::<m![S, 2], m![S, A / 4], m![A], m![B], m![A % 4]>(FetchLiftDimension::Slice);
}
#[test]
fn valid_two_broadcast_lift() {
verify_fetch_lift::<m![2, S, 2], m![A / 4, S, B / 4], m![A / 4, B / 4], m![X], m![1]>(
FetchLiftDimension::Slice,
);
}
#[test]
fn relabelled_placement_rejects() {
assert!(matches!(
config_fetch_lift(FetchLiftInput {
dimension: FetchLiftDimension::Slice,
in_placement: <m![S, A / 4]>::to_value(),
out_placement: <m![S, B / 4]>::to_value(),
}),
Err(FetchLiftError::PlacementMismatch { .. })
));
}
#[test]
fn unchanged_placement_rejects() {
assert_eq!(
config_fetch_lift(FetchLiftInput {
dimension: FetchLiftDimension::Slice,
in_placement: <m![S, 2]>::to_value(),
out_placement: <m![S, 2]>::to_value(),
}),
Err(FetchLiftError::NoAxisLifted {
dimension: FetchLiftDimension::Slice,
})
);
}
}