use std::sync::Arc;
use parking_lot::Mutex;
use super::MxFp8Spec;
use crate::{
BlockScaledMxFp8Plan, BlockScaledMxFp8Spec, CompileOptions, Compiler, Context, DeviceBuffer,
Error, LaunchConfig, MemoryPool, Result, Stream, TypedKernel, bf16, cuda_export,
cuda_kernel_file,
};
cuda_export!(
MxFp8QuantizeKernel = "mircuda_mxfp8_quantize_bf16"(
input: &DeviceBuffer<bf16>,
output: &mut DeviceBuffer<u8>,
scales: &mut DeviceBuffer<u8>,
rows: u32,
columns: u32,
)
);
cuda_export!(
MxFp8SwizzleKernel = "mircuda_mxfp8_swizzle_scales"(
input: &DeviceBuffer<u8>,
output: &mut DeviceBuffer<u8>,
rows: u32,
columns: u32,
)
);
#[derive(Debug)]
pub struct MxFp8TensorCore {
quantize: TypedKernel<MxFp8QuantizeKernel>,
swizzle: TypedKernel<MxFp8SwizzleKernel>,
plan: Mutex<BlockScaledMxFp8Plan>,
scratch: Arc<MxFp8TensorCoreScratch>,
spec: MxFp8Spec,
}
#[derive(Debug)]
pub struct MxFp8TensorCoreScratch {
quantized_input: Mutex<DeviceBuffer<u8>>,
input_scales: Mutex<DeviceBuffer<u8>>,
tokens: usize,
input_features: usize,
}
impl MxFp8TensorCore {
pub fn new(
compiler: &Compiler,
context: &Context,
pool: &MemoryPool,
stream: &Stream,
spec: MxFp8Spec,
) -> Result<Self> {
let scratch = Arc::new(MxFp8TensorCoreScratch::new(context, pool, stream, spec)?);
Self::new_with_scratch(compiler, context, stream, spec, scratch)
}
pub fn new_with_scratch(
compiler: &Compiler,
context: &Context,
stream: &Stream,
spec: MxFp8Spec,
scratch: Arc<MxFp8TensorCoreScratch>,
) -> Result<Self> {
scratch.validate(spec)?;
let native = BlockScaledMxFp8Spec::new(
spec.tokens(),
spec.output_features(),
spec.input_features(),
)?;
let module = compiler
.compile(cuda_kernel_file!("../../kernels/mxfp8.cu"), &CompileOptions::default())?;
Ok(Self {
quantize: module.kernel()?,
swizzle: module.kernel()?,
plan: Mutex::new(BlockScaledMxFp8Plan::new(context, stream, native)?),
scratch,
spec,
})
}
pub fn swizzle_weight_scales(
&self,
pool: &MemoryPool,
stream: &Stream,
scales: &DeviceBuffer<u8>,
) -> Result<DeviceBuffer<u8>> {
let rows = self.spec.output_features();
let columns = self.spec.input_features() / 32;
exact("MXFP8 weight scales", rows * columns, scales.len())?;
let native = self.plan.lock().spec();
let mut output = pool.allocate_zeroed::<u8>(stream, native.scale_bytes(rows)?)?;
self.swizzle.launch(
stream,
LaunchConfig::for_elements(rows * columns, 256)?,
(scales, &mut output, u32::try_from(rows)?, u32::try_from(columns)?),
)?;
self.prepare_weight_scales(stream, &output)?;
Ok(output)
}
pub fn prepare_weight_scales(
&self,
_stream: &Stream,
weight_scales: &DeviceBuffer<u8>,
) -> Result<()> {
let rows = self.spec.output_features();
let expected = self.plan.lock().spec().scale_bytes(rows)?;
exact("swizzled MXFP8 weight scales", expected, weight_scales.len())
}
#[must_use]
pub fn workspace_bytes(&self) -> usize {
self.plan.lock().workspace_bytes()
}
pub fn execute(
&self,
stream: &Stream,
input: &DeviceBuffer<bf16>,
weight: &DeviceBuffer<u32>,
weight_scales: &DeviceBuffer<u8>,
output: &mut DeviceBuffer<bf16>,
) -> Result<()> {
exact("MXFP8 input", self.spec.tokens() * self.spec.input_features(), input.len())?;
exact("MXFP8 weight", self.spec.weight_words()?, weight.len())?;
exact("MXFP8 output", self.spec.tokens() * self.spec.output_features(), output.len())?;
let mut quantized = self.scratch.quantized_input.lock();
let mut input_scales = self.scratch.input_scales.lock();
self.quantize.launch(
stream,
LaunchConfig {
grid: (
u32::try_from(self.spec.input_features() / 32)?,
u32::try_from(self.spec.tokens())?,
1,
),
block: (32, 1, 1),
shared_memory_bytes: 0,
},
(
input,
&mut quantized,
&mut input_scales,
u32::try_from(self.spec.tokens())?,
u32::try_from(self.spec.input_features())?,
),
)?;
self.plan
.lock()
.execute(stream, &quantized, &input_scales, weight, weight_scales, output)
}
}
impl MxFp8TensorCoreScratch {
pub fn new(
_context: &Context,
pool: &MemoryPool,
stream: &Stream,
spec: MxFp8Spec,
) -> Result<Self> {
let native = BlockScaledMxFp8Spec::new(
spec.tokens(),
spec.output_features(),
spec.input_features(),
)?;
Ok(Self {
quantized_input: Mutex::new(
pool.allocate::<u8>(stream, spec.tokens() * spec.input_features())?,
),
input_scales: Mutex::new(
pool.allocate_zeroed::<u8>(stream, native.scale_bytes(spec.tokens())?)?,
),
tokens: spec.tokens(),
input_features: spec.input_features(),
})
}
const fn validate(&self, spec: MxFp8Spec) -> Result<()> {
if self.tokens == spec.tokens() && self.input_features == spec.input_features() {
Ok(())
} else {
Err(Error::InvalidMatmulShape)
}
}
}
const fn exact(operand: &'static str, expected: usize, actual: usize) -> Result<()> {
if expected == actual {
Ok(())
} else {
Err(Error::MatmulLengthMismatch { operand, expected, actual })
}
}