Skip to main content

cubek_convolution/components/
selection.rs

1use cubecl::{
2    client::Client,
3    ir::{ElemType, VectorSize},
4};
5use cubek_matmul::{
6    definition::{AccumulatorOperand, MatmulAvailabilityError, MatmulElems, MatmulVectorSizes},
7    multi_level::{
8        components::{stage::PartitionBuffering, tile::TileMatmulKind},
9        definition::{BatchMatmulBlueprint, TilingScheme, adjust_dtypes},
10        routines::{NUM_SM_APPROX, NUM_TENSOR_CORES_APPROX, find_instruction_size},
11    },
12};
13
14use cubek_matmul::multi_level::{stage::SwizzleMode, stage::SwizzleModes};
15
16use crate::components::ConvolutionProblem;
17
18/// A heuristic to find the number of tiles in the stage.
19///
20/// Maximizes tensor core usage unless doing so would significantly impair
21/// parallelization across SMs. It ensures the number of cubes is as close as
22/// possible to the available SMs.
23pub(crate) fn find_stage_size_m_n(
24    m: usize,
25    n: usize,
26    num_sm: usize,
27    max_tensor_cores: usize,
28    instruction_m: usize,
29    instruction_n: usize,
30    stage_size_k: usize,
31) -> (usize, usize) {
32    let max_tiles_elems_m = 256 / instruction_m;
33    let max_tiles_elems_n = 256 / instruction_n;
34    let max_tiles_total_stage = 16 / stage_size_k;
35
36    let mut dim_num_tiles_m = max_tensor_cores
37        .min(max_tiles_elems_m)
38        .min(max_tiles_total_stage);
39
40    let mut dim_num_tiles_n = max_tensor_cores
41        .min(max_tiles_elems_n)
42        .min(max_tiles_total_stage);
43
44    let total_tiles_m = m.div_ceil(instruction_m);
45    let total_tiles_n = n.div_ceil(instruction_n);
46
47    while total_tiles_n < dim_num_tiles_n && dim_num_tiles_n > 1 {
48        dim_num_tiles_n /= 2;
49    }
50
51    let total_tiles = total_tiles_m * total_tiles_n;
52
53    let mut stage_num_tiles = dim_num_tiles_m * dim_num_tiles_n;
54    let mut num_cubes_expected = total_tiles.div_ceil(stage_num_tiles);
55
56    // We keep track of two configurations to select the closest to `num_sm`, whether it's a bit over or under
57    let mut previous_dim_num_tiles = dim_num_tiles_m;
58    let mut previous_num_cubes = num_cubes_expected;
59
60    // Refine tensor core usage to stay as close as possible to `num_sm`
61    while num_cubes_expected < num_sm && dim_num_tiles_m > 1 {
62        previous_dim_num_tiles = dim_num_tiles_m;
63        previous_num_cubes = num_cubes_expected;
64
65        // Reduce tensor core usage
66        dim_num_tiles_m = dim_num_tiles_m.div_ceil(2);
67        stage_num_tiles = dim_num_tiles_m * dim_num_tiles_n;
68
69        // Number of cubes grows as a consequence of smaller stage
70        num_cubes_expected = total_tiles.div_ceil(stage_num_tiles);
71    }
72
73    // Compare previous and current values to determine the closest to `num_sm`
74    if (previous_num_cubes as isize - num_sm as isize).abs()
75        <= (num_cubes_expected as isize - num_sm as isize).abs()
76    {
77        (previous_dim_num_tiles, dim_num_tiles_n)
78    } else {
79        (dim_num_tiles_n, dim_num_tiles_m)
80    }
81}
82
83pub fn convolution_matmul_selection(
84    tile_matmul: TileMatmulKind,
85    client: &Client,
86    problem: &ConvolutionProblem,
87    plane_dim: u32,
88    swizzle: bool,
89    vector_sizes: &MatmulVectorSizes,
90    dtypes: &mut MatmulElems,
91) -> Result<BatchMatmulBlueprint, MatmulAvailabilityError> {
92    adjust_dtypes(client, dtypes, tile_matmul.requires_accelerator());
93
94    // rough heuristic based on previous bench results where 512 channels with a 3x3 kernel seemed
95    // to be the rough cutoff for the k=4 size.
96    let stage_k = if problem.k >= 4096 { 4 } else { 2 };
97
98    let tile_size = find_instruction_size::<_, _>(
99        client,
100        (
101            dtypes.lhs_register,
102            dtypes.rhs_register,
103            dtypes.acc_register,
104        ),
105        (problem.m, problem.n, problem.k).into(),
106        (None, None, None),
107        |c, cfg| tile_matmul.is_supported(c, cfg),
108        |c, l, r, a| tile_matmul.supported_sizes(c, l, r, a),
109    )?;
110
111    let hardware = &client.properties().hardware;
112    let num_sm = hardware
113        .num_streaming_multiprocessors
114        .unwrap_or(NUM_TENSOR_CORES_APPROX);
115    let max_tensor_cores = hardware.num_tensor_cores.unwrap_or(NUM_SM_APPROX);
116
117    let (stage_size_m, stage_size_n) = find_stage_size_m_n(
118        problem.m,
119        problem.n,
120        num_sm as usize,
121        max_tensor_cores as usize,
122        tile_size.m() as usize,
123        tile_size.n() as usize,
124        stage_k as usize,
125    );
126
127    let tiling_scheme = TilingScheme::builder()
128        .with_stage_size((stage_size_m as u32, 1, 1).into())
129        .with_tile_size(tile_size)
130        .with_partition_size((1, stage_size_n as u32, stage_k).into())
131        .build()
132        .unwrap();
133
134    let mut builder = BatchMatmulBlueprint::builder(
135        tile_matmul,
136        tiling_scheme,
137        plane_dim,
138        // Shapes and layouts only: the shared-memory budget is checked against the
139        // problem the launcher builds, which is where the bias is known.
140        &problem.as_matmul_problem(AccumulatorOperand::Absent),
141    )
142    .partition_buffering(PartitionBuffering::Single);
143
144    if swizzle {
145        let swizzle_dim = tiling_scheme.elements_per_stage_along_k() as usize;
146
147        let lhs = select_swizzle(swizzle_dim, dtypes.lhs_stage, vector_sizes.lhs);
148        let rhs = select_swizzle(swizzle_dim, dtypes.rhs_stage, vector_sizes.rhs);
149        builder = builder.shared_swizzle(SwizzleModes {
150            lhs,
151            rhs,
152            ..Default::default()
153        });
154    }
155
156    Ok(builder.build())
157}
158
159/// All modes currently use atom size 16
160const SWIZZLE_ATOM: usize = 16;
161
162fn select_swizzle(swizzle_dim: usize, elem: ElemType, vector_size: VectorSize) -> SwizzleMode {
163    // Vector size exceeds swizzle atom
164    if elem.size() * vector_size > SWIZZLE_ATOM {
165        return SwizzleMode::None;
166    }
167    let swizzle_dim_bytes = swizzle_dim * elem.size();
168    if !swizzle_dim_bytes.is_power_of_two() {
169        return SwizzleMode::None;
170    }
171    match swizzle_dim_bytes {
172        32 => SwizzleMode::B32,
173        64 => SwizzleMode::B64,
174        _ => SwizzleMode::B128,
175        //_ => SwizzleMode::None,
176    }
177}