Skip to main content

cubecl_runtime/config/
streaming.rs

1use super::logger::{LogLevel, LoggerConfig};
2
3pub use cubecl_environment::stream::StreamPolicy;
4
5/// Configuration for streaming settings in `CubeCL`.
6#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
7pub struct StreamingConfig {
8    /// Logger configuration for streaming logs, using binary log levels.
9    #[serde(default)]
10    pub logger: LoggerConfig<StreamingLogLevel>,
11    /// The maximum number of streams to be used.
12    #[serde(default = "default_max_streams")]
13    pub max_streams: u8,
14    /// Backend stream priority hint.
15    ///
16    /// Backends that expose stream priorities (e.g. CUDA via
17    /// `cuStreamCreateWithPriority`) map this to their native range. Backends
18    /// without a notion of stream priority ignore it. The default is
19    /// [`StreamPriority::Default`], which preserves existing behavior.
20    #[serde(default)]
21    pub priority: StreamPriority,
22    /// How the current stream is derived: `"per-thread"` (default),
23    /// `"per-task"` (stable stream per async task, requires the `tokio`
24    /// feature to take effect) or `"single"`.
25    ///
26    /// A programmatic `cubecl_environment::stream::set_policy` call always
27    /// wins over this setting.
28    #[serde(default)]
29    pub policy: StreamPolicy,
30}
31
32impl Default for StreamingConfig {
33    fn default() -> Self {
34        Self {
35            logger: Default::default(),
36            max_streams: default_max_streams(),
37            priority: StreamPriority::default(),
38            policy: StreamPolicy::default(),
39        }
40    }
41}
42
43fn default_max_streams() -> u8 {
44    128
45}
46
47/// Stream priority hint, mapped to the backend's native priority range.
48///
49/// CUDA convention is that lower numerical priorities run first; this enum is
50/// the portable abstraction so other runtimes can adopt it without changing
51/// their public surface. Backends clamp to their supported range — passing
52/// [`StreamPriority::Low`] on a device whose only priority bucket is the
53/// default is harmless.
54///
55/// The motivating use case is sharing a single GPU with a desktop compositor
56/// (WSL2, dev laptops, single-GPU workstations): running long compute batches
57/// on a low-priority stream lets the compositor preempt cleanly and keeps the
58/// UI responsive.
59#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
60pub enum StreamPriority {
61    /// Default backend priority — current behavior on every backend.
62    #[default]
63    #[serde(rename = "default")]
64    Default,
65    /// Lowest priority the backend supports. Useful when sharing the GPU with
66    /// an interactive workload such as a compositor; long-running compute
67    /// batches yield to higher-priority work like UI rendering.
68    #[serde(rename = "low")]
69    Low,
70    /// Highest priority the backend supports. Useful for latency-critical
71    /// foreground work that must not be preempted by background batches.
72    #[serde(rename = "high")]
73    High,
74}
75
76/// Log levels for streaming in `CubeCL`.
77#[derive(Default, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
78pub enum StreamingLogLevel {
79    /// Compilation logging is disabled.
80    #[default]
81    #[serde(rename = "disabled")]
82    Disabled,
83
84    /// Basic streaming information is logged such as when streams are merged.
85    #[serde(rename = "basic")]
86    Basic,
87
88    /// Full streaming details are logged.
89    #[serde(rename = "full")]
90    Full,
91}
92
93impl LogLevel for StreamingLogLevel {}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn default_priority_is_default_variant() {
101        assert_eq!(StreamingConfig::default().priority, StreamPriority::Default);
102    }
103
104    #[cfg(feature = "std")]
105    #[test]
106    fn priority_omitted_in_toml_falls_back_to_default() {
107        // Existing config files written before this field was added must
108        // continue to deserialize unchanged.
109        let cfg: StreamingConfig = toml::from_str("max_streams = 64").unwrap();
110        assert_eq!(cfg.priority, StreamPriority::Default);
111        assert_eq!(cfg.max_streams, 64);
112    }
113
114    #[test]
115    fn priority_serde_roundtrip() {
116        for p in [
117            StreamPriority::Default,
118            StreamPriority::Low,
119            StreamPriority::High,
120        ] {
121            let s = serde_json::to_string(&p).unwrap();
122            let back: StreamPriority = serde_json::from_str(&s).unwrap();
123            assert_eq!(p, back);
124        }
125    }
126
127    #[test]
128    fn priority_serde_uses_lowercase_names() {
129        // Stable on-disk representation — don't break user configs by accident.
130        assert_eq!(
131            serde_json::to_string(&StreamPriority::Low).unwrap(),
132            "\"low\""
133        );
134        assert_eq!(
135            serde_json::to_string(&StreamPriority::High).unwrap(),
136            "\"high\""
137        );
138        assert_eq!(
139            serde_json::to_string(&StreamPriority::Default).unwrap(),
140            "\"default\""
141        );
142    }
143}