use crate as cubecl;
use alloc::vec::Vec;
use cubecl_runtime::runtime::Runtime;
use cubecl::prelude::*;
use cubecl_common::profile::{Duration, ProfileDuration, TimingMethod};
use cubecl_environment::stream::StreamId;
use cubecl_runtime::server::{Handle, ProfileError};
const ITERATIONS: u32 = 1024;
const LEN: usize = 1 << 20;
#[cube(launch_unchecked)]
fn busy_kernel(output: &mut [f32]) {
if ABSOLUTE_POS < output.len() {
let mut x = output[ABSOLUTE_POS];
for _ in 0..ITERATIONS {
x = x * 1.0001 + 1.0;
}
output[ABSOLUTE_POS] = x;
}
}
const FEW_LAUNCHES: usize = 32;
const MANY_LAUNCHES: usize = 512;
const MIN_GROWTH: u32 = 4;
#[cube(launch_unchecked)]
fn touch_kernel(output: &mut [f32]) {
if ABSOLUTE_POS == 0 {
output[0] += 1.0;
}
}
fn touch(client: &Client, output: &Handle) {
unsafe {
touch_kernel::launch_unchecked(
client,
CubeCount::new_single(),
CubeDim::new_single(),
BufferArg::from_raw_parts(output.clone(), 1),
);
}
}
fn launch(client: &Client, output: &Handle) {
unsafe {
busy_kernel::launch_unchecked(
client,
CubeCount::Static((LEN as u32).div_ceil(256), 1, 1),
CubeDim::new_1d(256),
BufferArg::from_raw_parts(output.clone(), LEN),
);
}
}
fn resolve(profile: ProfileDuration) -> Duration {
maybe_resolve(profile).expect("the window dispatched work, so it must be measured")
}
fn maybe_resolve(profile: ProfileDuration) -> Option<Duration> {
assert_eq!(profile.timing_method(), TimingMethod::Device);
cubecl_environment::future::block_on(profile.resolve()).map(|ticks| ticks.duration())
}
pub fn test_empty_window_reports_no_device_time<R: Runtime>(client: Client) {
match client.profile(|| {}, "empty") {
Ok((_, profile)) => {
if let Some(duration) = maybe_resolve(profile) {
assert!(
duration < Duration::from_millis(1),
"a window with no GPU work must measure next to nothing, got {duration:?}"
);
}
}
Err(ProfileError::NotMeasured { .. }) => {}
Err(err) => panic!("an empty window must measure or abstain, got {err}"),
}
}
pub fn test_kernel_window_reports_positive_device_time<R: Runtime>(client: Client) {
let output = client.empty(LEN * core::mem::size_of::<f32>());
let (_, profile) = client.profile(|| launch(&client, &output), "busy").unwrap();
let duration = resolve(profile);
assert!(duration > Duration::ZERO, "real GPU work must measure > 0");
assert!(
duration < Duration::from_secs(5),
"implausibly large: {duration:?}"
);
}
pub fn test_window_spans_every_pass_in_it<R: Runtime>(client: Client) {
let output = client.empty(core::mem::size_of::<f32>());
touch(&client, &output);
cubecl_environment::future::block_on(client.sync()).unwrap();
let window = |launches: usize| {
let (_, profile) = client
.profile(
|| {
for _ in 0..launches {
touch(&client, &output);
}
},
"touch",
)
.unwrap();
resolve(profile)
};
let few = window(FEW_LAUNCHES);
let many = window(MANY_LAUNCHES);
assert!(
many > few * MIN_GROWTH,
"{MANY_LAUNCHES} launches measured {many:?} against {few:?} for {FEW_LAUNCHES}"
);
}
pub fn test_split_window_closes_on_its_own_stream<R: Runtime>(client: Client) {
let output = client.empty(core::mem::size_of::<f32>());
let mut closer = client.clone();
unsafe {
closer.set_stream(StreamId { value: 10002 });
}
touch(&client, &output);
cubecl_environment::future::block_on(client.sync()).unwrap();
let window = |launches: usize| {
let window = client.profile_start().unwrap();
for _ in 0..launches {
touch(&client, &output);
}
resolve(closer.profile_end(window).unwrap())
};
let few = window(FEW_LAUNCHES);
let many = window(MANY_LAUNCHES);
assert!(
many > few * MIN_GROWTH,
"closed from another stream, {MANY_LAUNCHES} launches measured {many:?} \
against {few:?} for {FEW_LAUNCHES}: the window did not span the work \
recorded on the stream it was opened on"
);
}
pub fn test_abandoned_window_is_dropped<R: Runtime>(client: Client) {
let output = client.empty(LEN * core::mem::size_of::<f32>());
let window = client.profile_start().unwrap();
launch(&client, &output);
client.profile_abandon(window);
match client.profile_end(window) {
Err(ProfileError::NotRegistered { .. }) => {}
Ok(_) => panic!("an abandoned window must not measure"),
Err(err) => panic!("an abandoned window must be unknown, got {err}"),
}
let (_, profile) = client.profile(|| launch(&client, &output), "busy").unwrap();
assert!(
resolve(profile) > Duration::ZERO,
"real GPU work must measure > 0"
);
}
pub fn test_nested_windows_are_contained_by_the_outer_one<R: Runtime>(client: Client) {
let output = client.empty(LEN * core::mem::size_of::<f32>());
let (inner, outer) = client
.profile(
|| {
(0..4)
.map(|_| {
let (_, profile) =
client.profile(|| launch(&client, &output), "busy").unwrap();
profile
})
.collect::<Vec<_>>()
},
"outer",
)
.unwrap();
let outer = resolve(outer);
let inner: Vec<_> = inner.into_iter().map(resolve).collect();
let sum: Duration = inner.iter().sum();
for duration in &inner {
assert!(
*duration > Duration::ZERO,
"an inner window measured nothing"
);
}
assert!(
outer >= sum,
"the outer window must contain the inner ones: {outer:?} < {sum:?}"
);
assert!(
outer < sum * 2,
"the outer window is measuring the profiling rather than the work: {outer:?} vs {sum:?}"
);
}
#[allow(missing_docs)]
#[macro_export]
macro_rules! testgen_profiling {
() => {
use super::*;
#[$crate::runtime_tests::test_log::test]
fn test_empty_window_reports_no_device_time() {
let client = TestRuntime::client(&Default::default());
cubecl_core::runtime_tests::profiling::test_empty_window_reports_no_device_time::<
TestRuntime,
>(client);
}
#[$crate::runtime_tests::test_log::test]
fn test_kernel_window_reports_positive_device_time() {
let client = TestRuntime::client(&Default::default());
cubecl_core::runtime_tests::profiling::test_kernel_window_reports_positive_device_time::<
TestRuntime,
>(client);
}
#[$crate::runtime_tests::test_log::test]
fn test_window_spans_every_pass_in_it() {
let client = TestRuntime::client(&Default::default());
cubecl_core::runtime_tests::profiling::test_window_spans_every_pass_in_it::<
TestRuntime,
>(client);
}
#[$crate::runtime_tests::test_log::test]
fn test_split_window_closes_on_its_own_stream() {
let client = TestRuntime::client(&Default::default());
cubecl_core::runtime_tests::profiling::test_split_window_closes_on_its_own_stream::<
TestRuntime,
>(client);
}
#[$crate::runtime_tests::test_log::test]
fn test_abandoned_window_is_dropped() {
let client = TestRuntime::client(&Default::default());
cubecl_core::runtime_tests::profiling::test_abandoned_window_is_dropped::<
TestRuntime,
>(client);
}
};
}
#[allow(missing_docs)]
#[macro_export]
macro_rules! testgen_profiling_nested {
() => {
use super::*;
#[$crate::runtime_tests::test_log::test]
fn test_nested_windows_are_contained_by_the_outer_one() {
let client = TestRuntime::client(&Default::default());
cubecl_core::runtime_tests::profiling::test_nested_windows_are_contained_by_the_outer_one::<
TestRuntime,
>(client);
}
};
}