1use super::args::{
2 FusedReduceInput, FusedReduceInputLaunch, FusedReduceOutput, FusedReduceOutputLaunch,
3};
4#[cfg(feature = "autotune")]
5use super::tune::fused_reduce_autotune;
6use crate::{
7 CubeFusionHandle, FallbackOperation,
8 engine::{
9 codegen::ir::{
10 FuseArg, FuseBlockConfig, FuseType, GlobalArgsLaunch, RefLayout,
11 multi_block_variables_init,
12 },
13 launch::{
14 FuseTraceLauncher,
15 runner::{TraceRunner, Vectorization},
16 },
17 trace::{FuseTrace, TraceError, TuneOutput},
18 },
19 optim::{FusedOperation, elemwise::ElemwiseRunner, reduce::args::FusedReduceArgs},
20};
21use burn_backend::cubecl::{dtype_to_storage_type, elem_type_to_dtype};
22use burn_fusion::stream::Context;
23use burn_ir::ReduceDimOpIr;
24use burn_std::DType;
25use cubecl::{client::Client, prelude::*};
26use cubek::reduce::{
27 ReduceDtypes, ReduceError, VectorizationMode,
28 components::instructions::ReduceOperationConfig,
29 init_tensors,
30 launch::{RoutineStrategy, reduce_kernel_virtual},
31 output_vectorization_axis,
32 routines::{
33 ReduceBlueprint, ReduceLaunchSettings, ReduceProblem, ReduceVectorSettings, Routine,
34 cube::CubeRoutine, plane::PlaneRoutine, unit::UnitRoutine,
35 },
36};
37use serde::{Deserialize, Serialize};
38use std::sync::Arc;
39
40#[cfg(not(feature = "autotune"))]
41use cubek::reduce::routines::{BlueprintStrategy, unit::UnitStrategy};
42
43pub struct ReduceOptimization {
44 pub(crate) info: Arc<ReduceOptimizationInfo>,
45}
46
47pub(crate) struct ReduceOptimizationInfo {
48 pub(crate) trace: FuseTrace,
49 trace_read_fallback: FuseTrace,
50 trace_write_fallback: FuseTrace,
51 pub(crate) client: Client,
52 pub(crate) device: cubecl::Device,
53 pub(crate) len: usize,
54 pub(crate) len_read: usize,
55 pub(crate) reduce: FusedReduce,
56 settings: ReduceSettings,
57}
58
59impl ReduceOptimizationInfo {
60 pub fn from_state(device: &cubecl::Device, state: ReduceOptimizationState) -> Self {
61 let client = device.client();
62
63 Self {
64 trace: state.trace,
65 trace_read_fallback: state.trace_read_fallback,
66 trace_write_fallback: state.trace_write_fallback,
67 client,
68 device: device.clone(),
69 len: state.len,
70 len_read: state.len_read,
71 reduce: state.reduce,
72 settings: state.settings,
73 }
74 }
75 pub fn to_state(&self) -> ReduceOptimizationState {
76 ReduceOptimizationState {
77 trace: self.trace.clone(),
78 trace_read_fallback: self.trace_read_fallback.clone(),
79 trace_write_fallback: self.trace_write_fallback.clone(),
80 len: self.len,
81 len_read: self.len_read,
82 reduce: self.reduce.clone(),
83 settings: self.settings,
84 }
85 }
86}
87
88#[derive(Serialize, Deserialize, Copy, Clone)]
89pub enum ReduceSettings {
90 Always,
91 OnlyParallel,
96 Never,
97}
98
99pub(crate) struct ReduceOptimizationTuneArg {
100 pub(crate) info: Arc<ReduceOptimizationInfo>,
101 pub(crate) fallback: Arc<Box<dyn FallbackOperation>>,
102}
103
104impl Clone for ReduceOptimizationTuneArg {
105 fn clone(&self) -> Self {
106 Self {
107 info: self.info.clone(),
108 fallback: self.fallback.clone(),
109 }
110 }
111}
112
113#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
114pub enum ReduceInstruction {
115 ArgMax,
116 ArgMin,
117 Mean,
118 Prod,
119 Sum,
120 Max,
121 Min,
122 MaxAbs,
123 Any,
124 All,
125}
126
127pub trait ReduceFallbackFn: Send + Sync {
128 fn run(&self, context: &mut Context<CubeFusionHandle>);
129}
130
131#[derive(Serialize, Deserialize)]
132pub struct ReduceOptimizationState {
133 pub(crate) trace: FuseTrace,
134 pub(crate) trace_read_fallback: FuseTrace,
135 pub(crate) trace_write_fallback: FuseTrace,
136 pub(crate) reduce: FusedReduce,
137 pub(crate) len: usize,
138 pub(crate) len_read: usize,
139 pub(crate) settings: ReduceSettings,
140}
141
142impl core::fmt::Debug for ReduceOptimizationState {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.write_fmt(format_args!(
145 "{{ len_read: {}, len_total: {} }}",
146 self.len_read, self.len
147 ))
148 }
149}
150
151#[derive(Clone, Debug, Serialize, Deserialize)]
152pub struct FusedReduce {
153 pub(crate) input: FuseArg,
154 pub(crate) output: FuseArg,
155 pub(crate) acc: FuseType,
156 pub(crate) axis: usize,
157 pub(crate) op: ReduceDimOpIr,
158 pub(crate) use_planes: bool,
159 pub(crate) shared: bool,
160 pub(crate) inst: ReduceInstruction,
161}
162
163#[derive(new)]
164pub struct FusedReduceLaunch<'a> {
165 reduce: &'a FusedReduce,
166 strategy: RoutineStrategy,
167}
168
169#[derive(Debug)]
170pub enum FusedReduceError {
171 Reduce(ReduceError),
172 InvalidSelection(Box<&'static str>),
173 InvalidInput,
174}
175
176impl From<ReduceError> for FusedReduceError {
177 fn from(value: ReduceError) -> Self {
178 Self::Reduce(value)
179 }
180}
181
182impl ReduceOptimizationTuneArg {
183 pub fn execute_fused(
184 &self,
185 context: &mut Context<CubeFusionHandle>,
186 strategy: RoutineStrategy,
187 ) -> Result<TuneOutput, TraceError<FusedReduceError>> {
188 let launch = FusedReduceLaunch::new(&self.info.reduce, strategy);
189 let launcher = FuseTraceLauncher::new(&self.info.trace, &launch);
190 launcher.launch(&self.info.client, &self.info.device, context)
191 }
192
193 pub fn execute_fallback(&self, context: &mut Context<CubeFusionHandle>) -> TuneOutput {
194 let launcher = FuseTraceLauncher::new(&self.info.trace_read_fallback, &ElemwiseRunner);
195
196 #[allow(unused_mut)] let mut output_read = launcher
198 .launch(&self.info.client, &self.info.device, context)
199 .unwrap();
200
201 self.fallback.run(context);
202
203 #[cfg(feature = "autotune-checks")]
204 if let TuneOutput::Checked { handles } = &mut output_read {
205 let out_desc = context.tensors.get(&self.info.reduce.op.out.id).unwrap();
206 let handle_out = context
207 .handles
208 .get_handle(&out_desc.id, &burn_ir::TensorStatus::ReadOnly);
209
210 handles.insert(
211 self.info.reduce.op.out.id,
212 (out_desc.shape.clone(), handle_out.clone()),
213 );
214 }
215
216 let launcher = FuseTraceLauncher::new(&self.info.trace_write_fallback, &ElemwiseRunner);
217
218 let output_write = launcher
219 .launch(&self.info.client, &self.info.device, context)
220 .unwrap();
221
222 output_read.merge(output_write)
223 }
224}
225
226#[allow(clippy::too_many_arguments)]
227impl ReduceOptimization {
228 pub fn new(
229 trace: FuseTrace,
230 trace_read_fallback: FuseTrace,
231 trace_write_fallback: FuseTrace,
232 client: Client,
233 device: cubecl::Device,
234 len: usize,
235 len_read: usize,
236 reduce: FusedReduce,
237 settings: ReduceSettings,
238 ) -> Self {
239 let info = ReduceOptimizationInfo {
240 trace,
241 trace_read_fallback,
242 trace_write_fallback,
243 client,
244 device,
245 len,
246 len_read,
247 reduce,
248 settings,
249 };
250
251 Self {
252 info: Arc::new(info),
253 }
254 }
255 pub fn execute(
257 &mut self,
258 context: &mut Context<CubeFusionHandle>,
259 fallback: impl FnOnce(usize) -> Box<dyn FallbackOperation>,
260 ) {
261 let fallback = fallback(self.info.len_read);
263 let arg = ReduceOptimizationTuneArg {
264 info: self.info.clone(),
265 fallback: Arc::new(fallback),
266 };
267
268 let input = context.tensors.get(&self.info.reduce.op.input.id).unwrap();
271 if input.shape.num_elements() == 0 {
272 arg.execute_fallback(context);
273 return;
274 }
275
276 #[cfg(feature = "autotune")]
277 fused_reduce_autotune(arg, context);
278
279 #[cfg(not(feature = "autotune"))]
280 if arg
281 .execute_fused(
282 context,
283 RoutineStrategy::Unit(BlueprintStrategy::Inferred(UnitStrategy)),
284 )
285 .is_err()
286 {
287 arg.execute_fallback(context);
288 }
289 }
290
291 pub fn num_output_buffers(&self) -> usize {
292 self.info.trace_read_fallback.resources.outputs.len()
293 }
294
295 pub fn to_state(&self) -> ReduceOptimizationState {
296 ReduceOptimizationState {
297 trace: self.info.trace.clone(),
298 trace_read_fallback: self.info.trace_read_fallback.clone(),
299 trace_write_fallback: self.info.trace_write_fallback.clone(),
300 reduce: self.info.reduce.clone(),
301 len: self.info.len,
302 len_read: self.info.len_read,
303 settings: self.info.settings,
304 }
305 }
306
307 pub fn from_state(device: &cubecl::Device, state: ReduceOptimizationState) -> Self {
308 let client = device.client();
309
310 let info = ReduceOptimizationInfo {
311 trace: state.trace,
312 trace_read_fallback: state.trace_read_fallback,
313 trace_write_fallback: state.trace_write_fallback,
314 reduce: state.reduce,
315 len: state.len,
316 len_read: state.len_read,
317 client,
318 device: device.clone(),
319 settings: state.settings,
320 };
321
322 Self {
323 info: Arc::new(info),
324 }
325 }
326
327 pub fn num_ops_fused(&self) -> usize {
329 self.info.len
330 }
331}
332
333impl Vectorization for FusedReduceLaunch<'_> {}
335
336impl TraceRunner for FusedReduceLaunch<'_> {
337 type Error = FusedReduceError;
338
339 fn run<'a>(
340 &'a self,
341 client: &'a Client,
342 inputs: GlobalArgsLaunch,
343 outputs: GlobalArgsLaunch,
344 configs: &'a [FuseBlockConfig],
345 ) -> Result<(), FusedReduceError> {
346 let [config_read, config_write] = [&configs[0], &configs[1]];
347 let ref_args = match &config_read.ref_layout {
350 RefLayout::Concrete(FuseArg::Output(..)) => &outputs,
351 _ => &inputs,
352 };
353 let shape = ref_args.shape_ref(&config_read.ref_layout, config_read.rank);
354 let ref_strides = ref_args.strides_ref(&config_read.ref_layout, config_read.rank);
355 let reduce_count: usize = shape
356 .iter()
357 .enumerate()
358 .map(|(i, s)| if i == self.reduce.axis { 1 } else { *s })
359 .product();
360
361 let vectorization_mode = match self.reduce.axis == config_read.rank - 1 {
362 true => VectorizationMode::Parallel,
363 false => VectorizationMode::Perpendicular,
364 };
365 let address_type = inputs
366 .required_address_type()
367 .max(outputs.required_address_type());
368
369 let settings = ReduceVectorSettings {
370 vectorization_mode,
371 vector_size_input: config_read.width,
372 vector_size_output: config_write.width,
373 unchecked_fast_paths: false,
376 fuse_on_read: true,
379 };
380 let problem = ReduceProblem {
381 reduce_len: shape[self.reduce.axis],
382 reduce_count,
383 axis: self.reduce.axis,
384 dtypes: ReduceDtypes {
385 input: dtype_to_storage_type(self.reduce.op.input.dtype),
386 output: dtype_to_storage_type(self.reduce.op.out.dtype),
387 accumulation: self.reduce.acc.into_elem(),
388 },
389 address_type,
390 instruction: reduce_instruction2config(&self.reduce.inst),
391 };
392
393 let (blueprint, settings) = match self.strategy.clone() {
394 RoutineStrategy::Unit(strategy) => {
395 let routine = UnitRoutine;
396 routine.prepare(client, problem, settings, strategy)?
397 }
398 RoutineStrategy::Plane(strategy) => {
399 let routine = PlaneRoutine;
400 routine.prepare(client, problem, settings, strategy)?
401 }
402 RoutineStrategy::Cube(strategy) => {
403 let routine = CubeRoutine;
404 routine.prepare(client, problem, settings, strategy)?
405 }
406 };
407
408 let out_vec_axis =
409 output_vectorization_axis(&ref_strides, self.reduce.axis, vectorization_mode);
410
411 let kwargs = ReduceKwArgs {
412 client,
413 inputs,
414 outputs,
415 reduce_axis: self.reduce.axis,
416 out_vec_axis,
417 config_fuse_read: config_read.clone(),
418 config_fuse_write: config_write.clone(),
419 input: self.reduce.input.clone(),
420 output: self.reduce.output.clone(),
421 blueprint,
422 settings,
423 };
424 let result = launch_reduce_mixed_precision(
425 kwargs,
426 self.reduce.inst,
427 self.reduce.op.input.dtype,
428 self.reduce.op.out.dtype,
429 elem_type_to_dtype(self.reduce.acc.into_elem()),
430 );
431
432 match result {
433 Ok(_) => Ok(()),
434 Err(err) => Err(FusedReduceError::Reduce(ReduceError::Launch(err))),
435 }
436 }
437}
438
439struct ReduceKwArgs<'b> {
440 client: &'b Client,
441 inputs: GlobalArgsLaunch,
442 outputs: GlobalArgsLaunch,
443 reduce_axis: usize,
444 out_vec_axis: usize,
445 blueprint: ReduceBlueprint,
446 settings: ReduceLaunchSettings,
447 config_fuse_read: FuseBlockConfig,
448 config_fuse_write: FuseBlockConfig,
449 input: FuseArg,
450 output: FuseArg,
451}
452
453fn launch_reduce_mixed_precision(
454 kwargs: ReduceKwArgs<'_>,
455 instruction: ReduceInstruction,
456 dtype_input: DType,
457 dtype_output: DType,
458 dtype_acc: DType,
459) -> Result<(), LaunchError> {
460 let config = reduce_instruction2config(&instruction);
461 launch_reduce(kwargs, config, dtype_input, dtype_output, dtype_acc)
462}
463
464pub(crate) fn reduce_instruction2config(instruction: &ReduceInstruction) -> ReduceOperationConfig {
465 match instruction {
466 ReduceInstruction::ArgMax => ReduceOperationConfig::ArgMax,
467 ReduceInstruction::ArgMin => ReduceOperationConfig::ArgMin,
468 ReduceInstruction::Prod => ReduceOperationConfig::Prod,
469 ReduceInstruction::Mean => ReduceOperationConfig::Mean,
470 ReduceInstruction::Sum => ReduceOperationConfig::Sum,
471 ReduceInstruction::Max => ReduceOperationConfig::Max,
472 ReduceInstruction::Min => ReduceOperationConfig::Min,
473 ReduceInstruction::MaxAbs => ReduceOperationConfig::MaxAbs,
474 ReduceInstruction::Any => ReduceOperationConfig::Any,
475 ReduceInstruction::All => ReduceOperationConfig::All,
476 }
477}
478
479fn launch_reduce(
480 kwargs: ReduceKwArgs<'_>,
481 inst: ReduceOperationConfig,
482 dtype_input: DType,
483 dtype_output: DType,
484 dtype_acc: DType,
485) -> Result<(), LaunchError> {
486 unsafe {
487 reduce_kernel_fused::launch_unchecked(
488 kwargs.client,
489 kwargs.settings.cube_count,
490 kwargs.settings.cube_dim,
491 kwargs.settings.address_type,
492 kwargs.config_fuse_read.width,
493 kwargs.config_fuse_write.width,
494 FusedReduceInputLaunch::new(kwargs.inputs, kwargs.config_fuse_read, kwargs.input),
495 FusedReduceOutputLaunch::new(kwargs.outputs, kwargs.config_fuse_write, kwargs.output),
496 kwargs.reduce_axis,
497 kwargs.out_vec_axis,
498 kwargs.blueprint,
499 inst,
500 dtype_to_storage_type(dtype_input),
501 dtype_to_storage_type(dtype_output),
502 dtype_to_storage_type(dtype_acc),
503 )
504 };
505
506 Ok(())
507}
508
509#[cube(launch_unchecked, address_type = "dynamic")]
510pub fn reduce_kernel_fused<In: Numeric, SizeIn: Size, Out: Numeric, SizeOut: Size, Acc: Numeric>(
511 input: &FusedReduceInput,
512 output: &mut FusedReduceOutput,
513 reduce_axis: usize,
514 out_vec_axis: usize,
515 #[comptime] blueprint: ReduceBlueprint,
516 #[comptime] config: ReduceOperationConfig,
517 #[define(In)] _input_dtype: ElemType,
518 #[define(Out)] _output_dtype: ElemType,
519 #[define(Acc)] _acc_dtype: ElemType,
520) {
521 multi_block_variables_init(&input.config, &mut output.global.variables);
522 multi_block_variables_init(&output.config, &mut output.global.variables);
523
524 let (input, mut output) =
525 init_tensors::<FusedReduceArgs, In, SizeIn, Out, SizeOut>(input, output);
526
527 reduce_kernel_virtual::<In, SizeIn, Out, SizeOut, Acc>(
528 &input,
529 &mut output,
530 reduce_axis,
531 out_vec_axis,
532 blueprint,
533 config,
534 );
535}
536
537pub const NAME: &str = "Reduce";
539
540impl ReduceOptimizationInfo {
541 pub(crate) fn max_relative_shape_id(&self) -> Option<usize> {
543 [
544 self.trace.max_relative_shape_id(),
545 self.trace_read_fallback.max_relative_shape_id(),
546 self.trace_write_fallback.max_relative_shape_id(),
547 ]
548 .into_iter()
549 .flatten()
550 .max()
551 }
552}
553
554impl FusedOperation for ReduceOptimization {
555 fn max_relative_shape_id(&self) -> Option<usize> {
556 self.info.max_relative_shape_id()
557 }
558
559 const NAME: &'static str = self::NAME;
560 type State = ReduceOptimizationState;
561
562 fn num_ops_fused(&self) -> usize {
563 Self::num_ops_fused(self)
564 }
565
566 fn run(
567 &mut self,
568 context: &mut Context<CubeFusionHandle>,
569 fallback: &dyn Fn(usize) -> Box<dyn FallbackOperation>,
570 ) {
571 Self::execute(self, context, |index| fallback(index))
572 }
573
574 fn to_state(&self) -> Self::State {
575 Self::to_state(self)
576 }
577
578 fn from_state(device: &cubecl::Device, state: Self::State) -> Self {
579 Self::from_state(device, state)
580 }
581}