use super::super::PlanError;
use super::super::segdense::{
Choice, Config, HALF_SHARED, Hardware, Kind, Reduction, SegdensePin, Site, f16_bits,
half_scale, pack_half_columns, selected_rows,
};
use crate::inference::cuda::{ComputeCapability, CudaMath, PtxTier};
const fn device(major: u32, minor: u32, multiprocessors: u32, shared_kib: u32) -> Hardware {
Hardware {
capability: ComputeCapability::new(major, minor),
multiprocessors,
shared_optin: shared_kib * 1024,
}
}
const A100: Hardware = device(8, 0, 108, 163);
const H100: Hardware = device(9, 0, 132, 227);
const RTX_4060_TI: Hardware = device(8, 9, 34, 99);
const RTX_5060_TI: Hardware = device(12, 0, 36, 99);
const RTX_3090: Hardware = device(8, 6, 82, 99);
const T4: Hardware = device(7, 5, 40, 64);
const SITES: [Site; 6] = [
Site::Conv1,
Site::Conv2,
Site::Linear0,
Site::Linear1,
Site::Linear2,
Site::Embedding,
];
fn kernel(site: Site, batch: usize, math: CudaMath, tier: PtxTier, hardware: Hardware) -> Config {
config(site, batch, math, tier, hardware).expect("production batch")
}
fn config(
site: Site,
batch: usize,
math: CudaMath,
tier: PtxTier,
hardware: Hardware,
) -> Option<Config> {
site.choice(batch, math, tier, hardware).map(Choice::config)
}
#[test]
fn every_model_batch_has_a_kernel_and_no_other_batch_does() {
for hardware in [A100, H100, RTX_4060_TI, RTX_5060_TI, RTX_3090, T4] {
for site in SITES {
for math in [CudaMath::Fp32, CudaMath::Tf32] {
for tier in [PtxTier::Sm75, PtxTier::Sm80] {
for batch in [0, 2, 7, 31, 33, 64] {
assert_eq!(site.choice(batch, math, tier, hardware), None);
}
for batch in [1, 32] {
let choice = site.choice(batch, math, tier, hardware).unwrap();
assert_eq!((choice.entry.site(), choice.entry.batch()), (site, batch));
assert_eq!(choice.entry.is_split(), choice.splits > 0);
let config = choice.config();
assert!(config.kernel.contains(&format!("_b{batch}")));
assert!(config.shared <= hardware.shared_optin.max(48 * 1024));
}
}
}
}
}
}
#[test]
fn pins_refuse_other_boundaries_and_lower_modules() {
let pin = SegdensePin::select(
Site::Linear0,
32,
CudaMath::Tf32,
PtxTier::Sm80,
&crate::inference::cuda::device::test_support::Builder::new(ComputeCapability::new(12, 0))
.multiprocessors(36)
.build(),
)
.unwrap();
assert!(
pin.check(Site::Linear0, 32, CudaMath::Tf32, PtxTier::Sm80)
.is_ok()
);
assert!(
pin.check(Site::Linear0, 32, CudaMath::Tf32, PtxTier::Sm120)
.is_ok()
);
for (site, batch, math) in [
(Site::Linear1, 32, CudaMath::Tf32),
(Site::Linear0, 1, CudaMath::Tf32),
(Site::Linear0, 32, CudaMath::Fp32),
] {
assert!(matches!(
pin.check(site, batch, math, PtxTier::Sm80),
Err(PlanError::Geometry(_))
));
}
assert!(matches!(
pin.check(Site::Linear0, 32, CudaMath::Tf32, PtxTier::Sm75),
Err(PlanError::DeviceUnsupported { .. })
));
}
#[test]
fn tensor_convolutions_only_on_tensor_heavy_parts() {
let convs = [
(
Site::Conv1,
"spk_segdense_conv1_b32_tc",
"spk_segdense_conv1_b32_x3",
"spk_segdense_conv1_b32",
),
(
Site::Conv2,
"spk_segdense_conv2_b32_tc",
"spk_segdense_conv2_b32_x3",
"spk_segdense_conv2_b32",
),
];
for (site, tf32, fp32, simt) in convs {
for hardware in [A100, H100] {
let tc = kernel(site, 32, CudaMath::Tf32, PtxTier::Sm80, hardware);
let x3 = kernel(site, 32, CudaMath::Fp32, PtxTier::Sm80, hardware);
assert_eq!((tc.kernel, x3.kernel), (tf32, fp32));
assert!(x3.shared <= hardware.shared_optin);
}
for hardware in [RTX_4060_TI, RTX_5060_TI, RTX_3090] {
let tf32_mode = kernel(site, 32, CudaMath::Tf32, PtxTier::Sm80, hardware);
let fp32_mode = kernel(site, 32, CudaMath::Fp32, PtxTier::Sm80, hardware);
assert_eq!((tf32_mode.kernel, fp32_mode.kernel), (simt, simt));
}
}
assert_eq!(
kernel(Site::Conv1, 1, CudaMath::Tf32, PtxTier::Sm80, A100).kernel,
"spk_segdense_conv1_b1"
);
}
#[test]
fn sm75_tier_never_selects_tensor_kernels() {
for hardware in [A100, RTX_4060_TI, T4] {
for site in SITES {
for batch in [1, 32] {
for math in [CudaMath::Fp32, CudaMath::Tf32] {
let config = kernel(site, batch, math, PtxTier::Sm75, hardware);
let plain_tf32 = (site, batch, math) == (Site::Linear1, 1, CudaMath::Tf32);
assert!(
(plain_tf32 || !config.kernel.contains("_tf32"))
&& !config.kernel.ends_with("_tc")
&& !config.kernel.ends_with("_x3")
&& !config.kernel.ends_with("_f16"),
"{site:?} {batch} {math:?}: {}",
config.kernel
);
}
}
}
}
}
#[test]
fn embedding_splits_fill_one_wave() {
let splits = |math, hardware| match kernel(Site::Embedding, 32, math, PtxTier::Sm80, hardware) {
Config {
kernel,
kind: Kind::Split { splits, .. },
..
} => (kernel, splits),
other => panic!("not split-K: {other:?}"),
};
assert_eq!(
splits(CudaMath::Tf32, RTX_4060_TI),
("spk_segdense_embed_b32_f16", 17)
);
assert_eq!(
splits(CudaMath::Tf32, RTX_5060_TI),
("spk_segdense_embed_b32_f16", 18)
);
let small = |hardware: Hardware| Hardware {
multiprocessors: 30,
..hardware
};
assert_eq!(
splits(CudaMath::Tf32, small(RTX_4060_TI)),
("spk_segdense_embed_b32_tf32", 30)
);
assert_eq!(
splits(CudaMath::Tf32, small(RTX_5060_TI)),
("spk_segdense_embed_b32_tf32_k2", 15)
);
let no_optin = Hardware {
shared_optin: HALF_SHARED - 1,
..RTX_4060_TI
};
assert_eq!(
splits(CudaMath::Tf32, no_optin),
("spk_segdense_embed_b32_tf32", 34)
);
assert_eq!(
splits(CudaMath::Tf32, A100),
("spk_segdense_embed_b32_tf32_e64", 54)
);
assert_eq!(
splits(CudaMath::Fp32, RTX_4060_TI),
("spk_segdense_embed_b32", 34)
);
assert_eq!(
splits(CudaMath::Fp32, A100),
("spk_segdense_embed_b32_x3", 54)
);
assert_eq!(splits(CudaMath::Fp32, device(8, 9, 1, 99)).1, 1);
assert_eq!(splits(CudaMath::Fp32, device(8, 9, 132, 99)).1, 128);
assert_eq!(splits(CudaMath::Tf32, device(8, 9, 132, 99)).1, 66);
}
#[test]
fn reductions_cover_every_output_once() {
for splits in 1..=128 {
let shape = Reduction::shape(splits);
let kernel = match splits {
18 | 34 | 36 => format!("spk_segdense_reduce_e{splits}"),
..=40 => "spk_segdense_reduce_embed_flat".to_owned(),
_ => "spk_segdense_reduce_embed".to_owned(),
};
assert_eq!(shape.kernel, kernel);
assert_eq!(shape.splits, splits);
let outputs = match shape.kernel {
"spk_segdense_reduce_embed" => shape.grid * 128,
_ => shape.grid * shape.block * 4,
};
assert_eq!(outputs, 96 * 256);
}
}
#[test]
fn selected_rows_have_the_largest_weight_energy() {
let weight = [1.0, 0.0, 3.0, -4.0, 0.0, 0.0, 0.0, 5.0, 2.0, 0.0];
assert_eq!(selected_rows(&weight, 2, 3), [1, 3, 4]);
assert_eq!(selected_rows(&weight, 2, 1), [1]);
assert_eq!(selected_rows(&weight, 2, 5), [0, 1, 2, 3, 4]);
}
#[test]
fn f16_rounding_matches_binary16() {
let cases: [(f32, u16); 16] = [
(1.0, 0x3c00),
(-2.0, 0xc000),
(-0.0, 0x8000),
(65504.0, 0x7bff),
(65519.996, 0x7bff),
(65520.0, 0x7c00),
(1.0 + 2f32.powi(-11), 0x3c00),
(1.0 + 3.0 * 2f32.powi(-11), 0x3c02),
(2f32.powi(-14), 0x0400),
(2f32.powi(-14) * (1.0 - 2f32.powi(-11)), 0x0400),
(2f32.powi(-24), 0x0001),
(2f32.powi(-25), 0x0000),
(1.5 * 2f32.powi(-25), 0x0001),
(f32::MIN_POSITIVE / 2.0, 0x0000),
(f32::INFINITY, 0x7c00),
(f32::NAN, 0x7e00),
];
for (value, expected) in cases {
assert_eq!(f16_bits(value), expected, "{value:e}");
}
}
#[test]
fn half_columns_scale_into_the_f16_range() {
let tiny = 2f32.powi(-20);
let weight = [1.0, -3.0, 0.5, 2.0, tiny, -tiny / 2.0, 0.0, tiny / 4.0];
let packed = pack_half_columns(&weight, 2, 4);
assert_eq!(packed.len(), 4 / 2 * 2 + 2);
assert_eq!(packed[4..], [2f32.powi(-13), 2f32.powi(-34)]);
let words: Vec<u32> = packed[..4].iter().map(|word| word.to_bits()).collect();
let pair = |low: f32, high: f32| u32::from(f16_bits(low)) | (u32::from(f16_bits(high)) << 16);
assert_eq!(
words,
[
pair(8192.0, -24576.0),
pair(16384.0, -8192.0),
pair(4096.0, 16384.0),
pair(0.0, 4096.0),
]
);
assert_eq!(half_scale(0.0), (2f32.powi(126), 2f32.powi(-126)));
assert_eq!(half_scale(f32::INFINITY), (2f32.powi(-113), 2f32.powi(113)));
}