Skip to content

Commit 1827493

Browse files
committed
chore: remove PR1 agent comments, trim kernel docs, add PR4 benchmark
1 parent 38ee656 commit 1827493

5 files changed

Lines changed: 123 additions & 91 deletions

File tree

qdp/qdp-kernels/src/amplitude.cu

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,18 @@ __global__ void amplitude_encode_kernel(
4141
double v1 = 0.0;
4242
double v2 = 0.0;
4343

44-
// Vectorized Load Optimization:
45-
// If we are well within bounds, treat input as double2 to issue a single 128-bit load instruction.
46-
// Use __ldg() to pull through the read-only cache; cudaMalloc aligns to 256 bytes so the
47-
// reinterpret_cast<double2*> load is naturally aligned.
44+
// double2 load via __ldg when aligned and in bounds.
4845
if (state_idx_base + 1 < input_len) {
49-
// Reinterpret cast to load two doubles at once
5046
const double2 loaded = __ldg(reinterpret_cast<const double2*>(input) + idx);
5147
v1 = loaded.x;
5248
v2 = loaded.y;
5349
}
54-
// Handle edge case: Odd input length
5550
else if (state_idx_base < input_len) {
5651
v1 = __ldg(input + state_idx_base);
57-
// v2 remains 0.0
5852
}
5953

60-
// Write output:
61-
// Apply pre-calculated reciprocal (multiplication is faster than division)
6254
state[state_idx_base] = make_cuDoubleComplex(v1 * inv_norm, 0.0);
6355

64-
// Check boundary for the second element (state_len is usually power of 2, but good to be safe)
6556
if (state_idx_base + 1 < state_len) {
6657
state[state_idx_base + 1] = make_cuDoubleComplex(v2 * inv_norm, 0.0);
6758
}
@@ -82,7 +73,6 @@ __global__ void amplitude_encode_kernel_f32(
8273
float v2 = 0.0f;
8374

8475
if (state_idx_base + 1 < input_len) {
85-
// Mirror the double kernel: cached vectorized load for two floats
8676
const float2 loaded = __ldg(reinterpret_cast<const float2*>(input) + idx);
8777
v1 = loaded.x;
8878
v2 = loaded.y;
@@ -225,17 +215,7 @@ int launch_amplitude_encode_f32(
225215
return (int)cudaGetLastError();
226216
}
227217

228-
/// Optimized batch amplitude encoding kernel
229-
///
230-
/// Memory Layout (row-major):
231-
/// - input_batch: [sample0_data | sample1_data | ... | sampleN_data]
232-
/// - state_batch: [sample0_state | sample1_state | ... | sampleN_state]
233-
///
234-
/// Optimizations:
235-
/// 1. Vectorized double2 loads for 128-bit memory transactions when aligned
236-
/// 2. Grid-stride loop for arbitrary batch sizes
237-
/// 3. Coalesced memory access within warps
238-
/// 4. Scalar fallback for misaligned sample bases and odd tails
218+
/// Batch amplitude encoding kernel (grid-stride, vectorized loads when aligned).
239219
__global__ void amplitude_encode_batch_kernel(
240220
const double* __restrict__ input_batch,
241221
cuDoubleComplex* __restrict__ state_batch,
@@ -244,25 +224,20 @@ __global__ void amplitude_encode_batch_kernel(
244224
size_t input_len,
245225
size_t state_len
246226
) {
247-
// Grid-stride loop pattern for flexibility
248-
const size_t elements_per_sample = state_len / 2; // Each thread handles 2 elements
227+
const size_t elements_per_sample = state_len / 2;
249228
const size_t total_work = num_samples * elements_per_sample;
250229
const size_t stride = gridDim.x * blockDim.x;
251230

252231
size_t global_idx = blockIdx.x * blockDim.x + threadIdx.x;
253232

254-
// Process elements in grid-stride fashion
255233
for (size_t idx = global_idx; idx < total_work; idx += stride) {
256-
// Decompose linear index into (sample, element_pair)
257234
const size_t sample_idx = idx / elements_per_sample;
258235
const size_t elem_pair = idx % elements_per_sample;
259236

260-
// Calculate base addresses (strength-reduced)
261237
const size_t input_base = sample_idx * input_len;
262238
const size_t state_base = sample_idx * state_len;
263239
const size_t elem_offset = elem_pair * 2;
264240

265-
// Load inverse norm (cached by L1)
266241
const double inv_norm = inv_norms[sample_idx];
267242

268243
double v1, v2;
@@ -281,34 +256,20 @@ __global__ void amplitude_encode_batch_kernel(
281256
? __ldg(sample_input + elem_offset + 1)
282257
: 0.0;
283258
} else {
284-
// Padding region
285259
v1 = v2 = 0.0;
286260
}
287261

288-
// Normalize and write as complex numbers
289-
// Compiler will optimize multiplications
290262
const cuDoubleComplex c1 = make_cuDoubleComplex(v1 * inv_norm, 0.0);
291263
const cuDoubleComplex c2 = make_cuDoubleComplex(v2 * inv_norm, 0.0);
292264

293-
// Write to global memory (coalesced within warp)
294265
state_batch[state_base + elem_offset] = c1;
295266
if (elem_offset + 1 < state_len) {
296267
state_batch[state_base + elem_offset + 1] = c2;
297268
}
298269
}
299270
}
300271

301-
/// Optimized batch amplitude encoding kernel (float32)
302-
///
303-
/// Memory Layout (row-major):
304-
/// - input_batch: [sample0_data | sample1_data | ... | sampleN_data]
305-
/// - state_batch: [sample0_state | sample1_state | ... | sampleN_state]
306-
///
307-
/// Optimizations:
308-
/// 1. Vectorized float2 loads for 64-bit memory transactions
309-
/// 2. Grid-stride loop for arbitrary batch sizes
310-
/// 3. Coalesced memory access within warps
311-
/// 4. Minimized register pressure
272+
/// Batch amplitude encoding kernel (float32).
312273
__global__ void amplitude_encode_batch_kernel_f32(
313274
const float* __restrict__ input_batch,
314275
cuComplex* __restrict__ state_batch,
@@ -317,25 +278,20 @@ __global__ void amplitude_encode_batch_kernel_f32(
317278
size_t input_len,
318279
size_t state_len
319280
) {
320-
// Grid-stride loop pattern for flexibility
321281
const size_t elements_per_sample = state_len / 2;
322282
const size_t total_work = num_samples * elements_per_sample;
323283
const size_t stride = gridDim.x * blockDim.x;
324284

325285
size_t global_idx = blockIdx.x * blockDim.x + threadIdx.x;
326286

327-
// Process elements in grid-stride fashion
328287
for (size_t idx = global_idx; idx < total_work; idx += stride) {
329-
// Decompose linear index into (sample, element_pair)
330288
const size_t sample_idx = idx / elements_per_sample;
331289
const size_t elem_pair = idx % elements_per_sample;
332290

333-
// Calculate base addresses (strength-reduced)
334291
const size_t input_base = sample_idx * input_len;
335292
const size_t state_base = sample_idx * state_len;
336293
const size_t elem_offset = elem_pair * 2;
337294

338-
// Load inverse norm (cached by L1)
339295
const float inv_norm = inv_norms[sample_idx];
340296

341297
float v1, v2;
@@ -357,11 +313,9 @@ __global__ void amplitude_encode_batch_kernel_f32(
357313
v1 = v2 = 0.0f;
358314
}
359315

360-
// Normalize and write as complex numbers
361316
const cuComplex c1 = make_cuComplex(v1 * inv_norm, 0.0f);
362317
const cuComplex c2 = make_cuComplex(v2 * inv_norm, 0.0f);
363318

364-
// Write to global memory (coalesced within warp)
365319
state_batch[state_base + elem_offset] = c1;
366320
if (elem_offset + 1 < state_len) {
367321
state_batch[state_base + elem_offset + 1] = c2;
@@ -397,14 +351,9 @@ int launch_amplitude_encode_batch(
397351

398352
cuDoubleComplex* state_complex_d = static_cast<cuDoubleComplex*>(state_batch_d);
399353

400-
// Optimal configuration for modern GPUs (SM 7.0+)
401-
// - Block size: DEFAULT_BLOCK_SIZE threads (8 warps, good occupancy)
402-
// - Grid size: Enough blocks to saturate GPU, but not excessive
403354
const int blockSize = DEFAULT_BLOCK_SIZE;
404355
const size_t total_work = num_samples * (state_len / 2);
405356

406-
// Calculate grid size: aim for high occupancy without too many blocks
407-
// Limit to reasonable number of blocks to avoid scheduler overhead
408357
const size_t blocks_needed = (total_work + blockSize - 1) / blockSize;
409358
const size_t max_blocks = MAX_GRID_BLOCKS;
410359
const size_t gridSize = (blocks_needed < max_blocks) ? blocks_needed : max_blocks;
@@ -462,7 +411,6 @@ __global__ void l2_norm_kernel(
462411
size_t input_len,
463412
double* __restrict__ out_accum
464413
) {
465-
// Vectorized double2 loads for bandwidth and coalescing
466414
const size_t vec_idx = blockIdx.x * blockDim.x + threadIdx.x;
467415
const size_t stride = gridDim.x * blockDim.x;
468416

@@ -497,7 +445,6 @@ __global__ void l2_norm_kernel_f32(
497445
size_t input_len,
498446
float* __restrict__ out_accum
499447
) {
500-
// Vectorized float2 loads for bandwidth and coalescing
501448
const size_t vec_idx = blockIdx.x * blockDim.x + threadIdx.x;
502449
const size_t stride = gridDim.x * blockDim.x;
503450

qdp/qdp-kernels/src/iqp.cu

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ __device__ double compute_phase(
4343

4444
// Single-qubit Z terms: sum_i x_i * data[i]
4545
for (unsigned int i = 0; i < num_qubits; ++i) {
46-
// PR1 Optimization: Use arithmetic multiplication instead of conditional branch
47-
// (if x_i != 0) to eliminate warp divergence across threads.
4846
phase += data[i] * (double)((x >> i) & 1U);
4947
}
5048

@@ -53,7 +51,6 @@ __device__ double compute_phase(
5351
unsigned int pair_idx = num_qubits;
5452
for (unsigned int i = 0; i < num_qubits; ++i) {
5553
for (unsigned int j = i + 1; j < num_qubits; ++j) {
56-
// PR1 Optimization: Boolean arithmetic avoids branching for ZZ terms.
5754
phase += data[pair_idx] * (double)(((x >> i) & 1U) & ((x >> j) & 1U));
5855
pair_idx++;
5956
}
@@ -89,10 +86,7 @@ __device__ cuDoubleComplex compute_amplitude_naive(
8986
return make_cuDoubleComplex(real_sum, imag_sum);
9087
}
9188

92-
// ============================================================================
93-
// Naive Implementation: O(2^n) per amplitude, O(4^n) for the full state
94-
// (kept as fallback for small n and verification)
95-
// ============================================================================
89+
// Naive O(2^n) per amplitude; fallback when FWT overhead dominates.
9690

9791
__global__ void iqp_encode_kernel_naive(
9892
const double* __restrict__ data,
@@ -112,9 +106,7 @@ __global__ void iqp_encode_kernel_naive(
112106
}
113107

114108

115-
// ============================================================================
116-
// FWT O(n * 2^n) Implementation
117-
// ============================================================================
109+
// FWT O(n * 2^n) path.
118110

119111
// Step 1: Compute f[x] = exp(i*theta(x)) for all x.
120112
// Uses a grid-stride loop so large state vectors can reuse a fixed launch size.
@@ -262,9 +254,7 @@ __global__ void normalize_state_kernel(
262254
}
263255
}
264256

265-
// ============================================================================
266-
// Naive O(4^n) Batch Implementation (kept as fallback)
267-
// ============================================================================
257+
// Naive batch fallback for small n.
268258

269259
__global__ void iqp_encode_batch_kernel_naive(
270260
const double* __restrict__ data_batch,
@@ -278,7 +268,6 @@ __global__ void iqp_encode_batch_kernel_naive(
278268
const size_t total_elements = num_samples * state_len;
279269
const size_t stride = gridDim.x * blockDim.x;
280270
const size_t state_mask = state_len - 1;
281-
// Normalize by 1/2^n (state_len = 2^n) - hoisted outside the loop
282271
const double norm = 1.0 / (double)state_len;
283272

284273
for (size_t global_idx = blockIdx.x * blockDim.x + threadIdx.x;
@@ -295,9 +284,7 @@ __global__ void iqp_encode_batch_kernel_naive(
295284
}
296285

297286

298-
// ============================================================================
299-
// FWT O(n * 2^n) Batch Implementation
300-
// ============================================================================
287+
// FWT batch path.
301288

302289
// Step 1: Compute the normalized phase vector for all samples in batch.
303290
__global__ void iqp_phase_batch_kernel(

qdp/qdp-kernels/src/iqp_tc.cu

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one or more
3+
// contributor license agreements. See the NOTICE file distributed with
4+
// this work for additional information regarding copyright ownership.
5+
// The ASF licenses this file to You under the Apache License, Version 2.0
6+
// (the "License"); you may not use this file except in compliance with
7+
// the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
117
// iqp_tc.cu
218
#include <cuda_runtime.h>
319
#include <cuComplex.h>
@@ -26,7 +42,7 @@ __device__ double compute_phase_tc(
2642
return phase;
2743
}
2844

29-
// PR3: Shared-memory FWT path (Operator Fusion)
45+
// Shared-memory FWT path (Operator Fusion)
3046
// Fuses Phase computation, Fast Walsh-Hadamard Transform, and Normalization
3147
// entirely within Shared Memory. This completely avoids DRAM roundtrips for N <= 12.
3248
__global__ void iqp_phase_fwt_normalize_tc_kernel(
@@ -162,7 +178,7 @@ void iqp_tc_launch_transpose(const double* d_in, double* d_out, int B, int rows,
162178
iqp_tc_batch_transpose_kernel<<<grid, block, 0, stream>>>(d_in, d_out, B, rows, cols);
163179
}
164180

165-
// PR4: Naive Implicit Hadamard GEMM (Fallback before PR5/6 Tensor Core integration)
181+
// Naive Implicit Hadamard GEMM (Fallback before PR5/6 Tensor Core integration)
166182
// Computes Y = X * H_K where H_K is a KxK Hadamard matrix generated on-the-fly.
167183
__global__ void naive_implicit_hadamard_gemm_kernel(const double* __restrict__ X, double* __restrict__ Y, int B, int M, int K, double norm) {
168184
int k = blockIdx.x * blockDim.x + threadIdx.x;
@@ -185,7 +201,7 @@ void launch_naive_implicit_hadamard(const double* d_in, double* d_out, int B, in
185201
naive_implicit_hadamard_gemm_kernel<<<grid, block, 0, stream>>>(d_in, d_out, B, M, K, norm);
186202
}
187203

188-
// PR2: Recombine Real and Imaginary parts back into cuDoubleComplex
204+
// Recombine Real and Imaginary parts back into cuDoubleComplex
189205
// This restores the memory layout after Tensor Core matrix multiplications.
190206
__global__ void recombine_complex_kernel(
191207
const double* __restrict__ real_part,
@@ -209,7 +225,7 @@ extern "C" int launch_iqp_encode_tc(
209225
cudaStream_t stream
210226
) {
211227
if (num_qubits <= FWT_SHARED_MEM_THRESHOLD) {
212-
// PR3: For N <= 12, use the fused Shared Memory FWT kernel
228+
// For N <= 12, use the fused Shared Memory FWT kernel
213229
double norm_factor = 1.0 / (double)state_len;
214230
unsigned int data_len = num_qubits;
215231
// Request max dynamic shared memory for this kernel
@@ -218,7 +234,7 @@ extern "C" int launch_iqp_encode_tc(
218234
data_batch_d, static_cast<cuDoubleComplex*>(state_batch_d), num_samples, state_len, num_qubits, data_len, enable_zz, norm_factor
219235
);
220236
} else {
221-
// PR4: Blocked TC-FWT (Kronecker Product Decomposition)
237+
// Blocked TC-FWT (Kronecker Product Decomposition)
222238
size_t m_samples = num_samples;
223239
size_t total_elements = m_samples * state_len;
224240

@@ -236,18 +252,18 @@ extern "C" int launch_iqp_encode_tc(
236252
cudaMalloc(&d_out_imag, total_elements * sizeof(double));
237253
cudaMalloc(&d_temp_real, total_elements * sizeof(double));
238254
cudaMalloc(&d_temp_imag, total_elements * sizeof(double));
239-
255+
240256
// 1. Initialize Phase (Split Real/Imag)
241257
unsigned int data_len = num_qubits;
242258
const size_t blocks = (total_elements + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE;
243259
iqp_phase_split_kernel<<<blocks, DEFAULT_BLOCK_SIZE, 0, stream>>>(
244260
data_batch_d, d_state_real, d_state_imag, num_samples, state_len, num_qubits, data_len, enable_zz
245261
);
246-
262+
247263
double norm_factor = 1.0 / (double)state_len;
248264

249265
// 3. TC-FWT Step 1: Z = X * H_{n2} (X shape: B*dim1 x dim2)
250-
// PR4: Uses Naive GEMM Placeholder. PR5/6 will replace this with Ozaki Implicit Engine.
266+
// Uses Naive GEMM Placeholder. PR5/6 will replace this with Ozaki Implicit Engine.
251267
launch_naive_implicit_hadamard(d_state_real, d_out_real, num_samples * dim1, dim2, dim2, 1.0, stream);
252268
launch_naive_implicit_hadamard(d_state_imag, d_out_imag, num_samples * dim1, dim2, dim2, 1.0, stream);
253269

@@ -262,19 +278,19 @@ extern "C" int launch_iqp_encode_tc(
262278
// 6. TC-FWT Step 4: Transpose back (B, dim2, dim1) -> (B, dim1, dim2)
263279
iqp_tc_launch_transpose(d_out_real, d_temp_real, num_samples, dim2, dim1, stream);
264280
iqp_tc_launch_transpose(d_out_imag, d_temp_imag, num_samples, dim2, dim1, stream);
265-
281+
266282
// 7. Recombine and Write back
267283
recombine_complex_kernel<<<blocks, DEFAULT_BLOCK_SIZE, 0, stream>>>(
268284
d_temp_real, d_temp_imag, static_cast<cuDoubleComplex*>(state_batch_d), total_elements
269285
);
270-
286+
271287
cudaFree(d_state_real);
272288
cudaFree(d_state_imag);
273289
cudaFree(d_out_real);
274290
cudaFree(d_out_imag);
275291
cudaFree(d_temp_real);
276292
cudaFree(d_temp_imag);
277293
}
278-
294+
279295
return (int)cudaSuccess;
280296
}

qdp/qdp-kernels/src/phase.cu

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,12 @@ __global__ void phase_encode_kernel(
4949
// φ(idx) = Σ_k phases[k] * b_k, b_k = (idx >> k) & 1
5050
double phi = 0.0;
5151
for (unsigned int bit = 0; bit < num_qubits; ++bit) {
52-
// PR1 Optimization: Use cast & multiplication instead of `if ((idx >> bit) & 1U)`
53-
// to avoid thread divergence in the GPU warp.
5452
phi += phases[bit] * (double)((idx >> bit) & 1U);
5553
}
5654

5755
double re, im;
5856
sincos(phi, &im, &re); // re = cos(φ), im = sin(φ)
5957

60-
// PR1 Optimization: norm_factor is pre-calculated on the host (CPU) and passed
61-
// down to save GPU cycles that would be spent calculating pow(1/sqrt(2), n) repeatedly.
6258
state[idx] = make_cuDoubleComplex(norm_factor * re, norm_factor * im);
6359
}
6460

@@ -83,14 +79,12 @@ __global__ void phase_encode_batch_kernel(
8379

8480
double phi = 0.0;
8581
for (unsigned int bit = 0; bit < num_qubits; ++bit) {
86-
// PR1 Optimization: Use cast & multiplication to eliminate warp divergence
8782
phi += phases[bit] * (double)((element_idx >> bit) & 1U);
8883
}
8984

9085
double re, im;
9186
sincos(phi, &im, &re);
9287

93-
// PR1 Optimization: norm_factor is pre-calculated on the host (CPU)
9488
state_batch[global_idx] = make_cuDoubleComplex(norm_factor * re, norm_factor * im);
9589
}
9690
}

0 commit comments

Comments
 (0)