#include #include #include #include #include "torch_binding.h" // Not CHECK: torch/library.h pulls in glog's CHECK and redefining it warns. #define M3_F32(x) TORCH_CHECK((x).is_cuda() && (x).scalar_type() == at::kFloat && (x).is_contiguous(), \ #x " must be a contiguous float32 CUDA tensor") #define STEP_ARGS \ const void* q, const void* k, const void* v, const void* z, const float* adt, \ const float* dt, const float* trap, const void* q_bias, const void* k_bias, \ const float* angles, const void* mimo_v, const void* mimo_o, const void* mimo_z, \ const void* Dvec, const float* angle_in, float* angle_out, float* S_st, \ const float* kprev_in, const float* vprev_in, float* kprev_out, float* vprev_out, \ void* y, int B, int H, int Gqk, int R, int N, int P, int Na, void* stream void mamba3_mimo_step_launch_f32(STEP_ARGS); void mamba3_mimo_step_launch_bf16(STEP_ARGS); static const void* vptr(const c10::optional& t, at::ScalarType st) { if (!t.has_value() || !t->defined()) return nullptr; TORCH_CHECK(t->is_cuda() && t->scalar_type() == st && t->is_contiguous(), "optional tensors must be contiguous CUDA tensors of the input dtype"); return t->data_ptr(); } void mamba3_cumulative_angles_launch( const float* angles, const float* dt, float* out, float* seg_tot, int B, int S, int H, int Na, int nseg, void* stream); int mamba3_cumulative_angles_segments(int B, int S, int H); torch::Tensor mamba3_cumulative_angles(torch::Tensor angles, torch::Tensor dt) { M3_F32(angles); M3_F32(dt); TORCH_CHECK(angles.dim() == 4 && dt.dim() == 3, "angles is (B,S,H,Na), dt is (B,H,S)"); const int B = (int)angles.size(0), S = (int)angles.size(1); const int H = (int)angles.size(2), Na = (int)angles.size(3); TORCH_CHECK(dt.size(0) == B && dt.size(1) == H && dt.size(2) == S, "dt must be (B,H,S) matching angles"); auto out = torch::empty_like(angles); const int nseg = mamba3_cumulative_angles_segments(B, S, H); auto seg_tot = torch::empty({B * H, nseg, Na}, angles.options()); mamba3_cumulative_angles_launch( angles.data_ptr(), dt.data_ptr(), out.data_ptr(), seg_tot.data_ptr(), B, S, H, Na, nseg, c10::cuda::getCurrentCUDAStream().stream()); C10_CUDA_KERNEL_LAUNCH_CHECK(); return out; } // Takes and returns no tensors, so it carries no meta kernel and belongs // outside a compiled region; it exists so a caller, and the dispatch test, can // ask which path a geometry takes without restating the gates. std::vector mamba3_fwd_dispatch( int64_t mimo_rank, int64_t dstate, int64_t headdim, int64_t chunk_size, bool bf16) { TORCH_CHECK(chunk_size > 0, "chunk_size must be positive"); int state = 0, scan = 0, tf32 = 0; mamba3_fwd_dispatch_paths((int)mimo_rank, (int)dstate, (int)headdim, (int)chunk_size, bf16 ? 1 : 0, &state, &scan, &tf32); return {state, scan, tf32}; } torch::Tensor mamba3_mimo_step( torch::Tensor q, torch::Tensor k, torch::Tensor v, c10::optional z, torch::Tensor adt, torch::Tensor dt, torch::Tensor trap, torch::Tensor q_bias, torch::Tensor k_bias, torch::Tensor angles, torch::Tensor mimo_v, torch::Tensor mimo_o, c10::optional mimo_z, c10::optional D, torch::Tensor angle_in, torch::Tensor angle_out, torch::Tensor S_st, torch::Tensor kprev_in, torch::Tensor vprev_in, torch::Tensor kprev_out, torch::Tensor vprev_out) { const auto sdt = q.scalar_type(); TORCH_CHECK(sdt == at::kFloat || sdt == at::kBFloat16, "inputs must be float32 or bfloat16"); for (const auto& t : {q, k, v, q_bias, k_bias, mimo_v, mimo_o}) TORCH_CHECK(t.is_cuda() && t.is_contiguous() && t.scalar_type() == sdt, "all value inputs must be contiguous CUDA tensors of one dtype"); M3_F32(adt); M3_F32(dt); M3_F32(trap); M3_F32(angles); M3_F32(angle_in); M3_F32(angle_out); M3_F32(S_st); M3_F32(kprev_in); M3_F32(vprev_in); M3_F32(kprev_out); M3_F32(vprev_out); const int B = (int)q.size(0), R = (int)q.size(1), Gqk = (int)q.size(2), N = (int)q.size(3); const int H = (int)v.size(1), P = (int)v.size(2), Na = (int)angles.size(2); TORCH_CHECK(N % 2 == 0, "state dimension N must be even"); TORCH_CHECK(H % Gqk == 0, "nheads must be divisible by the query-group count"); TORCH_CHECK(Na <= N / 2, "angle count must not exceed N/2"); TORCH_CHECK(R <= 8, "mimo_rank above 8 is not supported"); TORCH_CHECK(S_st.size(2) == P && S_st.size(3) == N, "state shape does not match inputs"); auto y = torch::empty({B, H, P}, v.options()); auto fn = (sdt == at::kFloat) ? mamba3_mimo_step_launch_f32 : mamba3_mimo_step_launch_bf16; fn(q.data_ptr(), k.data_ptr(), v.data_ptr(), vptr(z, sdt), adt.data_ptr(), dt.data_ptr(), trap.data_ptr(), q_bias.data_ptr(), k_bias.data_ptr(), angles.data_ptr(), mimo_v.data_ptr(), mimo_o.data_ptr(), vptr(mimo_z, sdt), vptr(D, sdt), angle_in.data_ptr(), angle_out.data_ptr(), S_st.data_ptr(), kprev_in.data_ptr(), vprev_in.data_ptr(), kprev_out.data_ptr(), vprev_out.data_ptr(), y.data_ptr(), B, H, Gqk, R, N, P, Na, c10::cuda::getCurrentCUDAStream().stream()); C10_CUDA_KERNEL_LAUNCH_CHECK(); return y; } #define FWD_ARGS \ const void* q, const void* k, const void* v, const void* z, const void* q_bias, \ const void* k_bias, const void* mimo_v, const void* mimo_o, const void* mimo_z, \ const void* Dvec, const void* angles, const float* dt, const float* trap, \ const float* dA_cs, const float* dA_cs_rev, const void* norm_w, float norm_eps, \ int fused_norm, void* kv_ws, void* out, \ int B, int S, int H, int Gqk, int R, int N, int P, int Na, int C, void* stream void mamba3_mimo_fwd_launch_f32(FWD_ARGS); void mamba3_mimo_fwd_launch_bf16(FWD_ARGS); torch::Tensor mamba3_mimo_fwd( torch::Tensor q, torch::Tensor k, torch::Tensor v, c10::optional z, torch::Tensor q_bias, torch::Tensor k_bias, torch::Tensor mimo_v, torch::Tensor mimo_o, c10::optional mimo_z, c10::optional D, torch::Tensor angles, torch::Tensor dt, torch::Tensor trap, torch::Tensor dA_cs, torch::Tensor dA_cs_rev, int64_t chunk_size, c10::optional norm_weight, double norm_eps) { const auto sdt = q.scalar_type(); TORCH_CHECK(sdt == at::kFloat || sdt == at::kBFloat16, "inputs must be float32 or bfloat16"); for (const auto& t : {q, k, v, q_bias, k_bias, mimo_v, mimo_o}) TORCH_CHECK(t.is_cuda() && t.is_contiguous() && t.scalar_type() == sdt, "all value inputs must be contiguous CUDA tensors of one dtype"); // The schedule and the accumulated rotation angles stay float32 in both paths. M3_F32(dt); M3_F32(trap); M3_F32(dA_cs); M3_F32(dA_cs_rev); M3_F32(angles); const int B = (int)q.size(0), S = (int)q.size(1), R = (int)q.size(2); const int Gqk = (int)q.size(3), N = (int)q.size(4); const int H = (int)v.size(2), P = (int)v.size(3), Na = (int)angles.size(3); const int C = (int)chunk_size; TORCH_CHECK(C > 0, "chunk_size must be positive"); const int Nc = (S + C - 1) / C; TORCH_CHECK(N % 2 == 0, "state dimension N must be even"); TORCH_CHECK(H % Gqk == 0, "nheads must be divisible by the query-group count"); TORCH_CHECK(Na <= N / 2, "angle count must not exceed N/2"); // The tensor-core scan is instantiated for a fixed set of ranks; above it the // dispatch has no kernel to fall back to that carries the rank at runtime. TORCH_CHECK(R <= 8, "mimo_rank above 8 is not supported"); const size_t smem = mamba3_fwd_smem_bytes(R, N, P, C, sdt == at::kBFloat16 ? 1 : 0); const size_t cap = (size_t)mamba3_max_smem_optin(); TORCH_CHECK(smem <= cap, "chunk_size ", C, " with mimo_rank ", R, ", headdim ", P, " and state ", N, " needs ", smem / 1024, " KB of shared memory per block, above this " "device's ", cap / 1024, " KB opt-in limit"); const int fused = (norm_weight.has_value() && norm_weight->defined()) ? 1 : 0; auto out = torch::empty({B, S, H, P}, v.options()); // The state workspace carries the value dtype. chunk_scan casts the states // to bfloat16 the moment it reads them, so bf16 storage loses nothing there // and halves the traffic of the scan that walks this buffer. auto kv_ws = torch::empty({B, H, Nc, N, P}, v.options()); auto fn = (sdt == at::kFloat) ? mamba3_mimo_fwd_launch_f32 : mamba3_mimo_fwd_launch_bf16; fn(q.data_ptr(), k.data_ptr(), v.data_ptr(), vptr(z, sdt), q_bias.data_ptr(), k_bias.data_ptr(), mimo_v.data_ptr(), mimo_o.data_ptr(), vptr(mimo_z, sdt), vptr(D, sdt), angles.data_ptr(), dt.data_ptr(), trap.data_ptr(), dA_cs.data_ptr(), dA_cs_rev.data_ptr(), vptr(norm_weight, sdt), (float)norm_eps, fused, kv_ws.data_ptr(), out.data_ptr(), B, S, H, Gqk, R, N, P, Na, C, c10::cuda::getCurrentCUDAStream().stream()); C10_CUDA_KERNEL_LAUNCH_CHECK(); return out; } #define BWD_ARGS \ const void* q, const void* k, const void* v, const void* z, const void* q_bias, \ const void* k_bias, const void* mimo_v, const void* mimo_o, const void* mimo_z, \ const void* Dvec, const float* angles, const float* dt, const float* trap, \ const float* dA_cs, const float* dA_cs_rev, const float* states, const void* norm_w, \ float norm_eps, int fused_norm, const void* dy, float* dO_ws, float* dSt_ws, float* ws, \ float* dq, float* dk, float* dv, float* dz, float* dq_bias, float* dk_bias, \ float* dmimo_v, float* dmimo_o, float* dmimo_z, float* dD, float* dnorm_w, \ float* d_dA_cs, float* d_dA_cs_rev, float* ddt, float* dtrap, float* dang, \ int B, int S, int H, int Gqk, int R, int N, int P, int Na, int C, void* stream void mamba3_mimo_bwd_launch_f32(BWD_ARGS); void mamba3_mimo_bwd_launch_bf16(BWD_ARGS); void mamba3_mimo_states_launch_f32( const void* k, const void* v, const void* k_bias, const void* mimo_v, const float* angles, const float* dt, const float* trap, const float* dA_cs, const float* dA_cs_rev, float* kv_ws, int B, int S, int H, int Gqk, int R, int N, int P, int Na, int C, void* stream); std::vector mamba3_mimo_bwd( torch::Tensor q, torch::Tensor k, torch::Tensor v, c10::optional z, torch::Tensor q_bias, torch::Tensor k_bias, torch::Tensor mimo_v, torch::Tensor mimo_o, c10::optional mimo_z, c10::optional D, torch::Tensor angles, torch::Tensor dt, torch::Tensor trap, torch::Tensor dA_cs, torch::Tensor dA_cs_rev, int64_t chunk_size, c10::optional norm_weight, double norm_eps, torch::Tensor dy) { const auto sdt = q.scalar_type(); TORCH_CHECK(sdt == at::kFloat || sdt == at::kBFloat16, "backward inputs must be float32 or bfloat16"); // The kernels index these with contiguous strides; a strided view would be // read as if it were packed and would return wrong gradients silently. for (const auto& t : {q, k, v, q_bias, k_bias, mimo_v, mimo_o}) TORCH_CHECK(t.is_cuda() && t.is_contiguous() && t.scalar_type() == sdt, "all value inputs must be contiguous CUDA tensors of one dtype"); M3_F32(dt); M3_F32(trap); M3_F32(dA_cs); M3_F32(dA_cs_rev); M3_F32(angles); const int B = (int)q.size(0), S = (int)q.size(1), R = (int)q.size(2); const int Gqk = (int)q.size(3), N = (int)q.size(4); const int H = (int)v.size(2), P = (int)v.size(3), Na = (int)angles.size(3); TORCH_CHECK(chunk_size > 0, "chunk_size must be positive"); TORCH_CHECK(R <= 8, "mimo_rank above 8 is not supported"); const int C = (int)chunk_size, Nc = (S + C - 1) / C, CR = C * R; // The backward has no row-block streamed path, so its tiles are resident and // its budget is tighter than the forward's at the same geometry. const size_t smem = std::max(mamba3_bwd_smem_bytes(R, N, P, C), mamba3_states_smem_bytes(R, N, P, C)); const size_t cap = (size_t)mamba3_max_smem_optin(); TORCH_CHECK(smem <= cap, "backward with chunk_size ", C, ", mimo_rank ", R, ", headdim ", P, " and state ", N, " needs ", smem / 1024, " KB of shared memory per " "block, above this device's ", cap / 1024, " KB opt-in limit; reduce " "chunk_size * mimo_rank or headdim"); auto f32 = v.options().dtype(at::kFloat); // The state recompute runs in float32 regardless; it feeds the inter term. auto kf = k.to(at::kFloat).contiguous(), vf = v.to(at::kFloat).contiguous(); auto kbf = k_bias.to(at::kFloat).contiguous(), mvf = mimo_v.to(at::kFloat).contiguous(); auto states = torch::empty({B, H, Nc, N, P}, f32); mamba3_mimo_states_launch_f32( kf.data_ptr(), vf.data_ptr(), kbf.data_ptr(), mvf.data_ptr(), angles.data_ptr(), dt.data_ptr(), trap.data_ptr(), dA_cs.data_ptr(), dA_cs_rev.data_ptr(), states.data_ptr(), B, S, H, Gqk, R, N, P, Na, C, c10::cuda::getCurrentCUDAStream().stream()); C10_CUDA_KERNEL_LAUNCH_CHECK(); auto dO_ws = torch::empty({B, H, Nc, CR, P}, f32); auto dSt_ws = torch::empty({B, H, Nc, N, P}, f32); // Per-block slots for the parameter gradients, reduced in block order so the // result does not depend on scheduling. const int64_t nblk = (int64_t)B * Nc; const int64_t gy = ((int64_t)N * P + 255) / 256; const int64_t nws = nblk * ((int64_t)3 * H * R * P + 2 * H * R * N + H * P + H) + 2LL * B * H * S + (int64_t)B * H * Nc * gy; auto ws = torch::empty({nws}, f32); auto dq = torch::zeros_like(q, f32), dk = torch::zeros_like(k, f32); auto dv = torch::zeros_like(v, f32), dz = torch::zeros_like(v, f32); auto dqb = torch::zeros_like(q_bias, f32), dkb = torch::zeros_like(k_bias, f32); auto dmv = torch::zeros_like(mimo_v, f32), dmo = torch::zeros_like(mimo_o, f32); auto dmz = torch::zeros_like(mimo_v, f32); auto dD = torch::zeros({H}, f32), dnw = torch::zeros({H, P}, f32); auto dacs = torch::zeros_like(dA_cs), dacsr = torch::zeros_like(dA_cs_rev); auto ddt = torch::zeros_like(dt), dtrap = torch::zeros_like(trap); auto dang = torch::zeros_like(angles); const int fused = (norm_weight.has_value() && norm_weight->defined()) ? 1 : 0; auto bwd = (sdt == at::kFloat) ? mamba3_mimo_bwd_launch_f32 : mamba3_mimo_bwd_launch_bf16; bwd(q.data_ptr(), k.data_ptr(), v.data_ptr(), vptr(z, sdt), q_bias.data_ptr(), k_bias.data_ptr(), mimo_v.data_ptr(), mimo_o.data_ptr(), vptr(mimo_z, sdt), vptr(D, sdt), angles.data_ptr(), dt.data_ptr(), trap.data_ptr(), dA_cs.data_ptr(), dA_cs_rev.data_ptr(), states.data_ptr(), vptr(norm_weight, sdt), (float)norm_eps, fused, dy.contiguous().data_ptr(), dO_ws.data_ptr(), dSt_ws.data_ptr(), ws.data_ptr(), dq.data_ptr(), dk.data_ptr(), dv.data_ptr(), dz.data_ptr(), dqb.data_ptr(), dkb.data_ptr(), dmv.data_ptr(), dmo.data_ptr(), dmz.data_ptr(), dD.data_ptr(), dnw.data_ptr(), dacs.data_ptr(), dacsr.data_ptr(), ddt.data_ptr(), dtrap.data_ptr(), dang.data_ptr(), B, S, H, Gqk, R, N, P, Na, C, c10::cuda::getCurrentCUDAStream().stream()); C10_CUDA_KERNEL_LAUNCH_CHECK(); return {dq, dk, dv, dz, dqb, dkb, dmv, dmo, dmz, dD, dnw, dacs, dacsr, ddt, dtrap, dang}; } // registration.h comes from the kernel builder; a local JIT build has neither // it nor TORCH_LIBRARY_EXPAND, and reaches the ops through torch.ops instead. #if defined(__has_include) && __has_include("registration.h") # include "registration.h" # define MAMBA3_HAVE_REGISTRATION 1 #else # define TORCH_LIBRARY_EXPAND(NAME, MOD) TORCH_LIBRARY(NAME, MOD) # define CUDA_KERNEL 1 #endif TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) { // Defined with its implementation: no tensor arguments, so there is no // dispatch key to select on and no meta kernel to register. ops.def("mamba3_fwd_dispatch(int mimo_rank, int dstate, int headdim, " "int chunk_size, bool bf16) -> int[]", &mamba3_fwd_dispatch); ops.def("mamba3_cumulative_angles(Tensor angles, Tensor dt) -> Tensor"); ops.def("mamba3_mimo_step(Tensor q, Tensor k, Tensor v, Tensor? z, Tensor adt, " "Tensor dt, Tensor trap, Tensor q_bias, Tensor k_bias, Tensor angles, " "Tensor mimo_v, Tensor mimo_o, Tensor? mimo_z, Tensor? D, " "Tensor angle_in, Tensor(a!) angle_out, Tensor(b!) S_st, " "Tensor kprev_in, Tensor vprev_in, Tensor(c!) kprev_out, " "Tensor(d!) vprev_out) -> Tensor"); ops.def("mamba3_mimo_fwd(Tensor q, Tensor k, Tensor v, Tensor? z, Tensor q_bias, " "Tensor k_bias, Tensor mimo_v, Tensor mimo_o, Tensor? mimo_z, Tensor? D, " "Tensor angles, Tensor dt, Tensor trap, Tensor dA_cs, Tensor dA_cs_rev, " "int chunk_size, Tensor? norm_weight, float norm_eps) -> Tensor"); ops.def("mamba3_mimo_bwd(Tensor q, Tensor k, Tensor v, Tensor? z, Tensor q_bias, " "Tensor k_bias, Tensor mimo_v, Tensor mimo_o, Tensor? mimo_z, Tensor? D, " "Tensor angles, Tensor dt, Tensor trap, Tensor dA_cs, Tensor dA_cs_rev, " "int chunk_size, Tensor? norm_weight, float norm_eps, Tensor dy) -> Tensor[]"); #if defined(CUDA_KERNEL) || defined(ROCM_KERNEL) ops.impl("mamba3_cumulative_angles", torch::kCUDA, &mamba3_cumulative_angles); ops.impl("mamba3_mimo_step", torch::kCUDA, &mamba3_mimo_step); ops.impl("mamba3_mimo_fwd", torch::kCUDA, &mamba3_mimo_fwd); ops.impl("mamba3_mimo_bwd", torch::kCUDA, &mamba3_mimo_bwd); #endif } #ifdef MAMBA3_HAVE_REGISTRATION REGISTER_EXTENSION(TORCH_EXTENSION_NAME) #endif