use super::{GroupedExpertRows,MoeError,Nf4ExpertProjection,Nf4ExpertExecution,Nf4ExpertError};
use rublas::tensor_int4::{AwqGroupedGemm,Int4Error};
use ruda_kernel::{dsl::{Runtime,calculate_ruda_count_elemwise,prelude::RudaDim},tensor::RudaTensor};
use std::fmt;
#[derive(Debug)]
pub enum PackedExpertError {Rows(MoeError),Nf4(Nf4ExpertError),Awq(Int4Error)}
impl From<MoeError> for PackedExpertError {fn from(error:MoeError) -> Self {Self::Rows(error)}}
impl From<Nf4ExpertError> for PackedExpertError {fn from(error:Nf4ExpertError) -> Self {Self::Nf4(error)}}
impl From<Int4Error> for PackedExpertError {fn from(error:Int4Error) -> Self {Self::Awq(error)}}
impl fmt::Display for PackedExpertError {
fn fmt(&self,f:&mut fmt::Formatter<'_>) -> fmt::Result {match self {Self::Rows(error)=>write!(f,"{error}"),Self::Nf4(error)=>write!(f,"{error}"),Self::Awq(error)=>write!(f,"{error}")}}
}
impl std::error::Error for PackedExpertError {}
#[derive(Clone,Debug)]
pub enum PackedExpertProjection<R:Runtime> {
Nf4 {projection:Nf4ExpertProjection<R>,execution:Nf4ExpertExecution},
Awq(AwqGroupedGemm<R>),
}
impl<R:Runtime> PackedExpertProjection<R> {
pub fn dimensions(&self) -> [usize;3] {match self {
Self::Nf4 {projection,..}=>{let (e,l)=projection.layout();[e,l.input_features,l.output_features]},
Self::Awq(projection)=>{let (e,l)=projection.layout();[e,l.input_features,l.output_features]}}}
pub fn forward(&self,rows:&GroupedExpertRows<R>) -> Result<RudaTensor<R>,PackedExpertError> {
if rows.experts!=self.dimensions()[0] {return Err(MoeError("packed payload and private native expert count differ").into());}
Ok(match self {Self::Nf4 {projection,execution}=>projection.forward(rows,execution.tile_rows,execution.use_tensor_core)?,
Self::Awq(projection)=>projection.forward(rows.values.clone(),rows.row_experts.clone())?})
}
pub fn input_backward(&self,rows:&GroupedExpertRows<R>,gradient:RudaTensor<R>) -> Result<RudaTensor<R>,PackedExpertError> {
if rows.experts!=self.dimensions()[0] || gradient.meta.num_dims()!=2 || gradient.meta.shape()[0]!=rows.values.meta.shape()[0]
|| gradient.dtype!=rows.values.dtype {return Err(MoeError("packed expert seed rows/storage differ from original forward").into());}
Ok(match self {Self::Nf4 {projection,execution}=>ruprim::elementwise::cast::cast(
projection.input_backward_f32(rows,gradient,execution.tile_rows,execution.use_tensor_core)?,rows.values.dtype),
Self::Awq(projection)=>projection.input_backward(gradient,rows.row_experts.clone())?})
}
}
#[derive(Clone,Debug)]
pub struct PackedSwiGluExperts<R:Runtime> {gate:PackedExpertProjection<R>,up:PackedExpertProjection<R>,down:PackedExpertProjection<R>}
#[derive(Clone,Debug)]
pub struct PackedSwiGluCache<R:Runtime> {rows:GroupedExpertRows<R>,gate:RudaTensor<R>,up:RudaTensor<R>,experts:PackedSwiGluExperts<R>}
impl<R:Runtime> PackedSwiGluExperts<R> {
pub fn new(gate:PackedExpertProjection<R>,up:PackedExpertProjection<R>,down:PackedExpertProjection<R>) -> Result<Self,PackedExpertError> {
let [e,h,i]=gate.dimensions();
if up.dimensions()!=[e,h,i] || down.dimensions()!=[e,i,h] {return Err(MoeError("packed expert gate/up/down source geometry differs").into());}
Ok(Self {gate,up,down})
}
pub fn forward(&self,rows:&GroupedExpertRows<R>) -> Result<RudaTensor<R>,PackedExpertError> {
let gate=self.gate.forward(rows)?;let up=self.up.forward(rows)?;let size=gate.meta.num_elements();
if size!=0 {let dim=RudaDim::new(gate.client.properties(),size);
super::kernels::experts::swiglu::launch::<R>(&gate.client,calculate_ruda_count_elemwise(&gate.client,size,dim),dim,
gate.clone().into_array_arg(),up.into_array_arg(),gate.dtype.into());}
let mut activated=rows.clone();activated.values=gate;self.down.forward(&activated)
}
pub fn forward_training(&self,rows:&GroupedExpertRows<R>) -> Result<(RudaTensor<R>,PackedSwiGluCache<R>),PackedExpertError> {
let gate=self.gate.forward(rows)?;let up=self.up.forward(rows)?;let activated=super::empty(&gate,gate.meta.shape().clone(),gate.dtype);let size=activated.meta.num_elements();
if size!=0 {let dim=RudaDim::new(gate.client.properties(),size);
super::kernels::experts::swiglu_out::launch::<R>(&gate.client,calculate_ruda_count_elemwise(&gate.client,size,dim),dim,
gate.clone().into_array_arg(),up.clone().into_array_arg(),activated.clone().into_array_arg(),gate.dtype.into());}
let mut group=rows.clone();group.values=activated;
Ok((self.down.forward(&group)?,PackedSwiGluCache {rows:rows.clone(),gate,up,experts:self.clone()}))
}
}
impl<R:Runtime> PackedSwiGluCache<R> {
pub fn input_backward(self,gradient:RudaTensor<R>) -> Result<RudaTensor<R>,PackedExpertError> {
let dtype=self.rows.values.dtype;let down=self.experts.down.input_backward(&self.rows,gradient)?;
let dg=super::empty(&self.gate,self.gate.meta.shape().clone(),dtype);let du=super::empty(&self.up,self.up.meta.shape().clone(),dtype);let size=dg.meta.num_elements();
if size!=0 {let dim=RudaDim::new(dg.client.properties(),size);
super::kernels::experts::swiglu_backward::launch::<R>(&dg.client,calculate_ruda_count_elemwise(&dg.client,size,dim),dim,
self.gate.into_array_arg(),self.up.into_array_arg(),down.into_array_arg(),dg.clone().into_array_arg(),du.clone().into_array_arg(),dtype.into());}
let dg=self.experts.gate.input_backward(&self.rows,dg)?;let du=self.experts.up.input_backward(&self.rows,du)?;
let output=super::empty(&dg,dg.meta.shape().clone(),dtype);let size=output.meta.num_elements();
if size!=0 {let dim=RudaDim::new(output.client.properties(),size);
super::kernels::experts::add::launch::<R>(&output.client,calculate_ruda_count_elemwise(&output.client,size,dim),dim,
dg.into_array_arg(),du.into_array_arg(),output.clone().into_array_arg(),dtype.into());}
Ok(output)
}
}