Skip to content

Commit 2222cf2

Browse files
hero78119kunxian-xiaCopilotclaude
authored
jagged: group reshaped q prime commitments (#59)
* wip jagged_commit * feat: jagged sumcheck (#32) * claude code impl plan * implement the jagged_sumcheck using time-space tradeoff sumcheck prover algorithm * remove debugging codes * ref to the original paper * add jagged sumcheck bench * #32 parallelize (#34) * par wip * check f(z) * g(z) matches expected evaluation * fix clippy * fix clippy: add #[cfg(test)] to test-only method and fix unused import/variable warnings Agent-Logs-Url: https://github.com/scroll-tech/gkr-backend/sessions/4a61316e-cf28-47b8-a43e-fb6ab432701e Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> * refactor test * apply functional programming style * avoid unnecessary BaseField-to-ExtensionField conversion in q_evals access Use E * BaseField multiplication directly instead of converting q_evals elements to extension field with .into() first. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * replace col_row binary search with incremental ColRowIter Add ColRowIter that does one binary search at construction and O(1) per step, replacing per-element binary searches in build_m_table, bind_and_materialize, compute_claimed_sum, and final_evaluations_slow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * extend jagged sumcheck benchmark to cover n=25..31 * switch jagged sumcheck benchmark to BabyBearExt4 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * remove jagged sumcheck plan doc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: make streaming epoch schedule configurable in jagged_sumcheck_prove (#38) * claude code impl plan * implement the jagged_sumcheck using time-space tradeoff sumcheck prover algorithm * remove debugging codes * ref to the original paper * add jagged sumcheck bench * #32 parallelize (#34) * par wip * check f(z) * g(z) matches expected evaluation * fix clippy * fix clippy: add #[cfg(test)] to test-only method and fix unused import/variable warnings Agent-Logs-Url: https://github.com/scroll-tech/gkr-backend/sessions/4a61316e-cf28-47b8-a43e-fb6ab432701e Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> * refactor test * apply functional programming style * avoid unnecessary BaseField-to-ExtensionField conversion in q_evals access Use E * BaseField multiplication directly instead of converting q_evals elements to extension field with .into() first. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * replace col_row binary search with incremental ColRowIter Add ColRowIter that does one binary search at construction and O(1) per step, replacing per-element binary searches in build_m_table, bind_and_materialize, compute_claimed_sum, and final_evaluations_slow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * extend jagged sumcheck benchmark to cover n=25..31 * switch jagged sumcheck benchmark to BabyBearExt4 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * remove jagged sumcheck plan doc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Initial plan * Make EPOCH_SIZES configurable in jagged_sumcheck_prove via optional parameter Agent-Logs-Url: https://github.com/scroll-tech/gkr-backend/sessions/b18805c7-6e15-44c0-ab03-a1905068d964 Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> * Fix doc spacing and add debug_assert for epoch_sizes validation Agent-Logs-Url: https://github.com/scroll-tech/gkr-backend/sessions/b18805c7-6e15-44c0-ab03-a1905068d964 Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> --------- Co-authored-by: kunxian xia <xiakunxian130@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Feat: evaluator for jagged indicator function (#39) * implement evaluate_g using width-4 ROBP from Section 3.2/4 Evaluate the MLE of indicator function g(a,b,c,d) = [a+c=b AND b<d] using a width-4 read-once branching program with state (carry, lt). Placed in separate jagged_evaluator.rs module. Includes brute-force test for correctness verification (n=1..4). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * implement forward and backward algorithms for evaluate_g Separate evaluate_g into two functions following the ROBP-based MLE evaluation: forward (source→sinks, MLE definition) and backward (sinks→source, Claim 4.2.1/Lemma 4.2 from jagged PCS paper). Extract shared transition_weights helper. Default to backward for future batch/symbolic evaluation support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: implement jagged_batch_open and jagged_batch_verify Add the opening protocol for the jagged PCS: given K evaluation claims on individual column polynomials, prove they're consistent with the commitment to the giga polynomial q'. Uses the jagged sumcheck to reduce to a single evaluation of q', then opens via the inner PCS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * update doc comments * polish doc comments * polish doc comments * reorg * move types to a separate file * fix: add missing rayon trait imports for --all-features clippy Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * update doc comments: document correction factors for different-height polynomials The zero-padding interpretation (p_i^pad) explains why C_i = eq(z_r[s_i..], 0) is needed when batching polynomials of different heights with a single eq table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: rename generic parameter Pcs to InnerPcs for readability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: document 2^{s_i} block sizes and zero-padding cost vs SP1 Clarify that each polynomial in q' occupies a full 2^{s_i} block (not the original h_i entries) because bit-reversal scatters the zero-padding throughout the block. Add note comparing with SP1's jagged PCS which uses raw h_i, highlighting the Σ(2^{s_i} - h_i) extra zeros as the cost of suffix-to-prefix bit-reversal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: assist sumcheck for jagged PCS (#42) * feat: implement assist sumcheck to reduce K ROBP evaluations to one Implements the assist sumcheck protocol (Lemma 5.1 of eprint 2025/917) which batches K indicator function evaluations into a single opening, using an interleaved variable ordering and forward-backward ROBP state decomposition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: parallelize backward precomputation and fuse round accumulations - Parallelize the O(K × n_robp) backward vector precomputation with rayon - Derive bwd_sum_d from bwd_sum in O(1) instead of a second O(K) pass - Fuse the two per-step weight updates into one - Add assist_sumcheck benchmark (K=100/500/1000) K=1000 drops from 14.1ms to 4.6ms (3.1x speedup with --features parallel). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: annotate key MLE telescoping identity in assist sumcheck Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bench: update assist sumcheck benchmark to K=1000,2000,4000,8000 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: derive ROBP transition matrices from raw transition table Separate the ROBP state machine logic from the eq-weighting algebra. The raw transition table ROBP_TRANSITION encodes the state machine directly, and symbol_transition_matrices derives M_i^{(c,d)} via the closed formula Σ_{a,b} eq₁(z1,a)·eq₁(z2,b)·[transition(from,(a,b,c,d))=to]. Also document why z₁/z₂ are bound first (reducing alphabet from {0,1}⁴ to {0,1}²) and z₃/z₄ interleaved to match ROBP step order. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: suppress needless_range_loop clippy lint and apply fmt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bench: add end-to-end jagged PCS benchmark (commit, open, verify) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: suppress clippy needless_range_loop in evaluator tests, add K=16000 bench Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: transpose bwd/c_bits/d_bits to step-major layout for cache-friendly access Reorganize data structures from poly-major bwd[y][i] to step-major bwd[i][y], and similarly for c_bits and d_bits. The main loop now scans contiguous memory when accumulating over K polynomials, reducing cache misses at large K (~38% improvement at K=16000). Backward precomputation uses split_at_mut + par_iter for safe parallel writes. Also adds the round polynomial formula (§2.3, Eq. 4) as a code comment to clarify the bwd_sum accumulation logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: parallelize assist sumcheck round-polynomial computation and weight update Thread-local bwd_sum buckets + per-thread p(0),p(1),p(2) evaluation eliminates the sequential O(K) bottleneck in the main step loop. Each thread builds local buckets, computes local round polynomial values, and only the scalars are summed across threads. Benchmark (BabyBearExt4, --features parallel, vs HEAD): K=1000: 7.8ms → 6.8ms (-14%) K=2000: 13.5ms → 10.2ms (-24%) K=4000: 25.6ms → 16.2ms (-38%) K=8000: 50.7ms → 28.0ms (-46%) K=16000: 100.5ms → 53.6ms (-48%) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: eliminate bwd_sum merge by reusing per-thread buckets in round 2i+1 Instead of merging thread-local bwd_sums after round 2i, cache them and absorb alpha directly into each thread's local bwd_sum for round 2i+1. Only scalar p(0),p(1),p(2) values are summed across threads in both rounds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: gate IndexedParallelIterator import behind parallel feature, remove tautological debug_assert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fmt * fix: use wildcard maybe_rayon import, remove unused direct rayon dep Wildcard `use p3::maybe_rayon::prelude::*` avoids unused-import warnings caused by Cargo feature unification: `p3/parallel` can be activated by other workspace crates even when `mpcs/parallel` is off, making a `#[cfg(feature = "parallel")]` gate on IndexedParallelIterator incorrect. Also remove `dep:rayon` from mpcs — all parallelism goes through `p3::maybe_rayon`, so the direct rayon dependency was unnecessary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * docs: label batched opening claim and document correction factor inclusion in prover Agent-Logs-Url: https://github.com/scroll-tech/gkr-backend/sessions/22b3ba65-8e69-45eb-ae09-0a5267f8b518 Co-authored-by: kunxian-xia <1082586+kunxian-xia@users.noreply.github.com> * feat: add reshape step to jagged PCS (#48) * feat: make JaggedSumcheckInput::compute_claimed_sum public * feat: add jagged vs direct PCS comparison bench and use p3 RowMajorMatrix - Add comparison benchmark (comparison.rs) measuring commit, batch_open, batch_verify, and proof size for jagged PCS vs direct inner PCS - Refactor jagged_commit to accept p3::matrix::dense::RowMajorMatrix instead of witness::RowMajorMatrix, supporting non-power-of-two matrix heights (internally padded to next power of two before bit-reversal) - Update jagged_pcs bench to use BabyBearExt4, parallel make_rmm, jittered non-power-of-two heights, and eq-table-based column evaluation - Cap reshape_log_height to 25 to fit BabyBear two-adicity constraint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * misc: prefix align without bit reverse * jagged: direct concat for prefix-aligned points (#56) misc: prefix align without bit reverse * WIP integrate jagged pcs cpu * Fix jagged PCS padded opening normalization * misc: clippy fix * avoid reconstruct q_mles * more docs * misc: fmt and clippy * feat: group jagged q prime commitments * retain fast path for one column * fix label * misc: function scope --------- Co-authored-by: kunxian xia <xiakunxian130@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: hero78119 <3962077+hero78119@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: kunxian-xia <1082586+kunxian-xia@users.noreply.github.com>
1 parent e8f8f5c commit 2222cf2

6 files changed

Lines changed: 148 additions & 58 deletions

File tree

crates/mpcs/src/jagged/mod.rs

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ use multilinear_extensions::{
128128
util::ceil_log2,
129129
virtual_poly::{VPAuxInfo, build_eq_x_r_vec},
130130
};
131-
use p3::{field::FieldAlgebra, matrix::Matrix, maybe_rayon::prelude::*};
131+
use p3::{field::FieldAlgebra, maybe_rayon::prelude::*};
132132
use serde::{Serialize, Serializer, de::DeserializeOwned};
133133
use std::sync::Arc;
134134
use transcript::Transcript;
@@ -138,6 +138,43 @@ use witness::{InstancePaddingStrategy, RowMajorMatrix as WitnessRowMajorMatrix};
138138
#[derive(Debug)]
139139
pub struct Jagged<InnerPcs>(PhantomData<InnerPcs>);
140140

141+
pub const JAGGED_RESHAPE_GROUP_WIDTH: usize = 8;
142+
143+
type InnerOpenings<E> = Vec<(Point<E>, Vec<E>)>;
144+
type InnerVerifyOpenings<E> = Vec<(usize, (Point<E>, Vec<E>))>;
145+
146+
fn reshape_group_width() -> usize {
147+
JAGGED_RESHAPE_GROUP_WIDTH
148+
}
149+
150+
fn chunk_col_evals<E: ExtensionField>(col_evals: &[E]) -> Vec<Vec<E>> {
151+
col_evals
152+
.chunks(reshape_group_width())
153+
.map(|chunk| chunk.to_vec())
154+
.collect()
155+
}
156+
157+
fn inner_openings_for_col_evals<E: ExtensionField>(
158+
rho_row: &[E],
159+
col_evals: &[E],
160+
) -> InnerOpenings<E> {
161+
chunk_col_evals(col_evals)
162+
.into_iter()
163+
.map(|evals| (rho_row.to_vec(), evals))
164+
.collect()
165+
}
166+
167+
fn inner_verify_openings_for_col_evals<E: ExtensionField>(
168+
log_h: usize,
169+
rho_row: &[E],
170+
col_evals: &[E],
171+
) -> InnerVerifyOpenings<E> {
172+
chunk_col_evals(col_evals)
173+
.into_iter()
174+
.map(|evals| (log_h, (rho_row.to_vec(), evals)))
175+
.collect()
176+
}
177+
141178
impl<InnerPcs> Clone for Jagged<InnerPcs> {
142179
fn clone(&self) -> Self {
143180
Self(PhantomData)
@@ -265,46 +302,50 @@ pub fn jagged_commit<E: ExtensionField, InnerPcs: PolynomialCommitmentScheme<E>>
265302
let h = 1usize << log_h;
266303
let w = total_size.div_ceil(h);
267304

268-
let giga_data = if w == 1 {
269-
concatenated
305+
let giga_rmms = if w == 1 {
306+
vec![WitnessRowMajorMatrix::<E::BaseField>::new_by_values(
307+
concatenated,
308+
1,
309+
InstancePaddingStrategy::Default,
310+
)]
270311
} else {
271-
// Transpose the flat q' evaluations to row-major for the inner PCS.
272-
// In the flat array, column i occupies concatenated[i*h .. (i+1)*h];
273-
// the last column may be short and is zero-padded.
274-
let padded_total = w * h;
275-
let mut row_major: Vec<E::BaseField> = Vec::with_capacity(padded_total);
276-
#[allow(clippy::uninit_vec)]
277-
unsafe {
278-
row_major.set_len(padded_total)
279-
};
280-
281-
(0..w).into_par_iter().for_each(|i| {
282-
let src_start = i * h;
283-
let col_len = if i < w - 1 { h } else { total_size - src_start };
284-
let dst = unsafe {
285-
&mut *std::ptr::slice_from_raw_parts_mut(
286-
row_major.as_ptr() as *mut E::BaseField,
287-
padded_total,
288-
)
312+
let mut giga_rmms = Vec::with_capacity(w.div_ceil(reshape_group_width()));
313+
for group_start_col in (0..w).step_by(reshape_group_width()) {
314+
let group_cols = (w - group_start_col).min(reshape_group_width());
315+
let mut row_major: Vec<E::BaseField> = Vec::with_capacity(h * group_cols);
316+
#[allow(clippy::uninit_vec)]
317+
unsafe {
318+
row_major.set_len(h * group_cols)
289319
};
290-
for b in 0..col_len {
291-
dst[b * w + i] = concatenated[src_start + b];
292-
}
293-
for b in col_len..h {
294-
dst[b * w + i] = E::BaseField::ZERO;
295-
}
296-
});
297320

298-
row_major
299-
};
321+
(0..group_cols).into_par_iter().for_each(|local_col| {
322+
let global_col = group_start_col + local_col;
323+
let src_start = global_col * h;
324+
let col_len = total_size.saturating_sub(src_start).min(h);
325+
let dst = unsafe {
326+
&mut *std::ptr::slice_from_raw_parts_mut(
327+
row_major.as_ptr() as *mut E::BaseField,
328+
h * group_cols,
329+
)
330+
};
331+
for b in 0..col_len {
332+
dst[b * group_cols + local_col] = concatenated[src_start + b];
333+
}
334+
for b in col_len..h {
335+
dst[b * group_cols + local_col] = E::BaseField::ZERO;
336+
}
337+
});
300338

301-
let giga_rmm = WitnessRowMajorMatrix::<E::BaseField>::new_by_values(
302-
giga_data,
303-
w,
304-
InstancePaddingStrategy::Default,
305-
);
339+
giga_rmms.push(WitnessRowMajorMatrix::<E::BaseField>::new_by_values(
340+
row_major,
341+
group_cols,
342+
InstancePaddingStrategy::Default,
343+
));
344+
}
345+
giga_rmms
346+
};
306347

307-
let inner = InnerPcs::batch_commit(pp, vec![giga_rmm])?;
348+
let inner = InnerPcs::batch_commit(pp, giga_rmms)?;
308349

309350
Ok(JaggedCommitmentWithWitness {
310351
inner,
@@ -319,15 +360,16 @@ fn default_reshape_log_height<E: ExtensionField, InnerPcs: PolynomialCommitmentS
319360
pp: &InnerPcs::ProverParam,
320361
rmms: &[WitnessRowMajorMatrix<E::BaseField>],
321362
) -> usize {
322-
let total_evals = rmms
363+
let max_poly_height = rmms
323364
.iter()
324-
.map(|rmm| rmm.occupied_physical_rows() * rmm.width())
325-
.sum::<usize>();
326-
let total_log = ceil_log2(total_evals.max(1));
327-
pp.get_max_message_size_log().min(total_log)
365+
.map(|rmm| rmm.occupied_physical_rows())
366+
.max()
367+
.unwrap_or(1);
368+
pp.get_max_message_size_log()
369+
.min(ceil_log2(max_poly_height.max(1)))
328370
}
329371

330-
fn flatten_padded_openings_as_native<E: ExtensionField>(
372+
pub fn flatten_padded_openings_as_native<E: ExtensionField>(
331373
poly_heights: &[usize],
332374
openings: Vec<(Point<E>, Vec<E>)>,
333375
) -> Result<(Point<E>, Vec<E>), Error> {
@@ -478,7 +520,7 @@ pub fn jagged_batch_open<E: ExtensionField, InnerPcs: PolynomialCommitmentScheme
478520
// Write evals to transcript, then sample z_col.
479521
transcript.append_field_element_exts(evals);
480522
let num_col_vars = ceil_log2(num_polys).max(1);
481-
let z_col: Vec<E> = transcript.sample_and_append_vec(b"jagged_z_col", num_col_vars);
523+
let z_col: Vec<E> = transcript.sample_and_append_vec(b"z_col", num_col_vars);
482524

483525
let eq_col = build_eq_x_r_vec(&z_col);
484526

@@ -554,7 +596,10 @@ pub fn jagged_batch_open<E: ExtensionField, InnerPcs: PolynomialCommitmentScheme
554596
// Open column MLEs at ρ_row via inner PCS.
555597
let inner_proof = InnerPcs::batch_open(
556598
pp,
557-
vec![(&comm.inner, vec![(rho_row.to_vec(), col_evals.clone())])],
599+
vec![(
600+
&comm.inner,
601+
inner_openings_for_col_evals(rho_row, &col_evals),
602+
)],
558603
transcript,
559604
)?;
560605

@@ -601,7 +646,7 @@ pub fn jagged_batch_verify<E: ExtensionField, InnerPcs: PolynomialCommitmentSche
601646
// Replay transcript: write evals, sample z_col.
602647
transcript.append_field_element_exts(evals);
603648
let num_col_vars = ceil_log2(num_polys).max(1);
604-
let z_col: Vec<E> = transcript.sample_and_append_vec(b"jagged_z_col", num_col_vars);
649+
let z_col: Vec<E> = transcript.sample_and_append_vec(b"z_col", num_col_vars);
605650

606651
// Batched opening claim: v = Σ_i eq_col[i] · C_i · evals[i]
607652
// where C_i = eq(z_row[s_i..], 0) = Π_{j=s_i}^{max_s-1} (1 - z_row[j]) is the
@@ -694,7 +739,7 @@ pub fn jagged_batch_verify<E: ExtensionField, InnerPcs: PolynomialCommitmentSche
694739
vp,
695740
vec![(
696741
comm.inner.clone(),
697-
vec![(log_h, (rho_row.to_vec(), proof.col_evals.clone()))],
742+
inner_verify_openings_for_col_evals(log_h, rho_row, &proof.col_evals),
698743
)],
699744
&proof.inner_proof,
700745
transcript,

crates/mpcs/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,10 @@ pub use basefold::{
273273
};
274274
pub mod jagged;
275275
pub use jagged::{
276-
Jagged, JaggedBatchOpenProof, JaggedCommitment, JaggedCommitmentWithWitness, JaggedProof,
277-
JaggedSumcheckInput, assist_sumcheck_prove, evaluate_g, evaluate_g_backward,
278-
evaluate_g_forward, jagged_batch_open, jagged_batch_verify, jagged_commit,
279-
jagged_sumcheck_prove,
276+
JAGGED_RESHAPE_GROUP_WIDTH, Jagged, JaggedBatchOpenProof, JaggedCommitment,
277+
JaggedCommitmentWithWitness, JaggedProof, JaggedSumcheckInput, assist_sumcheck_prove,
278+
evaluate_g, evaluate_g_backward, evaluate_g_forward, jagged_batch_open, jagged_batch_verify,
279+
jagged_commit, jagged_sumcheck_prove,
280280
};
281281
#[cfg(feature = "whir")]
282282
extern crate whir as whir_external;

crates/whir/src/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ pub fn stack_evaluations_mut_rmm<F: Field>(
104104
folding_factor: usize,
105105
) {
106106
let folding_factor_exp = 1 << folding_factor;
107-
assert!(rmm.height() % folding_factor_exp == 0);
107+
assert!(rmm.height().is_multiple_of(folding_factor_exp));
108108
let size_of_new_domain = rmm.height() / folding_factor_exp;
109109

110110
// interpret evals as (folding_factor_exp x size_of_new_domain)-matrix and transpose in-place

crates/whir/src/whir/batch/committer.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ use crate::{
1111
};
1212
use derive_more::Debug;
1313
use ff_ext::ExtensionField;
14-
use p3::{
15-
matrix::{Matrix, dense::RowMajorMatrix},
16-
maybe_rayon::prelude::*,
17-
util::log2_strict_usize,
18-
};
14+
use p3::{matrix::dense::RowMajorMatrix, maybe_rayon::prelude::*, util::log2_strict_usize};
1915
use sumcheck::macros::{entered_span, exit_span};
2016
use transcript::{BasicTranscript, Transcript};
2117

crates/whir/src/whir/fold.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::{
55

66
use p3::{
77
field::{Field, TwoAdicField},
8-
matrix::Matrix,
98
maybe_rayon::prelude::*,
109
};
1110

crates/witness/src/lib.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub struct RowMajorMatrix<T: Sized + Sync + Clone + Send + Copy> {
4141
log2_num_rotation: usize,
4242
is_padded: bool,
4343
padding_strategy: InstancePaddingStrategy,
44+
host_elided_padded_height: Option<usize>,
4445
// Optional opaque handle to device-resident storage that mirrors `inner.values`.
4546
// This lets GPU-side code keep an associated buffer/layout without forcing witness
4647
// to depend on a concrete device runtime. There is no automatic host<->device sync:
@@ -81,6 +82,7 @@ impl<T: Sized + Sync + Clone + Send + Copy + Default + FieldAlgebra> RowMajorMat
8182
is_padded: true,
8283
log2_num_rotation: 0,
8384
padding_strategy: InstancePaddingStrategy::Default,
85+
host_elided_padded_height: None,
8486
device_backing: None,
8587
}
8688
}
@@ -91,6 +93,7 @@ impl<T: Sized + Sync + Clone + Send + Copy + Default + FieldAlgebra> RowMajorMat
9193
log2_num_rotation: 0,
9294
is_padded: true,
9395
padding_strategy: InstancePaddingStrategy::Default,
96+
host_elided_padded_height: None,
9497
device_backing: None,
9598
}
9699
}
@@ -121,7 +124,16 @@ impl<T: Sized + Sync + Clone + Send + Copy + Default + FieldAlgebra> RowMajorMat
121124
}
122125

123126
pub fn num_vars(&self) -> usize {
124-
self.inner.height().ilog2() as usize
127+
self.height().ilog2() as usize
128+
}
129+
130+
pub fn height(&self) -> usize {
131+
self.host_elided_padded_height
132+
.unwrap_or_else(|| self.inner.height())
133+
}
134+
135+
pub fn width(&self) -> usize {
136+
self.inner.width
125137
}
126138

127139
pub fn new(
@@ -157,10 +169,38 @@ impl<T: Sized + Sync + Clone + Send + Copy + Default + FieldAlgebra> RowMajorMat
157169
log2_num_rotation,
158170
is_padded: matches!(padding_strategy, InstancePaddingStrategy::Default),
159171
padding_strategy,
172+
host_elided_padded_height: None,
160173
device_backing: None,
161174
}
162175
}
163176

177+
/// Create matrix metadata backed only by device storage.
178+
///
179+
/// This is intended for GPU proving paths that already own a device-resident
180+
/// matrix and need to reuse `RowMajorMatrix` shape/device metadata without
181+
/// allocating a duplicate host-side zero matrix. Callers must not use host
182+
/// value accessors unless they later materialize host values separately.
183+
pub fn new_by_device_backing<D: Any + Send + Sync + 'static>(
184+
num_rows: usize,
185+
num_cols: usize,
186+
padding_strategy: InstancePaddingStrategy,
187+
storage: D,
188+
layout: DeviceMatrixLayout,
189+
) -> Self {
190+
let num_row_padded = next_pow2_instance_padding(num_rows);
191+
let mut matrix = RowMajorMatrix {
192+
inner: p3::matrix::dense::RowMajorMatrix::new(vec![], num_cols),
193+
num_rows,
194+
log2_num_rotation: 0,
195+
is_padded: matches!(padding_strategy, InstancePaddingStrategy::Default),
196+
padding_strategy,
197+
host_elided_padded_height: Some(num_row_padded),
198+
device_backing: None,
199+
};
200+
matrix.set_device_backing(storage, layout);
201+
matrix
202+
}
203+
164204
pub fn new_by_inner_matrix(
165205
mut m: p3::matrix::dense::RowMajorMatrix<T>,
166206
padding_strategy: InstancePaddingStrategy,
@@ -176,6 +216,7 @@ impl<T: Sized + Sync + Clone + Send + Copy + Default + FieldAlgebra> RowMajorMat
176216
log2_num_rotation: 0,
177217
is_padded: matches!(padding_strategy, InstancePaddingStrategy::Default),
178218
padding_strategy,
219+
host_elided_padded_height: None,
179220
device_backing: None,
180221
}
181222
}
@@ -294,10 +335,18 @@ impl<T: Sized + Sync + Clone + Send + Copy + Default + FieldAlgebra> RowMajorMat
294335
}
295336

296337
pub fn values(&self) -> &[T] {
338+
assert!(
339+
self.host_elided_padded_height.is_none(),
340+
"host values were elided for this device-backed RowMajorMatrix"
341+
);
297342
&self.inner.values
298343
}
299344

300345
pub fn pad_to_height(&mut self, new_height: usize, fill: T) {
346+
assert!(
347+
self.host_elided_padded_height.is_none(),
348+
"cannot pad RowMajorMatrix with elided host values"
349+
);
301350
let (cur_height, n_cols) = (self.height(), self.n_col());
302351
assert!(new_height >= cur_height);
303352
self.invalidate_device_backing();
@@ -317,6 +366,7 @@ impl<T: Sized + Sync + Clone + Send + Copy> Clone for RowMajorMatrix<T> {
317366
log2_num_rotation: self.log2_num_rotation,
318367
is_padded: self.is_padded,
319368
padding_strategy: self.padding_strategy.clone(),
369+
host_elided_padded_height: self.host_elided_padded_height,
320370
device_backing: None,
321371
}
322372
}

0 commit comments

Comments
 (0)