1use std::sync::Arc;
4
5use cudarc::driver::LaunchConfig;
6use xlog_core::{Result, XlogError};
7use xlog_cuda::memory::TrackedCudaSlice;
8use xlog_cuda::provider::{weights_kernels, WEIGHTS_MODULE};
9use xlog_cuda::{CudaKernelProvider, LaunchAsync};
10
11use crate::compilation::gpu_cnf::GpuCnfVarTables;
12
13pub struct GpuWeights {
15 pub log_true: TrackedCudaSlice<f64>,
17 pub log_false: TrackedCudaSlice<f64>,
19}
20
21fn kernel_count_u32(context: &str, count: usize) -> Result<u32> {
22 u32::try_from(count)
23 .map_err(|_| XlogError::Compilation(format!("{context} exceeds GPU u32 index space")))
24}
25
26fn grid_for(count: u32, block: u32) -> Result<u32> {
27 if count == 0 {
28 return Ok(0);
29 }
30 if block == 0 {
31 return Err(XlogError::Compilation(
32 "GPU weight kernel block size must be nonzero".to_string(),
33 ));
34 }
35 let grid = (count as u64).div_ceil(block as u64);
36 let step = grid
37 .checked_mul(block as u64)
38 .ok_or_else(|| XlogError::Compilation("GPU weight grid-stride overflow".to_string()))?;
39 if step > u32::MAX as u64 {
40 return Err(XlogError::Compilation(
41 "GPU weight grid-stride step exceeds u32 index space".to_string(),
42 ));
43 }
44 u32::try_from(grid).map_err(|_| {
45 XlogError::Compilation("GPU weight kernel grid exceeds u32 index space".to_string())
46 })
47}
48
49fn checked_var_table_count(var_cap: u32) -> Result<u32> {
50 var_cap.checked_add(1).ok_or_else(|| {
51 XlogError::Compilation("GPU weight var_cap exceeds u32 table index space".to_string())
52 })
53}
54
55fn weights_len_for_var_cap(var_cap: u32) -> Result<usize> {
56 (var_cap as usize)
57 .checked_add(1)
58 .ok_or_else(|| XlogError::Compilation("weight table size overflow".to_string()))
59}
60
61fn query_weights_len_for_var_cap(var_cap: u32) -> Result<usize> {
62 (var_cap as usize)
63 .checked_add(1)
64 .ok_or_else(|| XlogError::Compilation("query var_cap overflow".to_string()))
65}
66
67fn evidence_len_for_var_cap(var_cap: u32) -> Result<usize> {
68 (var_cap as usize)
69 .checked_add(1)
70 .ok_or_else(|| XlogError::Compilation("evidence var_cap overflow".to_string()))
71}
72
73pub fn build_evidence_by_var_gpu(
75 node_var: &TrackedCudaSlice<u32>,
76 evidence_nodes: &TrackedCudaSlice<u32>,
77 evidence_vals: &TrackedCudaSlice<u8>,
78 var_cap: u32,
79 provider: &Arc<CudaKernelProvider>,
80) -> Result<TrackedCudaSlice<u8>> {
81 if evidence_nodes.len() != evidence_vals.len() {
82 return Err(XlogError::Compilation(format!(
83 "GPU evidence nodes len {} != vals len {}",
84 evidence_nodes.len(),
85 evidence_vals.len()
86 )));
87 }
88 let len = evidence_len_for_var_cap(var_cap)?;
89
90 let memory = provider.memory();
91 let device = provider.device().inner();
92 let mut evidence_by_var = memory.alloc::<u8>(len)?;
93 device
94 .memset_zeros(&mut evidence_by_var)
95 .map_err(|e| XlogError::Kernel(format!("Failed to zero evidence buffer: {}", e)))?;
96
97 let count = evidence_nodes.len();
98 if count == 0 {
99 return Ok(evidence_by_var);
100 }
101 let count_u32 = kernel_count_u32("GPU evidence node count", count)?;
102
103 let func = device
104 .get_func(
105 WEIGHTS_MODULE,
106 weights_kernels::WEIGHTS_SET_EVIDENCE_FROM_NODES,
107 )
108 .ok_or_else(|| {
109 XlogError::Kernel("weights_set_evidence_from_nodes kernel not found".to_string())
110 })?;
111 let block = 256u32;
112 let grid = grid_for(count_u32, block)?;
113 unsafe {
115 func.clone().launch(
116 LaunchConfig {
117 grid_dim: (grid.max(1), 1, 1),
118 block_dim: (block, 1, 1),
119 shared_mem_bytes: 0,
120 },
121 (
122 node_var,
123 evidence_nodes,
124 evidence_vals,
125 count_u32,
126 var_cap,
127 &mut evidence_by_var,
128 ),
129 )
130 }
131 .map_err(|e| XlogError::Kernel(format!("weights_set_evidence_from_nodes failed: {}", e)))?;
132 Ok(evidence_by_var)
134}
135
136pub fn map_nodes_to_vars_gpu(
138 node_var: &TrackedCudaSlice<u32>,
139 node_ids: &TrackedCudaSlice<u32>,
140 var_cap: u32,
141 provider: &Arc<CudaKernelProvider>,
142) -> Result<TrackedCudaSlice<u32>> {
143 let memory = provider.memory();
144 let device = provider.device().inner();
145 let mut out = memory.alloc::<u32>(node_ids.len())?;
146 let count = node_ids.len();
147 if count == 0 {
148 return Ok(out);
149 }
150 let count_u32 = kernel_count_u32("GPU node-to-var map count", count)?;
151
152 let func = device
153 .get_func(WEIGHTS_MODULE, weights_kernels::WEIGHTS_MAP_NODES_TO_VARS)
154 .ok_or_else(|| {
155 XlogError::Kernel("weights_map_nodes_to_vars kernel not found".to_string())
156 })?;
157
158 let block = 256u32;
159 let grid = grid_for(count_u32, block)?;
160 unsafe {
162 func.clone().launch(
163 LaunchConfig {
164 grid_dim: (grid.max(1), 1, 1),
165 block_dim: (block, 1, 1),
166 shared_mem_bytes: 0,
167 },
168 (node_var, node_ids, count_u32, var_cap, &mut out),
169 )
170 }
171 .map_err(|e| XlogError::Kernel(format!("weights_map_nodes_to_vars failed: {}", e)))?;
172 Ok(out)
174}
175
176pub fn apply_query_vars_device(
178 provider: &Arc<CudaKernelProvider>,
179 query_vars: &TrackedCudaSlice<u32>,
180 var_cap: u32,
181 log_false: &mut TrackedCudaSlice<f64>,
182 saved: &mut TrackedCudaSlice<f64>,
183) -> Result<()> {
184 let count = query_vars.len();
185 if saved.len() < count {
186 return Err(XlogError::Compilation(format!(
187 "query restore buffer len {} < query vars len {}",
188 saved.len(),
189 count
190 )));
191 }
192 let weights_len = query_weights_len_for_var_cap(var_cap)?;
193 if log_false.len() < weights_len {
194 return Err(XlogError::Compilation(format!(
195 "log_false len {} < var_cap+1 {}",
196 log_false.len(),
197 weights_len
198 )));
199 }
200 if count == 0 {
201 return Ok(());
202 }
203 let count_u32 = kernel_count_u32("GPU query apply count", count)?;
204
205 let device = provider.device().inner();
206 let func = device
207 .get_func(WEIGHTS_MODULE, weights_kernels::WEIGHTS_APPLY_QUERY_VARS)
208 .ok_or_else(|| {
209 XlogError::Kernel("weights_apply_query_vars kernel not found".to_string())
210 })?;
211
212 let block = 256u32;
213 let grid = grid_for(count_u32, block)?;
214 unsafe {
216 func.clone().launch(
217 LaunchConfig {
218 grid_dim: (grid.max(1), 1, 1),
219 block_dim: (block, 1, 1),
220 shared_mem_bytes: 0,
221 },
222 (query_vars, count_u32, var_cap, log_false, saved),
223 )
224 }
225 .map_err(|e| XlogError::Kernel(format!("weights_apply_query_vars failed: {}", e)))?;
226 Ok(())
228}
229
230pub fn restore_query_vars_device(
232 provider: &Arc<CudaKernelProvider>,
233 query_vars: &TrackedCudaSlice<u32>,
234 var_cap: u32,
235 log_false: &mut TrackedCudaSlice<f64>,
236 saved: &TrackedCudaSlice<f64>,
237) -> Result<()> {
238 let count = query_vars.len();
239 if saved.len() < count {
240 return Err(XlogError::Compilation(format!(
241 "query restore buffer len {} < query vars len {}",
242 saved.len(),
243 count
244 )));
245 }
246 let weights_len = query_weights_len_for_var_cap(var_cap)?;
247 if log_false.len() < weights_len {
248 return Err(XlogError::Compilation(format!(
249 "log_false len {} < var_cap+1 {}",
250 log_false.len(),
251 weights_len
252 )));
253 }
254 if count == 0 {
255 return Ok(());
256 }
257 let count_u32 = kernel_count_u32("GPU query restore count", count)?;
258
259 let device = provider.device().inner();
260 let func = device
261 .get_func(WEIGHTS_MODULE, weights_kernels::WEIGHTS_RESTORE_QUERY_VARS)
262 .ok_or_else(|| {
263 XlogError::Kernel("weights_restore_query_vars kernel not found".to_string())
264 })?;
265
266 let block = 256u32;
267 let grid = grid_for(count_u32, block)?;
268 unsafe {
270 func.clone().launch(
271 LaunchConfig {
272 grid_dim: (grid.max(1), 1, 1),
273 block_dim: (block, 1, 1),
274 shared_mem_bytes: 0,
275 },
276 (query_vars, count_u32, var_cap, log_false, saved),
277 )
278 }
279 .map_err(|e| XlogError::Kernel(format!("weights_restore_query_vars failed: {}", e)))?;
280 Ok(())
282}
283
284pub fn build_weights_gpu(
286 vars: &GpuCnfVarTables,
287 leaf_probs: &TrackedCudaSlice<f64>,
288 choice_true: &TrackedCudaSlice<f64>,
289 choice_false: &TrackedCudaSlice<f64>,
290 evidence_by_var: &TrackedCudaSlice<u8>,
291 provider: &Arc<CudaKernelProvider>,
292) -> Result<GpuWeights> {
293 let var_cap = vars.max_var;
294 let weights_len = weights_len_for_var_cap(var_cap)?;
295
296 if vars.leaf_var.len() < leaf_probs.len() {
297 return Err(XlogError::Compilation(format!(
298 "leaf_probs len {} exceeds leaf_var len {}",
299 leaf_probs.len(),
300 vars.leaf_var.len()
301 )));
302 }
303 if vars.choice_var.len() < choice_true.len() {
304 return Err(XlogError::Compilation(format!(
305 "choice_true len {} exceeds choice_var len {}",
306 choice_true.len(),
307 vars.choice_var.len()
308 )));
309 }
310 if choice_true.len() != choice_false.len() {
311 return Err(XlogError::Compilation(format!(
312 "choice_true len {} != choice_false len {}",
313 choice_true.len(),
314 choice_false.len()
315 )));
316 }
317 if evidence_by_var.len() != weights_len {
318 return Err(XlogError::Compilation(format!(
319 "evidence_by_var len {} != weights len {}",
320 evidence_by_var.len(),
321 weights_len
322 )));
323 }
324
325 let memory = provider.memory();
326 let device = provider.device().inner();
327 let mut log_true = memory.alloc::<f64>(weights_len)?;
328 let mut log_false = memory.alloc::<f64>(weights_len)?;
329
330 device
332 .memset_zeros(&mut log_true)
333 .map_err(|e| XlogError::Kernel(format!("Failed to zero log_true weights: {}", e)))?;
334 device
335 .memset_zeros(&mut log_false)
336 .map_err(|e| XlogError::Kernel(format!("Failed to zero log_false weights: {}", e)))?;
337
338 let block = 256u32;
339
340 if !leaf_probs.is_empty() {
341 let leaf_count = kernel_count_u32("GPU leaf probability count", leaf_probs.len())?;
342 let func = device
343 .get_func(WEIGHTS_MODULE, weights_kernels::WEIGHTS_FILL_LEAF)
344 .ok_or_else(|| XlogError::Kernel("weights_fill_leaf kernel not found".to_string()))?;
345 let grid = grid_for(leaf_count, block)?;
346 unsafe {
348 func.clone().launch(
349 LaunchConfig {
350 grid_dim: (grid.max(1), 1, 1),
351 block_dim: (block, 1, 1),
352 shared_mem_bytes: 0,
353 },
354 (
355 &vars.leaf_var,
356 leaf_probs,
357 leaf_count,
358 var_cap,
359 &mut log_true,
360 &mut log_false,
361 ),
362 )
363 }
364 .map_err(|e| XlogError::Kernel(format!("weights_fill_leaf failed: {}", e)))?;
365 }
366
367 if !choice_true.is_empty() {
368 let choice_count = kernel_count_u32("GPU choice probability count", choice_true.len())?;
369 let func = device
370 .get_func(WEIGHTS_MODULE, weights_kernels::WEIGHTS_FILL_CHOICE)
371 .ok_or_else(|| XlogError::Kernel("weights_fill_choice kernel not found".to_string()))?;
372 let grid = grid_for(choice_count, block)?;
373 unsafe {
375 func.clone().launch(
376 LaunchConfig {
377 grid_dim: (grid.max(1), 1, 1),
378 block_dim: (block, 1, 1),
379 shared_mem_bytes: 0,
380 },
381 (
382 &vars.choice_var,
383 choice_true,
384 choice_false,
385 choice_count,
386 var_cap,
387 &mut log_true,
388 &mut log_false,
389 ),
390 )
391 }
392 .map_err(|e| XlogError::Kernel(format!("weights_fill_choice failed: {}", e)))?;
393 }
394
395 if !evidence_by_var.is_empty() {
396 let var_table_count = checked_var_table_count(var_cap)?;
397 let func = device
398 .get_func(WEIGHTS_MODULE, weights_kernels::WEIGHTS_APPLY_EVIDENCE)
399 .ok_or_else(|| {
400 XlogError::Kernel("weights_apply_evidence kernel not found".to_string())
401 })?;
402 let grid = grid_for(var_table_count, block)?;
403 unsafe {
405 func.clone().launch(
406 LaunchConfig {
407 grid_dim: (grid.max(1), 1, 1),
408 block_dim: (block, 1, 1),
409 shared_mem_bytes: 0,
410 },
411 (evidence_by_var, var_cap, &mut log_true, &mut log_false),
412 )
413 }
414 .map_err(|e| XlogError::Kernel(format!("weights_apply_evidence failed: {}", e)))?;
415 }
416 Ok(GpuWeights {
418 log_true,
419 log_false,
420 })
421}