cubecl_core/compute/
launcher.rs1use alloc::{boxed::Box, vec::Vec};
2
3use crate::prelude::{BufferArg, PeerArg, TensorArg, TensorMapArg, TensorMapKind};
4use crate::{InfoBuilder, ScalarArgType};
5#[cfg(feature = "std")]
6use core::cell::RefCell;
7use cubecl_ir::{AddressType, ElemType, Scope, settings::KernelSettings};
8use cubecl_runtime::kernel::BufferIOAttr;
9use cubecl_runtime::server::{
10 BufferBinding, CubeCount, KernelResource, PeerBinding, TensorMapBinding,
11};
12use cubecl_runtime::{client::Client, kernel::CubeKernel, server::KernelArguments};
13
14#[cfg(feature = "std")]
15std::thread_local! {
16 static INFO: RefCell<InfoBuilder> = RefCell::new(InfoBuilder::default());
17 static SCOPE: RefCell<Scope> = RefCell::new(Scope::dummy());
19}
20
21pub struct KernelLauncher {
23 resources: Vec<KernelResource>,
24 declared_io: Vec<BufferIOAttr>,
27 declaring: BufferIOAttr,
29 address_type: AddressType,
30 pub settings: KernelSettings,
31 #[cfg(not(feature = "std"))]
32 info: InfoBuilder,
33 #[cfg(not(feature = "std"))]
34 pub scope: Scope,
35}
36
37impl KernelLauncher {
38 #[cfg(feature = "std")]
39 pub fn with_scope<T>(&mut self, fun: impl FnMut(&Scope) -> T) -> T {
40 SCOPE.with_borrow(fun)
41 }
42
43 #[cfg(not(feature = "std"))]
44 pub fn with_scope<T>(&mut self, mut fun: impl FnMut(&Scope) -> T) -> T {
45 fun(&self.scope)
46 }
47
48 #[cfg(feature = "std")]
49 fn with_info<T>(&mut self, fun: impl FnMut(&mut InfoBuilder) -> T) -> T {
50 INFO.with_borrow_mut(fun)
51 }
52
53 #[cfg(not(feature = "std"))]
54 fn with_info<T>(&mut self, mut fun: impl FnMut(&mut InfoBuilder) -> T) -> T {
55 fun(&mut self.info)
56 }
57
58 pub fn register_scalar<C: ScalarArgType>(&mut self, scalar: C) {
60 self.with_info(|info| info.scalars.push(scalar));
61 }
62
63 pub fn register_scalar_raw(&mut self, bytes: &[u8], dtype: ElemType) {
65 self.with_info(|info| info.scalars.push_raw(bytes, dtype));
66 }
67
68 #[track_caller]
70 pub fn launch<K: CubeKernel>(self, cube_count: CubeCount, kernel: K, client: &Client) {
71 let bindings = self.into_bindings();
72 let kernel = Box::new(kernel);
73
74 client.launch(kernel, cube_count, bindings)
75 }
76
77 pub fn discard(self) {
86 let _ = self.into_bindings();
87 }
88
89 fn into_bindings(mut self) -> KernelArguments {
99 let mut bindings = KernelArguments::new();
100 let address_type = self.address_type;
101 let info = self.with_info(|info| info.finish(address_type));
102
103 bindings.resources = self.resources;
104 bindings.declared_io = self.declared_io;
105 bindings.info = info;
106
107 bindings
108 }
109}
110
111impl KernelLauncher {
113 pub fn declare_io(&mut self, io: BufferIOAttr) {
125 self.declaring = io;
126 }
127
128 fn alias_io(&mut self, input_pos: usize) {
137 if self.declaring.is_writable()
138 && let Some(io) = self.declared_io.get_mut(input_pos)
139 {
140 *io = BufferIOAttr::ReadWrite;
141 }
142 }
143
144 fn push_resource(&mut self, resource: KernelResource) {
146 let io = match &resource {
147 KernelResource::TensorMap(_) => BufferIOAttr::ReadWrite,
152 KernelResource::Buffer(_) | KernelResource::Peer(_) => self.declaring,
153 };
154 self.declared_io.push(io);
155 self.resources.push(resource);
156 }
157
158 pub fn register_tensor(&mut self, tensor: TensorArg, elem_size: usize) {
160 if let Some(tensor) = self.process_tensor(tensor, elem_size) {
161 self.push_resource(KernelResource::Buffer(tensor));
162 }
163 }
164
165 fn process_tensor(&mut self, tensor: TensorArg, elem_size: usize) -> Option<BufferBinding> {
166 let tensor = match tensor {
167 TensorArg::Handle { handle, .. } => handle,
168 TensorArg::Alias { input_pos, .. } => {
169 self.alias_io(input_pos);
170 return None;
171 }
172 };
173
174 let buffer_len = tensor.handle.size_in_used() / elem_size as u64;
175 let address_type = self.address_type;
176
177 self.with_info(|info| {
178 info.metadata.register_tensor(
179 buffer_len,
180 tensor.shape.clone(),
181 tensor.strides.clone(),
182 address_type,
183 )
184 });
185 Some(tensor.handle)
186 }
187
188 pub fn register_buffer(&mut self, array: BufferArg, elem_size: usize) {
190 if let Some(tensor) = self.process_buffer(array, elem_size) {
191 self.push_resource(KernelResource::Buffer(tensor));
192 }
193 }
194
195 fn process_buffer(&mut self, array: BufferArg, elem_size: usize) -> Option<BufferBinding> {
196 let array = match array {
197 BufferArg::Handle { handle, .. } => handle,
198 BufferArg::Alias { input_pos, .. } => {
199 self.alias_io(input_pos);
200 return None;
201 }
202 };
203
204 let buffer_len = array.handle.size_in_used() / elem_size as u64;
205 let address_type = self.address_type;
206 self.with_info(|info| info.metadata.register_buffer(buffer_len, address_type));
207 Some(array.handle)
208 }
209
210 pub fn register_peer(&mut self, buffer: PeerArg, elem_size: usize) {
214 let binding = self
215 .process_buffer(
216 BufferArg::Handle {
217 handle: buffer.handle,
218 },
219 elem_size,
220 )
221 .expect("a handle is no alias");
222 let peer = PeerBinding::new(binding)
223 .expect("a peer argument is built from a handle shared with its peers");
224 self.push_resource(KernelResource::Peer(peer));
225 }
226
227 pub fn register_tensor_map<K: TensorMapKind>(
229 &mut self,
230 map: TensorMapArg<K>,
231 elem_size: usize,
232 ) {
233 let binding = self
234 .process_tensor(map.tensor, elem_size)
235 .expect("Can't use alias for TensorMap");
236
237 let map = map.metadata.clone();
238 self.push_resource(KernelResource::TensorMap(TensorMapBinding { binding, map }));
239 }
240}
241
242impl KernelLauncher {
243 pub fn new(settings: KernelSettings) -> Self {
244 Self {
245 address_type: settings.address_type,
246 settings,
247 resources: Vec::new(),
248 declared_io: Vec::new(),
249 declaring: BufferIOAttr::ReadWrite,
250 #[cfg(not(feature = "std"))]
251 info: InfoBuilder::default(),
252 #[cfg(not(feature = "std"))]
253 scope: Scope::dummy(),
254 }
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use cubecl_ir::settings::{Dim3, ExecutionMode};
262
263 fn settings() -> KernelSettings {
264 KernelSettings::new(Dim3::new_single(), ExecutionMode::Checked, AddressType::U32)
265 }
266
267 fn info_of(launcher: KernelLauncher) -> Vec<u64> {
268 launcher.into_bindings().info.data
269 }
270
271 #[test]
277 fn a_discarded_launcher_leaves_nothing_for_the_next_launch() {
278 let empty = info_of(KernelLauncher::new(settings()));
279
280 let mut registered = KernelLauncher::new(settings());
284 registered.register_scalar(1u32);
285 assert_ne!(info_of(registered), empty);
286
287 let mut dummy = KernelLauncher::new(settings());
288 dummy.register_scalar(1u32);
289 dummy.discard();
290
291 assert_eq!(
292 info_of(KernelLauncher::new(settings())),
293 empty,
294 "a discarded launcher left its scalars behind for the next launch"
295 );
296 }
297}