use std::borrow::Cow;
use std::sync::atomic::{AtomicBool, Ordering};
use itertools::Itertools;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use stwo::core::air::Component;
use stwo::core::constraints::coset_vanishing;
use stwo::core::fields::m31::BaseField;
use stwo::core::pcs::TreeVec;
use stwo::core::poly::circle::CanonicCoset;
use stwo::core::utils::bit_reverse;
use stwo::prover::backend::gpu::conversion::secure_col_coords_mut_to_simd;
use stwo::prover::backend::gpu::GpuBackend;
use stwo::prover::backend::simd::column::VeryPackedSecureColumnByCoords;
use stwo::prover::backend::simd::m31::LOG_N_LANES;
use stwo::prover::backend::simd::very_packed_m31::{VeryPackedBaseField, LOG_N_VERY_PACKED_ELEMS};
use stwo::prover::poly::circle::{CircleEvaluation, PolyOps};
use stwo::prover::poly::BitReversedOrder;
use stwo::prover::{ColumnAccumulator, ComponentProver, DomainEvaluationAccumulator, Trace};
use tracing::{span, Level};
use super::GpuDomainEvaluator;
use crate::{FrameworkComponent, FrameworkEval, PREPROCESSED_TRACE_IDX};
#[cfg(feature = "cuda-runtime")]
use stwo::prover::backend::gpu::constraints::ConstraintKernel;
static USE_GPU_CONSTRAINT_KERNELS: AtomicBool = AtomicBool::new(false);
const GPU_KERNEL_MIN_DOMAIN_LOG_SIZE: u32 = 16;
pub fn set_gpu_constraint_kernels_enabled(enable: bool) {
USE_GPU_CONSTRAINT_KERNELS.store(enable, Ordering::SeqCst);
tracing::info!("GPU constraint kernels {}", if enable { "enabled" } else { "disabled" });
}
pub fn is_gpu_constraint_kernels_enabled() -> bool {
USE_GPU_CONSTRAINT_KERNELS.load(Ordering::SeqCst)
}
pub fn will_use_gpu_kernels(eval_domain_log_size: u32) -> bool {
is_gpu_constraint_kernels_enabled()
&& eval_domain_log_size >= GPU_KERNEL_MIN_DOMAIN_LOG_SIZE
&& cfg!(feature = "cuda-runtime")
}
const CHUNK_SIZE: usize = 1;
impl<E: FrameworkEval + Sync> ComponentProver<GpuBackend> for FrameworkComponent<E> {
fn evaluate_constraint_quotients_on_domain(
&self,
trace: &Trace<'_, GpuBackend>,
evaluation_accumulator: &mut DomainEvaluationAccumulator<GpuBackend>,
) {
if self.n_constraints() == 0 {
return;
}
let eval_domain = CanonicCoset::new(self.max_constraint_log_degree_bound()).circle_domain();
let trace_domain = CanonicCoset::new(self.eval.log_size());
let mut component_polys = trace.polys.sub_tree(&self.trace_locations);
component_polys[PREPROCESSED_TRACE_IDX] = self
.preprocessed_column_indices
.iter()
.map(|idx| &trace.polys[PREPROCESSED_TRACE_IDX][*idx])
.collect();
if std::env::var("STWO_DEBUG_GPU_COLUMNS").is_ok() {
eprintln!(
"[GPU_COMPONENT] preprocessed_column_indices={:?}, total_preprocessed={}, component_preprocessed={}, trace_locations={:?}",
self.preprocessed_column_indices,
trace.polys[PREPROCESSED_TRACE_IDX].len(),
component_polys[PREPROCESSED_TRACE_IDX].len(),
self.trace_locations,
);
}
let need_to_extend = component_polys
.iter()
.flatten()
.any(|c| c.evals.domain.log_size() != eval_domain.log_size());
let trace: TreeVec<
Vec<Cow<'_, CircleEvaluation<GpuBackend, BaseField, BitReversedOrder>>>,
> = if need_to_extend {
let _span = span!(Level::INFO, "GPU Constraint Extension").entered();
let twiddles = GpuBackend::precompute_twiddles(eval_domain.half_coset);
component_polys
.as_cols_ref()
.map_cols(|col| Cow::Owned(col.get_evaluation_on_domain(eval_domain, &twiddles)))
} else {
component_polys.map_cols(|c| Cow::Borrowed(&c.evals))
};
let log_expand = eval_domain.log_size() - trace_domain.log_size();
let mut denom_inv = (0..1 << log_expand)
.map(|i| coset_vanishing(trace_domain.coset(), eval_domain.at(i)).inverse())
.collect_vec();
bit_reverse(&mut denom_inv);
let [mut accum] =
evaluation_accumulator.columns([(eval_domain.log_size(), self.n_constraints())]);
accum.random_coeff_powers.reverse();
let _span = span!(
Level::INFO,
"GPU Constraint point-wise eval",
class = "GpuConstraintEval"
)
.entered();
let use_gpu_kernels = will_use_gpu_kernels(eval_domain.log_size());
if use_gpu_kernels {
tracing::info!(
"Using direct GPU constraint kernels for domain size 2^{}",
eval_domain.log_size()
);
#[cfg(feature = "cuda-runtime")]
{
self.evaluate_constraints_gpu_kernel(&trace, &denom_inv, &mut accum, trace_domain, eval_domain);
}
#[cfg(not(feature = "cuda-runtime"))]
{
self.evaluate_constraints_vectorized(&trace, &denom_inv, &mut accum, trace_domain, eval_domain);
}
} else {
self.evaluate_constraints_vectorized(&trace, &denom_inv, &mut accum, trace_domain, eval_domain);
}
}
}
impl<E: FrameworkEval + Sync> FrameworkComponent<E> {
fn evaluate_constraints_vectorized(
&self,
trace: &TreeVec<Vec<Cow<'_, CircleEvaluation<GpuBackend, BaseField, BitReversedOrder>>>>,
denom_inv: &[BaseField],
accum: &mut ColumnAccumulator<'_, GpuBackend>,
trace_domain: CanonicCoset,
eval_domain: stwo::core::poly::circle::CircleDomain,
) {
let _span = span!(Level::INFO, "GPU Vectorized Constraint Eval").entered();
let simd_col = secure_col_coords_mut_to_simd(accum.col);
let col = unsafe { VeryPackedSecureColumnByCoords::transform_under_mut(simd_col) };
let range = 0..(1 << (eval_domain.log_size() - LOG_N_LANES - LOG_N_VERY_PACKED_ELEMS));
#[cfg(not(feature = "parallel"))]
let iter = range.step_by(CHUNK_SIZE).zip(col.chunks_mut(CHUNK_SIZE));
#[cfg(feature = "parallel")]
let iter = range
.into_par_iter()
.step_by(CHUNK_SIZE)
.zip(col.chunks_mut(CHUNK_SIZE));
let self_eval = &self.eval;
let self_claimed_sum = self.claimed_sum;
iter.for_each(|(chunk_idx, mut chunk)| {
let trace_cols = trace.as_cols_ref().map_cols(|c| c.as_ref());
for idx_in_chunk in 0..CHUNK_SIZE {
let vec_row = chunk_idx * CHUNK_SIZE + idx_in_chunk;
let eval = GpuDomainEvaluator::new(
&trace_cols,
vec_row,
&accum.random_coeff_powers,
trace_domain.log_size(),
eval_domain.log_size(),
self_eval.log_size(),
self_claimed_sum,
);
let row_res = self_eval.evaluate(eval).row_res;
unsafe {
let denom = VeryPackedBaseField::broadcast(
denom_inv[vec_row
>> (trace_domain.log_size() - LOG_N_LANES - LOG_N_VERY_PACKED_ELEMS)],
);
chunk.set_packed(
idx_in_chunk,
chunk.packed_at(idx_in_chunk) + row_res * denom,
)
}
}
});
tracing::debug!(
"GPU vectorized constraint evaluation completed for {} rows",
1 << eval_domain.log_size()
);
}
#[cfg(feature = "cuda-runtime")]
fn evaluate_constraints_gpu_kernel(
&self,
trace: &TreeVec<Vec<Cow<'_, CircleEvaluation<GpuBackend, BaseField, BitReversedOrder>>>>,
denom_inv: &[BaseField],
accum: &mut ColumnAccumulator<'_, GpuBackend>,
trace_domain: CanonicCoset,
eval_domain: stwo::core::poly::circle::CircleDomain,
) {
let _span = span!(Level::INFO, "GPU Direct Kernel Constraint Eval").entered();
let device = match stwo::prover::backend::gpu::cuda_executor::get_cuda_executor() {
Ok(executor) => executor.device.clone(),
Err(e) => {
tracing::warn!("Failed to get CUDA device, falling back to vectorized: {}", e);
self.evaluate_constraints_vectorized(trace, denom_inv, accum, trace_domain, eval_domain);
return;
}
};
let kernel = match ConstraintKernel::new(device) {
Ok(k) => k,
Err(e) => {
tracing::warn!("Failed to compile constraint kernel, falling back to vectorized: {}", e);
self.evaluate_constraints_vectorized(trace, denom_inv, accum, trace_domain, eval_domain);
return;
}
};
let _ = kernel; self.evaluate_constraints_vectorized(trace, denom_inv, accum, trace_domain, eval_domain);
tracing::debug!(
"GPU kernel constraint infrastructure invoked for {} rows (using vectorized fallback)",
1 << eval_domain.log_size()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunk_size() {
assert_eq!(CHUNK_SIZE, 1);
}
#[test]
fn test_gpu_kernel_flag_default() {
set_gpu_constraint_kernels_enabled(false);
assert!(!is_gpu_constraint_kernels_enabled());
}
#[test]
fn test_gpu_kernel_flag_enable_disable() {
set_gpu_constraint_kernels_enabled(true);
assert!(is_gpu_constraint_kernels_enabled());
set_gpu_constraint_kernels_enabled(false);
assert!(!is_gpu_constraint_kernels_enabled());
}
#[test]
fn test_gpu_kernel_min_domain_size() {
assert!(GPU_KERNEL_MIN_DOMAIN_LOG_SIZE >= 14);
assert!(GPU_KERNEL_MIN_DOMAIN_LOG_SIZE <= 20);
}
#[test]
fn test_will_use_gpu_kernels() {
set_gpu_constraint_kernels_enabled(false);
assert!(!will_use_gpu_kernels(20));
set_gpu_constraint_kernels_enabled(true);
assert!(!will_use_gpu_kernels(10));
#[cfg(feature = "cuda-runtime")]
assert!(will_use_gpu_kernels(GPU_KERNEL_MIN_DOMAIN_LOG_SIZE));
#[cfg(not(feature = "cuda-runtime"))]
assert!(!will_use_gpu_kernels(GPU_KERNEL_MIN_DOMAIN_LOG_SIZE));
set_gpu_constraint_kernels_enabled(false);
}
}