use core::marker::PhantomData;
use cubecl_environment::sync::{Arc, AtomicUsize, Ordering};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ExecutionStatistics {
pub compilation: CompilationStatistics,
pub autotune: AutotuneStatistics,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CompilationStatistics {
pub registered: usize,
pub compiled: usize,
pub loaded: usize,
pub failed: usize,
pub abandoned: usize,
pub refused: usize,
pub stored: usize,
}
impl CompilationStatistics {
pub fn settled(&self) -> usize {
self.compiled + self.loaded + self.failed + self.abandoned
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct AutotuneStatistics {
pub registered: usize,
pub measured: usize,
pub failed: usize,
pub abandoned: usize,
pub persisted: usize,
}
impl AutotuneStatistics {
pub fn settled(&self) -> usize {
self.measured + self.failed + self.abandoned
}
}
#[derive(Debug, Default)]
pub struct StatisticsCollector {
tallies: Arc<CollectorTallies>,
}
#[derive(Debug, Clone)]
pub struct StatisticsReader {
tallies: Arc<CollectorTallies>,
}
#[derive(Debug, Clone)]
pub(crate) struct StatisticsRecorder {
tallies: Arc<CollectorTallies>,
}
impl StatisticsCollector {
pub fn new() -> Self {
Self::default()
}
pub fn reader(&self) -> StatisticsReader {
StatisticsReader {
tallies: self.tallies.clone(),
}
}
pub fn statistics(&self) -> ExecutionStatistics {
self.tallies.statistics()
}
pub(crate) fn recorder(&self) -> StatisticsRecorder {
StatisticsRecorder {
tallies: self.tallies.clone(),
}
}
}
impl StatisticsReader {
pub fn statistics(&self) -> ExecutionStatistics {
self.tallies.statistics()
}
}
impl StatisticsRecorder {
pub(crate) fn tallies_into(&self, collector: &StatisticsCollector) -> bool {
Arc::ptr_eq(&self.tallies, &collector.tallies)
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KernelOutcome {
Compiled,
Loaded,
Failed,
Abandoned,
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KernelHandling {
Refused,
Stored,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TuneOutcome {
Measured,
Failed,
Abandoned,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TunePick {
Persisted,
}
#[doc(hidden)]
pub type KernelRegistration = Registration<KernelOutcome>;
#[doc(hidden)]
pub type SettledKernel = Settled<KernelOutcome>;
pub(crate) type TuneRegistration = Registration<TuneOutcome>;
#[doc(hidden)]
#[must_use]
pub struct Registration<O: Outcome> {
recorder: Option<StatisticsRecorder>,
outcome: PhantomData<O>,
}
#[doc(hidden)]
#[derive(Debug)]
pub struct Settled<O: Outcome> {
recorder: StatisticsRecorder,
recorded: u8,
outcome: PhantomData<O>,
}
impl<O: Outcome> Registration<O> {
pub fn register() -> Option<Self> {
let recorder = super::IssuedRecorder::running()
.unwrap_or_else(super::ProcessModeOverride::active_recorder)?;
O::tally(&recorder.tallies).register();
Some(Self {
recorder: Some(recorder),
outcome: PhantomData,
})
}
pub fn settle(mut self, outcome: O) -> Settled<O> {
let recorder = self.recorder.take().expect("settled once, by value");
O::tally(&recorder.tallies).settle(outcome.index());
Settled {
recorder,
recorded: 0,
outcome: PhantomData,
}
}
}
impl<O: Outcome> Drop for Registration<O> {
fn drop(&mut self) {
if let Some(recorder) = self.recorder.take() {
O::tally(&recorder.tallies).settle(O::ABANDONED.index());
}
}
}
impl<O: Outcome> core::fmt::Debug for Registration<O> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Registration")
.field("settled", &self.recorder.is_none())
.finish()
}
}
impl<O: Outcome> Settled<O> {
pub fn record(&mut self, later: O::Later) {
let index = O::later_index(later);
let bit = 1 << index;
if self.recorded & bit == 0 {
self.recorded |= bit;
O::tally(&self.recorder.tallies).record(index);
}
}
}
#[derive(Debug, Default)]
pub struct CollectorTallies {
kernels: Tally,
tunes: Tally,
}
impl CollectorTallies {
fn statistics(&self) -> ExecutionStatistics {
let kernels = self.kernels.read();
let tunes = self.tunes.read();
ExecutionStatistics {
compilation: CompilationStatistics {
registered: kernels.registered,
compiled: kernels.settled[KernelOutcome::Compiled.index()],
loaded: kernels.settled[KernelOutcome::Loaded.index()],
failed: kernels.settled[KernelOutcome::Failed.index()],
abandoned: kernels.settled[KernelOutcome::Abandoned.index()],
refused: kernels.later[KernelOutcome::later_index(KernelHandling::Refused)],
stored: kernels.later[KernelOutcome::later_index(KernelHandling::Stored)],
},
autotune: AutotuneStatistics {
registered: tunes.registered,
measured: tunes.settled[TuneOutcome::Measured.index()],
failed: tunes.settled[TuneOutcome::Failed.index()],
abandoned: tunes.settled[TuneOutcome::Abandoned.index()],
persisted: tunes.later[TuneOutcome::later_index(TunePick::Persisted)],
},
}
}
}
const OUTCOMES: usize = 4;
const LATER: usize = 2;
const _: () = assert!(KernelOutcome::OUTCOMES <= OUTCOMES && KernelOutcome::LATER <= LATER);
const _: () = assert!(TuneOutcome::OUTCOMES <= OUTCOMES && TuneOutcome::LATER <= LATER);
#[derive(Debug, Default)]
pub struct Tally {
registered: AtomicUsize,
settled: [AtomicUsize; OUTCOMES],
later: [AtomicUsize; LATER],
}
struct TallyRead {
registered: usize,
settled: [usize; OUTCOMES],
later: [usize; LATER],
}
impl Tally {
fn register(&self) {
self.registered.fetch_add(1, Ordering::Release);
}
fn settle(&self, outcome: usize) {
self.settled[outcome].fetch_add(1, Ordering::Release);
}
fn record(&self, later: usize) {
self.later[later].fetch_add(1, Ordering::Release);
}
fn read(&self) -> TallyRead {
let later = core::array::from_fn(|index| self.later[index].load(Ordering::Acquire));
let settled = core::array::from_fn(|index| self.settled[index].load(Ordering::Acquire));
TallyRead {
registered: self.registered.load(Ordering::Acquire),
settled,
later,
}
}
}
#[doc(hidden)]
pub trait Outcome: Copy + 'static {
type Later: Copy;
const OUTCOMES: usize;
const LATER: usize;
const ABANDONED: Self;
fn index(self) -> usize;
fn later_index(later: Self::Later) -> usize;
fn tally(tallies: &CollectorTallies) -> &Tally;
}
impl Outcome for KernelOutcome {
type Later = KernelHandling;
const OUTCOMES: usize = 4;
const LATER: usize = 2;
const ABANDONED: Self = Self::Abandoned;
fn index(self) -> usize {
match self {
Self::Compiled => 0,
Self::Loaded => 1,
Self::Failed => 2,
Self::Abandoned => 3,
}
}
fn later_index(later: KernelHandling) -> usize {
match later {
KernelHandling::Refused => 0,
KernelHandling::Stored => 1,
}
}
fn tally(tallies: &CollectorTallies) -> &Tally {
&tallies.kernels
}
}
impl Outcome for TuneOutcome {
type Later = TunePick;
const OUTCOMES: usize = 3;
const LATER: usize = 1;
const ABANDONED: Self = Self::Abandoned;
fn index(self) -> usize {
match self {
Self::Measured => 0,
Self::Failed => 1,
Self::Abandoned => 2,
}
}
fn later_index(later: TunePick) -> usize {
match later {
TunePick::Persisted => 0,
}
}
fn tally(tallies: &CollectorTallies) -> &Tally {
&tallies.tunes
}
}