1#![allow(missing_docs)]
2
3use super::{SumAutotuneKey, accumulator_len};
4use crate::{
5 CubeAutotuneKey, CubeTuneId,
6 kernel::reduce::bounds::{
7 with_reduce_bounds, with_reduce_with_indices_bounds, with_sum_bounds,
8 },
9 tensor::CubeTensor,
10};
11use burn_backend::cubecl::dtype_to_elem_type;
12use cubecl::{
13 AutotuneKey,
14 client::Client,
15 tune::{LocalTuner, Tunable, TunableSet, TuneGroup, local_tuner},
16};
17use cubek::reduce::{
18 ReduceDtypes, ReduceStrategy, ReduceWithIndicesDtypes,
19 components::instructions::ReduceOperationConfig,
20 launch::{RoutineStrategy, VectorizationStrategy, tune_key::ReduceAutotuneKey},
21 routines::{BlueprintStrategy, cube::CubeStrategy, plane::PlaneStrategy, unit::UnitStrategy},
22};
23use serde::{Deserialize, Serialize};
24
25#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize, AutotuneKey)]
32pub struct ReduceDimAutotuneKey {
33 accumulator_len: usize,
35 reduce: ReduceAutotuneKey,
37}
38
39fn autotune_level() -> cubecl::config::autotune::AutotuneLevel {
42 use cubecl::config::{CubeClRuntimeConfig, RuntimeConfig};
43 static LEVEL: std::sync::OnceLock<cubecl::config::autotune::AutotuneLevel> =
44 std::sync::OnceLock::new();
45 LEVEL
46 .get_or_init(|| CubeClRuntimeConfig::get().autotune.level.clone())
47 .clone()
48}
49
50fn with_routine_tunables<In, Launch>(
56 mut set: TunableSet<ReduceDimAutotuneKey, In, ()>,
57 group_suffix: &str,
58 launch: Launch,
59) -> TunableSet<ReduceDimAutotuneKey, In, ()>
60where
61 In: Clone + Send + Sync + 'static,
62 Launch: Fn(ReduceStrategy, In) -> Result<(), String> + Clone + Send + Sync + 'static,
63{
64 const PRIORITY_MAX: i8 = 2;
65 const PRIORITY_MIN: i8 = 1;
66 const PRIORITY_SKIP: i8 = -1;
67
68 let default_group =
69 TuneGroup::<ReduceDimAutotuneKey>::new(&format!("default_{group_suffix}"), |_key| {
70 PRIORITY_MAX
71 });
72 let vectorized_parallel_group = TuneGroup::<ReduceDimAutotuneKey>::new(
73 &format!("vectorized_parallel_{group_suffix}"),
74 |key| {
75 if key.reduce.axis_is_contiguous {
76 PRIORITY_MAX
77 } else {
78 PRIORITY_SKIP
81 }
82 },
83 );
84
85 enum ReduceProps {
86 GreatWithLowReduceCount,
87 GreatWithHighReduceCount,
88 Balanced,
89 }
90
91 for (vectorization, vector_size_ident) in [
92 (
93 VectorizationStrategy {
94 parallel_output_vectorization: true,
95 },
96 "_vectorized_parallel_reduce",
97 ),
98 (
99 VectorizationStrategy {
100 parallel_output_vectorization: false,
101 },
102 "",
103 ),
104 ] {
105 for (name, routine, props) in [
106 (
107 "unit",
108 RoutineStrategy::Unit(BlueprintStrategy::Inferred(UnitStrategy)),
109 ReduceProps::GreatWithHighReduceCount,
110 ),
111 (
112 "plane",
113 RoutineStrategy::Plane(BlueprintStrategy::Inferred(PlaneStrategy {
114 independent: true,
115 })),
116 ReduceProps::Balanced,
117 ),
118 (
119 "cube",
120 RoutineStrategy::Cube(BlueprintStrategy::Inferred(CubeStrategy {
121 use_planes: true,
122 })),
123 ReduceProps::GreatWithLowReduceCount,
124 ),
125 ] {
126 let name = format!("{name}{vector_size_ident}");
127 let launch = launch.clone();
128 let mut tunable = Tunable::new(&name, move |inputs| {
129 let strategy = ReduceStrategy {
130 routine: routine.clone(),
131 vectorization,
132 autotune_level: autotune_level(),
137 };
138 launch(strategy, inputs)
139 });
140 if vectorization.parallel_output_vectorization {
141 tunable = tunable.group(&vectorized_parallel_group, |_| PRIORITY_MAX);
142 }
143
144 tunable = tunable.group(&default_group, move |key| match props {
145 ReduceProps::GreatWithLowReduceCount => {
146 if key.reduce.vector_count < 128 {
147 PRIORITY_MAX
148 } else {
149 PRIORITY_MIN
152 }
153 }
154 ReduceProps::GreatWithHighReduceCount => {
155 if key.reduce.vector_count > 64 {
156 PRIORITY_MAX
157 } else {
158 PRIORITY_MIN
160 }
161 }
162 ReduceProps::Balanced => PRIORITY_MAX,
163 });
164 set = set.with(tunable);
165 }
166 }
167
168 set
169}
170
171pub fn autotune_reduce(
173 client: &Client,
174 input: CubeTensor,
175 output: CubeTensor,
176 axis: usize,
177 config: ReduceOperationConfig,
178 dtypes: ReduceDtypes,
179) {
180 use reduce_ops::*;
181
182 static TUNER: LocalTuner<ReduceDimAutotuneKey, CubeTuneId> = local_tuner!("reduce-dim");
183
184 let tune_id = CubeTuneId::new(&input.client, &input.device);
185 let tunables = TUNER.init(&tune_id, || {
186 with_routine_tunables(
187 with_reduce_bounds(TunableSet::new(create_key, reduce_input_gen)),
188 "reduce",
189 |strategy,
190 (input, output, axis, config, dtypes): (
191 CubeTensor,
192 CubeTensor,
193 usize,
194 ReduceOperationConfig,
195 ReduceDtypes,
196 )| {
197 cubek::reduce::reduce(
198 &output.client,
199 input.binding(),
200 output.clone().binding(),
201 axis,
202 strategy,
203 config,
204 dtypes,
205 )
206 .map_err(|e| format!("{e}"))
207 },
208 )
209 });
210
211 TUNER.execute(
212 &tune_id,
213 client,
214 tunables,
215 (input, output, axis, config, dtypes),
216 );
217}
218
219pub(crate) fn create_key(
220 (input, output, axis, config, dtypes): &(
221 CubeTensor,
222 CubeTensor,
223 usize,
224 ReduceOperationConfig,
225 ReduceDtypes,
226 ),
227) -> ReduceDimAutotuneKey {
228 let elem_input = dtype_to_elem_type(input.dtype);
229 let elem_output = dtype_to_elem_type(output.dtype);
230 let elem_acc = dtypes.accumulation;
231
232 ReduceDimAutotuneKey::new(
233 accumulator_len(*config),
234 ReduceAutotuneKey::generate(
235 elem_input,
236 elem_output,
237 elem_acc,
238 input.meta.shape(),
239 input.meta.strides()[*axis] == 1,
240 *axis,
241 ),
242 )
243}
244
245#[allow(clippy::too_many_arguments)]
254pub fn autotune_reduce_with_indices(
255 client: &Client,
256 input: CubeTensor,
257 values: CubeTensor,
258 indices: CubeTensor,
259 axis: usize,
260 config: ReduceOperationConfig,
261 dtypes: ReduceWithIndicesDtypes,
262) {
263 use reduce_with_indices_ops::*;
264
265 static TUNER: LocalTuner<ReduceDimAutotuneKey, CubeTuneId> =
266 local_tuner!("reduce-dim-with-indices");
267
268 let tune_id = CubeTuneId::new(&input.client, &input.device);
269 let tunables = TUNER.init(&tune_id, || {
270 with_routine_tunables(
271 with_reduce_with_indices_bounds(TunableSet::new(
272 create_key_with_indices,
273 reduce_with_indices_input_gen,
274 )),
275 "reduce_with_indices",
276 |strategy,
277 (input, values, indices, axis, config, dtypes): (
278 CubeTensor,
279 CubeTensor,
280 CubeTensor,
281 usize,
282 ReduceOperationConfig,
283 ReduceWithIndicesDtypes,
284 )| {
285 cubek::reduce::reduce_with_indices(
286 &values.client,
287 input.binding(),
288 values.clone().binding(),
289 indices.clone().binding(),
290 axis,
291 strategy,
292 config,
293 dtypes,
294 )
295 .map_err(|e| format!("{e}"))
296 },
297 )
298 });
299
300 TUNER.execute(
301 &tune_id,
302 client,
303 tunables,
304 (input, values, indices, axis, config, dtypes),
305 );
306}
307
308pub(crate) fn create_key_with_indices(
309 (input, values, _indices, axis, config, dtypes): &(
310 CubeTensor,
311 CubeTensor,
312 CubeTensor,
313 usize,
314 ReduceOperationConfig,
315 ReduceWithIndicesDtypes,
316 ),
317) -> ReduceDimAutotuneKey {
318 let elem_input = dtype_to_elem_type(input.dtype);
319 let elem_output = dtype_to_elem_type(values.dtype);
320 let elem_acc = dtypes.accumulation;
321
322 ReduceDimAutotuneKey::new(
323 accumulator_len(*config),
324 ReduceAutotuneKey::generate(
325 elem_input,
326 elem_output,
327 elem_acc,
328 input.meta.shape(),
329 input.meta.strides()[*axis] == 1,
330 *axis,
331 ),
332 )
333}
334
335mod reduce_with_indices_ops {
336 #![allow(missing_docs)]
337
338 use super::*;
339
340 pub(crate) fn reduce_with_indices_input_gen(
341 _key: &ReduceDimAutotuneKey,
342 (input, values, indices, dim, config, dtypes): &(
343 CubeTensor,
344 CubeTensor,
345 CubeTensor,
346 usize,
347 ReduceOperationConfig,
348 ReduceWithIndicesDtypes,
349 ),
350 ) -> (
351 CubeTensor,
352 CubeTensor,
353 CubeTensor,
354 usize,
355 ReduceOperationConfig,
356 ReduceWithIndicesDtypes,
357 ) {
358 (
359 input.clone(),
360 values.copy(),
361 indices.copy(),
362 *dim,
363 *config,
364 *dtypes,
365 )
366 }
367}
368
369mod reduce_ops {
370 #![allow(missing_docs)]
371
372 use cubek::reduce::ReduceDtypes;
373
374 use super::*;
375
376 pub(crate) fn reduce_input_gen(
377 _key: &ReduceDimAutotuneKey,
378 (input, output, dim, config, dtypes): &(
379 CubeTensor,
380 CubeTensor,
381 usize,
382 ReduceOperationConfig,
383 ReduceDtypes,
384 ),
385 ) -> (
386 CubeTensor,
387 CubeTensor,
388 usize,
389 ReduceOperationConfig,
390 ReduceDtypes,
391 ) {
392 (input.clone(), output.copy(), *dim, *config, *dtypes)
393 }
394}
395
396#[cfg(feature = "autotune")]
398pub fn autotune_sum(client: &Client, input: CubeTensor) -> CubeTensor {
399 use sum_ops::*;
400
401 static TUNER: LocalTuner<CubeAutotuneKey, CubeTuneId> = local_tuner!("autotune-sum");
402
403 let tune_id = CubeTuneId::new(&input.client, &input.device);
404 let tunables = TUNER.init(&tune_id, || {
405 with_sum_bounds(TunableSet::new(create_key_sum, sum_input_gen))
406 .with(Tunable::new("sum_chained", sum_chained))
407 .with(Tunable::new("sum_one_shot", sum_one_shot::<1>))
408 .with(Tunable::new("sum_one_shot", sum_one_shot::<2>))
409 .with(Tunable::new("sum_one_shot", sum_one_shot::<4>))
410 .with(Tunable::new("sum_one_shot", sum_one_shot::<8>))
411 .with(Tunable::new("sum_one_shot", sum_one_shot::<16>))
412 .with(Tunable::new("sum_one_shot", sum_one_shot::<32>))
413 .with(Tunable::new("sum_one_shot", sum_one_shot::<64>))
414 });
415
416 TUNER.execute(&tune_id, client, tunables, input)
417}
418
419pub(crate) fn create_key_sum(input: &CubeTensor) -> CubeAutotuneKey {
420 CubeAutotuneKey::Sum(SumAutotuneKey::generate(input))
421}
422
423impl SumAutotuneKey {
424 #[allow(unused)]
425 pub(crate) fn generate(input: &CubeTensor) -> Self {
426 let dtype = input.dtype;
427 let length = input.meta.num_elements();
428 Self::new(dtype, length)
429 }
430}
431mod sum_ops {
432 #![allow(missing_docs)]
433 use crate::ops::numeric::zeros_client;
434
435 use super::*;
436
437 pub(crate) fn sum_input_gen(_key: &CubeAutotuneKey, input: &CubeTensor) -> CubeTensor {
438 input.clone()
439 }
440
441 pub(crate) fn sum_one_shot<const C: u32>(input: CubeTensor) -> Result<CubeTensor, String> {
442 let client = input.client.clone();
443 let device = input.device.clone();
444 let output = zeros_client(client.clone(), device, [1].into(), input.dtype);
445 let dtype = input.dtype;
446
447 cubek::reduce::shared_sum(
448 &output.client,
449 input.binding(),
450 output.clone().binding(),
451 C,
452 dtype_to_elem_type(dtype),
453 )
454 .map_err(|e| e.to_string())
455 .map(|_| output)
456 }
457
458 #[cfg(feature = "autotune")]
459 pub(crate) fn sum_chained(input: CubeTensor) -> Result<CubeTensor, String> {
460 crate::kernel::reduce::reduce(
461 input,
462 None,
463 crate::kernel::reduce::KernelReduceStrategy::Autotune,
464 cubek::reduce::components::instructions::ReduceOperationConfig::Sum,
465 )
466 .map_err(|e| e.to_string())
467 }
468}