use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use oxibonsai_core::quant_ternary::BlockTQ2_0_g128;
use oxibonsai_kernels::{CudaGraph, CudaGraphError, DitSingleBlockWeights};
use crate::blocks::{DoubleMod, ModTriple};
use crate::forward::QkvNorm;
use crate::math::RopeTables;
use crate::weights::{DitWeights, QuantizedLinear};
#[derive(Debug, thiserror::Error)]
pub enum CudaGpuMatmulError {
#[error("CUDA graph unavailable: {0}")]
GraphUnavailable(String),
#[error("CUDA TQ2 GEMM failed: {0}")]
Cuda(#[from] CudaGraphError),
#[error("DiT weight lookup failed: {0}")]
Weights(String),
}
fn blocks_as_bytes(blocks: &[BlockTQ2_0_g128]) -> &[u8] {
debug_assert_eq!(std::mem::size_of::<BlockTQ2_0_g128>(), 34);
let len = std::mem::size_of_val(blocks);
unsafe { std::slice::from_raw_parts(blocks.as_ptr() as *const u8, len) }
}
static GPU_USED: AtomicBool = AtomicBool::new(false);
pub fn gpu_was_used() -> bool {
GPU_USED.load(Ordering::Relaxed)
}
static GPU_ENABLED: OnceLock<bool> = OnceLock::new();
pub fn dit_gpu_enabled() -> bool {
*GPU_ENABLED.get_or_init(|| !matches!(std::env::var("OXI_DIT_GPU").ok().as_deref(), Some("0")))
}
static DIT_ATTN_GPU_USED: AtomicBool = AtomicBool::new(false);
pub fn dit_attn_gpu_was_used() -> bool {
DIT_ATTN_GPU_USED.load(Ordering::Relaxed)
}
static ATTN_GPU_ENABLED: OnceLock<bool> = OnceLock::new();
pub fn dit_attn_gpu_enabled() -> bool {
*ATTN_GPU_ENABLED
.get_or_init(|| !matches!(std::env::var("OXI_DIT_ATTN_GPU").ok().as_deref(), Some("0")))
}
pub fn joint_attention_gpu(
q: &[f32],
k: &[f32],
v: &[f32],
num_heads: usize,
seq: usize,
head_dim: usize,
) -> Result<Vec<f32>, CudaGpuMatmulError> {
let graph =
CudaGraph::global().map_err(|e| CudaGpuMatmulError::GraphUnavailable(e.to_string()))?;
let mut out = vec![0.0f32; seq * num_heads * head_dim];
graph.encode_joint_attention_flash_pooled(q, k, v, &mut out, num_heads, seq, head_dim)?;
DIT_ATTN_GPU_USED.store(true, Ordering::Relaxed);
Ok(out)
}
pub fn ternary_matmul_gpu(
blocks: &[BlockTQ2_0_g128],
input: &[f32],
out: &mut [f32],
m: usize,
n: usize,
k: usize,
) -> Result<(), CudaGpuMatmulError> {
let graph =
CudaGraph::global().map_err(|e| CudaGpuMatmulError::GraphUnavailable(e.to_string()))?;
let key = blocks.as_ptr() as u64;
let handle =
graph.get_or_upload_weight_tq2_soa_lazy(key, || blocks_as_bytes(blocks).to_vec())?;
graph.encode_gemm_tq2(&handle, input, out, m, n, k)?;
GPU_USED.store(true, Ordering::Relaxed);
Ok(())
}
pub fn dense_matmul_gpu(
weight: &[f32],
input: &[f32],
out: &mut [f32],
m: usize,
n: usize,
k: usize,
) -> Result<(), CudaGpuMatmulError> {
let graph =
CudaGraph::global().map_err(|e| CudaGpuMatmulError::GraphUnavailable(e.to_string()))?;
let key = weight.as_ptr() as u64;
let handle = graph.get_or_upload_f32_weight(key, weight)?;
graph.encode_gemm_f32(&handle, input, out, m, n, k)?;
graph.evict_f32_weight(key)?;
GPU_USED.store(true, Ordering::Relaxed);
Ok(())
}
static DIT_FUSED_USED: AtomicBool = AtomicBool::new(false);
pub fn dit_fused_block_was_used() -> bool {
DIT_FUSED_USED.load(Ordering::Relaxed)
}
static DIT_FUSED_ENABLED: OnceLock<bool> = OnceLock::new();
pub fn dit_fused_enabled() -> bool {
*DIT_FUSED_ENABLED
.get_or_init(|| !matches!(std::env::var("OXI_DIT_FUSED").ok().as_deref(), Some("0")))
}
#[allow(clippy::too_many_arguments)]
pub fn single_block_gpu(
weights: &DitWeights,
index: u32,
h: &mut [f32],
seq: usize,
hidden_size: usize,
num_heads: usize,
head_dim: usize,
ffn_inner: usize,
eps: f32,
rope: &RopeTables,
mod_single: &ModTriple,
norms: &QkvNorm,
) -> Result<(), CudaGpuMatmulError> {
let graph =
CudaGraph::global().map_err(|e| CudaGpuMatmulError::GraphUnavailable(e.to_string()))?;
let p = format!("single_transformer_blocks.{index}");
let proj = weights
.quantized_linear(&format!("{p}.attn.to_qkv_mlp_proj"))
.map_err(|e| CudaGpuMatmulError::Weights(e.to_string()))?;
let to_out = weights
.quantized_linear(&format!("{p}.attn.to_out"))
.map_err(|e| CudaGpuMatmulError::Weights(e.to_string()))?;
let proj_key = proj.blocks.as_ptr() as u64;
let out_key = to_out.blocks.as_ptr() as u64;
graph.encode_dit_single_block(
h,
proj_key,
blocks_as_bytes(proj.blocks),
proj.out_features as usize,
out_key,
blocks_as_bytes(to_out.blocks),
&norms.q,
&norms.k,
&mod_single.shift,
&mod_single.scale,
&mod_single.gate,
&rope.cos,
&rope.sin,
seq,
hidden_size,
num_heads,
head_dim,
ffn_inner,
eps,
)?;
GPU_USED.store(true, Ordering::Relaxed);
DIT_FUSED_USED.store(true, Ordering::Relaxed);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn single_blocks_gpu(
weights: &DitWeights,
num_single: usize,
joint: &mut [f32],
seq: usize,
hidden_size: usize,
num_heads: usize,
head_dim: usize,
ffn_inner: usize,
eps: f32,
rope: &RopeTables,
mod_single: &ModTriple,
norms: &[QkvNorm],
) -> Result<(), CudaGpuMatmulError> {
let graph =
CudaGraph::global().map_err(|e| CudaGpuMatmulError::GraphUnavailable(e.to_string()))?;
if norms.len() != num_single {
return Err(CudaGpuMatmulError::Weights(format!(
"single_blocks_gpu: norms {} != num_single {num_single}",
norms.len()
)));
}
let mut lins: Vec<(QuantizedLinear, QuantizedLinear)> = Vec::with_capacity(num_single);
let mut proj_out = 0usize;
for j in 0..num_single {
let p = format!("single_transformer_blocks.{j}");
let proj = weights
.quantized_linear(&format!("{p}.attn.to_qkv_mlp_proj"))
.map_err(|e| CudaGpuMatmulError::Weights(e.to_string()))?;
let to_out = weights
.quantized_linear(&format!("{p}.attn.to_out"))
.map_err(|e| CudaGpuMatmulError::Weights(e.to_string()))?;
if j == 0 {
proj_out = proj.out_features as usize;
}
lins.push((proj, to_out));
}
let block_params: Vec<DitSingleBlockWeights> = lins
.iter()
.enumerate()
.map(|(j, (proj, to_out))| DitSingleBlockWeights {
proj_handle: proj.blocks.as_ptr() as u64,
proj_bytes: blocks_as_bytes(proj.blocks),
out_handle: to_out.blocks.as_ptr() as u64,
out_bytes: blocks_as_bytes(to_out.blocks),
norm_q: &norms[j].q,
norm_k: &norms[j].k,
})
.collect();
graph.encode_dit_single_blocks(
joint,
&block_params,
proj_out,
&mod_single.shift,
&mod_single.scale,
&mod_single.gate,
&rope.cos,
&rope.sin,
seq,
hidden_size,
num_heads,
head_dim,
ffn_inner,
eps,
)?;
GPU_USED.store(true, Ordering::Relaxed);
DIT_FUSED_USED.store(true, Ordering::Relaxed);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn double_block_gpu(
weights: &DitWeights,
index: u32,
hidden: &mut [f32],
enc: &mut [f32],
seq_img: usize,
seq_txt: usize,
hidden_size: usize,
num_heads: usize,
head_dim: usize,
ffn_inner: usize,
eps: f32,
rope: &RopeTables,
mod_img: &DoubleMod,
mod_txt: &DoubleMod,
) -> Result<(), CudaGpuMatmulError> {
let graph =
CudaGraph::global().map_err(|e| CudaGpuMatmulError::GraphUnavailable(e.to_string()))?;
let p = format!("transformer_blocks.{index}");
let ql = |suffix: &str| -> Result<QuantizedLinear<'_>, CudaGpuMatmulError> {
weights
.quantized_linear(&format!("{p}.{suffix}"))
.map_err(|e| CudaGpuMatmulError::Weights(e.to_string()))
};
let nrm = |suffix: &str| -> Result<Vec<f32>, CudaGpuMatmulError> {
Ok(weights
.bf16_tensor(&format!("{p}.{suffix}"))
.map_err(|e| CudaGpuMatmulError::Weights(e.to_string()))?
.to_f32_vec())
};
fn tw<'a>(q: &QuantizedLinear<'a>) -> (u64, &'a [u8]) {
(q.blocks.as_ptr() as u64, blocks_as_bytes(q.blocks))
}
let to_q = ql("attn.to_q")?;
let to_k = ql("attn.to_k")?;
let to_v = ql("attn.to_v")?;
let add_q = ql("attn.add_q_proj")?;
let add_k = ql("attn.add_k_proj")?;
let add_v = ql("attn.add_v_proj")?;
let to_out = ql("attn.to_out.0")?;
let to_add_out = ql("attn.to_add_out")?;
let ff_in = ql("ff.linear_in")?;
let ff_out = ql("ff.linear_out")?;
let ffc_in = ql("ff_context.linear_in")?;
let ffc_out = ql("ff_context.linear_out")?;
let norm_q_img = nrm("attn.norm_q.weight")?;
let norm_k_img = nrm("attn.norm_k.weight")?;
let norm_q_txt = nrm("attn.norm_added_q.weight")?;
let norm_k_txt = nrm("attn.norm_added_k.weight")?;
graph.encode_dit_double_block(
hidden,
enc,
tw(&to_q),
tw(&to_k),
tw(&to_v),
tw(&add_q),
tw(&add_k),
tw(&add_v),
tw(&to_out),
tw(&to_add_out),
tw(&ff_in),
tw(&ff_out),
tw(&ffc_in),
tw(&ffc_out),
&norm_q_img,
&norm_k_img,
&norm_q_txt,
&norm_k_txt,
(&mod_img.msa.shift, &mod_img.msa.scale, &mod_img.msa.gate),
(&mod_img.mlp.shift, &mod_img.mlp.scale, &mod_img.mlp.gate),
(&mod_txt.msa.shift, &mod_txt.msa.scale, &mod_txt.msa.gate),
(&mod_txt.mlp.shift, &mod_txt.mlp.scale, &mod_txt.mlp.gate),
&rope.cos,
&rope.sin,
seq_img,
seq_txt,
hidden_size,
num_heads,
head_dim,
ffn_inner,
eps,
)?;
GPU_USED.store(true, Ordering::Relaxed);
DIT_FUSED_USED.store(true, Ordering::Relaxed);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_is_thirty_four_bytes() {
assert_eq!(std::mem::size_of::<BlockTQ2_0_g128>(), 34);
}
#[test]
fn blocks_as_bytes_length_is_34_per_block() {
let blocks = vec![
BlockTQ2_0_g128 {
qs: [0u8; 32],
d: half::f16::ZERO,
};
2
];
let bytes = blocks_as_bytes(&blocks);
assert_eq!(bytes.len(), 68);
assert_eq!(bytes.as_ptr() as usize, blocks.as_ptr() as usize);
}
#[test]
fn gpu_enabled_defaults_on_when_env_unset() {
if std::env::var("OXI_DIT_GPU").is_err() {
assert!(dit_gpu_enabled());
}
}
#[test]
fn attn_gpu_enabled_defaults_on_when_env_unset() {
if std::env::var("OXI_DIT_ATTN_GPU").is_err() {
assert!(dit_attn_gpu_enabled());
}
}
#[test]
fn fused_enabled_defaults_on_when_env_unset() {
if std::env::var("OXI_DIT_FUSED").is_err() {
assert!(dit_fused_enabled());
}
}
}