use std::fmt::Write as FmtWrite;
use crate::arch::SmVersion;
use crate::error::PtxGenError;
use crate::ir::PtxType;
pub struct CausalSoftmaxTemplate {
pub precision: PtxType,
pub target: SmVersion,
}
impl CausalSoftmaxTemplate {
#[must_use]
pub fn kernel_name(&self) -> String {
let type_str = self.precision.as_ptx_str().trim_start_matches('.');
format!("causal_softmax_{type_str}")
}
fn validate(&self) -> Result<(), PtxGenError> {
if !matches!(self.precision, PtxType::F32 | PtxType::F64) {
return Err(PtxGenError::InvalidType(format!(
"causal softmax requires F32 or F64, got {}",
self.precision.as_ptx_str()
)));
}
Ok(())
}
const fn neg_inf(&self) -> &'static str {
match self.precision {
PtxType::F64 => "0dFFF0000000000000",
_ => "0fFF800000",
}
}
const fn zero(&self) -> &'static str {
match self.precision {
PtxType::F64 => "0d0000000000000000",
_ => "0f00000000",
}
}
const fn one(&self) -> &'static str {
match self.precision {
PtxType::F64 => "0d3FF0000000000000",
_ => "0f3F800000",
}
}
const fn log2e(&self) -> &'static str {
match self.precision {
PtxType::F64 => "0d3FF71547652B82FE",
_ => "0f3FB8AA3B",
}
}
#[allow(clippy::too_many_lines)]
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 neg_inf = self.neg_inf();
let zero = self.zero();
let one = self.one();
let log2e = self.log2e();
let mut ptx = String::with_capacity(4096);
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_output,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u32 %param_rows,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u32 %param_cols,").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .param .u32 %param_seq_len").map_err(PtxGenError::FormatError)?;
writeln!(ptx, ")").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "{{").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg .b32 %r<12>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg .b64 %rd<8>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg {ty} %f<8>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " .reg .pred %p<4>;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // row = 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_rows];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u32 %r5, [%param_cols];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u32 %r9, [%param_seq_len];")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p0, %r3, %r4;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p0 bra $CS_DONE;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(
ptx,
" // rd2 = input + row*cols*bytes ; rd3 = output + row*cols*bytes"
)
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u64 %rd0, [%param_input];")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.param.u64 %rd1, [%param_output];")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u32 %r8, %r3, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd4, %r8;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd4, %rd4, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd2, %rd0, %rd4;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd3, %rd1, %rd4;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // row_in_seq = row % seq_len").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " rem.u32 %r10, %r3, %r9;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // live = min(row_in_seq + 1, cols)")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u32 %r7, %r10, 1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " min.u32 %r7, %r7, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // Pass 1: masked row max over j in [0, live)")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov{ty} %f0, {neg_inf};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r6, 0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_MAX_LOOP:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p1, %r6, %r7;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p1 bra $CS_MAX_DONE;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd5, %r6;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd5, %rd5, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd6, %rd2, %rd5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.global{ty} %f1, [%rd6];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " max{ty} %f0, %f0, %f1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u32 %r6, %r6, 1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " bra $CS_MAX_LOOP;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_MAX_DONE:").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
let neg_1e38 = match self.precision {
PtxType::F64 => "0dC7D2CED32A16A1B1",
_ => "0fFE967699",
};
writeln!(ptx, " // if (max < -1e38) write one-hot and return")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov{ty} %f5, {neg_1e38};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.lt{ty} %p1, %f0, %f5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p1 bra $CS_ONEHOT;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // Pass 2: masked sum of exp(x - max)")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov{ty} %f2, {zero};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r6, 0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_SUM_LOOP:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p1, %r6, %r7;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p1 bra $CS_SUM_DONE;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd5, %r6;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd5, %rd5, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd6, %rd2, %rd5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.global{ty} %f1, [%rd6];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " sub{ty} %f3, %f1, %f0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul{ty} %f3, %f3, {log2e};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ex2.approx{ty} %f3, %f3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add{ty} %f2, %f2, %f3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u32 %r6, %r6, 1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " bra $CS_SUM_LOOP;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_SUM_DONE:").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
let small = match self.precision {
PtxType::F64 => "0d3DDB7CDFD9D7BDBB",
_ => "0f2EDBE6FF",
};
writeln!(ptx, " // if (sum < 1e-10) write one-hot and return")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov{ty} %f5, {small};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.lt{ty} %p1, %f2, %f5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p1 bra $CS_ONEHOT;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(
ptx,
" // Pass 3: normalize live columns, zero masked columns"
)
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " rcp.approx{ty} %f4, %f2;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r6, 0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_NORM_LOOP:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p1, %r6, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p1 bra $CS_DONE;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd5, %r6;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd5, %rd5, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd7, %rd3, %rd5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p2, %r6, %r7;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p2 bra $CS_NORM_ZERO;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd6, %rd2, %rd5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ld.global{ty} %f1, [%rd6];").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " sub{ty} %f3, %f1, %f0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul{ty} %f3, %f3, {log2e};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " ex2.approx{ty} %f3, %f3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul{ty} %f3, %f3, %f4;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " st.global{ty} [%rd7], %f3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " bra $CS_NORM_NEXT;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_NORM_ZERO:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov{ty} %f3, {zero};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " st.global{ty} [%rd7], %f3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_NORM_NEXT:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u32 %r6, %r6, 1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " bra $CS_NORM_LOOP;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_ONEHOT:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " // out[j] = (j == 0) ? 1 : 0").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov.u32 %r6, 0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_OH_LOOP:").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.ge.u32 %p1, %r6, %r5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p1 bra $CS_DONE;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " cvt.u64.u32 %rd5, %r6;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mul.lo.u64 %rd5, %rd5, {byte_size};")
.map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u64 %rd7, %rd3, %rd5;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " setp.eq.u32 %p2, %r6, 0;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " mov{ty} %f3, {zero};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " @%p2 mov{ty} %f3, {one};").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " st.global{ty} [%rd7], %f3;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " add.u32 %r6, %r6, 1;").map_err(PtxGenError::FormatError)?;
writeln!(ptx, " bra $CS_OH_LOOP;").map_err(PtxGenError::FormatError)?;
writeln!(ptx).map_err(PtxGenError::FormatError)?;
writeln!(ptx, "$CS_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 = CausalSoftmaxTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
assert_eq!(t.kernel_name(), "causal_softmax_f32");
}
#[test]
fn generates_f32_kernel() {
let t = CausalSoftmaxTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
let ptx = t.generate().expect("should generate causal softmax f32");
assert!(ptx.contains(".visible .entry causal_softmax_f32("));
assert!(ptx.contains("%param_rows"));
assert!(ptx.contains("%param_cols"));
assert!(ptx.contains("%param_seq_len"));
assert!(ptx.contains("ex2.approx.f32"));
assert!(ptx.contains("rcp.approx.f32"));
assert!(ptx.contains("min.u32"));
assert!(ptx.contains("$CS_ONEHOT"));
}
#[test]
fn generates_f64_kernel() {
let t = CausalSoftmaxTemplate {
precision: PtxType::F64,
target: SmVersion::Sm90,
};
let ptx = t.generate().expect("should generate causal softmax f64");
assert!(ptx.contains("causal_softmax_f64"));
assert!(ptx.contains("ex2.approx.f64"));
assert!(ptx.contains("0dFFF0000000000000"));
}
#[test]
fn rejects_integer_precision() {
let t = CausalSoftmaxTemplate {
precision: PtxType::S32,
target: SmVersion::Sm80,
};
assert!(t.generate().is_err());
}
#[test]
fn mask_excludes_future_columns() {
let t = CausalSoftmaxTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
let ptx = t.generate().expect("generate");
assert!(ptx.contains("$CS_NORM_ZERO"));
assert!(ptx.contains("setp.ge.u32 %p1, %r6, %r7;"));
}
#[test]
fn live_count_uses_row_modulo_seq_len() {
let t = CausalSoftmaxTemplate {
precision: PtxType::F32,
target: SmVersion::Sm80,
};
let ptx = t.generate().expect("generate");
assert!(ptx.contains("%param_seq_len"));
assert!(ptx.contains("rem.u32 %r10, %r3, %r9;"));
assert!(ptx.contains("add.u32 %r7, %r10, 1;"));
}
}