1use std::borrow::BorrowMut;
2
3use itertools::Itertools;
4use openvm_stark_backend::keygen::types::{MultiStarkVerifyingKey, MultiStarkVerifyingKey0};
5use openvm_stark_sdk::config::baby_bear_poseidon2::{BabyBearPoseidon2Config, D_EF, EF, F};
6use p3_field::{BasedVectorSpace, PrimeCharacteristicRing};
7use p3_matrix::dense::RowMajorMatrix;
8
9use crate::{
10 batch_constraint::{
11 eq_airs::Eq3bBlob, expr_eval::interactions_folding::air::InteractionsFoldingCols,
12 BatchConstraintBlobCpu,
13 },
14 system::Preflight,
15 tracegen::RowMajorChip,
16 utils::{pow_tidx_count, MultiProofVecVec, MultiVecWithBounds},
17};
18
19#[derive(Copy, Clone)]
20#[repr(C)]
21pub(in crate::batch_constraint) struct InteractionsFoldingRecord {
22 value: EF,
23 air_idx: usize,
24 sort_idx: usize,
25 interaction_idx: usize,
26 node_idx: usize,
27 idx_in_message: usize,
28 has_interactions: bool,
29 is_first_in_air: bool,
30 is_last_in_air: bool,
31 is_mult: bool,
32 is_bus_index: bool,
33}
34
35pub(crate) struct InteractionsFoldingBlob {
36 pub(in crate::batch_constraint) records: MultiProofVecVec<InteractionsFoldingRecord>,
37 pub(in crate::batch_constraint) folded_claims: MultiProofVecVec<(isize, EF)>,
39}
40
41impl InteractionsFoldingBlob {
42 pub fn new(
43 vk: &MultiStarkVerifyingKey0<BabyBearPoseidon2Config>,
44 expr_evals: &MultiVecWithBounds<EF, 2>,
45 eq_3b_blob: &Eq3bBlob,
46 preflights: &[&Preflight],
47 ) -> Self {
48 let l_skip = vk.params.l_skip;
49 let interactions = vk
50 .per_air
51 .iter()
52 .map(|vk| vk.symbolic_constraints.interactions.clone())
53 .collect_vec();
54
55 let logup_pow_offset = pow_tidx_count(vk.params.logup.pow_bits);
56 let mut records = MultiProofVecVec::new();
57 let mut folded = MultiProofVecVec::new();
58 for (pidx, preflight) in preflights.iter().enumerate() {
59 let beta_tidx = preflight.proof_shape.post_tidx + logup_pow_offset + D_EF;
60 let beta = EF::from_basis_coefficients_slice(
61 &preflight.transcript.values()[beta_tidx..beta_tidx + D_EF],
62 )
63 .unwrap();
64
65 let eq_3bs = &eq_3b_blob.all_stacked_ids[pidx];
66 let mut cur_eq3b_idx = 0;
67
68 let vdata = &preflight.proof_shape.sorted_trace_vdata;
69 for (sort_idx, (air_idx, vdata)) in vdata.iter().enumerate() {
70 let n = vdata.log_height as isize - l_skip as isize;
71 let inters = &interactions[*air_idx];
72 let mut num_sum = EF::ZERO;
73 let mut denom_sum = EF::ZERO;
74 if inters.is_empty() {
75 records.push(InteractionsFoldingRecord {
76 value: EF::ZERO,
77 air_idx: *air_idx,
78 sort_idx,
79 interaction_idx: 0,
80 node_idx: 0,
81 idx_in_message: 0,
82 has_interactions: false,
83 is_first_in_air: true,
84 is_last_in_air: true,
85 is_mult: false,
86 is_bus_index: false,
87 });
88 cur_eq3b_idx += 1;
89 } else {
90 for (interaction_idx, inter) in inters.iter().enumerate() {
94 let eq_3b = eq_3bs[cur_eq3b_idx].eq_mle(
95 &preflight.batch_constraint.xi,
96 vk.params.l_skip,
97 preflight.proof_shape.n_logup,
98 );
99 cur_eq3b_idx += 1;
100 records.push(InteractionsFoldingRecord {
101 value: expr_evals[[pidx, *air_idx]][inter.count],
102 air_idx: *air_idx,
103 sort_idx,
104 interaction_idx,
105 node_idx: inter.count,
106 idx_in_message: 0,
107 has_interactions: true,
108 is_first_in_air: interaction_idx == 0,
109 is_last_in_air: false,
110 is_mult: true, is_bus_index: false,
113 });
114 num_sum += expr_evals[[pidx, *air_idx]][inter.count] * eq_3b;
115
116 let mut beta_pow = EF::ONE;
117 let mut cur_sum = EF::ZERO;
118 for (j, &node_idx) in inter.message.iter().enumerate() {
119 let value = expr_evals[[pidx, *air_idx]][node_idx];
120 cur_sum += beta_pow * value;
121 beta_pow *= beta;
122 records.push(InteractionsFoldingRecord {
123 value,
124 air_idx: *air_idx,
125 sort_idx,
126 interaction_idx,
127 node_idx,
128 idx_in_message: j,
129 has_interactions: true,
130 is_first_in_air: false,
131 is_last_in_air: false,
132 is_mult: false,
133 is_bus_index: false,
134 });
135 }
136
137 cur_sum += beta_pow * EF::from_u16(inter.bus_index + 1);
138 records.push(InteractionsFoldingRecord {
139 value: EF::from_u16(inter.bus_index + 1),
140 air_idx: *air_idx,
141 sort_idx,
142 interaction_idx,
143 node_idx: inter.bus_index as usize + 1,
144 idx_in_message: inter.message.len(),
145 has_interactions: true,
146 is_first_in_air: false,
147 is_last_in_air: interaction_idx + 1 == inters.len(),
148 is_mult: false,
149 is_bus_index: true,
150 });
151 denom_sum += cur_sum * eq_3b;
152 }
153 }
154 folded.push((n, num_sum));
156 folded.push((n, denom_sum));
157 }
158 folded.end_proof();
159 records.end_proof();
160 }
161 Self {
162 records,
163 folded_claims: folded,
164 }
165 }
166}
167
168pub struct InteractionsFoldingTraceGenerator;
169
170impl RowMajorChip<F> for InteractionsFoldingTraceGenerator {
171 type Ctx<'a> = (
172 &'a MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
173 &'a BatchConstraintBlobCpu,
174 &'a [&'a Preflight],
175 );
176
177 #[tracing::instrument(level = "trace", skip_all)]
178 fn generate_trace(
179 &self,
180 ctx: &Self::Ctx<'_>,
181 required_height: Option<usize>,
182 ) -> Option<RowMajorMatrix<F>> {
183 let (vk, blob, preflights) = ctx;
184 let eq_3b_blob = &blob.common_blob.eq_3b_blob;
185 let if_blob = blob.if_blob.as_ref().unwrap();
186
187 let width = InteractionsFoldingCols::<F>::width();
188
189 let total_height = if_blob.records.len();
190 let padding_height = if let Some(height) = required_height {
191 if height < total_height {
192 return None;
193 }
194 height
195 } else {
196 total_height.next_power_of_two()
197 };
198 let mut trace = vec![F::ZERO; padding_height * width];
199
200 let logup_pow_offset = pow_tidx_count(vk.inner.params.logup.pow_bits);
201 let mut cur_height = 0;
202 for (pidx, preflight) in preflights.iter().enumerate() {
203 let beta_tidx = preflight.proof_shape.post_tidx + logup_pow_offset + D_EF;
204 let beta_slice = &preflight.transcript.values()[beta_tidx..beta_tidx + D_EF];
205 let records = &if_blob.records[pidx];
206 let eq_3bs = &eq_3b_blob.all_stacked_ids[pidx];
207
208 let mut is_first_in_message_indices = vec![];
209 let mut cur_eq3b_idx = -1i32;
210 let mut was_first_interaction_in_message = false;
211 trace[cur_height * width..(cur_height + records.len()) * width]
212 .chunks_exact_mut(width)
213 .enumerate()
214 .for_each(|(i, chunk)| {
215 let cols: &mut InteractionsFoldingCols<_> = chunk.borrow_mut();
216 let record = &records[i];
217 cols.is_valid = F::ONE;
218 cols.proof_idx = F::from_usize(pidx);
219 cols.beta_tidx = F::from_usize(beta_tidx);
220 cols.air_idx = F::from_usize(record.air_idx);
221 cols.sort_idx = F::from_usize(record.sort_idx);
222 cols.interaction_idx = F::from_usize(record.interaction_idx);
223 cols.has_interactions = F::from_bool(record.has_interactions);
224 cols.is_first_in_air = F::from_bool(record.is_first_in_air);
225 cols.is_first_in_message =
226 F::from_bool(record.is_mult || !record.has_interactions);
227 cols.is_second_in_message = F::from_bool(was_first_interaction_in_message);
228 was_first_interaction_in_message = record.is_mult;
229 cols.is_bus_index = F::from_bool(record.is_bus_index);
230 cols.idx_in_message = F::from_usize(record.idx_in_message);
231 cols.value
232 .copy_from_slice(record.value.as_basis_coefficients_slice());
233 cols.beta.copy_from_slice(beta_slice);
234
235 if !record.has_interactions || record.is_mult {
236 cur_eq3b_idx += 1;
237 }
238 if record.has_interactions {
239 cols.eq_3b.copy_from_slice(
240 eq_3bs[cur_eq3b_idx as usize]
241 .eq_mle(
242 &preflight.batch_constraint.xi,
243 vk.inner.params.l_skip,
244 preflight.proof_shape.n_logup,
245 )
246 .as_basis_coefficients_slice(),
247 );
248 }
249
250 if cols.is_first_in_message == F::ONE && record.has_interactions {
251 is_first_in_message_indices.push(i);
252 }
253 });
254
255 let mut cur_sum = EF::ZERO;
257 let beta = EF::from_basis_coefficients_slice(beta_slice).unwrap();
258 let mut cur_acc_num = EF::ZERO;
259 let mut cur_acc_denom = EF::ZERO;
260 trace[cur_height * width..(cur_height + records.len()) * width]
261 .chunks_exact_mut(width)
262 .enumerate()
263 .rev()
264 .for_each(|(i, chunk)| {
265 let cols: &mut InteractionsFoldingCols<_> = chunk.borrow_mut();
266 if cols.is_first_in_message == F::ONE {
268 cols.cur_sum.copy_from_slice(&cols.value);
269 cur_sum = EF::ZERO;
270 } else {
271 cur_sum = cur_sum * beta
272 + EF::from_basis_coefficients_slice(&cols.value).unwrap();
273 cols.cur_sum
274 .copy_from_slice(cur_sum.as_basis_coefficients_slice());
275 }
276
277 if cols.is_first_in_message == F::ONE {
279 cur_acc_num += EF::from_basis_coefficients_slice(&cols.cur_sum).unwrap()
281 * EF::from_basis_coefficients_slice(&cols.eq_3b).unwrap();
282 if cols.has_interactions == F::ZERO {
283 debug_assert_eq!(cols.cur_sum, [F::ZERO; D_EF]);
284 }
285 } else if is_first_in_message_indices.contains(&(i - 1)) {
286 cur_acc_denom += EF::from_basis_coefficients_slice(&cols.cur_sum).unwrap()
288 * EF::from_basis_coefficients_slice(&cols.eq_3b).unwrap();
289 }
290 cols.final_acc_num
291 .copy_from_slice(cur_acc_num.as_basis_coefficients_slice());
292 cols.final_acc_denom
293 .copy_from_slice(cur_acc_denom.as_basis_coefficients_slice());
294
295 if cols.is_first_in_air == F::ONE {
297 cur_acc_num = EF::ZERO;
298 cur_acc_denom = EF::ZERO;
299 }
300 });
301
302 {
304 let cols: &mut InteractionsFoldingCols<_> =
305 trace[cur_height * width..(cur_height + 1) * width].borrow_mut();
306 cols.is_first = F::ONE;
307 }
308 cur_height += records.len();
309 }
310 Some(RowMajorMatrix::new(trace, width))
311 }
312}
313
314#[cfg(feature = "cuda")]
315pub(in crate::batch_constraint) mod cuda {
316 use openvm_circuit_primitives::cuda_abi::UInt2;
317 use openvm_cuda_backend::{base::DeviceMatrix, GpuBackend};
318 use openvm_cuda_common::{copy::MemCopyH2D, d_buffer::DeviceBuffer, stream::GpuDeviceCtx};
319 use openvm_stark_backend::prover::AirProvingContext;
320
321 use super::*;
322 use crate::{
323 batch_constraint::cuda_abi::{
324 interactions_folding_tracegen, interactions_folding_tracegen_temp_bytes, AffineFpExt,
325 FpExtWithTidx, InteractionRecord,
326 },
327 cuda::{preflight::PreflightGpu, vk::VerifyingKeyGpu},
328 tracegen::ModuleChip,
329 utils::interaction_length,
330 };
331
332 pub struct InteractionsFoldingBlobGpu {
333 pub values: Vec<Vec<Vec<Vec<EF>>>>,
335 pub interaction_records: Vec<Vec<InteractionRecord>>,
337 pub interactions_folding_per_proof: Vec<FpExtWithTidx>,
339 pub folded_claims: MultiProofVecVec<(isize, EF)>,
341 pub num_valid_rows: usize,
343 }
344
345 impl InteractionsFoldingBlobGpu {
346 pub fn new(
347 vk: &VerifyingKeyGpu,
348 expr_evals: &MultiVecWithBounds<EF, 2>,
349 eq_3b_blob: &Eq3bBlob,
350 preflights: &[PreflightGpu],
351 _device_ctx: &GpuDeviceCtx,
352 ) -> Self {
353 let l_skip = vk.system_params.l_skip;
354 let interactions = vk
355 .cpu
356 .inner
357 .per_air
358 .iter()
359 .map(|vk| vk.symbolic_constraints.interactions.clone())
360 .collect_vec();
361
362 let mut global_current_row = 0;
363
364 let mut values = Vec::with_capacity(preflights.len());
365 let mut interaction_records = Vec::with_capacity(preflights.len());
366 let mut interactions_folding_per_proof = Vec::with_capacity(preflights.len());
367 let mut folded_claims = MultiProofVecVec::new();
368 let logup_pow_offset = pow_tidx_count(vk.cpu.inner.params.logup.pow_bits);
369
370 for (pidx, preflight) in preflights.iter().enumerate() {
371 let beta_tidx = preflight.proof_shape.post_tidx + logup_pow_offset + D_EF;
372 let beta = EF::from_basis_coefficients_slice(
373 &preflight.cpu.transcript.values()[beta_tidx..beta_tidx + D_EF],
374 )
375 .unwrap();
376
377 let eq_3bs = &eq_3b_blob.all_stacked_ids[pidx];
378 let mut cur_eq3b_idx = 0;
379
380 let vdata = &preflight.cpu.proof_shape.sorted_trace_vdata;
381 let mut proof_values = Vec::with_capacity(vdata.len());
382 let mut proof_interaction_records = vec![];
383
384 for (air_idx, vdata) in vdata {
385 let n = vdata.log_height as isize - l_skip as isize;
386 let inters = &interactions[*air_idx];
387
388 let mut num_sum = EF::ZERO;
389 let mut denom_sum = EF::ZERO;
390 let mut air_values = Vec::with_capacity(inters.len());
391
392 if inters.is_empty() {
393 air_values.push(vec![EF::ZERO]);
396 proof_interaction_records.push(InteractionRecord {
397 interaction_num_rows: 1,
398 global_start_row: global_current_row,
399 stacked_idx: 0,
400 });
401 global_current_row += 1;
402 cur_eq3b_idx += 1;
403 } else {
404 for inter in inters {
405 let stacked_idx_record = eq_3bs[cur_eq3b_idx];
406 let eq_3b = stacked_idx_record.eq_mle(
407 &preflight.cpu.batch_constraint.xi,
408 l_skip,
409 preflight.proof_shape.n_logup,
410 );
411 cur_eq3b_idx += 1;
412 num_sum += expr_evals[[pidx, *air_idx]][inter.count] * eq_3b;
413
414 let interaction_num_rows = interaction_length(inter);
415 proof_interaction_records.push(InteractionRecord {
416 interaction_num_rows: interaction_num_rows as u32,
417 global_start_row: global_current_row,
418 stacked_idx: stacked_idx_record.stacked_idx,
419 });
420 global_current_row += interaction_num_rows as u32;
421
422 let mut interaction_values = Vec::with_capacity(interaction_num_rows);
423 interaction_values.push(expr_evals[[pidx, *air_idx]][inter.count]);
424
425 let mut beta_pow = EF::ONE;
426 let mut cur_sum = EF::ZERO;
427 for &node_idx in &inter.message {
428 let value = expr_evals[[pidx, *air_idx]][node_idx];
429 cur_sum += beta_pow * value;
430 beta_pow *= beta;
431 interaction_values.push(value);
432 }
433
434 let bus_value = EF::from_u16(inter.bus_index + 1);
435 cur_sum += beta_pow * bus_value;
436 interaction_values.push(bus_value);
437 denom_sum += cur_sum * eq_3b;
438
439 air_values.push(interaction_values);
440 }
441 }
442
443 proof_values.push(air_values);
444 folded_claims.push((n, num_sum));
445 folded_claims.push((n, denom_sum));
446 }
447
448 values.push(proof_values);
449 interaction_records.push(proof_interaction_records);
450 interactions_folding_per_proof.push(FpExtWithTidx {
451 value: beta,
452 tidx: beta_tidx as u32,
453 });
454 folded_claims.end_proof();
455 }
456
457 Self {
458 values,
459 interaction_records,
460 interactions_folding_per_proof,
461 folded_claims,
462 num_valid_rows: global_current_row as usize,
463 }
464 }
465 }
466
467 impl ModuleChip<GpuBackend> for InteractionsFoldingTraceGenerator {
468 type Ctx<'a> = (
469 &'a VerifyingKeyGpu,
470 &'a [PreflightGpu],
471 &'a InteractionsFoldingBlobGpu,
472 &'a GpuDeviceCtx,
473 );
474
475 #[tracing::instrument(name = "generate_trace", level = "trace", skip_all)]
476 fn generate_proving_ctx(
477 &self,
478 ctx: &Self::Ctx<'_>,
479 required_height: Option<usize>,
480 ) -> Option<AirProvingContext<GpuBackend>> {
481 let (child_vk, preflights_gpu, blob, device_ctx) = ctx;
482
483 let num_airs = preflights_gpu
484 .iter()
485 .map(|preflight| preflight.cpu.proof_shape.sorted_trace_vdata.len() as u32)
486 .collect_vec();
487 let n_logups = preflights_gpu
488 .iter()
489 .map(|p| p.proof_shape.n_logup as u32)
490 .collect_vec();
491 let mut num_valid_rows = 0u32;
492
493 let mut row_bounds = Vec::with_capacity(preflights_gpu.len());
494 let mut air_interaction_bounds = Vec::with_capacity(preflights_gpu.len());
495 let mut interaction_row_bounds = Vec::with_capacity(preflights_gpu.len());
496
497 let expected_num_valid_rows = blob.num_valid_rows;
498 let mut idx_keys = Vec::with_capacity(expected_num_valid_rows);
499 let mut flat_values = Vec::with_capacity(expected_num_valid_rows);
500
501 for (proof_idx, proof_values) in blob.values.iter().enumerate() {
502 let mut proof_num_rows = 0;
503 let mut proof_num_interactions = 0;
504 let mut proof_air_interaction_bounds =
505 Vec::with_capacity(num_airs[proof_idx] as usize);
506 let mut proof_interaction_row_bounds = vec![];
507
508 for air_values in proof_values {
509 for interaction_values in air_values {
510 let global_interaction_idx = proof_num_interactions;
511 proof_num_interactions += 1;
512 for v in interaction_values {
513 flat_values.push(*v);
514 idx_keys.push(UInt2 {
515 x: proof_idx as u32,
516 y: global_interaction_idx,
517 });
518 }
519 proof_num_rows += interaction_values.len() as u32;
520 proof_interaction_row_bounds.push(proof_num_rows);
521 }
522 proof_air_interaction_bounds.push(proof_num_interactions);
523 }
524
525 num_valid_rows += proof_num_rows;
526 row_bounds.push(num_valid_rows);
527 air_interaction_bounds.push(
528 proof_air_interaction_bounds
529 .to_device_on(device_ctx)
530 .unwrap(),
531 );
532 interaction_row_bounds.push(
533 proof_interaction_row_bounds
534 .to_device_on(device_ctx)
535 .unwrap(),
536 );
537 }
538
539 assert_eq!(num_valid_rows as usize, expected_num_valid_rows);
540
541 let records = blob
542 .interaction_records
543 .iter()
544 .map(|records| records.to_device_on(device_ctx).unwrap())
545 .collect_vec();
546 let xis = preflights_gpu
547 .iter()
548 .map(|preflight| {
549 preflight
550 .cpu
551 .batch_constraint
552 .xi
553 .to_device_on(device_ctx)
554 .unwrap()
555 })
556 .collect_vec();
557
558 let height = if let Some(height) = required_height {
559 if height < num_valid_rows as usize {
560 return None;
561 }
562 height
563 } else {
564 (num_valid_rows as usize).next_power_of_two()
565 };
566 let width = InteractionsFoldingCols::<F>::width();
567 let d_trace = DeviceMatrix::<F>::with_capacity_on(height, width, device_ctx);
568
569 let d_idx_keys = idx_keys.to_device_on(device_ctx).unwrap();
570 let d_values = flat_values.to_device_on(device_ctx).unwrap();
571 let d_cur_sum_evals =
572 DeviceBuffer::<AffineFpExt>::with_capacity_on(d_values.len(), device_ctx);
573
574 let d_air_interaction_bounds = air_interaction_bounds
575 .iter()
576 .map(|b| b.as_ptr())
577 .collect_vec();
578 let d_interaction_row_bounds = interaction_row_bounds
579 .iter()
580 .map(|b| b.as_ptr())
581 .collect_vec();
582 let d_sorted_trace_vdata = preflights_gpu
583 .iter()
584 .map(|preflight| preflight.proof_shape.sorted_trace_heights.as_ptr())
585 .collect_vec();
586 let d_records = records.iter().map(|b| b.as_ptr()).collect_vec();
587 let d_xis = xis.iter().map(|b| b.as_ptr()).collect_vec();
588
589 let d_per_proof = blob
590 .interactions_folding_per_proof
591 .to_device_on(device_ctx)
592 .unwrap();
593
594 unsafe {
595 let temp_bytes = interactions_folding_tracegen_temp_bytes(
596 d_trace.buffer(),
597 height,
598 &d_idx_keys,
599 &d_cur_sum_evals,
600 num_valid_rows,
601 device_ctx.stream.as_raw(),
602 )
603 .unwrap();
604 let d_temp_buffer = DeviceBuffer::<u8>::with_capacity_on(temp_bytes, device_ctx);
605 interactions_folding_tracegen(
606 d_trace.buffer(),
607 height,
608 width,
609 &d_idx_keys,
610 &d_cur_sum_evals,
611 &d_values,
612 &row_bounds,
613 d_air_interaction_bounds,
614 d_interaction_row_bounds,
615 d_sorted_trace_vdata,
616 d_records,
617 d_xis,
618 &d_per_proof,
619 &num_airs,
620 &n_logups,
621 preflights_gpu.len() as u32,
622 num_valid_rows,
623 child_vk.system_params.l_skip as u32,
624 &d_temp_buffer,
625 temp_bytes,
626 device_ctx.stream.as_raw(),
627 )
628 .unwrap();
629 }
630 Some(AirProvingContext::simple_no_pis(d_trace))
631 }
632 }
633}