1use std::collections::VecDeque;
4
5use sim_kernel::Symbol;
6
7use crate::{
8 WgpuAdapterProbe,
9 kernels::{PORTABLE_ELEMENTWISE_WGSL, PORTABLE_LINALG_WGSL, PORTABLE_REDUCTION_WGSL},
10};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum WgpuKernelOp {
15 Add,
17 Sub,
19 Mul,
21 Div,
23 Sqrt,
25 Exp,
27 Sin,
29 Cos,
31 Sum,
33 Min,
35 Max,
37 Norm,
39 Transpose,
41 Dot,
43 Matmul,
45}
46
47impl WgpuKernelOp {
48 pub fn is_binary(self) -> bool {
50 matches!(self, Self::Add | Self::Sub | Self::Mul | Self::Div)
51 }
52
53 pub fn is_unary(self) -> bool {
55 matches!(
56 self,
57 Self::Sqrt
58 | Self::Exp
59 | Self::Sin
60 | Self::Cos
61 | Self::Sum
62 | Self::Min
63 | Self::Max
64 | Self::Norm
65 | Self::Transpose
66 )
67 }
68
69 pub fn is_reduction(self) -> bool {
71 matches!(self, Self::Sum | Self::Min | Self::Max | Self::Norm)
72 }
73
74 pub fn is_linalg(self) -> bool {
76 matches!(self, Self::Transpose | Self::Dot | Self::Matmul)
77 }
78
79 fn symbol_name(self) -> &'static str {
80 match self {
81 Self::Add => "add",
82 Self::Sub => "sub",
83 Self::Mul => "mul",
84 Self::Div => "div",
85 Self::Sqrt => "sqrt",
86 Self::Exp => "exp",
87 Self::Sin => "sin",
88 Self::Cos => "cos",
89 Self::Sum => "sum",
90 Self::Min => "min",
91 Self::Max => "max",
92 Self::Norm => "norm",
93 Self::Transpose => "transpose",
94 Self::Dot => "dot",
95 Self::Matmul => "matmul",
96 }
97 }
98
99 fn wgsl_bytes(self) -> usize {
100 if self.is_reduction() {
101 PORTABLE_REDUCTION_WGSL.len()
102 } else if self.is_linalg() {
103 PORTABLE_LINALG_WGSL.len()
104 } else {
105 PORTABLE_ELEMENTWISE_WGSL.len()
106 }
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub enum WgpuKernelDType {
113 F32,
115 F16Native,
117 Bf16WidenedToF32,
119}
120
121impl WgpuKernelDType {
122 fn symbol_name(self) -> &'static str {
123 match self {
124 Self::F32 => "f32",
125 Self::F16Native => "f16",
126 Self::Bf16WidenedToF32 => "f32-widened-half",
127 }
128 }
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
133pub struct WgpuPipelineKey {
134 pub adapter_ordinal: usize,
136 pub op: WgpuKernelOp,
138 pub dtype: WgpuKernelDType,
140 pub rank: usize,
142 pub tile: WgpuTileProfile,
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub struct WgpuTileProfile {
149 pub workgroup_width: u32,
151 pub matrix_tile: u32,
153 pub reduction_lanes: u32,
155 pub max_dispatch_bytes: u64,
157}
158
159impl WgpuTileProfile {
160 pub fn from_probe(probe: &WgpuAdapterProbe) -> Self {
162 let limits = &probe.adapter.granted_limits;
163 let max_x = limits.max_compute_workgroup_size_x.max(1);
164 let max_invocations = limits.max_compute_invocations_per_workgroup.max(1);
165 let workgroup_width = max_x.min(max_invocations).clamp(1, 256);
166 let matrix_tile = 16_u32.min(workgroup_width).min(max_invocations).max(1);
167 let reduction_lanes = workgroup_width.clamp(1, 256);
168 Self {
169 workgroup_width,
170 matrix_tile,
171 reduction_lanes,
172 max_dispatch_bytes: limits.max_buffer_size.max(4),
173 }
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Eq)]
179pub struct WgpuPipelineRecord {
180 pub key: WgpuPipelineKey,
182 pub symbol: Symbol,
184 pub wgsl_bytes: usize,
186}
187
188#[derive(Clone, Debug, Default, PartialEq, Eq)]
190pub struct WgpuPipelineCacheSnapshot {
191 pub entries: usize,
193 pub hits: usize,
195 pub misses: usize,
197 pub evictions: usize,
199}
200
201#[derive(Clone, Debug)]
203pub struct WgpuPipelineCache {
204 capacity: usize,
205 entries: VecDeque<WgpuPipelineRecord>,
206 hits: usize,
207 misses: usize,
208 evictions: usize,
209}
210
211impl WgpuPipelineCache {
212 pub fn new(capacity: usize) -> Self {
214 Self {
215 capacity: capacity.max(1),
216 entries: VecDeque::new(),
217 hits: 0,
218 misses: 0,
219 evictions: 0,
220 }
221 }
222
223 pub fn get_or_insert(
225 &mut self,
226 probe: &WgpuAdapterProbe,
227 op: WgpuKernelOp,
228 dtype: WgpuKernelDType,
229 rank: usize,
230 ) -> WgpuPipelineRecord {
231 let tile = WgpuTileProfile::from_probe(probe);
232 let key = WgpuPipelineKey {
233 adapter_ordinal: probe.adapter.ordinal,
234 op,
235 dtype,
236 rank,
237 tile,
238 };
239 if let Some(record) = self.entries.iter().find(|record| record.key == key) {
240 self.hits += 1;
241 return record.clone();
242 }
243 self.misses += 1;
244 if self.entries.len() == self.capacity {
245 self.entries.pop_front();
246 self.evictions += 1;
247 }
248 let record = WgpuPipelineRecord {
249 symbol: Symbol::qualified(
250 "compute.pipeline.wgpu",
251 format!(
252 "{}/{}/{}/rank-{}",
253 probe.adapter.ordinal,
254 op.symbol_name(),
255 dtype.symbol_name(),
256 rank
257 ),
258 ),
259 key,
260 wgsl_bytes: op.wgsl_bytes(),
261 };
262 self.entries.push_back(record.clone());
263 record
264 }
265
266 pub fn snapshot(&self) -> WgpuPipelineCacheSnapshot {
268 WgpuPipelineCacheSnapshot {
269 entries: self.entries.len(),
270 hits: self.hits,
271 misses: self.misses,
272 evictions: self.evictions,
273 }
274 }
275}
276
277impl Default for WgpuPipelineCache {
278 fn default() -> Self {
279 Self::new(16)
280 }
281}