use super::kernels::Mamba3Kernels;
use super::weights::GpuMamba3WeightsInf;
use crate::mamba_ssm::gpu::buffers::GpuBuffer;
use crate::mamba_ssm::gpu::device::GpuDevice;
use crate::mamba3_siso::config::Mamba3Config;
use crate::mamba3_siso::weights::Mamba3Weights;
use std::ffi::c_int;
use std::sync::Arc;
type Stream = Arc<cudarc::driver::CudaStream>;
fn sgemm_no_bias(
blas: &cudarc::cublas::CudaBlas,
y: &GpuBuffer,
x: &GpuBuffer,
w_ptr: cudarc::driver::sys::CUdeviceptr,
batch: usize,
n_in: usize,
n_out: usize,
) -> Result<(), String> {
let alpha: f32 = 1.0;
let beta: f32 = 0.0;
let w_raw = w_ptr as *const f32;
let x_raw = x.cached_ptr() as *const f32;
let y_raw = y.cached_ptr() as *mut f32;
unsafe {
cudarc::cublas::result::sgemm(
*blas.handle(),
cudarc::cublas::sys::cublasOperation_t::CUBLAS_OP_N,
cudarc::cublas::sys::cublasOperation_t::CUBLAS_OP_N,
n_out as c_int,
batch as c_int,
n_in as c_int,
&alpha as *const f32,
w_raw,
n_out as c_int,
x_raw,
n_in as c_int,
&beta as *const f32,
y_raw,
n_out as c_int,
)
.map_err(|e| format!("cuBLAS sgemm failed: {e:?}"))?;
}
Ok(())
}
pub struct Mamba3GpuInferenceState {
pub ssm_state: GpuBuffer,
pub k_state: GpuBuffer,
pub v_state: GpuBuffer,
pub angle_state: GpuBuffer,
pub batch: usize,
pub n_layers: usize,
pub nheads: usize,
pub headdim: usize,
pub d_state: usize,
pub n_angles: usize,
}
impl Mamba3GpuInferenceState {
pub fn zeros(stream: &Stream, batch: usize, cfg: &Mamba3Config) -> Result<Self, String> {
let nh = cfg.nheads();
let hd = cfg.headdim;
let ds = cfg.d_state;
let na = cfg.num_rope_angles().max(1);
let nl = cfg.n_layers;
Ok(Self {
ssm_state: GpuBuffer::zeros(stream, nl * batch * nh * hd * ds)?,
k_state: GpuBuffer::zeros(stream, nl * batch * nh * ds)?,
v_state: GpuBuffer::zeros(stream, nl * batch * nh * hd)?,
angle_state: GpuBuffer::zeros(stream, nl * batch * nh * na)?,
batch,
n_layers: nl,
nheads: nh,
headdim: hd,
d_state: ds,
n_angles: na,
})
}
pub fn reset(&mut self, stream: &Stream) -> Result<(), String> {
self.ssm_state.zero(stream)?;
self.k_state.zero(stream)?;
self.v_state.zero(stream)?;
self.angle_state.zero(stream)
}
pub fn ssm_per_layer(&self) -> usize {
self.batch * self.nheads * self.headdim * self.d_state
}
pub fn k_per_layer(&self) -> usize {
self.batch * self.nheads * self.d_state
}
pub fn v_per_layer(&self) -> usize {
self.batch * self.nheads * self.headdim
}
pub fn angle_per_layer(&self) -> usize {
self.batch * self.nheads * self.n_angles
}
}
pub struct Mamba3GpuInferenceScratch {
pub gpu_input: GpuBuffer, pub temporal: GpuBuffer, pub residual: GpuBuffer, pub proj: GpuBuffer, pub z: GpuBuffer, pub x: GpuBuffer, pub b_raw: GpuBuffer, pub c_raw: GpuBuffer, pub b_normed: GpuBuffer, pub c_normed: GpuBuffer, pub b_rms: GpuBuffer, pub c_rms: GpuBuffer, pub b_biased: GpuBuffer, pub c_biased: GpuBuffer, pub k_cur: GpuBuffer, pub q_cur: GpuBuffer, pub dd_dt_raw: GpuBuffer, pub dd_a_raw: GpuBuffer, pub trap_raw: GpuBuffer, pub dt: GpuBuffer, pub a_val: GpuBuffer, pub trap: GpuBuffer, pub angles_raw: GpuBuffer, pub angle_cumsum: GpuBuffer, pub alpha: GpuBuffer, pub beta: GpuBuffer, pub gamma: GpuBuffer, pub y: GpuBuffer, pub gated: GpuBuffer, pub post_norm: GpuBuffer, pub rms_buf: GpuBuffer, pub gated_rms_buf: GpuBuffer, }
impl Mamba3GpuInferenceScratch {
pub fn zeros(
stream: &Stream,
batch: usize,
cfg: &Mamba3Config,
input_dim: usize,
) -> Result<Self, String> {
let dm = cfg.d_model;
let di = cfg.d_inner();
let ds = cfg.d_state;
let nh = cfg.nheads();
let ng = cfg.ngroups;
let ip = cfg.in_proj_out_dim();
let na = cfg.num_rope_angles().max(1);
Ok(Self {
gpu_input: GpuBuffer::zeros(stream, batch * input_dim)?,
temporal: GpuBuffer::zeros(stream, batch * dm)?,
residual: GpuBuffer::zeros(stream, batch * dm)?,
proj: GpuBuffer::zeros(stream, batch * ip)?,
z: GpuBuffer::zeros(stream, batch * di)?,
x: GpuBuffer::zeros(stream, batch * di)?,
b_raw: GpuBuffer::zeros(stream, batch * ng * ds)?,
c_raw: GpuBuffer::zeros(stream, batch * ng * ds)?,
b_normed: GpuBuffer::zeros(stream, batch * ng * ds)?,
c_normed: GpuBuffer::zeros(stream, batch * ng * ds)?,
b_rms: GpuBuffer::zeros(stream, batch * ng)?,
c_rms: GpuBuffer::zeros(stream, batch * ng)?,
b_biased: GpuBuffer::zeros(stream, batch * nh * ds)?,
c_biased: GpuBuffer::zeros(stream, batch * nh * ds)?,
k_cur: GpuBuffer::zeros(stream, batch * nh * ds)?,
q_cur: GpuBuffer::zeros(stream, batch * nh * ds)?,
dd_dt_raw: GpuBuffer::zeros(stream, batch * nh)?,
dd_a_raw: GpuBuffer::zeros(stream, batch * nh)?,
trap_raw: GpuBuffer::zeros(stream, batch * nh)?,
dt: GpuBuffer::zeros(stream, batch * nh)?,
a_val: GpuBuffer::zeros(stream, batch * nh)?,
trap: GpuBuffer::zeros(stream, batch * nh)?,
angles_raw: GpuBuffer::zeros(stream, batch * na)?,
angle_cumsum: GpuBuffer::zeros(stream, batch * nh * na)?,
alpha: GpuBuffer::zeros(stream, batch * nh)?,
beta: GpuBuffer::zeros(stream, batch * nh)?,
gamma: GpuBuffer::zeros(stream, batch * nh)?,
y: GpuBuffer::zeros(stream, batch * di)?,
gated: GpuBuffer::zeros(stream, batch * di)?,
post_norm: GpuBuffer::zeros(stream, batch * dm)?,
rms_buf: GpuBuffer::zeros(stream, batch)?,
gated_rms_buf: GpuBuffer::zeros(stream, batch * nh)?,
})
}
}
use crate::mamba_ssm::gpu::buffers::DtypedBuf;
use crate::mamba_ssm::gpu::dtype::WeightDtype;
pub struct Mamba3GpuInferenceMixedScratch {
pub gpu_input: GpuBuffer, pub temporal: DtypedBuf, pub residual: GpuBuffer, pub proj: DtypedBuf,
pub z: DtypedBuf,
pub x: DtypedBuf,
pub b_raw: DtypedBuf,
pub c_raw: DtypedBuf,
pub b_normed: DtypedBuf,
pub c_normed: DtypedBuf,
pub b_rms: GpuBuffer, pub c_rms: GpuBuffer, pub b_biased: DtypedBuf,
pub c_biased: DtypedBuf,
pub k_cur: DtypedBuf, pub q_cur: DtypedBuf, pub dd_dt_raw: GpuBuffer,
pub dd_a_raw: GpuBuffer,
pub trap_raw: GpuBuffer,
pub dt: GpuBuffer,
pub a_val: GpuBuffer,
pub trap: GpuBuffer,
pub angles_raw: GpuBuffer, pub angle_cumsum: GpuBuffer, pub alpha: GpuBuffer,
pub beta: GpuBuffer,
pub gamma: GpuBuffer,
pub y: DtypedBuf,
pub gated: DtypedBuf,
pub post_norm: DtypedBuf,
pub rms_buf: GpuBuffer,
pub gated_rms_buf: GpuBuffer,
pub dtype: WeightDtype,
}
impl Mamba3GpuInferenceMixedScratch {
pub fn zeros(
stream: &Stream,
batch: usize,
cfg: &Mamba3Config,
input_dim: usize,
dtype: WeightDtype,
) -> Result<Self, String> {
if matches!(dtype, WeightDtype::F32) {
return Err("Mamba3GpuInferenceMixedScratch requires bf16 or f16 dtype".to_string());
}
let dm = cfg.d_model;
let di = cfg.d_inner();
let ds = cfg.d_state;
let nh = cfg.nheads();
let ng = cfg.ngroups;
let ip = cfg.in_proj_out_dim();
let na = cfg.num_rope_angles().max(1);
Ok(Self {
gpu_input: GpuBuffer::zeros(stream, batch * input_dim)?,
temporal: DtypedBuf::zeros(stream, batch * dm, dtype)?,
residual: GpuBuffer::zeros(stream, batch * dm)?,
proj: DtypedBuf::zeros(stream, batch * ip, dtype)?,
z: DtypedBuf::zeros(stream, batch * di, dtype)?,
x: DtypedBuf::zeros(stream, batch * di, dtype)?,
b_raw: DtypedBuf::zeros(stream, batch * ng * ds, dtype)?,
c_raw: DtypedBuf::zeros(stream, batch * ng * ds, dtype)?,
b_normed: DtypedBuf::zeros(stream, batch * ng * ds, dtype)?,
c_normed: DtypedBuf::zeros(stream, batch * ng * ds, dtype)?,
b_rms: GpuBuffer::zeros(stream, batch * ng)?,
c_rms: GpuBuffer::zeros(stream, batch * ng)?,
b_biased: DtypedBuf::zeros(stream, batch * nh * ds, dtype)?,
c_biased: DtypedBuf::zeros(stream, batch * nh * ds, dtype)?,
k_cur: DtypedBuf::zeros(stream, batch * nh * ds, dtype)?,
q_cur: DtypedBuf::zeros(stream, batch * nh * ds, dtype)?,
dd_dt_raw: GpuBuffer::zeros(stream, batch * nh)?,
dd_a_raw: GpuBuffer::zeros(stream, batch * nh)?,
trap_raw: GpuBuffer::zeros(stream, batch * nh)?,
dt: GpuBuffer::zeros(stream, batch * nh)?,
a_val: GpuBuffer::zeros(stream, batch * nh)?,
trap: GpuBuffer::zeros(stream, batch * nh)?,
angles_raw: GpuBuffer::zeros(stream, batch * na)?,
angle_cumsum: GpuBuffer::zeros(stream, batch * nh * na)?,
alpha: GpuBuffer::zeros(stream, batch * nh)?,
beta: GpuBuffer::zeros(stream, batch * nh)?,
gamma: GpuBuffer::zeros(stream, batch * nh)?,
y: DtypedBuf::zeros(stream, batch * di, dtype)?,
gated: DtypedBuf::zeros(stream, batch * di, dtype)?,
post_norm: DtypedBuf::zeros(stream, batch * dm, dtype)?,
rms_buf: GpuBuffer::zeros(stream, batch)?,
gated_rms_buf: GpuBuffer::zeros(stream, batch * nh)?,
dtype,
})
}
}
pub struct Mamba3GpuInferenceEngine {
pub kernels: Mamba3Kernels,
pub weights: GpuMamba3WeightsInf,
pub stream: Stream,
pub blas: Arc<cudarc::cublas::CudaBlas>,
pub cfg: Mamba3Config,
pub batch: usize,
pub input_dim: usize,
pub identity_proj: bool,
graph: Option<cudarc::driver::CudaGraph>,
captured_state_ptr: u64,
captured_scratch_ptr: u64,
}
impl Mamba3GpuInferenceEngine {
pub fn new(
device: &GpuDevice,
cpu_weights: &Mamba3Weights,
cfg: Mamba3Config,
input_dim: usize,
batch: usize,
) -> Result<Self, String> {
cfg.validate()?;
unsafe { device.context().disable_event_tracking() };
let stream = device.fork_stream()?;
let arch = GpuDevice::nvrtc_arch(device.compute_capability);
let kernels = Mamba3Kernels::compile(device.context(), arch)?;
let (blas, _ws) = device.create_cublas(&stream)?;
let weights = GpuMamba3WeightsInf::from_cpu(&stream, cpu_weights, input_dim)?;
let identity_proj = cpu_weights.input_proj_w.is_empty();
Ok(Self {
kernels,
weights,
stream,
blas: Arc::new(blas),
cfg,
batch,
input_dim,
identity_proj,
graph: None,
captured_state_ptr: 0,
captured_scratch_ptr: 0,
})
}
pub fn capture_graph(
&mut self,
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceScratch,
) -> Result<(), String> {
let snap_state = state.ssm_state.cached_ptr();
let snap_scratch = scratch.gpu_input.cached_ptr();
let stream = self.stream.clone();
let graph = crate::mamba_ssm::gpu::graph_capture::capture_into_graph(&stream, || {
self.step_kernels(state, scratch)
})?;
graph.upload().map_err(|e| format!("graph upload: {e:?}"))?;
self.graph = Some(graph);
self.captured_state_ptr = snap_state;
self.captured_scratch_ptr = snap_scratch;
Ok(())
}
pub fn has_graph(&self) -> bool {
self.graph.is_some()
}
pub fn alloc_state(&self) -> Result<Mamba3GpuInferenceState, String> {
Mamba3GpuInferenceState::zeros(&self.stream, self.batch, &self.cfg)
}
pub fn alloc_scratch(&self) -> Result<Mamba3GpuInferenceScratch, String> {
Mamba3GpuInferenceScratch::zeros(&self.stream, self.batch, &self.cfg, self.input_dim)
}
pub fn config(&self) -> &Mamba3Config {
&self.cfg
}
pub fn batch(&self) -> usize {
self.batch
}
fn step_kernels(
&self,
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceScratch,
) -> Result<(), String> {
use cudarc::driver::PushKernelArg;
let b = self.batch;
let dm = self.cfg.d_model;
let b_i = b as i32;
let dm_i = dm as i32;
if self.identity_proj {
debug_assert_eq!(self.input_dim, dm);
scratch
.temporal
.copy_from_raw(&scratch.gpu_input, &self.stream)?;
} else {
sgemm_no_bias(
&self.blas,
&scratch.temporal,
&scratch.gpu_input,
self.weights.input_proj_w.ptr(),
b,
self.input_dim,
dm,
)?;
{
let n = b * dm;
let n_i = n as i32;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.vec_add_inplace);
builder.arg(scratch.temporal.inner());
builder.arg(self.weights.input_proj_b.inner());
builder.arg(&n_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("input_proj bias: {e:?}"))?;
}
}
for layer_idx in 0..self.cfg.n_layers {
self.step_layer_kernels(layer_idx, state, scratch)?;
}
{
let bytes = b * dm * std::mem::size_of::<f32>();
unsafe {
cudarc::driver::sys::cuMemcpyDtoDAsync_v2(
scratch.post_norm.cached_ptr(),
scratch.temporal.cached_ptr(),
bytes,
self.stream.cu_stream(),
);
}
}
{
let grid = crate::mamba_ssm::gpu::launch::grid_norm(b, dm);
let eps: f32 = 1e-5;
let mut builder = self.stream.launch_builder(&self.kernels.rmsnorm_fwd);
builder.arg(scratch.temporal.inner());
builder.arg(scratch.rms_buf.inner());
builder.arg(scratch.post_norm.inner());
builder.arg(self.weights.norm_f_weight.inner());
builder.arg(&b_i);
builder.arg(&dm_i);
builder.arg(&eps);
unsafe { builder.launch(grid) }.map_err(|e| format!("norm_f: {e:?}"))?;
}
Ok(())
}
pub fn step_layer_kernels(
&self,
layer_idx: usize,
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceScratch,
) -> Result<(), String> {
use cudarc::driver::PushKernelArg;
let b = self.batch;
let dm = self.cfg.d_model;
let di = self.cfg.d_inner();
let ds = self.cfg.d_state;
let nh = self.cfg.nheads();
let hd = self.cfg.headdim;
let ng = self.cfg.ngroups;
let ip = self.cfg.in_proj_out_dim();
let na = self.cfg.num_rope_angles();
let a_floor = self.cfg.a_floor;
let lw = &self.weights.layers[layer_idx];
let b_i = b as i32;
let dm_i = dm as i32;
let di_i = di as i32;
let ds_i = ds as i32;
let nh_i = nh as i32;
let hd_i = hd as i32;
let ng_i = ng as i32;
let na_i = na as i32;
let ssm_off = layer_idx * state.ssm_per_layer();
let k_off = layer_idx * state.k_per_layer();
let v_off = layer_idx * state.v_per_layer();
let a_off = layer_idx * state.angle_per_layer();
{
let src = scratch.temporal.cached_ptr();
let dst = scratch.residual.cached_ptr();
let bytes = b * dm * std::mem::size_of::<f32>();
let result = unsafe {
cudarc::driver::sys::cuMemcpyDtoDAsync_v2(dst, src, bytes, self.stream.cu_stream())
};
if result != cudarc::driver::sys::CUresult::CUDA_SUCCESS {
return Err(format!("D2D copy residual: {:?}", result));
}
}
{
let grid = crate::mamba_ssm::gpu::launch::grid_norm(b, dm);
let eps: f32 = 1e-5;
let nw_ptr = lw.norm_weight.ptr();
let mut builder = self.stream.launch_builder(&self.kernels.rmsnorm_fwd);
builder.arg(scratch.post_norm.inner());
builder.arg(scratch.rms_buf.inner());
builder.arg(scratch.residual.inner());
builder.arg(&nw_ptr);
builder.arg(&b_i);
builder.arg(&dm_i);
builder.arg(&eps);
unsafe { builder.launch(grid) }.map_err(|e| format!("F1 rmsnorm: {e:?}"))?;
}
sgemm_no_bias(
&self.blas,
&scratch.proj,
&scratch.post_norm,
lw.in_proj_w.ptr(),
b,
dm,
ip,
)?;
{
let n = b * ip;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.m3_split);
builder.arg(scratch.z.inner());
builder.arg(scratch.x.inner());
builder.arg(scratch.b_raw.inner());
builder.arg(scratch.c_raw.inner());
builder.arg(scratch.dt.inner());
builder.arg(scratch.a_val.inner());
builder.arg(scratch.trap.inner());
builder.arg(scratch.angles_raw.inner());
builder.arg(scratch.dd_dt_raw.inner());
builder.arg(scratch.dd_a_raw.inner());
builder.arg(scratch.trap_raw.inner());
builder.arg(scratch.proj.inner());
builder.arg(lw.dt_bias.inner());
builder.arg(&a_floor);
builder.arg(&b_i);
builder.arg(&di_i);
builder.arg(&ng_i);
builder.arg(&ds_i);
builder.arg(&nh_i);
builder.arg(&na_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F3 m3_split: {e:?}"))?;
}
{
let grid = cudarc::driver::LaunchConfig {
grid_dim: ((b * ng) as u32, 1, 1),
block_dim: (ds as u32, 1, 1),
shared_mem_bytes: (ds * 4) as u32,
};
let mut builder = self.stream.launch_builder(&self.kernels.bcnorm_fwd);
builder.arg(scratch.b_normed.inner());
builder.arg(scratch.b_rms.inner());
builder.arg(scratch.b_raw.inner());
builder.arg(lw.b_norm_weight.inner());
builder.arg(&b_i);
builder.arg(&ng_i);
builder.arg(&ds_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F4a bcnorm B: {e:?}"))?;
}
{
let grid = cudarc::driver::LaunchConfig {
grid_dim: ((b * ng) as u32, 1, 1),
block_dim: (ds as u32, 1, 1),
shared_mem_bytes: (ds * 4) as u32,
};
let mut builder = self.stream.launch_builder(&self.kernels.bcnorm_fwd);
builder.arg(scratch.c_normed.inner());
builder.arg(scratch.c_rms.inner());
builder.arg(scratch.c_raw.inner());
builder.arg(lw.c_norm_weight.inner());
builder.arg(&b_i);
builder.arg(&ng_i);
builder.arg(&ds_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F4a bcnorm C: {e:?}"))?;
}
{
let n = b * nh * ds;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.bc_bias_add);
builder.arg(scratch.b_biased.inner());
builder.arg(scratch.b_normed.inner());
builder.arg(lw.b_bias.inner());
builder.arg(&b_i);
builder.arg(&nh_i);
builder.arg(&ng_i);
builder.arg(&ds_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F4b bias B: {e:?}"))?;
}
{
let n = b * nh * ds;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.bc_bias_add);
builder.arg(scratch.c_biased.inner());
builder.arg(scratch.c_normed.inner());
builder.arg(lw.c_bias.inner());
builder.arg(&b_i);
builder.arg(&nh_i);
builder.arg(&ng_i);
builder.arg(&ds_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F4b bias C: {e:?}"))?;
}
if na > 0 {
let grid = cudarc::driver::LaunchConfig {
grid_dim: (b as u32, ((nh * na).div_ceil(256)) as u32, 1),
block_dim: (256.min((nh * na) as u32), 1, 1),
shared_mem_bytes: 0,
};
let a_ptr = state.angle_state.inner_at(a_off);
let mut builder = self
.stream
.launch_builder(&self.kernels.m3_angle_dt_fwd_batch);
builder.arg(scratch.angle_cumsum.inner());
builder.arg(&a_ptr);
builder.arg(scratch.angles_raw.inner());
builder.arg(scratch.dt.inner());
builder.arg(&b_i);
builder.arg(&nh_i);
builder.arg(&na_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F4c angle_dt: {e:?}"))?;
let n = b * nh * ds;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.rope_fwd);
builder.arg(scratch.k_cur.inner());
builder.arg(scratch.q_cur.inner());
builder.arg(scratch.b_biased.inner());
builder.arg(scratch.c_biased.inner());
builder.arg(scratch.angle_cumsum.inner());
builder.arg(&b_i);
builder.arg(&nh_i);
builder.arg(&ds_i);
builder.arg(&na_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F4c rope: {e:?}"))?;
} else {
{
let bytes = b * nh * ds * std::mem::size_of::<f32>();
unsafe {
cudarc::driver::sys::cuMemcpyDtoDAsync_v2(
scratch.k_cur.cached_ptr(),
scratch.b_biased.cached_ptr(),
bytes,
self.stream.cu_stream(),
);
cudarc::driver::sys::cuMemcpyDtoDAsync_v2(
scratch.q_cur.cached_ptr(),
scratch.c_biased.cached_ptr(),
bytes,
self.stream.cu_stream(),
);
}
}
}
{
let n = b * nh;
let n_i = n as i32;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.m3_compute_abg);
builder.arg(scratch.alpha.inner());
builder.arg(scratch.beta.inner());
builder.arg(scratch.gamma.inner());
builder.arg(scratch.dt.inner());
builder.arg(scratch.a_val.inner());
builder.arg(scratch.trap.inner());
builder.arg(&n_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F5 compute_abg: {e:?}"))?;
}
{
let grid = cudarc::driver::LaunchConfig {
grid_dim: (b as u32, nh as u32, 1),
block_dim: (hd as u32, 1, 1),
shared_mem_bytes: 0,
};
let ssm_ptr = state.ssm_state.inner_at(ssm_off);
let k_ptr = state.k_state.inner_at(k_off);
let v_ptr = state.v_state.inner_at(v_off);
let mut builder = self.stream.launch_builder(&self.kernels.m3_step_fwd);
builder.arg(&ssm_ptr);
builder.arg(&k_ptr);
builder.arg(&v_ptr);
builder.arg(scratch.y.inner());
builder.arg(scratch.x.inner());
builder.arg(scratch.k_cur.inner());
builder.arg(scratch.q_cur.inner());
builder.arg(scratch.alpha.inner());
builder.arg(scratch.beta.inner());
builder.arg(scratch.gamma.inner());
builder.arg(lw.d_param.inner());
builder.arg(&b_i);
builder.arg(&nh_i);
builder.arg(&hd_i);
builder.arg(&ds_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F6 m3_step_fwd: {e:?}"))?;
}
if self.cfg.is_outproj_norm {
let grid = crate::mamba_ssm::gpu::launch::grid_norm(b, di);
let mut builder = self.stream.launch_builder(&self.kernels.rmsnorm_gated_fwd);
builder.arg(scratch.gated.inner());
builder.arg(scratch.gated_rms_buf.inner()); builder.arg(scratch.y.inner());
builder.arg(scratch.z.inner());
builder.arg(lw.norm_gate_weight.inner());
builder.arg(&b_i);
builder.arg(&di_i);
builder.arg(&hd_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F7 rmsnorm_gated: {e:?}"))?;
} else {
let n = b * di;
let n_i = n as i32;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.silu_gate_fwd);
builder.arg(scratch.gated.inner());
builder.arg(scratch.y.inner());
builder.arg(scratch.z.inner());
builder.arg(&n_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F7 silu_gate: {e:?}"))?;
}
sgemm_no_bias(
&self.blas,
&scratch.temporal,
&scratch.gated,
lw.out_proj_w.ptr(),
b,
di,
dm,
)?;
{
let n = b * dm;
let n_i = n as i32;
let grid = crate::mamba_ssm::gpu::launch::grid_1d(n);
let mut builder = self.stream.launch_builder(&self.kernels.vec_add_inplace);
builder.arg(scratch.temporal.inner());
builder.arg(scratch.residual.inner());
builder.arg(&n_i);
unsafe { builder.launch(grid) }.map_err(|e| format!("F9 residual: {e:?}"))?;
}
Ok(())
}
pub fn step(
&self,
input: &[f32],
output: &mut [f32],
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceScratch,
) -> Result<(), String> {
scratch.gpu_input.upload(&self.stream, input)?;
if let Some(ref g) = self.graph {
assert_eq!(
state.ssm_state.cached_ptr(),
self.captured_state_ptr,
"CUDA Graph replay requires the same state buffers used during capture"
);
assert_eq!(
scratch.gpu_input.cached_ptr(),
self.captured_scratch_ptr,
"CUDA Graph replay requires the same scratch buffers used during capture"
);
g.launch().map_err(|e| format!("graph launch: {e:?}"))?;
} else {
self.step_kernels(state, scratch)?;
}
self.stream
.synchronize()
.map_err(|e| format!("sync: {e:?}"))?;
let cpu_out = scratch.temporal.to_cpu(&self.stream)?;
output[..cpu_out.len()].copy_from_slice(&cpu_out);
Ok(())
}
pub fn step_gpu_only(
&self,
input: &[f32],
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceScratch,
) -> Result<(), String> {
scratch.gpu_input.upload(&self.stream, input)?;
if let Some(ref g) = self.graph {
assert_eq!(state.ssm_state.cached_ptr(), self.captured_state_ptr);
assert_eq!(scratch.gpu_input.cached_ptr(), self.captured_scratch_ptr);
g.launch()
.map_err(|e| format!("graph launch (step_gpu_only): {e:?}"))?;
} else {
self.step_kernels(state, scratch)?;
}
Ok(())
}
}
use crate::mamba_ssm::gpu::blas::{TypedPtr, gpu_gemm_typed_raw_no_bias};
use crate::mamba3_siso::gpu::weights::GpuMamba3MixedWeights;
pub struct Mamba3GpuInferenceMixed {
engine: Mamba3GpuInferenceEngine, mixed_weights: GpuMamba3MixedWeights,
graph: Option<cudarc::driver::CudaGraph>,
captured_state_ptr: u64,
captured_scratch_ptr: u64,
}
impl Mamba3GpuInferenceMixed {
pub fn new(
device: &GpuDevice,
cpu_weights: &Mamba3Weights,
cfg: Mamba3Config,
input_dim: usize,
batch: usize,
bulk_dtype: WeightDtype,
) -> Result<Self, String> {
cfg.validate()?;
let engine = Mamba3GpuInferenceEngine::new(device, cpu_weights, cfg, input_dim, batch)?;
let mixed_weights =
GpuMamba3MixedWeights::from_cpu(&engine.stream, cpu_weights, bulk_dtype)?;
Ok(Self {
engine,
mixed_weights,
graph: None,
captured_state_ptr: 0,
captured_scratch_ptr: 0,
})
}
pub fn alloc_state(&self) -> Result<Mamba3GpuInferenceState, String> {
self.engine.alloc_state()
}
pub fn alloc_mixed_scratch(&self) -> Result<Mamba3GpuInferenceMixedScratch, String> {
Mamba3GpuInferenceMixedScratch::zeros(
&self.engine.stream,
self.engine.batch,
&self.engine.cfg,
self.engine.input_dim,
self.mixed_weights.bulk_dtype,
)
}
pub fn ctx_stream(&self) -> &Stream {
&self.engine.stream
}
pub fn bulk_dtype(&self) -> WeightDtype {
self.mixed_weights.bulk_dtype
}
pub fn engine_ref(&self) -> &Mamba3GpuInferenceEngine {
&self.engine
}
pub fn has_graph(&self) -> bool {
self.graph.is_some()
}
pub(super) fn step_kernels_mixed_native(
&self,
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceMixedScratch,
) -> Result<(), String> {
use crate::mamba_ssm::gpu::launch::grid_1d;
use cudarc::driver::PushKernelArg;
let engine = &self.engine;
assert!(
engine.identity_proj,
"Mamba3 step_kernels_mixed_native requires identity_proj=true (LLM path)"
);
assert_eq!(
scratch.dtype, self.mixed_weights.bulk_dtype,
"Mamba3 mixed scratch dtype must match mixed weights bulk_dtype"
);
let dt = scratch.dtype;
let b = engine.batch;
let cfg = &engine.cfg;
let dm = cfg.d_model;
let di = cfg.d_inner();
let ds = cfg.d_state;
let nh = cfg.nheads();
let hd = cfg.headdim;
let ng = cfg.ngroups;
let ip = cfg.in_proj_out_dim();
let na = cfg.num_rope_angles();
let a_floor = cfg.a_floor;
let k = &engine.kernels;
let w = &self.mixed_weights;
let b_i = b as i32;
let dm_i = dm as i32;
let di_i = di as i32;
let ds_i = ds as i32;
let nh_i = nh as i32;
let hd_i = hd as i32;
let ng_i = ng as i32;
let na_i = na as i32;
scratch
.residual
.copy_from_raw(&scratch.gpu_input, &engine.stream)?;
let f32_sz = std::mem::size_of::<f32>() as u64;
for layer_idx in 0..w.layers.len() {
let lw = &w.layers[layer_idx];
let ssm_off = layer_idx * state.ssm_per_layer();
let k_off = layer_idx * state.k_per_layer();
let v_off = layer_idx * state.v_per_layer();
let a_off = layer_idx * state.angle_per_layer();
let _ = f32_sz;
{
let eps: f32 = 1e-5;
let grid = crate::mamba_ssm::gpu::launch::grid_norm(b, dm);
let mut bld = engine
.stream
.launch_builder(k.rmsnorm_fwd_f32in_typed.get(dt));
let pn_ptr = scratch.post_norm.cached_ptr();
let rms_ptr = scratch.rms_buf.cached_ptr();
let res_ptr = scratch.residual.cached_ptr();
let nw = lw.norm_weight.ptr();
bld.arg(&pn_ptr);
bld.arg(&rms_ptr);
bld.arg(&res_ptr);
bld.arg(&nw);
bld.arg(&b_i);
bld.arg(&dm_i);
bld.arg(&eps);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F1 rmsnorm: {e:?}"))?;
}
gpu_gemm_typed_raw_no_bias(
&engine.blas,
TypedPtr {
ptr: scratch.proj.cached_ptr(),
dtype: dt,
},
TypedPtr {
ptr: scratch.post_norm.cached_ptr(),
dtype: dt,
},
TypedPtr {
ptr: lw.in_proj_w.ptr(),
dtype: lw.in_proj_w.dtype(),
},
(b, dm, ip),
)?;
{
let n = b * ip;
let grid = grid_1d(n);
let mut bld = engine.stream.launch_builder(k.m3_split_typed.get(dt));
let z_ptr = scratch.z.cached_ptr();
let x_ptr = scratch.x.cached_ptr();
let br_ptr = scratch.b_raw.cached_ptr();
let cr_ptr = scratch.c_raw.cached_ptr();
let dt_ptr = scratch.dt.cached_ptr();
let av_ptr = scratch.a_val.cached_ptr();
let tp_ptr = scratch.trap.cached_ptr();
let ang_ptr = scratch.angles_raw.cached_ptr();
let dd_dt_ptr = scratch.dd_dt_raw.cached_ptr();
let dd_a_ptr = scratch.dd_a_raw.cached_ptr();
let tr_ptr = scratch.trap_raw.cached_ptr();
let proj_ptr = scratch.proj.cached_ptr();
let dtb_ptr = lw.dt_bias.ptr();
bld.arg(&z_ptr);
bld.arg(&x_ptr);
bld.arg(&br_ptr);
bld.arg(&cr_ptr);
bld.arg(&dt_ptr);
bld.arg(&av_ptr);
bld.arg(&tp_ptr);
bld.arg(&ang_ptr);
bld.arg(&dd_dt_ptr);
bld.arg(&dd_a_ptr);
bld.arg(&tr_ptr);
bld.arg(&proj_ptr);
bld.arg(&dtb_ptr);
bld.arg(&a_floor);
bld.arg(&b_i);
bld.arg(&di_i);
bld.arg(&ng_i);
bld.arg(&ds_i);
bld.arg(&nh_i);
bld.arg(&na_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F3 split: {e:?}"))?;
}
{
let grid = cudarc::driver::LaunchConfig {
grid_dim: ((b * ng) as u32, 2, 1),
block_dim: (ds as u32, 1, 1),
shared_mem_bytes: (ds * 4) as u32,
};
let bn_ptr = scratch.b_normed.cached_ptr();
let cn_ptr = scratch.c_normed.cached_ptr();
let br_ptr = scratch.b_rms.cached_ptr();
let cr_ptr = scratch.c_rms.cached_ptr();
let bs_ptr = scratch.b_raw.cached_ptr();
let cs_ptr = scratch.c_raw.cached_ptr();
let bw_ptr = lw.b_norm_weight.ptr();
let cw_ptr = lw.c_norm_weight.ptr();
let mut bld = engine.stream.launch_builder(k.bcnorm_fwd_bc_typed.get(dt));
bld.arg(&bn_ptr);
bld.arg(&cn_ptr);
bld.arg(&br_ptr);
bld.arg(&cr_ptr);
bld.arg(&bs_ptr);
bld.arg(&cs_ptr);
bld.arg(&bw_ptr);
bld.arg(&cw_ptr);
bld.arg(&b_i);
bld.arg(&ng_i);
bld.arg(&ds_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F4a bcnorm B+C: {e:?}"))?;
}
{
let n = b * nh * ds;
let grid_1 = grid_1d(n);
let grid = cudarc::driver::LaunchConfig {
grid_dim: (grid_1.grid_dim.0, 2, 1),
block_dim: grid_1.block_dim,
shared_mem_bytes: grid_1.shared_mem_bytes,
};
let bb_ptr = scratch.b_biased.cached_ptr();
let cb_ptr = scratch.c_biased.cached_ptr();
let bn_ptr = scratch.b_normed.cached_ptr();
let cn_ptr = scratch.c_normed.cached_ptr();
let bbi_ptr = lw.b_bias.ptr();
let cbi_ptr = lw.c_bias.ptr();
let mut bld = engine.stream.launch_builder(k.bc_bias_add_bc_typed.get(dt));
bld.arg(&bb_ptr);
bld.arg(&cb_ptr);
bld.arg(&bn_ptr);
bld.arg(&cn_ptr);
bld.arg(&bbi_ptr);
bld.arg(&cbi_ptr);
bld.arg(&b_i);
bld.arg(&nh_i);
bld.arg(&ng_i);
bld.arg(&ds_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F4b bias B+C: {e:?}"))?;
}
if na > 0 {
let grid = cudarc::driver::LaunchConfig {
grid_dim: (b as u32, ((nh * na).div_ceil(256)) as u32, 1),
block_dim: (256.min((nh * na) as u32), 1, 1),
shared_mem_bytes: 0,
};
let a_ptr = state.angle_state.inner_at(a_off);
let ac_ptr = scratch.angle_cumsum.cached_ptr();
let ar_ptr = scratch.angles_raw.cached_ptr();
let dt_ptr = scratch.dt.cached_ptr();
let mut bld = engine.stream.launch_builder(&k.m3_angle_dt_fwd_batch);
bld.arg(&ac_ptr);
bld.arg(&a_ptr);
bld.arg(&ar_ptr);
bld.arg(&dt_ptr);
bld.arg(&b_i);
bld.arg(&nh_i);
bld.arg(&na_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F4c angle_dt: {e:?}"))?;
let n = b * nh * ds;
let grid = grid_1d(n);
let mut bld = engine.stream.launch_builder(k.rope_fwd_typed.get(dt));
let kc_ptr = scratch.k_cur.cached_ptr();
let qc_ptr = scratch.q_cur.cached_ptr();
let bb_ptr = scratch.b_biased.cached_ptr();
let cb_ptr = scratch.c_biased.cached_ptr();
let ac_ptr = scratch.angle_cumsum.cached_ptr();
bld.arg(&kc_ptr);
bld.arg(&qc_ptr);
bld.arg(&bb_ptr);
bld.arg(&cb_ptr);
bld.arg(&ac_ptr);
bld.arg(&b_i);
bld.arg(&nh_i);
bld.arg(&ds_i);
bld.arg(&na_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F4c rope: {e:?}"))?;
} else {
let bytes = b * nh * ds * dt.size_bytes();
unsafe {
cudarc::driver::sys::cuMemcpyDtoDAsync_v2(
scratch.k_cur.cached_ptr(),
scratch.b_biased.cached_ptr(),
bytes,
engine.stream.cu_stream(),
);
cudarc::driver::sys::cuMemcpyDtoDAsync_v2(
scratch.q_cur.cached_ptr(),
scratch.c_biased.cached_ptr(),
bytes,
engine.stream.cu_stream(),
);
}
}
{
let n = b * nh;
let n_i = n as i32;
let grid = grid_1d(n);
let mut bld = engine.stream.launch_builder(&k.m3_compute_abg);
bld.arg(scratch.alpha.inner());
bld.arg(scratch.beta.inner());
bld.arg(scratch.gamma.inner());
bld.arg(scratch.dt.inner());
bld.arg(scratch.a_val.inner());
bld.arg(scratch.trap.inner());
bld.arg(&n_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F5 compute_abg: {e:?}"))?;
}
{
let grid = cudarc::driver::LaunchConfig {
grid_dim: (b as u32, nh as u32, 1),
block_dim: (hd as u32, 1, 1),
shared_mem_bytes: 0,
};
let ssm_ptr = state.ssm_state.inner_at(ssm_off);
let kst_ptr = state.k_state.inner_at(k_off);
let vst_ptr = state.v_state.inner_at(v_off);
let mut bld = engine.stream.launch_builder(k.m3_step_fwd_typed.get(dt));
let y_ptr = scratch.y.cached_ptr();
let x_ptr = scratch.x.cached_ptr();
let kc_ptr = scratch.k_cur.cached_ptr();
let qc_ptr = scratch.q_cur.cached_ptr();
bld.arg(&ssm_ptr);
bld.arg(&kst_ptr);
bld.arg(&vst_ptr);
bld.arg(&y_ptr);
bld.arg(&x_ptr);
bld.arg(&kc_ptr);
bld.arg(&qc_ptr);
let alpha_ptr = scratch.alpha.cached_ptr();
let beta_ptr = scratch.beta.cached_ptr();
let gamma_ptr = scratch.gamma.cached_ptr();
let dp_ptr = lw.d_param.ptr();
bld.arg(&alpha_ptr);
bld.arg(&beta_ptr);
bld.arg(&gamma_ptr);
bld.arg(&dp_ptr);
bld.arg(&b_i);
bld.arg(&nh_i);
bld.arg(&hd_i);
bld.arg(&ds_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F6 step_fwd: {e:?}"))?;
}
if cfg.is_outproj_norm {
let grid = crate::mamba_ssm::gpu::launch::grid_norm(b, di);
let mut bld = engine
.stream
.launch_builder(k.rmsnorm_gated_fwd_typed.get(dt));
let gated_ptr = scratch.gated.cached_ptr();
let gr_ptr = scratch.gated_rms_buf.cached_ptr();
let y_ptr = scratch.y.cached_ptr();
let z_ptr = scratch.z.cached_ptr();
let nw_ptr = lw.norm_gate_weight.ptr();
bld.arg(&gated_ptr);
bld.arg(&gr_ptr);
bld.arg(&y_ptr);
bld.arg(&z_ptr);
bld.arg(&nw_ptr);
bld.arg(&b_i);
bld.arg(&di_i);
bld.arg(&hd_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F7 rmsnorm_gated: {e:?}"))?;
} else {
let n = b * di;
let n_i = n as i32;
let grid = grid_1d(n);
let mut bld = engine.stream.launch_builder(k.silu_gate_fwd_typed.get(dt));
let gated_ptr = scratch.gated.cached_ptr();
let y_ptr = scratch.y.cached_ptr();
let z_ptr = scratch.z.cached_ptr();
bld.arg(&gated_ptr);
bld.arg(&y_ptr);
bld.arg(&z_ptr);
bld.arg(&n_i);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 F7 silu_gate: {e:?}"))?;
}
gpu_gemm_typed_raw_no_bias(
&engine.blas,
TypedPtr {
ptr: scratch.temporal.cached_ptr(),
dtype: dt,
},
TypedPtr {
ptr: scratch.gated.cached_ptr(),
dtype: dt,
},
TypedPtr {
ptr: lw.out_proj_w.ptr(),
dtype: lw.out_proj_w.dtype(),
},
(b, di, dm),
)?;
{
let n = (b * dm) as i32;
let grid = grid_1d(b * dm);
let mut bld = engine
.stream
.launch_builder(k.residual_add_f32_typed.get(dt));
let r_ptr = scratch.residual.cached_ptr();
let t_ptr = scratch.temporal.cached_ptr();
bld.arg(&r_ptr);
bld.arg(&r_ptr);
bld.arg(&t_ptr);
bld.arg(&n);
unsafe { bld.launch(grid) }
.map_err(|e| format!("M3 F9 residual_add_f32: {e:?}"))?;
}
}
{
let grid = crate::mamba_ssm::gpu::launch::grid_norm(b, dm);
let eps: f32 = 1e-5;
let mut bld = engine
.stream
.launch_builder(k.rmsnorm_fwd_f32in_typed.get(dt));
let t_ptr = scratch.temporal.cached_ptr();
let rms_ptr = scratch.rms_buf.cached_ptr();
let res_ptr = scratch.residual.cached_ptr();
let nfw = w.norm_f_weight.ptr();
bld.arg(&t_ptr);
bld.arg(&rms_ptr);
bld.arg(&res_ptr);
bld.arg(&nfw);
bld.arg(&b_i);
bld.arg(&dm_i);
bld.arg(&eps);
unsafe { bld.launch(grid) }.map_err(|e| format!("M3 norm_f: {e:?}"))?;
}
Ok(())
}
pub fn step_mixed_native(
&self,
input: &[f32],
output: &mut [f32],
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceMixedScratch,
) -> Result<(), String> {
scratch.gpu_input.upload(&self.engine.stream, input)?;
if let Some(ref g) = self.graph {
assert_eq!(state.ssm_state.cached_ptr(), self.captured_state_ptr);
assert_eq!(scratch.gpu_input.cached_ptr(), self.captured_scratch_ptr);
g.launch()
.map_err(|e| format!("M3 graph launch mixed: {e:?}"))?;
} else {
self.step_kernels_mixed_native(state, scratch)?;
}
self.engine
.stream
.synchronize()
.map_err(|e| format!("M3 sync: {e:?}"))?;
scratch.temporal.download_f32(&self.engine.stream, output)?;
Ok(())
}
pub fn step_gpu_only_mixed_native(
&self,
input: &[f32],
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceMixedScratch,
) -> Result<(), String> {
scratch.gpu_input.upload(&self.engine.stream, input)?;
if let Some(ref g) = self.graph {
assert_eq!(state.ssm_state.cached_ptr(), self.captured_state_ptr);
assert_eq!(scratch.gpu_input.cached_ptr(), self.captured_scratch_ptr);
g.launch()
.map_err(|e| format!("M3 graph launch mixed: {e:?}"))?;
Ok(())
} else {
self.step_kernels_mixed_native(state, scratch)
}
}
pub fn capture_graph_mixed_native(
&mut self,
state: &mut Mamba3GpuInferenceState,
scratch: &mut Mamba3GpuInferenceMixedScratch,
) -> Result<(), String> {
let snap_state = state.ssm_state.cached_ptr();
let snap_scratch = scratch.gpu_input.cached_ptr();
let stream = self.engine.stream.clone();
let graph = crate::mamba_ssm::gpu::graph_capture::capture_into_graph(&stream, || {
self.step_kernels_mixed_native(state, scratch)
})?;
self.graph = Some(graph);
self.captured_state_ptr = snap_state;
self.captured_scratch_ptr = snap_scratch;
Ok(())
}
}
enum M3BackboneEngine {
F32(Box<Mamba3GpuInferenceEngine>),
Mixed(Box<Mamba3GpuInferenceMixed>),
}
enum M3BackboneScratch {
F32(Box<Mamba3GpuInferenceScratch>),
Mixed(Box<Mamba3GpuInferenceMixedScratch>),
}
impl M3BackboneScratch {
fn temporal_ptr(&self) -> cudarc::driver::sys::CUdeviceptr {
match self {
M3BackboneScratch::F32(s) => s.temporal.cached_ptr(),
M3BackboneScratch::Mixed(s) => s.temporal.cached_ptr(),
}
}
fn temporal_dtype(&self) -> WeightDtype {
match self {
M3BackboneScratch::F32(_) => WeightDtype::F32,
M3BackboneScratch::Mixed(s) => s.dtype,
}
}
}
pub struct GpuMamba3Backbone {
engine: M3BackboneEngine,
state: Mamba3GpuInferenceState,
scratch: M3BackboneScratch,
}
impl GpuMamba3Backbone {
pub fn new(
gpu_ordinal: usize,
cpu_weights: &Mamba3Weights,
cfg: Mamba3Config,
input_dim: usize,
batch: usize,
) -> Result<Self, String> {
Self::new_with_dtype(
gpu_ordinal,
cpu_weights,
cfg,
input_dim,
batch,
WeightDtype::F32,
)
}
pub fn new_with_dtype(
gpu_ordinal: usize,
cpu_weights: &Mamba3Weights,
cfg: Mamba3Config,
input_dim: usize,
batch: usize,
dtype: WeightDtype,
) -> Result<Self, String> {
let device = GpuDevice::new(gpu_ordinal)?;
let (engine, state, scratch) = match dtype {
WeightDtype::F32 => {
let e = Mamba3GpuInferenceEngine::new(&device, cpu_weights, cfg, input_dim, batch)?;
let s = e.alloc_state()?;
let sc = M3BackboneScratch::F32(Box::new(e.alloc_scratch()?));
(M3BackboneEngine::F32(Box::new(e)), s, sc)
}
WeightDtype::Bf16 | WeightDtype::F16 => {
let e = Mamba3GpuInferenceMixed::new(
&device,
cpu_weights,
cfg,
input_dim,
batch,
dtype,
)?;
let s = e.alloc_state()?;
let sc = M3BackboneScratch::Mixed(Box::new(e.alloc_mixed_scratch()?));
(M3BackboneEngine::Mixed(Box::new(e)), s, sc)
}
};
Ok(Self {
engine,
state,
scratch,
})
}
pub fn dtype(&self) -> WeightDtype {
match &self.engine {
M3BackboneEngine::F32(_) => WeightDtype::F32,
M3BackboneEngine::Mixed(e) => e.bulk_dtype(),
}
}
pub fn temporal_ptr(&self) -> cudarc::driver::sys::CUdeviceptr {
self.scratch.temporal_ptr()
}
pub fn temporal_dtype(&self) -> WeightDtype {
self.scratch.temporal_dtype()
}
pub fn step(&mut self, input: &[f32], output: &mut [f32]) -> Result<(), String> {
match (&self.engine, &mut self.scratch) {
(M3BackboneEngine::F32(e), M3BackboneScratch::F32(sc)) => {
e.step(input, output, &mut self.state, sc)
}
(M3BackboneEngine::Mixed(e), M3BackboneScratch::Mixed(sc)) => {
e.step_mixed_native(input, output, &mut self.state, sc)
}
_ => Err("M3 engine/scratch dtype mismatch (internal invariant)".to_string()),
}
}
pub fn step_gpu_only(&mut self, input: &[f32]) -> Result<(), String> {
match (&self.engine, &mut self.scratch) {
(M3BackboneEngine::F32(e), M3BackboneScratch::F32(sc)) => {
e.step_gpu_only(input, &mut self.state, sc)
}
(M3BackboneEngine::Mixed(e), M3BackboneScratch::Mixed(sc)) => {
e.step_gpu_only_mixed_native(input, &mut self.state, sc)
}
_ => Err("M3 engine/scratch dtype mismatch".to_string()),
}
}
pub fn reset(&mut self) -> Result<(), String> {
let stream = match &self.engine {
M3BackboneEngine::F32(e) => e.stream.clone(),
M3BackboneEngine::Mixed(e) => e.ctx_stream().clone(),
};
self.state.reset(&stream)
}
pub fn capture_graph(&mut self) -> Result<(), String> {
let (input_dim, batch, d_model) = match &self.engine {
M3BackboneEngine::F32(e) => (e.input_dim, e.batch, e.cfg.d_model),
M3BackboneEngine::Mixed(e) => {
let er = e.engine_ref();
(er.input_dim, er.batch, er.cfg.d_model)
}
};
let input = vec![0.0f32; batch * input_dim];
let mut output = vec![0.0f32; batch * d_model];
self.step(&input, &mut output)?;
self.reset()?;
match (&mut self.engine, &mut self.scratch) {
(M3BackboneEngine::F32(e), M3BackboneScratch::F32(sc)) => {
e.capture_graph(&mut self.state, sc)
}
(M3BackboneEngine::Mixed(e), M3BackboneScratch::Mixed(sc)) => {
e.capture_graph_mixed_native(&mut self.state, sc)
}
_ => Err("M3 engine/scratch dtype mismatch".to_string()),
}
}
pub fn config(&self) -> &Mamba3Config {
match &self.engine {
M3BackboneEngine::F32(e) => &e.cfg,
M3BackboneEngine::Mixed(e) => &e.engine_ref().cfg,
}
}
pub fn batch(&self) -> usize {
match &self.engine {
M3BackboneEngine::F32(e) => e.batch,
M3BackboneEngine::Mixed(e) => e.engine_ref().batch,
}
}
pub fn has_graph(&self) -> bool {
match &self.engine {
M3BackboneEngine::F32(e) => e.has_graph(),
M3BackboneEngine::Mixed(e) => e.has_graph(),
}
}
pub fn stream(&self) -> &Stream {
match &self.engine {
M3BackboneEngine::F32(e) => &e.stream,
M3BackboneEngine::Mixed(e) => e.ctx_stream(),
}
}
pub fn blas(&self) -> &Arc<cudarc::cublas::CudaBlas> {
match &self.engine {
M3BackboneEngine::F32(e) => &e.blas,
M3BackboneEngine::Mixed(e) => &e.engine_ref().blas,
}
}
pub fn download_temporal(&self, output: &mut [f32]) -> Result<(), String> {
self.stream()
.synchronize()
.map_err(|e| format!("M3 sync: {e:?}"))?;
match &self.scratch {
M3BackboneScratch::F32(s) => {
let tmp = s.temporal.to_cpu(self.stream())?;
output[..tmp.len()].copy_from_slice(&tmp);
Ok(())
}
M3BackboneScratch::Mixed(s) => s.temporal.download_f32(self.stream(), output),
}
}
}