1use std::{mem::size_of, sync::Arc};
2
3use openvm_circuit::{primitives::Chip, system::program::ProgramExecutionCols};
4use openvm_cuda_backend::{base::DeviceMatrix, prelude::F, GpuBackend, GpuDevice};
5use openvm_cuda_common::{copy::MemCopyH2D, d_buffer::DeviceBuffer, stream::GpuDeviceCtx};
6use openvm_instructions::{
7 program::{Program, DEFAULT_PC_STEP},
8 LocalOpcode, SystemOpcode,
9};
10use openvm_stark_backend::prover::{
11 AirProvingContext, CommittedTraceData, MatrixDimensions, TraceCommitter,
12};
13use p3_field::PrimeCharacteristicRing;
14
15use crate::cuda_abi::program;
16
17pub struct ProgramChipGPU {
18 pub cached: Option<CommittedTraceData<GpuBackend>>,
19 pub device_ctx: GpuDeviceCtx,
20}
21
22impl ProgramChipGPU {
23 pub fn new(device_ctx: GpuDeviceCtx) -> Self {
24 Self {
25 cached: None,
26 device_ctx,
27 }
28 }
29
30 pub fn generate_cached_trace(
31 program: Program<F>,
32 device_ctx: &GpuDeviceCtx,
33 ) -> DeviceMatrix<F> {
34 let instructions = program
35 .enumerate_by_pc()
36 .into_iter()
37 .map(|(pc, instruction, _)| {
38 [
39 F::from_u32(pc),
40 instruction.opcode.to_field(),
41 instruction.a,
42 instruction.b,
43 instruction.c,
44 instruction.d,
45 instruction.e,
46 instruction.f,
47 instruction.g,
48 ]
49 })
50 .collect::<Vec<_>>();
51
52 let num_records = instructions.len();
53 let height = num_records.next_power_of_two();
54 let records = instructions
55 .into_iter()
56 .flatten()
57 .collect::<Vec<_>>()
58 .to_device_on(device_ctx)
59 .unwrap();
60
61 let trace = DeviceMatrix::<F>::with_capacity_on(
62 height,
63 size_of::<ProgramExecutionCols<u8>>(),
64 device_ctx,
65 );
66 trace.buffer().fill_zero_on(device_ctx).unwrap();
67 unsafe {
68 program::cached_tracegen(
69 trace.buffer(),
70 trace.height(),
71 trace.width(),
72 &records,
73 program.pc_base,
74 DEFAULT_PC_STEP,
75 SystemOpcode::TERMINATE.global_opcode().as_usize(),
76 device_ctx.stream.as_raw(),
77 )
78 .expect("Failed to generate cached trace");
79 }
80 trace
81 }
82
83 pub fn get_committed_trace(
84 trace: DeviceMatrix<F>,
85 device: &GpuDevice,
86 ) -> CommittedTraceData<GpuBackend> {
87 let (commitment, data) = TraceCommitter::<GpuBackend>::commit(device, &[&trace]).unwrap();
88 CommittedTraceData {
89 commitment,
90 data: Arc::new(data),
91 trace,
92 }
93 }
94}
95
96impl Default for ProgramChipGPU {
97 fn default() -> Self {
98 panic!("ProgramChipGPU requires an explicit GpuDeviceCtx")
99 }
100}
101
102impl Chip<Vec<u32>, GpuBackend> for ProgramChipGPU {
103 fn generate_proving_ctx(&self, filtered_exec_freqs: Vec<u32>) -> AirProvingContext<GpuBackend> {
104 let cached = self.cached.clone().expect("Cached program must be loaded");
105 let height = cached.height();
106 let filtered_len = filtered_exec_freqs.len();
107 assert!(
108 filtered_len <= height,
109 "filtered_exec_freqs len={filtered_len} > cached trace height={height}"
110 );
111 let mut buffer: DeviceBuffer<F> = DeviceBuffer::with_capacity_on(height, &self.device_ctx);
112
113 filtered_exec_freqs
114 .into_iter()
115 .map(F::from_u32)
116 .collect::<Vec<_>>()
117 .copy_to_on(&mut buffer, &self.device_ctx)
118 .unwrap();
119 if filtered_len < height {
121 buffer
122 .fill_zero_suffix_on(filtered_len, &self.device_ctx)
123 .unwrap();
124 }
125
126 let common_main = DeviceMatrix::new(Arc::new(buffer), height, 1);
127
128 AirProvingContext {
129 cached_mains: vec![cached],
130 common_main,
131 public_values: vec![],
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use std::sync::Arc;
139
140 use openvm_cuda_backend::{data_transporter::assert_eq_host_and_device_matrix, prelude::F};
141 use openvm_instructions::{
142 instruction::Instruction,
143 program::{Program, DEFAULT_PC_STEP},
144 LocalOpcode,
145 SystemOpcode::*,
146 };
147 use openvm_stark_backend::{prover::TraceCommitter, StarkEngine};
148
149 use super::ProgramChipGPU;
150 use crate::{
151 system::program::{
152 tests::{BEQ, BNE, JAL, STOREW, SUB},
153 trace::generate_cached_trace,
154 },
155 utils::{test_cpu_engine, test_gpu_engine},
156 };
157
158 fn test_cached_committed_trace_data(program: Program<F>) {
159 let gpu_engine = test_gpu_engine();
160 let gpu_device = gpu_engine.device();
161 let gpu_trace =
162 ProgramChipGPU::generate_cached_trace(program.clone(), &gpu_device.device_ctx);
163 let gpu_cached = ProgramChipGPU::get_committed_trace(gpu_trace, gpu_device);
164
165 let cpu_engine = test_cpu_engine();
166 let cpu_device = cpu_engine.device();
167 let cpu_trace = Arc::new(generate_cached_trace(&program));
168 let (cpu_commit, _) = cpu_device.commit(&[&cpu_trace]).unwrap();
169
170 assert_eq_host_and_device_matrix(cpu_trace, &gpu_cached.trace, &gpu_device.device_ctx);
172 assert_eq!(gpu_cached.commitment, cpu_commit);
173 }
174
175 #[test]
176 fn test_cuda_program_cached_tracegen_1() {
177 let instructions = vec![
178 Instruction::large_from_isize(STOREW, 2, 0, 0, 0, 1, 0, 1),
179 Instruction::large_from_isize(STOREW, 1, 1, 0, 0, 1, 0, 1),
180 Instruction::from_isize(BEQ, 0, 0, 3 * DEFAULT_PC_STEP as isize, 1, 0),
181 Instruction::from_isize(SUB, 0, 0, 1, 1, 1),
182 Instruction::from_isize(JAL, 2, -2 * (DEFAULT_PC_STEP as isize), 0, 1, 0),
183 Instruction::from_isize(TERMINATE.global_opcode(), 0, 0, 0, 0, 0),
184 ];
185 let program = Program::from_instructions(&instructions);
186 test_cached_committed_trace_data(program);
187 }
188
189 #[test]
190 fn test_cuda_program_cached_tracegen_2() {
191 let instructions = vec![
192 Instruction::large_from_isize(STOREW, 5, 0, 0, 0, 1, 0, 1),
193 Instruction::from_isize(BNE, 0, 4, 3 * DEFAULT_PC_STEP as isize, 1, 0),
194 Instruction::from_isize(JAL, 2, -2 * DEFAULT_PC_STEP as isize, 0, 1, 0),
195 Instruction::from_isize(TERMINATE.global_opcode(), 0, 0, 0, 0, 0),
196 Instruction::from_isize(BEQ, 0, 5, -(DEFAULT_PC_STEP as isize), 1, 0),
197 ];
198 let program = Program::from_instructions(&instructions);
199 test_cached_committed_trace_data(program);
200 }
201
202 #[test]
203 fn test_cuda_program_cached_tracegen_undefined_instructions() {
204 let instructions = vec![
205 Some(Instruction::large_from_isize(STOREW, 2, 0, 0, 0, 1, 0, 1)),
206 Some(Instruction::large_from_isize(STOREW, 1, 1, 0, 0, 1, 0, 1)),
207 Some(Instruction::from_isize(
208 BEQ,
209 0,
210 2,
211 3 * DEFAULT_PC_STEP as isize,
212 1,
213 0,
214 )),
215 None,
216 None,
217 Some(Instruction::from_isize(
218 TERMINATE.global_opcode(),
219 0,
220 0,
221 0,
222 0,
223 0,
224 )),
225 ];
226 let program = Program::new_without_debug_infos_with_option(&instructions, 0);
227 test_cached_committed_trace_data(program);
228 }
229}