1use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use std::time::Instant;
10
11use cudarc::driver::DeviceSlice;
12use xlog_core::{Result, XlogError};
13use xlog_cuda::memory::TrackedCudaSlice;
14use xlog_cuda::CudaKernelProvider;
15use xlog_solve::{GpuCdclConfig, GpuCnf};
16
17use crate::compilation::gpu_cache::{GpuCircuitCache, GpuCircuitCacheHandle};
18use crate::gpu::{GpuCircuitBuilder, GpuCircuitLayout, GpuXgcf};
19
20pub mod disk_cache;
21pub mod gpu_cache;
22pub mod gpu_cnf;
23pub mod gpu_d4;
24pub mod gpu_pir;
25pub mod gpu_pir_intern;
26pub mod gpu_weights;
27pub mod validation;
28
29pub use gpu_cnf::{encode_cnf_gpu, GpuCnfEncoding, GpuCnfVarTables};
30pub use gpu_d4::GpuCompileConfig;
31pub use gpu_pir::{GpuPirGraph, GpuPirRoots, PIR_AND, PIR_LIT, PIR_NEG_LIT, PIR_OR};
32pub use gpu_pir_intern::{GpuPirInterner, PirBatch};
35pub use gpu_weights::GpuWeights;
36pub use gpu_weights::{
37 apply_query_vars_device, build_evidence_by_var_gpu, build_weights_gpu, map_nodes_to_vars_gpu,
38 restore_query_vars_device,
39};
40pub use validation::{
41 build_equivalence_queries_gpu, validate_equivalence_gpu, validate_equivalence_gpu_gated,
42 GpuEquivalenceConfig, GpuEquivalenceQueries,
43};
44#[derive(Debug, Clone, Default)]
49pub struct CircuitCompileProfile {
50 pub cnf_hash_sec: f64,
51 pub d4_compile_sec: f64,
52 pub verify_sec: f64,
53 pub smooth_sec: f64,
54 pub cache_store_sec: f64,
55 pub free_var_mask_sec: f64,
56 pub gpu_cache_hit: bool,
57 pub disk_cache_hit: bool,
58 pub frontier_items: u32,
61}
62
63#[derive(Debug, Default)]
70pub(crate) struct CircuitCompilationLedger {
71 compiler_invocations: AtomicU64,
72 materializations: AtomicU64,
73 disk_cache_restores: AtomicU64,
74 gpu_cache_hits: AtomicU64,
75}
76
77#[cfg(feature = "host-io")]
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub(crate) struct CircuitCompilationSnapshot {
80 pub(crate) compiler_invocations: u64,
81 pub(crate) materializations: u64,
82 pub(crate) disk_cache_restores: u64,
83 pub(crate) gpu_cache_hits: u64,
84}
85
86impl CircuitCompilationLedger {
87 pub(crate) fn new() -> Self {
88 Self::default()
89 }
90
91 #[cfg(feature = "host-io")]
92 pub(crate) fn snapshot(&self) -> CircuitCompilationSnapshot {
93 CircuitCompilationSnapshot {
94 compiler_invocations: self.compiler_invocations.load(Ordering::Acquire),
95 materializations: self.materializations.load(Ordering::Acquire),
96 disk_cache_restores: self.disk_cache_restores.load(Ordering::Acquire),
97 gpu_cache_hits: self.gpu_cache_hits.load(Ordering::Acquire),
98 }
99 }
100
101 fn record_compiler_invocation(&self) -> Result<()> {
102 increment_circuit_event(
103 &self.compiler_invocations,
104 "GPU circuit compiler invocation",
105 )
106 }
107
108 fn record_materialization(&self) -> Result<()> {
109 increment_circuit_event(&self.materializations, "exact circuit materialization")
110 }
111
112 fn record_disk_cache_restore(&self) -> Result<()> {
113 increment_circuit_event(&self.disk_cache_restores, "verified disk-cache restoration")
114 }
115
116 fn record_gpu_cache_hit(&self) -> Result<()> {
117 increment_circuit_event(&self.gpu_cache_hits, "GPU circuit-cache hit")
118 }
119}
120
121pub(crate) struct CircuitCompilationContext<'a> {
123 pub(crate) canonical_cnf_hash: Option<u64>,
124 pub(crate) ledger: &'a CircuitCompilationLedger,
125}
126
127fn increment_circuit_event(counter: &AtomicU64, event: &str) -> Result<()> {
128 counter
129 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| {
130 value.checked_add(1)
131 })
132 .map(|_| ())
133 .map_err(|_| XlogError::Compilation(format!("{event} counter overflowed")))
134}
135
136pub(crate) fn warmup_profiling_enabled() -> bool {
137 std::env::var("XLOG_WARMUP_PROFILE")
138 .map(|v| v == "1")
139 .unwrap_or(false)
140}
141
142pub struct DeviceRandomVarList {
144 list: TrackedCudaSlice<u32>,
145 count: u32,
146}
147
148impl DeviceRandomVarList {
149 pub fn from_device(list: TrackedCudaSlice<u32>, count: u32) -> Result<Self> {
150 let len = u32::try_from(list.len()).map_err(|_| {
151 XlogError::Compilation("DeviceRandomVarList: list length exceeds u32".to_string())
152 })?;
153 if count > len {
154 return Err(XlogError::Compilation(format!(
155 "DeviceRandomVarList: count {} exceeds list len {}",
156 count, len
157 )));
158 }
159 Ok(Self { list, count })
160 }
161
162 pub fn from_host(provider: &CudaKernelProvider, host: &[u32]) -> Result<Self> {
163 let memory = provider.memory();
164 let mut list = memory.alloc::<u32>(host.len())?;
165 if !host.is_empty() {
166 provider
167 .htod_sync_copy_into_tracked(host, &mut list)
168 .map_err(|e| {
169 XlogError::Kernel(format!("DeviceRandomVarList upload failed: {}", e))
170 })?;
171 }
172 let count = u32::try_from(host.len()).map_err(|_| {
173 XlogError::Compilation("DeviceRandomVarList: host len exceeds u32".to_string())
174 })?;
175 Ok(Self { list, count })
176 }
177
178 pub fn is_empty(&self) -> bool {
179 self.count == 0
180 }
181
182 pub fn count(&self) -> u32 {
183 self.count
184 }
185
186 pub fn list(&self) -> &TrackedCudaSlice<u32> {
187 &self.list
188 }
189}
190
191fn upload_disk_artifact_for_verification(
192 artifact: &disk_cache::CircuitArtifact,
193 provider: &Arc<CudaKernelProvider>,
194) -> Result<GpuXgcf> {
195 let memory = provider.memory().clone();
196
197 macro_rules! upload {
198 ($field:expr, $ty:ty, $name:literal) => {{
199 let mut device = memory.alloc::<$ty>($field.len())?;
200 provider
201 .htod_sync_copy_into_tracked($field, &mut device)
202 .map_err(|e| {
203 XlogError::Kernel(format!("disk cache verify upload {} failed: {}", $name, e))
204 })?;
205 device
206 }};
207 }
208
209 let builder = GpuCircuitBuilder {
210 node_type: upload!(&artifact.node_type, u8, "node_type"),
211 child_offsets: upload!(&artifact.child_offsets, u32, "child_offsets"),
212 child_indices: upload!(&artifact.child_indices, u32, "child_indices"),
213 lit: upload!(&artifact.lit, i32, "lit"),
214 decision_var: upload!(&artifact.decision_var, u32, "decision_var"),
215 decision_child_false: upload!(&artifact.decision_child_false, u32, "decision_child_false"),
216 decision_child_true: upload!(&artifact.decision_child_true, u32, "decision_child_true"),
217 };
218 let layout = GpuCircuitLayout {
219 num_nodes: artifact.num_nodes,
220 num_edges: artifact.num_edges,
221 num_levels: artifact.num_levels,
222 level_offsets: upload!(&artifact.level_offsets, u32, "level_offsets"),
223 level_nodes: upload!(&artifact.level_nodes, u32, "level_nodes"),
224 root: artifact.root,
225 max_var: artifact.max_var,
226 num_nodes_device: None,
227 num_edges_device: None,
228 };
229
230 GpuXgcf::from_device(builder, layout, provider)
231}
232
233pub fn compile_gpu_d4_and_verify(
235 cnf: &GpuCnf,
236 decision_var_limit: &TrackedCudaSlice<u32>,
237 provider: &Arc<CudaKernelProvider>,
238 config: &GpuCompileConfig,
239) -> Result<GpuXgcf> {
240 if config.cdcl_conflict_budget.is_some() {
241 return Err(XlogError::Compilation(
242 "cdcl_conflict_budget is not supported by the GPU CDCL verifier".to_string(),
243 ));
244 }
245 validation::check_verify_size_bound(cnf, "compile_gpu_d4_and_verify")?;
248 let circuit = gpu_d4::compile_gpu_d4(cnf, provider, config)?;
249 let cdcl = cdcl_config_from_compile(config)?;
250 validate_equivalence_gpu(
251 cnf,
252 decision_var_limit,
253 &circuit,
254 provider,
255 GpuEquivalenceConfig {
256 cdcl,
257 reuse_workspace: config.incremental_verify,
258 },
259 )?;
260 Ok(circuit)
261}
262
263pub fn compile_gpu_d4_and_verify_cached(
269 cnf: &GpuCnf,
270 decision_var_limit: &TrackedCudaSlice<u32>,
271 provider: &Arc<CudaKernelProvider>,
272 config: &GpuCompileConfig,
273 cache: &mut GpuCircuitCache,
274 random_vars: &DeviceRandomVarList,
275 canonical_cnf_hash: Option<u64>,
276) -> Result<(GpuCircuitCacheHandle, Option<CircuitCompileProfile>)> {
277 let ledger = CircuitCompilationLedger::new();
278 compile_gpu_d4_and_verify_cached_with_ledger(
279 cnf,
280 decision_var_limit,
281 provider,
282 config,
283 cache,
284 random_vars,
285 CircuitCompilationContext {
286 canonical_cnf_hash,
287 ledger: &ledger,
288 },
289 )
290}
291
292pub(crate) fn compile_gpu_d4_and_verify_cached_with_ledger(
293 cnf: &GpuCnf,
294 decision_var_limit: &TrackedCudaSlice<u32>,
295 provider: &Arc<CudaKernelProvider>,
296 config: &GpuCompileConfig,
297 cache: &mut GpuCircuitCache,
298 random_vars: &DeviceRandomVarList,
299 context: CircuitCompilationContext<'_>,
300) -> Result<(GpuCircuitCacheHandle, Option<CircuitCompileProfile>)> {
301 let CircuitCompilationContext {
302 canonical_cnf_hash,
303 ledger,
304 } = context;
305 if config.cdcl_conflict_budget.is_some() {
306 return Err(XlogError::Compilation(
307 "cdcl_conflict_budget is not supported by the GPU CDCL verifier".to_string(),
308 ));
309 }
310 validation::check_verify_size_bound(cnf, "compile_gpu_d4_and_verify_cached")?;
313
314 let profiling = warmup_profiling_enabled();
315 let mut profile = CircuitCompileProfile::default();
316
317 #[cfg(debug_assertions)]
319 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: hash_cnf_gpu");
320 let t_hash = if profiling {
321 Some(Instant::now())
322 } else {
323 None
324 };
325 let key = gpu_cache::hash_cnf_gpu(cnf, provider)?;
326 if let Some(t0) = t_hash {
327 provider
328 .device()
329 .synchronize()
330 .map_err(|e| XlogError::Kernel(format!("sync after hash_cnf_gpu: {}", e)))?;
331 profile.cnf_hash_sec = t0.elapsed().as_secs_f64();
332 }
333 #[cfg(debug_assertions)]
334 {
335 if !profiling {
336 provider
337 .device()
338 .synchronize()
339 .map_err(|e| XlogError::Kernel(format!("sync after hash_cnf_gpu failed: {}", e)))?;
340 }
341 }
342 #[cfg(debug_assertions)]
343 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: lookup_or_insert_device");
344 let lookup = cache.lookup_or_insert_device(&key)?;
345 let mut handle = lookup.into_handle()?;
346
347 let compile_needed_host: Vec<u32> = provider
354 .device()
355 .inner()
356 .dtoh_sync_copy(handle.compile_needed_device())
357 .map_err(|e| XlogError::Kernel(format!("dtoh compile_needed: {}", e)))?;
358 let compile_needed = compile_needed_host[0];
359
360 if compile_needed == 0 {
362 profile.gpu_cache_hit = true;
363 ledger.record_gpu_cache_hit()?;
364 ledger.record_materialization()?;
365 let out_profile = if profiling { Some(profile) } else { None };
366 return Ok((handle, out_profile));
367 }
368
369 let cache_key = if compile_needed == 1 {
373 if let Some(canonical_hash) = canonical_cnf_hash {
374 let config_hash = hash_compile_config(config);
375 let random_vars_hash = hash_random_vars(random_vars, provider)?;
376 let gpu_cnf_hash = read_gpu_cnf_hash(&key, provider)?;
377 let cnf_hash = combine_disk_cnf_hash(canonical_hash, gpu_cnf_hash);
378 let sm = detect_compute_capability(provider)?;
379 Some(disk_cache::CircuitCacheKey {
380 cnf_hash,
381 config_hash,
382 random_vars_hash,
383 sm,
384 })
385 } else {
386 None
387 }
388 } else {
389 None
390 };
391
392 if let Some(ref disk_key) = cache_key {
394 #[cfg(debug_assertions)]
395 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: checking disk cache");
396 if let Ok(Some(artifact)) = disk_cache::read_artifact(disk_key) {
397 #[cfg(debug_assertions)]
398 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: disk cache hit");
399 if artifact.max_var > cnf.var_cap {
407 #[cfg(debug_assertions)]
408 eprintln!(
409 "[xlog-prob] compile_gpu_d4_and_verify_cached: stale disk artifact \
410 (max_var {} > cnf var_cap {}), evicting",
411 artifact.max_var, cnf.var_cap
412 );
413 disk_cache::evict_artifact(disk_key);
414 } else {
415 let circuit = upload_disk_artifact_for_verification(&artifact, provider)?;
416 let verifier_decision_var_limit = if random_vars.is_empty() {
417 &cnf.num_vars
418 } else {
419 decision_var_limit
420 };
421 let cdcl = cdcl_config_from_compile(config)?;
422 let t_verify = if profiling {
423 Some(Instant::now())
424 } else {
425 None
426 };
427 match validate_equivalence_gpu_gated(
428 cnf,
429 verifier_decision_var_limit,
430 &circuit,
431 provider,
432 GpuEquivalenceConfig {
433 cdcl,
434 reuse_workspace: config.incremental_verify,
435 },
436 handle.compile_needed_device(),
437 ) {
438 Ok(()) => {
439 if let Some(t0) = t_verify {
440 provider.device().synchronize().map_err(|e| {
441 XlogError::Kernel(format!("sync after disk cache verify: {}", e))
442 })?;
443 profile.verify_sec = t0.elapsed().as_secs_f64();
444 }
445 cache.restore_from_host_arrays(&mut handle, &artifact)?;
446 provider.device().synchronize().map_err(|e| {
447 XlogError::Kernel(format!("sync after disk cache restore: {}", e))
448 })?;
449 profile.disk_cache_hit = true;
450 ledger.record_disk_cache_restore()?;
451 ledger.record_materialization()?;
452 let out_profile = if profiling { Some(profile) } else { None };
453 return Ok((handle, out_profile));
454 }
455 Err(_stale) => {
456 #[cfg(debug_assertions)]
460 eprintln!(
461 "[xlog-prob] compile_gpu_d4_and_verify_cached: cached circuit \
462 failed equivalence against current CNF ({_stale}), evicting"
463 );
464 disk_cache::evict_artifact(disk_key);
465 }
466 }
467 }
468 }
469 #[cfg(debug_assertions)]
470 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: disk cache miss");
471 }
472
473 let d4_config = d4_config_for_smoothing(config, random_vars.count())?;
474
475 #[cfg(debug_assertions)]
477 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: compile_gpu_d4_gated");
478 let t_d4 = if profiling {
479 Some(Instant::now())
480 } else {
481 None
482 };
483 ledger.record_compiler_invocation()?;
484 let (circuit_base, frontier_items) = gpu_d4::compile_gpu_d4_gated_with_stats(
485 cnf,
486 provider,
487 &d4_config,
488 handle.compile_needed_device(),
489 )?;
490 profile.frontier_items = frontier_items;
491 if let Some(t0) = t_d4 {
492 provider
493 .device()
494 .synchronize()
495 .map_err(|e| XlogError::Kernel(format!("sync after d4 compile: {}", e)))?;
496 profile.d4_compile_sec = t0.elapsed().as_secs_f64();
497 }
498 #[cfg(debug_assertions)]
499 {
500 if !profiling {
501 provider.device().synchronize().map_err(|e| {
502 XlogError::Kernel(format!("sync after compile_gpu_d4_gated failed: {}", e))
503 })?;
504 }
505 }
506 if circuit_base.num_nodes() == 0 || circuit_base.num_levels() == 0 {
507 ledger.record_materialization()?;
511 let out_profile = if profiling { Some(profile) } else { None };
512 return Ok((handle, out_profile));
513 }
514
515 let verifier_decision_var_limit = if random_vars.is_empty() {
524 &cnf.num_vars
525 } else {
526 decision_var_limit
527 };
528 let cdcl = cdcl_config_from_compile(config)?;
529 #[cfg(debug_assertions)]
530 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: validate_equivalence_gpu_gated");
531 let t_verify = if profiling {
532 Some(Instant::now())
533 } else {
534 None
535 };
536 validate_equivalence_gpu_gated(
537 cnf,
538 verifier_decision_var_limit,
539 &circuit_base,
540 provider,
541 GpuEquivalenceConfig {
542 cdcl,
543 reuse_workspace: config.incremental_verify,
544 },
545 handle.compile_needed_device(),
546 )?;
547 if let Some(t0) = t_verify {
548 provider
549 .device()
550 .synchronize()
551 .map_err(|e| XlogError::Kernel(format!("sync after verify: {}", e)))?;
552 profile.verify_sec = t0.elapsed().as_secs_f64();
553 }
554 #[cfg(debug_assertions)]
555 {
556 if !profiling {
557 provider.device().synchronize().map_err(|e| {
558 XlogError::Kernel(format!(
559 "sync after validate_equivalence_gpu_gated failed: {}",
560 e
561 ))
562 })?;
563 }
564 }
565
566 let t_smooth = if profiling {
571 Some(Instant::now())
572 } else {
573 None
574 };
575 let circuit_eval = if random_vars.is_empty() {
576 circuit_base
577 } else {
578 #[cfg(debug_assertions)]
579 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: smooth_random_vars_device");
580 let smoothed = circuit_base.smooth_random_vars_device(
581 provider,
582 random_vars.list(),
583 random_vars.count(),
584 config.smooth_node_cap,
585 config.smooth_edge_cap,
586 )?;
587 #[cfg(debug_assertions)]
588 {
589 if !profiling {
590 provider.device().synchronize().map_err(|e| {
591 XlogError::Kernel(format!(
592 "sync after smooth_random_vars_device failed: {}",
593 e
594 ))
595 })?;
596 }
597 }
598 smoothed
599 };
600 if let Some(t0) = t_smooth {
601 provider
602 .device()
603 .synchronize()
604 .map_err(|e| XlogError::Kernel(format!("sync after smooth: {}", e)))?;
605 profile.smooth_sec = t0.elapsed().as_secs_f64();
606 }
607
608 #[cfg(debug_assertions)]
610 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: store_from_xgcf");
611 let t_store = if profiling {
612 Some(Instant::now())
613 } else {
614 None
615 };
616 cache.store_from_xgcf(&mut handle, &circuit_eval)?;
617 if let Some(t0) = t_store {
618 provider
619 .device()
620 .synchronize()
621 .map_err(|e| XlogError::Kernel(format!("sync after cache store: {}", e)))?;
622 profile.cache_store_sec = t0.elapsed().as_secs_f64();
623 }
624 #[cfg(debug_assertions)]
625 {
626 if !profiling {
627 provider.device().synchronize().map_err(|e| {
628 XlogError::Kernel(format!("sync after store_from_xgcf failed: {}", e))
629 })?;
630 }
631 }
632
633 #[cfg(debug_assertions)]
635 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: compute_free_var_mask_gpu_gated");
636 let t_fvm = if profiling {
637 Some(Instant::now())
638 } else {
639 None
640 };
641 let free_var_mask = gpu_d4::compute_free_var_mask_gpu_gated(
642 cnf,
643 &circuit_eval,
644 provider,
645 handle.compile_needed_device(),
646 )?;
647 #[cfg(debug_assertions)]
648 {
649 if !profiling {
650 provider.device().synchronize().map_err(|e| {
651 XlogError::Kernel(format!(
652 "sync after compute_free_var_mask_gpu_gated failed: {}",
653 e
654 ))
655 })?;
656 }
657 }
658 let mask_host: Vec<u8> = provider
663 .device()
664 .inner()
665 .dtoh_sync_copy(&free_var_mask)
666 .map_err(|e| XlogError::Kernel(format!("Failed to read free_var_mask: {}", e)))?;
667 let has_free_vars = mask_host.iter().any(|&b| b != 0);
668 #[cfg(debug_assertions)]
669 eprintln!(
670 "[xlog-prob] free_var_mask: {} free vars, batched eval {}",
671 if has_free_vars { "has" } else { "no" },
672 if has_free_vars { "DISABLED" } else { "ENABLED" },
673 );
674 if has_free_vars {
675 cache.store_free_var_mask(&handle, &free_var_mask)?;
676 #[cfg(debug_assertions)]
677 {
678 if !profiling {
679 provider.device().synchronize().map_err(|e| {
680 XlogError::Kernel(format!("sync after store_free_var_mask failed: {}", e))
681 })?;
682 }
683 }
684 }
685 if let Some(t0) = t_fvm {
686 provider
687 .device()
688 .synchronize()
689 .map_err(|e| XlogError::Kernel(format!("sync after free_var_mask: {}", e)))?;
690 profile.free_var_mask_sec = t0.elapsed().as_secs_f64();
691 }
692
693 if let Some(ref disk_key) = cache_key {
698 if let Ok(artifact) = cache.build_artifact_from_device(&handle, provider) {
699 let _ = disk_cache::write_artifact(disk_key, &artifact);
700 #[cfg(debug_assertions)]
701 eprintln!("[xlog-prob] compile_gpu_d4_and_verify_cached: wrote disk cache artifact");
702 }
703 }
704
705 ledger.record_materialization()?;
706 let out_profile = if profiling { Some(profile) } else { None };
707 Ok((handle, out_profile))
708}
709
710fn d4_config_for_smoothing(
711 config: &GpuCompileConfig,
712 random_var_count: u32,
713) -> Result<GpuCompileConfig> {
714 if random_var_count == 0 {
715 return Ok(*config);
716 }
717 let headroom = 2u32
718 .checked_add(random_var_count)
719 .ok_or_else(|| XlogError::Compilation("smooth headroom overflow".to_string()))?;
720 if config.smooth_node_cap <= headroom {
721 return Err(XlogError::Compilation(format!(
722 "GpuCompileConfig smooth_node_cap {} too small for smoothing headroom {}",
723 config.smooth_node_cap, headroom
724 )));
725 }
726 let base_cap = config
727 .smooth_node_cap
728 .checked_sub(headroom)
729 .ok_or_else(|| XlogError::Compilation("smooth node cap underflow".to_string()))?;
730 if base_cap < 3 {
731 return Err(XlogError::Compilation(
732 "GpuCompileConfig smooth_node_cap leaves <3 base nodes".to_string(),
733 ));
734 }
735 let mut out = *config;
736 out.smooth_node_cap = base_cap;
737 Ok(out)
738}
739
740fn cdcl_config_from_compile(config: &GpuCompileConfig) -> Result<GpuCdclConfig> {
741 if config.cdcl_restart_interval == 0 {
742 return Err(XlogError::Compilation(
743 "cdcl_restart_interval must be > 0".to_string(),
744 ));
745 }
746 if config.cdcl_learned_bytes == 0 {
747 return Err(XlogError::Compilation(
748 "cdcl_learned_bytes must be > 0".to_string(),
749 ));
750 }
751
752 const AVG_LEN: u64 = 4;
754 const META_BYTES_PER_CLAUSE: u64 = 24; const PROOF_BYTES_PER_CLAUSE: u64 = 8 + (8 * AVG_LEN); const LIT_BYTES_PER_CLAUSE: u64 = 4 * AVG_LEN;
757
758 let bytes_per_clause = META_BYTES_PER_CLAUSE
759 .checked_add(PROOF_BYTES_PER_CLAUSE)
760 .and_then(|v| v.checked_add(LIT_BYTES_PER_CLAUSE))
761 .ok_or_else(|| XlogError::Compilation("cdcl bytes per clause overflow".to_string()))?;
762
763 let max_clauses = config
764 .cdcl_learned_bytes
765 .checked_div(bytes_per_clause)
766 .ok_or_else(|| XlogError::Compilation("cdcl_learned_bytes div overflow".to_string()))?;
767 if max_clauses == 0 {
768 return Err(XlogError::Compilation(
769 "cdcl_learned_bytes too small for learned clause arena".to_string(),
770 ));
771 }
772
773 let max_lits = max_clauses
774 .checked_mul(AVG_LEN)
775 .ok_or_else(|| XlogError::Compilation("max_learned_lits overflow".to_string()))?;
776 let max_proof_u32 = max_clauses
777 .checked_mul(2 + 2 * AVG_LEN)
778 .ok_or_else(|| XlogError::Compilation("max_proof_u32 overflow".to_string()))?;
779
780 let max_learned_clauses = u32::try_from(max_clauses)
781 .map_err(|_| XlogError::Compilation("max_learned_clauses exceeds u32::MAX".to_string()))?;
782 let max_learned_lits = u32::try_from(max_lits)
783 .map_err(|_| XlogError::Compilation("max_learned_lits exceeds u32::MAX".to_string()))?;
784 let max_proof_u32 = u32::try_from(max_proof_u32)
785 .map_err(|_| XlogError::Compilation("max_proof_u32 exceeds u32::MAX".to_string()))?;
786
787 let reduce_interval = config
788 .cdcl_restart_interval
789 .checked_mul(20)
790 .ok_or_else(|| XlogError::Compilation("cdcl reduce_interval overflow".to_string()))?;
791
792 let mut gpu_cdcl = GpuCdclConfig::default();
793 gpu_cdcl.max_learned_clauses = max_learned_clauses;
794 gpu_cdcl.max_learned_lits = max_learned_lits;
795 gpu_cdcl.max_proof_u32 = max_proof_u32;
796 gpu_cdcl.restart_base = config.cdcl_restart_interval;
797 gpu_cdcl.reduce_interval = reduce_interval;
798 gpu_cdcl.max_conflicts = verify_max_conflicts();
805 Ok(gpu_cdcl)
806}
807
808fn verify_max_conflicts() -> u32 {
811 static BUDGET: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
812 *BUDGET.get_or_init(|| {
813 std::env::var("XLOG_D4_VERIFY_MAX_CONFLICTS")
814 .ok()
815 .and_then(|v| v.trim().parse::<u32>().ok())
816 .unwrap_or(0)
817 })
818}
819
820fn fnv1a_u64(bytes: &[u8]) -> u64 {
827 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
828 const FNV_PRIME: u64 = 0x100000001b3;
829 let mut h = FNV_OFFSET;
830 for &b in bytes {
831 h ^= b as u64;
832 h = h.wrapping_mul(FNV_PRIME);
833 }
834 h
835}
836
837fn combine_disk_cnf_hash(canonical_hash: u64, gpu_cnf_hash: u64) -> u64 {
838 let mut buf = Vec::with_capacity(16);
839 buf.extend_from_slice(&canonical_hash.to_le_bytes());
840 buf.extend_from_slice(&gpu_cnf_hash.to_le_bytes());
841 fnv1a_u64(&buf)
842}
843
844fn read_gpu_cnf_hash(
845 hash: &TrackedCudaSlice<u64>,
846 provider: &Arc<CudaKernelProvider>,
847) -> Result<u64> {
848 let host: Vec<u64> = provider
849 .device()
850 .inner()
851 .dtoh_sync_copy(hash)
852 .map_err(|e| XlogError::Kernel(format!("dtoh GPU CNF hash for disk cache key: {}", e)))?;
853 host.first()
854 .copied()
855 .ok_or_else(|| XlogError::Kernel("empty GPU CNF hash for disk cache key".to_string()))
856}
857
858fn hash_compile_config(config: &GpuCompileConfig) -> u64 {
860 let mut buf = Vec::new();
861 buf.extend_from_slice(&config.frontier_depth.to_le_bytes());
862 buf.extend_from_slice(&config.max_frontier_items.to_le_bytes());
863 buf.extend_from_slice(&config.max_depth.to_le_bytes());
864 buf.extend_from_slice(&config.smooth_node_cap.to_le_bytes());
865 buf.extend_from_slice(&config.smooth_edge_cap.to_le_bytes());
866 buf.extend_from_slice(&config.cdcl_restart_interval.to_le_bytes());
869 buf.extend_from_slice(&config.cdcl_learned_bytes.to_le_bytes());
870 fnv1a_u64(&buf)
871}
872
873fn hash_random_vars(
875 random_vars: &DeviceRandomVarList,
876 provider: &Arc<CudaKernelProvider>,
877) -> Result<u64> {
878 let count = random_vars.count();
879 let mut buf = Vec::new();
880 buf.extend_from_slice(&count.to_le_bytes());
881 if count > 0 {
882 let host: Vec<u32> = provider
883 .device()
884 .inner()
885 .dtoh_sync_copy(random_vars.list())
886 .map_err(|e| {
887 XlogError::Kernel(format!("dtoh random_vars for disk cache hash: {}", e))
888 })?;
889 for &v in &host[..count as usize] {
891 buf.extend_from_slice(&v.to_le_bytes());
892 }
893 }
894 Ok(fnv1a_u64(&buf))
895}
896
897fn detect_compute_capability(provider: &Arc<CudaKernelProvider>) -> Result<u32> {
899 use cudarc::driver::sys::CUdevice_attribute;
900
901 let device = provider.device().inner();
902 let major = device
903 .attribute(CUdevice_attribute::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR)
904 .map_err(|e| {
905 XlogError::Kernel(format!("Failed to query compute capability major: {}", e))
906 })?;
907 let minor = device
908 .attribute(CUdevice_attribute::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR)
909 .map_err(|e| {
910 XlogError::Kernel(format!("Failed to query compute capability minor: {}", e))
911 })?;
912 let major_u32: u32 = major.try_into().map_err(|_| {
913 XlogError::Kernel(format!(
914 "compute capability major {} cannot be converted to u32",
915 major
916 ))
917 })?;
918 let minor_u32: u32 = minor.try_into().map_err(|_| {
919 XlogError::Kernel(format!(
920 "compute capability minor {} cannot be converted to u32",
921 minor
922 ))
923 })?;
924 Ok(major_u32 * 10 + minor_u32)
925}