11use std:: { borrow:: Borrow , mem:: size_of, slice:: from_raw_parts, sync:: Arc } ;
22
3- use derive_new:: new;
43use openvm_circuit:: { arch:: DenseRecordArena , utils:: next_power_of_two_or_zero} ;
54use openvm_circuit_primitives:: var_range:: VariableRangeCheckerChipGPU ;
65use openvm_cuda_backend:: {
76 base:: DeviceMatrix , chip:: get_empty_air_proving_ctx, prover_backend:: GpuBackend , types:: F ,
87} ;
98use openvm_cuda_common:: copy:: MemCopyH2D ;
109use openvm_stark_backend:: { prover:: types:: AirProvingContext , Chip } ;
11- use p3_field:: { Field , PrimeField32 } ;
10+ use p3_field:: { Field , FieldAlgebra , PrimeField32 } ;
1211
13- use super :: columns:: NativePoseidon2Cols ;
14- use crate :: cuda_abi:: poseidon2_cuda;
12+ use super :: columns:: { MultiObserveCols , NativePoseidon2Cols } ;
13+ use crate :: {
14+ cuda_abi:: poseidon2_cuda,
15+ hint_space_provider:: SharedHintSpaceProviderChip ,
16+ } ;
1517
16- #[ derive( new) ]
1718pub struct NativePoseidon2ChipGpu < const SBOX_REGISTERS : usize > {
1819 pub range_checker : Arc < VariableRangeCheckerChipGPU > ,
1920 pub timestamp_max_bits : usize ,
21+ pub hint_space_provider : Option < SharedHintSpaceProviderChip < F > > ,
22+ }
23+
24+ impl < const SBOX_REGISTERS : usize > NativePoseidon2ChipGpu < SBOX_REGISTERS > {
25+ pub fn new ( range_checker : Arc < VariableRangeCheckerChipGPU > , timestamp_max_bits : usize ) -> Self {
26+ Self {
27+ range_checker,
28+ timestamp_max_bits,
29+ hint_space_provider : None ,
30+ }
31+ }
32+
33+ pub fn new_with_hint_space_provider (
34+ range_checker : Arc < VariableRangeCheckerChipGPU > ,
35+ timestamp_max_bits : usize ,
36+ hint_space_provider : SharedHintSpaceProviderChip < F > ,
37+ ) -> Self {
38+ Self {
39+ range_checker,
40+ timestamp_max_bits,
41+ hint_space_provider : Some ( hint_space_provider) ,
42+ }
43+ }
44+
45+ /// Scans multi-observe execution records to populate the hint provider with
46+ /// (hint_id, offset, value) triples for hint-mode rows.
47+ fn populate_hint_provider ( & self , records : & [ u8 ] ) {
48+ let Some ( hint_space_provider) = & self . hint_space_provider else {
49+ return ;
50+ } ;
51+
52+ let width = NativePoseidon2Cols :: < F , SBOX_REGISTERS > :: width ( ) ;
53+ let record_size = width * size_of :: < F > ( ) ;
54+ if records. len ( ) % record_size != 0 {
55+ return ;
56+ }
57+ let height = records. len ( ) / record_size;
58+
59+ let row_slice = unsafe {
60+ let ptr = records. as_ptr ( ) as * const F ;
61+ from_raw_parts ( ptr, height * width)
62+ } ;
63+
64+ let mut row_idx = 0 ;
65+ while row_idx < height {
66+ let start = row_idx * width;
67+ let cols: & NativePoseidon2Cols < F , SBOX_REGISTERS > =
68+ row_slice[ start..( start + width) ] . borrow ( ) ;
69+
70+ if cols. multi_observe_row . is_one ( ) {
71+ let num_rows = cols. inner . export . as_canonical_u32 ( ) as usize ;
72+ if num_rows > 1 {
73+ let head_multi_observe_cols: & MultiObserveCols < F > =
74+ cols. specific [ ..MultiObserveCols :: < u8 > :: width ( ) ] . borrow ( ) ;
75+ let is_hint = head_multi_observe_cols. ctx [ 2 ] != F :: ZERO ;
76+ if is_hint {
77+ let hint_id = head_multi_observe_cols. hint_id ;
78+ for local_row in 1 ..num_rows {
79+ let chunk_cols: & NativePoseidon2Cols < F , SBOX_REGISTERS > =
80+ row_slice[ ( row_idx + local_row) * width
81+ ..( row_idx + local_row + 1 ) * width]
82+ . borrow ( ) ;
83+ let multi_observe_cols: & MultiObserveCols < F > = chunk_cols. specific
84+ [ ..MultiObserveCols :: < u8 > :: width ( ) ]
85+ . borrow ( ) ;
86+
87+ let chunk_start = multi_observe_cols. start_idx . as_canonical_u32 ( ) ;
88+ let chunk_end = multi_observe_cols. end_idx . as_canonical_u32 ( ) ;
89+ let curr_len = multi_observe_cols. curr_len . as_canonical_u32 ( ) ;
90+
91+ for j in chunk_start..chunk_end {
92+ let input_idx = curr_len + ( j - chunk_start) ;
93+ let val = multi_observe_cols. data [ j as usize ] ;
94+ hint_space_provider. request (
95+ hint_id,
96+ F :: from_canonical_u32 ( input_idx) ,
97+ val,
98+ ) ;
99+ }
100+ }
101+ }
102+ }
103+ row_idx += num_rows. max ( 1 ) ;
104+ continue ;
105+ }
106+
107+ if cols. simple . is_one ( ) {
108+ row_idx += 1 ;
109+ } else {
110+ let num_non_inside_row = cols. inner . export . as_canonical_u32 ( ) as usize ;
111+ let non_inside_start = start + ( num_non_inside_row - 1 ) * width;
112+ let last_non_inside_cols: & NativePoseidon2Cols < F , SBOX_REGISTERS > =
113+ row_slice[ non_inside_start..( non_inside_start + width) ] . borrow ( ) ;
114+ let total_num_row = last_non_inside_cols. inner . export . as_canonical_u32 ( ) as usize ;
115+ row_idx += total_num_row;
116+ }
117+ }
118+ }
20119}
21120
22121impl < const SBOX_REGISTERS : usize > Chip < DenseRecordArena , GpuBackend >
@@ -28,6 +127,9 @@ impl<const SBOX_REGISTERS: usize> Chip<DenseRecordArena, GpuBackend>
28127 return get_empty_air_proving_ctx :: < GpuBackend > ( ) ;
29128 }
30129
130+ // Populate hint space provider from multi-observe records before GPU upload.
131+ self . populate_hint_provider ( records) ;
132+
31133 // For Poseidon2, the records are already the trace rows
32134 // Use the columns width directly
33135 let width = NativePoseidon2Cols :: < F , SBOX_REGISTERS > :: width ( ) ;
0 commit comments