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