1use std::ffi::c_void;
4
5use cudarc::driver::LaunchConfig;
6use xlog_core::{Result, XlogError};
7use xlog_cuda::memory::TrackedCudaSlice;
8use xlog_cuda::provider::{
9 arith_kernels, d4_kernels, filter_kernels, ARITH_MODULE, D4_MODULE, FILTER_MODULE,
10};
11use xlog_cuda::{circuit_kernels, AsKernelParam, CudaKernelProvider, LaunchAsync, CIRCUIT_MODULE};
12
13use crate::compilation::gpu_d4::exclusive_scan_u32_inplace;
14use crate::logsumexp::validate_circuit_log_weight_pair;
15#[cfg(feature = "host-io")]
16use crate::logsumexp::{validate_circuit_gradient_values, validate_circuit_value};
17use crate::xgcf::{Xgcf, XgcfNodeType};
18
19pub struct GpuCircuitBuilder {
24 pub node_type: TrackedCudaSlice<u8>,
26 pub child_offsets: TrackedCudaSlice<u32>,
28 pub child_indices: TrackedCudaSlice<u32>,
30 pub lit: TrackedCudaSlice<i32>,
32 pub decision_var: TrackedCudaSlice<u32>,
34 pub decision_child_false: TrackedCudaSlice<u32>,
36 pub decision_child_true: TrackedCudaSlice<u32>,
38}
39
40pub struct GpuCircuitLayout {
42 pub num_nodes: u32,
44 pub num_edges: u32,
46 pub num_levels: u32,
48 pub level_offsets: TrackedCudaSlice<u32>,
50 pub level_nodes: TrackedCudaSlice<u32>,
52 pub root: u32,
54 pub max_var: u32,
56 pub num_nodes_device: Option<TrackedCudaSlice<u32>>,
58 pub num_edges_device: Option<TrackedCudaSlice<u32>>,
60}
61
62pub struct GpuXgcf {
64 node_type: TrackedCudaSlice<u8>,
65 child_offsets: TrackedCudaSlice<u32>,
66 child_indices: TrackedCudaSlice<u32>,
67 lit: TrackedCudaSlice<i32>,
68 decision_var: TrackedCudaSlice<u32>,
69 decision_child_false: TrackedCudaSlice<u32>,
70 decision_child_true: TrackedCudaSlice<u32>,
71 level_nodes: TrackedCudaSlice<u32>,
72 level_offsets: TrackedCudaSlice<u32>,
73 level_offsets_host: Option<Vec<u32>>,
76 node_cap: u32,
77 edge_cap: u32,
78 num_levels: u32,
79 root: u32,
80 max_var: u32,
81 meta_num_nodes: TrackedCudaSlice<u32>,
82 meta_num_edges: TrackedCudaSlice<u32>,
83 var_log_true: TrackedCudaSlice<f64>,
84 var_log_false: TrackedCudaSlice<f64>,
85 values: TrackedCudaSlice<f64>,
86 adj: TrackedCudaSlice<f64>,
87 grad_true: TrackedCudaSlice<f64>,
88 grad_false: TrackedCudaSlice<f64>,
89 free_var_mask: Option<TrackedCudaSlice<u8>>,
90}
91
92fn checked_gpu_u32_len(context: &str, len: usize) -> Result<u32> {
93 u32::try_from(len)
94 .map_err(|_| XlogError::Compilation(format!("{context} exceeds u32::MAX: {len}")))
95}
96
97fn checked_gpu_len_add_one(context: &str, len: usize) -> Result<usize> {
98 len.checked_add(1)
99 .ok_or_else(|| XlogError::Compilation(format!("{context} length overflow")))
100}
101
102fn checked_gpu_launch_blocks(context: &str, item_count: usize, block_size: u32) -> Result<u32> {
103 let item_count = u32::try_from(item_count).map_err(|_| {
104 XlogError::Kernel(format!(
105 "{context} launch item count exceeds u32::MAX: {item_count}"
106 ))
107 })?;
108 item_count
109 .checked_add(block_size - 1)
110 .map(|rounded| rounded / block_size)
111 .ok_or_else(|| XlogError::Kernel(format!("{context} launch grid overflow")))
112}
113
114fn checked_host_level_width(level_offsets: &[u32], level: usize) -> Result<usize> {
115 let start = level_offsets[level];
116 let end = level_offsets[level + 1];
117 if end < start {
118 return Err(XlogError::Compilation(format!(
119 "XGCF invariant violation: level_offsets decrease at level {} ({} > {})",
120 level, start, end
121 )));
122 }
123 Ok((end - start) as usize)
124}
125
126fn validate_xgcf_for_gpu_upload(circuit: &Xgcf) -> Result<(u32, u32, u32)> {
127 let n = circuit.node_type.len();
128 if n == 0 {
129 return Err(XlogError::Compilation(
130 "GPU XGCF upload requires at least one node".to_string(),
131 ));
132 }
133 let node_count = checked_gpu_u32_len("GPU XGCF node count", n)?;
134 let child_offsets_len = checked_gpu_len_add_one("GPU XGCF child_offsets", n)?;
135 if circuit.child_offsets.len() != child_offsets_len {
136 return Err(XlogError::Compilation(format!(
137 "XGCF invariant violation: child_offsets len {} != num_nodes+1 ({})",
138 circuit.child_offsets.len(),
139 child_offsets_len
140 )));
141 }
142 if circuit.lit.len() != n
143 || circuit.decision_var.len() != n
144 || circuit.decision_child_false.len() != n
145 || circuit.decision_child_true.len() != n
146 {
147 return Err(XlogError::Compilation(
148 "XGCF invariant violation: per-node arrays length mismatch".to_string(),
149 ));
150 }
151
152 let edge_count = checked_gpu_u32_len("GPU XGCF edge count", circuit.child_indices.len())?;
153 let mut previous_offset = 0u32;
154 for (idx, &offset) in circuit.child_offsets.iter().enumerate() {
155 if offset < previous_offset {
156 return Err(XlogError::Compilation(format!(
157 "XGCF invariant violation: child_offsets decrease at index {} ({} > {})",
158 idx, previous_offset, offset
159 )));
160 }
161 if offset > edge_count {
162 return Err(XlogError::Compilation(format!(
163 "XGCF invariant violation: child_offsets[{}] {} exceeds child_indices len {}",
164 idx, offset, edge_count
165 )));
166 }
167 previous_offset = offset;
168 }
169 if previous_offset != edge_count {
170 return Err(XlogError::Compilation(format!(
171 "XGCF invariant violation: final child offset {} != child_indices len {}",
172 previous_offset, edge_count
173 )));
174 }
175 for (edge, &child) in circuit.child_indices.iter().enumerate() {
176 if child >= node_count {
177 return Err(XlogError::Compilation(format!(
178 "XGCF invariant violation: child_indices[{}] {} out of bounds (num_nodes={})",
179 edge, child, node_count
180 )));
181 }
182 }
183
184 for (idx, &ty) in circuit.node_type.iter().enumerate() {
185 match ty {
186 XgcfNodeType::Const0 | XgcfNodeType::Const1 => {}
187 XgcfNodeType::Lit => {
188 if circuit.lit[idx] == 0 {
189 return Err(XlogError::Compilation(format!(
190 "XGCF invariant violation: LIT node {} has lit=0",
191 idx
192 )));
193 }
194 }
195 XgcfNodeType::And | XgcfNodeType::Or => {
196 if circuit.child_offsets[idx] == circuit.child_offsets[idx + 1] {
197 return Err(XlogError::Compilation(format!(
198 "XGCF invariant violation: {:?} node {} has no children",
199 ty, idx
200 )));
201 }
202 }
203 XgcfNodeType::Decision => {
204 if circuit.decision_var[idx] == 0 {
205 return Err(XlogError::Compilation(format!(
206 "XGCF invariant violation: DECISION node {} has var=0",
207 idx
208 )));
209 }
210 if circuit.decision_child_false[idx] >= node_count {
211 return Err(XlogError::Compilation(format!(
212 "XGCF invariant violation: DECISION node {} false child {} out of bounds",
213 idx, circuit.decision_child_false[idx]
214 )));
215 }
216 if circuit.decision_child_true[idx] >= node_count {
217 return Err(XlogError::Compilation(format!(
218 "XGCF invariant violation: DECISION node {} true child {} out of bounds",
219 idx, circuit.decision_child_true[idx]
220 )));
221 }
222 }
223 }
224 }
225
226 if circuit.level_offsets.is_empty() || circuit.level_offsets[0] != 0 {
227 return Err(XlogError::Compilation(
228 "XGCF invariant violation: level_offsets must start at 0".to_string(),
229 ));
230 }
231 let level_nodes_len =
232 checked_gpu_u32_len("GPU XGCF level_nodes len", circuit.level_nodes.len())?;
233 let mut previous_level_offset = 0u32;
234 for (idx, &offset) in circuit.level_offsets.iter().enumerate() {
235 if offset < previous_level_offset {
236 return Err(XlogError::Compilation(format!(
237 "XGCF invariant violation: level_offsets decrease at index {} ({} > {})",
238 idx, previous_level_offset, offset
239 )));
240 }
241 if offset > level_nodes_len {
242 return Err(XlogError::Compilation(format!(
243 "XGCF invariant violation: level_offsets[{}] {} exceeds level_nodes len {}",
244 idx, offset, level_nodes_len
245 )));
246 }
247 previous_level_offset = offset;
248 }
249 if previous_level_offset != level_nodes_len {
250 return Err(XlogError::Compilation(format!(
251 "XGCF invariant violation: level_offsets last {} != level_nodes.len {}",
252 previous_level_offset, level_nodes_len
253 )));
254 }
255 for (idx, &node) in circuit.level_nodes.iter().enumerate() {
256 if node >= node_count {
257 return Err(XlogError::Compilation(format!(
258 "XGCF invariant violation: level_nodes[{}] {} out of bounds (num_nodes={})",
259 idx, node, node_count
260 )));
261 }
262 }
263 let num_levels_usize = circuit.level_offsets.len() - 1;
264 let num_levels = checked_gpu_u32_len("GPU XGCF level count", num_levels_usize)?;
265 if num_levels == 0 {
266 return Err(XlogError::Compilation(
267 "GPU XGCF upload requires at least one level".to_string(),
268 ));
269 }
270
271 if circuit.roots.len() != 1 {
272 return Err(XlogError::Compilation(format!(
273 "GPU XGCF eval expects exactly 1 root, got {}",
274 circuit.roots.len()
275 )));
276 }
277 if circuit.roots[0] >= node_count {
278 return Err(XlogError::Compilation(format!(
279 "XGCF invariant violation: root {} out of bounds (num_nodes={})",
280 circuit.roots[0], node_count
281 )));
282 }
283
284 Ok((node_count, edge_count, num_levels))
285}
286
287impl GpuXgcf {
288 pub fn from_device(
290 builder: GpuCircuitBuilder,
291 layout: GpuCircuitLayout,
292 provider: &CudaKernelProvider,
293 ) -> Result<GpuXgcf> {
294 if layout.num_nodes == 0 {
295 return Err(XlogError::Compilation(
296 "GpuXgcf::from_device requires num_nodes > 0".to_string(),
297 ));
298 }
299 if layout.root >= layout.num_nodes {
300 return Err(XlogError::Compilation(format!(
301 "GpuXgcf::from_device: root {} out of bounds (num_nodes={})",
302 layout.root, layout.num_nodes
303 )));
304 }
305 if layout.num_levels == 0 {
306 return Err(XlogError::Compilation(
307 "GpuXgcf::from_device requires num_levels > 0".to_string(),
308 ));
309 }
310
311 let num_nodes = layout.num_nodes as usize;
312 let num_edges = layout.num_edges as usize;
313 let node_cap = builder.node_type.len();
314 if num_nodes == 0 || num_nodes > node_cap {
315 return Err(XlogError::Compilation(
316 "GpuXgcf::from_device: num_nodes out of bounds".to_string(),
317 ));
318 }
319 let child_offsets_len =
320 checked_gpu_len_add_one("GpuXgcf::from_device child_offsets", node_cap)?;
321 if builder.child_offsets.len() != child_offsets_len
322 || builder.lit.len() != node_cap
323 || builder.decision_var.len() != node_cap
324 || builder.decision_child_false.len() != node_cap
325 || builder.decision_child_true.len() != node_cap
326 {
327 return Err(XlogError::Compilation(
328 "GpuXgcf::from_device: circuit buffer length mismatch".to_string(),
329 ));
330 }
331 if num_edges > builder.child_indices.len() {
332 return Err(XlogError::Compilation(
333 "GpuXgcf::from_device: num_edges out of bounds".to_string(),
334 ));
335 }
336
337 let num_levels = layout.num_levels as usize;
338 let level_offsets_len =
339 checked_gpu_len_add_one("GpuXgcf::from_device level_offsets", num_levels)?;
340 if layout.level_offsets.len() != level_offsets_len {
341 return Err(XlogError::Compilation(format!(
342 "GpuXgcf::from_device: level_offsets len {} != num_levels+1 ({})",
343 layout.level_offsets.len(),
344 level_offsets_len
345 )));
346 }
347 if layout.level_nodes.len() < num_nodes {
348 return Err(XlogError::Compilation(format!(
349 "GpuXgcf::from_device: level_nodes len {} < num_nodes ({})",
350 layout.level_nodes.len(),
351 num_nodes
352 )));
353 }
354
355 let memory = provider.memory();
356
357 let weights_len = (layout.max_var as usize) + 1;
358 let var_log_true = memory.alloc::<f64>(weights_len)?;
359 let var_log_false = memory.alloc::<f64>(weights_len)?;
360 let values = memory.alloc::<f64>(num_nodes)?;
361 let adj = memory.alloc::<f64>(num_nodes)?;
362 let grad_true = memory.alloc::<f64>(weights_len)?;
363 let grad_false = memory.alloc::<f64>(weights_len)?;
364
365 let meta_num_nodes = match layout.num_nodes_device {
366 Some(meta) => meta,
367 None => {
368 let mut meta = memory.alloc::<u32>(1)?;
369 provider
370 .htod_launch_metadata_sync_copy_into(&[layout.num_nodes], &mut meta)
371 .map_err(|e| {
372 XlogError::Kernel(format!("Failed to upload num_nodes meta: {}", e))
373 })?;
374 meta
375 }
376 };
377 let meta_num_edges = match layout.num_edges_device {
378 Some(meta) => meta,
379 None => {
380 let mut meta = memory.alloc::<u32>(1)?;
381 provider
382 .htod_launch_metadata_sync_copy_into(&[layout.num_edges], &mut meta)
383 .map_err(|e| {
384 XlogError::Kernel(format!("Failed to upload num_edges meta: {}", e))
385 })?;
386 meta
387 }
388 };
389
390 Ok(Self {
391 node_type: builder.node_type,
392 child_offsets: builder.child_offsets,
393 child_indices: builder.child_indices,
394 lit: builder.lit,
395 decision_var: builder.decision_var,
396 decision_child_false: builder.decision_child_false,
397 decision_child_true: builder.decision_child_true,
398 level_nodes: layout.level_nodes,
399 level_offsets: layout.level_offsets,
400 level_offsets_host: None,
401 node_cap: layout.num_nodes,
402 edge_cap: layout.num_edges,
403 num_levels: layout.num_levels,
404 root: layout.root,
405 max_var: layout.max_var,
406 meta_num_nodes,
407 meta_num_edges,
408 var_log_true,
409 var_log_false,
410 values,
411 adj,
412 grad_true,
413 grad_false,
414 free_var_mask: None,
415 })
416 }
417
418 pub fn smooth_random_vars_device(
423 &self,
424 provider: &CudaKernelProvider,
425 random_var_list: &TrackedCudaSlice<u32>,
426 random_var_count: u32,
427 smooth_node_cap: u32,
428 smooth_edge_cap: u32,
429 ) -> Result<GpuXgcf> {
430 if smooth_node_cap == 0 || smooth_edge_cap == 0 {
431 return Err(XlogError::Compilation(
432 "GPU smoothing requires non-zero node/edge caps".to_string(),
433 ));
434 }
435
436 let num_nodes = self.node_cap;
437 if num_nodes == 0 {
438 return Err(XlogError::Compilation(
439 "GPU smoothing: num_nodes must be > 0".to_string(),
440 ));
441 }
442 if self.child_offsets.len() < (num_nodes as usize + 1) {
443 return Err(XlogError::Compilation(
444 "GPU smoothing: child_offsets len mismatch".to_string(),
445 ));
446 }
447 let num_edges = self.edge_cap;
448 if num_edges == 0 {
449 return Err(XlogError::Compilation(
450 "GPU smoothing: num_edges must be > 0".to_string(),
451 ));
452 }
453
454 let list_len = u32::try_from(random_var_list.len()).map_err(|_| {
455 XlogError::Compilation("GPU smoothing: random var list len exceeds u32".to_string())
456 })?;
457 let num_random_vars = random_var_count;
458 if num_random_vars > list_len {
459 return Err(XlogError::Compilation(format!(
460 "GPU smoothing: random var count {} exceeds list len {}",
461 num_random_vars, list_len
462 )));
463 }
464
465 let base_node = 2u32.checked_add(num_random_vars).ok_or_else(|| {
466 XlogError::Compilation("GPU smoothing: base node overflow".to_string())
467 })?;
468 let base_nodes = (base_node as u64)
469 .checked_add(num_nodes as u64)
470 .ok_or_else(|| {
471 XlogError::Compilation("GPU smoothing: base node overflow".to_string())
472 })?;
473 if base_nodes > smooth_node_cap as u64 {
474 return Err(XlogError::Compilation(format!(
475 "GPU smoothing: base nodes {} exceed smooth_node_cap {}",
476 base_nodes, smooth_node_cap
477 )));
478 }
479
480 let words_per_support = num_random_vars.div_ceil(32).max(1);
481
482 let support_len = (num_nodes as u64)
483 .checked_mul(words_per_support as u64)
484 .and_then(|v| usize::try_from(v).ok())
485 .ok_or_else(|| {
486 XlogError::Compilation("GPU smoothing: support size overflow".to_string())
487 })?;
488
489 let dec_entries = (num_nodes as u64)
490 .checked_mul(2)
491 .and_then(|v| usize::try_from(v).ok())
492 .ok_or_else(|| {
493 XlogError::Compilation("GPU smoothing: decision array overflow".to_string())
494 })?;
495 let dec_entries_u32 = u32::try_from(dec_entries).map_err(|_| {
496 XlogError::Compilation("GPU smoothing: decision entries exceed u32".to_string())
497 })?;
498
499 let device = provider.device().inner();
500 let memory = provider.memory();
501 let block_size: u32 = 256;
502
503 let map_len = (self.max_var as usize)
504 .checked_add(1)
505 .ok_or_else(|| XlogError::Compilation("GPU smoothing: max_var overflow".to_string()))?;
506 let map_len_u32 = u32::try_from(map_len).map_err(|_| {
507 XlogError::Compilation("GPU smoothing: random map len exceeds u32".to_string())
508 })?;
509 let mut d_random_map = memory.alloc::<u32>(map_len)?;
510 if map_len > 0 {
511 let fill_const = device
512 .get_func(FILTER_MODULE, filter_kernels::FILL_U32_CONST)
513 .ok_or_else(|| XlogError::Kernel("fill_u32_const kernel not found".to_string()))?;
514 let grid = map_len_u32.div_ceil(block_size);
515 unsafe {
517 fill_const.clone().launch(
518 LaunchConfig {
519 grid_dim: (grid, 1, 1),
520 block_dim: (block_size, 1, 1),
521 shared_mem_bytes: 0,
522 },
523 (&mut d_random_map, map_len_u32, u32::MAX),
524 )
525 }
526 .map_err(|e| XlogError::Kernel(format!("fill_u32_const failed: {}", e)))?;
527 }
528 if num_random_vars > 0 {
529 let map_kernel = device
530 .get_func(FILTER_MODULE, filter_kernels::RANDOM_VAR_TO_BIT_FROM_LIST)
531 .ok_or_else(|| {
532 XlogError::Kernel("random_var_to_bit_from_list kernel not found".to_string())
533 })?;
534 let grid = num_random_vars.div_ceil(block_size);
535 unsafe {
537 map_kernel.clone().launch(
538 LaunchConfig {
539 grid_dim: (grid, 1, 1),
540 block_dim: (block_size, 1, 1),
541 shared_mem_bytes: 0,
542 },
543 (
544 random_var_list,
545 num_random_vars,
546 map_len_u32,
547 &mut d_random_map,
548 ),
549 )
550 }
551 .map_err(|e| XlogError::Kernel(format!("random_var_to_bit_from_list failed: {}", e)))?;
552 }
553
554 let mut support = memory.alloc::<u32>(support_len)?;
555 device
556 .memset_zeros(&mut support)
557 .map_err(|e| XlogError::Kernel(format!("Failed to zero support: {}", e)))?;
558
559 let support_kernel = device
560 .get_func(D4_MODULE, d4_kernels::D4_SUPPORT_LEVEL)
561 .ok_or_else(|| XlogError::Kernel("d4_support_level kernel not found".to_string()))?;
562
563 let num_levels = self.num_levels as usize;
564 let random_map_len = map_len_u32;
565 for level in 0..num_levels {
566 let num_level_nodes: usize = match self.level_offsets_host.as_ref() {
567 Some(off) => checked_host_level_width(off, level)?,
568 None => self.level_nodes.len(),
569 };
570 if num_level_nodes == 0 {
571 continue;
572 }
573 let num_blocks =
574 checked_gpu_launch_blocks("d4_support_level", num_level_nodes, block_size)?;
575 let config = LaunchConfig {
576 grid_dim: (num_blocks, 1, 1),
577 block_dim: (block_size, 1, 1),
578 shared_mem_bytes: 0,
579 };
580 let level_u32 = level as u32;
581 let mut params: Vec<*mut c_void> = vec![
582 (&self.node_type).as_kernel_param(),
583 (&self.child_offsets).as_kernel_param(),
584 (&self.child_indices).as_kernel_param(),
585 (&self.lit).as_kernel_param(),
586 (&self.decision_var).as_kernel_param(),
587 (&self.decision_child_false).as_kernel_param(),
588 (&self.decision_child_true).as_kernel_param(),
589 (&self.level_nodes).as_kernel_param(),
590 (&self.level_offsets).as_kernel_param(),
591 level_u32.as_kernel_param(),
592 (&d_random_map).as_kernel_param(),
593 random_map_len.as_kernel_param(),
594 words_per_support.as_kernel_param(),
595 (&support).as_kernel_param(),
596 ];
597 unsafe { support_kernel.clone().launch(config, &mut params) }
599 .map_err(|e| XlogError::Kernel(format!("d4_support_level failed: {}", e)))?;
600 }
601
602 if num_random_vars > 0 {
603 let root_kernel = device
604 .get_func(D4_MODULE, d4_kernels::D4_SUPPORT_SET_ROOT_BITS)
605 .ok_or_else(|| {
606 XlogError::Kernel("d4_support_set_root_bits kernel not found".to_string())
607 })?;
608 let num_words = num_random_vars.div_ceil(32);
609 let grid = num_words.div_ceil(block_size);
610 unsafe {
612 root_kernel.clone().launch(
613 LaunchConfig {
614 grid_dim: (grid, 1, 1),
615 block_dim: (block_size, 1, 1),
616 shared_mem_bytes: 0,
617 },
618 (self.root, num_random_vars, words_per_support, &mut support),
619 )
620 }
621 .map_err(|e| XlogError::Kernel(format!("d4_support_set_root_bits failed: {}", e)))?;
622 }
623
624 let mut wrap_prefix_or = memory.alloc::<u32>(num_edges as usize)?;
625 let mut wrap_missing_or = memory.alloc::<u32>(num_edges as usize)?;
626 let mut wrap_prefix_dec = memory.alloc::<u32>(dec_entries)?;
627 let mut wrap_missing_dec = memory.alloc::<u32>(dec_entries)?;
628
629 device
630 .memset_zeros(&mut wrap_prefix_or)
631 .map_err(|e| XlogError::Kernel(format!("Failed to zero wrap_prefix_or: {}", e)))?;
632 device
633 .memset_zeros(&mut wrap_missing_or)
634 .map_err(|e| XlogError::Kernel(format!("Failed to zero wrap_missing_or: {}", e)))?;
635 device
636 .memset_zeros(&mut wrap_prefix_dec)
637 .map_err(|e| XlogError::Kernel(format!("Failed to zero wrap_prefix_dec: {}", e)))?;
638 device
639 .memset_zeros(&mut wrap_missing_dec)
640 .map_err(|e| XlogError::Kernel(format!("Failed to zero wrap_missing_dec: {}", e)))?;
641
642 let mut out_edge_counts = memory.alloc::<u32>(smooth_node_cap as usize)?;
643 device
644 .memset_zeros(&mut out_edge_counts)
645 .map_err(|e| XlogError::Kernel(format!("Failed to zero edge_counts: {}", e)))?;
646
647 let count_kernel = device
648 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_COUNT)
649 .ok_or_else(|| XlogError::Kernel("d4_smooth_count kernel not found".to_string()))?;
650 let num_blocks = num_nodes.div_ceil(block_size);
651 let mut params: Vec<*mut c_void> = vec![
652 (&self.node_type).as_kernel_param(),
653 (&self.child_offsets).as_kernel_param(),
654 (&self.child_indices).as_kernel_param(),
655 (&self.decision_var).as_kernel_param(),
656 (&self.decision_child_false).as_kernel_param(),
657 (&self.decision_child_true).as_kernel_param(),
658 (&self.meta_num_nodes).as_kernel_param(),
659 (&support).as_kernel_param(),
660 words_per_support.as_kernel_param(),
661 (&d_random_map).as_kernel_param(),
662 random_map_len.as_kernel_param(),
663 (&wrap_prefix_or).as_kernel_param(),
664 (&wrap_missing_or).as_kernel_param(),
665 (&wrap_prefix_dec).as_kernel_param(),
666 (&wrap_missing_dec).as_kernel_param(),
667 (&out_edge_counts).as_kernel_param(),
668 base_node.as_kernel_param(),
669 smooth_node_cap.as_kernel_param(),
670 ];
671 unsafe {
673 count_kernel.clone().launch(
674 LaunchConfig {
675 grid_dim: (num_blocks, 1, 1),
676 block_dim: (block_size, 1, 1),
677 shared_mem_bytes: 0,
678 },
679 &mut params,
680 )
681 }
682 .map_err(|e| XlogError::Kernel(format!("d4_smooth_count failed: {}", e)))?;
683
684 exclusive_scan_u32_inplace(provider, &mut wrap_prefix_or, num_edges)?;
685 exclusive_scan_u32_inplace(provider, &mut wrap_prefix_dec, dec_entries_u32)?;
686
687 let mut wrap_counts = memory.alloc::<u32>(3)?;
688 device
689 .memset_zeros(&mut wrap_counts)
690 .map_err(|e| XlogError::Kernel(format!("Failed to zero wrap_counts: {}", e)))?;
691
692 let counts_kernel = device
693 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_WRAPPER_COUNTS)
694 .ok_or_else(|| {
695 XlogError::Kernel("d4_smooth_wrapper_counts kernel not found".to_string())
696 })?;
697 unsafe {
699 counts_kernel.clone().launch(
700 LaunchConfig {
701 grid_dim: (1, 1, 1),
702 block_dim: (1, 1, 1),
703 shared_mem_bytes: 0,
704 },
705 (
706 &wrap_prefix_or,
707 &wrap_missing_or,
708 num_edges,
709 &wrap_prefix_dec,
710 &wrap_missing_dec,
711 dec_entries_u32,
712 base_node,
713 &self.meta_num_nodes,
714 u32::MAX,
715 &mut wrap_counts,
716 ),
717 )
718 }
719 .map_err(|e| XlogError::Kernel(format!("d4_smooth_wrapper_counts failed: {}", e)))?;
720
721 let wrap_or_kernel = device
722 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_WRAPPER_EDGE_COUNTS_OR)
723 .ok_or_else(|| {
724 XlogError::Kernel("d4_smooth_wrapper_edge_counts_or kernel not found".to_string())
725 })?;
726 if num_edges > 0 {
727 let num_blocks = num_edges.div_ceil(block_size);
728 unsafe {
730 wrap_or_kernel.clone().launch(
731 LaunchConfig {
732 grid_dim: (num_blocks, 1, 1),
733 block_dim: (block_size, 1, 1),
734 shared_mem_bytes: 0,
735 },
736 (
737 &wrap_prefix_or,
738 &wrap_missing_or,
739 num_edges,
740 base_node,
741 &self.meta_num_nodes,
742 smooth_node_cap,
743 &mut out_edge_counts,
744 ),
745 )
746 }
747 .map_err(|e| {
748 XlogError::Kernel(format!("d4_smooth_wrapper_edge_counts_or failed: {}", e))
749 })?;
750 }
751
752 let wrap_dec_kernel = device
753 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_WRAPPER_EDGE_COUNTS_DEC)
754 .ok_or_else(|| {
755 XlogError::Kernel("d4_smooth_wrapper_edge_counts_dec kernel not found".to_string())
756 })?;
757 if dec_entries > 0 {
758 let num_blocks = dec_entries_u32.div_ceil(block_size);
759 unsafe {
761 wrap_dec_kernel.clone().launch(
762 LaunchConfig {
763 grid_dim: (num_blocks, 1, 1),
764 block_dim: (block_size, 1, 1),
765 shared_mem_bytes: 0,
766 },
767 (
768 &wrap_prefix_dec,
769 &wrap_missing_dec,
770 dec_entries_u32,
771 base_node,
772 &self.meta_num_nodes,
773 &wrap_counts,
774 smooth_node_cap,
775 &mut out_edge_counts,
776 ),
777 )
778 }
779 .map_err(|e| {
780 XlogError::Kernel(format!("d4_smooth_wrapper_edge_counts_dec failed: {}", e))
781 })?;
782 }
783
784 let mut out_child_offsets = memory.alloc::<u32>((smooth_node_cap as usize) + 1)?;
785 device
786 .memset_zeros(&mut out_child_offsets)
787 .map_err(|e| XlogError::Kernel(format!("Failed to zero child_offsets: {}", e)))?;
788 if smooth_node_cap > 0 {
789 device
790 .dtod_copy(
791 &out_edge_counts,
792 &mut out_child_offsets.slice_mut(0..smooth_node_cap as usize),
793 )
794 .map_err(|e| XlogError::Kernel(format!("Failed to copy edge_counts: {}", e)))?;
795 }
796 let child_scan_len = smooth_node_cap.checked_add(1).ok_or_else(|| {
797 XlogError::Compilation("GPU smoothing: child offset scan overflow".to_string())
798 })?;
799 exclusive_scan_u32_inplace(provider, &mut out_child_offsets, child_scan_len)?;
800
801 let edge_cap_check = device
802 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_CHECK_EDGE_CAP)
803 .ok_or_else(|| {
804 XlogError::Kernel("d4_smooth_check_edge_cap kernel not found".to_string())
805 })?;
806 let mut meta_num_nodes = memory.alloc::<u32>(1)?;
807 let mut meta_num_edges = memory.alloc::<u32>(1)?;
808 device
809 .memset_zeros(&mut meta_num_nodes)
810 .map_err(|e| XlogError::Kernel(format!("Failed to zero smooth num_nodes: {}", e)))?;
811 device
812 .memset_zeros(&mut meta_num_edges)
813 .map_err(|e| XlogError::Kernel(format!("Failed to zero smooth num_edges: {}", e)))?;
814 unsafe {
816 edge_cap_check.clone().launch(
817 LaunchConfig {
818 grid_dim: (1, 1, 1),
819 block_dim: (1, 1, 1),
820 shared_mem_bytes: 0,
821 },
822 (
823 &out_child_offsets,
824 smooth_node_cap,
825 smooth_edge_cap,
826 &wrap_counts,
827 &mut meta_num_nodes,
828 &mut meta_num_edges,
829 ),
830 )
831 }
832 .map_err(|e| XlogError::Kernel(format!("d4_smooth_check_edge_cap failed: {}", e)))?;
833
834 let mut out_node_type = memory.alloc::<u8>(smooth_node_cap as usize)?;
835 let mut out_child_indices = memory.alloc::<u32>(smooth_edge_cap as usize)?;
836 let mut out_lit = memory.alloc::<i32>(smooth_node_cap as usize)?;
837 let mut out_decision_var = memory.alloc::<u32>(smooth_node_cap as usize)?;
838 let mut out_decision_child_false = memory.alloc::<u32>(smooth_node_cap as usize)?;
839 let mut out_decision_child_true = memory.alloc::<u32>(smooth_node_cap as usize)?;
840 let mut out_node_level = memory.alloc::<u32>(smooth_node_cap as usize)?;
841
842 device
843 .memset_zeros(&mut out_node_type)
844 .map_err(|e| XlogError::Kernel(format!("Failed to zero node_type: {}", e)))?;
845 device
846 .memset_zeros(&mut out_child_indices)
847 .map_err(|e| XlogError::Kernel(format!("Failed to zero child_indices: {}", e)))?;
848 device
849 .memset_zeros(&mut out_lit)
850 .map_err(|e| XlogError::Kernel(format!("Failed to zero lit: {}", e)))?;
851 device
852 .memset_zeros(&mut out_decision_var)
853 .map_err(|e| XlogError::Kernel(format!("Failed to zero decision_var: {}", e)))?;
854 device
855 .memset_zeros(&mut out_decision_child_false)
856 .map_err(|e| {
857 XlogError::Kernel(format!("Failed to zero decision_child_false: {}", e))
858 })?;
859 device
860 .memset_zeros(&mut out_decision_child_true)
861 .map_err(|e| XlogError::Kernel(format!("Failed to zero decision_child_true: {}", e)))?;
862 device
863 .memset_zeros(&mut out_node_level)
864 .map_err(|e| XlogError::Kernel(format!("Failed to zero node_level: {}", e)))?;
865
866 let init_kernel = device
867 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_INIT_NODES)
868 .ok_or_else(|| {
869 XlogError::Kernel("d4_smooth_init_nodes kernel not found".to_string())
870 })?;
871 let init_blocks = checked_gpu_launch_blocks(
872 "d4_smooth_init_nodes",
873 num_random_vars.max(1) as usize,
874 block_size,
875 )?;
876 unsafe {
878 init_kernel.clone().launch(
879 LaunchConfig {
880 grid_dim: (init_blocks, 1, 1),
881 block_dim: (block_size, 1, 1),
882 shared_mem_bytes: 0,
883 },
884 (
885 random_var_list,
886 num_random_vars,
887 smooth_node_cap,
888 &mut out_node_type,
889 &mut out_lit,
890 &mut out_decision_var,
891 &mut out_decision_child_false,
892 &mut out_decision_child_true,
893 &mut out_node_level,
894 ),
895 )
896 }
897 .map_err(|e| XlogError::Kernel(format!("d4_smooth_init_nodes failed: {}", e)))?;
898
899 let num_levels_out = self
900 .num_levels
901 .checked_mul(2)
902 .and_then(|levels| levels.checked_add(4))
903 .ok_or_else(|| {
904 XlogError::Compilation("GPU smoothing output level count overflow".to_string())
905 })?;
906 let num_levels_out_usize = num_levels_out as usize;
907 let level_offsets_len =
908 checked_gpu_len_add_one("GPU smoothing level offsets", num_levels_out_usize)?;
909
910 let emit_kernel = device
911 .get_func(D4_MODULE, d4_kernels::D4_SMOOTH_EMIT_LEVEL)
912 .ok_or_else(|| {
913 XlogError::Kernel("d4_smooth_emit_level kernel not found".to_string())
914 })?;
915 for level in 0..num_levels {
916 let num_level_nodes: usize = match self.level_offsets_host.as_ref() {
917 Some(off) => checked_host_level_width(off, level)?,
918 None => self.level_nodes.len(),
919 };
920 if num_level_nodes == 0 {
921 continue;
922 }
923 let num_blocks =
924 checked_gpu_launch_blocks("xgcf_smooth_forward", num_level_nodes, block_size)?;
925 let level_u32 = level as u32;
926 let mut params: Vec<*mut c_void> = vec![
927 (&self.node_type).as_kernel_param(),
928 (&self.child_offsets).as_kernel_param(),
929 (&self.child_indices).as_kernel_param(),
930 (&self.lit).as_kernel_param(),
931 (&self.decision_var).as_kernel_param(),
932 (&self.decision_child_false).as_kernel_param(),
933 (&self.decision_child_true).as_kernel_param(),
934 (&self.level_nodes).as_kernel_param(),
935 (&self.level_offsets).as_kernel_param(),
936 level_u32.as_kernel_param(),
937 (&support).as_kernel_param(),
938 words_per_support.as_kernel_param(),
939 (&wrap_prefix_or).as_kernel_param(),
940 (&wrap_missing_or).as_kernel_param(),
941 (&wrap_prefix_dec).as_kernel_param(),
942 (&wrap_missing_dec).as_kernel_param(),
943 base_node.as_kernel_param(),
944 (&self.meta_num_nodes).as_kernel_param(),
945 (&wrap_counts).as_kernel_param(),
946 num_random_vars.as_kernel_param(),
947 num_levels_out.as_kernel_param(),
948 (&out_node_type).as_kernel_param(),
949 (&out_child_offsets).as_kernel_param(),
950 (&out_child_indices).as_kernel_param(),
951 (&out_lit).as_kernel_param(),
952 (&out_decision_var).as_kernel_param(),
953 (&out_decision_child_false).as_kernel_param(),
954 (&out_decision_child_true).as_kernel_param(),
955 (&out_node_level).as_kernel_param(),
956 ];
957 unsafe {
959 emit_kernel.clone().launch(
960 LaunchConfig {
961 grid_dim: (num_blocks, 1, 1),
962 block_dim: (block_size, 1, 1),
963 shared_mem_bytes: 0,
964 },
965 &mut params,
966 )
967 }
968 .map_err(|e| XlogError::Kernel(format!("d4_smooth_emit_level failed: {}", e)))?;
969 }
970
971 let mut level_counts = memory.alloc::<u32>(num_levels_out_usize)?;
972 let mut level_offsets = memory.alloc::<u32>(level_offsets_len)?;
973 let mut level_cursors = memory.alloc::<u32>(num_levels_out_usize)?;
974 let mut level_nodes = memory.alloc::<u32>(smooth_node_cap as usize)?;
975
976 device
977 .memset_zeros(&mut level_counts)
978 .map_err(|e| XlogError::Kernel(format!("Failed to zero level_counts: {}", e)))?;
979 device
980 .memset_zeros(&mut level_offsets)
981 .map_err(|e| XlogError::Kernel(format!("Failed to zero level_offsets: {}", e)))?;
982 device
983 .memset_zeros(&mut level_cursors)
984 .map_err(|e| XlogError::Kernel(format!("Failed to zero level_cursors: {}", e)))?;
985 device
986 .memset_zeros(&mut level_nodes)
987 .map_err(|e| XlogError::Kernel(format!("Failed to zero level_nodes: {}", e)))?;
988
989 let mut compile_needed = memory.alloc::<u32>(1)?;
990 provider
991 .htod_launch_metadata_sync_copy_into(&[1u32], &mut compile_needed)
992 .map_err(|e| XlogError::Kernel(format!("Failed to upload compile_needed: {}", e)))?;
993
994 let levelize_counts = device
995 .get_func(D4_MODULE, d4_kernels::D4_LEVELIZE_COUNTS)
996 .ok_or_else(|| XlogError::Kernel("d4_levelize_counts kernel not found".to_string()))?;
997 let num_blocks =
998 checked_gpu_launch_blocks("d4_smooth_levelize", smooth_node_cap as usize, block_size)?;
999 unsafe {
1001 levelize_counts.clone().launch(
1002 LaunchConfig {
1003 grid_dim: (num_blocks, 1, 1),
1004 block_dim: (block_size, 1, 1),
1005 shared_mem_bytes: 0,
1006 },
1007 (
1008 &compile_needed,
1009 &out_node_level,
1010 &meta_num_nodes,
1011 num_levels_out,
1012 &mut level_counts,
1013 ),
1014 )
1015 }
1016 .map_err(|e| XlogError::Kernel(format!("d4_levelize_counts failed: {}", e)))?;
1017
1018 device
1019 .dtod_copy(
1020 &level_counts,
1021 &mut level_offsets.slice_mut(0..num_levels_out_usize),
1022 )
1023 .map_err(|e| XlogError::Kernel(format!("Failed to copy level_counts: {}", e)))?;
1024 let level_scan_len = num_levels_out.checked_add(1).ok_or_else(|| {
1025 XlogError::Compilation("GPU smoothing: level offset scan overflow".to_string())
1026 })?;
1027 exclusive_scan_u32_inplace(provider, &mut level_offsets, level_scan_len)?;
1028
1029 let levelize_emit = device
1030 .get_func(D4_MODULE, d4_kernels::D4_LEVELIZE_EMIT)
1031 .ok_or_else(|| XlogError::Kernel("d4_levelize_emit kernel not found".to_string()))?;
1032 unsafe {
1034 levelize_emit.clone().launch(
1035 LaunchConfig {
1036 grid_dim: (num_blocks, 1, 1),
1037 block_dim: (block_size, 1, 1),
1038 shared_mem_bytes: 0,
1039 },
1040 (
1041 &compile_needed,
1042 &out_node_level,
1043 &meta_num_nodes,
1044 num_levels_out,
1045 &level_offsets,
1046 &mut level_cursors,
1047 &mut level_nodes,
1048 ),
1049 )
1050 }
1051 .map_err(|e| XlogError::Kernel(format!("d4_levelize_emit failed: {}", e)))?;
1052
1053 let builder = GpuCircuitBuilder {
1055 node_type: out_node_type,
1056 child_offsets: out_child_offsets,
1057 child_indices: out_child_indices,
1058 lit: out_lit,
1059 decision_var: out_decision_var,
1060 decision_child_false: out_decision_child_false,
1061 decision_child_true: out_decision_child_true,
1062 };
1063 let layout = GpuCircuitLayout {
1064 num_nodes: smooth_node_cap,
1065 num_edges: smooth_edge_cap,
1066 num_levels: num_levels_out,
1067 level_offsets,
1068 level_nodes,
1069 root: base_node + self.root,
1070 max_var: self.max_var,
1071 num_nodes_device: Some(meta_num_nodes),
1072 num_edges_device: Some(meta_num_edges),
1073 };
1074
1075 let circuit = GpuXgcf::from_device(builder, layout, provider)?;
1076 provider.device().synchronize().map_err(|error| {
1081 XlogError::Kernel(format!("GPU circuit smoothing completion failed: {error}"))
1082 })?;
1083 Ok(circuit)
1084 }
1085
1086 pub fn upload(provider: &CudaKernelProvider, circuit: &Xgcf) -> Result<Self> {
1088 let (node_cap, edge_cap, num_levels) = validate_xgcf_for_gpu_upload(circuit)?;
1089
1090 let memory = provider.memory().clone();
1091
1092 let n = circuit.node_type.len();
1093 let mut host_node_type: Vec<u8> = Vec::with_capacity(n);
1094 for &ty in &circuit.node_type {
1095 host_node_type.push(ty as u8);
1096 }
1097
1098 let mut max_var: u32 = 0;
1099 for (&ty, &lit) in circuit.node_type.iter().zip(circuit.lit.iter()) {
1100 if ty == XgcfNodeType::Lit && lit != 0 {
1101 max_var = max_var.max(lit.unsigned_abs());
1102 }
1103 }
1104 for &var in &circuit.decision_var {
1105 max_var = max_var.max(var);
1106 }
1107
1108 let mut d_node_type = memory.alloc::<u8>(n)?;
1109 provider
1110 .htod_sync_copy_into_tracked(&host_node_type, &mut d_node_type)
1111 .map_err(|e| XlogError::Kernel(format!("Failed to upload circuit node_type: {}", e)))?;
1112
1113 let mut d_child_offsets = memory.alloc::<u32>(circuit.child_offsets.len())?;
1114 provider
1115 .htod_sync_copy_into_tracked(&circuit.child_offsets, &mut d_child_offsets)
1116 .map_err(|e| {
1117 XlogError::Kernel(format!("Failed to upload circuit child_offsets: {}", e))
1118 })?;
1119
1120 let mut d_child_indices = memory.alloc::<u32>(circuit.child_indices.len())?;
1121 provider
1122 .htod_sync_copy_into_tracked(&circuit.child_indices, &mut d_child_indices)
1123 .map_err(|e| {
1124 XlogError::Kernel(format!("Failed to upload circuit child_indices: {}", e))
1125 })?;
1126
1127 let mut d_lit = memory.alloc::<i32>(circuit.lit.len())?;
1128 provider
1129 .htod_sync_copy_into_tracked(&circuit.lit, &mut d_lit)
1130 .map_err(|e| XlogError::Kernel(format!("Failed to upload circuit lit: {}", e)))?;
1131
1132 let mut d_decision_var = memory.alloc::<u32>(circuit.decision_var.len())?;
1133 provider
1134 .htod_sync_copy_into_tracked(&circuit.decision_var, &mut d_decision_var)
1135 .map_err(|e| {
1136 XlogError::Kernel(format!("Failed to upload circuit decision_var: {}", e))
1137 })?;
1138
1139 let mut d_decision_child_false = memory.alloc::<u32>(circuit.decision_child_false.len())?;
1140 provider
1141 .htod_sync_copy_into_tracked(&circuit.decision_child_false, &mut d_decision_child_false)
1142 .map_err(|e| {
1143 XlogError::Kernel(format!(
1144 "Failed to upload circuit decision_child_false: {}",
1145 e
1146 ))
1147 })?;
1148
1149 let mut d_decision_child_true = memory.alloc::<u32>(circuit.decision_child_true.len())?;
1150 provider
1151 .htod_sync_copy_into_tracked(&circuit.decision_child_true, &mut d_decision_child_true)
1152 .map_err(|e| {
1153 XlogError::Kernel(format!(
1154 "Failed to upload circuit decision_child_true: {}",
1155 e
1156 ))
1157 })?;
1158
1159 let mut d_level_nodes = memory.alloc::<u32>(circuit.level_nodes.len())?;
1160 provider
1161 .htod_sync_copy_into_tracked(&circuit.level_nodes, &mut d_level_nodes)
1162 .map_err(|e| {
1163 XlogError::Kernel(format!("Failed to upload circuit level_nodes: {}", e))
1164 })?;
1165
1166 let mut d_level_offsets = memory.alloc::<u32>(circuit.level_offsets.len())?;
1167 provider
1168 .htod_sync_copy_into_tracked(&circuit.level_offsets, &mut d_level_offsets)
1169 .map_err(|e| {
1170 XlogError::Kernel(format!("Failed to upload circuit level_offsets: {}", e))
1171 })?;
1172
1173 let weights_len = (max_var as usize) + 1;
1174 let var_log_true = memory.alloc::<f64>(weights_len)?;
1175 let var_log_false = memory.alloc::<f64>(weights_len)?;
1176 let values = memory.alloc::<f64>(n)?;
1177 let adj = memory.alloc::<f64>(n)?;
1178 let grad_true = memory.alloc::<f64>(weights_len)?;
1179 let grad_false = memory.alloc::<f64>(weights_len)?;
1180 let mut meta_num_nodes = memory.alloc::<u32>(1)?;
1181 provider
1182 .htod_launch_metadata_sync_copy_into(&[node_cap], &mut meta_num_nodes)
1183 .map_err(|e| XlogError::Kernel(format!("Failed to upload num_nodes meta: {}", e)))?;
1184 let mut meta_num_edges = memory.alloc::<u32>(1)?;
1185 provider
1186 .htod_launch_metadata_sync_copy_into(&[edge_cap], &mut meta_num_edges)
1187 .map_err(|e| XlogError::Kernel(format!("Failed to upload num_edges meta: {}", e)))?;
1188
1189 Ok(Self {
1190 node_type: d_node_type,
1191 child_offsets: d_child_offsets,
1192 child_indices: d_child_indices,
1193 lit: d_lit,
1194 decision_var: d_decision_var,
1195 decision_child_false: d_decision_child_false,
1196 decision_child_true: d_decision_child_true,
1197 level_nodes: d_level_nodes,
1198 level_offsets: d_level_offsets,
1199 level_offsets_host: Some(circuit.level_offsets.clone()),
1200 node_cap,
1201 edge_cap,
1202 num_levels,
1203 root: circuit.roots[0],
1204 max_var,
1205 meta_num_nodes,
1206 meta_num_edges,
1207 var_log_true,
1208 var_log_false,
1209 values,
1210 adj,
1211 grad_true,
1212 grad_false,
1213 free_var_mask: None,
1214 })
1215 }
1216
1217 pub fn max_var(&self) -> u32 {
1219 self.max_var
1220 }
1221
1222 pub fn root(&self) -> u32 {
1224 self.root
1225 }
1226
1227 pub fn num_nodes(&self) -> usize {
1229 self.node_cap as usize
1230 }
1231
1232 pub fn num_edges(&self) -> usize {
1234 self.edge_cap as usize
1235 }
1236
1237 pub fn num_levels(&self) -> u32 {
1239 self.num_levels
1240 }
1241
1242 pub fn num_nodes_device(&self) -> &TrackedCudaSlice<u32> {
1244 &self.meta_num_nodes
1245 }
1246
1247 pub fn num_edges_device(&self) -> &TrackedCudaSlice<u32> {
1249 &self.meta_num_edges
1250 }
1251
1252 pub fn level_nodes(&self) -> &TrackedCudaSlice<u32> {
1254 &self.level_nodes
1255 }
1256
1257 pub fn level_offsets(&self) -> &TrackedCudaSlice<u32> {
1259 &self.level_offsets
1260 }
1261
1262 pub fn node_type(&self) -> &TrackedCudaSlice<u8> {
1264 &self.node_type
1265 }
1266
1267 pub fn child_offsets(&self) -> &TrackedCudaSlice<u32> {
1269 &self.child_offsets
1270 }
1271
1272 pub fn child_indices(&self) -> &TrackedCudaSlice<u32> {
1274 &self.child_indices
1275 }
1276
1277 pub fn lit(&self) -> &TrackedCudaSlice<i32> {
1279 &self.lit
1280 }
1281
1282 pub fn decision_var(&self) -> &TrackedCudaSlice<u32> {
1284 &self.decision_var
1285 }
1286
1287 pub fn decision_child_false(&self) -> &TrackedCudaSlice<u32> {
1289 &self.decision_child_false
1290 }
1291
1292 pub fn decision_child_true(&self) -> &TrackedCudaSlice<u32> {
1294 &self.decision_child_true
1295 }
1296
1297 pub fn values(&self) -> &TrackedCudaSlice<f64> {
1299 &self.values
1300 }
1301
1302 pub fn grad_true(&self) -> &TrackedCudaSlice<f64> {
1304 &self.grad_true
1305 }
1306
1307 pub fn grad_false(&self) -> &TrackedCudaSlice<f64> {
1309 &self.grad_false
1310 }
1311
1312 pub fn var_log_true(&self) -> &TrackedCudaSlice<f64> {
1314 &self.var_log_true
1315 }
1316
1317 pub fn var_log_false(&self) -> &TrackedCudaSlice<f64> {
1319 &self.var_log_false
1320 }
1321
1322 pub fn var_log_weights_mut(
1327 &mut self,
1328 ) -> (&mut TrackedCudaSlice<f64>, &mut TrackedCudaSlice<f64>) {
1329 (&mut self.var_log_true, &mut self.var_log_false)
1330 }
1331
1332 pub fn set_free_var_mask_device(&mut self, mask: TrackedCudaSlice<u8>) -> Result<()> {
1334 if mask.len() != self.var_log_true.len() {
1335 return Err(XlogError::Compilation(format!(
1336 "GPU free-var mask len {} != weights len {}",
1337 mask.len(),
1338 self.var_log_true.len()
1339 )));
1340 }
1341 self.free_var_mask = Some(mask);
1342 Ok(())
1343 }
1344
1345 pub fn set_base_weights(
1353 &mut self,
1354 provider: &CudaKernelProvider,
1355 var_log_weights: &[(f64, f64)],
1356 ) -> Result<()> {
1357 let weights_len = (self.max_var as usize) + 1;
1358 if var_log_weights.len() < weights_len {
1359 return Err(XlogError::Compilation(format!(
1360 "GPU XGCF weights init expects weight table len >= {}, got {}",
1361 weights_len,
1362 var_log_weights.len()
1363 )));
1364 }
1365 for &weights in &var_log_weights[1..weights_len] {
1366 validate_circuit_log_weight_pair(weights)?;
1367 }
1368
1369 let mut host_true: Vec<f64> = Vec::with_capacity(weights_len);
1370 let mut host_false: Vec<f64> = Vec::with_capacity(weights_len);
1371 for &(t, f) in &var_log_weights[..weights_len] {
1372 host_true.push(t);
1373 host_false.push(f);
1374 }
1375
1376 provider
1377 .htod_sync_copy_into_tracked(&host_true, &mut self.var_log_true)
1378 .map_err(|e| XlogError::Kernel(format!("Failed to upload log_true weights: {}", e)))?;
1379 provider
1380 .htod_sync_copy_into_tracked(&host_false, &mut self.var_log_false)
1381 .map_err(|e| XlogError::Kernel(format!("Failed to upload log_false weights: {}", e)))?;
1382
1383 Ok(())
1384 }
1385
1386 pub fn eval_log_wmc_device_inplace(
1394 &mut self,
1395 provider: &CudaKernelProvider,
1396 out_log_z: &mut TrackedCudaSlice<f64>,
1397 ) -> Result<()> {
1398 if out_log_z.len() != 1 {
1399 return Err(XlogError::Compilation(format!(
1400 "GPU device logZ output len {} != 1",
1401 out_log_z.len()
1402 )));
1403 }
1404
1405 let device = provider.device().inner();
1406 let func = device
1407 .get_func(CIRCUIT_MODULE, circuit_kernels::XGCF_FORWARD_LEVEL)
1408 .ok_or_else(|| XlogError::Kernel("xgcf_forward_level kernel not found".to_string()))?;
1409
1410 let block_size: u32 = 256;
1411 let num_levels: usize = self.num_levels as usize;
1412 for level in 0..num_levels {
1413 let num_level_nodes: usize = match self.level_offsets_host.as_ref() {
1414 Some(off) => checked_host_level_width(off, level)?,
1415 None => self.level_nodes.len(),
1416 };
1417 if num_level_nodes == 0 {
1418 continue;
1419 }
1420
1421 let num_blocks =
1422 checked_gpu_launch_blocks("xgcf_forward_level", num_level_nodes, block_size)?;
1423 let config = LaunchConfig {
1424 grid_dim: (num_blocks, 1, 1),
1425 block_dim: (block_size, 1, 1),
1426 shared_mem_bytes: 0,
1427 };
1428 let level_u32: u32 = level as u32;
1429
1430 let mut params: Vec<*mut c_void> = vec![
1431 (&self.node_type).as_kernel_param(),
1432 (&self.child_offsets).as_kernel_param(),
1433 (&self.child_indices).as_kernel_param(),
1434 (&self.lit).as_kernel_param(),
1435 (&self.decision_var).as_kernel_param(),
1436 (&self.decision_child_false).as_kernel_param(),
1437 (&self.decision_child_true).as_kernel_param(),
1438 (&self.level_nodes).as_kernel_param(),
1439 (&self.level_offsets).as_kernel_param(),
1440 level_u32.as_kernel_param(),
1441 (&self.var_log_true).as_kernel_param(),
1442 (&self.var_log_false).as_kernel_param(),
1443 (&self.values).as_kernel_param(),
1444 ];
1445
1446 unsafe { func.clone().launch(config, &mut params) }
1448 .map_err(|e| XlogError::Kernel(format!("xgcf_forward_level failed: {}", e)))?;
1449 }
1450
1451 self.apply_free_var_correction(provider, true, false)?;
1452
1453 let root_idx = self.root as usize;
1454 let root_view = self.values.slice(root_idx..(root_idx + 1));
1455 device
1456 .dtod_copy(&root_view, out_log_z)
1457 .map_err(|e| XlogError::Kernel(format!("Failed to copy device logZ: {}", e)))?;
1458
1459 Ok(())
1462 }
1463
1464 pub fn eval_log_wmc_device_into(
1469 &mut self,
1470 provider: &CudaKernelProvider,
1471 var_log_weights: &[(f64, f64)],
1472 out_log_z: &mut TrackedCudaSlice<f64>,
1473 ) -> Result<()> {
1474 self.set_base_weights(provider, var_log_weights)?;
1475 self.eval_log_wmc_device_inplace(provider, out_log_z)
1476 }
1477
1478 pub fn eval_log_wmc_device(
1483 &mut self,
1484 provider: &CudaKernelProvider,
1485 var_log_weights: &[(f64, f64)],
1486 ) -> Result<TrackedCudaSlice<f64>> {
1487 let memory = provider.memory();
1488 let mut out_log_z = memory.alloc::<f64>(1)?;
1489 self.eval_log_wmc_device_into(provider, var_log_weights, &mut out_log_z)?;
1490 Ok(out_log_z)
1491 }
1492
1493 fn apply_free_var_correction(
1494 &mut self,
1495 provider: &CudaKernelProvider,
1496 apply_log_z: bool,
1497 apply_grads: bool,
1498 ) -> Result<()> {
1499 let Some(mask) = self.free_var_mask.as_ref() else {
1500 return Ok(());
1501 };
1502
1503 if mask.len() != self.var_log_true.len() {
1504 return Err(XlogError::Compilation(format!(
1505 "GPU free-var mask len {} != weights len {}",
1506 mask.len(),
1507 self.var_log_true.len()
1508 )));
1509 }
1510
1511 let n = u32::try_from(mask.len())
1512 .map_err(|_| XlogError::Compilation("GPU free-var mask length overflow".to_string()))?;
1513 if n == 0 {
1514 return Ok(());
1515 }
1516
1517 let device = provider.device().inner();
1518 let block_dim = 256u32;
1519 let grid_dim = n.div_ceil(block_dim);
1520
1521 if apply_grads {
1522 let apply_grad = device
1523 .get_func(CIRCUIT_MODULE, circuit_kernels::XGCF_FREE_VAR_APPLY_GRAD)
1524 .ok_or_else(|| {
1525 XlogError::Kernel("xgcf_free_var_apply_grad kernel not found".to_string())
1526 })?;
1527 unsafe {
1529 apply_grad.clone().launch(
1530 LaunchConfig {
1531 grid_dim: (grid_dim, 1, 1),
1532 block_dim: (block_dim, 1, 1),
1533 shared_mem_bytes: 0,
1534 },
1535 (
1536 mask,
1537 &self.var_log_true,
1538 &self.var_log_false,
1539 n,
1540 &mut self.grad_true,
1541 &mut self.grad_false,
1542 ),
1543 )
1544 }
1545 .map_err(|e| XlogError::Kernel(format!("xgcf_free_var_apply_grad failed: {}", e)))?;
1546 }
1547
1548 if apply_log_z {
1549 let reduce_stage = device
1550 .get_func(CIRCUIT_MODULE, circuit_kernels::XGCF_FREE_VAR_REDUCE_STAGE)
1551 .ok_or_else(|| {
1552 XlogError::Kernel("xgcf_free_var_reduce_stage kernel not found".to_string())
1553 })?;
1554 let add_scalar = device
1555 .get_func(CIRCUIT_MODULE, circuit_kernels::XGCF_ADD_SCALAR)
1556 .ok_or_else(|| XlogError::Kernel("xgcf_add_scalar kernel not found".to_string()))?;
1557
1558 let memory = provider.memory();
1559 let mut buf_a = memory.alloc::<f64>(mask.len())?;
1560 let mut buf_b = memory.alloc::<f64>(mask.len())?;
1561
1562 let mut stage_n = n;
1563 let mut stage0 = true;
1564 let mut output_is_a = true;
1565 loop {
1566 let out_len = stage_n.div_ceil(2);
1567 let stage_grid = out_len.div_ceil(block_dim);
1568
1569 let (in_buf, out_buf): (&TrackedCudaSlice<f64>, &mut TrackedCudaSlice<f64>) =
1570 if output_is_a {
1571 (&buf_b, &mut buf_a)
1572 } else {
1573 (&buf_a, &mut buf_b)
1574 };
1575 let mode = if stage0 { 0u32 } else { 1u32 };
1576
1577 unsafe {
1579 reduce_stage.clone().launch(
1580 LaunchConfig {
1581 grid_dim: (stage_grid, 1, 1),
1582 block_dim: (block_dim, 1, 1),
1583 shared_mem_bytes: 0,
1584 },
1585 (
1586 mask,
1587 &self.var_log_true,
1588 &self.var_log_false,
1589 in_buf,
1590 stage_n,
1591 mode,
1592 out_buf,
1593 ),
1594 )
1595 }
1596 .map_err(|e| {
1597 XlogError::Kernel(format!("xgcf_free_var_reduce_stage failed: {}", e))
1598 })?;
1599
1600 if out_len == 1 {
1601 let result_buf = if output_is_a { &buf_a } else { &buf_b };
1602 unsafe {
1604 add_scalar.clone().launch(
1605 LaunchConfig {
1606 grid_dim: (1, 1, 1),
1607 block_dim: (1, 1, 1),
1608 shared_mem_bytes: 0,
1609 },
1610 (&mut self.values, self.root, result_buf),
1611 )
1612 }
1613 .map_err(|e| XlogError::Kernel(format!("xgcf_add_scalar failed: {}", e)))?;
1614 break;
1615 }
1616
1617 stage_n = out_len;
1618 stage0 = false;
1619 output_is_a = !output_is_a;
1620 }
1621 }
1622
1623 Ok(())
1624 }
1625
1626 pub fn eval_grads_inplace(&mut self, provider: &CudaKernelProvider) -> Result<()> {
1634 let device = provider.device().inner();
1635
1636 let func = device
1638 .get_func(CIRCUIT_MODULE, circuit_kernels::XGCF_FORWARD_LEVEL)
1639 .ok_or_else(|| XlogError::Kernel("xgcf_forward_level kernel not found".to_string()))?;
1640
1641 let block_size: u32 = 256;
1642 let num_levels: usize = self.num_levels as usize;
1643 for level in 0..num_levels {
1644 let num_level_nodes: usize = match self.level_offsets_host.as_ref() {
1645 Some(off) => checked_host_level_width(off, level)?,
1646 None => self.level_nodes.len(),
1647 };
1648 if num_level_nodes == 0 {
1649 continue;
1650 }
1651
1652 let num_blocks =
1653 checked_gpu_launch_blocks("xgcf_forward_level", num_level_nodes, block_size)?;
1654 let config = LaunchConfig {
1655 grid_dim: (num_blocks, 1, 1),
1656 block_dim: (block_size, 1, 1),
1657 shared_mem_bytes: 0,
1658 };
1659 let level_u32: u32 = level as u32;
1660
1661 let mut params: Vec<*mut c_void> = vec![
1662 (&self.node_type).as_kernel_param(),
1663 (&self.child_offsets).as_kernel_param(),
1664 (&self.child_indices).as_kernel_param(),
1665 (&self.lit).as_kernel_param(),
1666 (&self.decision_var).as_kernel_param(),
1667 (&self.decision_child_false).as_kernel_param(),
1668 (&self.decision_child_true).as_kernel_param(),
1669 (&self.level_nodes).as_kernel_param(),
1670 (&self.level_offsets).as_kernel_param(),
1671 level_u32.as_kernel_param(),
1672 (&self.var_log_true).as_kernel_param(),
1673 (&self.var_log_false).as_kernel_param(),
1674 (&self.values).as_kernel_param(),
1675 ];
1676
1677 unsafe { func.clone().launch(config, &mut params) }
1679 .map_err(|e| XlogError::Kernel(format!("xgcf_forward_level failed: {}", e)))?;
1680 }
1681
1682 device
1684 .memset_zeros(&mut self.adj)
1685 .map_err(|e| XlogError::Kernel(format!("Failed to zero adj buffer: {}", e)))?;
1686 device
1687 .memset_zeros(&mut self.grad_true)
1688 .map_err(|e| XlogError::Kernel(format!("Failed to zero grad_true buffer: {}", e)))?;
1689 device
1690 .memset_zeros(&mut self.grad_false)
1691 .map_err(|e| XlogError::Kernel(format!("Failed to zero grad_false buffer: {}", e)))?;
1692
1693 let root_idx = self.root as usize;
1695 let mut root_adj_view = self.adj.slice_mut(root_idx..(root_idx + 1));
1696 let fill_const = device
1697 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_F64)
1698 .ok_or_else(|| {
1699 XlogError::Kernel("arith_fill_const_f64 kernel not found".to_string())
1700 })?;
1701 unsafe {
1703 fill_const.clone().launch(
1704 LaunchConfig {
1705 grid_dim: (1, 1, 1),
1706 block_dim: (1, 1, 1),
1707 shared_mem_bytes: 0,
1708 },
1709 (1.0_f64, 1u32, &mut root_adj_view),
1710 )
1711 }
1712 .map_err(|e| XlogError::Kernel(format!("arith_fill_const_f64 failed: {}", e)))?;
1713
1714 let propagate = device
1715 .get_func(
1716 CIRCUIT_MODULE,
1717 circuit_kernels::XGCF_BACKWARD_LEVEL_PROPAGATE,
1718 )
1719 .ok_or_else(|| {
1720 XlogError::Kernel("xgcf_backward_level_propagate kernel not found".to_string())
1721 })?;
1722 let decision_grad = device
1723 .get_func(
1724 CIRCUIT_MODULE,
1725 circuit_kernels::XGCF_BACKWARD_LEVEL_DECISION_GRAD,
1726 )
1727 .ok_or_else(|| {
1728 XlogError::Kernel("xgcf_backward_level_decision_grad kernel not found".to_string())
1729 })?;
1730 let lit_grad = device
1731 .get_func(
1732 CIRCUIT_MODULE,
1733 circuit_kernels::XGCF_BACKWARD_LEVEL_LIT_GRAD,
1734 )
1735 .ok_or_else(|| {
1736 XlogError::Kernel("xgcf_backward_level_lit_grad kernel not found".to_string())
1737 })?;
1738
1739 let num_levels: usize = self.num_levels as usize;
1740 for level in (0..num_levels).rev() {
1741 let num_level_nodes: usize = match self.level_offsets_host.as_ref() {
1742 Some(off) => checked_host_level_width(off, level)?,
1743 None => self.level_nodes.len(),
1744 };
1745 if num_level_nodes == 0 {
1746 continue;
1747 }
1748
1749 let num_blocks =
1750 checked_gpu_launch_blocks("xgcf_backward_level", num_level_nodes, block_size)?;
1751 let config = LaunchConfig {
1752 grid_dim: (num_blocks, 1, 1),
1753 block_dim: (block_size, 1, 1),
1754 shared_mem_bytes: 0,
1755 };
1756 let level_u32: u32 = level as u32;
1757
1758 let mut params: Vec<*mut c_void> = vec![
1759 (&self.node_type).as_kernel_param(),
1760 (&self.child_offsets).as_kernel_param(),
1761 (&self.child_indices).as_kernel_param(),
1762 (&self.decision_var).as_kernel_param(),
1763 (&self.decision_child_false).as_kernel_param(),
1764 (&self.decision_child_true).as_kernel_param(),
1765 (&self.level_nodes).as_kernel_param(),
1766 (&self.level_offsets).as_kernel_param(),
1767 level_u32.as_kernel_param(),
1768 (&self.var_log_true).as_kernel_param(),
1769 (&self.var_log_false).as_kernel_param(),
1770 (&self.values).as_kernel_param(),
1771 (&self.adj).as_kernel_param(),
1772 ];
1773
1774 unsafe { propagate.clone().launch(config, &mut params) }.map_err(|e| {
1776 XlogError::Kernel(format!("xgcf_backward_level_propagate failed: {}", e))
1777 })?;
1778
1779 let mut params: Vec<*mut c_void> = vec![
1780 (&self.node_type).as_kernel_param(),
1781 (&self.decision_var).as_kernel_param(),
1782 (&self.decision_child_false).as_kernel_param(),
1783 (&self.decision_child_true).as_kernel_param(),
1784 (&self.level_nodes).as_kernel_param(),
1785 (&self.level_offsets).as_kernel_param(),
1786 level_u32.as_kernel_param(),
1787 (&self.var_log_true).as_kernel_param(),
1788 (&self.var_log_false).as_kernel_param(),
1789 (&self.values).as_kernel_param(),
1790 (&self.adj).as_kernel_param(),
1791 (&self.grad_true).as_kernel_param(),
1792 (&self.grad_false).as_kernel_param(),
1793 ];
1794
1795 unsafe { decision_grad.clone().launch(config, &mut params) }.map_err(|e| {
1797 XlogError::Kernel(format!("xgcf_backward_level_decision_grad failed: {}", e))
1798 })?;
1799
1800 unsafe {
1802 lit_grad.clone().launch(
1803 config,
1804 (
1805 &self.node_type,
1806 &self.lit,
1807 &self.level_nodes,
1808 &self.level_offsets,
1809 level_u32,
1810 &self.adj,
1811 &self.grad_true,
1812 &self.grad_false,
1813 ),
1814 )
1815 }
1816 .map_err(|e| {
1817 XlogError::Kernel(format!("xgcf_backward_level_lit_grad failed: {}", e))
1818 })?;
1819 }
1820
1821 self.apply_free_var_correction(provider, true, true)?;
1822 Ok(())
1825 }
1826
1827 #[cfg(feature = "host-io")]
1828 pub fn eval_log_wmc(
1830 &mut self,
1831 provider: &CudaKernelProvider,
1832 var_log_weights: &[(f64, f64)],
1833 ) -> Result<f64> {
1834 let device = provider.device().inner();
1835 let mut out_log_z = provider.memory().alloc::<f64>(1)?;
1836 self.eval_log_wmc_device_into(provider, var_log_weights, &mut out_log_z)?;
1837
1838 let mut host = [0.0_f64];
1839 device
1840 .dtoh_sync_copy_into(&out_log_z, &mut host)
1841 .map_err(|e| XlogError::Kernel(format!("Failed to read circuit root value: {}", e)))?;
1842 validate_circuit_value(host[0])
1843 }
1844
1845 #[cfg(feature = "host-io")]
1846 pub fn eval_log_wmc_and_grads(
1848 &mut self,
1849 provider: &CudaKernelProvider,
1850 var_log_weights: &[(f64, f64)],
1851 ) -> Result<(f64, Vec<f64>, Vec<f64>)> {
1852 let weights_len = (self.max_var as usize) + 1;
1853 if var_log_weights.len() < weights_len {
1854 return Err(XlogError::Compilation(format!(
1855 "GPU XGCF weights init expects weight table len >= {}, got {}",
1856 weights_len,
1857 var_log_weights.len()
1858 )));
1859 }
1860 self.set_base_weights(provider, var_log_weights)?;
1861 self.eval_grads_inplace(provider)?;
1862
1863 let device = provider.device().inner();
1864
1865 let mut host_grad_true: Vec<f64> = vec![0.0; weights_len];
1866 let mut host_grad_false: Vec<f64> = vec![0.0; weights_len];
1867
1868 let root_idx = self.root as usize;
1869 let root_view = self.values.slice(root_idx..(root_idx + 1));
1870 let mut log_z = [0.0_f64];
1871 device
1872 .dtoh_sync_copy_into(&root_view, &mut log_z)
1873 .map_err(|e| XlogError::Kernel(format!("Failed to read circuit root value: {}", e)))?;
1874 let log_z = validate_circuit_value(log_z[0])?;
1875
1876 device
1877 .dtoh_sync_copy_into(&self.grad_true, &mut host_grad_true)
1878 .map_err(|e| XlogError::Kernel(format!("Failed to download grad_true: {}", e)))?;
1879 device
1880 .dtoh_sync_copy_into(&self.grad_false, &mut host_grad_false)
1881 .map_err(|e| XlogError::Kernel(format!("Failed to download grad_false: {}", e)))?;
1882 validate_circuit_gradient_values(&host_grad_true, &host_grad_false)?;
1883
1884 Ok((log_z, host_grad_true, host_grad_false))
1885 }
1886}