use std::cell::{RefCell, RefMut};
use std::sync::Arc;
use cudarc::driver::{
CudaEvent, CudaSlice, CudaStream, CudaView, CudaViewMut, DeviceRepr, ValidAsZeroBits,
};
use super::device::DeviceAttributes;
pub(crate) use super::error::{GeometryError, WeightFault};
use super::geometry::Conv2d;
use super::{CudaError, CudaMath, CudaRuntime, KernelModule, LoadedKernels, PtxTier, Sgemm};
mod conv;
mod fbank;
mod lstm;
mod lstmproj;
mod segdense;
mod sinc;
mod wideconv;
#[cfg(test)]
pub(super) use kernel_inventory::conv_kernel_inventory;
#[cfg(test)]
mod kernel_inventory {
pub(crate) const WIDECONV_KERNELS: &[&str] = super::wideconv::entries::ALL;
pub(crate) const SEGDENSE_KERNELS: [&str; 32] = [
"spk_segdense_pack",
"spk_segdense_pack_conv_mma",
"spk_segdense_conv1_b1",
"spk_segdense_conv1_b32",
"spk_segdense_conv1_b32_tc",
"spk_segdense_conv1_b32_x3",
"spk_segdense_conv2_b1",
"spk_segdense_conv2_b32",
"spk_segdense_conv2_b32_tc",
"spk_segdense_conv2_b32_x3",
"spk_segdense_linear0_b1",
"spk_segdense_linear0_b1_tf32",
"spk_segdense_linear0_b32",
"spk_segdense_linear0_b32_tf32",
"spk_segdense_linear1_b1",
"spk_segdense_linear1_b1_tf32",
"spk_segdense_linear1_b32",
"spk_segdense_linear1_b32_tf32",
"spk_segdense_classifier_b1",
"spk_segdense_classifier_b32",
"spk_segdense_embed_b1",
"spk_segdense_embed_b32",
"spk_segdense_embed_b32_tf32",
"spk_segdense_embed_b32_tf32_k2",
"spk_segdense_embed_b32_tf32_e64",
"spk_segdense_embed_b32_x3",
"spk_segdense_embed_b32_f16",
"spk_segdense_reduce_embed",
"spk_segdense_reduce_embed_flat",
"spk_segdense_reduce_e18",
"spk_segdense_reduce_e34",
"spk_segdense_reduce_e36",
];
use super::ConvShape as Shape;
use super::conv::{REQUIRED_KERNELS, SMALL_BATCH_WAVES, select_tiling};
pub(crate) fn conv_kernel_inventory() -> Vec<&'static str> {
let mut entries = REQUIRED_KERNELS.to_vec();
for shape in [Shape::C32, Shape::C64, Shape::C32Stride2] {
let (large, small) = shape.tilings();
let output = [16, 64];
let threshold = large.blocks(1, output).div_ceil(SMALL_BATCH_WAVES);
for multiprocessors in [1, threshold, threshold + 1] {
entries.push(select_tiling(large, small, 1, output, multiprocessors).entry);
}
}
entries
}
}
#[cfg(test)]
pub(super) use fbank::REQUIRED_KERNELS as FBANK_DFT_KERNELS;
#[cfg(test)]
pub(super) use kernel_inventory::{SEGDENSE_KERNELS, WIDECONV_KERNELS};
#[cfg(test)]
pub(super) use lstm::REQUIRED_KERNELS as LSTM_KERNELS;
#[cfg(test)]
pub(super) use lstmproj::REQUIRED_KERNELS as LSTMPROJ_KERNELS;
#[cfg(test)]
pub(super) use sinc::REQUIRED_KERNELS as SINC_KERNELS;
#[cfg(all(test, feature = "_cuda-libraries"))]
pub(super) use segdense::f16_bits;
pub(crate) use conv::Oxide as ConvOxide;
pub(crate) use fbank::Oxide as FbankOxide;
pub(crate) use lstm::Oxide as LstmOxide;
pub(crate) use segdense::{Area as SegdenseArea, Entry as SegdenseEntry, SegdensePin};
pub(crate) use segdense::{DenseOxide, SegConvOxide};
pub(crate) use lstmproj::Oxide as LstmProjOxide;
pub(crate) use sinc::Oxide as SincOxide;
#[cfg(all(test, feature = "_cuda-libraries"))]
pub(crate) use lstmproj::RecurrencePlan;
#[cfg(all(test, feature = "_cuda-libraries"))]
pub(crate) use wideconv::Device as WideconvDevice;
#[cfg(test)]
pub(crate) use wideconv::Fp16Tiles as WideconvFp16Tiles;
pub(crate) use wideconv::{
Algorithm as WideconvAlgorithm, FP16_OPERAND_LIMIT, Fp16Policy,
TensorKernel as WideconvTensorKernel, WinogradProducts as WideconvProducts,
};
#[cfg(test)]
pub(crate) use wideconv::{
Config as WideconvConfig, Partition as WideconvPartition, SplitCells as WideconvSplitCells,
};
pub(crate) use wideconv::{HalfIo, Oxide as WideconvOxide, Pin as WideconvPin};
#[derive(Debug, thiserror::Error)]
pub(crate) enum PlanError {
#[error("{reason}")]
DeviceUnsupported {
reason: String,
},
#[error("{layer}: {fault}")]
WeightsOutOfContract {
layer: &'static str,
fault: WeightFault,
},
#[error(transparent)]
Geometry(GeometryError),
#[error(transparent)]
Cuda(#[from] CudaError),
}
impl From<cudarc::driver::DriverError> for PlanError {
fn from(error: cudarc::driver::DriverError) -> Self {
Self::Cuda(error.into())
}
}
pub(crate) const QUALIFIED_BATCHES: [usize; 2] = [1, 32];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConfigPin {
Conv(ConvPin),
Wideconv(WideconvPin),
Lstm(LstmPin),
Sinc(SincPin),
Fbank(FbankPin),
Segdense(SegdensePin),
}
impl ConfigPin {
pub(crate) const fn area(self) -> KernelModule {
match self {
Self::Conv(_) => KernelModule::Resnet,
Self::Lstm(LstmPin::LegacyCooperative) => KernelModule::Lstm,
Self::Lstm(LstmPin::Projected(_)) => KernelModule::LstmProj,
Self::Wideconv(_) => KernelModule::Wideconv,
Self::Sinc(_) => KernelModule::Sincnet,
Self::Fbank(_) => KernelModule::FbankDft,
Self::Segdense(_) => KernelModule::Segdense,
}
}
pub(crate) const fn is_device_rule(self) -> bool {
matches!(
self,
Self::Conv(ConvPin::LegacyWaves(_))
| Self::Wideconv(WideconvPin::DeviceRule)
| Self::Lstm(LstmPin::LegacyCooperative)
)
}
pub(crate) const fn is_fp16(self) -> bool {
matches!(
self,
Self::Wideconv(WideconvPin::Configured(wideconv::Config {
algorithm: WideconvAlgorithm::Fp16(_),
..
}))
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FbankPin {
FftMelAccurate,
}
pub(crate) use super::fbank::{FBANK_FRAMES, FBANK_MEL_BINS, FBANK_WINDOW_SAMPLES, FbankConstants};
pub(crate) const FBANK_BATCHES: [usize; 32] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32,
];
#[derive(Debug, Clone, Copy)]
pub(crate) struct FbankSpec {
batch: usize,
math: CudaMath,
}
impl FbankSpec {
pub(crate) fn new(batch: usize, math: CudaMath) -> Result<Self, PlanError> {
if !super::implementation::BoundaryId::named("fbank.dft")
.batches()
.contains(batch)
{
return Err(PlanError::Geometry(GeometryError::Invalid {
context: "fbank.dft",
reason: format!("fbank.dft batch {batch} is outside 1..=32"),
}));
}
Ok(Self { batch, math })
}
pub(crate) const fn batch(self) -> usize {
self.batch
}
pub(crate) const fn math(self) -> CudaMath {
self.math
}
}
pub(crate) trait FbankCandidate: Sized {
type Pin;
const COVERAGE: Coverage;
const SPECIAL_VALUES: SpecialValues;
fn coverage(_tier: PtxTier) -> Coverage {
Self::COVERAGE
}
fn implemented_pin(spec: FbankSpec) -> Result<Self::Pin, PlanError>;
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: FbankSpec,
pin: Self::Pin,
) -> Result<Self, PlanError>;
fn enqueue(
&self,
waveform: &CudaView<'_, f32>,
energies: &mut CudaViewMut<'_, f32>,
phases: &Phases,
runtime: &CudaRuntime,
) -> Result<(), CudaError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConvShape {
C32,
C64,
C32Stride2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConvKernel {
C32,
C64,
C64Small,
C32Stride2,
C32Stride2Small,
C32Tensor,
C64Tensor,
C64TensorSlim,
C32Stride2Tensor,
}
impl ConvKernel {
pub(crate) const fn shape(self) -> ConvShape {
match self {
Self::C32 | Self::C32Tensor => ConvShape::C32,
Self::C64 | Self::C64Small | Self::C64Tensor | Self::C64TensorSlim => ConvShape::C64,
Self::C32Stride2 | Self::C32Stride2Small | Self::C32Stride2Tensor => {
ConvShape::C32Stride2
}
}
}
}
impl ConvShape {
pub(crate) const fn tensor_kernel(self) -> ConvKernel {
match self {
Self::C32 => ConvKernel::C32Tensor,
Self::C64 => ConvKernel::C64Tensor,
Self::C32Stride2 => ConvKernel::C32Stride2Tensor,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConvPin {
Kernel(ConvKernel),
LegacyWaves(ConvShape),
}
impl ConvPin {
pub(crate) const fn shape(self) -> ConvShape {
match self {
Self::Kernel(kernel) => kernel.shape(),
Self::LegacyWaves(shape) => shape,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LstmPin {
LegacyCooperative,
Projected(LstmProjection),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LstmProjection {
Small,
Large,
Tensor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SincPin {
ConvAbsPool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SpecialValues {
pub finite: FiniteContract,
pub nan: NanContract,
pub infinity: InfinityContract,
pub signed_zero: SignedZeroContract,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FiniteContract {
UnitWaveform,
AbsoluteSum {
headroom: u32,
},
BoundedActivation {
headroom: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NanContract {
Propagates,
PoolingIgnores,
Unspecified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InfinityContract {
Ieee,
Unspecified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SignedZeroContract {
ReluKeepsNegative,
Positive,
Unspecified,
}
pub(crate) const TEST_BATCHES: [usize; 5] = [1, 7, 32, 33, 64];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Batches {
All,
Only(&'static [usize]),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Maths {
All,
Only(&'static [CudaMath]),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CoverageEntry {
pub layers: &'static [&'static str],
pub batches: Batches,
pub maths: Maths,
}
impl CoverageEntry {
fn covers(&self, layer: &str, batch: usize, math: CudaMath) -> bool {
let batch_covered = match self.batches {
Batches::All => batch > 0,
Batches::Only(batches) => batches.contains(&batch),
};
let math_covered = match self.maths {
Maths::All => true,
Maths::Only(maths) => maths.contains(&math),
};
batch_covered && math_covered && self.layers.contains(&layer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Coverage(pub &'static [CoverageEntry]);
impl Coverage {
pub(crate) const NONE: Self = Self(&[]);
pub(crate) fn covers(&self, layer: &str, batch: usize, math: CudaMath) -> bool {
self.0.iter().any(|entry| entry.covers(layer, batch, math))
}
pub(crate) fn entries(&self) -> &'static [CoverageEntry] {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Epilogue {
Bias,
BiasRelu,
BiasReluResidual,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ConvLayerSpec<'a> {
pub name: &'a str,
pub conv: Conv2d,
pub epilogue: Epilogue,
pub weight: &'a CudaSlice<f32>,
pub bias: &'a CudaSlice<f32>,
}
#[derive(Debug)]
pub(crate) struct ConvInputs<'a, 'b> {
pub x: &'a CudaView<'b, f32>,
pub residual: Option<&'a CudaView<'b, f32>>,
pub weight: &'a CudaView<'b, f32>,
pub bias: &'a CudaView<'b, f32>,
}
pub(crate) trait ConvCandidate: Sized {
type Pin;
const COVERAGE: Coverage;
const SPECIAL_VALUES: SpecialValues;
fn coverage(_tier: PtxTier) -> Coverage {
Self::COVERAGE
}
fn implemented_pin(layer: &ConvLayerSpec<'_>) -> Result<Self::Pin, PlanError>;
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
layer: ConvLayerSpec<'_>,
pin: Self::Pin,
) -> Result<Self, PlanError>;
fn enqueue(
&self,
inputs: ConvInputs<'_, '_>,
y: &mut CudaViewMut<'_, f32>,
phases: &Phases,
stream: &CudaStream,
) -> Result<(), CudaError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SincOutput {
RawConv,
Pooled,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SincSpec<'a> {
pub batch: usize,
pub samples: usize,
pub sinc: usize,
pub pooled: usize,
pub math: CudaMath,
pub filters: &'a CudaSlice<f32>,
}
#[derive(Debug)]
pub(crate) struct SincInputs<'a, 'b> {
pub waveform: &'a CudaView<'b, f32>,
pub filters: &'a CudaView<'b, f32>,
}
pub(crate) trait SincCandidate: Sized {
const COVERAGE: Coverage;
const SPECIAL_VALUES: SpecialValues;
fn coverage(_tier: PtxTier) -> Coverage {
Self::COVERAGE
}
const OUTPUT: SincOutput;
fn implemented_pin(spec: &SincSpec<'_>) -> Result<SincPin, PlanError>;
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: SincSpec<'_>,
pin: SincPin,
) -> Result<Self, PlanError>;
fn enqueue(
&self,
inputs: SincInputs<'_, '_>,
output: &mut CudaViewMut<'_, f32>,
phases: &Phases,
stream: &CudaStream,
) -> Result<(), CudaError>;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct LstmLayerWeights<'a> {
pub input: usize,
pub w: &'a [f32],
pub r: &'a [f32],
pub b: &'a [f32],
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct LstmSpec<'a> {
pub batch: usize,
pub frames: usize,
pub math: CudaMath,
pub layers: [LstmLayerWeights<'a>; 4],
}
pub(crate) trait LstmCandidate: Sized {
const COVERAGE: Coverage;
const SPECIAL_VALUES: SpecialValues;
fn coverage(_tier: PtxTier) -> Coverage {
Self::COVERAGE
}
fn implemented_pin(spec: &LstmSpec<'_>) -> Result<LstmPin, PlanError>;
fn device_pin(
device: &DeviceAttributes,
tier: PtxTier,
spec: &LstmSpec<'_>,
) -> Result<LstmPin, PlanError>;
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: LstmSpec<'_>,
pin: LstmPin,
) -> Result<Self, PlanError>;
fn enqueue(
&self,
input: &CudaView<'_, f32>,
output: &mut CudaViewMut<'_, f32>,
phases: &LstmPhases<'_>,
stream: &CudaStream,
) -> Result<(), CudaError>;
fn diagnostic_layers(&self, _stream: &CudaStream) -> Vec<(usize, Vec<f32>)> {
Vec::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Direction {
Forward,
Reverse,
}
impl Direction {
pub(crate) fn name(self) -> &'static str {
match self {
Self::Forward => "forward",
Self::Reverse => "reverse",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Op {
Prologue,
Pack,
Main,
Reduce,
Epilogue,
}
#[derive(Debug)]
pub(crate) struct Phases(());
impl Phases {
pub(super) fn new() -> Self {
Self(())
}
pub(crate) fn op<T>(
&self,
_op: Op,
enqueue: impl FnOnce() -> Result<T, CudaError>,
) -> Result<T, CudaError> {
enqueue()
}
}
#[derive(Debug)]
pub(crate) struct LstmPhases<'a> {
phases: Phases,
projection: Projection<'a>,
}
impl<'a> LstmPhases<'a> {
pub(super) fn new(projection: Projection<'a>) -> Self {
Self {
phases: Phases::new(),
projection,
}
}
pub(crate) fn op<T>(
&self,
op: Op,
enqueue: impl FnOnce() -> Result<T, CudaError>,
) -> Result<T, CudaError> {
self.phases.op(op, enqueue)
}
pub(crate) fn input_proj<T>(
&self,
layer: usize,
_direction: Direction,
enqueue: impl FnOnce(&Projection<'_>) -> Result<T, CudaError>,
) -> Result<T, CudaError> {
check_layer(layer)?;
enqueue(&self.projection)
}
pub(crate) fn recurrence<T>(
&self,
layer: usize,
_direction: Direction,
enqueue: impl FnOnce() -> Result<T, CudaError>,
) -> Result<T, CudaError> {
check_layer(layer)?;
enqueue()
}
}
fn check_layer(layer: usize) -> Result<(), CudaError> {
if layer < 4 {
return Ok(());
}
Err(CudaError::Unsupported {
context: "LSTM phase",
reason: format!("layer {layer} is not one of the four stack layers"),
})
}
#[derive(Debug)]
pub(crate) struct Scratch<T>(RefCell<CudaSlice<T>>);
impl<T: DeviceRepr + ValidAsZeroBits> Scratch<T> {
pub(crate) fn zeros(runtime: &CudaRuntime, len: usize) -> Result<Self, CudaError> {
Ok(Self(RefCell::new(runtime.stream().alloc_zeros(len)?)))
}
pub(crate) fn from_host(runtime: &CudaRuntime, values: &[T]) -> Result<Self, CudaError> {
Ok(Self(RefCell::new(runtime.stream().clone_htod(values)?)))
}
pub(crate) fn get(&self) -> RefMut<'_, CudaSlice<T>> {
self.0.borrow_mut()
}
}
#[derive(Debug)]
pub(crate) struct SideStream {
stream: Arc<CudaStream>,
forked: CudaEvent,
joined: CudaEvent,
}
impl SideStream {
pub(crate) fn new(runtime: &CudaRuntime) -> Result<Self, CudaError> {
let context = runtime.context();
let stream = context.new_stream()?;
Ok(Self {
stream,
forked: context.new_event(None)?,
joined: context.new_event(None)?,
})
}
pub(crate) fn stream(&self) -> &Arc<CudaStream> {
&self.stream
}
pub(crate) fn split(&self, parent: &CudaStream) -> Result<(), CudaError> {
self.forked.record(parent)?;
self.stream.wait(&self.forked)?;
Ok(())
}
pub(crate) fn merge(&self, parent: &CudaStream) -> Result<(), CudaError> {
self.joined.record(&self.stream)?;
parent.wait(&self.joined)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct ProjectionGemm {
pub n: usize,
pub weight_transposed: bool,
pub beta: f32,
}
#[derive(Debug)]
pub(crate) struct Projection<'a> {
runtime: &'a CudaRuntime,
rows: usize,
math: CudaMath,
}
impl<'a> Projection<'a> {
pub(crate) const COLUMNS: [usize; 4] = [128, 256, 384, 512];
pub(super) fn new(runtime: &'a CudaRuntime, rows: usize, math: CudaMath) -> Self {
Self {
runtime,
rows,
math,
}
}
pub(crate) fn project(
&self,
layer: usize,
_direction: Direction,
gemm: ProjectionGemm,
a: &CudaView<'_, f32>,
weight: &CudaView<'_, f32>,
c: &mut CudaViewMut<'_, f32>,
) -> Result<(), CudaError> {
let context = "LSTM input projection";
let k = match layer {
0 => 60,
1..=3 => 256,
_ => {
return Err(CudaError::Unsupported {
context,
reason: format!("layer {layer} is not one of the four stack layers"),
});
}
};
if !Self::COLUMNS.contains(&gemm.n) || ![0.0, 1.0].contains(&gemm.beta) {
return Err(CudaError::Unsupported {
context,
reason: format!("n = {}, beta = {} is not a projection", gemm.n, gemm.beta),
});
}
let spec = Sgemm {
b_transposed: gemm.weight_transposed,
beta: gemm.beta,
math: self.math,
..Sgemm::new(self.rows, gemm.n, k)
};
self.runtime.sgemm(spec, a, weight, c)
}
}
#[cfg(test)]
mod tests {
use super::{
Batches, Coverage, CoverageEntry, CudaError, CudaMath, CudaRuntime, CudaStream,
CudaViewMut, FiniteContract, InfinityContract, LoadedKernels, Maths, NanContract, Phases,
PlanError, PtxTier, SignedZeroContract, SincCandidate, SincInputs, SincOutput, SincPin,
SincSpec, SpecialValues,
};
struct TierFixture;
impl SincCandidate for TierFixture {
const COVERAGE: Coverage = Coverage::NONE;
const SPECIAL_VALUES: SpecialValues = SpecialValues {
finite: FiniteContract::AbsoluteSum { headroom: 2 },
nan: NanContract::Unspecified,
infinity: InfinityContract::Unspecified,
signed_zero: SignedZeroContract::Unspecified,
};
const OUTPUT: SincOutput = SincOutput::Pooled;
fn implemented_pin(_spec: &SincSpec<'_>) -> Result<SincPin, PlanError> {
Ok(SincPin::ConvAbsPool)
}
fn coverage(tier: PtxTier) -> Coverage {
match tier {
PtxTier::Sm75 => Coverage(&[CoverageEntry {
layers: &["sincnet.conv0.abs_pool"],
batches: Batches::Only(&[1]),
maths: Maths::Only(&[CudaMath::Fp32]),
}]),
PtxTier::Sm80 => Coverage(&[CoverageEntry {
layers: &["sincnet.conv0.abs_pool"],
batches: Batches::Only(&[32]),
maths: Maths::Only(&[CudaMath::Tf32]),
}]),
_ => Coverage::NONE,
}
}
fn plan(
_runtime: &CudaRuntime,
_kernels: &LoadedKernels,
_spec: SincSpec<'_>,
_pin: SincPin,
) -> Result<Self, PlanError> {
unreachable!("coverage-only fixture does not construct GPU plans")
}
fn enqueue(
&self,
_inputs: SincInputs<'_, '_>,
_output: &mut CudaViewMut<'_, f32>,
_phases: &Phases,
_stream: &CudaStream,
) -> Result<(), CudaError> {
unreachable!("coverage-only fixture does not launch kernels")
}
}
#[test]
fn per_tier_coverage_is_not_the_static_default_or_another_tiers_triples() {
let layer = "sincnet.conv0.abs_pool";
let baseline = TierFixture::coverage(PtxTier::Sm75);
let higher = TierFixture::coverage(PtxTier::Sm80);
assert!(TierFixture::COVERAGE.entries().is_empty());
assert!(baseline.covers(layer, 1, CudaMath::Fp32));
assert!(!baseline.covers(layer, 32, CudaMath::Tf32));
assert!(higher.covers(layer, 32, CudaMath::Tf32));
assert!(!higher.covers(layer, 1, CudaMath::Fp32));
assert!(TierFixture::coverage(PtxTier::Sm120).entries().is_empty());
}
}
#[cfg(all(test, feature = "_cuda-libraries"))]
#[path = "candidate_tests.rs"]
mod candidate_tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DenseSite {
Linear0,
Linear1,
Classifier,
Embedding,
}
impl DenseSite {
pub(crate) fn boundary(self) -> super::implementation::BoundaryId {
super::implementation::BoundaryId::named(match self {
Self::Linear0 => "linear0",
Self::Linear1 => "linear1",
Self::Classifier => "linear2",
Self::Embedding => "resnet.seg_1",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DenseEpilogue {
Bias,
BiasLeakyRelu,
BiasLogSoftmax,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct DenseSpec {
site: DenseSite,
batch: usize,
math: CudaMath,
}
impl DenseSpec {
pub(crate) fn new(site: DenseSite, batch: usize, math: CudaMath) -> Result<Self, PlanError> {
let spec = Self { site, batch, math };
let (m, n, k) = spec.dimensions();
validate_fixed_batch("dense plan", batch, &[m, n, k, m * n, m * k, n * k])?;
Ok(spec)
}
pub(crate) fn check_rows(self, rows: usize) -> Result<(), PlanError> {
if rows == self.dimensions().0 {
return Ok(());
}
Err(PlanError::Geometry(GeometryError::Unimplemented {
context: "dense plan",
reason: format!(
"{} requires {} rows per item, got {rows}",
self.site.boundary(),
self.dimensions().0
),
}))
}
pub(crate) const fn site(self) -> DenseSite {
self.site
}
pub(crate) const fn batch(self) -> usize {
self.batch
}
pub(crate) const fn math(self) -> CudaMath {
self.math
}
pub(crate) const fn dimensions(self) -> (usize, usize, usize) {
match self.site {
DenseSite::Linear0 => (589, 128, 256),
DenseSite::Linear1 => (589, 128, 128),
DenseSite::Classifier => (589, 7, 128),
DenseSite::Embedding => (3, 256, 5120),
}
}
pub(crate) const fn epilogue(self) -> DenseEpilogue {
match self.site {
DenseSite::Linear0 | DenseSite::Linear1 => DenseEpilogue::BiasLeakyRelu,
DenseSite::Classifier => DenseEpilogue::BiasLogSoftmax,
DenseSite::Embedding => DenseEpilogue::Bias,
}
}
pub(crate) const fn transposed_weights(self) -> bool {
matches!(self.site, DenseSite::Embedding)
}
pub(crate) const fn beta(self) -> f32 {
if self.transposed_weights() { 1.0 } else { 0.0 }
}
pub(crate) fn input_len(self) -> usize {
let (m, _, k) = self.dimensions();
self.batch * m * k
}
pub(crate) fn weight_len(self) -> usize {
let (_, n, k) = self.dimensions();
n * k
}
pub(crate) fn bias_len(self) -> usize {
self.dimensions().1
}
pub(crate) fn output_len(self) -> usize {
let (m, n, _) = self.dimensions();
self.batch * m * n
}
}
pub(crate) trait DenseCandidate: Sized {
type Pin;
const COVERAGE: Coverage;
const SPECIAL_VALUES: SpecialValues;
fn coverage(_tier: PtxTier) -> Coverage {
Self::COVERAGE
}
fn implemented_pin(
spec: DenseSpec,
tier: PtxTier,
device: &DeviceAttributes,
) -> Result<Self::Pin, PlanError>;
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: DenseSpec,
weight: &CudaSlice<f32>,
bias: &CudaSlice<f32>,
pin: Self::Pin,
) -> Result<Self, PlanError>;
fn enqueue(
&self,
x: &CudaView<'_, f32>,
output: &mut CudaViewMut<'_, f32>,
phases: &Phases,
runtime: &CudaRuntime,
) -> Result<(), CudaError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SegConvSite {
Conv1,
Conv2,
}
impl SegConvSite {
pub(crate) fn boundary(self) -> super::implementation::BoundaryId {
super::implementation::BoundaryId::named(match self {
Self::Conv1 => "sincnet.conv1",
Self::Conv2 => "sincnet.conv2",
})
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SegConvSpec {
site: SegConvSite,
batch: usize,
math: CudaMath,
}
impl SegConvSpec {
pub(crate) fn new(site: SegConvSite, batch: usize, math: CudaMath) -> Result<Self, PlanError> {
let spec = Self { site, batch, math };
validate_fixed_batch(
"segmentation conv plan",
batch,
&[
spec.in_channels() * spec.input_steps(),
spec.out_channels() * spec.output_steps(),
],
)?;
Ok(spec)
}
pub(crate) fn check_conv(self, conv: Conv2d) -> Result<(), PlanError> {
if conv.batch == self.batch
&& conv.math == self.math
&& conv.in_channels == self.in_channels()
&& conv.out_channels == self.out_channels()
&& conv.input == [1, self.input_steps()]
&& conv.kernel == [1, self.kernel()]
&& conv.padding == [0, 0]
&& conv.stride == [1, 1]
&& conv.dilation == [1, 1]
{
return Ok(());
}
Err(PlanError::Geometry(GeometryError::Unimplemented {
context: "temporal plan",
reason: format!("{} does not implement shape {conv:?}", self.site.boundary()),
}))
}
pub(crate) const fn site(self) -> SegConvSite {
self.site
}
pub(crate) const fn batch(self) -> usize {
self.batch
}
pub(crate) const fn math(self) -> CudaMath {
self.math
}
pub(crate) const fn in_channels(self) -> usize {
match self.site {
SegConvSite::Conv1 => 80,
SegConvSite::Conv2 => 60,
}
}
pub(crate) const fn out_channels(self) -> usize {
60
}
pub(crate) const fn input_steps(self) -> usize {
match self.site {
SegConvSite::Conv1 => 5325,
SegConvSite::Conv2 => 1773,
}
}
pub(crate) const fn output_steps(self) -> usize {
self.input_steps() - 4
}
pub(crate) const fn kernel(self) -> usize {
5
}
pub(crate) fn input_len(self) -> usize {
self.batch * self.in_channels() * self.input_steps()
}
pub(crate) fn weight_len(self) -> usize {
self.out_channels() * self.in_channels() * self.kernel()
}
pub(crate) fn output_len(self) -> usize {
self.batch * self.out_channels() * self.output_steps()
}
}
pub(crate) trait SegConvCandidate: Sized {
type Pin;
const COVERAGE: Coverage;
const SPECIAL_VALUES: SpecialValues;
fn coverage(_tier: PtxTier) -> Coverage {
Self::COVERAGE
}
fn implemented_pin(
spec: SegConvSpec,
tier: PtxTier,
device: &DeviceAttributes,
) -> Result<Self::Pin, PlanError>;
fn plan(
runtime: &CudaRuntime,
kernels: &LoadedKernels,
spec: SegConvSpec,
weight: &CudaSlice<f32>,
pin: Self::Pin,
) -> Result<Self, PlanError>;
fn enqueue(
&self,
x: &CudaView<'_, f32>,
output: &mut CudaViewMut<'_, f32>,
phases: &Phases,
runtime: &CudaRuntime,
) -> Result<(), CudaError>;
}
fn validate_fixed_batch(
context: &'static str,
batch: usize,
lengths: &[usize],
) -> Result<(), PlanError> {
if batch > 0
&& lengths.iter().all(|length| {
batch
.checked_mul(*length)
.is_some_and(|value| value <= i32::MAX as usize)
})
{
return Ok(());
}
Err(PlanError::Geometry(GeometryError::Invalid {
context,
reason: format!("batch {batch} is zero or exceeds the buffer index range"),
}))
}
pub(crate) trait DriverCandidate {
const AREA: KernelModule;
fn driver_coverage(tier: PtxTier, device: &DeviceAttributes, fp16: Fp16Policy) -> Coverage;
fn hybrid_fp16(
_device: &DeviceAttributes,
_tier: PtxTier,
_recipe: Option<super::implementation::policy::Recipe>,
fp16: Fp16Policy,
) -> Fp16Policy {
fp16
}
fn broad_evidence() -> Option<&'static super::implementation::BroadEvidence> {
None
}
fn speed_scope(
_boundary: super::implementation::BoundaryId,
_batch: usize,
_math: CudaMath,
_device: &DeviceAttributes,
_tier: PtxTier,
) -> Option<super::implementation::SpeedScope> {
Self::broad_evidence().map(super::implementation::SpeedScope::AllDevices)
}
fn speed_summary(_math: CudaMath) -> &'static str {
Self::broad_evidence().map_or("device-sensitive port measurements", |evidence| {
evidence.summary()
})
}
fn speed_evidence(
boundary: super::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &DeviceAttributes,
tier: PtxTier,
pin: ConfigPin,
) -> Option<(super::implementation::SpeedScope, &'static str)> {
if pin.is_fp16()
|| Self::driver_pin(boundary, batch, math, device, tier, Fp16Policy::Excluded).ok()?
!= pin
{
return None;
}
Self::speed_scope(boundary, batch, math, device, tier)
.map(|scope| (scope, Self::speed_summary(math)))
}
fn runtime_fp32_pin(
boundary: super::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &DeviceAttributes,
tier: PtxTier,
accuracy: super::tuning::accuracy::RuntimePolicy,
) -> Result<Option<ConfigPin>, PlanError> {
let conservative = Self::tuning_fp32_pin(boundary, batch, math, device, tier)?;
if conservative.is_some() || math != CudaMath::Tf32 || !accuracy.allows_exact_fp32() {
return Ok(conservative);
}
Self::driver_pin(
boundary,
batch,
CudaMath::Fp32,
device,
tier,
Fp16Policy::Excluded,
)
.map(Some)
}
fn tuning_fp32_pin(
_boundary: super::implementation::BoundaryId,
_batch: usize,
_math: CudaMath,
_device: &DeviceAttributes,
_tier: PtxTier,
) -> Result<Option<ConfigPin>, PlanError> {
Ok(None)
}
fn tuning_fp16_pin(
_boundary: super::implementation::BoundaryId,
_batch: usize,
_math: CudaMath,
_device: &DeviceAttributes,
_tier: PtxTier,
) -> Result<Option<ConfigPin>, PlanError> {
Ok(None)
}
fn tuning_pins(
boundary: super::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &DeviceAttributes,
tier: PtxTier,
) -> Vec<(ConfigPin, &'static str)> {
let mut pins = Vec::new();
if Self::driver_coverage(tier, device, Fp16Policy::Excluded).covers(
boundary.name(),
batch,
math,
) && let Ok(pin) =
Self::driver_pin(boundary, batch, math, device, tier, Fp16Policy::Excluded)
{
pins.push((pin, "default"));
if let Ok(Some(tf32)) = Self::tuning_tf32_pin(boundary, batch, math, device, tier)
&& tf32 != pin
{
pins.push((tf32, "tf32"));
}
if let Ok(Some(fp32)) = Self::tuning_fp32_pin(boundary, batch, math, device, tier)
&& fp32 != pin
{
pins.push((fp32, "fp32"));
}
}
if let Ok(Some(fp16)) = Self::tuning_fp16_pin(boundary, batch, math, device, tier) {
pins.push((fp16, "fp16"));
}
pins
}
fn tuning_tf32_pin(
_boundary: super::implementation::BoundaryId,
_batch: usize,
_math: CudaMath,
_device: &DeviceAttributes,
_tier: PtxTier,
) -> Result<Option<ConfigPin>, PlanError> {
Ok(None)
}
fn driver_pin(
boundary: super::implementation::BoundaryId,
batch: usize,
math: CudaMath,
device: &super::device::DeviceAttributes,
tier: PtxTier,
fp16: Fp16Policy,
) -> Result<ConfigPin, PlanError>;
}