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