1use cubecl_core::ir::ElemType;
2use cubecl_runtime::{
3 client::ComputeClient,
4 runtime::Runtime,
5 server::CubeDim,
6 throughput::{
7 DEFAULT_BUFFER_BYTES, MemoryAccess, MemoryCurve, MemoryPoint, ThroughputKey,
8 ThroughputMode, ThroughputValue, working_set_sweep,
9 },
10 tune::{Bounds, Thresholds, Work, calculate_bounds},
11};
12
13use crate::throughput::{
14 compute_cmma, compute_direct, launch_overhead, memory_direct, memory_read, memory_write,
15};
16
17const CPU_CHAIN_DEPTH: usize = 64;
25
26pub fn device_throughput<R: Runtime>(
28 device: &R::Device,
29 keys: &[ThroughputKey],
30) -> alloc::vec::Vec<ThroughputValue> {
31 let client = R::client(device);
32 keys.iter()
33 .map(|key| measure_peak_throughput::<R>(&client, *key))
34 .collect()
35}
36
37pub fn measure_memory_curve<R: Runtime>(
47 client: &ComputeClient<R>,
48 access: MemoryAccess,
49) -> MemoryCurve {
50 let points = working_set_sweep(working_set_cap(client, access))
51 .into_iter()
52 .map(|bytes| {
53 let key = ThroughputKey {
54 mode: ThroughputMode::MemoryWorkingSet { access, bytes },
55 };
56
57 MemoryPoint {
58 bytes,
59 value: measure_peak_throughput::<R>(client, key),
60 }
61 });
62
63 MemoryCurve::new(access, points)
64}
65
66fn working_set_cap<R: Runtime>(client: &ComputeClient<R>, access: MemoryAccess) -> u64 {
69 let max_alloc = client.properties().memory.max_page_size;
70
71 DEFAULT_BUFFER_BYTES.min(max_alloc) * access.buffers()
72}
73
74pub fn measure_peak_throughput<R: Runtime>(
78 client: &ComputeClient<R>,
79 key: ThroughputKey,
80) -> ThroughputValue {
81 let _measurement = cubecl_runtime::dry_run::RealRun::new();
86
87 let launch_config = launch_config(client, key.dtype());
88
89 let kernel_config = match key.mode {
90 ThroughputMode::ComputeDirect { .. } => {
91 compute_direct::build_kernel(client, key, launch_config)
92 }
93 ThroughputMode::ComputeCmma {
94 config: cmma_config,
95 ..
96 } => {
97 if client.properties().features.matmul.cmma.is_empty() {
98 return ThroughputValue::ZERO;
99 }
100 compute_cmma::build_kernel(client, key, cmma_config, launch_config)
101 }
102 ThroughputMode::Memory
103 | ThroughputMode::MemoryRead
104 | ThroughputMode::MemoryWrite
105 | ThroughputMode::MemoryWorkingSet { .. } => {
106 let (access, working_set) = key
109 .mode
110 .memory_probe()
111 .expect("A memory mode describes a probe");
112 let working_set = working_set.min(usize::MAX as u64) as usize;
113
114 match access {
115 MemoryAccess::Copy => {
116 memory_direct::build_kernel(client, key, launch_config, working_set)
117 }
118 MemoryAccess::Read => {
119 memory_read::build_kernel(client, key, launch_config, working_set)
120 }
121 MemoryAccess::Write => {
122 memory_write::build_kernel(client, key, launch_config, working_set)
123 }
124 }
125 }
126 ThroughputMode::Launch => launch_overhead::build_kernel(client, key, launch_config),
127 };
128
129 let value = client.measure_throughput(key, kernel_config);
130
131 client.memory_cleanup();
132
133 value
134}
135
136pub fn roofline_bounds<R: Runtime>(
140 client: &ComputeClient<R>,
141 compute_key: ThroughputKey,
142 work: Work,
143 thresholds: Thresholds,
144) -> Bounds {
145 let memory_key = ThroughputKey {
146 mode: ThroughputMode::Memory,
147 };
148 let launch_key = ThroughputKey {
149 mode: ThroughputMode::Launch,
150 };
151
152 Bounds {
153 bounds: calculate_bounds(
154 work,
155 thresholds,
156 &measure_peak_throughput(client, compute_key),
157 &measure_peak_throughput(client, memory_key),
158 &memory_key,
159 ),
160 launch_overhead: measure_peak_throughput(client, launch_key).duration_per_op(),
161 }
162}
163
164#[derive(Clone, Copy)]
166pub struct LaunchConfig {
167 pub cube_dim: usize,
169 pub cube_count: usize,
171 pub vector_size: usize,
173 pub plane_size: usize,
175}
176
177fn launch_config<R: Runtime>(client: &ComputeClient<R>, dtype: ElemType) -> LaunchConfig {
178 let hardware = &client.properties().hardware;
179
180 let plane_size = hardware.plane_size_max.max(1);
181 let vector_size = client
182 .io_optimized_vector_sizes(dtype.size())
183 .next()
184 .unwrap_or(1);
185
186 if let Some(cores) = hardware.num_cpu_cores {
191 return LaunchConfig {
192 cube_dim: cores as usize,
193 cube_count: CPU_CHAIN_DEPTH,
194 vector_size,
195 plane_size: plane_size as usize,
196 };
197 }
198
199 let requested = (hardware.max_units_per_cube / plane_size * plane_size)
200 .max(plane_size)
201 .min(hardware.max_cube_dim.0);
202
203 let cube_dim = CubeDim::new(client, requested as usize).num_elems();
204
205 let sms = hardware.num_streaming_multiprocessors.unwrap_or(64);
206 let cube_count = (sms * 32).min(hardware.max_cube_count.0);
207
208 LaunchConfig {
209 cube_dim: cube_dim as usize,
210 cube_count: cube_count as usize,
211 vector_size,
212 plane_size: plane_size as usize,
213 }
214}