use std::fmt::Write as FmtWrite;
use crate::arch::SmVersion;
use crate::error::PtxGenError;
use crate::ir::PtxType;
pub struct BiasAddTemplate {
pub precision: PtxType,
pub target: SmVersion,
}
impl BiasAddTemplate {
#[must_use]
pub fn kernel_name(&self) -> String {
let type_str = self.precision.as_ptx_str().trim_start_matches('.');
format!("bias_add_{type_str}")
}
fn validate(&self) -> Result<(), PtxGenError> {
if !matches!(self.precision, PtxType::F32 | PtxType::F64) {
return Err(PtxGenError::InvalidType(format!(
"bias_add requires F32 or F64, got {}",
self.precision.as_ptx_str()
)));
}
Ok(())
}
pub fn generate(&self) -> Result<String, PtxGenError> {
self.validate()?;
let ty = self.precision.as_ptx_str();
let byte_size = self.precision.size_bytes();
let kernel_name = self.kernel_name();
let mut ptx = String::with_capacity(2048);
writeln!(ptx, ".version {}", self.target.ptx_version())
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, ".target {}", self.target.as_ptx_str()).map_err(PtxGenError::FormatError)?;
writeln!(ptx, ".address_size 64").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, ".visible .entry {kernel_name}(").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u64 %param_input,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u64 %param_bias,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u64 %param_output,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u32 %param_m,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u32 %param_n").map_err(PtxGenError::FormatError)?;
writeln!(ptx, ")").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "{{").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg .b32 %r<9>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg .b64 %rd<7>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg {ty} %f<3>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg .pred %p<2>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // tid = blockIdx.x * blockDim.x + threadIdx.x")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r0, %tid.x;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r1, %ctaid.x;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r2, %ntid.x;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mad.lo.u32 %r3, %r1, %r2, %r0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u32 %r4, [%param_m];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u32 %r5, [%param_n];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u32 %r6, %r4, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p0, %r3, %r6;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p0 bra $BA_DONE;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // col = tid % n").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " div.u32 %r8, %r3, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u32 %r8, %r8, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " sub.u32 %r7, %r3, %r8;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // load input[tid]").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u64 %rd0, [%param_input];")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u64 %rd1, [%param_bias];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u64 %rd2, [%param_output];")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd3, %r3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd3, %rd3, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd4, %rd0, %rd3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.global{ty} %f0, [%rd4];").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // load bias[col]").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd5, %r7;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd5, %rd5, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd6, %rd1, %rd5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.global{ty} %f1, [%rd6];").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // out[tid] = input[tid] + bias[col]")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add{ty} %f2, %f0, %f1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd4, %rd2, %rd3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " st.global{ty} [%rd4], %f2;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$BA_DONE:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ret;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "}}").map_err(PtxGenError::FormatError)?;
Ok(ptx)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kernel_name_format() {
let t = BiasAddTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
assert_eq!(t.kernel_name(), "bias_add_f32");
let t64 = BiasAddTemplate {
precision: PtxType::F64,
target: SmVersion::Sm80,
};
assert_eq!(t64.kernel_name(), "bias_add_f64");
}
#[test]
fn generates_f32_kernel() {
let t = BiasAddTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
let ptx = t.generate().expect("should generate bias_add f32");
assert!(ptx.contains(".visible .entry bias_add_f32("));
assert!(ptx.contains("%param_input"));
assert!(ptx.contains("%param_bias"));
assert!(ptx.contains("%param_output"));
assert!(ptx.contains("%param_m"));
assert!(ptx.contains("%param_n"));
assert!(ptx.contains("add.f32 %f2, %f0, %f1;"));
assert!(ptx.contains("div.u32"));
assert!(ptx.contains("sub.u32 %r7"));
assert!(ptx.contains("mul.lo.u64 %rd3, %rd3, 4;"));
assert!(ptx.contains("ld.global.f32"));
assert!(ptx.contains("st.global.f32"));
}
#[test]
fn generates_f64_kernel() {
let t = BiasAddTemplate {
precision: PtxType::F64,
target: SmVersion::Sm90,
};
let ptx = t.generate().expect("should generate bias_add f64");
assert!(ptx.contains("bias_add_f64"));
assert!(ptx.contains("add.f64 %f2, %f0, %f1;"));
assert!(ptx.contains("mul.lo.u64 %rd3, %rd3, 8;"));
}
#[test]
fn rejects_integer_precision() {
let t = BiasAddTemplate {
precision: PtxType::S32,
target: SmVersion::Sm80,
};
assert!(t.generate().is_err());
}
#[test]
fn rejects_half_precision() {
let t = BiasAddTemplate {
precision: PtxType::F16,
target: SmVersion::Sm80,
};
assert!(t.generate().is_err());
}
#[test]
fn guards_bounds_and_reuses_offset() {
let t = BiasAddTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
let ptx = t.generate().expect("generate");
assert!(ptx.contains("mul.lo.u32 %r6, %r4, %r5;"));
assert!(ptx.contains("setp.ge.u32 %p0, %r3, %r6;"));
assert!(ptx.contains("@%p0 bra $BA_DONE;"));
assert!(ptx.contains("add.u64 %rd4, %rd2, %rd3;"));
}
}