use super::{elements, empty, float_tensor, kernels, MoeError, RoutingPlan};
use ruda_core::{device::Device, tensor::DType};
use ruda_kernel::{dsl::prelude::*, tensor::RudaTensor};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouterScoring { Softmax, Sigmoid }
#[derive(Debug, Clone, Copy)]
pub struct RouterWeightOptions {
pub scoring: RouterScoring,
pub renormalize: bool,
pub scale: f32,
}
impl RouterWeightOptions {
pub fn validate(&self) -> Result<(), MoeError> {
if !self.scale.is_finite() || self.scale <= 0.0 {
return Err(MoeError("router scale must be finite positive FP32"));
}
Ok(())
}
}
fn same_queue<R: Runtime>(a: &RudaTensor<R>, b: &RudaTensor<R>) -> Result<(), MoeError> {
if !b.is_contiguous() || b.qparams.is_some() || a.device.to_id() != b.device.to_id()
|| !a.client.same_execution_queue(&b.client) {
return Err(MoeError("router operands require contiguous, same-device, same-queue storage"));
}
elements(b.meta.shape())?;
Ok(())
}
fn validate<R: Runtime>(logits: &RudaTensor<R>, indices: &RudaTensor<R>, options: RouterWeightOptions)
-> Result<(usize, usize, usize, u32), MoeError>
{
options.validate()?;
float_tensor(logits)?;
same_queue(logits, logits)?;
same_queue(logits, indices)?;
if logits.meta.num_dims() != 2 || indices.meta.num_dims() != 2
|| !matches!(indices.dtype, DType::U32 | DType::I32 | DType::I64) {
return Err(MoeError("router requires [tokens,experts] logits and [tokens,top_k] integer indices"));
}
let (tokens, experts, k) = (logits.meta.shape()[0], logits.meta.shape()[1], indices.meta.shape()[1]);
if experts == 0 || indices.meta.shape()[0] != tokens || k == 0 || k > experts || k > 64 {
return Err(MoeError("router requires 1 <= top_k <= min(experts,64) and matching token counts"));
}
let hw = &logits.client.properties().hardware;
let lanes = hw.plane_size_max;
if !matches!(lanes, 32 | 64) || lanes > hw.max_ruda_dim.0 || tokens > hw.max_ruda_count.0 as usize {
return Err(MoeError("router requires a full 32/64-lane plane and a legal row grid"));
}
Ok((tokens, experts, k, lanes))
}
pub fn selected_router_weights<R: Runtime>(logits: &RudaTensor<R>, indices: &RudaTensor<R>,
options: RouterWeightOptions) -> Result<RudaTensor<R>, MoeError>
{
validate(logits, indices, options)?;
let output = empty(logits, indices.meta.shape().to_vec(), DType::F32);
unsafe { selected_router_weights_into(logits, indices, &output, options)?; }
Ok(output)
}
pub unsafe fn selected_router_weights_into<R: Runtime>(logits: &RudaTensor<R>, indices: &RudaTensor<R>,
output: &RudaTensor<R>, options: RouterWeightOptions) -> Result<(), MoeError>
{
let (tokens, experts, k, lanes) = validate(logits, indices, options)?;
same_queue(logits, output)?;
if output.meta.shape() != indices.meta.shape() || output.dtype != DType::F32 {
return Err(MoeError("router weight output must be matching FP32 storage"));
}
if tokens != 0 {
kernels::router_training::weights::launch::<R>(
&logits.client, RudaCount::Static(tokens as u32, 1, 1), RudaDim::new_1d(lanes),
logits.clone().into_array_arg(), indices.clone().into_array_arg(), output.clone().into_array_arg(),
experts, k, lanes as usize, options.scoring == RouterScoring::Softmax,
options.renormalize, options.scale, [logits.dtype.into(), indices.dtype.into()]);
}
Ok(())
}
pub fn selected_router_backward<R: Runtime>(logits: &RudaTensor<R>, indices: &RudaTensor<R>,
grad_weights: &RudaTensor<R>, options: RouterWeightOptions) -> Result<RudaTensor<R>, MoeError>
{
validate(logits, indices, options)?;
let output = empty(logits, logits.meta.shape().to_vec(), logits.dtype);
unsafe { selected_router_backward_into(logits, indices, grad_weights, &output, options)?; }
Ok(output)
}
pub unsafe fn selected_router_backward_into<R: Runtime>(logits: &RudaTensor<R>, indices: &RudaTensor<R>,
grad_weights: &RudaTensor<R>, output: &RudaTensor<R>, options: RouterWeightOptions)
-> Result<(), MoeError>
{
let (tokens, experts, k, lanes) = validate(logits, indices, options)?;
same_queue(logits, grad_weights)?; same_queue(logits, output)?;
if grad_weights.meta.shape() != indices.meta.shape() || grad_weights.dtype != DType::F32
|| output.meta.shape() != logits.meta.shape() || output.dtype != logits.dtype {
return Err(MoeError("router backward gradient shape/dtype mismatch"));
}
if tokens != 0 {
kernels::router_training::backward::launch::<R>(
&logits.client, RudaCount::Static(tokens as u32, 1, 1), RudaDim::new_1d(lanes),
logits.clone().into_array_arg(), indices.clone().into_array_arg(), grad_weights.clone().into_array_arg(),
output.clone().into_array_arg(), experts, k, lanes as usize,
options.scoring == RouterScoring::Softmax, options.renormalize, options.scale,
[logits.dtype.into(), indices.dtype.into()]);
}
Ok(())
}
pub unsafe fn prepare_selected_router_weights<R:Runtime>(logits:&RudaTensor<R>,indices:&RudaTensor<R>,
output:&RudaTensor<R>,options:RouterWeightOptions)->Result<Vec<PreparedKernel<R>>,MoeError>
{
let (tokens,experts,k,lanes)=validate(logits,indices,options)?;same_queue(logits,output)?;
if output.meta.shape()!=indices.meta.shape() || output.dtype!=DType::F32 {
return Err(MoeError("router weight output must be matching FP32 storage"));
}
if tokens==0 {return Ok(Vec::new());}
Ok(vec![unsafe {kernels::router_training::weights::prepare::<R>(&logits.client,
RudaCount::Static(tokens as u32,1,1),RudaDim::new_1d(lanes),logits.clone().into_array_arg(),
indices.clone().into_array_arg(),output.clone().into_array_arg(),experts,k,lanes as usize,
options.scoring==RouterScoring::Softmax,options.renormalize,options.scale,[logits.dtype.into(),indices.dtype.into()])}])
}
pub unsafe fn prepare_selected_router_backward<R:Runtime>(logits:&RudaTensor<R>,indices:&RudaTensor<R>,
grad_weights:&RudaTensor<R>,output:&RudaTensor<R>,options:RouterWeightOptions)->Result<Vec<PreparedKernel<R>>,MoeError>
{
let (tokens,experts,k,lanes)=validate(logits,indices,options)?;
same_queue(logits,grad_weights)?;same_queue(logits,output)?;
if grad_weights.meta.shape()!=indices.meta.shape() || grad_weights.dtype!=DType::F32
|| output.meta.shape()!=logits.meta.shape() || output.dtype!=logits.dtype {
return Err(MoeError("router backward gradient shape/dtype mismatch"));
}
if tokens==0 {return Ok(Vec::new());}
Ok(vec![unsafe {kernels::router_training::backward::prepare::<R>(&logits.client,
RudaCount::Static(tokens as u32,1,1),RudaDim::new_1d(lanes),logits.clone().into_array_arg(),
indices.clone().into_array_arg(),grad_weights.clone().into_array_arg(),output.clone().into_array_arg(),
experts,k,lanes as usize,options.scoring==RouterScoring::Softmax,options.renormalize,options.scale,
[logits.dtype.into(),indices.dtype.into()])}])
}
#[derive(Debug, Clone)]
pub struct RouterTrainingPlan<R: Runtime> {
plan: RoutingPlan<R>,
logits: RudaTensor<R>,
options: RouterWeightOptions,
}
impl<R: Runtime> RoutingPlan<R> {
pub fn into_training(mut self, logits: RudaTensor<R>, options: RouterWeightOptions)
-> Result<RouterTrainingPlan<R>, MoeError>
{
if logits.meta.shape().as_slice() != [self.tokens, self.experts] {
return Err(MoeError("router training logits do not match routing plan"));
}
self.weights = selected_router_weights(&logits, &self.indices, options)?;
Ok(RouterTrainingPlan { plan: self, logits, options })
}
}
impl<R: Runtime> RouterTrainingPlan<R> {
pub fn routing(&self) -> &RoutingPlan<R> { &self.plan }
pub fn backward(&self, grad_weights: &RudaTensor<R>) -> Result<RudaTensor<R>, MoeError> {
selected_router_backward(&self.logits, &self.plan.indices, grad_weights, self.options)
}
}