1use std::{collections::BTreeSet, ffi::c_void, sync::Arc};
4
5use cudarc::driver::LaunchConfig;
6use xlog_core::{RelId, Result, ScalarType, Schema, XlogError};
7use xlog_cuda::provider::{
8 epistemic_kernels, HostLaunchMetadataTransferStats, HostTransferStats, EPISTEMIC_MODULE,
9};
10use xlog_cuda::{
11 memory::{validate_logical_row_count, TrackedCudaSlice},
12 sys, AsKernelParam, CudaBuffer, CudaColumn, DeviceSlice, DriverError, LaunchAsync,
13};
14use xlog_ir::rir::{MultiwayPlan, PlannedHashReason, RirNode, StreamGroupId};
15use xlog_ir::{
16 EirEpistemicMode, EirEpistemicOp, EirTerm, EpistemicExecutablePlan, EpistemicExecutionBackend,
17 EpistemicFallbackPolicy, EpistemicGpuBufferKind, EpistemicGpuHotPathPhase, EpistemicGpuPlan,
18 EpistemicTupleMembershipBinding, EpistemicWcojReductionStatus,
19};
20
21use super::Executor;
22
23const XLOG_CONSTRAINT_RELATION_PREFIX: &str = "__xlog_constraint_";
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct EpistemicGpuWorkspaceCapacities {
28 pub max_candidates: usize,
30 pub max_worlds: usize,
32 pub max_models_per_reduction: usize,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct EpistemicGpuWorkspaceLayout {
39 pub candidate_assumption_bytes: usize,
41 pub world_view_bytes: usize,
43 pub model_membership_bytes: usize,
45 pub rejection_reason_slots: usize,
47}
48
49impl EpistemicGpuWorkspaceLayout {
50 pub fn for_plan(
52 plan: &EpistemicGpuPlan,
53 capacities: EpistemicGpuWorkspaceCapacities,
54 ) -> Result<Self> {
55 require_positive(
56 capacities.max_candidates,
57 "epistemic GPU workspace candidates",
58 )?;
59 require_positive(capacities.max_worlds, "epistemic GPU workspace worlds")?;
60 require_positive(
61 capacities.max_models_per_reduction,
62 "epistemic GPU workspace models",
63 )?;
64 require_positive(
65 plan.epistemic_literals.len(),
66 "epistemic GPU workspace literals",
67 )?;
68 require_positive(plan.reductions.len(), "epistemic GPU workspace reductions")?;
69
70 let literal_count = plan.epistemic_literals.len();
71 let reduction_count = plan.reductions.len();
72 let candidate_assumption_bytes = checked_product(capacities.max_candidates, literal_count)?;
73 let world_view_stride = capacities
74 .max_worlds
75 .max(world_view_bitset_bytes_per_candidate(literal_count)?);
76 let world_view_bytes = checked_product(capacities.max_candidates, world_view_stride)?;
77 let model_membership_bytes = checked_product(
78 checked_product(
79 checked_product(
80 capacities.max_candidates,
81 capacities.max_models_per_reduction,
82 )?,
83 reduction_count,
84 )?,
85 literal_count,
86 )?;
87
88 Ok(Self {
89 candidate_assumption_bytes,
90 world_view_bytes,
91 model_membership_bytes,
92 rejection_reason_slots: capacities.max_candidates,
93 })
94 }
95
96 pub fn total_bytes(&self) -> usize {
98 self.try_total_bytes()
99 .expect("epistemic GPU workspace layout byte total overflowed")
100 }
101
102 pub fn try_total_bytes(&self) -> Result<usize> {
104 let rejection_reason_bytes =
105 checked_product(self.rejection_reason_slots, std::mem::size_of::<u32>())?;
106 checked_sum(
107 checked_sum(
108 checked_sum(self.candidate_assumption_bytes, self.world_view_bytes)?,
109 self.model_membership_bytes,
110 )?,
111 rejection_reason_bytes,
112 )
113 }
114}
115
116pub struct EpistemicGpuWorkspace {
118 pub layout: EpistemicGpuWorkspaceLayout,
120 pub candidate_assumptions: TrackedCudaSlice<u8>,
122 pub world_views: TrackedCudaSlice<u8>,
124 pub model_membership: TrackedCudaSlice<u8>,
126 pub rejection_reasons: TrackedCudaSlice<u32>,
128 pub constraint_violation_index: TrackedCudaSlice<u32>,
135}
136
137impl EpistemicGpuWorkspace {
138 pub fn require_buffer_lengths_match_layout(&self, construct: &str) -> Result<()> {
140 if self.candidate_assumptions.len() != self.layout.candidate_assumption_bytes
141 || self.world_views.len() != self.layout.world_view_bytes
142 || self.model_membership.len() != self.layout.model_membership_bytes
143 || self.rejection_reasons.len() != self.layout.rejection_reason_slots
144 || self.constraint_violation_index.len() != self.layout.rejection_reason_slots
145 {
146 return Err(XlogError::UnsupportedEpistemicConstruct {
147 construct: construct.to_string(),
148 context: format!(
149 "prepared GPU workspace buffer lengths do not match layout: \
150 candidate_bytes={}/{} world_view_bytes={}/{} model_membership_bytes={}/{} \
151 rejection_reason_slots={}/{} constraint_violation_index_slots={}/{}",
152 self.candidate_assumptions.len(),
153 self.layout.candidate_assumption_bytes,
154 self.world_views.len(),
155 self.layout.world_view_bytes,
156 self.model_membership.len(),
157 self.layout.model_membership_bytes,
158 self.rejection_reasons.len(),
159 self.layout.rejection_reason_slots,
160 self.constraint_violation_index.len(),
161 self.layout.rejection_reason_slots
162 ),
163 });
164 }
165
166 Ok(())
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct EpistemicGpuWorkspaceResetTrace {
173 pub candidate_assumption_bytes: usize,
175 pub world_view_bytes: usize,
177 pub model_membership_bytes: usize,
179 pub rejection_reason_bytes: usize,
181 pub device_zero_ops: u32,
183 pub host_write_ops: u32,
185}
186
187#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
189pub struct EpistemicGpuKernelTimingTrace {
190 pub cuda_event_pairs: u32,
192 pub timing_sync_ops: u32,
194 pub kernel_elapsed_nanos: u64,
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub struct EpistemicGpuCandidateGenerationTrace {
201 pub literal_count: usize,
203 pub generated_candidates: usize,
205 pub candidate_assumption_bytes: usize,
207 pub kernel_launches: u32,
209 pub host_write_ops: u32,
211 pub kernel_timing: EpistemicGpuKernelTimingTrace,
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct EpistemicGpuCandidateValidationTrace {
218 pub literal_count: usize,
220 pub validated_candidates: usize,
222 pub candidate_assumption_bytes_checked: usize,
224 pub world_view_bytes_checked: usize,
226 pub rejection_reason_slots_written: usize,
228 pub kernel_launches: u32,
230 pub host_write_ops: u32,
232 pub kernel_timing: EpistemicGpuKernelTimingTrace,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub struct EpistemicGpuMaterializationTrace {
239 pub materialized_candidates: usize,
241 pub world_view_slots_written: usize,
243 pub kernel_launches: u32,
245 pub host_write_ops: u32,
247 pub kernel_timing: EpistemicGpuKernelTimingTrace,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub struct EpistemicGpuFinalResultMaterializationTrace {
254 pub materialized_candidates: usize,
256 pub output_row_count_device_reads: u32,
258 pub world_view_slots_written: usize,
260 pub kernel_launches: u32,
262 pub host_write_ops: u32,
264 pub kernel_timing: EpistemicGpuKernelTimingTrace,
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub struct EpistemicGpuFinalTupleMaterializationTrace {
271 pub output_column_count: usize,
273 pub output_row_capacity: usize,
275 pub tuple_bytes_capacity: usize,
277 pub output_row_count_device_reads: u32,
279 pub model_membership_bytes_checked: usize,
281 pub bounded_model_slots_per_reduction: usize,
283 pub row_specific_membership_row_capacity: usize,
285 pub row_filter_row_capacity_outside_model_slot_window: usize,
287 pub world_view_slots_checked: usize,
289 pub row_filter_count: usize,
291 pub negated_row_filter_count: usize,
293 pub final_row_count_device_writes: u32,
295 pub kernel_launches: u32,
297 pub host_write_ops: u32,
299 pub kernel_timing: EpistemicGpuKernelTimingTrace,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub struct EpistemicGpuTransferBudgetTrace {
306 pub candidate_count: usize,
308 pub tracked_dtoh_bytes: u64,
310 pub tracked_htod_bytes: u64,
312 pub tracked_dtoh_calls: u64,
314 pub tracked_htod_calls: u64,
316 pub tracked_aggregate_htod_bytes: u64,
318 pub tracked_aggregate_htod_calls: u64,
320 pub tracked_launch_metadata_htod_bytes: u64,
322 pub tracked_launch_metadata_htod_calls: u64,
324 pub tracked_data_plane_htod_bytes: u64,
326 pub tracked_data_plane_htod_calls: u64,
328 pub per_candidate_host_round_trips: u64,
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
334pub struct EpistemicGpuFinalResultTransferTrace {
335 pub final_output_rows: usize,
337 pub final_output_column_count: usize,
339 pub final_output_row_width_bytes: usize,
341 pub final_output_payload_bytes: u64,
343 pub row_count_device_reads: u32,
345 pub tracked_data_plane_dtoh_calls: u64,
347 pub tracked_data_plane_dtoh_bytes: u64,
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
353pub struct EpistemicGpuConstraintValidationTrace {
354 pub checked_constraint_relations: usize,
356 pub violated_constraint_relations: usize,
358 pub row_count_device_reads: u32,
360}
361
362impl EpistemicGpuConstraintValidationTrace {
363 pub fn require_matches_preflight(
365 &self,
366 construct: &str,
367 preflight: &EpistemicGpuRuntimePreflight,
368 ) -> Result<()> {
369 if self.checked_constraint_relations != preflight.reduced_constraint_relation_count
370 || self.violated_constraint_relations != 0
371 || self.row_count_device_reads as usize > self.checked_constraint_relations
372 {
373 return Err(XlogError::UnsupportedEpistemicConstruct {
374 construct: construct.to_string(),
375 context: format!(
376 "constraint validation trace must match reduced runtime preflight, got \
377 checked={} expected_checked={} violations={} row_count_reads={}",
378 self.checked_constraint_relations,
379 preflight.reduced_constraint_relation_count,
380 self.violated_constraint_relations,
381 self.row_count_device_reads
382 ),
383 });
384 }
385
386 Ok(())
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub enum EpistemicGpuRejectionReason {
393 InactiveWorld,
395 InvalidCandidateBit,
397 MissingReducedModel,
399 UnsatisfiedMembership,
401 WorldViewConstraintViolation,
403}
404
405impl EpistemicGpuRejectionReason {
406 pub const fn code(self) -> u32 {
408 match self {
409 Self::InactiveWorld => 2,
410 Self::InvalidCandidateBit => 3,
411 Self::MissingReducedModel => 4,
412 Self::UnsatisfiedMembership => 5,
413 Self::WorldViewConstraintViolation => 6,
414 }
415 }
416
417 pub fn from_code(code: u32) -> Result<Self> {
419 match code {
420 2 => Ok(Self::InactiveWorld),
421 3 => Ok(Self::InvalidCandidateBit),
422 4 => Ok(Self::MissingReducedModel),
423 5 => Ok(Self::UnsatisfiedMembership),
424 6 => Ok(Self::WorldViewConstraintViolation),
425 other => Err(XlogError::UnsupportedEpistemicConstruct {
426 construct: "epistemic GPU rejection reason".to_string(),
427 context: format!("unknown device rejection code {other}"),
428 }),
429 }
430 }
431}
432
433#[derive(Debug, Clone, PartialEq, Eq)]
435pub struct EpistemicGpuSemanticTrace {
436 pub generated_candidates: usize,
438 pub guesses: usize,
440 pub propagated_candidates: usize,
442 pub pruned_candidates: usize,
444 pub tested_candidates: usize,
446 pub reduced_model_slots_checked: usize,
448 pub accepted_candidates: usize,
450 pub accepted_candidate_indices: Vec<usize>,
452 pub accepted_world_views: usize,
454 pub rejected_candidates: usize,
456 pub rejected_candidate_indices: Vec<usize>,
458 pub rejection_reasons: Vec<u32>,
460 pub constraint_violation_indices: Vec<Option<u32>>,
466 pub rejection_reason_metadata_bytes: u64,
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq)]
472pub struct EpistemicGpuModelMembershipTrace {
473 pub literal_count: usize,
475 pub candidates_checked: usize,
477 pub reduction_count: usize,
479 pub models_per_reduction: usize,
481 pub model_membership_bytes_written: usize,
483 pub output_row_count_device_reads: u32,
485 pub tuple_source_row_count_device_reads: u32,
487 pub tuple_source_key_column_device_reads: u32,
489 pub rejection_reason_slots_checked: usize,
491 pub membership_source: EpistemicGpuModelMembershipSource,
493 pub kernel_launches: u32,
495 pub host_write_ops: u32,
497 pub kernel_timing: EpistemicGpuKernelTimingTrace,
499}
500
501#[derive(Debug, Clone, Copy, PartialEq, Eq)]
503pub enum EpistemicGpuModelMembershipSource {
504 ReducedOutputRowCountOnly,
506 StableModelTupleBuffer,
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, Eq)]
512pub struct EpistemicGpuWorldViewValidationTrace {
513 pub literal_count: usize,
515 pub candidates_checked: usize,
517 pub reduction_count: usize,
519 pub models_per_reduction: usize,
521 pub model_membership_bytes_checked: usize,
523 pub world_view_slots_checked: usize,
525 pub rejection_reason_slots_written: usize,
527 pub kernel_launches: u32,
529 pub host_write_ops: u32,
531 pub kernel_timing: EpistemicGpuKernelTimingTrace,
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
543pub struct EpistemicGpuConstraintWorldViewValidationTrace {
544 pub constraint_count: usize,
546 pub constraint_literal_refs: usize,
548 pub candidates_checked: usize,
550 pub rejection_reason_slots_written: usize,
552 pub kernel_launches: u32,
554 pub host_write_ops: u32,
556 pub kernel_timing: EpistemicGpuKernelTimingTrace,
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
562pub struct EpistemicGpuPropagationTrace {
563 pub literal_count: usize,
565 pub propagated_candidates: usize,
567 pub world_view_bytes_written: usize,
569 pub rejection_reason_slots_written: usize,
571 pub kernel_launches: u32,
573 pub host_write_ops: u32,
575 pub kernel_timing: EpistemicGpuKernelTimingTrace,
577}
578
579impl EpistemicGpuKernelTimingTrace {
580 pub const fn unrecorded() -> Self {
582 Self {
583 cuda_event_pairs: 0,
584 timing_sync_ops: 0,
585 kernel_elapsed_nanos: 0,
586 }
587 }
588
589 pub fn from_cuda_elapsed_ms(elapsed_ms: f32) -> Result<Self> {
591 if !elapsed_ms.is_finite() || elapsed_ms < 0.0 {
592 return Err(XlogError::Execution(format!(
593 "invalid epistemic GPU kernel elapsed time: {elapsed_ms}"
594 )));
595 }
596 let elapsed_nanos = ((elapsed_ms as f64) * 1_000_000.0).round();
597 if elapsed_nanos >= u64::MAX as f64 {
598 return Err(XlogError::UnsupportedEpistemicConstruct {
599 construct: "epistemic GPU kernel timing trace".to_string(),
600 context: format!(
601 "CUDA elapsed time {elapsed_ms}ms exceeds the u64 nanosecond trace counter"
602 ),
603 });
604 }
605
606 Ok(Self {
607 cuda_event_pairs: 1,
608 timing_sync_ops: 1,
609 kernel_elapsed_nanos: elapsed_nanos as u64,
610 })
611 }
612
613 pub const fn is_recorded(&self) -> bool {
615 self.cuda_event_pairs > 0 && self.timing_sync_ops > 0
616 }
617
618 pub fn saturating_add(self, other: Self) -> Self {
620 Self {
621 cuda_event_pairs: self.cuda_event_pairs.saturating_add(other.cuda_event_pairs),
622 timing_sync_ops: self.timing_sync_ops.saturating_add(other.timing_sync_ops),
623 kernel_elapsed_nanos: self
624 .kernel_elapsed_nanos
625 .saturating_add(other.kernel_elapsed_nanos),
626 }
627 }
628
629 pub fn checked_add(self, other: Self) -> Result<Self> {
631 Ok(Self {
632 cuda_event_pairs: self
633 .cuda_event_pairs
634 .checked_add(other.cuda_event_pairs)
635 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
636 construct: "epistemic GPU kernel timing trace".to_string(),
637 context: format!(
638 "CUDA event-pair counter overflowed while adding {} to {}",
639 other.cuda_event_pairs, self.cuda_event_pairs
640 ),
641 })?,
642 timing_sync_ops: self
643 .timing_sync_ops
644 .checked_add(other.timing_sync_ops)
645 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
646 construct: "epistemic GPU kernel timing trace".to_string(),
647 context: format!(
648 "CUDA timing-sync counter overflowed while adding {} to {}",
649 other.timing_sync_ops, self.timing_sync_ops
650 ),
651 })?,
652 kernel_elapsed_nanos: self
653 .kernel_elapsed_nanos
654 .checked_add(other.kernel_elapsed_nanos)
655 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
656 construct: "epistemic GPU kernel timing trace".to_string(),
657 context: format!(
658 "kernel elapsed-time counter overflowed while adding {} to {}",
659 other.kernel_elapsed_nanos, self.kernel_elapsed_nanos
660 ),
661 })?,
662 })
663 }
664
665 pub fn sum(traces: impl IntoIterator<Item = Self>) -> Self {
667 traces
668 .into_iter()
669 .fold(Self::unrecorded(), Self::saturating_add)
670 }
671
672 pub fn checked_sum(traces: impl IntoIterator<Item = Self>) -> Result<Self> {
674 traces
675 .into_iter()
676 .try_fold(Self::unrecorded(), Self::checked_add)
677 }
678}
679
680impl EpistemicGpuCandidateGenerationTrace {
681 pub fn for_counts(literal_count: usize, candidate_count: usize) -> Result<Self> {
683 require_positive(literal_count, "epistemic GPU candidate literals")?;
684 require_positive(candidate_count, "epistemic GPU candidate count")?;
685 if literal_count > 31 {
686 return Err(XlogError::UnsupportedEpistemicConstruct {
687 construct: "epistemic GPU candidate generation".to_string(),
688 context: format!("literal count {literal_count} exceeds 31-bit candidate mask"),
689 });
690 }
691 if candidate_count > (1usize << literal_count) {
692 return Err(capacity_exceeded(
693 "epistemic GPU candidate count",
694 candidate_count as u64,
695 (1usize << literal_count) as u64,
696 "candidates",
697 ));
698 }
699
700 let candidate_assumption_bytes = checked_product(literal_count, candidate_count)?;
701 require_u32_launch_bound(
702 candidate_assumption_bytes,
703 "epistemic GPU candidate generation launch",
704 )?;
705
706 Ok(Self {
707 literal_count,
708 generated_candidates: candidate_count,
709 candidate_assumption_bytes,
710 kernel_launches: 1,
711 host_write_ops: 0,
712 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
713 })
714 }
715
716 pub const fn with_kernel_timing(
718 mut self,
719 kernel_timing: EpistemicGpuKernelTimingTrace,
720 ) -> Self {
721 self.kernel_timing = kernel_timing;
722 self
723 }
724}
725
726impl EpistemicGpuCandidateValidationTrace {
727 pub fn for_counts(literal_count: usize, candidate_count: usize) -> Result<Self> {
729 require_positive(literal_count, "epistemic GPU candidate validation literals")?;
730 require_positive(
731 candidate_count,
732 "epistemic GPU candidate validation candidates",
733 )?;
734 require_u32_launch_dimensions(
735 &[literal_count, candidate_count],
736 "epistemic GPU validation launch",
737 )?;
738 let candidate_assumption_bytes_checked = checked_product(literal_count, candidate_count)?;
739
740 Ok(Self {
741 literal_count,
742 validated_candidates: candidate_count,
743 candidate_assumption_bytes_checked,
744 world_view_bytes_checked: candidate_count,
745 rejection_reason_slots_written: candidate_count,
746 kernel_launches: 1,
747 host_write_ops: 0,
748 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
749 })
750 }
751
752 pub const fn with_kernel_timing(
754 mut self,
755 kernel_timing: EpistemicGpuKernelTimingTrace,
756 ) -> Self {
757 self.kernel_timing = kernel_timing;
758 self
759 }
760
761 pub fn require_matches_candidate_generation(
763 &self,
764 construct: &str,
765 candidate_generation: &EpistemicGpuCandidateGenerationTrace,
766 ) -> Result<()> {
767 let expected_world_view_bytes = checked_product(
768 world_view_bitset_bytes_per_candidate(candidate_generation.literal_count)?,
769 candidate_generation.generated_candidates,
770 )?;
771 if self.literal_count != candidate_generation.literal_count
772 || self.validated_candidates != candidate_generation.generated_candidates
773 || self.candidate_assumption_bytes_checked
774 != candidate_generation.candidate_assumption_bytes
775 || self.world_view_bytes_checked != expected_world_view_bytes
776 || self.rejection_reason_slots_written != candidate_generation.generated_candidates
777 {
778 return Err(XlogError::UnsupportedEpistemicConstruct {
779 construct: construct.to_string(),
780 context: format!(
781 "candidate validation trace does not match generated GPU candidates: \
782 literals={}/{} candidates={}/{} candidate_bytes={}/{} \
783 world_view_bytes={}/{} rejection_slots={}/{}",
784 self.literal_count,
785 candidate_generation.literal_count,
786 self.validated_candidates,
787 candidate_generation.generated_candidates,
788 self.candidate_assumption_bytes_checked,
789 candidate_generation.candidate_assumption_bytes,
790 self.world_view_bytes_checked,
791 expected_world_view_bytes,
792 self.rejection_reason_slots_written,
793 candidate_generation.generated_candidates
794 ),
795 });
796 }
797
798 Ok(())
799 }
800}
801
802impl EpistemicGpuMaterializationTrace {
803 pub fn for_count(candidate_count: usize) -> Result<Self> {
805 require_positive(candidate_count, "epistemic GPU materialization candidates")?;
806 require_u32_launch_bound(candidate_count, "epistemic GPU materialization launch")?;
807
808 Ok(Self {
809 materialized_candidates: candidate_count,
810 world_view_slots_written: candidate_count,
811 kernel_launches: 1,
812 host_write_ops: 0,
813 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
814 })
815 }
816
817 pub const fn with_kernel_timing(
819 mut self,
820 kernel_timing: EpistemicGpuKernelTimingTrace,
821 ) -> Self {
822 self.kernel_timing = kernel_timing;
823 self
824 }
825}
826
827impl EpistemicGpuFinalResultMaterializationTrace {
828 pub fn for_count(candidate_count: usize) -> Result<Self> {
830 require_positive(
831 candidate_count,
832 "epistemic GPU final-result materialization candidates",
833 )?;
834 require_u32_launch_bound(candidate_count, "epistemic GPU final-result launch")?;
835
836 Ok(Self {
837 materialized_candidates: candidate_count,
838 output_row_count_device_reads: 1,
839 world_view_slots_written: candidate_count,
840 kernel_launches: 1,
841 host_write_ops: 0,
842 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
843 })
844 }
845
846 pub const fn with_kernel_timing(
848 mut self,
849 kernel_timing: EpistemicGpuKernelTimingTrace,
850 ) -> Self {
851 self.kernel_timing = kernel_timing;
852 self
853 }
854}
855
856impl EpistemicGpuFinalTupleMaterializationTrace {
857 pub fn for_counts(
859 output_column_count: usize,
860 output_row_capacity: usize,
861 tuple_bytes_capacity: usize,
862 literal_count: usize,
863 candidate_count: usize,
864 reduction_count: usize,
865 models_per_reduction: usize,
866 ) -> Result<Self> {
867 if output_column_count > u32::MAX as usize {
868 return Err(capacity_exceeded(
869 "epistemic GPU final-tuple output columns",
870 output_column_count as u64,
871 u32::MAX as u64,
872 "columns",
873 ));
874 }
875 require_u32_launch_bound(output_row_capacity, "epistemic GPU final-tuple output rows")?;
876 require_positive(literal_count, "epistemic GPU final-tuple literals")?;
877 require_positive(candidate_count, "epistemic GPU final-tuple candidates")?;
878 require_positive(reduction_count, "epistemic GPU final-tuple reductions")?;
879 require_positive(models_per_reduction, "epistemic GPU final-tuple models")?;
880 let model_membership_bytes_checked = checked_product(
881 checked_product(
882 checked_product(candidate_count, reduction_count)?,
883 models_per_reduction,
884 )?,
885 literal_count,
886 )?;
887 require_u32_launch_bound(
888 model_membership_bytes_checked,
889 "epistemic GPU final-tuple membership launch",
890 )?;
891 let output_row_count_device_reads = checked_sum(output_column_count, 1)?;
892 let kernel_launches = checked_sum(output_row_count_device_reads, 1)?;
893 if kernel_launches > u32::MAX as usize {
894 return Err(capacity_exceeded(
895 "epistemic GPU final-tuple kernel launches",
896 kernel_launches as u64,
897 u32::MAX as u64,
898 "launches",
899 ));
900 }
901
902 Ok(Self {
903 output_column_count,
904 output_row_capacity,
905 tuple_bytes_capacity,
906 output_row_count_device_reads: output_row_count_device_reads as u32,
907 model_membership_bytes_checked,
908 bounded_model_slots_per_reduction: models_per_reduction,
909 row_specific_membership_row_capacity: 0,
910 row_filter_row_capacity_outside_model_slot_window: 0,
911 world_view_slots_checked: candidate_count,
912 row_filter_count: 0,
913 negated_row_filter_count: 0,
914 final_row_count_device_writes: 1,
915 kernel_launches: kernel_launches as u32,
916 host_write_ops: 0,
917 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
918 })
919 }
920
921 pub const fn with_kernel_timing(
923 mut self,
924 kernel_timing: EpistemicGpuKernelTimingTrace,
925 ) -> Self {
926 self.kernel_timing = kernel_timing;
927 self
928 }
929
930 pub fn with_row_filter_counts(
932 mut self,
933 row_filter_count: usize,
934 negated_row_filter_count: usize,
935 ) -> Result<Self> {
936 if negated_row_filter_count > row_filter_count {
937 return Err(capacity_exceeded(
938 "epistemic GPU final-tuple negated row filters",
939 negated_row_filter_count as u64,
940 row_filter_count as u64,
941 "row filters",
942 ));
943 }
944 self.row_filter_count = row_filter_count;
945 self.negated_row_filter_count = negated_row_filter_count;
946 if row_filter_count > 0 {
947 self.row_specific_membership_row_capacity = self
948 .output_row_capacity
949 .min(self.bounded_model_slots_per_reduction);
950 self.row_filter_row_capacity_outside_model_slot_window = self
951 .output_row_capacity
952 .saturating_sub(self.row_specific_membership_row_capacity);
953 }
954 Ok(self)
955 }
956
957 pub fn require_row_filter_materialization_evidence(
959 &self,
960 construct: &str,
961 final_output_rows: usize,
962 ) -> Result<()> {
963 if final_output_rows > self.output_row_capacity {
964 return Err(XlogError::UnsupportedEpistemicConstruct {
965 construct: construct.to_string(),
966 context: format!(
967 "final tuple materialization reported {} logical rows for output row \
968 capacity {}",
969 final_output_rows, self.output_row_capacity
970 ),
971 });
972 }
973 if self.negated_row_filter_count > self.row_filter_count {
974 return Err(XlogError::UnsupportedEpistemicConstruct {
975 construct: construct.to_string(),
976 context: format!(
977 "row-filtered final tuple materialization reported {} negated row filters \
978 for {} total row filters",
979 self.negated_row_filter_count, self.row_filter_count
980 ),
981 });
982 }
983 if self.row_filter_count == 0 {
984 if self.row_specific_membership_row_capacity != 0
985 || self.row_filter_row_capacity_outside_model_slot_window != 0
986 {
987 return Err(XlogError::UnsupportedEpistemicConstruct {
988 construct: construct.to_string(),
989 context: format!(
990 "final tuple materialization without row filters reported row-filter \
991 coverage row_specific_capacity={} fallback_capacity={}",
992 self.row_specific_membership_row_capacity,
993 self.row_filter_row_capacity_outside_model_slot_window
994 ),
995 });
996 }
997 return Ok(());
998 }
999
1000 if final_output_rows == 0 && self.output_row_capacity == 0 {
1008 return Ok(());
1009 }
1010
1011 let covered_row_capacity = checked_sum(
1012 self.row_specific_membership_row_capacity,
1013 self.row_filter_row_capacity_outside_model_slot_window,
1014 )?;
1015 if self.output_row_capacity == 0
1016 || self.row_specific_membership_row_capacity == 0
1017 || covered_row_capacity != self.output_row_capacity
1018 {
1019 return Err(XlogError::UnsupportedEpistemicConstruct {
1020 construct: construct.to_string(),
1021 context: format!(
1022 "row-filtered final tuple materialization requires GPU row-filter coverage, \
1023 got row_filters={} final_output_rows={} output_row_capacity={} \
1024 row_specific_capacity={} fallback_capacity={} model_slots_per_reduction={}",
1025 self.row_filter_count,
1026 final_output_rows,
1027 self.output_row_capacity,
1028 self.row_specific_membership_row_capacity,
1029 self.row_filter_row_capacity_outside_model_slot_window,
1030 self.bounded_model_slots_per_reduction
1031 ),
1032 });
1033 }
1034
1035 let fallback_rows =
1036 final_output_rows.saturating_sub(self.row_specific_membership_row_capacity);
1037 if fallback_rows > self.row_filter_row_capacity_outside_model_slot_window {
1038 return Err(XlogError::UnsupportedEpistemicConstruct {
1039 construct: construct.to_string(),
1040 context: format!(
1041 "row-filtered final tuple materialization has {} logical rows beyond the \
1042 row-specific model-slot window but only {} fallback row-filter capacity",
1043 fallback_rows, self.row_filter_row_capacity_outside_model_slot_window
1044 ),
1045 });
1046 }
1047 Ok(())
1048 }
1049}
1050
1051impl EpistemicGpuTransferBudgetTrace {
1052 pub fn from_host_transfer_stats(
1054 candidate_count: usize,
1055 before: HostTransferStats,
1056 after: HostTransferStats,
1057 ) -> Result<Self> {
1058 Self::from_host_transfer_stats_with_launch_metadata(
1059 candidate_count,
1060 before,
1061 after,
1062 HostLaunchMetadataTransferStats::default(),
1063 HostLaunchMetadataTransferStats::default(),
1064 )
1065 }
1066
1067 pub fn from_host_transfer_stats_with_launch_metadata(
1070 candidate_count: usize,
1071 before: HostTransferStats,
1072 after: HostTransferStats,
1073 launch_metadata_before: HostLaunchMetadataTransferStats,
1074 launch_metadata_after: HostLaunchMetadataTransferStats,
1075 ) -> Result<Self> {
1076 require_positive(candidate_count, "epistemic GPU transfer-budget candidates")?;
1077
1078 let tracked_dtoh_bytes =
1079 transfer_counter_delta("dtoh_bytes", before.dtoh_bytes, after.dtoh_bytes)?;
1080 let tracked_data_plane_htod_bytes =
1081 transfer_counter_delta("htod_bytes", before.htod_bytes, after.htod_bytes)?;
1082 let tracked_dtoh_calls =
1083 transfer_counter_delta("dtoh_calls", before.dtoh_calls, after.dtoh_calls)?;
1084 let tracked_data_plane_htod_calls =
1085 transfer_counter_delta("htod_calls", before.htod_calls, after.htod_calls)?;
1086 let tracked_launch_metadata_htod_bytes = transfer_counter_delta(
1087 "launch_metadata_htod_bytes",
1088 launch_metadata_before.htod_bytes,
1089 launch_metadata_after.htod_bytes,
1090 )?;
1091 let tracked_launch_metadata_htod_calls = transfer_counter_delta(
1092 "launch_metadata_htod_calls",
1093 launch_metadata_before.htod_calls,
1094 launch_metadata_after.htod_calls,
1095 )?;
1096 let tracked_aggregate_htod_bytes = tracked_data_plane_htod_bytes
1097 .checked_add(tracked_launch_metadata_htod_bytes)
1098 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1099 construct: "epistemic GPU transfer budget".to_string(),
1100 context: format!(
1101 "aggregate H2D bytes overflowed while adding launch metadata: \
1102 data_plane_htod_bytes={tracked_data_plane_htod_bytes}, \
1103 launch_metadata_htod_bytes={tracked_launch_metadata_htod_bytes}"
1104 ),
1105 })?;
1106 let tracked_aggregate_htod_calls = tracked_data_plane_htod_calls
1107 .checked_add(tracked_launch_metadata_htod_calls)
1108 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1109 construct: "epistemic GPU transfer budget".to_string(),
1110 context: format!(
1111 "aggregate H2D calls overflowed while adding launch metadata: \
1112 data_plane_htod_calls={tracked_data_plane_htod_calls}, \
1113 launch_metadata_htod_calls={tracked_launch_metadata_htod_calls}"
1114 ),
1115 })?;
1116
1117 if tracked_launch_metadata_htod_bytes != 0 && tracked_launch_metadata_htod_calls == 0 {
1118 return Err(XlogError::UnsupportedEpistemicConstruct {
1119 construct: "epistemic GPU transfer budget".to_string(),
1120 context: format!(
1121 "launch metadata H2D bytes require matching H2D calls, got bytes={} calls=0",
1122 tracked_launch_metadata_htod_bytes
1123 ),
1124 });
1125 }
1126 if tracked_launch_metadata_htod_calls != 0 && tracked_launch_metadata_htod_bytes == 0 {
1127 return Err(XlogError::UnsupportedEpistemicConstruct {
1128 construct: "epistemic GPU transfer budget".to_string(),
1129 context: format!(
1130 "launch metadata H2D calls require matching payload bytes, got calls={} bytes=0",
1131 tracked_launch_metadata_htod_calls
1132 ),
1133 });
1134 }
1135
1136 if tracked_dtoh_bytes != 0
1137 || tracked_data_plane_htod_bytes != 0
1138 || tracked_dtoh_calls != 0
1139 || tracked_data_plane_htod_calls != 0
1140 {
1141 return Err(XlogError::UnsupportedEpistemicConstruct {
1142 construct: "epistemic GPU transfer budget".to_string(),
1143 context: format!(
1144 "tracked host transfer in GPU hot path: tracked data-plane host transfer: \
1145 dtoh_bytes={tracked_dtoh_bytes}, \
1146 data_plane_htod_bytes={tracked_data_plane_htod_bytes}, \
1147 dtoh_calls={tracked_dtoh_calls}, \
1148 data_plane_htod_calls={tracked_data_plane_htod_calls}, \
1149 launch_metadata_htod_bytes={tracked_launch_metadata_htod_bytes}, \
1150 launch_metadata_htod_calls={tracked_launch_metadata_htod_calls}"
1151 ),
1152 });
1153 }
1154
1155 Ok(Self {
1156 candidate_count,
1157 tracked_dtoh_bytes,
1158 tracked_htod_bytes: tracked_data_plane_htod_bytes,
1159 tracked_dtoh_calls,
1160 tracked_htod_calls: tracked_data_plane_htod_calls,
1161 tracked_aggregate_htod_bytes,
1162 tracked_aggregate_htod_calls,
1163 tracked_launch_metadata_htod_bytes,
1164 tracked_launch_metadata_htod_calls,
1165 tracked_data_plane_htod_bytes,
1166 tracked_data_plane_htod_calls,
1167 per_candidate_host_round_trips: 0,
1168 })
1169 }
1170}
1171
1172impl EpistemicGpuFinalResultTransferTrace {
1173 pub fn from_final_output(
1175 provider: &xlog_cuda::CudaKernelProvider,
1176 final_output: &CudaBuffer,
1177 ) -> Result<Self> {
1178 let row_count_was_cached = final_output.cached_row_count().is_some();
1179 let final_output_rows = provider.device_row_count(final_output)?;
1180 let final_output_column_count = final_output.arity();
1181 let final_output_row_width_bytes = final_output.schema().row_size_bytes();
1182 let final_output_payload_bytes =
1183 checked_product(final_output_rows, final_output_row_width_bytes)? as u64;
1184
1185 Ok(Self {
1186 final_output_rows,
1187 final_output_column_count,
1188 final_output_row_width_bytes,
1189 final_output_payload_bytes,
1190 row_count_device_reads: u32::from(!row_count_was_cached),
1191 tracked_data_plane_dtoh_calls: 0,
1192 tracked_data_plane_dtoh_bytes: 0,
1193 })
1194 }
1195
1196 pub fn require_matches_final_output(
1198 &self,
1199 construct: &str,
1200 final_output: &CudaBuffer,
1201 ) -> Result<()> {
1202 let Some(cached_rows) = final_output.cached_row_count() else {
1203 return Err(XlogError::UnsupportedEpistemicConstruct {
1204 construct: construct.to_string(),
1205 context:
1206 "final-result transfer certification requires cached device final row count"
1207 .to_string(),
1208 });
1209 };
1210 let logical_rows =
1211 validate_logical_row_count(final_output.num_rows(), cached_rows as usize).map_err(
1212 |err| XlogError::UnsupportedEpistemicConstruct {
1213 construct: construct.to_string(),
1214 context: format!("invalid final-output logical row count: {err}"),
1215 },
1216 )?;
1217 let row_width = final_output.schema().row_size_bytes();
1218 let payload_bytes = checked_product(logical_rows, row_width)? as u64;
1219 if self.final_output_rows != logical_rows
1220 || self.final_output_column_count != final_output.arity()
1221 || self.final_output_row_width_bytes != row_width
1222 || self.final_output_payload_bytes != payload_bytes
1223 {
1224 return Err(XlogError::UnsupportedEpistemicConstruct {
1225 construct: construct.to_string(),
1226 context: format!(
1227 "final-result transfer trace does not match final device output: rows={}/{} \
1228 columns={}/{} row_width={}/{} payload_bytes={}/{}",
1229 self.final_output_rows,
1230 logical_rows,
1231 self.final_output_column_count,
1232 final_output.arity(),
1233 self.final_output_row_width_bytes,
1234 row_width,
1235 self.final_output_payload_bytes,
1236 payload_bytes
1237 ),
1238 });
1239 }
1240 if self.row_count_device_reads > 1 {
1241 return Err(XlogError::UnsupportedEpistemicConstruct {
1242 construct: construct.to_string(),
1243 context: format!(
1244 "final-result transfer reads one device row-count scalar at most, got {}",
1245 self.row_count_device_reads
1246 ),
1247 });
1248 }
1249
1250 Ok(())
1251 }
1252}
1253
1254impl EpistemicGpuSemanticTrace {
1255 pub fn require_matches_execution_traces(
1257 &self,
1258 construct: &str,
1259 candidate_generation: &EpistemicGpuCandidateGenerationTrace,
1260 propagation: &EpistemicGpuPropagationTrace,
1261 model_membership: &EpistemicGpuModelMembershipTrace,
1262 world_view_validation: &EpistemicGpuWorldViewValidationTrace,
1263 ) -> Result<()> {
1264 let expected_pruned = self
1265 .generated_candidates
1266 .checked_sub(propagation.propagated_candidates)
1267 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1268 construct: construct.to_string(),
1269 context: format!(
1270 "semantic trace phase counts cannot propagate more candidates than were \
1271 generated: generated={} propagated={}",
1272 self.generated_candidates, propagation.propagated_candidates
1273 ),
1274 })?;
1275 let expected_reduced_model_slots = checked_product(
1276 checked_product(
1277 world_view_validation.candidates_checked,
1278 model_membership.reduction_count,
1279 )?,
1280 model_membership.models_per_reduction,
1281 )?;
1282 let expected_guesses = checked_product(
1283 candidate_generation.generated_candidates,
1284 candidate_generation.literal_count,
1285 )?;
1286 if self.generated_candidates != candidate_generation.generated_candidates
1287 || self.guesses != expected_guesses
1288 || self.propagated_candidates != propagation.propagated_candidates
1289 || self.pruned_candidates != expected_pruned
1290 || self.tested_candidates != world_view_validation.candidates_checked
1291 || self.reduced_model_slots_checked != expected_reduced_model_slots
1292 {
1293 return Err(XlogError::UnsupportedEpistemicConstruct {
1294 construct: construct.to_string(),
1295 context: format!(
1296 "semantic trace phase counts must match retained GPU execution traces, got \
1297 generated={} expected_generated={} guesses={} expected_guesses={} \
1298 propagated={} expected_propagated={} pruned={} expected_pruned={} \
1299 tested={} expected_tested={} reduced_model_slots={} \
1300 expected_reduced_model_slots={}",
1301 self.generated_candidates,
1302 candidate_generation.generated_candidates,
1303 self.guesses,
1304 expected_guesses,
1305 self.propagated_candidates,
1306 propagation.propagated_candidates,
1307 self.pruned_candidates,
1308 expected_pruned,
1309 self.tested_candidates,
1310 world_view_validation.candidates_checked,
1311 self.reduced_model_slots_checked,
1312 expected_reduced_model_slots
1313 ),
1314 });
1315 }
1316
1317 Ok(())
1318 }
1319
1320 pub fn require_rejection_metadata_accounting(&self, construct: &str) -> Result<()> {
1322 let expected_metadata_bytes =
1323 checked_product(self.generated_candidates, std::mem::size_of::<u32>())? as u64;
1324 if self.rejection_reason_metadata_bytes != expected_metadata_bytes {
1325 return Err(XlogError::UnsupportedEpistemicConstruct {
1326 construct: construct.to_string(),
1327 context: format!(
1328 "semantic trace rejection metadata accounting must match the bounded device \
1329 rejection-buffer read, got bytes={} expected_bytes={}",
1330 self.rejection_reason_metadata_bytes, expected_metadata_bytes
1331 ),
1332 });
1333 }
1334
1335 Ok(())
1336 }
1337
1338 pub fn require_candidate_index_partition(&self, construct: &str) -> Result<()> {
1340 let accounted_candidates = self.accepted_candidates.checked_add(self.rejected_candidates).ok_or_else(|| {
1341 XlogError::UnsupportedEpistemicConstruct {
1342 construct: construct.to_string(),
1343 context: format!(
1344 "semantic trace candidate index partition accounting overflowed: accepted={} rejected={}",
1345 self.accepted_candidates, self.rejected_candidates
1346 ),
1347 }
1348 })?;
1349 if self.accepted_candidate_indices.len() != self.accepted_candidates
1350 || self.rejected_candidate_indices.len() != self.rejected_candidates
1351 || self.accepted_world_views != self.accepted_candidates
1352 || accounted_candidates != self.generated_candidates
1353 {
1354 return Err(XlogError::UnsupportedEpistemicConstruct {
1355 construct: construct.to_string(),
1356 context: format!(
1357 "semantic trace candidate index partition requires counts and index vectors \
1358 to match generated candidates, got generated={} accepted={} \
1359 accepted_indices={} accepted_world_views={} rejected={} rejected_indices={}",
1360 self.generated_candidates,
1361 self.accepted_candidates,
1362 self.accepted_candidate_indices.len(),
1363 self.accepted_world_views,
1364 self.rejected_candidates,
1365 self.rejected_candidate_indices.len()
1366 ),
1367 });
1368 }
1369 if self.rejection_reasons.len() != self.rejected_candidates {
1370 return Err(XlogError::UnsupportedEpistemicConstruct {
1371 construct: construct.to_string(),
1372 context: format!(
1373 "semantic trace rejection reason count must match rejected candidates, got \
1374 reasons={} rejected={}",
1375 self.rejection_reasons.len(),
1376 self.rejected_candidates
1377 ),
1378 });
1379 }
1380 self.typed_rejection_reasons()?;
1381
1382 let mut seen = BTreeSet::new();
1383 for (kind, indices) in [
1384 ("accepted", self.accepted_candidate_indices.as_slice()),
1385 ("rejected", self.rejected_candidate_indices.as_slice()),
1386 ] {
1387 for &index in indices {
1388 if index >= self.generated_candidates {
1389 return Err(XlogError::UnsupportedEpistemicConstruct {
1390 construct: construct.to_string(),
1391 context: format!(
1392 "semantic trace candidate index partition has out-of-range {kind} \
1393 index {index} for generated candidate count {}",
1394 self.generated_candidates
1395 ),
1396 });
1397 }
1398 if !seen.insert(index) {
1399 return Err(XlogError::UnsupportedEpistemicConstruct {
1400 construct: construct.to_string(),
1401 context: format!(
1402 "semantic trace candidate index partition contains duplicate \
1403 candidate index {index}"
1404 ),
1405 });
1406 }
1407 }
1408 }
1409 if seen.len() != self.generated_candidates {
1410 return Err(XlogError::UnsupportedEpistemicConstruct {
1411 construct: construct.to_string(),
1412 context: format!(
1413 "semantic trace candidate index partition covers {} of {} generated \
1414 candidates",
1415 seen.len(),
1416 self.generated_candidates
1417 ),
1418 });
1419 }
1420
1421 Ok(())
1422 }
1423
1424 pub fn typed_rejection_reasons(&self) -> Result<Vec<EpistemicGpuRejectionReason>> {
1426 self.rejection_reasons
1427 .iter()
1428 .copied()
1429 .map(EpistemicGpuRejectionReason::from_code)
1430 .collect()
1431 }
1432
1433 pub fn from_device_rejection_reasons(
1435 provider: &xlog_cuda::CudaKernelProvider,
1436 workspace: &EpistemicGpuWorkspace,
1437 candidate_generation: &EpistemicGpuCandidateGenerationTrace,
1438 propagation: &EpistemicGpuPropagationTrace,
1439 model_membership: &EpistemicGpuModelMembershipTrace,
1440 world_view_validation: &EpistemicGpuWorldViewValidationTrace,
1441 ) -> Result<Self> {
1442 let candidate_count = candidate_generation.generated_candidates;
1443 require_positive(candidate_count, "epistemic GPU semantic-trace candidates")?;
1444 if candidate_count > workspace.layout.rejection_reason_slots {
1445 return Err(capacity_exceeded(
1446 "epistemic GPU semantic-trace rejection metadata",
1447 candidate_count as u64,
1448 workspace.layout.rejection_reason_slots as u64,
1449 "rejection slots",
1450 ));
1451 }
1452 if propagation.literal_count != candidate_generation.literal_count
1453 || model_membership.literal_count != candidate_generation.literal_count
1454 || world_view_validation.literal_count != candidate_generation.literal_count
1455 {
1456 return Err(XlogError::UnsupportedEpistemicConstruct {
1457 construct: "epistemic GPU semantic trace".to_string(),
1458 context: format!(
1459 "semantic trace requires all GPU stages to agree on literal count, got \
1460 generated={} propagated={} membership={} validation={}",
1461 candidate_generation.literal_count,
1462 propagation.literal_count,
1463 model_membership.literal_count,
1464 world_view_validation.literal_count
1465 ),
1466 });
1467 }
1468 let pruned_candidates = candidate_count
1469 .checked_sub(propagation.propagated_candidates)
1470 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1471 construct: "epistemic GPU semantic trace".to_string(),
1472 context: format!(
1473 "semantic trace cannot prune more candidates than were generated: \
1474 generated={} propagated={}",
1475 candidate_count, propagation.propagated_candidates
1476 ),
1477 })?;
1478 if propagation.rejection_reason_slots_written < candidate_count {
1479 return Err(XlogError::UnsupportedEpistemicConstruct {
1480 construct: "epistemic GPU semantic trace".to_string(),
1481 context: format!(
1482 "semantic trace requires rejection metadata for every generated candidate, \
1483 got generated={} rejection_slots_initialized={}",
1484 candidate_count, propagation.rejection_reason_slots_written
1485 ),
1486 });
1487 }
1488 if model_membership.candidates_checked != candidate_count
1489 || world_view_validation.candidates_checked != candidate_count
1490 {
1491 return Err(XlogError::UnsupportedEpistemicConstruct {
1492 construct: "epistemic GPU semantic trace".to_string(),
1493 context: format!(
1494 "semantic trace requires GPU validation coverage for every generated \
1495 candidate, got generated={} membership_checked={} validation_checked={}",
1496 candidate_count,
1497 model_membership.candidates_checked,
1498 world_view_validation.candidates_checked
1499 ),
1500 });
1501 }
1502 if model_membership.reduction_count != world_view_validation.reduction_count
1503 || model_membership.models_per_reduction != world_view_validation.models_per_reduction
1504 {
1505 return Err(XlogError::UnsupportedEpistemicConstruct {
1506 construct: "epistemic GPU semantic trace".to_string(),
1507 context: format!(
1508 "semantic trace requires model-membership and world-view validation layouts \
1509 to match, got membership_reductions={} validation_reductions={} \
1510 membership_models_per_reduction={} validation_models_per_reduction={}",
1511 model_membership.reduction_count,
1512 world_view_validation.reduction_count,
1513 model_membership.models_per_reduction,
1514 world_view_validation.models_per_reduction
1515 ),
1516 });
1517 }
1518
1519 let raw_rejection_reasons = provider
1520 .dtoh_small_metadata_untracked(&workspace.rejection_reasons, candidate_count)?;
1521 let rejection_reason_metadata_bytes =
1522 checked_product(raw_rejection_reasons.len(), std::mem::size_of::<u32>())? as u64;
1523 let raw_constraint_violation_index = provider.dtoh_small_metadata_untracked(
1527 &workspace.constraint_violation_index,
1528 candidate_count,
1529 )?;
1530 let constraint_violation_code =
1531 EpistemicGpuRejectionReason::WorldViewConstraintViolation.code();
1532 let mut accepted_candidate_indices = Vec::new();
1533 let mut rejected_candidate_indices = Vec::new();
1534 let mut rejection_reasons = Vec::new();
1535 let mut constraint_violation_indices: Vec<Option<u32>> = Vec::new();
1536 for (candidate_index, reason) in raw_rejection_reasons.into_iter().enumerate() {
1537 if reason == 0 {
1538 accepted_candidate_indices.push(candidate_index);
1539 } else {
1540 EpistemicGpuRejectionReason::from_code(reason)?;
1541 rejected_candidate_indices.push(candidate_index);
1542 rejection_reasons.push(reason);
1543 let firing = raw_constraint_violation_index
1550 .get(candidate_index)
1551 .copied()
1552 .unwrap_or(u32::MAX);
1553 if reason == constraint_violation_code && firing != u32::MAX {
1554 constraint_violation_indices.push(Some(firing));
1555 } else {
1556 constraint_violation_indices.push(None);
1557 }
1558 }
1559 }
1560 let accepted_candidates = accepted_candidate_indices.len();
1561 let rejected_candidates = rejection_reasons.len();
1562 let reduced_model_slots_checked = checked_product(
1563 checked_product(
1564 world_view_validation.candidates_checked,
1565 model_membership.reduction_count,
1566 )?,
1567 model_membership.models_per_reduction,
1568 )?;
1569 Ok(Self {
1570 generated_candidates: candidate_count,
1571 guesses: checked_product(candidate_count, candidate_generation.literal_count)?,
1572 propagated_candidates: propagation.propagated_candidates,
1573 pruned_candidates,
1574 tested_candidates: world_view_validation.candidates_checked,
1575 reduced_model_slots_checked,
1576 accepted_candidates,
1577 accepted_candidate_indices,
1578 accepted_world_views: accepted_candidates,
1579 rejected_candidates,
1580 rejected_candidate_indices,
1581 rejection_reasons,
1582 constraint_violation_indices,
1583 rejection_reason_metadata_bytes,
1584 })
1585 }
1586}
1587
1588fn transfer_counter_delta(name: &str, before: u64, after: u64) -> Result<u64> {
1589 after
1590 .checked_sub(before)
1591 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1592 construct: "epistemic GPU transfer budget".to_string(),
1593 context: format!(
1594 "host transfer counter decreased during GPU hot path: {name} before={before}, \
1595 after={after}"
1596 ),
1597 })
1598}
1599
1600impl EpistemicGpuModelMembershipTrace {
1601 pub fn for_counts(
1603 literal_count: usize,
1604 candidate_count: usize,
1605 reduction_count: usize,
1606 models_per_reduction: usize,
1607 ) -> Result<Self> {
1608 require_positive(literal_count, "epistemic GPU model-membership literals")?;
1609 require_positive(candidate_count, "epistemic GPU model-membership candidates")?;
1610 require_positive(reduction_count, "epistemic GPU model-membership reductions")?;
1611 require_positive(
1612 models_per_reduction,
1613 "epistemic GPU model-membership models",
1614 )?;
1615 let model_membership_bytes_written = checked_product(
1616 checked_product(
1617 checked_product(candidate_count, reduction_count)?,
1618 models_per_reduction,
1619 )?,
1620 literal_count,
1621 )?;
1622 require_u32_launch_bound(
1623 model_membership_bytes_written,
1624 "epistemic GPU model-membership launch",
1625 )?;
1626
1627 Ok(Self {
1628 literal_count,
1629 candidates_checked: candidate_count,
1630 reduction_count,
1631 models_per_reduction,
1632 model_membership_bytes_written,
1633 output_row_count_device_reads: 1,
1634 tuple_source_row_count_device_reads: 0,
1635 tuple_source_key_column_device_reads: 0,
1636 rejection_reason_slots_checked: candidate_count,
1637 membership_source: EpistemicGpuModelMembershipSource::ReducedOutputRowCountOnly,
1638 kernel_launches: 1,
1639 host_write_ops: 0,
1640 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
1641 })
1642 }
1643
1644 pub fn for_stable_model_tuple_sources(
1646 literal_count: usize,
1647 candidate_count: usize,
1648 reduction_count: usize,
1649 models_per_reduction: usize,
1650 tuple_source_count: usize,
1651 ) -> Result<Self> {
1652 Self::for_stable_model_tuple_sources_with_key_columns(
1653 literal_count,
1654 candidate_count,
1655 reduction_count,
1656 models_per_reduction,
1657 tuple_source_count,
1658 0,
1659 )
1660 }
1661
1662 pub fn for_stable_model_tuple_sources_with_key_columns(
1664 literal_count: usize,
1665 candidate_count: usize,
1666 reduction_count: usize,
1667 models_per_reduction: usize,
1668 tuple_source_count: usize,
1669 tuple_source_key_column_count: usize,
1670 ) -> Result<Self> {
1671 require_positive(literal_count, "epistemic GPU model-membership literals")?;
1672 require_positive(candidate_count, "epistemic GPU model-membership candidates")?;
1673 require_positive(reduction_count, "epistemic GPU model-membership reductions")?;
1674 require_positive(
1675 models_per_reduction,
1676 "epistemic GPU model-membership models",
1677 )?;
1678 require_positive(
1679 tuple_source_count,
1680 "epistemic GPU model-membership tuple sources",
1681 )?;
1682 if tuple_source_count > u32::MAX as usize {
1683 return Err(capacity_exceeded(
1684 "epistemic GPU model-membership tuple sources",
1685 tuple_source_count as u64,
1686 u32::MAX as u64,
1687 "tuple sources",
1688 ));
1689 }
1690 if tuple_source_key_column_count > u32::MAX as usize {
1691 return Err(capacity_exceeded(
1692 "epistemic GPU model-membership tuple key columns",
1693 tuple_source_key_column_count as u64,
1694 u32::MAX as u64,
1695 "key columns",
1696 ));
1697 }
1698 let model_membership_bytes_written = checked_product(
1699 checked_product(
1700 checked_product(candidate_count, reduction_count)?,
1701 models_per_reduction,
1702 )?,
1703 literal_count,
1704 )?;
1705 require_u32_launch_bound(
1706 model_membership_bytes_written,
1707 "epistemic GPU model-membership launch",
1708 )?;
1709
1710 Ok(Self {
1711 literal_count,
1712 candidates_checked: candidate_count,
1713 reduction_count,
1714 models_per_reduction,
1715 model_membership_bytes_written,
1716 output_row_count_device_reads: 0,
1717 tuple_source_row_count_device_reads: tuple_source_count as u32,
1718 tuple_source_key_column_device_reads: tuple_source_key_column_count as u32,
1719 rejection_reason_slots_checked: candidate_count,
1720 membership_source: EpistemicGpuModelMembershipSource::StableModelTupleBuffer,
1721 kernel_launches: tuple_source_count as u32,
1722 host_write_ops: 0,
1723 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
1724 })
1725 }
1726
1727 pub const fn with_kernel_timing(
1729 mut self,
1730 kernel_timing: EpistemicGpuKernelTimingTrace,
1731 ) -> Self {
1732 self.kernel_timing = kernel_timing;
1733 self
1734 }
1735
1736 pub fn require_stable_model_tuple_source(&self) -> Result<()> {
1738 if self.membership_source != EpistemicGpuModelMembershipSource::StableModelTupleBuffer {
1739 return Err(XlogError::UnsupportedEpistemicConstruct {
1740 construct: "epistemic GPU stable-model membership certification".to_string(),
1741 context: format!(
1742 "model-membership source {:?} is bounded staging only; actual reduced \
1743 stable-model tuple membership is required before returning accepted \
1744 epistemic execution",
1745 self.membership_source
1746 ),
1747 });
1748 }
1749
1750 Ok(())
1751 }
1752
1753 pub fn require_planned_tuple_key_column_reads(
1755 &self,
1756 expected_key_column_reads: usize,
1757 ) -> Result<()> {
1758 if self.tuple_source_key_column_device_reads as usize != expected_key_column_reads {
1759 return Err(XlogError::UnsupportedEpistemicConstruct {
1760 construct: "epistemic GPU stable-model membership certification".to_string(),
1761 context: format!(
1762 "model-membership tuple-key device column reads must match the planned \
1763 nonzero-arity tuple keys, got reads={} expected={}",
1764 self.tuple_source_key_column_device_reads, expected_key_column_reads
1765 ),
1766 });
1767 }
1768
1769 Ok(())
1770 }
1771}
1772
1773impl EpistemicGpuWorldViewValidationTrace {
1774 pub fn for_counts(
1776 literal_count: usize,
1777 candidate_count: usize,
1778 reduction_count: usize,
1779 models_per_reduction: usize,
1780 ) -> Result<Self> {
1781 require_positive(
1782 literal_count,
1783 "epistemic GPU world-view validation literals",
1784 )?;
1785 require_positive(
1786 candidate_count,
1787 "epistemic GPU world-view validation candidates",
1788 )?;
1789 require_positive(
1790 reduction_count,
1791 "epistemic GPU world-view validation reductions",
1792 )?;
1793 require_positive(
1794 models_per_reduction,
1795 "epistemic GPU world-view validation models",
1796 )?;
1797 let model_membership_bytes_checked = checked_product(
1798 checked_product(
1799 checked_product(candidate_count, reduction_count)?,
1800 models_per_reduction,
1801 )?,
1802 literal_count,
1803 )?;
1804 require_u32_launch_bound(
1805 model_membership_bytes_checked,
1806 "epistemic GPU world-view validation membership launch",
1807 )?;
1808
1809 Ok(Self {
1810 literal_count,
1811 candidates_checked: candidate_count,
1812 reduction_count,
1813 models_per_reduction,
1814 model_membership_bytes_checked,
1815 world_view_slots_checked: candidate_count,
1816 rejection_reason_slots_written: candidate_count,
1817 kernel_launches: 1,
1818 host_write_ops: 0,
1819 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
1820 })
1821 }
1822
1823 pub const fn with_kernel_timing(
1825 mut self,
1826 kernel_timing: EpistemicGpuKernelTimingTrace,
1827 ) -> Self {
1828 self.kernel_timing = kernel_timing;
1829 self
1830 }
1831}
1832
1833impl EpistemicGpuPropagationTrace {
1834 pub fn for_counts(literal_count: usize, candidate_count: usize) -> Result<Self> {
1836 require_positive(literal_count, "epistemic GPU propagation literals")?;
1837 require_positive(candidate_count, "epistemic GPU propagation candidates")?;
1838 require_u32_launch_dimensions(
1839 &[literal_count, candidate_count],
1840 "epistemic GPU propagation launch",
1841 )?;
1842
1843 Ok(Self {
1844 literal_count,
1845 propagated_candidates: candidate_count,
1846 world_view_bytes_written: candidate_count,
1847 rejection_reason_slots_written: candidate_count,
1848 kernel_launches: 1,
1849 host_write_ops: 0,
1850 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
1851 })
1852 }
1853
1854 pub const fn with_kernel_timing(
1856 mut self,
1857 kernel_timing: EpistemicGpuKernelTimingTrace,
1858 ) -> Self {
1859 self.kernel_timing = kernel_timing;
1860 self
1861 }
1862}
1863
1864impl EpistemicGpuWorkspaceResetTrace {
1865 pub fn for_layout(layout: EpistemicGpuWorkspaceLayout) -> Self {
1867 Self::try_for_layout(layout)
1868 .expect("epistemic GPU workspace reset trace byte total overflowed")
1869 }
1870
1871 pub fn try_for_layout(layout: EpistemicGpuWorkspaceLayout) -> Result<Self> {
1873 Ok(Self {
1874 candidate_assumption_bytes: layout.candidate_assumption_bytes,
1875 world_view_bytes: layout.world_view_bytes,
1876 model_membership_bytes: layout.model_membership_bytes,
1877 rejection_reason_bytes: checked_product(
1878 layout.rejection_reason_slots,
1879 std::mem::size_of::<u32>(),
1880 )?,
1881 device_zero_ops: 4,
1882 host_write_ops: 0,
1883 })
1884 }
1885
1886 pub fn total_zeroed_bytes(&self) -> usize {
1888 self.try_total_zeroed_bytes()
1889 .expect("epistemic GPU workspace reset byte total overflowed")
1890 }
1891
1892 pub fn try_total_zeroed_bytes(&self) -> Result<usize> {
1894 checked_sum(
1895 checked_sum(
1896 checked_sum(self.candidate_assumption_bytes, self.world_view_bytes)?,
1897 self.model_membership_bytes,
1898 )?,
1899 self.rejection_reason_bytes,
1900 )
1901 }
1902
1903 pub fn require_matches_layout(
1905 &self,
1906 construct: &str,
1907 layout: EpistemicGpuWorkspaceLayout,
1908 ) -> Result<()> {
1909 let expected = Self::try_for_layout(layout)?;
1910 if *self != expected {
1911 return Err(XlogError::UnsupportedEpistemicConstruct {
1912 construct: construct.to_string(),
1913 context: format!(
1914 "workspace reset trace does not match prepared GPU workspace layout: \
1915 candidate_bytes={}/{} world_view_bytes={}/{} model_membership_bytes={}/{} \
1916 rejection_reason_bytes={}/{} device_zero_ops={}/{} host_write_ops={}/{}",
1917 self.candidate_assumption_bytes,
1918 expected.candidate_assumption_bytes,
1919 self.world_view_bytes,
1920 expected.world_view_bytes,
1921 self.model_membership_bytes,
1922 expected.model_membership_bytes,
1923 self.rejection_reason_bytes,
1924 expected.rejection_reason_bytes,
1925 self.device_zero_ops,
1926 expected.device_zero_ops,
1927 self.host_write_ops,
1928 expected.host_write_ops
1929 ),
1930 });
1931 }
1932
1933 Ok(())
1934 }
1935}
1936
1937#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1939pub struct EpistemicGpuRuntimePreflight {
1940 pub epistemic_mode: EirEpistemicMode,
1942 pub execution_backend: EpistemicExecutionBackend,
1944 pub fallback_policy: EpistemicFallbackPolicy,
1946 pub workspace_layout: EpistemicGpuWorkspaceLayout,
1948 pub reduced_runtime_rule_count: usize,
1950 pub reduced_constraint_relation_count: usize,
1952 pub wcoj_required_reduction_count: usize,
1954 pub multiway_reduction_count: usize,
1956 pub kclique_wcoj_plan_count: usize,
1958 pub wcoj_triangle_route_count: usize,
1960 pub wcoj_4cycle_route_count: usize,
1962 pub free_join_route_count: usize,
1969 pub kclique_wcoj_plan_count_by_arity: [usize; 4],
1971 pub kclique_wcoj_max_arity: u8,
1973 pub kclique_wcoj_edge_permutation_count: usize,
1975 pub kclique_stream_group_count: usize,
1977 pub kclique_skew_scheduled_plan_count: usize,
1979 pub planned_hash_route_count: usize,
1981 pub planned_hash_planner_wins_count: usize,
1983 pub planned_hash_incomplete_stats_count: usize,
1985 pub planned_hash_cost_evidence_count: usize,
1987 pub sorted_layout_requirement_count: usize,
1989 pub helper_split_spec_count: usize,
1991 pub helper_relation_rule_count: usize,
1993 pub helper_relation_scan_count: usize,
1995 pub tuple_membership_binding_count: usize,
1997 pub solver_assumption_binding_count: usize,
1999 pub solver_required_capability_count: usize,
2001 pub solver_required_status_count: usize,
2003 pub know_operator_count: usize,
2005 pub possible_operator_count: usize,
2007 pub not_know_operator_count: usize,
2009 pub not_possible_operator_count: usize,
2011}
2012
2013impl EpistemicGpuRuntimePreflight {
2014 pub fn is_g91_mode(&self) -> bool {
2016 matches!(self.epistemic_mode, EirEpistemicMode::G91)
2017 }
2018
2019 pub fn is_faeel_mode(&self) -> bool {
2021 matches!(self.epistemic_mode, EirEpistemicMode::Faeel)
2022 }
2023
2024 pub fn for_executable_plan(
2026 executable: &EpistemicExecutablePlan,
2027 capacities: EpistemicGpuWorkspaceCapacities,
2028 ) -> Result<Self> {
2029 executable.gpu_plan.validate_tuple_membership_bindings()?;
2030 executable.gpu_plan.validate_solver_contract()?;
2031 require_epistemic_gpu_kernel_phases(&executable.gpu_plan)?;
2039 require_epistemic_gpu_buffer_contract(&executable.gpu_plan)?;
2040
2041 let workspace_layout =
2042 EpistemicGpuWorkspaceLayout::for_plan(&executable.gpu_plan, capacities)?;
2043 let mut routes = RuntimeRouteSummary::default();
2044 let mut reduced_runtime_rule_count = 0usize;
2045 let mut reduced_constraint_relation_names = Vec::new();
2046 let wcoj_required_reduction_count = executable
2047 .gpu_plan
2048 .reductions
2049 .iter()
2050 .filter(|reduction| {
2051 matches!(
2052 reduction.wcoj_status,
2053 EpistemicWcojReductionStatus::RequiresPlannerEligibility
2054 )
2055 })
2056 .count();
2057 let helper_relation_ids = helper_relation_ids(executable);
2058 let mut helper_relation_rule_count = 0usize;
2059 let mut helper_relation_scan_count = 0usize;
2060
2061 for rule in executable
2062 .reduced_runtime_plan
2063 .rules_by_scc
2064 .iter()
2065 .flatten()
2066 {
2067 reduced_runtime_rule_count += 1;
2068 if rule.head.starts_with(XLOG_CONSTRAINT_RELATION_PREFIX)
2069 && !reduced_constraint_relation_names
2070 .iter()
2071 .any(|name| name == &rule.head)
2072 {
2073 reduced_constraint_relation_names.push(rule.head.as_str());
2074 }
2075 if rule.head.starts_with("__kclique_helper_") {
2076 helper_relation_rule_count += 1;
2077 }
2078 helper_relation_scan_count +=
2079 count_helper_relation_scans(&rule.body, &helper_relation_ids);
2080 summarize_runtime_routes(&rule.body, &mut routes);
2081 }
2082
2083 if wcoj_required_reduction_count > routes.multiway_reduction_count {
2084 return Err(XlogError::UnsupportedEpistemicConstruct {
2085 construct: "epistemic GPU WCOJ route certification".to_string(),
2086 context: format!(
2087 "plan requires {} WCOJ-eligible epistemic reductions, but reduced runtime \
2088 plan exposes {} MultiWayJoin routes",
2089 wcoj_required_reduction_count, routes.multiway_reduction_count
2090 ),
2091 });
2092 }
2093
2094 let planned_hash_reason_count = routes
2095 .planned_hash_planner_wins_count
2096 .checked_add(routes.planned_hash_incomplete_stats_count)
2097 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
2098 construct: "epistemic GPU planned-hash certification".to_string(),
2099 context: "planned-hash reason counters overflowed".to_string(),
2100 })?;
2101 if planned_hash_reason_count != routes.planned_hash_route_count
2102 || routes.planned_hash_cost_evidence_count < routes.planned_hash_planner_wins_count
2103 {
2104 return Err(XlogError::UnsupportedEpistemicConstruct {
2105 construct: "epistemic GPU planned-hash certification".to_string(),
2106 context: format!(
2107 "planned_hash_routes={}, planner_wins={}, incomplete_stats={}, \
2108 finite_cost_evidence={}",
2109 routes.planned_hash_route_count,
2110 routes.planned_hash_planner_wins_count,
2111 routes.planned_hash_incomplete_stats_count,
2112 routes.planned_hash_cost_evidence_count
2113 ),
2114 });
2115 }
2116
2117 if routes.kclique_wcoj_plan_count > 0 && routes.kclique_wcoj_edge_permutation_count == 0 {
2118 return Err(XlogError::UnsupportedEpistemicConstruct {
2119 construct: "epistemic GPU K-clique WCOJ certification".to_string(),
2120 context: format!(
2121 "K-clique WCOJ plans require live edge-permutation slots, got \
2122 kclique_plans={} edge_permutation_slots=0",
2123 routes.kclique_wcoj_plan_count
2124 ),
2125 });
2126 }
2127
2128 if routes.helper_split_spec_count > 0
2129 && (helper_relation_rule_count < routes.helper_split_spec_count
2130 || helper_relation_scan_count < routes.helper_split_spec_count)
2131 {
2132 return Err(XlogError::UnsupportedEpistemicConstruct {
2133 construct: "epistemic GPU helper-split certification".to_string(),
2134 context: format!(
2135 "helper_split_specs={}, helper_relation_rules={}, \
2136 helper_relation_scans={}",
2137 routes.helper_split_spec_count,
2138 helper_relation_rule_count,
2139 helper_relation_scan_count
2140 ),
2141 });
2142 }
2143
2144 let mut know_operator_count = 0usize;
2145 let mut possible_operator_count = 0usize;
2146 let mut not_know_operator_count = 0usize;
2147 let mut not_possible_operator_count = 0usize;
2148 for literal in &executable.gpu_plan.epistemic_literals {
2149 match (literal.op, literal.negated) {
2150 (EirEpistemicOp::Know, false) => know_operator_count += 1,
2151 (EirEpistemicOp::Possible, false) => possible_operator_count += 1,
2152 (EirEpistemicOp::Know, true) => not_know_operator_count += 1,
2153 (EirEpistemicOp::Possible, true) => not_possible_operator_count += 1,
2154 }
2155 }
2156
2157 Ok(Self {
2158 epistemic_mode: executable.gpu_plan.mode,
2159 execution_backend: executable.gpu_plan.execution_backend,
2160 fallback_policy: executable.gpu_plan.fallback_policy,
2161 workspace_layout,
2162 reduced_runtime_rule_count,
2163 reduced_constraint_relation_count: reduced_constraint_relation_names.len(),
2164 wcoj_required_reduction_count,
2165 multiway_reduction_count: routes.multiway_reduction_count,
2166 kclique_wcoj_plan_count: routes.kclique_wcoj_plan_count,
2167 wcoj_triangle_route_count: routes.wcoj_triangle_route_count,
2168 wcoj_4cycle_route_count: routes.wcoj_4cycle_route_count,
2169 free_join_route_count: routes.free_join_route_count,
2170 kclique_wcoj_plan_count_by_arity: routes.kclique_wcoj_plan_count_by_arity,
2171 kclique_wcoj_max_arity: routes.kclique_wcoj_max_arity,
2172 kclique_wcoj_edge_permutation_count: routes.kclique_wcoj_edge_permutation_count,
2173 kclique_stream_group_count: routes.kclique_stream_groups.len(),
2174 kclique_skew_scheduled_plan_count: routes.kclique_skew_scheduled_plan_count,
2175 planned_hash_route_count: routes.planned_hash_route_count,
2176 planned_hash_planner_wins_count: routes.planned_hash_planner_wins_count,
2177 planned_hash_incomplete_stats_count: routes.planned_hash_incomplete_stats_count,
2178 planned_hash_cost_evidence_count: routes.planned_hash_cost_evidence_count,
2179 sorted_layout_requirement_count: routes.sorted_layout_requirement_count,
2180 helper_split_spec_count: routes.helper_split_spec_count,
2181 helper_relation_rule_count,
2182 helper_relation_scan_count,
2183 tuple_membership_binding_count: executable.gpu_plan.tuple_membership_bindings.len(),
2184 solver_assumption_binding_count: executable
2185 .gpu_plan
2186 .solver_contract
2187 .assumption_bindings
2188 .len(),
2189 solver_required_capability_count: executable
2190 .gpu_plan
2191 .solver_contract
2192 .distinct_required_capability_count(),
2193 solver_required_status_count: executable
2194 .gpu_plan
2195 .solver_contract
2196 .distinct_required_status_count(),
2197 know_operator_count,
2198 possible_operator_count,
2199 not_know_operator_count,
2200 not_possible_operator_count,
2201 })
2202 }
2203}
2204
2205pub struct EpistemicGpuPreparedExecution {
2207 pub preflight: EpistemicGpuRuntimePreflight,
2209 pub tuple_membership_bindings: Vec<EpistemicTupleMembershipBinding>,
2211 pub workspace: EpistemicGpuWorkspace,
2213 pub workspace_reset: EpistemicGpuWorkspaceResetTrace,
2215}
2216
2217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2219pub struct EpistemicGpuRuntimeTrace {
2220 pub preflight: EpistemicGpuRuntimePreflight,
2222 pub counters_before: EpistemicGpuRuntimeCounters,
2224 pub counters_after: EpistemicGpuRuntimeCounters,
2226 pub counter_delta: EpistemicGpuRuntimeCounters,
2228 pub wcoj_certification: EpistemicGpuRuntimeWcojCertification,
2230}
2231
2232impl EpistemicGpuRuntimeTrace {
2233 pub fn from_preflight_and_counters(
2235 preflight: EpistemicGpuRuntimePreflight,
2236 counters_before: EpistemicGpuRuntimeCounters,
2237 counters_after: EpistemicGpuRuntimeCounters,
2238 ) -> Self {
2239 Self::try_from_preflight_and_counters(preflight, counters_before, counters_after)
2240 .expect("runtime counter snapshots must be monotonic")
2241 }
2242
2243 pub fn try_from_preflight_and_counters(
2246 preflight: EpistemicGpuRuntimePreflight,
2247 counters_before: EpistemicGpuRuntimeCounters,
2248 counters_after: EpistemicGpuRuntimeCounters,
2249 ) -> Result<Self> {
2250 let counter_delta = counters_after.checked_delta_since(counters_before)?;
2251 let wcoj_certification = EpistemicGpuRuntimeWcojCertification::try_for_preflight_and_delta(
2252 &preflight,
2253 &counter_delta,
2254 )?;
2255
2256 Ok(Self {
2257 preflight,
2258 counters_before,
2259 counters_after,
2260 counter_delta,
2261 wcoj_certification,
2262 })
2263 }
2264
2265 pub fn require_wcoj_certification(&self) -> Result<()> {
2267 match self.wcoj_certification {
2268 EpistemicGpuRuntimeWcojCertification::MissingRequiredWcojDispatch {
2269 required_multiway_reductions,
2270 required_kclique_plans,
2271 observed_wcoj_dispatches,
2272 observed_kclique_dispatches,
2273 } => Err(XlogError::UnsupportedEpistemicConstruct {
2274 construct: "epistemic GPU WCOJ dispatch certification".to_string(),
2275 context: format!(
2276 "required_multiway_reductions={required_multiway_reductions}, \
2277 required_kclique_plans={required_kclique_plans}, \
2278 observed_wcoj_dispatches={observed_wcoj_dispatches}, \
2279 observed_kclique_dispatches={observed_kclique_dispatches}"
2280 ),
2281 }),
2282 EpistemicGpuRuntimeWcojCertification::MissingRequiredWcojLayout {
2283 required_sorted_layouts,
2284 observed_layout_events,
2285 } => Err(XlogError::UnsupportedEpistemicConstruct {
2286 construct: "epistemic GPU WCOJ layout certification".to_string(),
2287 context: format!(
2288 "required_sorted_layouts={required_sorted_layouts}, \
2289 observed_layout_events={observed_layout_events}"
2290 ),
2291 }),
2292 EpistemicGpuRuntimeWcojCertification::MissingRequiredKcliqueMetadata {
2293 required_kclique_plans,
2294 observed_metadata_builds,
2295 observed_metadata_build_nanos,
2296 } => Err(XlogError::UnsupportedEpistemicConstruct {
2297 construct: "epistemic GPU K-clique metadata certification".to_string(),
2298 context: format!(
2299 "required_kclique_plans={required_kclique_plans}, \
2300 observed_metadata_builds={observed_metadata_builds}, \
2301 observed_metadata_build_nanos={observed_metadata_build_nanos}"
2302 ),
2303 }),
2304 EpistemicGpuRuntimeWcojCertification::NotRequired { .. }
2305 | EpistemicGpuRuntimeWcojCertification::Certified { .. } => Ok(()),
2306 }
2307 }
2308}
2309
2310#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
2312pub struct EpistemicGpuRuntimeCounters {
2313 pub wcoj_triangle_dispatch_count: u64,
2315 pub wcoj_4cycle_dispatch_count: u64,
2317 pub chain_dispatch_count: u64,
2319 pub wcoj_clique5_dispatch_count: u64,
2321 pub wcoj_clique6_dispatch_count: u64,
2323 pub wcoj_clique7_dispatch_count: u64,
2325 pub wcoj_clique8_dispatch_count: u64,
2327 pub free_join_dispatch_count: u64,
2333 pub factorized_delta_dispatch_count: u64,
2338 pub provider_wcoj_triangle_hg_dispatch_count: u64,
2340 pub wcoj_layout_sort_invocation_count: u64,
2342 pub wcoj_layout_fast_path_hit_count: u64,
2344 pub kclique_metadata_build_count: u64,
2346 pub kclique_metadata_build_nanos: u64,
2348 pub kclique_histogram_refresh_count: u64,
2350 pub kclique_histogram_refresh_nanos: u128,
2352}
2353
2354impl EpistemicGpuRuntimeCounters {
2355 fn checked_counter_delta(counter: &str, after: u64, before: u64) -> Result<u64> {
2356 after
2357 .checked_sub(before)
2358 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
2359 construct: "epistemic GPU runtime counter trace".to_string(),
2360 context: format!(
2361 "runtime proof counter {counter} decreased from {before} to {after}"
2362 ),
2363 })
2364 }
2365
2366 fn checked_counter_delta_u128(counter: &str, after: u128, before: u128) -> Result<u128> {
2367 after
2368 .checked_sub(before)
2369 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
2370 construct: "epistemic GPU runtime counter trace".to_string(),
2371 context: format!(
2372 "runtime proof counter {counter} decreased from {before} to {after}"
2373 ),
2374 })
2375 }
2376
2377 fn checked_counter_sum(counter: &str, values: &[u64]) -> Result<u64> {
2378 values.iter().try_fold(0u64, |acc, value| {
2379 acc.checked_add(*value)
2380 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
2381 construct: "epistemic GPU runtime counter trace".to_string(),
2382 context: format!(
2383 "runtime proof counter {counter} overflowed while adding {value} to {acc}"
2384 ),
2385 })
2386 })
2387 }
2388
2389 pub fn checked_delta_since(self, before: Self) -> Result<Self> {
2391 Ok(Self {
2392 wcoj_triangle_dispatch_count: Self::checked_counter_delta(
2393 "wcoj_triangle_dispatch_count",
2394 self.wcoj_triangle_dispatch_count,
2395 before.wcoj_triangle_dispatch_count,
2396 )?,
2397 wcoj_4cycle_dispatch_count: Self::checked_counter_delta(
2398 "wcoj_4cycle_dispatch_count",
2399 self.wcoj_4cycle_dispatch_count,
2400 before.wcoj_4cycle_dispatch_count,
2401 )?,
2402 chain_dispatch_count: Self::checked_counter_delta(
2403 "chain_dispatch_count",
2404 self.chain_dispatch_count,
2405 before.chain_dispatch_count,
2406 )?,
2407 wcoj_clique5_dispatch_count: Self::checked_counter_delta(
2408 "wcoj_clique5_dispatch_count",
2409 self.wcoj_clique5_dispatch_count,
2410 before.wcoj_clique5_dispatch_count,
2411 )?,
2412 wcoj_clique6_dispatch_count: Self::checked_counter_delta(
2413 "wcoj_clique6_dispatch_count",
2414 self.wcoj_clique6_dispatch_count,
2415 before.wcoj_clique6_dispatch_count,
2416 )?,
2417 wcoj_clique7_dispatch_count: Self::checked_counter_delta(
2418 "wcoj_clique7_dispatch_count",
2419 self.wcoj_clique7_dispatch_count,
2420 before.wcoj_clique7_dispatch_count,
2421 )?,
2422 wcoj_clique8_dispatch_count: Self::checked_counter_delta(
2423 "wcoj_clique8_dispatch_count",
2424 self.wcoj_clique8_dispatch_count,
2425 before.wcoj_clique8_dispatch_count,
2426 )?,
2427 factorized_delta_dispatch_count: Self::checked_counter_delta(
2428 "factorized_delta_dispatch_count",
2429 self.factorized_delta_dispatch_count,
2430 before.factorized_delta_dispatch_count,
2431 )?,
2432 free_join_dispatch_count: Self::checked_counter_delta(
2433 "free_join_dispatch_count",
2434 self.free_join_dispatch_count,
2435 before.free_join_dispatch_count,
2436 )?,
2437 provider_wcoj_triangle_hg_dispatch_count: Self::checked_counter_delta(
2438 "provider_wcoj_triangle_hg_dispatch_count",
2439 self.provider_wcoj_triangle_hg_dispatch_count,
2440 before.provider_wcoj_triangle_hg_dispatch_count,
2441 )?,
2442 wcoj_layout_sort_invocation_count: Self::checked_counter_delta(
2443 "wcoj_layout_sort_invocation_count",
2444 self.wcoj_layout_sort_invocation_count,
2445 before.wcoj_layout_sort_invocation_count,
2446 )?,
2447 wcoj_layout_fast_path_hit_count: Self::checked_counter_delta(
2448 "wcoj_layout_fast_path_hit_count",
2449 self.wcoj_layout_fast_path_hit_count,
2450 before.wcoj_layout_fast_path_hit_count,
2451 )?,
2452 kclique_metadata_build_count: Self::checked_counter_delta(
2453 "kclique_metadata_build_count",
2454 self.kclique_metadata_build_count,
2455 before.kclique_metadata_build_count,
2456 )?,
2457 kclique_metadata_build_nanos: Self::checked_counter_delta(
2458 "kclique_metadata_build_nanos",
2459 self.kclique_metadata_build_nanos,
2460 before.kclique_metadata_build_nanos,
2461 )?,
2462 kclique_histogram_refresh_count: Self::checked_counter_delta(
2463 "kclique_histogram_refresh_count",
2464 self.kclique_histogram_refresh_count,
2465 before.kclique_histogram_refresh_count,
2466 )?,
2467 kclique_histogram_refresh_nanos: Self::checked_counter_delta_u128(
2468 "kclique_histogram_refresh_nanos",
2469 self.kclique_histogram_refresh_nanos,
2470 before.kclique_histogram_refresh_nanos,
2471 )?,
2472 })
2473 }
2474
2475 pub fn saturating_delta_since(self, before: Self) -> Self {
2477 Self {
2478 wcoj_triangle_dispatch_count: self
2479 .wcoj_triangle_dispatch_count
2480 .saturating_sub(before.wcoj_triangle_dispatch_count),
2481 wcoj_4cycle_dispatch_count: self
2482 .wcoj_4cycle_dispatch_count
2483 .saturating_sub(before.wcoj_4cycle_dispatch_count),
2484 chain_dispatch_count: self
2485 .chain_dispatch_count
2486 .saturating_sub(before.chain_dispatch_count),
2487 wcoj_clique5_dispatch_count: self
2488 .wcoj_clique5_dispatch_count
2489 .saturating_sub(before.wcoj_clique5_dispatch_count),
2490 wcoj_clique6_dispatch_count: self
2491 .wcoj_clique6_dispatch_count
2492 .saturating_sub(before.wcoj_clique6_dispatch_count),
2493 wcoj_clique7_dispatch_count: self
2494 .wcoj_clique7_dispatch_count
2495 .saturating_sub(before.wcoj_clique7_dispatch_count),
2496 wcoj_clique8_dispatch_count: self
2497 .wcoj_clique8_dispatch_count
2498 .saturating_sub(before.wcoj_clique8_dispatch_count),
2499 free_join_dispatch_count: self
2500 .free_join_dispatch_count
2501 .saturating_sub(before.free_join_dispatch_count),
2502 factorized_delta_dispatch_count: self
2503 .factorized_delta_dispatch_count
2504 .saturating_sub(before.factorized_delta_dispatch_count),
2505 provider_wcoj_triangle_hg_dispatch_count: self
2506 .provider_wcoj_triangle_hg_dispatch_count
2507 .saturating_sub(before.provider_wcoj_triangle_hg_dispatch_count),
2508 wcoj_layout_sort_invocation_count: self
2509 .wcoj_layout_sort_invocation_count
2510 .saturating_sub(before.wcoj_layout_sort_invocation_count),
2511 wcoj_layout_fast_path_hit_count: self
2512 .wcoj_layout_fast_path_hit_count
2513 .saturating_sub(before.wcoj_layout_fast_path_hit_count),
2514 kclique_metadata_build_count: self
2515 .kclique_metadata_build_count
2516 .saturating_sub(before.kclique_metadata_build_count),
2517 kclique_metadata_build_nanos: self
2518 .kclique_metadata_build_nanos
2519 .saturating_sub(before.kclique_metadata_build_nanos),
2520 kclique_histogram_refresh_count: self
2521 .kclique_histogram_refresh_count
2522 .saturating_sub(before.kclique_histogram_refresh_count),
2523 kclique_histogram_refresh_nanos: self
2524 .kclique_histogram_refresh_nanos
2525 .saturating_sub(before.kclique_histogram_refresh_nanos),
2526 }
2527 }
2528
2529 pub fn wcoj_dispatch_count(&self) -> u64 {
2531 self.wcoj_triangle_dispatch_count
2532 .saturating_add(self.wcoj_4cycle_dispatch_count)
2533 .saturating_add(self.wcoj_clique_dispatch_count())
2534 }
2535
2536 pub fn checked_wcoj_dispatch_count(&self) -> Result<u64> {
2538 Self::checked_counter_sum(
2539 "wcoj_dispatch_count",
2540 &[
2541 self.wcoj_triangle_dispatch_count,
2542 self.wcoj_4cycle_dispatch_count,
2543 self.checked_wcoj_clique_dispatch_count()?,
2544 ],
2545 )
2546 }
2547
2548 pub fn wcoj_clique_dispatch_count(&self) -> u64 {
2550 self.wcoj_clique5_dispatch_count
2551 .saturating_add(self.wcoj_clique6_dispatch_count)
2552 .saturating_add(self.wcoj_clique7_dispatch_count)
2553 .saturating_add(self.wcoj_clique8_dispatch_count)
2554 }
2555
2556 pub fn checked_wcoj_clique_dispatch_count(&self) -> Result<u64> {
2558 Self::checked_counter_sum(
2559 "wcoj_clique_dispatch_count",
2560 &[
2561 self.wcoj_clique5_dispatch_count,
2562 self.wcoj_clique6_dispatch_count,
2563 self.wcoj_clique7_dispatch_count,
2564 self.wcoj_clique8_dispatch_count,
2565 ],
2566 )
2567 }
2568}
2569
2570#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2572pub enum EpistemicGpuRuntimeWcojCertification {
2573 NotRequired {
2575 observed_wcoj_dispatches: u64,
2577 planned_hash_routes: usize,
2579 planned_hash_planner_wins: usize,
2581 planned_hash_incomplete_stats: usize,
2583 planned_hash_cost_evidence: usize,
2585 },
2586 Certified {
2588 observed_wcoj_dispatches: u64,
2590 certified_multiway_reductions: usize,
2592 observed_kclique_dispatches: u64,
2594 certified_edge_permutation_slots: usize,
2596 certified_stream_groups: usize,
2598 certified_skew_scheduled_plans: usize,
2600 certified_sorted_layout_requirements: usize,
2602 certified_helper_split_specs: usize,
2604 certified_helper_relation_rules: usize,
2606 certified_helper_relation_scans: usize,
2608 observed_layout_sorts: u64,
2610 observed_layout_fast_path_hits: u64,
2612 observed_metadata_builds: u64,
2614 observed_metadata_build_nanos: u64,
2616 observed_histogram_refreshes: u64,
2618 observed_histogram_refresh_nanos: u128,
2620 },
2621 MissingRequiredWcojLayout {
2623 required_sorted_layouts: usize,
2625 observed_layout_events: u64,
2627 },
2628 MissingRequiredKcliqueMetadata {
2630 required_kclique_plans: usize,
2632 observed_metadata_builds: u64,
2634 observed_metadata_build_nanos: u64,
2636 },
2637 MissingRequiredWcojDispatch {
2639 required_multiway_reductions: usize,
2641 required_kclique_plans: usize,
2643 observed_wcoj_dispatches: u64,
2645 observed_kclique_dispatches: u64,
2647 },
2648}
2649
2650#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2652pub struct EpistemicGpuProviderIdentity {
2653 pub device_ordinal: usize,
2655 pub device_ptr: usize,
2657 pub memory_ptr: usize,
2659}
2660
2661impl EpistemicGpuProviderIdentity {
2662 pub fn from_provider(provider: &xlog_cuda::CudaKernelProvider) -> Self {
2664 Self {
2665 device_ordinal: provider.device().ordinal(),
2666 device_ptr: Arc::as_ptr(provider.device()) as usize,
2667 memory_ptr: Arc::as_ptr(provider.memory()) as usize,
2668 }
2669 }
2670}
2671
2672pub struct EpistemicGpuExecutionResult {
2674 pub provider_identity: EpistemicGpuProviderIdentity,
2676 pub prepared: EpistemicGpuPreparedExecution,
2678 pub candidate_generation: EpistemicGpuCandidateGenerationTrace,
2680 pub propagation: EpistemicGpuPropagationTrace,
2682 pub candidate_validation: EpistemicGpuCandidateValidationTrace,
2684 pub model_membership: EpistemicGpuModelMembershipTrace,
2686 pub world_view_validation: EpistemicGpuWorldViewValidationTrace,
2688 pub constraint_world_view_validation: EpistemicGpuConstraintWorldViewValidationTrace,
2690 pub materialization: EpistemicGpuMaterializationTrace,
2692 pub final_result_materialization: EpistemicGpuFinalResultMaterializationTrace,
2694 pub final_tuple_materialization: EpistemicGpuFinalTupleMaterializationTrace,
2696 pub transfer_budget: EpistemicGpuTransferBudgetTrace,
2698 pub final_result_transfer: EpistemicGpuFinalResultTransferTrace,
2700 pub constraint_validation: EpistemicGpuConstraintValidationTrace,
2702 pub semantic_trace: EpistemicGpuSemanticTrace,
2704 pub tuple_membership_bindings: Vec<EpistemicTupleMembershipBinding>,
2706 pub final_output: CudaBuffer,
2714 pub additional_head_outputs: Vec<(String, CudaBuffer)>,
2720 pub tuple_evidence_output: Option<CudaBuffer>,
2722 pub output: CudaBuffer,
2724 pub trace: EpistemicGpuRuntimeTrace,
2726}
2727
2728impl EpistemicGpuExecutionResult {
2729 pub fn tuple_evidence_output(&self) -> &CudaBuffer {
2731 self.tuple_evidence_output
2732 .as_ref()
2733 .unwrap_or(&self.final_output)
2734 }
2735
2736 pub fn require_runtime_dispatch_certification(&self) -> Result<()> {
2738 if self.trace.preflight != self.prepared.preflight {
2739 return Err(XlogError::UnsupportedEpistemicConstruct {
2740 construct: "epistemic GPU runtime dispatch certification".to_string(),
2741 context: "runtime trace preflight does not match prepared execution preflight"
2742 .to_string(),
2743 });
2744 }
2745 if self.prepared.workspace.layout != self.prepared.preflight.workspace_layout {
2746 return Err(XlogError::UnsupportedEpistemicConstruct {
2747 construct: "epistemic GPU runtime dispatch certification".to_string(),
2748 context: "prepared GPU workspace layout does not match preflight workspace layout"
2749 .to_string(),
2750 });
2751 }
2752 self.prepared
2753 .workspace
2754 .require_buffer_lengths_match_layout("epistemic GPU runtime dispatch certification")?;
2755 if self.tuple_membership_bindings.len()
2756 != self.prepared.preflight.tuple_membership_binding_count
2757 {
2758 return Err(XlogError::UnsupportedEpistemicConstruct {
2759 construct: "epistemic GPU runtime dispatch certification".to_string(),
2760 context: format!(
2761 "runtime tuple-membership bindings do not match prepared preflight, got {} \
2762 bindings for preflight count {}",
2763 self.tuple_membership_bindings.len(),
2764 self.prepared.preflight.tuple_membership_binding_count
2765 ),
2766 });
2767 }
2768 if self.tuple_membership_bindings != self.prepared.tuple_membership_bindings {
2769 return Err(XlogError::UnsupportedEpistemicConstruct {
2770 construct: "epistemic GPU runtime dispatch certification".to_string(),
2771 context: "runtime tuple-membership bindings do not match prepared GPU execution"
2772 .to_string(),
2773 });
2774 }
2775 self.model_membership
2776 .require_planned_tuple_key_column_reads(expected_tuple_key_column_reads(
2777 &self.prepared.tuple_membership_bindings,
2778 )?)?;
2779 self.prepared.workspace_reset.require_matches_layout(
2780 "epistemic GPU runtime dispatch certification",
2781 self.prepared.preflight.workspace_layout,
2782 )?;
2783 self.final_result_transfer.require_matches_final_output(
2784 "epistemic GPU runtime dispatch certification",
2785 &self.final_output,
2786 )?;
2787 self.constraint_validation.require_matches_preflight(
2788 "epistemic GPU runtime dispatch certification",
2789 &self.prepared.preflight,
2790 )?;
2791 self.candidate_validation
2792 .require_matches_candidate_generation(
2793 "epistemic GPU runtime dispatch certification",
2794 &self.candidate_generation,
2795 )?;
2796 self.semantic_trace.require_matches_execution_traces(
2797 "epistemic GPU runtime dispatch certification",
2798 &self.candidate_generation,
2799 &self.propagation,
2800 &self.model_membership,
2801 &self.world_view_validation,
2802 )?;
2803 self.semantic_trace.require_rejection_metadata_accounting(
2804 "epistemic GPU runtime dispatch certification",
2805 )?;
2806 self.semantic_trace
2807 .require_candidate_index_partition("epistemic GPU runtime dispatch certification")?;
2808 let aggregate_kernel_timing = self.try_aggregate_kernel_timing()?;
2809 if !aggregate_kernel_timing.is_recorded() {
2810 return Err(XlogError::UnsupportedEpistemicConstruct {
2811 construct: "epistemic GPU runtime dispatch certification".to_string(),
2812 context: "accepted GPU execution did not record CUDA-event timing".to_string(),
2813 });
2814 }
2815 self.trace.require_wcoj_certification()
2816 }
2817
2818 pub fn aggregate_kernel_timing(&self) -> EpistemicGpuKernelTimingTrace {
2820 self.try_aggregate_kernel_timing()
2821 .expect("epistemic GPU kernel timing aggregation overflowed")
2822 }
2823
2824 pub fn try_aggregate_kernel_timing(&self) -> Result<EpistemicGpuKernelTimingTrace> {
2826 let traces = [
2827 self.candidate_generation.kernel_timing,
2828 self.propagation.kernel_timing,
2829 self.candidate_validation.kernel_timing,
2830 self.model_membership.kernel_timing,
2831 self.world_view_validation.kernel_timing,
2832 self.materialization.kernel_timing,
2833 self.final_result_materialization.kernel_timing,
2834 self.final_tuple_materialization.kernel_timing,
2835 ];
2836
2837 if traces
2838 .iter()
2839 .all(EpistemicGpuKernelTimingTrace::is_recorded)
2840 {
2841 EpistemicGpuKernelTimingTrace::checked_sum(traces)
2842 } else {
2843 Ok(EpistemicGpuKernelTimingTrace::unrecorded())
2844 }
2845 }
2846}
2847
2848#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2850pub struct EpistemicGpuBatchExecutionTrace {
2851 pub component_count: usize,
2853 pub gpu_runtime_component_executions: usize,
2855 pub tracked_dtoh_calls: u64,
2857 pub tracked_htod_calls: u64,
2859 pub tracked_aggregate_htod_calls: u64,
2861 pub tracked_launch_metadata_htod_calls: u64,
2863 pub tracked_data_plane_htod_calls: u64,
2865 pub per_candidate_host_round_trips: u64,
2867 pub final_output_rows: usize,
2869 pub final_output_payload_bytes: u64,
2871 pub final_result_row_count_device_reads: u32,
2873 pub final_result_data_plane_dtoh_calls: u64,
2875 pub final_result_data_plane_dtoh_bytes: u64,
2877 pub checked_constraint_relations: usize,
2879 pub violated_constraint_relations: usize,
2881 pub constraint_row_count_device_reads: u32,
2883 pub accepted_world_views: usize,
2885 pub rejected_candidates: usize,
2887 pub know_operator_count: usize,
2889 pub possible_operator_count: usize,
2891 pub not_know_operator_count: usize,
2893 pub not_possible_operator_count: usize,
2895 pub aggregate_kernel_timing: EpistemicGpuKernelTimingTrace,
2897}
2898
2899impl EpistemicGpuBatchExecutionTrace {
2900 pub fn from_component_results(results: &[EpistemicGpuExecutionResult]) -> Self {
2902 Self::try_from_component_results(results)
2903 .expect("epistemic GPU batch trace aggregation overflowed")
2904 }
2905
2906 pub fn try_from_component_results(results: &[EpistemicGpuExecutionResult]) -> Result<Self> {
2909 let component_kernel_timings = results
2910 .iter()
2911 .map(EpistemicGpuExecutionResult::try_aggregate_kernel_timing)
2912 .collect::<Result<Vec<_>>>()?;
2913 let aggregate_kernel_timing = if component_kernel_timings
2914 .iter()
2915 .all(EpistemicGpuKernelTimingTrace::is_recorded)
2916 {
2917 EpistemicGpuKernelTimingTrace::checked_sum(component_kernel_timings)
2918 } else {
2919 Ok(EpistemicGpuKernelTimingTrace::unrecorded())
2920 };
2921 let aggregate_kernel_timing = aggregate_kernel_timing?;
2922
2923 Ok(Self {
2924 component_count: results.len(),
2925 gpu_runtime_component_executions: results.len(),
2926 tracked_dtoh_calls: checked_batch_sum_u64(
2927 "tracked_dtoh_calls",
2928 results
2929 .iter()
2930 .map(|result| result.transfer_budget.tracked_dtoh_calls),
2931 )?,
2932 tracked_htod_calls: checked_batch_sum_u64(
2933 "tracked_htod_calls",
2934 results
2935 .iter()
2936 .map(|result| result.transfer_budget.tracked_htod_calls),
2937 )?,
2938 tracked_aggregate_htod_calls: checked_batch_sum_u64(
2939 "tracked_aggregate_htod_calls",
2940 results
2941 .iter()
2942 .map(|result| result.transfer_budget.tracked_aggregate_htod_calls),
2943 )?,
2944 tracked_launch_metadata_htod_calls: checked_batch_sum_u64(
2945 "tracked_launch_metadata_htod_calls",
2946 results
2947 .iter()
2948 .map(|result| result.transfer_budget.tracked_launch_metadata_htod_calls),
2949 )?,
2950 tracked_data_plane_htod_calls: checked_batch_sum_u64(
2951 "tracked_data_plane_htod_calls",
2952 results
2953 .iter()
2954 .map(|result| result.transfer_budget.tracked_data_plane_htod_calls),
2955 )?,
2956 per_candidate_host_round_trips: checked_batch_sum_u64(
2957 "per_candidate_host_round_trips",
2958 results
2959 .iter()
2960 .map(|result| result.transfer_budget.per_candidate_host_round_trips),
2961 )?,
2962 final_output_rows: checked_batch_sum_usize(
2963 "final_output_rows",
2964 results
2965 .iter()
2966 .map(|result| result.final_result_transfer.final_output_rows),
2967 )?,
2968 final_output_payload_bytes: checked_batch_sum_u64(
2969 "final_output_payload_bytes",
2970 results
2971 .iter()
2972 .map(|result| result.final_result_transfer.final_output_payload_bytes),
2973 )?,
2974 final_result_row_count_device_reads: checked_batch_sum_u32(
2975 "final_result_row_count_device_reads",
2976 results
2977 .iter()
2978 .map(|result| result.final_result_transfer.row_count_device_reads),
2979 )?,
2980 final_result_data_plane_dtoh_calls: checked_batch_sum_u64(
2981 "final_result_data_plane_dtoh_calls",
2982 results
2983 .iter()
2984 .map(|result| result.final_result_transfer.tracked_data_plane_dtoh_calls),
2985 )?,
2986 final_result_data_plane_dtoh_bytes: checked_batch_sum_u64(
2987 "final_result_data_plane_dtoh_bytes",
2988 results
2989 .iter()
2990 .map(|result| result.final_result_transfer.tracked_data_plane_dtoh_bytes),
2991 )?,
2992 checked_constraint_relations: checked_batch_sum_usize(
2993 "checked_constraint_relations",
2994 results
2995 .iter()
2996 .map(|result| result.constraint_validation.checked_constraint_relations),
2997 )?,
2998 violated_constraint_relations: checked_batch_sum_usize(
2999 "violated_constraint_relations",
3000 results
3001 .iter()
3002 .map(|result| result.constraint_validation.violated_constraint_relations),
3003 )?,
3004 constraint_row_count_device_reads: checked_batch_sum_u32(
3005 "constraint_row_count_device_reads",
3006 results
3007 .iter()
3008 .map(|result| result.constraint_validation.row_count_device_reads),
3009 )?,
3010 accepted_world_views: checked_batch_sum_usize(
3011 "accepted_world_views",
3012 results
3013 .iter()
3014 .map(|result| result.semantic_trace.accepted_world_views),
3015 )?,
3016 rejected_candidates: checked_batch_sum_usize(
3017 "rejected_candidates",
3018 results
3019 .iter()
3020 .map(|result| result.semantic_trace.rejected_candidates),
3021 )?,
3022 know_operator_count: checked_batch_sum_usize(
3023 "know_operator_count",
3024 results
3025 .iter()
3026 .map(|result| result.prepared.preflight.know_operator_count),
3027 )?,
3028 possible_operator_count: checked_batch_sum_usize(
3029 "possible_operator_count",
3030 results
3031 .iter()
3032 .map(|result| result.prepared.preflight.possible_operator_count),
3033 )?,
3034 not_know_operator_count: checked_batch_sum_usize(
3035 "not_know_operator_count",
3036 results
3037 .iter()
3038 .map(|result| result.prepared.preflight.not_know_operator_count),
3039 )?,
3040 not_possible_operator_count: checked_batch_sum_usize(
3041 "not_possible_operator_count",
3042 results
3043 .iter()
3044 .map(|result| result.prepared.preflight.not_possible_operator_count),
3045 )?,
3046 aggregate_kernel_timing,
3047 })
3048 }
3049}
3050
3051fn checked_batch_sum_u64(
3052 counter: &'static str,
3053 values: impl IntoIterator<Item = u64>,
3054) -> Result<u64> {
3055 values.into_iter().try_fold(0u64, |acc, value| {
3056 acc.checked_add(value)
3057 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3058 construct: "epistemic GPU batch execution trace".to_string(),
3059 context: format!(
3060 "batch counter {counter} overflowed while aggregating component traces: \
3061 acc={acc} next={value}"
3062 ),
3063 })
3064 })
3065}
3066
3067fn checked_batch_sum_u32(
3068 counter: &'static str,
3069 values: impl IntoIterator<Item = u32>,
3070) -> Result<u32> {
3071 values.into_iter().try_fold(0u32, |acc, value| {
3072 acc.checked_add(value)
3073 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3074 construct: "epistemic GPU batch execution trace".to_string(),
3075 context: format!(
3076 "batch counter {counter} overflowed while aggregating component traces: \
3077 acc={acc} next={value}"
3078 ),
3079 })
3080 })
3081}
3082
3083fn checked_batch_sum_usize(
3084 counter: &'static str,
3085 values: impl IntoIterator<Item = usize>,
3086) -> Result<usize> {
3087 values.into_iter().try_fold(0usize, |acc, value| {
3088 acc.checked_add(value)
3089 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3090 construct: "epistemic GPU batch execution trace".to_string(),
3091 context: format!(
3092 "batch counter {counter} overflowed while aggregating component traces: \
3093 acc={acc} next={value}"
3094 ),
3095 })
3096 })
3097}
3098
3099pub struct EpistemicGpuBatchExecutionResult {
3101 pub results: Vec<EpistemicGpuExecutionResult>,
3103 pub trace: EpistemicGpuBatchExecutionTrace,
3105}
3106
3107impl EpistemicGpuBatchExecutionResult {
3108 pub fn require_trace_matches_components(&self, construct: &str) -> Result<()> {
3110 if self.results.is_empty() {
3111 return Err(XlogError::UnsupportedEpistemicConstruct {
3112 construct: construct.to_string(),
3113 context: "batch evidence requires at least one GPU component".to_string(),
3114 });
3115 }
3116 let expected = EpistemicGpuBatchExecutionTrace::try_from_component_results(&self.results)?;
3117 if self.trace != expected {
3118 return Err(XlogError::UnsupportedEpistemicConstruct {
3119 construct: construct.to_string(),
3120 context: format!(
3121 "batch aggregate trace does not match component GPU execution results: \
3122 trace_components={}/{} expected_components={}/{} \
3123 trace_final_rows={} expected_final_rows={} trace_dtoh_calls={} \
3124 expected_dtoh_calls={} trace_data_plane_htod_calls={} \
3125 expected_data_plane_htod_calls={} trace_constraint_violations={} \
3126 expected_constraint_violations={} trace_accepted_world_views={} \
3127 expected_accepted_world_views={}",
3128 self.trace.gpu_runtime_component_executions,
3129 self.trace.component_count,
3130 expected.gpu_runtime_component_executions,
3131 expected.component_count,
3132 self.trace.final_output_rows,
3133 expected.final_output_rows,
3134 self.trace.tracked_dtoh_calls,
3135 expected.tracked_dtoh_calls,
3136 self.trace.tracked_data_plane_htod_calls,
3137 expected.tracked_data_plane_htod_calls,
3138 self.trace.violated_constraint_relations,
3139 expected.violated_constraint_relations,
3140 self.trace.accepted_world_views,
3141 expected.accepted_world_views
3142 ),
3143 });
3144 }
3145 if !self.trace.aggregate_kernel_timing.is_recorded() {
3146 return Err(XlogError::UnsupportedEpistemicConstruct {
3147 construct: construct.to_string(),
3148 context: "batch GPU execution did not record aggregate CUDA-event timing"
3149 .to_string(),
3150 });
3151 }
3152 Ok(())
3153 }
3154}
3155
3156impl EpistemicGpuRuntimeWcojCertification {
3157 pub fn for_preflight_and_delta(
3159 preflight: &EpistemicGpuRuntimePreflight,
3160 delta: &EpistemicGpuRuntimeCounters,
3161 ) -> Self {
3162 Self::try_for_preflight_and_delta(preflight, delta)
3163 .expect("runtime WCOJ certification counters must not overflow")
3164 }
3165
3166 pub fn try_for_preflight_and_delta(
3169 preflight: &EpistemicGpuRuntimePreflight,
3170 delta: &EpistemicGpuRuntimeCounters,
3171 ) -> Result<Self> {
3172 let observed_wcoj_dispatches = delta.checked_wcoj_dispatch_count()?;
3173 let observed_kclique_dispatches = delta.checked_wcoj_clique_dispatch_count()?;
3174 let wcoj_routed_reduction_count = preflight
3175 .multiway_reduction_count
3176 .checked_sub(preflight.planned_hash_route_count)
3177 .and_then(|count| count.checked_sub(preflight.free_join_route_count))
3182 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3183 construct: "epistemic GPU WCOJ route certification".to_string(),
3184 context: format!(
3185 "planned hash + free-join routes exceed observed route obligations: \
3186 multiway_reductions={} planned_hash_routes={} free_join_routes={}",
3187 preflight.multiway_reduction_count,
3188 preflight.planned_hash_route_count,
3189 preflight.free_join_route_count
3190 ),
3191 })?;
3192 let required_multiway_reductions = wcoj_routed_reduction_count;
3193
3194 if required_multiway_reductions == 0 {
3195 return Ok(Self::NotRequired {
3196 observed_wcoj_dispatches,
3197 planned_hash_routes: preflight.planned_hash_route_count,
3198 planned_hash_planner_wins: preflight.planned_hash_planner_wins_count,
3199 planned_hash_incomplete_stats: preflight.planned_hash_incomplete_stats_count,
3200 planned_hash_cost_evidence: preflight.planned_hash_cost_evidence_count,
3201 });
3202 }
3203
3204 if observed_wcoj_dispatches < required_multiway_reductions as u64
3205 || observed_kclique_dispatches < preflight.kclique_wcoj_plan_count as u64
3206 || delta.wcoj_triangle_dispatch_count < preflight.wcoj_triangle_route_count as u64
3207 || delta.wcoj_4cycle_dispatch_count < preflight.wcoj_4cycle_route_count as u64
3208 || delta.wcoj_clique5_dispatch_count
3209 < preflight.kclique_wcoj_plan_count_by_arity[0] as u64
3210 || delta.wcoj_clique6_dispatch_count
3211 < preflight.kclique_wcoj_plan_count_by_arity[1] as u64
3212 || delta.wcoj_clique7_dispatch_count
3213 < preflight.kclique_wcoj_plan_count_by_arity[2] as u64
3214 || delta.wcoj_clique8_dispatch_count
3215 < preflight.kclique_wcoj_plan_count_by_arity[3] as u64
3216 {
3217 return Ok(Self::MissingRequiredWcojDispatch {
3218 required_multiway_reductions,
3219 required_kclique_plans: preflight.kclique_wcoj_plan_count,
3220 observed_wcoj_dispatches,
3221 observed_kclique_dispatches,
3222 });
3223 }
3224
3225 let observed_layout_events = EpistemicGpuRuntimeCounters::checked_counter_sum(
3226 "wcoj_layout_events",
3227 &[
3228 delta.wcoj_layout_sort_invocation_count,
3229 delta.wcoj_layout_fast_path_hit_count,
3230 ],
3231 )?;
3232 if observed_layout_events < preflight.sorted_layout_requirement_count as u64 {
3233 return Ok(Self::MissingRequiredWcojLayout {
3234 required_sorted_layouts: preflight.sorted_layout_requirement_count,
3235 observed_layout_events,
3236 });
3237 }
3238
3239 if preflight.kclique_wcoj_plan_count > 0
3240 && (delta.kclique_metadata_build_count < preflight.kclique_wcoj_plan_count as u64
3241 || delta.kclique_metadata_build_nanos == 0)
3242 {
3243 return Ok(Self::MissingRequiredKcliqueMetadata {
3244 required_kclique_plans: preflight.kclique_wcoj_plan_count,
3245 observed_metadata_builds: delta.kclique_metadata_build_count,
3246 observed_metadata_build_nanos: delta.kclique_metadata_build_nanos,
3247 });
3248 }
3249
3250 Ok(Self::Certified {
3251 observed_wcoj_dispatches,
3252 certified_multiway_reductions: required_multiway_reductions,
3253 observed_kclique_dispatches,
3254 certified_edge_permutation_slots: preflight.kclique_wcoj_edge_permutation_count,
3255 certified_stream_groups: preflight.kclique_stream_group_count,
3256 certified_skew_scheduled_plans: preflight.kclique_skew_scheduled_plan_count,
3257 certified_sorted_layout_requirements: preflight.sorted_layout_requirement_count,
3258 certified_helper_split_specs: preflight.helper_split_spec_count,
3259 certified_helper_relation_rules: preflight.helper_relation_rule_count,
3260 certified_helper_relation_scans: preflight.helper_relation_scan_count,
3261 observed_layout_sorts: delta.wcoj_layout_sort_invocation_count,
3262 observed_layout_fast_path_hits: delta.wcoj_layout_fast_path_hit_count,
3263 observed_metadata_builds: delta.kclique_metadata_build_count,
3264 observed_metadata_build_nanos: delta.kclique_metadata_build_nanos,
3265 observed_histogram_refreshes: delta.kclique_histogram_refresh_count,
3266 observed_histogram_refresh_nanos: delta.kclique_histogram_refresh_nanos,
3267 })
3268 }
3269}
3270
3271enum TupleSourceLaunch<'a> {
3272 ArityZero {
3273 literal_index: u32,
3274 reduction_index: u32,
3275 negated: u8,
3276 row_count: &'a TrackedCudaSlice<u32>,
3277 },
3278 ArityOne {
3279 literal_index: u32,
3280 reduction_index: u32,
3281 negated: u8,
3282 row_count: &'a TrackedCudaSlice<u32>,
3283 key_col0: &'a CudaColumn,
3284 key_col0_width: u32,
3285 expected_key_col0_bits: u64,
3286 expected_key_col0_type_code: u8,
3287 },
3288 ArityTwo {
3289 literal_index: u32,
3290 reduction_index: u32,
3291 negated: u8,
3292 row_count: &'a TrackedCudaSlice<u32>,
3293 key_col0: &'a CudaColumn,
3294 key_col0_width: u32,
3295 expected_key_col0_bits: u64,
3296 expected_key_col0_type_code: u8,
3297 key_col1: &'a CudaColumn,
3298 key_col1_width: u32,
3299 expected_key_col1_bits: u64,
3300 expected_key_col1_type_code: u8,
3301 },
3302 ArityThree {
3303 literal_index: u32,
3304 reduction_index: u32,
3305 negated: u8,
3306 row_count: &'a TrackedCudaSlice<u32>,
3307 key_col0: &'a CudaColumn,
3308 key_col0_width: u32,
3309 expected_key_col0_bits: u64,
3310 expected_key_col0_type_code: u8,
3311 key_col1: &'a CudaColumn,
3312 key_col1_width: u32,
3313 expected_key_col1_bits: u64,
3314 expected_key_col1_type_code: u8,
3315 key_col2: &'a CudaColumn,
3316 key_col2_width: u32,
3317 expected_key_col2_bits: u64,
3318 expected_key_col2_type_code: u8,
3319 },
3320 ArityN {
3321 literal_index: u32,
3322 reduction_index: u32,
3323 negated: u8,
3324 row_count: &'a TrackedCudaSlice<u32>,
3325 bound_value_row_count: &'a TrackedCudaSlice<u32>,
3326 key_col_count: u32,
3327 key_col_ptrs: TrackedCudaSlice<u64>,
3328 key_col_widths: TrackedCudaSlice<u32>,
3329 expected_key_bits: TrackedCudaSlice<u64>,
3330 expected_key_type_codes: TrackedCudaSlice<u8>,
3331 tuple_key_match_modes: TrackedCudaSlice<u8>,
3332 bound_value_col_ptrs: TrackedCudaSlice<u64>,
3333 bound_value_col_widths: TrackedCudaSlice<u32>,
3334 has_bound_value_keys: u8,
3335 },
3336}
3337
3338const TUPLE_KEY_MATCH_MODE_GROUND: u8 = 0;
3339const TUPLE_KEY_MATCH_MODE_BOUND_OUTPUT: u8 = 1;
3340const TUPLE_KEY_MATCH_MODE_WILDCARD: u8 = 2;
3342
3343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3344struct TupleKeyExpectation {
3345 bits: u64,
3346 type_code: u8,
3347}
3348
3349impl TupleKeyExpectation {
3350 fn from_term(term: &EirTerm, column_type: ScalarType) -> Result<Self> {
3351 let bits = match (term, column_type) {
3352 (EirTerm::Integer(value), ScalarType::U32) => {
3353 u32::try_from(*value).map(u64::from).map_err(|_| {
3354 tuple_key_expectation_error(format!(
3355 "integer {value} is out of range for U32 tuple-key column"
3356 ))
3357 })?
3358 }
3359 (EirTerm::Integer(value), ScalarType::I32) => i32::try_from(*value)
3360 .map(|v| v as u32 as u64)
3361 .map_err(|_| {
3362 tuple_key_expectation_error(format!(
3363 "integer {value} is out of range for I32 tuple-key column"
3364 ))
3365 })?,
3366 (EirTerm::Integer(value), ScalarType::U64) => u64::try_from(*value).map_err(|_| {
3367 tuple_key_expectation_error(format!(
3368 "integer {value} is out of range for U64 tuple-key column"
3369 ))
3370 })?,
3371 (EirTerm::Integer(value), ScalarType::I64) => *value as u64,
3372 (EirTerm::Integer(value), ScalarType::Bool) => match *value {
3373 0 => 0,
3374 1 => 1,
3375 _ => {
3376 return Err(tuple_key_expectation_error(format!(
3377 "integer {value} is out of range for Bool tuple-key column"
3378 )))
3379 }
3380 },
3381 (EirTerm::Symbol(value), ScalarType::Symbol) => u64::from(*value),
3382 (EirTerm::String(value), ScalarType::Symbol) => {
3383 u64::from(xlog_core::symbol::intern(value))
3384 }
3385 (EirTerm::FloatBits(bits), ScalarType::F64) => *bits,
3386 (EirTerm::FloatBits(bits), ScalarType::F32) => {
3387 (f64::from_bits(*bits) as f32).to_bits() as u64
3388 }
3389 (EirTerm::Variable(_), _) => {
3390 return Err(tuple_key_expectation_error(format!(
3391 "term {term:?} cannot be encoded as a ground tuple-key expectation"
3392 )))
3393 }
3394 (
3395 EirTerm::Anonymous
3396 | EirTerm::List(_)
3397 | EirTerm::Cons { .. }
3398 | EirTerm::Compound { .. }
3399 | EirTerm::PredRef(_)
3400 | EirTerm::Aggregate { .. },
3401 _,
3402 ) => {
3403 return Err(tuple_key_expectation_error(format!(
3404 "term {term:?} cannot be used for GPU tuple-key matching"
3405 )))
3406 }
3407 _ => {
3408 return Err(tuple_key_expectation_error(format!(
3409 "term {term:?} cannot be encoded for {column_type:?} tuple-key column"
3410 )))
3411 }
3412 };
3413
3414 Ok(Self {
3415 bits,
3416 type_code: column_type.to_code(),
3417 })
3418 }
3419}
3420
3421fn tuple_key_expectation_error(context: String) -> XlogError {
3422 XlogError::UnsupportedEpistemicConstruct {
3423 construct: "epistemic GPU tuple-key expectation".to_string(),
3424 context,
3425 }
3426}
3427
3428impl Executor {
3429 fn resolve_modal_tuple_source(&self, predicate: &str, arity: usize) -> Option<&CudaBuffer> {
3448 let qualified = format!("{predicate}/{arity}");
3449 self.store()
3450 .get(qualified.as_str())
3451 .or_else(|| self.store().get(predicate))
3452 }
3453
3454 pub fn epistemic_gpu_runtime_counters(&self) -> EpistemicGpuRuntimeCounters {
3456 EpistemicGpuRuntimeCounters {
3457 wcoj_triangle_dispatch_count: self.wcoj_triangle_dispatch_count(),
3458 wcoj_4cycle_dispatch_count: self.wcoj_4cycle_dispatch_count(),
3459 chain_dispatch_count: self.chain_dispatch_count(),
3460 wcoj_clique5_dispatch_count: self.wcoj_clique5_dispatch_count(),
3461 wcoj_clique6_dispatch_count: self.wcoj_clique6_dispatch_count(),
3462 wcoj_clique7_dispatch_count: self.wcoj_clique7_dispatch_count(),
3463 wcoj_clique8_dispatch_count: self.wcoj_clique8_dispatch_count(),
3464 free_join_dispatch_count: self.free_join_dispatch_count(),
3465 factorized_delta_dispatch_count: self.factorized_delta_dispatch_count(),
3466 provider_wcoj_triangle_hg_dispatch_count: self
3467 .provider
3468 .wcoj_triangle_hg_dispatch_count(),
3469 wcoj_layout_sort_invocation_count: self.provider.wcoj_layout_sort_invocation_count(),
3470 wcoj_layout_fast_path_hit_count: self.provider.wcoj_layout_fast_path_hit_count(),
3471 kclique_metadata_build_count: self.provider.kclique_metadata_build_count(),
3472 kclique_metadata_build_nanos: self.provider.kclique_metadata_build_nanos(),
3473 kclique_histogram_refresh_count: self.kclique_histogram_refresh_count(),
3474 kclique_histogram_refresh_nanos: self.kclique_histogram_refresh_nanos(),
3475 }
3476 }
3477
3478 fn time_epistemic_gpu_kernel_launch<E: std::fmt::Display>(
3479 &self,
3480 operation: &str,
3481 launch: impl FnOnce() -> std::result::Result<(), E>,
3482 ) -> Result<EpistemicGpuKernelTimingTrace> {
3483 let stream = self.provider.device().inner().stream().clone();
3484 let start = stream
3485 .record_event(Some(sys::CUevent_flags::CU_EVENT_DEFAULT))
3486 .map_err(|e| XlogError::execution_ctx(operation, "record start timing event", &e))?;
3487 launch().map_err(|e| XlogError::execution_ctx(operation, "launch kernel", &e))?;
3488 let end = stream
3489 .record_event(Some(sys::CUevent_flags::CU_EVENT_DEFAULT))
3490 .map_err(|e| XlogError::execution_ctx(operation, "record end timing event", &e))?;
3491 let elapsed_ms = start
3492 .elapsed_ms(&end)
3493 .map_err(|e| XlogError::execution_ctx(operation, "measure CUDA event elapsed", &e))?;
3494
3495 EpistemicGpuKernelTimingTrace::from_cuda_elapsed_ms(elapsed_ms)
3496 }
3497
3498 pub fn allocate_epistemic_gpu_workspace(
3500 &self,
3501 plan: &EpistemicGpuPlan,
3502 capacities: EpistemicGpuWorkspaceCapacities,
3503 ) -> Result<EpistemicGpuWorkspace> {
3504 let layout = EpistemicGpuWorkspaceLayout::for_plan(plan, capacities)?;
3505 let memory = self.provider.memory();
3506
3507 Ok(EpistemicGpuWorkspace {
3508 layout,
3509 candidate_assumptions: memory.alloc::<u8>(layout.candidate_assumption_bytes)?,
3510 world_views: memory.alloc::<u8>(layout.world_view_bytes)?,
3511 model_membership: memory.alloc::<u8>(layout.model_membership_bytes)?,
3512 rejection_reasons: memory.alloc::<u32>(layout.rejection_reason_slots)?,
3513 constraint_violation_index: memory.alloc::<u32>(layout.rejection_reason_slots)?,
3514 })
3515 }
3516
3517 pub fn reset_epistemic_gpu_workspace(
3519 &self,
3520 workspace: &mut EpistemicGpuWorkspace,
3521 ) -> Result<EpistemicGpuWorkspaceResetTrace> {
3522 let device = self.provider.device().inner();
3523
3524 device
3525 .memset_zeros(&mut workspace.candidate_assumptions)
3526 .map_err(|e| {
3527 XlogError::execution_ctx(
3528 "epistemic GPU workspace reset",
3529 "candidate assumptions memset",
3530 &e,
3531 )
3532 })?;
3533 device
3534 .memset_zeros(&mut workspace.world_views)
3535 .map_err(|e| {
3536 XlogError::execution_ctx("epistemic GPU workspace reset", "world views memset", &e)
3537 })?;
3538 device
3539 .memset_zeros(&mut workspace.model_membership)
3540 .map_err(|e| {
3541 XlogError::execution_ctx(
3542 "epistemic GPU workspace reset",
3543 "model membership memset",
3544 &e,
3545 )
3546 })?;
3547 device
3548 .memset_zeros(&mut workspace.rejection_reasons)
3549 .map_err(|e| {
3550 XlogError::execution_ctx(
3551 "epistemic GPU workspace reset",
3552 "rejection reasons memset",
3553 &e,
3554 )
3555 })?;
3556
3557 EpistemicGpuWorkspaceResetTrace::try_for_layout(workspace.layout)
3558 }
3559
3560 pub fn generate_epistemic_gpu_candidates(
3562 &self,
3563 workspace: &mut EpistemicGpuWorkspace,
3564 literal_count: usize,
3565 candidate_count: usize,
3566 ) -> Result<EpistemicGpuCandidateGenerationTrace> {
3567 let trace =
3568 EpistemicGpuCandidateGenerationTrace::for_counts(literal_count, candidate_count)?;
3569 if trace.candidate_assumption_bytes > workspace.layout.candidate_assumption_bytes {
3570 return Err(XlogError::ResourceExhausted {
3571 context: "epistemic GPU candidate assumption workspace".to_string(),
3572 estimated_bytes: trace.candidate_assumption_bytes as u64,
3573 budget_bytes: workspace.layout.candidate_assumption_bytes as u64,
3574 });
3575 }
3576 if trace.candidate_assumption_bytes > u32::MAX as usize {
3577 return Err(capacity_exceeded(
3578 "epistemic GPU candidate generation launch",
3579 trace.candidate_assumption_bytes as u64,
3580 u32::MAX as u64,
3581 "launch elements",
3582 ));
3583 }
3584
3585 let literal_count =
3586 checked_u32_dimension(literal_count, "epistemic GPU candidate generation literals")?;
3587 let candidate_count = checked_u32_dimension(
3588 candidate_count,
3589 "epistemic GPU candidate generation candidates",
3590 )?;
3591 let total = checked_u32_dimension(
3592 trace.candidate_assumption_bytes,
3593 "epistemic GPU candidate generation launch elements",
3594 )?;
3595 let func = self
3596 .provider
3597 .device()
3598 .inner()
3599 .get_func(
3600 EPISTEMIC_MODULE,
3601 epistemic_kernels::EPISTEMIC_GENERATE_CANDIDATE_ASSUMPTIONS_U8,
3602 )
3603 .ok_or_else(|| {
3604 XlogError::Execution("epistemic candidate generation kernel not found".to_string())
3605 })?;
3606 let config = LaunchConfig::for_num_elems(total);
3607
3608 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
3609 "epistemic GPU candidate generation",
3610 || unsafe {
3611 func.clone().launch(
3614 config,
3615 (
3616 literal_count,
3617 candidate_count,
3618 &mut workspace.candidate_assumptions,
3619 ),
3620 )
3621 },
3622 )?;
3623
3624 Ok(trace.with_kernel_timing(kernel_timing))
3625 }
3626
3627 pub fn propagate_epistemic_gpu_candidates(
3629 &self,
3630 workspace: &mut EpistemicGpuWorkspace,
3631 literal_count: usize,
3632 candidate_count: usize,
3633 ) -> Result<EpistemicGpuPropagationTrace> {
3634 let mut trace = EpistemicGpuPropagationTrace::for_counts(literal_count, candidate_count)?;
3635 let candidate_assumption_bytes = checked_product(literal_count, candidate_count)?;
3636 if candidate_assumption_bytes > workspace.layout.candidate_assumption_bytes {
3637 return Err(XlogError::ResourceExhausted {
3638 context: "epistemic GPU propagation candidate workspace".to_string(),
3639 estimated_bytes: candidate_assumption_bytes as u64,
3640 budget_bytes: workspace.layout.candidate_assumption_bytes as u64,
3641 });
3642 }
3643 if trace.rejection_reason_slots_written > workspace.layout.rejection_reason_slots {
3644 return Err(capacity_exceeded(
3645 "epistemic GPU propagation rejection workspace",
3646 trace.rejection_reason_slots_written as u64,
3647 workspace.layout.rejection_reason_slots as u64,
3648 "rejection slots",
3649 ));
3650 }
3651 if literal_count > u32::MAX as usize || candidate_count > u32::MAX as usize {
3652 return Err(capacity_exceeded(
3653 "epistemic GPU propagation launch",
3654 literal_count.max(candidate_count) as u64,
3655 u32::MAX as u64,
3656 "launch elements",
3657 ));
3658 }
3659
3660 let world_stride =
3661 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
3662 require_positive_u32_dimension(world_stride, "epistemic GPU propagation world stride")?;
3663 let world_view_bitset_bytes_per_candidate =
3664 world_view_bitset_bytes_per_candidate(literal_count)?;
3665 if world_view_bitset_bytes_per_candidate > world_stride {
3666 return Err(XlogError::ResourceExhausted {
3667 context: "epistemic GPU propagation world-view bitset stride".to_string(),
3668 estimated_bytes: world_view_bitset_bytes_per_candidate as u64,
3669 budget_bytes: world_stride as u64,
3670 });
3671 }
3672 let world_view_bitset_bytes =
3673 checked_product(world_view_bitset_bytes_per_candidate, candidate_count)?;
3674 if world_view_bitset_bytes > workspace.layout.world_view_bytes {
3675 return Err(XlogError::ResourceExhausted {
3676 context: "epistemic GPU propagation world-view bitsets".to_string(),
3677 estimated_bytes: world_view_bitset_bytes as u64,
3678 budget_bytes: workspace.layout.world_view_bytes as u64,
3679 });
3680 }
3681 trace.world_view_bytes_written = checked_product(world_stride, candidate_count)?;
3682 if trace.world_view_bytes_written > workspace.layout.world_view_bytes {
3683 return Err(XlogError::ResourceExhausted {
3684 context: "epistemic GPU propagation world-view workspace".to_string(),
3685 estimated_bytes: trace.world_view_bytes_written as u64,
3686 budget_bytes: workspace.layout.world_view_bytes as u64,
3687 });
3688 }
3689
3690 let literal_count =
3691 checked_u32_dimension(literal_count, "epistemic GPU propagation literals")?;
3692 let candidate_count =
3693 checked_u32_dimension(candidate_count, "epistemic GPU propagation candidates")?;
3694 let world_stride =
3695 checked_u32_dimension(world_stride, "epistemic GPU propagation world stride")?;
3696 let func = self
3697 .provider
3698 .device()
3699 .inner()
3700 .get_func(
3701 EPISTEMIC_MODULE,
3702 epistemic_kernels::EPISTEMIC_PROPAGATE_CANDIDATES_U8,
3703 )
3704 .ok_or_else(|| {
3705 XlogError::Execution("epistemic candidate propagation kernel not found".to_string())
3706 })?;
3707 let config = LaunchConfig::for_num_elems(candidate_count);
3708
3709 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
3710 "epistemic GPU candidate propagation",
3711 || unsafe {
3712 func.clone().launch(
3715 config,
3716 (
3717 literal_count,
3718 candidate_count,
3719 world_stride,
3720 &workspace.candidate_assumptions,
3721 &mut workspace.world_views,
3722 &mut workspace.rejection_reasons,
3723 ),
3724 )
3725 },
3726 )?;
3727
3728 Ok(trace.with_kernel_timing(kernel_timing))
3729 }
3730
3731 pub fn validate_epistemic_gpu_candidates(
3733 &self,
3734 workspace: &mut EpistemicGpuWorkspace,
3735 literal_count: usize,
3736 candidate_count: usize,
3737 ) -> Result<EpistemicGpuCandidateValidationTrace> {
3738 let mut trace =
3739 EpistemicGpuCandidateValidationTrace::for_counts(literal_count, candidate_count)?;
3740 if trace.candidate_assumption_bytes_checked > workspace.layout.candidate_assumption_bytes {
3741 return Err(XlogError::ResourceExhausted {
3742 context: "epistemic GPU validation candidate workspace".to_string(),
3743 estimated_bytes: trace.candidate_assumption_bytes_checked as u64,
3744 budget_bytes: workspace.layout.candidate_assumption_bytes as u64,
3745 });
3746 }
3747 if trace.rejection_reason_slots_written > workspace.layout.rejection_reason_slots {
3748 return Err(capacity_exceeded(
3749 "epistemic GPU validation rejection workspace",
3750 trace.rejection_reason_slots_written as u64,
3751 workspace.layout.rejection_reason_slots as u64,
3752 "rejection slots",
3753 ));
3754 }
3755 if literal_count > u32::MAX as usize || candidate_count > u32::MAX as usize {
3756 return Err(capacity_exceeded(
3757 "epistemic GPU validation launch",
3758 literal_count.max(candidate_count) as u64,
3759 u32::MAX as u64,
3760 "launch elements",
3761 ));
3762 }
3763
3764 let world_stride =
3765 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
3766 require_positive_u32_dimension(world_stride, "epistemic GPU validation world stride")?;
3767 let world_view_bitset_bytes_per_candidate =
3768 world_view_bitset_bytes_per_candidate(literal_count)?;
3769 if world_view_bitset_bytes_per_candidate > world_stride {
3770 return Err(XlogError::ResourceExhausted {
3771 context: "epistemic GPU validation world-view bitset stride".to_string(),
3772 estimated_bytes: world_view_bitset_bytes_per_candidate as u64,
3773 budget_bytes: world_stride as u64,
3774 });
3775 }
3776 let world_view_bitset_bytes =
3777 checked_product(world_view_bitset_bytes_per_candidate, candidate_count)?;
3778 if world_view_bitset_bytes > workspace.layout.world_view_bytes {
3779 return Err(XlogError::ResourceExhausted {
3780 context: "epistemic GPU validation world-view bitsets".to_string(),
3781 estimated_bytes: world_view_bitset_bytes as u64,
3782 budget_bytes: workspace.layout.world_view_bytes as u64,
3783 });
3784 }
3785 trace.world_view_bytes_checked = world_view_bitset_bytes;
3786 if trace.world_view_bytes_checked > workspace.layout.world_view_bytes {
3787 return Err(XlogError::ResourceExhausted {
3788 context: "epistemic GPU validation world-view workspace".to_string(),
3789 estimated_bytes: trace.world_view_bytes_checked as u64,
3790 budget_bytes: workspace.layout.world_view_bytes as u64,
3791 });
3792 }
3793
3794 let literal_count =
3795 checked_u32_dimension(literal_count, "epistemic GPU validation literals")?;
3796 let candidate_count =
3797 checked_u32_dimension(candidate_count, "epistemic GPU validation candidates")?;
3798 let world_stride =
3799 checked_u32_dimension(world_stride, "epistemic GPU validation world stride")?;
3800 let func = self
3801 .provider
3802 .device()
3803 .inner()
3804 .get_func(
3805 EPISTEMIC_MODULE,
3806 epistemic_kernels::EPISTEMIC_VALIDATE_CANDIDATE_BITS_U8,
3807 )
3808 .ok_or_else(|| {
3809 XlogError::Execution("epistemic candidate validation kernel not found".to_string())
3810 })?;
3811 let config = LaunchConfig::for_num_elems(candidate_count);
3812
3813 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
3814 "epistemic GPU candidate validation",
3815 || unsafe {
3816 func.clone().launch(
3819 config,
3820 (
3821 literal_count,
3822 candidate_count,
3823 world_stride,
3824 &workspace.candidate_assumptions,
3825 &workspace.world_views,
3826 &mut workspace.rejection_reasons,
3827 ),
3828 )
3829 },
3830 )?;
3831
3832 Ok(trace.with_kernel_timing(kernel_timing))
3833 }
3834
3835 pub fn populate_epistemic_gpu_model_membership(
3837 &self,
3838 workspace: &mut EpistemicGpuWorkspace,
3839 output: &CudaBuffer,
3840 literal_count: usize,
3841 candidate_count: usize,
3842 reduction_count: usize,
3843 models_per_reduction: usize,
3844 ) -> Result<EpistemicGpuModelMembershipTrace> {
3845 let trace = EpistemicGpuModelMembershipTrace::for_counts(
3846 literal_count,
3847 candidate_count,
3848 reduction_count,
3849 models_per_reduction,
3850 )?;
3851 let candidate_assumption_bytes = checked_product(literal_count, candidate_count)?;
3852 if candidate_assumption_bytes > workspace.layout.candidate_assumption_bytes {
3853 return Err(XlogError::ResourceExhausted {
3854 context: "epistemic GPU model-membership candidate workspace".to_string(),
3855 estimated_bytes: candidate_assumption_bytes as u64,
3856 budget_bytes: workspace.layout.candidate_assumption_bytes as u64,
3857 });
3858 }
3859 if candidate_count > workspace.layout.world_view_bytes {
3860 return Err(XlogError::ResourceExhausted {
3861 context: "epistemic GPU model-membership world-view workspace".to_string(),
3862 estimated_bytes: candidate_count as u64,
3863 budget_bytes: workspace.layout.world_view_bytes as u64,
3864 });
3865 }
3866 if trace.model_membership_bytes_written > workspace.layout.model_membership_bytes {
3867 return Err(XlogError::ResourceExhausted {
3868 context: "epistemic GPU model-membership workspace".to_string(),
3869 estimated_bytes: trace.model_membership_bytes_written as u64,
3870 budget_bytes: workspace.layout.model_membership_bytes as u64,
3871 });
3872 }
3873 if trace.rejection_reason_slots_checked > workspace.layout.rejection_reason_slots {
3874 return Err(capacity_exceeded(
3875 "epistemic GPU model-membership rejection workspace",
3876 trace.rejection_reason_slots_checked as u64,
3877 workspace.layout.rejection_reason_slots as u64,
3878 "rejection slots",
3879 ));
3880 }
3881 if trace.model_membership_bytes_written > u32::MAX as usize {
3882 return Err(capacity_exceeded(
3883 "epistemic GPU model-membership launch",
3884 trace.model_membership_bytes_written as u64,
3885 u32::MAX as u64,
3886 "launch elements",
3887 ));
3888 }
3889 if literal_count > u32::MAX as usize
3890 || candidate_count > u32::MAX as usize
3891 || reduction_count > u32::MAX as usize
3892 || models_per_reduction > u32::MAX as usize
3893 {
3894 return Err(capacity_exceeded(
3895 "epistemic GPU model-membership dimensions",
3896 literal_count
3897 .max(candidate_count)
3898 .max(reduction_count)
3899 .max(models_per_reduction) as u64,
3900 u32::MAX as u64,
3901 "dimension elements",
3902 ));
3903 }
3904
3905 let world_stride =
3906 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
3907 require_positive_u32_dimension(
3908 world_stride,
3909 "epistemic GPU model-membership world stride",
3910 )?;
3911
3912 let literal_count = literal_count as u32;
3913 let candidate_count = candidate_count as u32;
3914 let reduction_count = reduction_count as u32;
3915 let models_per_reduction = models_per_reduction as u32;
3916 let world_stride = world_stride as u32;
3917 let total = trace.model_membership_bytes_written as u32;
3918 let func = self
3919 .provider
3920 .device()
3921 .inner()
3922 .get_func(
3923 EPISTEMIC_MODULE,
3924 epistemic_kernels::EPISTEMIC_POPULATE_MODEL_MEMBERSHIP_U8,
3925 )
3926 .ok_or_else(|| {
3927 XlogError::Execution("epistemic model-membership kernel not found".to_string())
3928 })?;
3929 let config = LaunchConfig::for_num_elems(total);
3930
3931 let kernel_timing =
3932 self.time_epistemic_gpu_kernel_launch("epistemic GPU model membership", || unsafe {
3933 func.clone().launch(
3937 config,
3938 (
3939 literal_count,
3940 candidate_count,
3941 reduction_count,
3942 models_per_reduction,
3943 world_stride,
3944 output.num_rows_device(),
3945 &workspace.candidate_assumptions,
3946 &workspace.world_views,
3947 &mut workspace.model_membership,
3948 &mut workspace.rejection_reasons,
3949 ),
3950 )
3951 })?;
3952
3953 Ok(trace.with_kernel_timing(kernel_timing))
3954 }
3955
3956 pub fn populate_epistemic_gpu_model_membership_from_tuple_sources(
3958 &self,
3959 workspace: &mut EpistemicGpuWorkspace,
3960 output: &CudaBuffer,
3961 gpu_plan: &EpistemicGpuPlan,
3962 candidate_count: usize,
3963 models_per_reduction: usize,
3964 ) -> Result<EpistemicGpuModelMembershipTrace> {
3965 gpu_plan.validate_tuple_membership_bindings()?;
3966
3967 let literal_count = gpu_plan.epistemic_literals.len();
3968 let reduction_count = gpu_plan.reductions.len();
3969 let tuple_source_key_column_count = gpu_plan
3970 .tuple_membership_bindings
3971 .iter()
3972 .try_fold(0usize, |acc, binding| {
3973 checked_sum(acc, binding.key_columns.len())
3974 })?;
3975 let mut trace =
3976 EpistemicGpuModelMembershipTrace::for_stable_model_tuple_sources_with_key_columns(
3977 literal_count,
3978 candidate_count,
3979 reduction_count,
3980 models_per_reduction,
3981 gpu_plan.tuple_membership_bindings.len(),
3982 tuple_source_key_column_count,
3983 )?;
3984 trace.output_row_count_device_reads = trace.kernel_launches;
3985 let candidate_assumption_bytes = checked_product(literal_count, candidate_count)?;
3986 if candidate_assumption_bytes > workspace.layout.candidate_assumption_bytes {
3987 return Err(XlogError::ResourceExhausted {
3988 context: "epistemic GPU model-membership candidate workspace".to_string(),
3989 estimated_bytes: candidate_assumption_bytes as u64,
3990 budget_bytes: workspace.layout.candidate_assumption_bytes as u64,
3991 });
3992 }
3993 if candidate_count > workspace.layout.world_view_bytes {
3994 return Err(XlogError::ResourceExhausted {
3995 context: "epistemic GPU model-membership world-view workspace".to_string(),
3996 estimated_bytes: candidate_count as u64,
3997 budget_bytes: workspace.layout.world_view_bytes as u64,
3998 });
3999 }
4000 if trace.model_membership_bytes_written > workspace.layout.model_membership_bytes {
4001 return Err(XlogError::ResourceExhausted {
4002 context: "epistemic GPU model-membership workspace".to_string(),
4003 estimated_bytes: trace.model_membership_bytes_written as u64,
4004 budget_bytes: workspace.layout.model_membership_bytes as u64,
4005 });
4006 }
4007 if trace.rejection_reason_slots_checked > workspace.layout.rejection_reason_slots {
4008 return Err(capacity_exceeded(
4009 "epistemic GPU model-membership rejection workspace",
4010 trace.rejection_reason_slots_checked as u64,
4011 workspace.layout.rejection_reason_slots as u64,
4012 "rejection slots",
4013 ));
4014 }
4015 if trace.model_membership_bytes_written > u32::MAX as usize {
4016 return Err(capacity_exceeded(
4017 "epistemic GPU model-membership launch",
4018 trace.model_membership_bytes_written as u64,
4019 u32::MAX as u64,
4020 "launch elements",
4021 ));
4022 }
4023 if literal_count > u32::MAX as usize
4024 || candidate_count > u32::MAX as usize
4025 || reduction_count > u32::MAX as usize
4026 || models_per_reduction > u32::MAX as usize
4027 {
4028 return Err(capacity_exceeded(
4029 "epistemic GPU model-membership dimensions",
4030 literal_count
4031 .max(candidate_count)
4032 .max(reduction_count)
4033 .max(models_per_reduction) as u64,
4034 u32::MAX as u64,
4035 "dimension elements",
4036 ));
4037 }
4038
4039 let world_stride =
4040 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
4041 require_positive_u32_dimension(
4042 world_stride,
4043 "epistemic GPU model-membership world stride",
4044 )?;
4045
4046 let per_binding_launch_elems = checked_product(candidate_count, models_per_reduction)?;
4047 if per_binding_launch_elems > u32::MAX as usize {
4048 return Err(capacity_exceeded(
4049 "epistemic GPU model-membership tuple-source launch",
4050 per_binding_launch_elems as u64,
4051 u32::MAX as u64,
4052 "launch elements",
4053 ));
4054 }
4055
4056 let mut tuple_sources = Vec::with_capacity(gpu_plan.tuple_membership_bindings.len());
4057 for binding in &gpu_plan.tuple_membership_bindings {
4058 let source_relation = self
4059 .resolve_modal_tuple_source(binding.predicate.as_str(), binding.arity)
4060 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4061 construct: "epistemic GPU stable-model tuple membership".to_string(),
4062 context: format!(
4063 "missing reduced stable-model tuple source relation {} (arity {})",
4064 binding.predicate, binding.arity
4065 ),
4066 })?;
4067 if source_relation.arity() != binding.arity {
4068 return Err(XlogError::UnsupportedEpistemicConstruct {
4069 construct: "epistemic GPU stable-model tuple membership".to_string(),
4070 context: format!(
4071 "tuple source relation {} arity {} does not match binding arity {}",
4072 binding.predicate,
4073 source_relation.arity(),
4074 binding.arity
4075 ),
4076 });
4077 }
4078 let has_bound_value_keys = binding
4079 .key_terms
4080 .iter()
4081 .any(|term| matches!(term, EirTerm::Variable(_)));
4082 let has_value_level_keys = binding
4087 .key_terms
4088 .iter()
4089 .any(|term| matches!(term, EirTerm::Variable(_) | EirTerm::Anonymous));
4090 match binding.key_columns.as_slice() {
4091 [] => tuple_sources.push(TupleSourceLaunch::ArityZero {
4092 literal_index: binding.literal_index as u32,
4093 reduction_index: binding.reduction_index as u32,
4094 negated: binding.negated as u8,
4095 row_count: source_relation.num_rows_device(),
4096 }),
4097 &[key_col] if !has_value_level_keys => {
4098 let key_col0 = source_relation.column(key_col).ok_or_else(|| {
4099 XlogError::UnsupportedEpistemicConstruct {
4100 construct: "epistemic GPU stable-model tuple membership".to_string(),
4101 context: format!(
4102 "tuple source relation {} missing key column {}",
4103 binding.predicate, key_col
4104 ),
4105 }
4106 })?;
4107 let key_col0_type =
4108 source_relation
4109 .schema()
4110 .column_type(key_col)
4111 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4112 construct: "epistemic GPU stable-model tuple membership"
4113 .to_string(),
4114 context: format!(
4115 "tuple source relation {} missing schema for key column {}",
4116 binding.predicate, key_col
4117 ),
4118 })?;
4119 let key_col0_width = key_col0_type.size_bytes();
4120 let key_col0_expectation =
4121 TupleKeyExpectation::from_term(&binding.key_terms[0], key_col0_type)?;
4122 if key_col0_width > u32::MAX as usize {
4123 return Err(capacity_exceeded(
4124 "epistemic GPU tuple-key column width",
4125 key_col0_width as u64,
4126 u32::MAX as u64,
4127 "bytes per column",
4128 ));
4129 }
4130 tuple_sources.push(TupleSourceLaunch::ArityOne {
4131 literal_index: binding.literal_index as u32,
4132 reduction_index: binding.reduction_index as u32,
4133 negated: binding.negated as u8,
4134 row_count: source_relation.num_rows_device(),
4135 key_col0,
4136 key_col0_width: key_col0_width as u32,
4137 expected_key_col0_bits: key_col0_expectation.bits,
4138 expected_key_col0_type_code: key_col0_expectation.type_code,
4139 });
4140 }
4141 &[key_col0, key_col1] if !has_value_level_keys => {
4142 let key_col0_ref = source_relation.column(key_col0).ok_or_else(|| {
4143 XlogError::UnsupportedEpistemicConstruct {
4144 construct: "epistemic GPU stable-model tuple membership".to_string(),
4145 context: format!(
4146 "tuple source relation {} missing key column {}",
4147 binding.predicate, key_col0
4148 ),
4149 }
4150 })?;
4151 let key_col1_ref = source_relation.column(key_col1).ok_or_else(|| {
4152 XlogError::UnsupportedEpistemicConstruct {
4153 construct: "epistemic GPU stable-model tuple membership".to_string(),
4154 context: format!(
4155 "tuple source relation {} missing key column {}",
4156 binding.predicate, key_col1
4157 ),
4158 }
4159 })?;
4160 let key_col0_type =
4161 source_relation
4162 .schema()
4163 .column_type(key_col0)
4164 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4165 construct: "epistemic GPU stable-model tuple membership"
4166 .to_string(),
4167 context: format!(
4168 "tuple source relation {} missing schema for key column {}",
4169 binding.predicate, key_col0
4170 ),
4171 })?;
4172 let key_col1_type =
4173 source_relation
4174 .schema()
4175 .column_type(key_col1)
4176 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4177 construct: "epistemic GPU stable-model tuple membership"
4178 .to_string(),
4179 context: format!(
4180 "tuple source relation {} missing schema for key column {}",
4181 binding.predicate, key_col1
4182 ),
4183 })?;
4184 let key_col0_width = key_col0_type.size_bytes();
4185 let key_col1_width = key_col1_type.size_bytes();
4186 let key_col0_expectation =
4187 TupleKeyExpectation::from_term(&binding.key_terms[0], key_col0_type)?;
4188 let key_col1_expectation =
4189 TupleKeyExpectation::from_term(&binding.key_terms[1], key_col1_type)?;
4190 let max_width = key_col0_width.max(key_col1_width);
4191 if max_width > u32::MAX as usize {
4192 return Err(capacity_exceeded(
4193 "epistemic GPU tuple-key column width",
4194 max_width as u64,
4195 u32::MAX as u64,
4196 "bytes per column",
4197 ));
4198 }
4199 tuple_sources.push(TupleSourceLaunch::ArityTwo {
4200 literal_index: binding.literal_index as u32,
4201 reduction_index: binding.reduction_index as u32,
4202 negated: binding.negated as u8,
4203 row_count: source_relation.num_rows_device(),
4204 key_col0: key_col0_ref,
4205 key_col0_width: key_col0_width as u32,
4206 expected_key_col0_bits: key_col0_expectation.bits,
4207 expected_key_col0_type_code: key_col0_expectation.type_code,
4208 key_col1: key_col1_ref,
4209 key_col1_width: key_col1_width as u32,
4210 expected_key_col1_bits: key_col1_expectation.bits,
4211 expected_key_col1_type_code: key_col1_expectation.type_code,
4212 });
4213 }
4214 &[key_col0, key_col1, key_col2] if !has_value_level_keys => {
4215 let key_col0_ref = source_relation.column(key_col0).ok_or_else(|| {
4216 XlogError::UnsupportedEpistemicConstruct {
4217 construct: "epistemic GPU stable-model tuple membership".to_string(),
4218 context: format!(
4219 "tuple source relation {} missing key column {}",
4220 binding.predicate, key_col0
4221 ),
4222 }
4223 })?;
4224 let key_col1_ref = source_relation.column(key_col1).ok_or_else(|| {
4225 XlogError::UnsupportedEpistemicConstruct {
4226 construct: "epistemic GPU stable-model tuple membership".to_string(),
4227 context: format!(
4228 "tuple source relation {} missing key column {}",
4229 binding.predicate, key_col1
4230 ),
4231 }
4232 })?;
4233 let key_col2_ref = source_relation.column(key_col2).ok_or_else(|| {
4234 XlogError::UnsupportedEpistemicConstruct {
4235 construct: "epistemic GPU stable-model tuple membership".to_string(),
4236 context: format!(
4237 "tuple source relation {} missing key column {}",
4238 binding.predicate, key_col2
4239 ),
4240 }
4241 })?;
4242 let key_col0_type =
4243 source_relation
4244 .schema()
4245 .column_type(key_col0)
4246 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4247 construct: "epistemic GPU stable-model tuple membership"
4248 .to_string(),
4249 context: format!(
4250 "tuple source relation {} missing schema for key column {}",
4251 binding.predicate, key_col0
4252 ),
4253 })?;
4254 let key_col1_type =
4255 source_relation
4256 .schema()
4257 .column_type(key_col1)
4258 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4259 construct: "epistemic GPU stable-model tuple membership"
4260 .to_string(),
4261 context: format!(
4262 "tuple source relation {} missing schema for key column {}",
4263 binding.predicate, key_col1
4264 ),
4265 })?;
4266 let key_col2_type =
4267 source_relation
4268 .schema()
4269 .column_type(key_col2)
4270 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4271 construct: "epistemic GPU stable-model tuple membership"
4272 .to_string(),
4273 context: format!(
4274 "tuple source relation {} missing schema for key column {}",
4275 binding.predicate, key_col2
4276 ),
4277 })?;
4278 let key_col0_width = key_col0_type.size_bytes();
4279 let key_col1_width = key_col1_type.size_bytes();
4280 let key_col2_width = key_col2_type.size_bytes();
4281 let key_col0_expectation =
4282 TupleKeyExpectation::from_term(&binding.key_terms[0], key_col0_type)?;
4283 let key_col1_expectation =
4284 TupleKeyExpectation::from_term(&binding.key_terms[1], key_col1_type)?;
4285 let key_col2_expectation =
4286 TupleKeyExpectation::from_term(&binding.key_terms[2], key_col2_type)?;
4287 let max_width = key_col0_width.max(key_col1_width).max(key_col2_width);
4288 if max_width > u32::MAX as usize {
4289 return Err(capacity_exceeded(
4290 "epistemic GPU tuple-key column width",
4291 max_width as u64,
4292 u32::MAX as u64,
4293 "bytes per column",
4294 ));
4295 }
4296 tuple_sources.push(TupleSourceLaunch::ArityThree {
4297 literal_index: binding.literal_index as u32,
4298 reduction_index: binding.reduction_index as u32,
4299 negated: binding.negated as u8,
4300 row_count: source_relation.num_rows_device(),
4301 key_col0: key_col0_ref,
4302 key_col0_width: key_col0_width as u32,
4303 expected_key_col0_bits: key_col0_expectation.bits,
4304 expected_key_col0_type_code: key_col0_expectation.type_code,
4305 key_col1: key_col1_ref,
4306 key_col1_width: key_col1_width as u32,
4307 expected_key_col1_bits: key_col1_expectation.bits,
4308 expected_key_col1_type_code: key_col1_expectation.type_code,
4309 key_col2: key_col2_ref,
4310 key_col2_width: key_col2_width as u32,
4311 expected_key_col2_bits: key_col2_expectation.bits,
4312 expected_key_col2_type_code: key_col2_expectation.type_code,
4313 });
4314 }
4315 key_columns => {
4316 if key_columns.len() > u32::MAX as usize {
4317 return Err(capacity_exceeded(
4318 "epistemic GPU tuple-key arity",
4319 key_columns.len() as u64,
4320 u32::MAX as u64,
4321 "key columns",
4322 ));
4323 }
4324
4325 let mut key_col_ptrs_host = Vec::with_capacity(key_columns.len());
4326 let mut key_col_widths_host = Vec::with_capacity(key_columns.len());
4327 let mut expected_key_bits_host = Vec::with_capacity(key_columns.len());
4328 let mut expected_key_type_codes_host = Vec::with_capacity(key_columns.len());
4329 let mut tuple_key_match_modes_host = Vec::with_capacity(key_columns.len());
4330 let mut bound_value_col_ptrs_host = Vec::with_capacity(key_columns.len());
4331 let mut bound_value_col_widths_host = Vec::with_capacity(key_columns.len());
4332 for (term_index, &key_col) in key_columns.iter().enumerate() {
4333 let key_col_ref = source_relation.column(key_col).ok_or_else(|| {
4334 XlogError::UnsupportedEpistemicConstruct {
4335 construct: "epistemic GPU stable-model tuple membership"
4336 .to_string(),
4337 context: format!(
4338 "tuple source relation {} missing key column {}",
4339 binding.predicate, key_col
4340 ),
4341 }
4342 })?;
4343 let key_col_type = source_relation
4344 .schema()
4345 .column_type(key_col)
4346 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4347 construct: "epistemic GPU stable-model tuple membership"
4348 .to_string(),
4349 context: format!(
4350 "tuple source relation {} missing schema for key column {}",
4351 binding.predicate, key_col
4352 ),
4353 })?;
4354 let key_col_width = key_col_type.size_bytes();
4355 if key_col_width > u32::MAX as usize {
4356 return Err(capacity_exceeded(
4357 "epistemic GPU tuple-key column width",
4358 key_col_width as u64,
4359 u32::MAX as u64,
4360 "bytes per column",
4361 ));
4362 }
4363
4364 key_col_ptrs_host.push(*key_col_ref.device_ptr());
4365 key_col_widths_host.push(key_col_width as u32);
4366 match &binding.key_terms[term_index] {
4367 EirTerm::Variable(variable_name) => {
4368 let bound_col_index = binding.bound_output_columns[term_index]
4369 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
4370 construct: "epistemic GPU bound tuple-key matching"
4371 .to_string(),
4372 context: format!(
4373 "tuple key variable {variable_name} has no reduced \
4374 output column binding"
4375 ),
4376 })?;
4377 let bound_col =
4378 output.column(bound_col_index).ok_or_else(|| {
4379 XlogError::UnsupportedEpistemicConstruct {
4380 construct: "epistemic GPU bound tuple-key matching"
4381 .to_string(),
4382 context: format!(
4383 "reduced output is missing device column \
4384 {bound_col_index} for variable {variable_name}"
4385 ),
4386 }
4387 })?;
4388 let bound_col_type =
4389 output.schema().column_type(bound_col_index).ok_or_else(
4390 || XlogError::UnsupportedEpistemicConstruct {
4391 construct: "epistemic GPU bound tuple-key matching"
4392 .to_string(),
4393 context: format!(
4394 "reduced output is missing schema for variable \
4395 {variable_name}"
4396 ),
4397 },
4398 )?;
4399 if bound_col_type != key_col_type {
4400 return Err(XlogError::UnsupportedEpistemicConstruct {
4401 construct: "epistemic GPU bound tuple-key matching"
4402 .to_string(),
4403 context: format!(
4404 "bound variable {variable_name} has output type \
4405 {bound_col_type:?}, but tuple source {} key column \
4406 {} has type {key_col_type:?}",
4407 binding.predicate, key_col
4408 ),
4409 });
4410 }
4411 let bound_col_width = bound_col_type.size_bytes();
4412 if bound_col_width > u32::MAX as usize {
4413 return Err(capacity_exceeded(
4414 "epistemic GPU bound tuple-key column width",
4415 bound_col_width as u64,
4416 u32::MAX as u64,
4417 "bytes per column",
4418 ));
4419 }
4420
4421 expected_key_bits_host.push(0);
4422 expected_key_type_codes_host.push(key_col_type.to_code());
4423 tuple_key_match_modes_host.push(TUPLE_KEY_MATCH_MODE_BOUND_OUTPUT);
4424 bound_value_col_ptrs_host.push(*bound_col.device_ptr());
4425 bound_value_col_widths_host.push(bound_col_width as u32);
4426 }
4427 EirTerm::Anonymous => {
4428 expected_key_bits_host.push(0);
4433 expected_key_type_codes_host.push(key_col_type.to_code());
4434 tuple_key_match_modes_host.push(TUPLE_KEY_MATCH_MODE_WILDCARD);
4435 bound_value_col_ptrs_host.push(0);
4436 bound_value_col_widths_host.push(0);
4437 }
4438 term => {
4439 let expectation =
4440 TupleKeyExpectation::from_term(term, key_col_type)?;
4441 expected_key_bits_host.push(expectation.bits);
4442 expected_key_type_codes_host.push(expectation.type_code);
4443 tuple_key_match_modes_host.push(TUPLE_KEY_MATCH_MODE_GROUND);
4444 bound_value_col_ptrs_host.push(0);
4445 bound_value_col_widths_host.push(0);
4446 }
4447 }
4448 }
4449
4450 let memory = self.provider.memory();
4451 let mut key_col_ptrs = memory.alloc::<u64>(key_columns.len())?;
4452 let mut key_col_widths = memory.alloc::<u32>(key_columns.len())?;
4453 let mut expected_key_bits = memory.alloc::<u64>(key_columns.len())?;
4454 let mut expected_key_type_codes = memory.alloc::<u8>(key_columns.len())?;
4455 let mut tuple_key_match_modes = memory.alloc::<u8>(key_columns.len())?;
4456 let mut bound_value_col_ptrs = memory.alloc::<u64>(key_columns.len())?;
4457 let mut bound_value_col_widths = memory.alloc::<u32>(key_columns.len())?;
4458 self.provider
4459 .htod_launch_metadata_sync_copy_into(&key_col_ptrs_host, &mut key_col_ptrs)
4460 .map_err(|e| {
4461 XlogError::execution_ctx(
4462 "epistemic GPU tuple-key metadata",
4463 "upload key column pointers",
4464 &e,
4465 )
4466 })?;
4467 self.provider
4468 .htod_launch_metadata_sync_copy_into(
4469 &key_col_widths_host,
4470 &mut key_col_widths,
4471 )
4472 .map_err(|e| {
4473 XlogError::execution_ctx(
4474 "epistemic GPU tuple-key metadata",
4475 "upload key column widths",
4476 &e,
4477 )
4478 })?;
4479 self.provider
4480 .htod_launch_metadata_sync_copy_into(
4481 &expected_key_bits_host,
4482 &mut expected_key_bits,
4483 )
4484 .map_err(|e| {
4485 XlogError::execution_ctx(
4486 "epistemic GPU tuple-key metadata",
4487 "upload expected key bits",
4488 &e,
4489 )
4490 })?;
4491 self.provider
4492 .htod_launch_metadata_sync_copy_into(
4493 &expected_key_type_codes_host,
4494 &mut expected_key_type_codes,
4495 )
4496 .map_err(|e| {
4497 XlogError::execution_ctx(
4498 "epistemic GPU tuple-key metadata",
4499 "upload expected key type codes",
4500 &e,
4501 )
4502 })?;
4503 self.provider
4504 .htod_launch_metadata_sync_copy_into(
4505 &tuple_key_match_modes_host,
4506 &mut tuple_key_match_modes,
4507 )
4508 .map_err(|e| {
4509 XlogError::execution_ctx(
4510 "epistemic GPU tuple-key metadata",
4511 "upload tuple key match modes",
4512 &e,
4513 )
4514 })?;
4515 self.provider
4516 .htod_launch_metadata_sync_copy_into(
4517 &bound_value_col_ptrs_host,
4518 &mut bound_value_col_ptrs,
4519 )
4520 .map_err(|e| {
4521 XlogError::execution_ctx(
4522 "epistemic GPU tuple-key metadata",
4523 "upload bound value column pointers",
4524 &e,
4525 )
4526 })?;
4527 self.provider
4528 .htod_launch_metadata_sync_copy_into(
4529 &bound_value_col_widths_host,
4530 &mut bound_value_col_widths,
4531 )
4532 .map_err(|e| {
4533 XlogError::execution_ctx(
4534 "epistemic GPU tuple-key metadata",
4535 "upload bound value column widths",
4536 &e,
4537 )
4538 })?;
4539
4540 tuple_sources.push(TupleSourceLaunch::ArityN {
4541 literal_index: binding.literal_index as u32,
4542 reduction_index: binding.reduction_index as u32,
4543 negated: binding.negated as u8,
4544 row_count: source_relation.num_rows_device(),
4545 bound_value_row_count: output.num_rows_device(),
4546 key_col_count: key_columns.len() as u32,
4547 key_col_ptrs,
4548 key_col_widths,
4549 expected_key_bits,
4550 expected_key_type_codes,
4551 tuple_key_match_modes,
4552 bound_value_col_ptrs,
4553 bound_value_col_widths,
4554 has_bound_value_keys: has_bound_value_keys as u8,
4555 });
4556 }
4557 }
4558 }
4559
4560 let literal_count = literal_count as u32;
4561 let candidate_count = candidate_count as u32;
4562 let reduction_count = reduction_count as u32;
4563 let models_per_reduction = models_per_reduction as u32;
4564 let world_stride = world_stride as u32;
4565 let func = self
4566 .provider
4567 .device()
4568 .inner()
4569 .get_func(
4570 EPISTEMIC_MODULE,
4571 epistemic_kernels::EPISTEMIC_POPULATE_MODEL_MEMBERSHIP_FROM_TUPLE_SOURCE_U8,
4572 )
4573 .ok_or_else(|| {
4574 XlogError::Execution(
4575 "epistemic tuple-source model-membership kernel not found".to_string(),
4576 )
4577 })?;
4578 let func_arity1 = self
4579 .provider
4580 .device()
4581 .inner()
4582 .get_func(
4583 EPISTEMIC_MODULE,
4584 epistemic_kernels::EPISTEMIC_POPULATE_MODEL_MEMBERSHIP_FROM_TUPLE_SOURCE_ARITY1_U8,
4585 )
4586 .ok_or_else(|| {
4587 XlogError::Execution(
4588 "epistemic arity-one tuple-source model-membership kernel not found"
4589 .to_string(),
4590 )
4591 })?;
4592 let func_arity2 = self
4593 .provider
4594 .device()
4595 .inner()
4596 .get_func(
4597 EPISTEMIC_MODULE,
4598 epistemic_kernels::EPISTEMIC_POPULATE_MODEL_MEMBERSHIP_FROM_TUPLE_SOURCE_ARITY2_U8,
4599 )
4600 .ok_or_else(|| {
4601 XlogError::Execution(
4602 "epistemic arity-two tuple-source model-membership kernel not found"
4603 .to_string(),
4604 )
4605 })?;
4606 let func_arity3 = self
4607 .provider
4608 .device()
4609 .inner()
4610 .get_func(
4611 EPISTEMIC_MODULE,
4612 epistemic_kernels::EPISTEMIC_POPULATE_MODEL_MEMBERSHIP_FROM_TUPLE_SOURCE_ARITY3_U8,
4613 )
4614 .ok_or_else(|| {
4615 XlogError::Execution(
4616 "epistemic arity-three tuple-source model-membership kernel not found"
4617 .to_string(),
4618 )
4619 })?;
4620 let func_arity_n = self
4621 .provider
4622 .device()
4623 .inner()
4624 .get_func(
4625 EPISTEMIC_MODULE,
4626 epistemic_kernels::EPISTEMIC_POPULATE_MODEL_MEMBERSHIP_FROM_TUPLE_SOURCE_ARITY_N_U8,
4627 )
4628 .ok_or_else(|| {
4629 XlogError::Execution(
4630 "epistemic generic-arity tuple-source model-membership kernel not found"
4631 .to_string(),
4632 )
4633 })?;
4634 let config = LaunchConfig::for_num_elems(per_binding_launch_elems as u32);
4635
4636 let mut kernel_timings = Vec::with_capacity(tuple_sources.len());
4637 for tuple_source in &tuple_sources {
4638 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
4639 "epistemic GPU tuple-source model membership",
4640 || unsafe {
4641 match tuple_source {
4642 TupleSourceLaunch::ArityZero {
4643 literal_index,
4644 reduction_index,
4645 negated,
4646 row_count,
4647 } => {
4648 let mut params: Vec<*mut c_void> = vec![
4652 literal_count.as_kernel_param(),
4653 candidate_count.as_kernel_param(),
4654 reduction_count.as_kernel_param(),
4655 models_per_reduction.as_kernel_param(),
4656 world_stride.as_kernel_param(),
4657 literal_index.as_kernel_param(),
4658 reduction_index.as_kernel_param(),
4659 negated.as_kernel_param(),
4660 output.num_rows_device().as_kernel_param(),
4661 row_count.as_kernel_param(),
4662 (&workspace.candidate_assumptions).as_kernel_param(),
4663 (&workspace.world_views).as_kernel_param(),
4664 (&workspace.model_membership).as_kernel_param(),
4665 (&workspace.rejection_reasons).as_kernel_param(),
4666 ];
4667 func.clone().launch(config, &mut params)?;
4668 }
4669 TupleSourceLaunch::ArityOne {
4670 literal_index,
4671 reduction_index,
4672 negated,
4673 row_count,
4674 key_col0,
4675 key_col0_width,
4676 expected_key_col0_bits,
4677 expected_key_col0_type_code,
4678 } => {
4679 let mut params: Vec<*mut c_void> = vec![
4683 literal_count.as_kernel_param(),
4684 candidate_count.as_kernel_param(),
4685 reduction_count.as_kernel_param(),
4686 models_per_reduction.as_kernel_param(),
4687 world_stride.as_kernel_param(),
4688 literal_index.as_kernel_param(),
4689 reduction_index.as_kernel_param(),
4690 negated.as_kernel_param(),
4691 output.num_rows_device().as_kernel_param(),
4692 row_count.as_kernel_param(),
4693 key_col0.as_kernel_param(),
4694 key_col0_width.as_kernel_param(),
4695 expected_key_col0_bits.as_kernel_param(),
4696 expected_key_col0_type_code.as_kernel_param(),
4697 (&workspace.candidate_assumptions).as_kernel_param(),
4698 (&workspace.world_views).as_kernel_param(),
4699 (&workspace.model_membership).as_kernel_param(),
4700 (&workspace.rejection_reasons).as_kernel_param(),
4701 ];
4702 func_arity1.clone().launch(config, &mut params)?;
4703 }
4704 TupleSourceLaunch::ArityTwo {
4705 literal_index,
4706 reduction_index,
4707 negated,
4708 row_count,
4709 key_col0,
4710 key_col0_width,
4711 expected_key_col0_bits,
4712 expected_key_col0_type_code,
4713 key_col1,
4714 key_col1_width,
4715 expected_key_col1_bits,
4716 expected_key_col1_type_code,
4717 } => {
4718 let mut params: Vec<*mut c_void> = vec![
4722 literal_count.as_kernel_param(),
4723 candidate_count.as_kernel_param(),
4724 reduction_count.as_kernel_param(),
4725 models_per_reduction.as_kernel_param(),
4726 world_stride.as_kernel_param(),
4727 literal_index.as_kernel_param(),
4728 reduction_index.as_kernel_param(),
4729 negated.as_kernel_param(),
4730 output.num_rows_device().as_kernel_param(),
4731 row_count.as_kernel_param(),
4732 key_col0.as_kernel_param(),
4733 key_col0_width.as_kernel_param(),
4734 expected_key_col0_bits.as_kernel_param(),
4735 expected_key_col0_type_code.as_kernel_param(),
4736 key_col1.as_kernel_param(),
4737 key_col1_width.as_kernel_param(),
4738 expected_key_col1_bits.as_kernel_param(),
4739 expected_key_col1_type_code.as_kernel_param(),
4740 (&workspace.candidate_assumptions).as_kernel_param(),
4741 (&workspace.world_views).as_kernel_param(),
4742 (&workspace.model_membership).as_kernel_param(),
4743 (&workspace.rejection_reasons).as_kernel_param(),
4744 ];
4745 func_arity2.clone().launch(config, &mut params)?;
4746 }
4747 TupleSourceLaunch::ArityThree {
4748 literal_index,
4749 reduction_index,
4750 negated,
4751 row_count,
4752 key_col0,
4753 key_col0_width,
4754 expected_key_col0_bits,
4755 expected_key_col0_type_code,
4756 key_col1,
4757 key_col1_width,
4758 expected_key_col1_bits,
4759 expected_key_col1_type_code,
4760 key_col2,
4761 key_col2_width,
4762 expected_key_col2_bits,
4763 expected_key_col2_type_code,
4764 } => {
4765 let mut params: Vec<*mut c_void> = vec![
4769 literal_count.as_kernel_param(),
4770 candidate_count.as_kernel_param(),
4771 reduction_count.as_kernel_param(),
4772 models_per_reduction.as_kernel_param(),
4773 world_stride.as_kernel_param(),
4774 literal_index.as_kernel_param(),
4775 reduction_index.as_kernel_param(),
4776 negated.as_kernel_param(),
4777 output.num_rows_device().as_kernel_param(),
4778 row_count.as_kernel_param(),
4779 key_col0.as_kernel_param(),
4780 key_col0_width.as_kernel_param(),
4781 expected_key_col0_bits.as_kernel_param(),
4782 expected_key_col0_type_code.as_kernel_param(),
4783 key_col1.as_kernel_param(),
4784 key_col1_width.as_kernel_param(),
4785 expected_key_col1_bits.as_kernel_param(),
4786 expected_key_col1_type_code.as_kernel_param(),
4787 key_col2.as_kernel_param(),
4788 key_col2_width.as_kernel_param(),
4789 expected_key_col2_bits.as_kernel_param(),
4790 expected_key_col2_type_code.as_kernel_param(),
4791 (&workspace.candidate_assumptions).as_kernel_param(),
4792 (&workspace.world_views).as_kernel_param(),
4793 (&workspace.model_membership).as_kernel_param(),
4794 (&workspace.rejection_reasons).as_kernel_param(),
4795 ];
4796 func_arity3.clone().launch(config, &mut params)?;
4797 }
4798 TupleSourceLaunch::ArityN {
4799 literal_index,
4800 reduction_index,
4801 negated,
4802 row_count,
4803 bound_value_row_count,
4804 key_col_count,
4805 key_col_ptrs,
4806 key_col_widths,
4807 expected_key_bits,
4808 expected_key_type_codes,
4809 tuple_key_match_modes,
4810 bound_value_col_ptrs,
4811 bound_value_col_widths,
4812 has_bound_value_keys,
4813 } => {
4814 let mut params: Vec<*mut c_void> = vec![
4819 literal_count.as_kernel_param(),
4820 candidate_count.as_kernel_param(),
4821 reduction_count.as_kernel_param(),
4822 models_per_reduction.as_kernel_param(),
4823 world_stride.as_kernel_param(),
4824 literal_index.as_kernel_param(),
4825 reduction_index.as_kernel_param(),
4826 negated.as_kernel_param(),
4827 output.num_rows_device().as_kernel_param(),
4828 row_count.as_kernel_param(),
4829 key_col_ptrs.as_kernel_param(),
4830 key_col_widths.as_kernel_param(),
4831 expected_key_bits.as_kernel_param(),
4832 expected_key_type_codes.as_kernel_param(),
4833 tuple_key_match_modes.as_kernel_param(),
4834 bound_value_col_ptrs.as_kernel_param(),
4835 bound_value_col_widths.as_kernel_param(),
4836 bound_value_row_count.as_kernel_param(),
4837 key_col_count.as_kernel_param(),
4838 has_bound_value_keys.as_kernel_param(),
4839 (&workspace.candidate_assumptions).as_kernel_param(),
4840 (&workspace.world_views).as_kernel_param(),
4841 (&workspace.model_membership).as_kernel_param(),
4842 (&workspace.rejection_reasons).as_kernel_param(),
4843 ];
4844 func_arity_n.clone().launch(config, &mut params)?;
4845 }
4846 };
4847 Ok::<(), DriverError>(())
4848 },
4849 )?;
4850 kernel_timings.push(kernel_timing);
4851 }
4852 let kernel_timing = EpistemicGpuKernelTimingTrace::checked_sum(kernel_timings)?;
4853
4854 Ok(trace.with_kernel_timing(kernel_timing))
4855 }
4856
4857 pub fn validate_epistemic_gpu_world_views(
4859 &self,
4860 workspace: &mut EpistemicGpuWorkspace,
4861 gpu_plan: &EpistemicGpuPlan,
4862 candidate_count: usize,
4863 models_per_reduction: usize,
4864 ) -> Result<EpistemicGpuWorldViewValidationTrace> {
4865 gpu_plan.validate_tuple_membership_bindings()?;
4866 let literal_count = gpu_plan.epistemic_literals.len();
4867 let reduction_count = gpu_plan.reductions.len();
4868 let trace = EpistemicGpuWorldViewValidationTrace::for_counts(
4869 literal_count,
4870 candidate_count,
4871 reduction_count,
4872 models_per_reduction,
4873 )?;
4874 if trace.model_membership_bytes_checked > workspace.layout.model_membership_bytes {
4875 return Err(XlogError::ResourceExhausted {
4876 context: "epistemic GPU world-view validation membership workspace".to_string(),
4877 estimated_bytes: trace.model_membership_bytes_checked as u64,
4878 budget_bytes: workspace.layout.model_membership_bytes as u64,
4879 });
4880 }
4881 if trace.world_view_slots_checked > workspace.layout.world_view_bytes {
4882 return Err(XlogError::ResourceExhausted {
4883 context: "epistemic GPU world-view validation world-view workspace".to_string(),
4884 estimated_bytes: trace.world_view_slots_checked as u64,
4885 budget_bytes: workspace.layout.world_view_bytes as u64,
4886 });
4887 }
4888 if trace.rejection_reason_slots_written > workspace.layout.rejection_reason_slots {
4889 return Err(capacity_exceeded(
4890 "epistemic GPU world-view validation rejection workspace",
4891 trace.rejection_reason_slots_written as u64,
4892 workspace.layout.rejection_reason_slots as u64,
4893 "rejection slots",
4894 ));
4895 }
4896 if trace.model_membership_bytes_checked > u32::MAX as usize {
4897 return Err(capacity_exceeded(
4898 "epistemic GPU world-view validation membership launch",
4899 trace.model_membership_bytes_checked as u64,
4900 u32::MAX as u64,
4901 "launch elements",
4902 ));
4903 }
4904 if literal_count > u32::MAX as usize
4905 || candidate_count > u32::MAX as usize
4906 || reduction_count > u32::MAX as usize
4907 || models_per_reduction > u32::MAX as usize
4908 {
4909 return Err(capacity_exceeded(
4910 "epistemic GPU world-view validation dimensions",
4911 literal_count
4912 .max(candidate_count)
4913 .max(reduction_count)
4914 .max(models_per_reduction) as u64,
4915 u32::MAX as u64,
4916 "dimension elements",
4917 ));
4918 }
4919
4920 let mut literal_op_codes_host = vec![0u8; literal_count];
4921 let mut literal_negated_host = vec![0u8; literal_count];
4922 let mut literal_bound_to_output_host = vec![0u8; literal_count];
4923 let mut literal_reduction_indices_host = vec![0u32; literal_count];
4924 for binding in &gpu_plan.tuple_membership_bindings {
4925 literal_op_codes_host[binding.literal_index] = epistemic_operator_code(binding.op);
4926 literal_negated_host[binding.literal_index] = u8::from(binding.negated);
4927 literal_bound_to_output_host[binding.literal_index] =
4928 u8::from(binding.bound_output_columns.iter().any(Option::is_some));
4929 literal_reduction_indices_host[binding.literal_index] = binding.reduction_index as u32;
4930 }
4931 let memory = self.provider.memory();
4932 let mut literal_op_codes = memory.alloc::<u8>(literal_count)?;
4933 let mut literal_negated = memory.alloc::<u8>(literal_count)?;
4934 let mut literal_bound_to_output = memory.alloc::<u8>(literal_count)?;
4935 let mut literal_reduction_indices = memory.alloc::<u32>(literal_count)?;
4936 self.provider
4937 .htod_launch_metadata_sync_copy_into(&literal_op_codes_host, &mut literal_op_codes)
4938 .map_err(|e| {
4939 XlogError::execution_ctx(
4940 "epistemic GPU world-view validation metadata",
4941 "upload literal operator codes",
4942 &e,
4943 )
4944 })?;
4945 self.provider
4946 .htod_launch_metadata_sync_copy_into(&literal_negated_host, &mut literal_negated)
4947 .map_err(|e| {
4948 XlogError::execution_ctx(
4949 "epistemic GPU world-view validation metadata",
4950 "upload literal negation flags",
4951 &e,
4952 )
4953 })?;
4954 self.provider
4955 .htod_launch_metadata_sync_copy_into(
4956 &literal_bound_to_output_host,
4957 &mut literal_bound_to_output,
4958 )
4959 .map_err(|e| {
4960 XlogError::execution_ctx(
4961 "epistemic GPU world-view validation metadata",
4962 "upload literal output-binding flags",
4963 &e,
4964 )
4965 })?;
4966 self.provider
4967 .htod_launch_metadata_sync_copy_into(
4968 &literal_reduction_indices_host,
4969 &mut literal_reduction_indices,
4970 )
4971 .map_err(|e| {
4972 XlogError::execution_ctx(
4973 "epistemic GPU world-view validation metadata",
4974 "upload literal reduction indices",
4975 &e,
4976 )
4977 })?;
4978
4979 let world_stride =
4980 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
4981 require_positive_u32_dimension(
4982 world_stride,
4983 "epistemic GPU world-view validation world stride",
4984 )?;
4985
4986 let literal_count = literal_count as u32;
4987 let candidate_count = candidate_count as u32;
4988 let reduction_count = reduction_count as u32;
4989 let models_per_reduction = models_per_reduction as u32;
4990 let world_stride = world_stride as u32;
4991 let func = self
4992 .provider
4993 .device()
4994 .inner()
4995 .get_func(
4996 EPISTEMIC_MODULE,
4997 epistemic_kernels::EPISTEMIC_VALIDATE_WORLD_VIEWS_U8,
4998 )
4999 .ok_or_else(|| {
5000 XlogError::Execution("epistemic world-view validation kernel not found".to_string())
5001 })?;
5002 let config = LaunchConfig::for_num_elems(candidate_count);
5003
5004 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
5005 "epistemic GPU world-view validation",
5006 || unsafe {
5007 let mut params: Vec<*mut c_void> = vec![
5011 literal_count.as_kernel_param(),
5012 candidate_count.as_kernel_param(),
5013 reduction_count.as_kernel_param(),
5014 models_per_reduction.as_kernel_param(),
5015 world_stride.as_kernel_param(),
5016 (&literal_op_codes).as_kernel_param(),
5017 (&literal_negated).as_kernel_param(),
5018 (&literal_bound_to_output).as_kernel_param(),
5019 (&literal_reduction_indices).as_kernel_param(),
5020 (&workspace.candidate_assumptions).as_kernel_param(),
5021 (&workspace.model_membership).as_kernel_param(),
5022 (&workspace.world_views).as_kernel_param(),
5023 (&workspace.rejection_reasons).as_kernel_param(),
5024 ];
5025 func.clone().launch(config, &mut params)
5026 },
5027 )?;
5028
5029 Ok(trace.with_kernel_timing(kernel_timing))
5030 }
5031
5032 pub fn validate_epistemic_gpu_world_view_constraints(
5042 &self,
5043 workspace: &mut EpistemicGpuWorkspace,
5044 gpu_plan: &EpistemicGpuPlan,
5045 candidate_count: usize,
5046 ) -> Result<EpistemicGpuConstraintWorldViewValidationTrace> {
5047 gpu_plan.validate_constraints()?;
5048 let literal_count = gpu_plan.epistemic_literals.len();
5049 let constraint_count = gpu_plan.constraints.len();
5050
5051 if candidate_count > workspace.layout.rejection_reason_slots {
5060 return Err(capacity_exceeded(
5061 "epistemic GPU constraint-violation index workspace",
5062 candidate_count as u64,
5063 workspace.layout.rejection_reason_slots as u64,
5064 "candidate slots",
5065 ));
5066 }
5067 if candidate_count > 0 {
5068 let sentinel_host = vec![u32::MAX; candidate_count];
5069 let fill_len = candidate_count;
5070 let mut sentinel_view = workspace.constraint_violation_index.slice_mut(0..fill_len);
5071 self.provider
5072 .htod_launch_metadata_sync_copy_into(&sentinel_host, &mut sentinel_view)
5073 .map_err(|e| {
5074 XlogError::execution_ctx(
5075 "epistemic GPU world-view constraint metadata",
5076 "initialize constraint-violation index sentinel",
5077 &e,
5078 )
5079 })?;
5080 }
5081
5082 let mut offsets_host = Vec::with_capacity(constraint_count);
5084 let mut counts_host = Vec::with_capacity(constraint_count);
5085 let mut indices_host: Vec<u32> = Vec::new();
5086 for constraint in &gpu_plan.constraints {
5087 offsets_host.push(indices_host.len() as u32);
5088 counts_host.push(constraint.literal_indices.len() as u32);
5089 for &literal_index in &constraint.literal_indices {
5090 indices_host.push(literal_index as u32);
5091 }
5092 }
5093 let constraint_literal_refs = indices_host.len();
5094
5095 let trace = EpistemicGpuConstraintWorldViewValidationTrace {
5096 constraint_count,
5097 constraint_literal_refs,
5098 candidates_checked: candidate_count,
5099 rejection_reason_slots_written: candidate_count,
5100 kernel_launches: 0,
5101 host_write_ops: 0,
5102 kernel_timing: EpistemicGpuKernelTimingTrace::unrecorded(),
5103 };
5104
5105 if constraint_count == 0 {
5106 return Ok(trace);
5109 }
5110
5111 if candidate_count > workspace.layout.rejection_reason_slots {
5112 return Err(capacity_exceeded(
5113 "epistemic GPU world-view constraint rejection workspace",
5114 candidate_count as u64,
5115 workspace.layout.rejection_reason_slots as u64,
5116 "rejection slots",
5117 ));
5118 }
5119 if candidate_count > u32::MAX as usize
5120 || literal_count > u32::MAX as usize
5121 || constraint_count > u32::MAX as usize
5122 || constraint_literal_refs > u32::MAX as usize
5123 {
5124 return Err(capacity_exceeded(
5125 "epistemic GPU world-view constraint dimensions",
5126 candidate_count
5127 .max(literal_count)
5128 .max(constraint_count)
5129 .max(constraint_literal_refs) as u64,
5130 u32::MAX as u64,
5131 "dimension elements",
5132 ));
5133 }
5134
5135 let memory = self.provider.memory();
5136 let mut constraint_literal_offsets = memory.alloc::<u32>(constraint_count)?;
5137 let mut constraint_literal_counts = memory.alloc::<u32>(constraint_count)?;
5138 let mut constraint_literal_indices = memory.alloc::<u32>(constraint_literal_refs.max(1))?;
5139 self.provider
5140 .htod_launch_metadata_sync_copy_into(&offsets_host, &mut constraint_literal_offsets)
5141 .map_err(|e| {
5142 XlogError::execution_ctx(
5143 "epistemic GPU world-view constraint metadata",
5144 "upload constraint literal offsets",
5145 &e,
5146 )
5147 })?;
5148 self.provider
5149 .htod_launch_metadata_sync_copy_into(&counts_host, &mut constraint_literal_counts)
5150 .map_err(|e| {
5151 XlogError::execution_ctx(
5152 "epistemic GPU world-view constraint metadata",
5153 "upload constraint literal counts",
5154 &e,
5155 )
5156 })?;
5157 if !indices_host.is_empty() {
5158 self.provider
5159 .htod_launch_metadata_sync_copy_into(&indices_host, &mut constraint_literal_indices)
5160 .map_err(|e| {
5161 XlogError::execution_ctx(
5162 "epistemic GPU world-view constraint metadata",
5163 "upload constraint literal indices",
5164 &e,
5165 )
5166 })?;
5167 }
5168
5169 let literal_count_u32 = literal_count as u32;
5170 let candidate_count_u32 = candidate_count as u32;
5171 let constraint_count_u32 = constraint_count as u32;
5172 let func = self
5173 .provider
5174 .device()
5175 .inner()
5176 .get_func(
5177 EPISTEMIC_MODULE,
5178 epistemic_kernels::EPISTEMIC_VALIDATE_CONSTRAINTS_U8,
5179 )
5180 .ok_or_else(|| {
5181 XlogError::Execution(
5182 "epistemic world-view constraint validation kernel not found".to_string(),
5183 )
5184 })?;
5185 let config = LaunchConfig::for_num_elems(candidate_count_u32);
5186
5187 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
5188 "epistemic GPU world-view constraint validation",
5189 || unsafe {
5190 let mut params: Vec<*mut c_void> = vec![
5195 literal_count_u32.as_kernel_param(),
5196 candidate_count_u32.as_kernel_param(),
5197 constraint_count_u32.as_kernel_param(),
5198 (&constraint_literal_offsets).as_kernel_param(),
5199 (&constraint_literal_counts).as_kernel_param(),
5200 (&constraint_literal_indices).as_kernel_param(),
5201 (&workspace.candidate_assumptions).as_kernel_param(),
5202 (&workspace.rejection_reasons).as_kernel_param(),
5203 (&workspace.constraint_violation_index).as_kernel_param(),
5204 ];
5205 func.clone().launch(config, &mut params)
5206 },
5207 )?;
5208
5209 Ok(EpistemicGpuConstraintWorldViewValidationTrace {
5210 kernel_launches: 1,
5211 kernel_timing,
5212 ..trace
5213 })
5214 }
5215
5216 pub fn materialize_epistemic_gpu_candidates(
5218 &self,
5219 workspace: &mut EpistemicGpuWorkspace,
5220 candidate_count: usize,
5221 ) -> Result<EpistemicGpuMaterializationTrace> {
5222 let trace = EpistemicGpuMaterializationTrace::for_count(candidate_count)?;
5223 if trace.world_view_slots_written > workspace.layout.world_view_bytes {
5224 return Err(XlogError::ResourceExhausted {
5225 context: "epistemic GPU materialization world-view workspace".to_string(),
5226 estimated_bytes: trace.world_view_slots_written as u64,
5227 budget_bytes: workspace.layout.world_view_bytes as u64,
5228 });
5229 }
5230 if candidate_count > workspace.layout.rejection_reason_slots {
5231 return Err(capacity_exceeded(
5232 "epistemic GPU materialization rejection workspace",
5233 candidate_count as u64,
5234 workspace.layout.rejection_reason_slots as u64,
5235 "rejection slots",
5236 ));
5237 }
5238 if candidate_count > u32::MAX as usize {
5239 return Err(capacity_exceeded(
5240 "epistemic GPU materialization launch",
5241 candidate_count as u64,
5242 u32::MAX as u64,
5243 "candidates",
5244 ));
5245 }
5246
5247 let world_stride =
5248 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
5249 require_positive_u32_dimension(world_stride, "epistemic GPU materialization world stride")?;
5250
5251 let candidate_count = candidate_count as u32;
5252 let world_stride = world_stride as u32;
5253 let func = self
5254 .provider
5255 .device()
5256 .inner()
5257 .get_func(
5258 EPISTEMIC_MODULE,
5259 epistemic_kernels::EPISTEMIC_MATERIALIZE_ACCEPTED_CANDIDATES_U8,
5260 )
5261 .ok_or_else(|| {
5262 XlogError::Execution(
5263 "epistemic candidate materialization kernel not found".to_string(),
5264 )
5265 })?;
5266 let config = LaunchConfig::for_num_elems(candidate_count);
5267
5268 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
5269 "epistemic GPU candidate materialization",
5270 || unsafe {
5271 func.clone().launch(
5274 config,
5275 (
5276 candidate_count,
5277 world_stride,
5278 &workspace.rejection_reasons,
5279 &mut workspace.world_views,
5280 ),
5281 )
5282 },
5283 )?;
5284
5285 Ok(trace.with_kernel_timing(kernel_timing))
5286 }
5287
5288 pub fn materialize_epistemic_gpu_final_results(
5290 &self,
5291 workspace: &mut EpistemicGpuWorkspace,
5292 output: &CudaBuffer,
5293 candidate_count: usize,
5294 ) -> Result<EpistemicGpuFinalResultMaterializationTrace> {
5295 let trace = EpistemicGpuFinalResultMaterializationTrace::for_count(candidate_count)?;
5296 if trace.world_view_slots_written > workspace.layout.world_view_bytes {
5297 return Err(XlogError::ResourceExhausted {
5298 context: "epistemic GPU final-result world-view workspace".to_string(),
5299 estimated_bytes: trace.world_view_slots_written as u64,
5300 budget_bytes: workspace.layout.world_view_bytes as u64,
5301 });
5302 }
5303 if candidate_count > workspace.layout.rejection_reason_slots {
5304 return Err(capacity_exceeded(
5305 "epistemic GPU final-result rejection workspace",
5306 candidate_count as u64,
5307 workspace.layout.rejection_reason_slots as u64,
5308 "rejection slots",
5309 ));
5310 }
5311 if candidate_count > u32::MAX as usize {
5312 return Err(capacity_exceeded(
5313 "epistemic GPU final-result launch",
5314 candidate_count as u64,
5315 u32::MAX as u64,
5316 "candidates",
5317 ));
5318 }
5319
5320 let world_stride =
5321 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
5322 require_positive_u32_dimension(world_stride, "epistemic GPU final-result world stride")?;
5323
5324 let candidate_count = candidate_count as u32;
5325 let world_stride = world_stride as u32;
5326 let func = self
5327 .provider
5328 .device()
5329 .inner()
5330 .get_func(
5331 EPISTEMIC_MODULE,
5332 epistemic_kernels::EPISTEMIC_MATERIALIZE_FINAL_RESULT_FLAGS_U8,
5333 )
5334 .ok_or_else(|| {
5335 XlogError::Execution(
5336 "epistemic final-result materialization kernel not found".to_string(),
5337 )
5338 })?;
5339 let config = LaunchConfig::for_num_elems(candidate_count);
5340
5341 let kernel_timing = self.time_epistemic_gpu_kernel_launch(
5342 "epistemic GPU final result materialization",
5343 || unsafe {
5344 func.clone().launch(
5349 config,
5350 (
5351 candidate_count,
5352 world_stride,
5353 output.num_rows_device(),
5354 &workspace.rejection_reasons,
5355 &mut workspace.world_views,
5356 ),
5357 )
5358 },
5359 )?;
5360
5361 Ok(trace.with_kernel_timing(kernel_timing))
5362 }
5363
5364 #[expect(
5366 clippy::too_many_arguments,
5367 reason = "final tuple materialization passes independently typed source, schema, acceptance, and output contracts to one GPU operation"
5368 )]
5369 pub fn materialize_epistemic_gpu_final_tuples(
5370 &self,
5371 workspace: &mut EpistemicGpuWorkspace,
5372 output: &CudaBuffer,
5373 gpu_plan: &EpistemicGpuPlan,
5374 literal_count: usize,
5375 candidate_count: usize,
5376 reduction_count: usize,
5377 models_per_reduction: usize,
5378 ) -> Result<(CudaBuffer, EpistemicGpuFinalTupleMaterializationTrace)> {
5379 self.materialize_epistemic_gpu_final_tuples_scoped(
5380 workspace,
5381 output,
5382 gpu_plan,
5383 literal_count,
5384 candidate_count,
5385 reduction_count,
5386 models_per_reduction,
5387 None,
5388 )
5389 }
5390
5391 #[expect(
5404 clippy::too_many_arguments,
5405 reason = "scoped tuple materialization preserves the public operation's independent GPU inputs plus its accepted-world selector"
5406 )]
5407 fn materialize_epistemic_gpu_final_tuples_scoped(
5408 &self,
5409 workspace: &mut EpistemicGpuWorkspace,
5410 output: &CudaBuffer,
5411 gpu_plan: &EpistemicGpuPlan,
5412 literal_count: usize,
5413 candidate_count: usize,
5414 reduction_count: usize,
5415 models_per_reduction: usize,
5416 head_reduction_filter: Option<&BTreeSet<usize>>,
5417 ) -> Result<(CudaBuffer, EpistemicGpuFinalTupleMaterializationTrace)> {
5418 gpu_plan.validate_tuple_membership_bindings()?;
5419 if candidate_count > workspace.layout.rejection_reason_slots {
5420 return Err(capacity_exceeded(
5421 "epistemic GPU final-tuple rejection workspace",
5422 candidate_count as u64,
5423 workspace.layout.rejection_reason_slots as u64,
5424 "candidate slots",
5425 ));
5426 }
5427 let literal_count_u32 =
5428 checked_u32_dimension(literal_count, "epistemic GPU final-tuple literals")?;
5429 let candidate_count_u32 =
5430 checked_u32_dimension(candidate_count, "epistemic GPU final-tuple candidates")?;
5431 let reduction_count_u32 =
5432 checked_u32_dimension(reduction_count, "epistemic GPU final-tuple reductions")?;
5433 let models_per_reduction_u32 = checked_u32_dimension(
5434 models_per_reduction,
5435 "epistemic GPU final-tuple models per reduction",
5436 )?;
5437 let output_row_capacity = usize::try_from(output.num_rows()).map_err(|_| {
5438 capacity_exceeded(
5439 "epistemic GPU final-tuple output rows",
5440 output.num_rows(),
5441 usize::MAX as u64,
5442 "rows",
5443 )
5444 })?;
5445 let output_row_capacity_u32 =
5446 checked_u32_dimension(output_row_capacity, "epistemic GPU final-tuple output rows")?;
5447 let final_output_columns =
5448 final_output_columns_for_materialization(output, gpu_plan, head_reduction_filter)?;
5449 let mut tuple_bytes_capacity = 0usize;
5450 let mut source_columns: Vec<(&CudaColumn, u32, u32)> =
5451 Vec::with_capacity(final_output_columns.len());
5452 let mut result_columns_raw: Vec<TrackedCudaSlice<u8>> =
5453 Vec::with_capacity(final_output_columns.len());
5454 let mut final_schema_columns = Vec::with_capacity(final_output_columns.len());
5455 let mut final_schema_sort_labels = Vec::with_capacity(final_output_columns.len());
5456 for &col_idx in &final_output_columns {
5457 let src_col = output.column(col_idx).ok_or_else(|| {
5458 XlogError::Execution(format!("epistemic final tuple missing column {col_idx}"))
5459 })?;
5460 let (column_name, column_type) = output
5461 .schema()
5462 .columns
5463 .get(col_idx)
5464 .ok_or_else(|| {
5465 XlogError::Execution(format!(
5466 "epistemic final tuple missing schema column {col_idx}"
5467 ))
5468 })?
5469 .clone();
5470 let column_width = column_type.size_bytes();
5471 let expected_column_bytes = checked_product(output_row_capacity, column_width)?;
5472 if src_col.len() < expected_column_bytes {
5473 return Err(XlogError::ResourceExhausted {
5474 context: "epistemic GPU final-tuple column capacity".to_string(),
5475 estimated_bytes: expected_column_bytes as u64,
5476 budget_bytes: src_col.len() as u64,
5477 });
5478 }
5479 let column_byte_len =
5480 checked_u32_dimension(src_col.len(), "epistemic GPU final-tuple column")?;
5481 let column_width =
5482 checked_u32_dimension(column_width, "epistemic GPU final-tuple column width")?;
5483 tuple_bytes_capacity = checked_sum(tuple_bytes_capacity, src_col.len())?;
5484 source_columns.push((src_col, column_byte_len, column_width));
5485 result_columns_raw.push(self.provider.memory().alloc::<u8>(src_col.len())?);
5486 final_schema_columns.push((column_name, column_type));
5487 final_schema_sort_labels.push(
5488 output
5489 .schema()
5490 .column_sort_label(col_idx)
5491 .unwrap_or("")
5492 .to_string(),
5493 );
5494 }
5495
5496 let mut final_row_count = self.provider.memory().alloc::<u32>(1)?;
5497 let mut row_map = self
5498 .provider
5499 .memory()
5500 .alloc::<u32>(output_row_capacity.max(1))?;
5501 let row_filter_bindings: Vec<_> = gpu_plan
5502 .tuple_membership_bindings
5503 .iter()
5504 .filter(|binding| binding.bound_output_columns.iter().any(Option::is_some))
5505 .filter(|binding| {
5506 head_reduction_filter
5507 .map(|reductions| reductions.contains(&binding.reduction_index))
5508 .unwrap_or(true)
5509 })
5510 .collect();
5511 if row_filter_bindings.len() > u32::MAX as usize {
5512 return Err(capacity_exceeded(
5513 "epistemic GPU final tuple row-filter count",
5514 row_filter_bindings.len() as u64,
5515 u32::MAX as u64,
5516 "filters",
5517 ));
5518 }
5519 let negated_row_filter_count = row_filter_bindings
5520 .iter()
5521 .filter(|binding| binding.negated)
5522 .count();
5523 let trace = EpistemicGpuFinalTupleMaterializationTrace::for_counts(
5524 final_output_columns.len(),
5525 output_row_capacity,
5526 tuple_bytes_capacity,
5527 literal_count,
5528 candidate_count,
5529 reduction_count,
5530 models_per_reduction,
5531 )?
5532 .with_row_filter_counts(row_filter_bindings.len(), negated_row_filter_count)?;
5533 if trace.model_membership_bytes_checked > workspace.layout.model_membership_bytes {
5534 return Err(XlogError::ResourceExhausted {
5535 context: "epistemic GPU final-tuple membership workspace".to_string(),
5536 estimated_bytes: trace.model_membership_bytes_checked as u64,
5537 budget_bytes: workspace.layout.model_membership_bytes as u64,
5538 });
5539 }
5540 if trace.world_view_slots_checked > workspace.layout.world_view_bytes {
5541 return Err(XlogError::ResourceExhausted {
5542 context: "epistemic GPU final-tuple world-view workspace".to_string(),
5543 estimated_bytes: trace.world_view_slots_checked as u64,
5544 budget_bytes: workspace.layout.world_view_bytes as u64,
5545 });
5546 }
5547 if trace.model_membership_bytes_checked > u32::MAX as usize {
5548 return Err(capacity_exceeded(
5549 "epistemic GPU final-tuple membership launch",
5550 trace.model_membership_bytes_checked as u64,
5551 u32::MAX as u64,
5552 "byte launch elements",
5553 ));
5554 }
5555
5556 let world_stride =
5557 workspace.layout.world_view_bytes / workspace.layout.rejection_reason_slots;
5558 require_positive_u32_dimension(world_stride, "epistemic GPU final-tuple world stride")?;
5559 let world_stride =
5560 checked_u32_dimension(world_stride, "epistemic GPU final-tuple world stride")?;
5561 let mut metadata_len = 0usize;
5562 for binding in &row_filter_bindings {
5563 metadata_len = checked_sum(metadata_len, binding.key_columns.len())?;
5564 }
5565 let metadata_len = metadata_len.max(1);
5566 let row_filter_metadata_len = row_filter_bindings.len().max(1);
5567 checked_u32_dimension(
5568 metadata_len,
5569 "epistemic GPU final tuple row-filter key metadata",
5570 )?;
5571 checked_u32_dimension(
5572 row_filter_metadata_len,
5573 "epistemic GPU final tuple row-filter metadata",
5574 )?;
5575 let memory = self.provider.memory();
5576 let device = self.provider.device().inner();
5577 let mut tuple_source_row_count_ptrs = memory.alloc::<u64>(row_filter_metadata_len)?;
5578 let mut row_filter_negated = memory.alloc::<u8>(row_filter_metadata_len)?;
5579 let mut row_filter_key_offsets = memory.alloc::<u32>(row_filter_metadata_len)?;
5580 let mut row_filter_key_counts = memory.alloc::<u32>(row_filter_metadata_len)?;
5581 let mut key_col_ptrs = memory.alloc::<u64>(metadata_len)?;
5582 let mut key_col_widths = memory.alloc::<u32>(metadata_len)?;
5583 let mut expected_key_bits = memory.alloc::<u64>(metadata_len)?;
5584 let mut expected_key_type_codes = memory.alloc::<u8>(metadata_len)?;
5585 let mut tuple_key_match_modes = memory.alloc::<u8>(metadata_len)?;
5586 let mut bound_value_col_ptrs = memory.alloc::<u64>(metadata_len)?;
5587 let mut bound_value_col_widths = memory.alloc::<u32>(metadata_len)?;
5588 let row_filter_count = checked_u32_dimension(
5589 row_filter_bindings.len(),
5590 "epistemic GPU final tuple row-filter count",
5591 )?;
5592 let mut tuple_source_row_counts = Vec::with_capacity(row_filter_bindings.len());
5593
5594 if !row_filter_bindings.is_empty() {
5595 let mut tuple_source_row_count_ptrs_host =
5596 Vec::with_capacity(row_filter_bindings.len());
5597 let mut row_filter_negated_host = Vec::with_capacity(row_filter_bindings.len());
5598 let mut row_filter_key_offsets_host = Vec::with_capacity(row_filter_bindings.len());
5599 let mut row_filter_key_counts_host = Vec::with_capacity(row_filter_bindings.len());
5600 let mut key_col_ptrs_host = Vec::with_capacity(metadata_len);
5601 let mut key_col_widths_host = Vec::with_capacity(metadata_len);
5602 let mut expected_key_bits_host = Vec::with_capacity(metadata_len);
5603 let mut expected_key_type_codes_host = Vec::with_capacity(metadata_len);
5604 let mut tuple_key_match_modes_host = Vec::with_capacity(metadata_len);
5605 let mut bound_value_col_ptrs_host = Vec::with_capacity(metadata_len);
5606 let mut bound_value_col_widths_host = Vec::with_capacity(metadata_len);
5607
5608 for binding in &row_filter_bindings {
5609 let row_filter_key_offset = checked_u32_dimension(
5610 key_col_ptrs_host.len(),
5611 "epistemic GPU final tuple row-filter key offset",
5612 )?;
5613 let row_filter_key_count = checked_u32_dimension(
5614 binding.key_columns.len(),
5615 "epistemic GPU final tuple row-filter key arity",
5616 )?;
5617 row_filter_key_offsets_host.push(row_filter_key_offset);
5618 row_filter_key_counts_host.push(row_filter_key_count);
5619 row_filter_negated_host.push(binding.negated as u8);
5620
5621 let source_relation = self
5622 .resolve_modal_tuple_source(binding.predicate.as_str(), binding.arity)
5623 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5624 construct: "epistemic GPU final tuple row filtering".to_string(),
5625 context: format!(
5626 "missing tuple source relation {} (arity {}) for final row filter",
5627 binding.predicate, binding.arity
5628 ),
5629 })?;
5630 let tuple_source_row_count = self.clone_device_row_count(source_relation)?;
5631 tuple_source_row_count_ptrs_host.push(*tuple_source_row_count.device_ptr());
5632 tuple_source_row_counts.push(tuple_source_row_count);
5633
5634 for (term_index, &key_col) in binding.key_columns.iter().enumerate() {
5635 let key_col_ref = source_relation.column(key_col).ok_or_else(|| {
5636 XlogError::UnsupportedEpistemicConstruct {
5637 construct: "epistemic GPU final tuple row filtering".to_string(),
5638 context: format!(
5639 "tuple source relation {} missing key column {}",
5640 binding.predicate, key_col
5641 ),
5642 }
5643 })?;
5644 let key_col_type =
5645 source_relation
5646 .schema()
5647 .column_type(key_col)
5648 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5649 construct: "epistemic GPU final tuple row filtering".to_string(),
5650 context: format!(
5651 "tuple source relation {} missing schema for key column {}",
5652 binding.predicate, key_col
5653 ),
5654 })?;
5655 let key_col_width = key_col_type.size_bytes();
5656 if key_col_width > u32::MAX as usize {
5657 return Err(capacity_exceeded(
5658 "epistemic GPU final tuple row-filter key width",
5659 key_col_width as u64,
5660 u32::MAX as u64,
5661 "bytes per column",
5662 ));
5663 }
5664
5665 key_col_ptrs_host.push(*key_col_ref.device_ptr());
5666 key_col_widths_host.push(key_col_width as u32);
5667 match &binding.key_terms[term_index] {
5668 EirTerm::Variable(variable_name) => {
5669 let bound_col_index = binding.bound_output_columns[term_index]
5670 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5671 construct: "epistemic GPU final tuple row filtering"
5672 .to_string(),
5673 context: format!(
5674 "tuple key variable {variable_name} has no reduced \
5675 output column binding"
5676 ),
5677 })?;
5678 let bound_col = output.column(bound_col_index).ok_or_else(|| {
5679 XlogError::UnsupportedEpistemicConstruct {
5680 construct: "epistemic GPU final tuple row filtering"
5681 .to_string(),
5682 context: format!(
5683 "reduced output missing device column {bound_col_index} \
5684 for variable {variable_name}"
5685 ),
5686 }
5687 })?;
5688 let bound_col_type = output
5689 .schema()
5690 .column_type(bound_col_index)
5691 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5692 construct: "epistemic GPU final tuple row filtering"
5693 .to_string(),
5694 context: format!(
5695 "reduced output missing schema for variable \
5696 {variable_name}"
5697 ),
5698 })?;
5699 if bound_col_type != key_col_type {
5700 return Err(XlogError::UnsupportedEpistemicConstruct {
5701 construct: "epistemic GPU final tuple row filtering"
5702 .to_string(),
5703 context: format!(
5704 "bound variable {variable_name} has output type \
5705 {bound_col_type:?}, but tuple source {} key column {} \
5706 has type {key_col_type:?}",
5707 binding.predicate, key_col
5708 ),
5709 });
5710 }
5711 let bound_col_width = bound_col_type.size_bytes();
5712 if bound_col_width > u32::MAX as usize {
5713 return Err(capacity_exceeded(
5714 "epistemic GPU final tuple row-filter bound width",
5715 bound_col_width as u64,
5716 u32::MAX as u64,
5717 "bytes per column",
5718 ));
5719 }
5720 expected_key_bits_host.push(0);
5721 expected_key_type_codes_host.push(key_col_type.to_code());
5722 tuple_key_match_modes_host.push(TUPLE_KEY_MATCH_MODE_BOUND_OUTPUT);
5723 bound_value_col_ptrs_host.push(*bound_col.device_ptr());
5724 bound_value_col_widths_host.push(bound_col_width as u32);
5725 }
5726 EirTerm::Anonymous => {
5727 expected_key_bits_host.push(0);
5730 expected_key_type_codes_host.push(key_col_type.to_code());
5731 tuple_key_match_modes_host.push(TUPLE_KEY_MATCH_MODE_WILDCARD);
5732 bound_value_col_ptrs_host.push(0);
5733 bound_value_col_widths_host.push(0);
5734 }
5735 term => {
5736 let expectation = TupleKeyExpectation::from_term(term, key_col_type)?;
5737 expected_key_bits_host.push(expectation.bits);
5738 expected_key_type_codes_host.push(expectation.type_code);
5739 tuple_key_match_modes_host.push(TUPLE_KEY_MATCH_MODE_GROUND);
5740 bound_value_col_ptrs_host.push(0);
5741 bound_value_col_widths_host.push(0);
5742 }
5743 }
5744 }
5745 }
5746
5747 let metadata_context = "epistemic GPU final tuple row-filter metadata";
5748 self.provider
5749 .htod_launch_metadata_sync_copy_into(
5750 &tuple_source_row_count_ptrs_host,
5751 &mut tuple_source_row_count_ptrs,
5752 )
5753 .map_err(|e| {
5754 XlogError::execution_ctx(
5755 metadata_context,
5756 "upload tuple source row-count pointers",
5757 &e,
5758 )
5759 })?;
5760 self.provider
5761 .htod_launch_metadata_sync_copy_into(
5762 &row_filter_negated_host,
5763 &mut row_filter_negated,
5764 )
5765 .map_err(|e| {
5766 XlogError::execution_ctx(metadata_context, "upload row-filter polarity", &e)
5767 })?;
5768 self.provider
5769 .htod_launch_metadata_sync_copy_into(
5770 &row_filter_key_offsets_host,
5771 &mut row_filter_key_offsets,
5772 )
5773 .map_err(|e| {
5774 XlogError::execution_ctx(metadata_context, "upload row-filter key offsets", &e)
5775 })?;
5776 self.provider
5777 .htod_launch_metadata_sync_copy_into(
5778 &row_filter_key_counts_host,
5779 &mut row_filter_key_counts,
5780 )
5781 .map_err(|e| {
5782 XlogError::execution_ctx(metadata_context, "upload row-filter key counts", &e)
5783 })?;
5784 self.provider
5785 .htod_launch_metadata_sync_copy_into(&key_col_ptrs_host, &mut key_col_ptrs)
5786 .map_err(|e| {
5787 XlogError::execution_ctx(metadata_context, "upload key column pointers", &e)
5788 })?;
5789 self.provider
5790 .htod_launch_metadata_sync_copy_into(&key_col_widths_host, &mut key_col_widths)
5791 .map_err(|e| {
5792 XlogError::execution_ctx(metadata_context, "upload key column widths", &e)
5793 })?;
5794 self.provider
5795 .htod_launch_metadata_sync_copy_into(
5796 &expected_key_bits_host,
5797 &mut expected_key_bits,
5798 )
5799 .map_err(|e| {
5800 XlogError::execution_ctx(metadata_context, "upload expected key bits", &e)
5801 })?;
5802 self.provider
5803 .htod_launch_metadata_sync_copy_into(
5804 &expected_key_type_codes_host,
5805 &mut expected_key_type_codes,
5806 )
5807 .map_err(|e| {
5808 XlogError::execution_ctx(metadata_context, "upload expected key type codes", &e)
5809 })?;
5810 self.provider
5811 .htod_launch_metadata_sync_copy_into(
5812 &tuple_key_match_modes_host,
5813 &mut tuple_key_match_modes,
5814 )
5815 .map_err(|e| {
5816 XlogError::execution_ctx(metadata_context, "upload tuple key match modes", &e)
5817 })?;
5818 self.provider
5819 .htod_launch_metadata_sync_copy_into(
5820 &bound_value_col_ptrs_host,
5821 &mut bound_value_col_ptrs,
5822 )
5823 .map_err(|e| {
5824 XlogError::execution_ctx(
5825 metadata_context,
5826 "upload bound value column pointers",
5827 &e,
5828 )
5829 })?;
5830 self.provider
5831 .htod_launch_metadata_sync_copy_into(
5832 &bound_value_col_widths_host,
5833 &mut bound_value_col_widths,
5834 )
5835 .map_err(|e| {
5836 XlogError::execution_ctx(
5837 metadata_context,
5838 "upload bound value column widths",
5839 &e,
5840 )
5841 })?;
5842 } else {
5843 let metadata_context = "epistemic GPU final tuple row-filter metadata";
5844 device
5845 .memset_zeros(&mut tuple_source_row_count_ptrs)
5846 .map_err(|e| {
5847 XlogError::execution_ctx(
5848 metadata_context,
5849 "tuple source row-count pointer memset",
5850 &e,
5851 )
5852 })?;
5853 device.memset_zeros(&mut row_filter_negated).map_err(|e| {
5854 XlogError::execution_ctx(metadata_context, "row-filter polarity memset", &e)
5855 })?;
5856 device
5857 .memset_zeros(&mut row_filter_key_offsets)
5858 .map_err(|e| {
5859 XlogError::execution_ctx(metadata_context, "row-filter key offset memset", &e)
5860 })?;
5861 device
5862 .memset_zeros(&mut row_filter_key_counts)
5863 .map_err(|e| {
5864 XlogError::execution_ctx(metadata_context, "row-filter key count memset", &e)
5865 })?;
5866 device.memset_zeros(&mut key_col_ptrs).map_err(|e| {
5867 XlogError::execution_ctx(metadata_context, "key column pointer memset", &e)
5868 })?;
5869 device.memset_zeros(&mut key_col_widths).map_err(|e| {
5870 XlogError::execution_ctx(metadata_context, "key column width memset", &e)
5871 })?;
5872 device.memset_zeros(&mut expected_key_bits).map_err(|e| {
5873 XlogError::execution_ctx(metadata_context, "expected key bits memset", &e)
5874 })?;
5875 device
5876 .memset_zeros(&mut expected_key_type_codes)
5877 .map_err(|e| {
5878 XlogError::execution_ctx(metadata_context, "expected key type code memset", &e)
5879 })?;
5880 device
5881 .memset_zeros(&mut tuple_key_match_modes)
5882 .map_err(|e| {
5883 XlogError::execution_ctx(metadata_context, "tuple key match mode memset", &e)
5884 })?;
5885 device
5886 .memset_zeros(&mut bound_value_col_ptrs)
5887 .map_err(|e| {
5888 XlogError::execution_ctx(
5889 metadata_context,
5890 "bound value column pointer memset",
5891 &e,
5892 )
5893 })?;
5894 device
5895 .memset_zeros(&mut bound_value_col_widths)
5896 .map_err(|e| {
5897 XlogError::execution_ctx(
5898 metadata_context,
5899 "bound value column width memset",
5900 &e,
5901 )
5902 })?;
5903 }
5904
5905 let mut is_constraint_literal = vec![false; literal_count.max(1)];
5923 for constraint in &gpu_plan.constraints {
5924 for &literal_index in &constraint.literal_indices {
5925 if literal_index < literal_count {
5926 is_constraint_literal[literal_index] = true;
5927 }
5928 }
5929 }
5930 let mut gate_literal_required_host = vec![0u8; literal_count.max(1)];
5931 for binding in &gpu_plan.tuple_membership_bindings {
5932 if !binding.bound_output_columns.iter().any(Option::is_some)
5933 && binding.literal_index < literal_count
5934 && !is_constraint_literal[binding.literal_index]
5935 && head_reduction_filter
5936 .map(|reductions| reductions.contains(&binding.reduction_index))
5937 .unwrap_or(true)
5938 {
5939 gate_literal_required_host[binding.literal_index] = 1u8;
5940 }
5941 }
5942 let mut gate_literal_required = memory.alloc::<u8>(literal_count.max(1))?;
5950 self.provider
5951 .htod_launch_metadata_sync_copy_into(
5952 &gate_literal_required_host,
5953 &mut gate_literal_required,
5954 )
5955 .map_err(|e| {
5956 XlogError::execution_ctx(
5957 "epistemic GPU final tuple gate metadata",
5958 "upload global-gate literal mask",
5959 &e,
5960 )
5961 })?;
5962
5963 let row_map_func = self
5964 .provider
5965 .device()
5966 .inner()
5967 .get_func(
5968 EPISTEMIC_MODULE,
5969 epistemic_kernels::EPISTEMIC_BUILD_FINAL_TUPLE_ROW_MAP_U8,
5970 )
5971 .ok_or_else(|| {
5972 XlogError::Execution("epistemic final tuple row-map kernel not found".to_string())
5973 })?;
5974 let close_rejections_func = self
5975 .provider
5976 .device()
5977 .inner()
5978 .get_func(
5979 EPISTEMIC_MODULE,
5980 epistemic_kernels::EPISTEMIC_CLOSE_FINAL_TUPLE_REJECTIONS_U8,
5981 )
5982 .ok_or_else(|| {
5983 XlogError::Execution(
5984 "epistemic final tuple rejection-close kernel not found".to_string(),
5985 )
5986 })?;
5987 let func = self
5988 .provider
5989 .device()
5990 .inner()
5991 .get_func(
5992 EPISTEMIC_MODULE,
5993 epistemic_kernels::EPISTEMIC_MATERIALIZE_FINAL_TUPLE_COLUMN_U8,
5994 )
5995 .ok_or_else(|| {
5996 XlogError::Execution(
5997 "epistemic final tuple materialization kernel not found".to_string(),
5998 )
5999 })?;
6000
6001 let mut kernel_timings = Vec::with_capacity(checked_sum(source_columns.len(), 2)?);
6002 let row_map_timing = self.time_epistemic_gpu_kernel_launch(
6003 "epistemic GPU final tuple row map",
6004 || unsafe {
6005 self.provider
6006 .device()
6007 .inner()
6008 .memset_zeros(&mut final_row_count)?;
6009 self.provider.device().inner().memset_zeros(&mut row_map)?;
6010 let mut row_map_params: Vec<*mut c_void> = vec![
6011 output_row_capacity_u32.as_kernel_param(),
6012 literal_count_u32.as_kernel_param(),
6013 candidate_count_u32.as_kernel_param(),
6014 reduction_count_u32.as_kernel_param(),
6015 models_per_reduction_u32.as_kernel_param(),
6016 world_stride.as_kernel_param(),
6017 output.num_rows_device().as_kernel_param(),
6018 (&workspace.rejection_reasons).as_kernel_param(),
6019 (&workspace.model_membership).as_kernel_param(),
6020 (&workspace.world_views).as_kernel_param(),
6021 (&tuple_source_row_count_ptrs).as_kernel_param(),
6022 (&row_filter_negated).as_kernel_param(),
6023 (&row_filter_key_offsets).as_kernel_param(),
6024 (&row_filter_key_counts).as_kernel_param(),
6025 (&key_col_ptrs).as_kernel_param(),
6026 (&key_col_widths).as_kernel_param(),
6027 (&expected_key_bits).as_kernel_param(),
6028 (&expected_key_type_codes).as_kernel_param(),
6029 (&tuple_key_match_modes).as_kernel_param(),
6030 (&bound_value_col_ptrs).as_kernel_param(),
6031 (&bound_value_col_widths).as_kernel_param(),
6032 row_filter_count.as_kernel_param(),
6033 (&row_map).as_kernel_param(),
6034 (&final_row_count).as_kernel_param(),
6035 (&workspace.candidate_assumptions).as_kernel_param(),
6036 (&gate_literal_required).as_kernel_param(),
6037 ];
6038 row_map_func.clone().launch(
6039 LaunchConfig::for_num_elems(output_row_capacity_u32.max(1)),
6040 &mut row_map_params,
6041 )?;
6042 Ok::<(), xlog_cuda::device_runtime::ResourceError>(())
6043 },
6044 )?;
6045 kernel_timings.push(row_map_timing);
6046
6047 let close_rejections_timing = self.time_epistemic_gpu_kernel_launch(
6048 "epistemic GPU final tuple rejection closeout",
6049 || unsafe {
6050 let mut close_rejections_params: Vec<*mut c_void> = vec![
6051 candidate_count_u32.as_kernel_param(),
6052 world_stride.as_kernel_param(),
6053 (&final_row_count).as_kernel_param(),
6054 (&workspace.rejection_reasons).as_kernel_param(),
6055 (&workspace.world_views).as_kernel_param(),
6056 ];
6057 close_rejections_func.clone().launch(
6058 LaunchConfig::for_num_elems(candidate_count_u32.max(1)),
6059 &mut close_rejections_params,
6060 )?;
6061 Ok::<(), DriverError>(())
6062 },
6063 )?;
6064 kernel_timings.push(close_rejections_timing);
6065
6066 for ((src_col, column_byte_len, column_row_width), dst_col) in
6067 source_columns.iter().zip(result_columns_raw.iter_mut())
6068 {
6069 let column_timing = self.time_epistemic_gpu_kernel_launch(
6070 "epistemic GPU final tuple column materialization",
6071 || unsafe {
6072 let mut params: Vec<*mut c_void> = vec![
6077 column_byte_len.as_kernel_param(),
6078 column_row_width.as_kernel_param(),
6079 literal_count_u32.as_kernel_param(),
6080 candidate_count_u32.as_kernel_param(),
6081 reduction_count_u32.as_kernel_param(),
6082 models_per_reduction_u32.as_kernel_param(),
6083 world_stride.as_kernel_param(),
6084 output.num_rows_device().as_kernel_param(),
6085 (&workspace.rejection_reasons).as_kernel_param(),
6086 (&workspace.model_membership).as_kernel_param(),
6087 (&workspace.world_views).as_kernel_param(),
6088 (&row_map).as_kernel_param(),
6089 (*src_col).as_kernel_param(),
6090 dst_col.as_kernel_param(),
6091 (&final_row_count).as_kernel_param(),
6092 ];
6093 func.clone().launch(
6094 LaunchConfig::for_num_elems((*column_byte_len).max(1)),
6095 &mut params,
6096 )?;
6097 Ok::<(), DriverError>(())
6098 },
6099 )?;
6100 kernel_timings.push(column_timing);
6101 }
6102 let kernel_timing = EpistemicGpuKernelTimingTrace::checked_sum(kernel_timings)?;
6103
6104 let result_columns: Vec<CudaColumn> =
6105 result_columns_raw.into_iter().map(Into::into).collect();
6106 let final_schema = Schema::new(final_schema_columns)
6107 .with_sort_labels(final_schema_sort_labels)
6108 .map_err(|err| XlogError::Execution(format!("epistemic final schema: {err}")))?;
6109 let final_output = CudaBuffer::from_columns(
6110 result_columns,
6111 output.num_rows(),
6112 final_row_count,
6113 final_schema,
6114 );
6115 let final_output = if gpu_plan.final_output_columns.is_none() {
6116 final_output
6117 } else {
6118 self.provider.dedup_full_row(&final_output)?
6119 };
6120
6121 Ok((final_output, trace.with_kernel_timing(kernel_timing)))
6122 }
6123
6124 pub fn prepare_epistemic_gpu_execution(
6126 &self,
6127 executable: &EpistemicExecutablePlan,
6128 capacities: EpistemicGpuWorkspaceCapacities,
6129 ) -> Result<EpistemicGpuPreparedExecution> {
6130 let preflight = EpistemicGpuRuntimePreflight::for_executable_plan(executable, capacities)?;
6131 let mut workspace =
6132 self.allocate_epistemic_gpu_workspace(&executable.gpu_plan, capacities)?;
6133 let workspace_reset = self.reset_epistemic_gpu_workspace(&mut workspace)?;
6134
6135 Ok(EpistemicGpuPreparedExecution {
6136 preflight,
6137 tuple_membership_bindings: executable.gpu_plan.tuple_membership_bindings.clone(),
6138 workspace,
6139 workspace_reset,
6140 })
6141 }
6142
6143 fn validate_epistemic_gpu_reduced_constraints(
6144 &self,
6145 executable: &EpistemicExecutablePlan,
6146 ) -> Result<EpistemicGpuConstraintValidationTrace> {
6147 let mut checked_constraint_relations = 0usize;
6148 let mut violated_constraint_relations = 0usize;
6149 let mut row_count_device_reads = 0u32;
6150 let mut violations = Vec::new();
6151
6152 let mut relation_names = Vec::new();
6153 for rule in executable
6154 .reduced_runtime_plan
6155 .rules_by_scc
6156 .iter()
6157 .flatten()
6158 {
6159 if rule.head.starts_with(XLOG_CONSTRAINT_RELATION_PREFIX)
6160 && !relation_names.iter().any(|name| name == &rule.head)
6161 {
6162 relation_names.push(rule.head.as_str());
6163 }
6164 }
6165
6166 for relation_name in relation_names {
6167 checked_constraint_relations += 1;
6168 let relation = self.store().get(relation_name).ok_or_else(|| {
6169 XlogError::Execution(format!(
6170 "missing reduced constraint relation {relation_name} after production runtime \
6171 dispatch"
6172 ))
6173 })?;
6174 let row_count_was_cached = relation.cached_row_count().is_some();
6175 let rows = self.provider.device_row_count(relation)?;
6176 row_count_device_reads += u32::from(!row_count_was_cached);
6177 if rows > 0 {
6178 violated_constraint_relations += 1;
6179 let constraint_index = relation_name
6180 .strip_prefix(XLOG_CONSTRAINT_RELATION_PREFIX)
6181 .and_then(|suffix| suffix.parse::<usize>().ok())
6182 .ok_or_else(|| {
6183 XlogError::Execution(format!(
6184 "invalid compiler-generated constraint relation {relation_name}"
6185 ))
6186 })?;
6187 violations.push((constraint_index, relation_name.to_string(), rows));
6188 }
6189 }
6190
6191 if let Some((constraint_index, relation_name, witness_rows)) =
6192 violations.into_iter().min_by_key(|(index, _, _)| *index)
6193 {
6194 return Err(XlogError::ConstraintViolation {
6195 constraint_index,
6196 relation_name,
6197 witness_rows,
6198 });
6199 }
6200
6201 Ok(EpistemicGpuConstraintValidationTrace {
6202 checked_constraint_relations,
6203 violated_constraint_relations,
6204 row_count_device_reads,
6205 })
6206 }
6207
6208 pub fn materialize_epistemic_head_relation(
6221 &mut self,
6222 name: &str,
6223 gated_output: &CudaBuffer,
6224 ) -> Result<()> {
6225 let cloned = self.clone_buffer(gated_output)?;
6226 self.put_relation(name, cloned);
6227 Ok(())
6228 }
6229
6230 pub fn clone_store_relation(&self, buffer: &CudaBuffer) -> Result<CudaBuffer> {
6234 self.clone_buffer(buffer)
6235 }
6236
6237 pub fn execute_epistemic_gpu_execution(
6239 &mut self,
6240 executable: &EpistemicExecutablePlan,
6241 capacities: EpistemicGpuWorkspaceCapacities,
6242 ) -> Result<EpistemicGpuExecutionResult> {
6243 let mut prepared = self.prepare_epistemic_gpu_execution(executable, capacities)?;
6244 let literal_count = executable.gpu_plan.epistemic_literals.len();
6245 let candidate_count = bounded_candidate_count(literal_count, capacities.max_candidates)?;
6246 let transfer_budget_start = self.provider.host_transfer_stats();
6247 let launch_metadata_transfer_start = self.provider.host_launch_metadata_transfer_stats();
6248 let candidate_generation = self.generate_epistemic_gpu_candidates(
6249 &mut prepared.workspace,
6250 literal_count,
6251 candidate_count,
6252 )?;
6253 let propagation = self.propagate_epistemic_gpu_candidates(
6254 &mut prepared.workspace,
6255 literal_count,
6256 candidate_count,
6257 )?;
6258 let candidate_validation = self.validate_epistemic_gpu_candidates(
6259 &mut prepared.workspace,
6260 literal_count,
6261 candidate_count,
6262 )?;
6263 let counters_before = self.epistemic_gpu_runtime_counters();
6264 let _reduced_return = self.execute_plan(&executable.reduced_runtime_plan)?;
6265 let counters_after = self.epistemic_gpu_runtime_counters();
6266 let trace = EpistemicGpuRuntimeTrace::try_from_preflight_and_counters(
6267 prepared.preflight,
6268 counters_before,
6269 counters_after,
6270 )?;
6271 trace.require_wcoj_certification()?;
6272 let output_relation = executable
6273 .gpu_plan
6274 .reductions
6275 .last()
6276 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
6277 construct: "epistemic GPU reduced output".to_string(),
6278 context: "executable plan has no epistemic reductions".to_string(),
6279 })?
6280 .head_predicate
6281 .as_str();
6282 let output = {
6283 let reduced_output = self.store().get(output_relation).ok_or_else(|| {
6284 XlogError::UnsupportedEpistemicConstruct {
6285 construct: "epistemic GPU reduced output".to_string(),
6286 context: format!(
6287 "missing reduced output relation {output_relation} after production \
6288 runtime dispatch"
6289 ),
6290 }
6291 })?;
6292 self.clone_buffer(reduced_output)?
6293 };
6294 let model_membership = self.populate_epistemic_gpu_model_membership_from_tuple_sources(
6295 &mut prepared.workspace,
6296 &output,
6297 &executable.gpu_plan,
6298 candidate_count,
6299 capacities.max_models_per_reduction,
6300 )?;
6301 model_membership.require_stable_model_tuple_source()?;
6302 let expected_tuple_key_column_reads =
6303 expected_tuple_key_column_reads(&executable.gpu_plan.tuple_membership_bindings)?;
6304 model_membership.require_planned_tuple_key_column_reads(expected_tuple_key_column_reads)?;
6305 let world_view_validation = self.validate_epistemic_gpu_world_views(
6306 &mut prepared.workspace,
6307 &executable.gpu_plan,
6308 candidate_count,
6309 capacities.max_models_per_reduction,
6310 )?;
6311 let constraint_world_view_validation = self.validate_epistemic_gpu_world_view_constraints(
6312 &mut prepared.workspace,
6313 &executable.gpu_plan,
6314 candidate_count,
6315 )?;
6316 let materialization =
6317 self.materialize_epistemic_gpu_candidates(&mut prepared.workspace, candidate_count)?;
6318 let final_result_materialization = self.materialize_epistemic_gpu_final_results(
6319 &mut prepared.workspace,
6320 &output,
6321 candidate_count,
6322 )?;
6323 let head_reductions = epistemic_head_reduction_indices(&executable.gpu_plan);
6328 let is_multi_head = head_reductions.len() > 1;
6329 let primary_head_filter = if is_multi_head {
6330 head_reductions.get(output_relation).cloned()
6331 } else {
6332 None
6333 };
6334 let (final_output, final_tuple_materialization) = self
6335 .materialize_epistemic_gpu_final_tuples_scoped(
6336 &mut prepared.workspace,
6337 &output,
6338 &executable.gpu_plan,
6339 literal_count,
6340 candidate_count,
6341 executable.gpu_plan.reductions.len(),
6342 capacities.max_models_per_reduction,
6343 primary_head_filter.as_ref(),
6344 )?;
6345 let mut additional_head_outputs: Vec<(String, CudaBuffer)> = Vec::new();
6350 if is_multi_head {
6351 for (head, reductions) in &head_reductions {
6352 if head.as_str() == output_relation {
6353 continue;
6354 }
6355 let head_output = {
6356 let reduced_head = self.store().get(head.as_str()).ok_or_else(|| {
6357 XlogError::UnsupportedEpistemicConstruct {
6358 construct: "epistemic GPU reduced output".to_string(),
6359 context: format!(
6360 "missing reduced output relation {head} after production runtime \
6361 dispatch for joint multi-head materialization"
6362 ),
6363 }
6364 })?;
6365 self.clone_buffer(reduced_head)?
6366 };
6367 let (head_final_output, _head_trace) = self
6368 .materialize_epistemic_gpu_final_tuples_scoped(
6369 &mut prepared.workspace,
6370 &head_output,
6371 &executable.gpu_plan,
6372 literal_count,
6373 candidate_count,
6374 executable.gpu_plan.reductions.len(),
6375 capacities.max_models_per_reduction,
6376 Some(reductions),
6377 )?;
6378 additional_head_outputs.push((head.clone(), head_final_output));
6379 }
6380 }
6381 let tuple_evidence_output = if executable.gpu_plan.final_output_columns.is_some() {
6382 let mut evidence_plan = executable.gpu_plan.clone();
6383 evidence_plan.final_output_columns = None;
6384 let (evidence_output, _) = self.materialize_epistemic_gpu_final_tuples(
6385 &mut prepared.workspace,
6386 &output,
6387 &evidence_plan,
6388 literal_count,
6389 candidate_count,
6390 executable.gpu_plan.reductions.len(),
6391 capacities.max_models_per_reduction,
6392 )?;
6393 Some(evidence_output)
6394 } else {
6395 None
6396 };
6397 let transfer_budget_end = self.provider.host_transfer_stats();
6398 let launch_metadata_transfer_end = self.provider.host_launch_metadata_transfer_stats();
6399 let constraint_validation = self.validate_epistemic_gpu_reduced_constraints(executable)?;
6400 let transfer_budget =
6401 EpistemicGpuTransferBudgetTrace::from_host_transfer_stats_with_launch_metadata(
6402 candidate_count,
6403 transfer_budget_start,
6404 transfer_budget_end,
6405 launch_metadata_transfer_start,
6406 launch_metadata_transfer_end,
6407 )?;
6408 let final_result_transfer =
6409 EpistemicGpuFinalResultTransferTrace::from_final_output(&self.provider, &final_output)?;
6410 final_tuple_materialization.require_row_filter_materialization_evidence(
6411 "epistemic GPU final tuple materialization",
6412 final_result_transfer.final_output_rows,
6413 )?;
6414 let semantic_trace = EpistemicGpuSemanticTrace::from_device_rejection_reasons(
6415 &self.provider,
6416 &prepared.workspace,
6417 &candidate_generation,
6418 &propagation,
6419 &model_membership,
6420 &world_view_validation,
6421 )?;
6422
6423 Ok(EpistemicGpuExecutionResult {
6424 provider_identity: EpistemicGpuProviderIdentity::from_provider(&self.provider),
6425 prepared,
6426 candidate_generation,
6427 propagation,
6428 candidate_validation,
6429 model_membership,
6430 world_view_validation,
6431 constraint_world_view_validation,
6432 materialization,
6433 final_result_materialization,
6434 final_tuple_materialization,
6435 transfer_budget,
6436 final_result_transfer,
6437 constraint_validation,
6438 semantic_trace,
6439 tuple_membership_bindings: executable.gpu_plan.tuple_membership_bindings.clone(),
6440 final_output,
6441 additional_head_outputs,
6442 tuple_evidence_output,
6443 output,
6444 trace,
6445 })
6446 }
6447
6448 pub fn execute_epistemic_gpu_execution_batch(
6456 &mut self,
6457 executables: &[&EpistemicExecutablePlan],
6458 capacities: EpistemicGpuWorkspaceCapacities,
6459 ) -> Result<Vec<EpistemicGpuExecutionResult>> {
6460 if executables.is_empty() {
6461 return Err(XlogError::UnsupportedEpistemicConstruct {
6462 construct: "epistemic GPU batch execution".to_string(),
6463 context: "batch execution requires at least one executable component".to_string(),
6464 });
6465 }
6466
6467 let mut results = Vec::with_capacity(executables.len());
6468 for executable in executables {
6469 results.push(self.execute_epistemic_gpu_execution(executable, capacities)?);
6470 }
6471 Ok(results)
6472 }
6473
6474 pub fn execute_epistemic_gpu_execution_batch_with_trace(
6481 &mut self,
6482 executables: &[&EpistemicExecutablePlan],
6483 capacities: EpistemicGpuWorkspaceCapacities,
6484 ) -> Result<EpistemicGpuBatchExecutionResult> {
6485 let results = self.execute_epistemic_gpu_execution_batch(executables, capacities)?;
6486 let trace = EpistemicGpuBatchExecutionTrace::try_from_component_results(&results)?;
6487 Ok(EpistemicGpuBatchExecutionResult { results, trace })
6488 }
6489}
6490
6491#[derive(Default)]
6492struct RuntimeRouteSummary {
6493 multiway_reduction_count: usize,
6494 kclique_wcoj_plan_count: usize,
6495 wcoj_triangle_route_count: usize,
6496 wcoj_4cycle_route_count: usize,
6497 free_join_route_count: usize,
6498 kclique_wcoj_plan_count_by_arity: [usize; 4],
6499 kclique_wcoj_max_arity: u8,
6500 kclique_wcoj_edge_permutation_count: usize,
6501 kclique_stream_groups: BTreeSet<StreamGroupId>,
6502 kclique_skew_scheduled_plan_count: usize,
6503 planned_hash_route_count: usize,
6504 planned_hash_planner_wins_count: usize,
6505 planned_hash_incomplete_stats_count: usize,
6506 planned_hash_cost_evidence_count: usize,
6507 sorted_layout_requirement_count: usize,
6508 helper_split_spec_count: usize,
6509}
6510
6511fn summarize_runtime_routes(node: &RirNode, routes: &mut RuntimeRouteSummary) {
6512 match node {
6513 RirNode::MultiWayJoin { inputs, plan, .. } => {
6514 routes.multiway_reduction_count += 1;
6515 match plan {
6516 Some(MultiwayPlan::WcojWithPlan(order)) => {
6517 routes.kclique_wcoj_plan_count += 1;
6518 if let Some(slot) = usize::from(order.k).checked_sub(5) {
6519 if slot < routes.kclique_wcoj_plan_count_by_arity.len() {
6520 routes.kclique_wcoj_plan_count_by_arity[slot] += 1;
6521 }
6522 }
6523 routes.kclique_wcoj_max_arity = routes.kclique_wcoj_max_arity.max(order.k);
6524 routes.kclique_wcoj_edge_permutation_count += order
6525 .edge_permutation
6526 .iter()
6527 .take_while(|slot| **slot != u8::MAX)
6528 .count();
6529 routes.kclique_stream_groups.insert(order.stream_group);
6530 if !order.helper_split_specs.is_empty() {
6531 routes.kclique_skew_scheduled_plan_count += 1;
6532 }
6533 routes.sorted_layout_requirement_count +=
6534 order.sorted_layout_requirements.edge_slots.len();
6535 routes.helper_split_spec_count += order.helper_split_specs.len();
6536 }
6537 Some(MultiwayPlan::PlannedHashRoute {
6538 reason,
6539 planner_evidence,
6540 }) => {
6541 routes.planned_hash_route_count += 1;
6542 match reason {
6543 PlannedHashReason::PlannerPredictsHashWins => {
6544 routes.planned_hash_planner_wins_count += 1;
6545 if planner_evidence.wcoj_cost.is_finite()
6546 && planner_evidence.hash_cost.is_finite()
6547 && planner_evidence.hash_cost <= planner_evidence.wcoj_cost
6548 {
6549 routes.planned_hash_cost_evidence_count += 1;
6550 }
6551 }
6552 PlannedHashReason::IncompleteStatsSafeDefault => {
6553 routes.planned_hash_incomplete_stats_count += 1;
6554 }
6555 }
6556 }
6557 Some(MultiwayPlan::FreeJoin) => {
6563 routes.free_join_route_count += 1;
6564 }
6565 None => {
6566 if super::wcoj_dispatch::match_multiway_triangle(node).is_some() {
6567 routes.wcoj_triangle_route_count += 1;
6568 } else if super::wcoj_dispatch::match_multiway_4cycle(node).is_some() {
6569 routes.wcoj_4cycle_route_count += 1;
6570 }
6571 }
6572 }
6573
6574 for input in inputs {
6575 summarize_runtime_routes(input, routes);
6576 }
6577 }
6578 RirNode::Filter { input, .. }
6579 | RirNode::Project { input, .. }
6580 | RirNode::Distinct { input, .. }
6581 | RirNode::GroupBy { input, .. } => summarize_runtime_routes(input, routes),
6582 RirNode::Join { left, right, .. } | RirNode::Diff { left, right } => {
6583 summarize_runtime_routes(left, routes);
6584 summarize_runtime_routes(right, routes);
6585 }
6586 RirNode::Union { inputs } => {
6587 for input in inputs {
6588 summarize_runtime_routes(input, routes);
6589 }
6590 }
6591 RirNode::Fixpoint {
6592 base, recursive, ..
6593 } => {
6594 summarize_runtime_routes(base, routes);
6595 summarize_runtime_routes(recursive, routes);
6596 }
6597 RirNode::ChainJoin { left, right, .. } => {
6598 summarize_runtime_routes(left, routes);
6599 summarize_runtime_routes(right, routes);
6600 }
6601 RirNode::TensorMaskedJoin { .. } | RirNode::Scan { .. } | RirNode::Unit => {}
6602 }
6603}
6604
6605fn helper_relation_ids(executable: &EpistemicExecutablePlan) -> BTreeSet<RelId> {
6606 executable
6607 .relation_ids
6608 .iter()
6609 .filter_map(|(name, rel)| name.starts_with("__kclique_helper_").then_some(*rel))
6610 .collect()
6611}
6612
6613fn count_helper_relation_scans(node: &RirNode, helper_relations: &BTreeSet<RelId>) -> usize {
6614 match node {
6615 RirNode::Scan { .. } => 0,
6616 RirNode::MultiWayJoin { plan, inputs, .. } => {
6617 let own_wcoj_inputs = if matches!(plan, Some(MultiwayPlan::WcojWithPlan(_))) {
6618 inputs
6619 .iter()
6620 .map(|input| count_helper_relation_leaf_scans(input, helper_relations))
6621 .sum()
6622 } else {
6623 0
6624 };
6625 own_wcoj_inputs
6626 + inputs
6627 .iter()
6628 .map(|input| count_helper_relation_scans(input, helper_relations))
6629 .sum::<usize>()
6630 }
6631 RirNode::Filter { input, .. }
6632 | RirNode::Project { input, .. }
6633 | RirNode::Distinct { input, .. }
6634 | RirNode::GroupBy { input, .. } => count_helper_relation_scans(input, helper_relations),
6635 RirNode::Join { left, right, .. } | RirNode::Diff { left, right } => {
6636 count_helper_relation_scans(left, helper_relations)
6637 + count_helper_relation_scans(right, helper_relations)
6638 }
6639 RirNode::Union { inputs } => inputs
6640 .iter()
6641 .map(|input| count_helper_relation_scans(input, helper_relations))
6642 .sum(),
6643 RirNode::Fixpoint {
6644 base, recursive, ..
6645 } => {
6646 count_helper_relation_scans(base, helper_relations)
6647 + count_helper_relation_scans(recursive, helper_relations)
6648 }
6649 RirNode::ChainJoin { left, right, .. } => {
6650 count_helper_relation_scans(left, helper_relations)
6651 + count_helper_relation_scans(right, helper_relations)
6652 }
6653 RirNode::TensorMaskedJoin { .. } | RirNode::Unit => 0,
6654 }
6655}
6656
6657fn count_helper_relation_leaf_scans(node: &RirNode, helper_relations: &BTreeSet<RelId>) -> usize {
6658 match node {
6659 RirNode::Scan { rel } => usize::from(helper_relations.contains(rel)),
6660 RirNode::Filter { input, .. }
6661 | RirNode::Project { input, .. }
6662 | RirNode::Distinct { input, .. }
6663 | RirNode::GroupBy { input, .. } => {
6664 count_helper_relation_leaf_scans(input, helper_relations)
6665 }
6666 RirNode::Join { left, right, .. } | RirNode::Diff { left, right } => {
6667 count_helper_relation_leaf_scans(left, helper_relations)
6668 + count_helper_relation_leaf_scans(right, helper_relations)
6669 }
6670 RirNode::Union { inputs } => inputs
6671 .iter()
6672 .map(|input| count_helper_relation_leaf_scans(input, helper_relations))
6673 .sum(),
6674 RirNode::Fixpoint {
6675 base, recursive, ..
6676 } => {
6677 count_helper_relation_leaf_scans(base, helper_relations)
6678 + count_helper_relation_leaf_scans(recursive, helper_relations)
6679 }
6680 RirNode::MultiWayJoin { inputs, .. } => inputs
6681 .iter()
6682 .map(|input| count_helper_relation_leaf_scans(input, helper_relations))
6683 .sum(),
6684 RirNode::ChainJoin { left, right, .. } => {
6685 count_helper_relation_leaf_scans(left, helper_relations)
6686 + count_helper_relation_leaf_scans(right, helper_relations)
6687 }
6688 RirNode::TensorMaskedJoin { .. } | RirNode::Unit => 0,
6689 }
6690}
6691
6692fn require_positive(value: usize, context: &str) -> Result<()> {
6693 if value == 0 {
6694 return Err(capacity_exceeded(context, 1, 0, "items"));
6695 }
6696 Ok(())
6697}
6698
6699fn checked_u32_dimension(value: usize, context: &str) -> Result<u32> {
6700 u32::try_from(value)
6701 .map_err(|_| capacity_exceeded(context, value as u64, u32::MAX as u64, "elements"))
6702}
6703
6704fn require_positive_u32_dimension(value: usize, context: &str) -> Result<()> {
6705 if value == 0 {
6706 return Err(capacity_exceeded(context, 1, 0, "elements"));
6707 }
6708 checked_u32_dimension(value, context).map(|_| ())
6709}
6710
6711fn capacity_exceeded(
6712 context: impl Into<String>,
6713 required: u64,
6714 limit: u64,
6715 unit: &'static str,
6716) -> XlogError {
6717 XlogError::CapacityExceeded {
6718 context: context.into(),
6719 required,
6720 limit,
6721 unit: unit.to_string(),
6722 }
6723}
6724
6725fn epistemic_head_reduction_indices(
6731 gpu_plan: &EpistemicGpuPlan,
6732) -> std::collections::BTreeMap<String, BTreeSet<usize>> {
6733 let mut heads: std::collections::BTreeMap<String, BTreeSet<usize>> =
6734 std::collections::BTreeMap::new();
6735 for (reduction_index, reduction) in gpu_plan.reductions.iter().enumerate() {
6736 heads
6737 .entry(reduction.head_predicate.clone())
6738 .or_default()
6739 .insert(reduction_index);
6740 }
6741 heads
6742}
6743
6744fn final_output_columns_for_materialization(
6745 output: &CudaBuffer,
6746 gpu_plan: &EpistemicGpuPlan,
6747 head_reduction_filter: Option<&BTreeSet<usize>>,
6748) -> Result<Vec<usize>> {
6749 if let Some(filter) = head_reduction_filter {
6759 if let Some(public_head_arity) = gpu_plan
6760 .reductions
6761 .iter()
6762 .enumerate()
6763 .filter(|(reduction_index, _)| filter.contains(reduction_index))
6764 .map(|(_, reduction)| reduction.public_head_arity)
6765 .max()
6766 {
6767 if public_head_arity > output.arity() {
6768 return Err(XlogError::UnsupportedEpistemicConstruct {
6769 construct: "epistemic GPU final output projection".to_string(),
6770 context: format!(
6771 "per-head public arity {} exceeds reduced output arity {} for the \
6772 joint multi-head materialization",
6773 public_head_arity,
6774 output.arity()
6775 ),
6776 });
6777 }
6778 return Ok((0..public_head_arity).collect());
6779 }
6780 }
6781
6782 let Some(final_output_columns) = &gpu_plan.final_output_columns else {
6783 return Ok((0..output.arity()).collect());
6784 };
6785
6786 let mut seen = vec![false; output.arity()];
6787 for &column in final_output_columns {
6788 if column >= output.arity() {
6789 return Err(XlogError::UnsupportedEpistemicConstruct {
6790 construct: "epistemic GPU final output projection".to_string(),
6791 context: format!(
6792 "final output column {} exceeds reduced output arity {}",
6793 column,
6794 output.arity()
6795 ),
6796 });
6797 }
6798 if seen[column] {
6799 return Err(XlogError::UnsupportedEpistemicConstruct {
6800 construct: "epistemic GPU final output projection".to_string(),
6801 context: format!("duplicate final output column {}", column),
6802 });
6803 }
6804 seen[column] = true;
6805 }
6806
6807 Ok(final_output_columns.clone())
6808}
6809
6810fn require_u32_launch_bound(value: usize, context: &str) -> Result<()> {
6811 checked_u32_dimension(value, context).map(|_| ())
6812}
6813
6814fn require_u32_launch_dimensions(values: &[usize], context: &str) -> Result<()> {
6815 let max_value = values.iter().copied().max().unwrap_or(0);
6816 require_u32_launch_bound(max_value, context)
6817}
6818
6819fn checked_product(left: usize, right: usize) -> Result<usize> {
6820 left.checked_mul(right).ok_or_else(|| {
6821 XlogError::Kernel(format!(
6822 "epistemic GPU workspace size overflow: {left} * {right}"
6823 ))
6824 })
6825}
6826
6827fn checked_sum(left: usize, right: usize) -> Result<usize> {
6828 left.checked_add(right).ok_or_else(|| {
6829 XlogError::Kernel(format!(
6830 "epistemic GPU workspace size overflow: {left} + {right}"
6831 ))
6832 })
6833}
6834
6835fn require_epistemic_gpu_kernel_phases(gpu_plan: &EpistemicGpuPlan) -> Result<()> {
6836 let required = [
6837 EpistemicGpuHotPathPhase::CandidateGeneration,
6838 EpistemicGpuHotPathPhase::Propagation,
6839 EpistemicGpuHotPathPhase::CandidateValidation,
6840 EpistemicGpuHotPathPhase::ModelMembership,
6841 EpistemicGpuHotPathPhase::WorldViewValidation,
6842 EpistemicGpuHotPathPhase::ResultMaterialization,
6843 EpistemicGpuHotPathPhase::FinalResultMaterialization,
6844 EpistemicGpuHotPathPhase::FinalTupleMaterialization,
6845 ];
6846
6847 for phase in required {
6848 if !gpu_plan.required_kernel_phases.contains(&phase) {
6849 return Err(XlogError::UnsupportedEpistemicConstruct {
6850 construct: "epistemic GPU kernel phase contract".to_string(),
6851 context: format!(
6852 "accepted GPU execution requires kernel phase {:?}, but the plan declared {:?}",
6853 phase, gpu_plan.required_kernel_phases
6854 ),
6855 });
6856 }
6857 }
6858
6859 Ok(())
6860}
6861
6862fn require_epistemic_gpu_buffer_contract(gpu_plan: &EpistemicGpuPlan) -> Result<()> {
6863 let required = [
6864 EpistemicGpuBufferKind::CandidateAssumptions,
6865 EpistemicGpuBufferKind::WorldViews,
6866 EpistemicGpuBufferKind::ModelMembership,
6867 EpistemicGpuBufferKind::RejectionReasons,
6868 ];
6869
6870 for buffer in required {
6871 if !gpu_plan.required_buffers.contains(&buffer) {
6872 return Err(XlogError::UnsupportedEpistemicConstruct {
6873 construct: "epistemic GPU buffer contract".to_string(),
6874 context: format!(
6875 "accepted GPU execution requires buffer {:?}, but the plan declared {:?}",
6876 buffer, gpu_plan.required_buffers
6877 ),
6878 });
6879 }
6880 }
6881
6882 Ok(())
6883}
6884
6885fn expected_tuple_key_column_reads(bindings: &[EpistemicTupleMembershipBinding]) -> Result<usize> {
6886 bindings.iter().try_fold(0usize, |acc, binding| {
6887 checked_sum(acc, binding.key_columns.len())
6888 })
6889}
6890
6891fn world_view_bitset_bytes_per_candidate(literal_count: usize) -> Result<usize> {
6892 Ok(checked_sum(literal_count, 7)? / 8)
6893}
6894
6895fn epistemic_operator_code(op: EirEpistemicOp) -> u8 {
6896 match op {
6897 EirEpistemicOp::Know => 1,
6898 EirEpistemicOp::Possible => 2,
6899 }
6900}
6901
6902fn bounded_candidate_count(literal_count: usize, max_candidates: usize) -> Result<usize> {
6903 require_positive(literal_count, "epistemic GPU execution literals")?;
6904 require_positive(max_candidates, "epistemic GPU execution candidates")?;
6905 if literal_count > 31 {
6906 return Err(XlogError::UnsupportedEpistemicConstruct {
6907 construct: "epistemic GPU execution candidate generation".to_string(),
6908 context: format!("literal count {literal_count} exceeds 31-bit candidate mask"),
6909 });
6910 }
6911 let required_candidates = 1usize << literal_count;
6912 if max_candidates < required_candidates {
6913 return Err(capacity_exceeded(
6914 "epistemic GPU execution candidate capacity",
6915 required_candidates as u64,
6916 max_candidates as u64,
6917 "candidates",
6918 ));
6919 }
6920 Ok(required_candidates)
6921}
6922
6923#[cfg(test)]
6924mod tests {
6925 use super::*;
6926 use xlog_core::ScalarType;
6927 use xlog_ir::EirTerm;
6928
6929 #[test]
6930 fn tuple_key_expectation_encodes_ground_integer_for_u32_column() {
6931 let expectation =
6932 TupleKeyExpectation::from_term(&EirTerm::Integer(42), ScalarType::U32).unwrap();
6933
6934 assert_eq!(
6935 expectation,
6936 TupleKeyExpectation {
6937 bits: 42,
6938 type_code: ScalarType::U32.to_code(),
6939 }
6940 );
6941 }
6942
6943 #[test]
6944 fn tuple_key_expectation_encodes_symbol_for_symbol_column() {
6945 let expectation =
6946 TupleKeyExpectation::from_term(&EirTerm::Symbol(7), ScalarType::Symbol).unwrap();
6947
6948 assert_eq!(
6949 expectation,
6950 TupleKeyExpectation {
6951 bits: 7,
6952 type_code: ScalarType::Symbol.to_code(),
6953 }
6954 );
6955 }
6956
6957 #[test]
6958 fn tuple_key_expectation_rejects_variable_as_ground_expectation() {
6959 let err =
6960 TupleKeyExpectation::from_term(&EirTerm::Variable("X".to_string()), ScalarType::U32)
6961 .expect_err("variable tuple keys require bound-output matching");
6962
6963 match err {
6964 XlogError::UnsupportedEpistemicConstruct { construct, context } => {
6965 assert_eq!(construct, "epistemic GPU tuple-key expectation");
6966 assert!(context.contains("cannot be encoded as a ground tuple-key expectation"));
6967 }
6968 other => panic!("expected tuple-key expectation error, got {other:?}"),
6969 }
6970 }
6971}