Skip to main content

cubecl_std/throughput/
launch.rs

1use cubecl_core::ir::ElemType;
2use cubecl_runtime::{client::Client, server::CubeDim};
3
4/// Independent cube positions each CPU worker interleaves, so a compute
5/// pass pipelines past instruction latency instead of serializing on one
6/// dependency chain. A depth, not a machine guess: a handful hides any
7/// core's fma latency, excess is free because the iteration budget is
8/// time-calibrated, and nothing about the launch scales with it. Memory
9/// probes with blocked addressing pin their own count back to one (see
10/// [`MemoryProbe::new`](crate::throughput::memory_probe::MemoryProbe::new)).
11const CPU_CHAIN_DEPTH: usize = 64;
12
13/// Units a GPU probe asks for. A wider cube measures no faster, and makes the
14/// memory probes report several times the bus rate.
15const PROBE_UNITS_PER_CUBE: u32 = 256;
16
17/// Hardware execution parameters for launching a compute kernel.
18#[derive(Clone, Copy)]
19pub struct LaunchConfig {
20    /// The cube the probe launches, resolved once so `ops_count` cannot
21    /// describe a launch that did not happen.
22    pub cube_dim: CubeDim,
23    /// The total number of cubes to dispatch.
24    pub cube_count: usize,
25    /// The vectorization factor (e.g., 4 for `vec4` operations).
26    pub vector_size: usize,
27    /// The number of threads in a hardware execution plane.
28    pub plane_size: usize,
29}
30
31impl LaunchConfig {
32    /// The launch a probe of `dtype` is issued in on this device.
33    pub(super) fn for_device(client: &Client, dtype: ElemType) -> Self {
34        let hardware = &client.properties().hardware;
35
36        let plane_size = hardware.plane_size_max.max(1);
37        let vector_size = client
38            .io_optimized_vector_sizes(dtype.size())
39            .next()
40            .unwrap_or(1);
41
42        // A CPU has no SMs, so `sms * 32` cubes is the wrong grid to size from:
43        // a cube's units are its real dispatched workers here, while its cube
44        // count is only a loop inside each of them. `num_cpu_cores` units, one
45        // per core, is the real worker count.
46        let (units, cube_count) = match hardware.num_cpu_cores {
47            Some(cores) => (cores, CPU_CHAIN_DEPTH as u32),
48            None => {
49                let sms = hardware.num_streaming_multiprocessors.unwrap_or(64);
50                (
51                    PROBE_UNITS_PER_CUBE,
52                    (sms * 32).min(hardware.max_cube_count.0),
53                )
54            }
55        };
56
57        Self {
58            cube_dim: CubeDim::new(client, units as usize),
59            cube_count: cube_count as usize,
60            vector_size,
61            plane_size: plane_size as usize,
62        }
63    }
64
65    /// The same launch, dispatched across `units` workers.
66    pub(super) fn with_units(self, client: &Client, units: u32) -> Self {
67        Self {
68            cube_dim: CubeDim::new(client, units as usize),
69            ..self
70        }
71    }
72}