use std::sync::Arc;
use cudarc::driver::sys::CUfunction_attribute;
use cudarc::driver::{
CudaFunction, CudaSlice, CudaStream, CudaView, CudaViewMut, LaunchConfig, PushKernelArg,
};
use super::{
Batches, Coverage, CoverageEntry, DenseCandidate, DenseSite, DenseSpec, FiniteContract,
GeometryError, InfinityContract, Maths, NanContract, Op, Phases, PlanError, Scratch,
SegConvCandidate, SegConvSite, SegConvSpec, SignedZeroContract, SpecialValues,
};
use crate::inference::cuda::device::DeviceAttributes;
use crate::inference::cuda::error::check_len;
use crate::inference::cuda::{
ComputeCapability, CudaError, CudaMath, CudaRuntime, KernelModule, LoadedKernels, PtxTier,
};
const CONTEXT: &str = "segdense plan";
const BATCHES: [usize; 2] = [1, 32];
const CONV_PADDED: usize = 64;
const TAPS: usize = 5;
const DEFAULT_SHARED: u32 = 48 * 1024;
const EMBED_MAX_SPLITS: u32 = 128;
const FLAT_MAX_SPLITS: u32 = 40;
const SELECT_ROWS: u32 = 16;
const HALF_STAGE: u32 = 32;
const HALF_SLICE: u32 = 320;
const HALF_MIN_SPLITS: u32 = (5120 / HALF_STAGE).div_ceil(HALF_SLICE / HALF_STAGE);
pub(super) const HALF_SHARED: u32 = {
let ring = 96 * (HALF_SLICE / 2 + 4) + 3 * (HALF_STAGE / 2) * (128 + 8);
let reduction = 192 * 32 * 64 / 32;
4 * (96 + if ring > reduction { ring } else { reduction })
};
const SPECIAL_VALUES: SpecialValues = SpecialValues {
finite: FiniteContract::AbsoluteSum { headroom: 2 },
nan: NanContract::Unspecified,
infinity: InfinityContract::Unspecified,
signed_zero: SignedZeroContract::Unspecified,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Hardware {
pub(super) capability: ComputeCapability,
pub(super) multiprocessors: u32,
pub(super) shared_optin: u32,
}
impl Hardware {
fn of(device: &DeviceAttributes) -> Self {
Self {
capability: device.capability(),
multiprocessors: device.multiprocessors().get(),
shared_optin: device.shared_optin_bytes(),
}
}
fn tensor_heavy(self) -> bool {
matches!(
(self.capability.major, self.capability.minor),
(8, 0) | (9, 0) | (10, 0)
)
}
fn consumer_blackwell(self) -> bool {
self.capability.major == 12
}
fn half_split_fits(self) -> bool {
self.splits(2, 1) >= HALF_MIN_SPLITS && self.shared_optin >= HALF_SHARED
}
fn splits(self, tiles: u32, resident: u32) -> u32 {
(self.multiprocessors * resident / tiles).clamp(1, EMBED_MAX_SPLITS)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Site {
Conv1,
Conv2,
Linear0,
Linear1,
Linear2,
Embedding,
}
impl From<DenseSite> for Site {
fn from(site: DenseSite) -> Self {
match site {
DenseSite::Linear0 => Self::Linear0,
DenseSite::Linear1 => Self::Linear1,
DenseSite::Classifier => Self::Linear2,
DenseSite::Embedding => Self::Embedding,
}
}
}
impl From<SegConvSite> for Site {
fn from(site: SegConvSite) -> Self {
match site {
SegConvSite::Conv1 => Self::Conv1,
SegConvSite::Conv2 => Self::Conv2,
}
}
}
impl Site {
fn from_boundary(
boundary: crate::inference::cuda::implementation::BoundaryId,
) -> Result<Self, PlanError> {
Ok(match boundary.name() {
"sincnet.conv1" => Site::Conv1,
"sincnet.conv2" => Site::Conv2,
"linear0" => Site::Linear0,
"linear1" => Site::Linear1,
"linear2" => Site::Linear2,
"resnet.seg_1" => Site::Embedding,
_ => {
return Err(PlanError::Geometry(GeometryError::Unimplemented {
context: CONTEXT,
reason: format!("unknown boundary {boundary}"),
}));
}
})
}
pub(crate) const fn dimensions(self) -> (usize, usize, usize, usize) {
match self {
Self::Conv1 => (5321, 60, 400, 5325),
Self::Conv2 => (1769, 60, 300, 1773),
Self::Linear0 => (589, 128, 256, 0),
Self::Linear1 => (589, 128, 128, 0),
Self::Linear2 => (589, 7, 128, 0),
Self::Embedding => (3, 256, 5120, 0),
}
}
const fn is_conv(self) -> bool {
matches!(self, Self::Conv1 | Self::Conv2)
}
pub(super) fn choice(
self,
batch: usize,
math: CudaMath,
tier: PtxTier,
hardware: Hardware,
) -> Option<Choice> {
if tier >= PtxTier::Sm80
&& let Some(choice) = self.tensor_choice(batch, math, hardware)
{
return Some(choice);
}
self.scalar_choice(batch, math, hardware)
}
fn scalar_choice(self, batch: usize, math: CudaMath, hardware: Hardware) -> Option<Choice> {
let entry = match (self, batch) {
(Self::Conv1, 1) => Entry::Conv1B1,
(Self::Conv1, 32) => Entry::Conv1B32,
(Self::Conv2, 1) => Entry::Conv2B1,
(Self::Conv2, 32) => Entry::Conv2B32,
(Self::Linear0, 1) => Entry::Linear0B1,
(Self::Linear0, 32) => Entry::Linear0B32,
(Self::Linear1, 1) if math == CudaMath::Tf32 => Entry::Linear1B1Tf32,
(Self::Linear1, 1) => Entry::Linear1B1,
(Self::Linear1, 32) => Entry::Linear1B32,
(Self::Linear2, 1) => Entry::ClassifierB1,
(Self::Linear2, 32) => Entry::ClassifierB32,
(Self::Embedding, 1) => Entry::EmbedB1,
(Self::Embedding, 32) => {
return Some(Choice::split(Entry::EmbedB32, hardware.splits(2, 2)));
}
_ => return None,
};
Some(Choice::fixed(entry))
}
fn tensor_choice(self, batch: usize, math: CudaMath, hardware: Hardware) -> Option<Choice> {
let heavy = hardware.tensor_heavy();
let entry = match (self, batch, math) {
(Self::Conv1, 32, CudaMath::Tf32) if heavy => Entry::Conv1B32Tc,
(Self::Conv1, 32, CudaMath::Fp32) if heavy => Entry::Conv1B32X3,
(Self::Conv2, 32, CudaMath::Tf32) if heavy => Entry::Conv2B32Tc,
(Self::Conv2, 32, CudaMath::Fp32) if heavy => Entry::Conv2B32X3,
(Self::Linear0, 1, CudaMath::Tf32) => Entry::Linear0B1Tf32,
(Self::Linear0, 32, CudaMath::Tf32) => Entry::Linear0B32Select,
(Self::Linear1, 32, CudaMath::Tf32) => Entry::Linear1B32Tf32,
(Self::Embedding, 32, CudaMath::Tf32) if heavy => {
return Some(Choice::split(Entry::EmbedB32Tf32E64, hardware.splits(4, 2)));
}
(Self::Embedding, 32, CudaMath::Tf32) if hardware.half_split_fits() => {
return Some(Choice::split(Entry::EmbedB32F16, hardware.splits(2, 1)));
}
(Self::Embedding, 32, CudaMath::Tf32) => return Some(Self::tf32_embedding(hardware)),
(Self::Embedding, 32, CudaMath::Fp32) if heavy => {
return Some(Choice::split(Entry::EmbedB32X3, hardware.splits(4, 2)));
}
_ => return None,
};
Some(Choice::fixed(entry))
}
fn tf32_embedding(hardware: Hardware) -> Choice {
if hardware.consumer_blackwell() {
return Choice::split(Entry::EmbedB32Tf32K2, hardware.splits(2, 1));
}
Choice::split(Entry::EmbedB32Tf32, hardware.splits(2, 2))
}
fn tuning_tf32_choice(
self,
batch: usize,
math: CudaMath,
hardware: Hardware,
) -> Option<Choice> {
let default = self.tensor_choice(batch, math, hardware)?;
(default.entry == Entry::EmbedB32F16).then(|| Self::tf32_embedding(hardware))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Entry {
Conv1B1,
Conv1B32,
Conv1B32Tc,
Conv1B32X3,
Conv2B1,
Conv2B32,
Conv2B32Tc,
Conv2B32X3,
Linear0B1,
Linear0B1Tf32,
Linear0B32,
Linear0B32Select,
Linear1B1,
Linear1B1Tf32,
Linear1B32,
Linear1B32Tf32,
ClassifierB1,
ClassifierB32,
EmbedB1,
EmbedB32,
EmbedB32Tf32,
EmbedB32Tf32K2,
EmbedB32Tf32E64,
EmbedB32X3,
EmbedB32F16,
}
impl Entry {
pub(super) const fn site(self) -> Site {
match self {
Self::Conv1B1 | Self::Conv1B32 | Self::Conv1B32Tc | Self::Conv1B32X3 => Site::Conv1,
Self::Conv2B1 | Self::Conv2B32 | Self::Conv2B32Tc | Self::Conv2B32X3 => Site::Conv2,
Self::Linear0B1 | Self::Linear0B1Tf32 | Self::Linear0B32 | Self::Linear0B32Select => {
Site::Linear0
}
Self::Linear1B1 | Self::Linear1B1Tf32 | Self::Linear1B32 | Self::Linear1B32Tf32 => {
Site::Linear1
}
Self::ClassifierB1 | Self::ClassifierB32 => Site::Linear2,
Self::EmbedB1
| Self::EmbedB32
| Self::EmbedB32Tf32
| Self::EmbedB32Tf32K2
| Self::EmbedB32Tf32E64
| Self::EmbedB32X3
| Self::EmbedB32F16 => Site::Embedding,
}
}
pub(super) const fn batch(self) -> usize {
match self {
Self::Conv1B1
| Self::Conv2B1
| Self::Linear0B1
| Self::Linear0B1Tf32
| Self::Linear1B1
| Self::Linear1B1Tf32
| Self::ClassifierB1
| Self::EmbedB1 => 1,
_ => 32,
}
}
pub(super) const fn config(self, splits: u32) -> Config {
match self {
Self::Conv1B1 => Config::conv("spk_segdense_conv1_b1", 64, 256),
Self::Conv1B32 => Config::conv("spk_segdense_conv1_b32", 128, 128),
Self::Conv1B32Tc => Config::tensor_conv("spk_segdense_conv1_b32_tc", 128, 80, 1, 128),
Self::Conv1B32X3 => Config::tensor_conv("spk_segdense_conv1_b32_x3", 128, 80, 2, 256),
Self::Conv2B1 => Config::conv("spk_segdense_conv2_b1", 32, 256),
Self::Conv2B32 => Config::conv("spk_segdense_conv2_b32", 128, 128),
Self::Conv2B32Tc => Config::tensor_conv("spk_segdense_conv2_b32_tc", 128, 64, 1, 128),
Self::Conv2B32X3 => Config::tensor_conv("spk_segdense_conv2_b32_x3", 128, 64, 2, 256),
Self::Linear0B1 => Config::gemm("spk_segdense_linear0_b1", 16, 32, 128),
Self::Linear0B1Tf32 => Config::gemm("spk_segdense_linear0_b1_tf32", 32, 32, 128),
Self::Linear0B32 => Config::gemm("spk_segdense_linear0_b32", 64, 128, 128),
Self::Linear0B32Select => {
Config::select_gemm("spk_segdense_linear0_b32_tf32", 64, 128, 128)
}
Self::Linear1B1 => Config::gemm("spk_segdense_linear1_b1", 16, 32, 128),
Self::Linear1B1Tf32 => Config::gemm("spk_segdense_linear1_b1_tf32", 16, 32, 128),
Self::Linear1B32 => Config::gemm("spk_segdense_linear1_b32", 64, 128, 128),
Self::Linear1B32Tf32 => Config::gemm("spk_segdense_linear1_b32_tf32", 64, 128, 128),
Self::ClassifierB1 => Config::classifier("spk_segdense_classifier_b1", 8, 64),
Self::ClassifierB32 => Config::classifier("spk_segdense_classifier_b32", 128, 256),
Self::EmbedB1 => Config {
kernel: "spk_segdense_embed_b1",
kind: Kind::Gemv { cols: 2 },
block: 256,
shared: 0,
},
Self::EmbedB32 => Config::split("spk_segdense_embed_b32", 128, splits, 192),
Self::EmbedB32Tf32 => Config::split("spk_segdense_embed_b32_tf32", 128, splits, 192),
Self::EmbedB32Tf32K2 => {
Config::split("spk_segdense_embed_b32_tf32_k2", 128, splits, 384)
}
Self::EmbedB32Tf32E64 => {
Config::split("spk_segdense_embed_b32_tf32_e64", 64, splits, 192)
}
Self::EmbedB32X3 => Config::split("spk_segdense_embed_b32_x3", 64, splits, 192),
Self::EmbedB32F16 => Config::half_split(splits),
}
}
pub(super) const fn is_split(self) -> bool {
matches!(self.config(1).kind, Kind::Split { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Choice {
pub(super) entry: Entry,
pub(super) splits: u8,
}
impl Choice {
pub(super) const fn fixed(entry: Entry) -> Self {
Self { entry, splits: 0 }
}
const fn split(entry: Entry, splits: u32) -> Self {
Self {
entry,
splits: splits as u8,
}
}
pub(super) const fn config(self) -> Config {
self.entry.config(self.splits as u32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
Conv { positions: u32 },
TensorConv {
positions: u32,
cin_pad: u32,
planes: u32,
},
Gemm { rows: u32, cols: u32, select: u32 },
Classifier { rows: u32 },
Gemv { cols: u32 },
Split {
rows: u32,
cols: u32,
splits: u32,
weights: SplitWeights,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SplitWeights {
Fp32,
ScaledHalf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Config {
pub kernel: &'static str,
pub kind: Kind,
pub block: u32,
pub shared: u32,
}
impl Config {
const fn conv(kernel: &'static str, positions: u32, block: u32) -> Self {
Self {
kernel,
kind: Kind::Conv { positions },
block,
shared: 0,
}
}
const fn tensor_conv(
kernel: &'static str,
positions: u32,
cin_pad: u32,
planes: u32,
block: u32,
) -> Self {
const STAGES: u32 = 3;
let stage = 8 * (positions + 16) + 2560 * planes;
let epilogue = 64 * (positions + 8);
let floats = if STAGES * stage > epilogue {
STAGES * stage
} else {
epilogue
};
Self {
kernel,
kind: Kind::TensorConv {
positions,
cin_pad,
planes,
},
block,
shared: 4 * floats,
}
}
const fn gemm(kernel: &'static str, rows: u32, cols: u32, block: u32) -> Self {
Self {
kernel,
kind: Kind::Gemm {
rows,
cols,
select: 0,
},
block,
shared: 0,
}
}
const fn select_gemm(kernel: &'static str, rows: u32, cols: u32, block: u32) -> Self {
Self {
kernel,
kind: Kind::Gemm {
rows,
cols,
select: SELECT_ROWS,
},
block,
shared: 0,
}
}
const fn classifier(kernel: &'static str, rows: u32, block: u32) -> Self {
Self {
kernel,
kind: Kind::Classifier { rows },
block,
shared: 0,
}
}
const fn split(kernel: &'static str, cols: u32, splits: u32, block: u32) -> Self {
Self {
kernel,
kind: Kind::Split {
rows: 96,
cols,
splits,
weights: SplitWeights::Fp32,
},
block,
shared: 0,
}
}
const fn half_split(splits: u32) -> Self {
Self {
kernel: "spk_segdense_embed_b32_f16",
kind: Kind::Split {
rows: 96,
cols: 128,
splits,
weights: SplitWeights::ScaledHalf,
},
block: 384,
shared: HALF_SHARED,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SegdensePin {
choice: Choice,
math: CudaMath,
tier: PtxTier,
}
impl SegdensePin {
pub(crate) const fn measured_rtx4060ti_embedding() -> Self {
Self {
choice: Choice::split(Entry::EmbedB32F16, 17),
math: CudaMath::Tf32,
tier: PtxTier::Sm80,
}
}
pub(crate) const fn measured_rtx5060ti_embedding() -> Self {
Self {
choice: Choice::split(Entry::EmbedB32F16, 18),
math: CudaMath::Tf32,
tier: PtxTier::Sm80,
}
}
pub(crate) fn select(
site: Site,
batch: usize,
math: CudaMath,
tier: PtxTier,
device: &DeviceAttributes,
) -> Result<Self, PlanError> {
let choice = site
.choice(batch, math, tier, Hardware::of(device))
.ok_or_else(|| {
PlanError::Geometry(GeometryError::Unimplemented {
context: CONTEXT,
reason: format!("{site:?} batch {batch}; the kernels compile batches 1 and 32"),
})
})?;
Ok(Self { choice, math, tier })
}
fn select_tuning_tf32(
site: Site,
batch: usize,
math: CudaMath,
tier: PtxTier,
device: &DeviceAttributes,
) -> Option<Self> {
if tier < PtxTier::Sm80 {
return None;
}
let choice = site.tuning_tf32_choice(batch, math, Hardware::of(device))?;
Some(Self { choice, math, tier })
}
fn select_fp32(
site: Site,
batch: usize,
math: CudaMath,
tier: PtxTier,
device: &DeviceAttributes,
) -> Result<Self, PlanError> {
let choice = site
.scalar_choice(batch, CudaMath::Fp32, Hardware::of(device))
.ok_or_else(|| {
PlanError::Geometry(GeometryError::Unimplemented {
context: CONTEXT,
reason: format!("{site:?} batch {batch}; the kernels compile batches 1 and 32"),
})
})?;
Ok(Self { choice, math, tier })
}
pub(crate) const fn entry(self) -> Entry {
self.choice.entry
}
pub(crate) const fn config(self) -> Config {
self.choice.config()
}
pub(crate) const fn splits(self) -> Option<u32> {
if self.choice.entry.is_split() {
Some(self.choice.splits as u32)
} else {
None
}
}
pub(super) fn check(
self,
site: Site,
batch: usize,
math: CudaMath,
tier: PtxTier,
) -> Result<(), PlanError> {
let entry = self.choice.entry;
if (entry.site(), entry.batch(), self.math) != (site, batch, math) {
return Err(PlanError::Geometry(GeometryError::Invalid {
context: CONTEXT,
reason: format!(
"pin {entry:?} {:?} cannot plan {site:?} batch {batch} {math:?}",
self.math
),
}));
}
if self.tier > tier {
return Err(PlanError::DeviceUnsupported {
reason: format!(
"{} needs the {} segdense module, the loaded one is {tier}",
self.config().kernel,
self.tier
),
});
}
Ok(())
}
}
#[derive(Debug)]
struct Producer {
main: CudaFunction,
reduce: Option<Reduction>,
config: Config,
weight: CudaSlice<f32>,
bias: CudaSlice<f32>,
partials: Scratch<f32>,
batch: usize,
rows: usize,
n: usize,
k: usize,
input_len: usize,
}
impl Producer {
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
weight: &CudaSlice<f32>,
bias: Option<&CudaSlice<f32>>,
pin: SegdensePin,
) -> Result<Self, PlanError> {
if kernels.request().area() != KernelModule::Segdense {
return Err(PlanError::Geometry(GeometryError::Invalid {
context: CONTEXT,
reason: format!(
"the {} module cannot run segdense plans",
kernels.request().area().name()
),
}));
}
let config = pin.config();
let site = pin.choice.entry.site();
let batch = pin.choice.entry.batch();
let (rows, n, k, input_len) = site.dimensions();
check_len(CONTEXT, n * k, weight.len())?;
if site.is_conv() != bias.is_none() {
return Err(PlanError::Geometry(GeometryError::Invalid {
context: CONTEXT,
reason: format!("{site:?} takes a bias exactly when it is not a convolution"),
}));
}
if let Some(bias) = bias {
check_len(CONTEXT, n, bias.len())?;
}
if let Kind::Split { splits, .. } = config.kind
&& !(1..=EMBED_MAX_SPLITS).contains(&splits)
{
return Err(PlanError::Geometry(GeometryError::Invalid {
context: CONTEXT,
reason: format!(
"{splits} split-K slices, the reduction adds 1 to {EMBED_MAX_SPLITS}"
),
}));
}
if let Kind::Split {
splits,
weights: SplitWeights::ScaledHalf,
..
} = config.kind
&& splits < HALF_MIN_SPLITS
{
return Err(PlanError::Geometry(GeometryError::Invalid {
context: CONTEXT,
reason: format!(
"{splits} split-K slices, the FP16 kernel needs at least {HALF_MIN_SPLITS}"
),
}));
}
let main = kernels.function(config.kernel)?;
if config.shared > DEFAULT_SHARED {
let optin = runtime.device().shared_optin_bytes();
if config.shared > optin {
return Err(PlanError::DeviceUnsupported {
reason: format!(
"{} needs {} bytes of shared memory, the device allows {optin}",
config.kernel, config.shared
),
});
}
main.set_attribute(
CUfunction_attribute::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
config.shared as i32,
)?;
}
let stream = runtime.stream();
let pack = kernels.function("spk_segdense_pack")?;
let packed = match config.kind {
Kind::Conv { .. } => {
pack_on_device(stream, &pack, weight, k * CONV_PADDED, [n, k, CONV_PADDED])?
}
Kind::Split {
weights: SplitWeights::ScaledHalf,
..
} => {
let weight = stream.clone_dtoh(weight)?;
stream.clone_htod(&pack_half_columns(&weight, n, k))?
}
Kind::Split { .. } => pack_on_device(stream, &pack, weight, k * n, [n, k, n])?,
Kind::TensorConv {
cin_pad, planes, ..
} => {
let (cin_pad, planes) = (cin_pad as usize, planes as usize);
let len = planes * cin_pad * TAPS * CONV_PADDED;
let function = kernels.function("spk_segdense_pack_conv_mma")?;
pack_on_device(stream, &function, weight, len, [k / TAPS, cin_pad, planes])?
}
Kind::Gemm { select, .. } if select > 0 => {
let mut weight = stream.clone_dtoh(weight)?;
let rows = selected_rows(&weight, n, select as usize);
weight.extend(rows.into_iter().map(f32::from_bits));
stream.clone_htod(&weight)?
}
Kind::Gemm { .. } | Kind::Classifier { .. } | Kind::Gemv { .. } => {
stream.clone_dtod(weight)?
}
};
let bias = match bias {
Some(bias) => stream.clone_dtod(bias)?,
None => stream.alloc_zeros(0)?,
};
let workspace = match config.kind {
Kind::Split { splits, .. } => splits as usize * batch * rows * n,
_ => 0,
};
let partials = Scratch::zeros(runtime, workspace)?;
let reduce = match config.kind {
Kind::Split { splits, .. } => {
let shape = Reduction::shape(splits);
Some(Reduction {
function: kernels.function(shape.kernel)?,
shape,
})
}
_ => None,
};
runtime.synchronize()?;
Ok(Self {
main,
reduce,
config,
weight: packed,
bias,
partials,
batch,
rows,
n,
k,
input_len,
})
}
fn enqueue(
&self,
input: &CudaView<'_, f32>,
output: &mut CudaViewMut<'_, f32>,
phases: &Phases,
stream: &CudaStream,
) -> Result<(), CudaError> {
let input_elements = if self.input_len == 0 {
self.batch * self.rows * self.k
} else {
self.batch * (self.k / TAPS) * self.input_len
};
check_len(CONTEXT, input_elements, input.len())?;
check_len(CONTEXT, self.batch * self.rows * self.n, output.len())?;
let m = (self.batch * self.rows) as u32;
let grid = match self.config.kind {
Kind::Conv { positions } | Kind::TensorConv { positions, .. } => (
self.rows.div_ceil(positions as usize) as u32,
self.batch as u32,
1,
),
Kind::Gemm { rows, cols, .. } => (m.div_ceil(rows), self.n as u32 / cols, 1),
Kind::Classifier { rows } => (m.div_ceil(rows), 1, 1),
Kind::Gemv { cols } => (self.n as u32 / cols, 1, 1),
Kind::Split {
rows, cols, splits, ..
} => (m.div_ceil(rows), self.n as u32 / cols, splits),
};
let config = LaunchConfig {
grid_dim: grid,
block_dim: (self.config.block, 1, 1),
shared_mem_bytes: self.config.shared,
};
let input_len = input.len() as u64;
let weight_len = self.weight.len() as u64;
let bias_len = self.bias.len() as u64;
let output_len = output.len() as u64;
let Some(reduce) = &self.reduce else {
return phases.op(Op::Main, || {
let mut launch = stream.launch_builder(&self.main);
launch
.arg(input)
.arg(&input_len)
.arg(&self.weight)
.arg(&weight_len);
if !matches!(
self.config.kind,
Kind::Conv { .. } | Kind::TensorConv { .. }
) {
launch.arg(&self.bias).arg(&bias_len);
}
launch.arg(&mut *output).arg(&output_len);
unsafe { launch.launch(config) }?;
Ok(())
});
};
let mut partials = self.partials.get();
let partials_len = partials.len() as u64;
phases.op(Op::Main, || {
let mut launch = stream.launch_builder(&self.main);
launch
.arg(input)
.arg(&input_len)
.arg(&self.weight)
.arg(&weight_len)
.arg(&self.bias)
.arg(&bias_len)
.arg(&mut *partials)
.arg(&partials_len);
unsafe { launch.launch(config) }?;
Ok(())
})?;
let shape = reduce.shape;
phases.op(Op::Reduce, || {
let mut launch = stream.launch_builder(&reduce.function);
launch
.arg(&*partials)
.arg(&partials_len)
.arg(&self.bias)
.arg(&bias_len)
.arg(&mut *output)
.arg(&output_len)
.arg(&shape.splits);
let config = LaunchConfig {
grid_dim: (shape.grid, 1, 1),
block_dim: (shape.block, 1, 1),
shared_mem_bytes: 0,
};
unsafe { launch.launch(config) }?;
Ok(())
})
}
}
fn pack_on_device(
stream: &Arc<CudaStream>,
function: &CudaFunction,
source: &CudaSlice<f32>,
len: usize,
dims: [usize; 3],
) -> Result<CudaSlice<f32>, CudaError> {
let mut weight = stream.alloc_zeros(len)?;
let lens = [source.len() as u64, weight.len() as u64];
let dims = dims.map(|value| value as u32);
let mut launch = stream.launch_builder(function);
launch
.arg(source)
.arg(&lens[0])
.arg(&mut weight)
.arg(&lens[1])
.arg(&dims[0])
.arg(&dims[1])
.arg(&dims[2]);
unsafe { launch.launch(LaunchConfig::for_num_elems(len as u32)) }?;
Ok(weight)
}
pub(super) fn selected_rows(weight: &[f32], n: usize, count: usize) -> Vec<u32> {
let mut energy: Vec<(f64, u32)> = weight
.chunks_exact(n)
.zip(0u32..)
.map(|(row, index)| {
let sum = row.iter().map(|&value| f64::from(value).powi(2)).sum();
(sum, index)
})
.collect();
energy.sort_by(|a, b| b.0.total_cmp(&a.0).then(a.1.cmp(&b.1)));
let mut rows: Vec<u32> = energy
.into_iter()
.take(count)
.map(|(_, index)| index)
.collect();
rows.sort_unstable();
rows
}
pub(super) fn pack_half_columns(weight: &[f32], n: usize, k: usize) -> Vec<f32> {
let mut packed = vec![0.0; k / 2 * n + n];
for (col, row) in weight.chunks_exact(k).take(n).enumerate() {
let largest = row
.iter()
.fold(0.0f32, |largest, value| largest.max(value.abs()));
let (scale, inverse) = half_scale(largest);
for (pair, &[first, second]) in row.as_chunks::<2>().0.iter().enumerate() {
let low = u32::from(f16_bits(first * scale));
let high = u32::from(f16_bits(second * scale));
packed[pair * n + col] = f32::from_bits(low | (high << 16));
}
packed[k / 2 * n + col] = inverse;
}
packed
}
pub(super) fn half_scale(largest: f32) -> (f32, f32) {
let exponent = ((largest.to_bits() >> 23) & 0xff) as i32 - 127;
let shift = (14 - exponent).clamp(-113, 126);
(
f32::from_bits(((shift + 127) as u32) << 23),
f32::from_bits(((127 - shift) as u32) << 23),
)
}
pub(crate) fn f16_bits(value: f32) -> u16 {
let bits = value.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let exponent = ((bits >> 23) & 0xff) as i32;
let mantissa = bits & 0x7f_ffff;
if exponent == 0xff {
return sign | 0x7c00 | if mantissa == 0 { 0 } else { 0x200 };
}
let half_exponent = exponent - 127 + 15;
if half_exponent >= 1 {
let base = ((half_exponent as u32) << 10) | (mantissa >> 13);
let rounded = base + u32::from(round_up(mantissa, 13, base));
return sign | rounded.min(0x7c00) as u16;
}
let shift = 126 - exponent;
if exponent == 0 || shift > 24 {
return sign;
}
let significand = mantissa | 0x80_0000;
let steps = significand >> shift;
sign | (steps + u32::from(round_up(significand, shift as u32, steps))) as u16
}
fn round_up(value: u32, bits: u32, kept: u32) -> bool {
let rest = value & ((1 << bits) - 1);
let half = 1 << (bits - 1);
rest > half || (rest == half && kept & 1 == 1)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct ReductionShape {
pub(super) kernel: &'static str,
pub(super) splits: u32,
pub(super) grid: u32,
pub(super) block: u32,
}
#[derive(Debug)]
pub(super) struct Reduction {
function: CudaFunction,
shape: ReductionShape,
}
impl Reduction {
pub(super) const fn shape(splits: u32) -> ReductionShape {
const OUTPUTS: u32 = 96 * 256;
let fixed = match splits {
18 => Some("spk_segdense_reduce_e18"),
34 => Some("spk_segdense_reduce_e34"),
36 => Some("spk_segdense_reduce_e36"),
_ => None,
};
if let Some(kernel) = fixed {
return ReductionShape {
kernel,
splits,
grid: OUTPUTS / 4 / 128,
block: 128,
};
}
if splits <= FLAT_MAX_SPLITS {
return ReductionShape {
kernel: "spk_segdense_reduce_embed_flat",
splits,
grid: OUTPUTS / 4 / 64,
block: 64,
};
}
ReductionShape {
kernel: "spk_segdense_reduce_embed",
splits,
grid: OUTPUTS / 128,
block: 256,
}
}
}
#[derive(Debug)]
pub(crate) struct DenseOxide(Producer);
impl DenseCandidate for DenseOxide {
type Pin = SegdensePin;
const COVERAGE: Coverage = Coverage(&[CoverageEntry {
layers: &["linear0", "linear1", "linear2", "resnet.seg_1"],
batches: Batches::Only(&BATCHES),
maths: Maths::All,
}]);
const SPECIAL_VALUES: SpecialValues = SPECIAL_VALUES;
fn implemented_pin(
spec: DenseSpec,
tier: PtxTier,
device: &DeviceAttributes,
) -> Result<SegdensePin, PlanError> {
SegdensePin::select(spec.site().into(), spec.batch(), spec.math(), tier, device)
}
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: DenseSpec,
weight: &CudaSlice<f32>,
bias: &CudaSlice<f32>,
pin: SegdensePin,
) -> Result<Self, PlanError> {
pin.check(
spec.site().into(),
spec.batch(),
spec.math(),
kernels.tier(),
)?;
Producer::plan(runtime, kernels, weight, Some(bias), pin).map(Self)
}
fn enqueue(
&self,
x: &CudaView<'_, f32>,
output: &mut CudaViewMut<'_, f32>,
phases: &Phases,
runtime: &CudaRuntime,
) -> Result<(), CudaError> {
self.0.enqueue(x, output, phases, runtime.stream())
}
}
#[derive(Debug)]
pub(crate) struct SegConvOxide(Producer);
impl SegConvCandidate for SegConvOxide {
type Pin = SegdensePin;
const COVERAGE: Coverage = Coverage(&[CoverageEntry {
layers: &["sincnet.conv1", "sincnet.conv2"],
batches: Batches::Only(&BATCHES),
maths: Maths::All,
}]);
const SPECIAL_VALUES: SpecialValues = SPECIAL_VALUES;
fn implemented_pin(
spec: SegConvSpec,
tier: PtxTier,
device: &DeviceAttributes,
) -> Result<SegdensePin, PlanError> {
SegdensePin::select(spec.site().into(), spec.batch(), spec.math(), tier, device)
}
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: SegConvSpec,
weight: &CudaSlice<f32>,
pin: SegdensePin,
) -> Result<Self, PlanError> {
pin.check(
spec.site().into(),
spec.batch(),
spec.math(),
kernels.tier(),
)?;
Producer::plan(runtime, kernels, weight, None, pin).map(Self)
}
fn enqueue(
&self,
x: &CudaView<'_, f32>,
output: &mut CudaViewMut<'_, f32>,
phases: &Phases,
runtime: &CudaRuntime,
) -> Result<(), CudaError> {
self.0.enqueue(x, output, phases, runtime.stream())
}
}
pub(crate) struct Area;
impl super::DriverCandidate for Area {
const AREA: KernelModule = KernelModule::Segdense;
fn driver_coverage(
_tier: PtxTier,
_device: &DeviceAttributes,
_fp16: super::Fp16Policy,
) -> Coverage {
Coverage(&[CoverageEntry {
layers: &[
"sincnet.conv1",
"sincnet.conv2",
"linear0",
"linear1",
"linear2",
"resnet.seg_1",
],
batches: Batches::Only(&BATCHES),
maths: Maths::All,
}])
}
fn broad_evidence() -> Option<&'static crate::inference::cuda::implementation::BroadEvidence> {
use crate::inference::cuda::implementation::{ArchitectureSpeed, BroadEvidence};
const EVIDENCE: BroadEvidence = BroadEvidence::with_limits(
&[
ArchitectureSpeed {
capability: ComputeCapability::new(8, 9),
minimum_speedup_milli: 1060,
},
ArchitectureSpeed {
capability: ComputeCapability::new(12, 0),
minimum_speedup_milli: 1060,
},
],
"segdense sm80: fused conv/pool producers and packed dense epilogues; do-segdense dev reports on Ada and Blackwell, every measured case >=1.06x; A100 24/24 dev",
ComputeCapability::new(8, 0),
PtxTier::Sm80,
);
Some(&EVIDENCE)
}
fn tuning_fp32_pin(
boundary: crate::inference::cuda::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &DeviceAttributes,
tier: PtxTier,
) -> Result<Option<super::ConfigPin>, PlanError> {
SegdensePin::select_fp32(Site::from_boundary(boundary)?, batch, math, tier, device)
.map(super::ConfigPin::Segdense)
.map(Some)
}
fn tuning_tf32_pin(
boundary: crate::inference::cuda::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &DeviceAttributes,
tier: PtxTier,
) -> Result<Option<super::ConfigPin>, PlanError> {
let site = Site::from_boundary(boundary)?;
Ok(
SegdensePin::select_tuning_tf32(site, batch, math, tier, device)
.map(super::ConfigPin::Segdense),
)
}
fn driver_pin(
boundary: crate::inference::cuda::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &DeviceAttributes,
tier: PtxTier,
_fp16: super::Fp16Policy,
) -> Result<super::ConfigPin, PlanError> {
let site = Site::from_boundary(boundary)?;
SegdensePin::select(site, batch, math, tier, device).map(super::ConfigPin::Segdense)
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
pub(crate) mod test_support;