Skip to main content

cubecl_runtime/execution/
stream_mode.rs

1use crate::client::Client;
2use alloc::vec::Vec;
3use cubecl_common::device::ServiceId;
4use cubecl_environment::stream::StreamId;
5use cubecl_environment::sync::{AtomicUsize, Mutex, Ordering};
6
7/// Whether a stream's launches run.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum StreamMode {
10    /// They run.
11    Execute,
12    /// Their kernels compile — now under
13    /// [`CompileAndAutotune`](super::ProcessMode::CompileAndAutotune),
14    /// queued for a batch otherwise — and the launches are discarded.
15    Discard,
16}
17
18impl StreamMode {
19    /// Its slot in a [`StreamModeRegistry`]'s live counts.
20    fn index(self) -> usize {
21        match self {
22            Self::Execute => 0,
23            Self::Discard => 1,
24        }
25    }
26
27    /// The other mode.
28    fn opposite(self) -> Self {
29        match self {
30            Self::Execute => Self::Discard,
31            Self::Discard => Self::Execute,
32        }
33    }
34}
35
36/// One stream of one device's service: what a [`StreamModeOverride`] sets
37/// the mode of. Stream ids are the process's, not a device's, so a
38/// measurement on one device leaves the same stream of every other device
39/// alone.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub(crate) struct ServiceStream {
42    /// The device's service, as its clients reach it.
43    pub service: ServiceId,
44    /// The stream on it.
45    pub stream: StreamId,
46}
47
48/// Sets the mode of one client's stream on its device while it lives,
49/// restored on drop: what a measurement opens, so its launches run whatever
50/// the process mode discards.
51///
52/// Keyed on the device and the stream the client's launches go out on — a
53/// client bound to a stream of its own does not follow the thread's — so it
54/// holds wherever those launches are issued from, a task resumed on another
55/// thread included. Overrides of one stream nest, the newest deciding, and
56/// may drop in any order.
57///
58/// Only the client's stream follows it: a launch another client issues on
59/// another stream — a candidate holding a client bound elsewhere — keeps its
60/// own stream's mode, and under a process mode that discards is discarded,
61/// so a measurement launches through the client it switched.
62///
63/// Every launch on that stream of that device follows it, whichever thread
64/// issues it. Where threads share a stream — `StreamPolicy::Single`, or a
65/// build without per-thread streams — a launch another thread issues while a
66/// measurement runs executes too, rather than being discarded.
67#[derive(Debug)]
68pub struct StreamModeOverride {
69    id: usize,
70    mode: StreamMode,
71}
72
73/// One live [`StreamModeOverride`].
74#[derive(Debug)]
75struct StreamModeEntry {
76    id: usize,
77    stream: ServiceStream,
78    mode: StreamMode,
79}
80
81/// The live [`StreamModeOverride`]s: what sets each stream's mode, and how
82/// many set each mode, so a launch looks them up only when one sets the mode
83/// its process mode does not — a measurement under no override, which sets
84/// the mode every stream already has, costs other launches nothing.
85pub(crate) struct StreamModeRegistry {
86    /// The live overrides, oldest first.
87    entries: Mutex<Vec<StreamModeEntry>>,
88    /// How many live overrides set each mode, by [`StreamMode::index`].
89    live: [AtomicUsize; 2],
90    /// The id the next override takes.
91    next_id: AtomicUsize,
92}
93
94/// The process's stream modes.
95pub(crate) static STREAM_MODES: StreamModeRegistry = StreamModeRegistry {
96    entries: Mutex::new(Vec::new()),
97    live: [AtomicUsize::new(0), AtomicUsize::new(0)],
98    next_id: AtomicUsize::new(0),
99};
100
101impl StreamModeRegistry {
102    /// Put `stream` in `mode` until the guard it hands back drops.
103    pub(crate) fn set(
104        &'static self,
105        mode: StreamMode,
106        stream: ServiceStream,
107    ) -> StreamModeOverride {
108        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
109        let mut entries = self.entries.lock();
110        entries.push(StreamModeEntry { id, stream, mode });
111        self.live[mode.index()].fetch_add(1, Ordering::Release);
112        StreamModeOverride { id, mode }
113    }
114
115    /// `stream`'s mode now: what the newest live override sets for it, or
116    /// `default` when none does.
117    pub(crate) fn mode(&self, stream: ServiceStream, default: StreamMode) -> StreamMode {
118        // An override setting the default changes nothing, unless it is
119        // newer than one setting the other mode on the same stream, and then
120        // there is one of those to look for.
121        if self.live[default.opposite().index()].load(Ordering::Acquire) == 0 {
122            return default;
123        }
124        self.entries
125            .lock()
126            .iter()
127            .rev()
128            .find(|entry| entry.stream == stream)
129            .map_or(default, |entry| entry.mode)
130    }
131
132    fn unset(&self, id: usize, mode: StreamMode) {
133        let mut entries = self.entries.lock();
134        entries.retain(|entry| entry.id != id);
135        self.live[mode.index()].fetch_sub(1, Ordering::Release);
136    }
137}
138
139impl StreamModeOverride {
140    /// Put `client`'s stream on its device in `mode` until the guard drops.
141    pub fn new(mode: StreamMode, client: &Client) -> Self {
142        STREAM_MODES.set(mode, client.service_stream())
143    }
144}
145
146impl Drop for StreamModeOverride {
147    fn drop(&mut self) {
148        STREAM_MODES.unset(self.id, self.mode);
149    }
150}