use crate::{
CompileOptions, Compiler, DeviceBuffer, Error, LaunchConfig, Result, Stream, TypedKernel, bf16,
cuda_export, cuda_kernel_file,
};
cuda_export!(
MxFp8Kernel = "mircuda_mxfp8_bf16"(
input: &DeviceBuffer<bf16>,
weight: &DeviceBuffer<u32>,
scales: &DeviceBuffer<u8>,
output: &mut DeviceBuffer<bf16>,
tokens: u32,
input_features: u32,
output_features: u32,
)
);
cuda_export!(
MxFp8TiledKernel = "mircuda_mxfp8_bf16_tiled"(
input: &DeviceBuffer<bf16>,
weight: &DeviceBuffer<u32>,
scales: &DeviceBuffer<u8>,
output: &mut DeviceBuffer<bf16>,
tokens: u32,
input_features: u32,
output_features: u32,
)
);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct MxFp8Spec {
tokens: usize,
input_features: usize,
output_features: usize,
}
impl MxFp8Spec {
pub fn new(tokens: usize, input_features: usize, output_features: usize) -> Result<Self> {
if tokens == 0
|| input_features == 0
|| output_features == 0
|| !input_features.is_multiple_of(32)
{
return Err(Error::InvalidMatmulShape);
}
let _ = u32::try_from(tokens)?;
let _ = u32::try_from(input_features)?;
let _ = u32::try_from(output_features)?;
let _ = tokens.checked_mul(input_features).ok_or(Error::InvalidMatmulShape)?;
let _ = output_features.checked_mul(input_features).ok_or(Error::InvalidMatmulShape)?;
let _ = tokens.checked_mul(output_features).ok_or(Error::InvalidMatmulShape)?;
Ok(Self { tokens, input_features, output_features })
}
#[must_use]
pub const fn tokens(self) -> usize {
self.tokens
}
#[must_use]
pub const fn input_features(self) -> usize {
self.input_features
}
#[must_use]
pub const fn output_features(self) -> usize {
self.output_features
}
pub fn weight_words(self) -> Result<usize> {
self.output_features
.checked_mul(self.input_features / 4)
.ok_or(Error::InvalidMatmulShape)
}
pub fn scale_elements(self) -> Result<usize> {
self.output_features
.checked_mul(self.input_features / 32)
.ok_or(Error::InvalidMatmulShape)
}
}
#[derive(Debug)]
pub struct MxFp8Matmul {
kernel: TypedKernel<MxFp8Kernel>,
tiled: TypedKernel<MxFp8TiledKernel>,
spec: MxFp8Spec,
}
impl MxFp8Matmul {
pub fn compile(compiler: &Compiler, spec: MxFp8Spec) -> Result<Self> {
let source = cuda_kernel_file!("../../kernels/mxfp8.cu");
let module = compiler.compile(source, &CompileOptions::default())?;
Ok(Self {
kernel: module.kernel()?,
tiled: module.kernel()?,
spec,
})
}
#[must_use]
pub const fn spec(&self) -> MxFp8Spec {
self.spec
}
pub fn execute(
&self,
stream: &Stream,
input: &DeviceBuffer<bf16>,
weight: &DeviceBuffer<u32>,
scales: &DeviceBuffer<u8>,
output: &mut DeviceBuffer<bf16>,
) -> Result<()> {
minimum("input", self.spec.tokens * self.spec.input_features, input.len())?;
validate("weight", self.spec.weight_words()?, weight.len())?;
validate("scales", self.spec.scale_elements()?, scales.len())?;
minimum("output", self.spec.tokens * self.spec.output_features, output.len())?;
let rows = u32::try_from(self.spec.output_features)?;
let tokens = u32::try_from(self.spec.tokens)?;
let launch = LaunchConfig {
grid: (
rows,
if tokens == 1 {
1
} else {
tokens.div_ceil(4)
},
1,
),
block: (256, 1, 1),
shared_memory_bytes: 0,
};
let operands = (
input,
weight,
scales,
output,
tokens,
u32::try_from(self.spec.input_features)?,
rows,
);
if tokens == 1 {
self.kernel.launch(stream, launch, operands)
} else {
self.tiled.launch(stream, launch, operands)
}
}
}
const fn validate(operand: &'static str, expected: usize, actual: usize) -> Result<()> {
if expected == actual {
Ok(())
} else {
Err(Error::MatmulLengthMismatch { operand, expected, actual })
}
}
const fn minimum(operand: &'static str, expected: usize, actual: usize) -> Result<()> {
if actual >= expected {
Ok(())
} else {
Err(Error::MatmulLengthMismatch { operand, expected, actual })
}
}