sinatras/pmpp-eval huggingface dataset:
Question: Consider a directed graph with vertices 0..7 and adjacency lists:
0->{5,2}, 1->{4}, 2->{3}, 3->{6}, 4->{}, 5->{1,7}, 6->{}, 7->{4,6}.
A level-synchronous vertex-centric push BFS launches one thread per vertex each iteration. Starting from source vertex 0, iteration 1 visits {0}; iteration 2 processes frontier {5,2}; iteration 3 processes frontier {1,7,3}. During iteration 3, how many threads actually iterate over their vertex's neighbor list?
A. 1
B. 2
C. 3
D. 8
Answer: C
Explanation: Iteration 3's frontier contains three vertices (1, 7, 3); only their threads traverse neighbor lists, while the remaining five threads are idle.
Question: The pooling forward routine below processes one feature map with stride K and averages when pooling_type is "avg":

```cpp
void poolingLayer_forward(int M, int H, int W, int K, float* Y, float* S, const char* pooling_type) {
    for (int m = 0; m < M; m++)
        for (int h = 0; h < H/K; h++)
            for (int w = 0; w < W/K; w++) {
                if (strcmp(pooling_type, "max") == 0)
                    S[m*(H/K)*(W/K) + h*(W/K) + w] = -FLT_MAX;
                else
                    S[m*(H/K)*(W/K) + h*(W/K) + w] = 0.0f;
                for (int p = 0; p < K; p++)
                    for (int q = 0; q < K; q++) {
                        float val = Y[m*H*W + (K*h + p)*W + (K*w + q)];
                        if (strcmp(pooling_type, "max") == 0) {
                            if (val > S[m*(H/K)*(W/K) + h*(W/K) + w])
                                S[m*(H/K)*(W/K) + h*(W/K) + w] = val;
                        } else {
                            S[m*(H/K)*(W/K) + h*(W/K) + w] += val / (K*K);
                        }
                    }
            }
}
```

Take M=1, H=4, W=4, K=2, pooling_type="avg", and the input feature map
Y = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]].
What value does the kernel store at output position (h=0, w=0)? Provide the result rounded to two decimals.
Answer: 3.50
Explanation: The 2x2 window [[1,2],[5,6]] averages to 3.5, so the stored value is 3.50.
Question: For input size [8, 64, 128, 128], the custom max pooling benchmark reports 82.433 ms while PyTorch reports 9.454 ms. Compute the speedup of PyTorch relative to the custom kernel (custom_time / pytorch_time) and round to two decimals.
Answer: 8.72
Explanation: Speedup = 82.433 / 9.454 = 8.719..., which rounds to 8.72.
Question: The CUDA kernel below unrolls an input tensor X into X_unroll for convolution.

```cpp
__global__ void unroll_Kernel(int C, int H, int W, int K, float* X, float* X_unroll) {
    int t = blockIdx.x * blockDim.x + threadIdx.x;
    int H_out = H - K + 1;
    int W_out = W - K + 1;
    int W_unroll = H_out * W_out;
    if (t < C * W_unroll) {
        int c = t / W_unroll;
        int w_unroll = t % W_unroll;
        int h_out = w_unroll / W_out;
        int w_out = w_unroll % W_out;
        int w_base = c * K * K;
        for (int p = 0; p < K; p++)
            for (int q = 0; q < K; q++) {
                int h_unroll = w_base + p*K + q;
                X_unroll[h_unroll * W_unroll + w_unroll] = X[c * H * W + (h_out + p) * W + (w_out + q)];
            }
    }
}
```

Consider consecutive thread indices t and t+1 that map to the same channel (same c) and satisfy w_out < W_out - 1. For a fixed (p, q) iteration, what is the difference between their global memory read addresses in X?
A. 0
B. 1
C. W
D. K*K
Answer: B
Explanation: With c and h_out identical and w_out incremented by one, the linearized address in X increases by exactly 1 element, yielding coalesced reads.
Question: Consider the loop-fission version of the FHD computation (indices are zero-based):

```cpp
for (int m = 0; m < M; m++) {
    rMu[m] = rPhi[m]*rD[m] + iPhi[m]*iD[m];
    iMu[m] = rPhi[m]*iD[m] - iPhi[m]*rD[m];
}
for (int m = 0; m < M; m++) {
    for (int n = 0; n < N; n++) {
        rFhD[n] += rMu[m]*cArg(m,n) - iMu[m]*sArg(m,n);
        iFhD[n] += iMu[m]*cArg(m,n) + rMu[m]*sArg(m,n);
    }
}
```

Assume M = 4 and N = 3. Before the very first update to rFhD occurs in the second loop, how many times have the statements computing rMu and iMu (lines 2-3) executed? Give the integer count.
Answer: 4
Explanation: The first loop completes all M iterations before the second loop starts, so lines 2-3 execute once for each m = 0,1,2,3, totalling 4 executions.
Question: The original loop nests iterate with n outer and m inner:

```cpp
for (int n = 0; n < N; n++) {
    for (int m = 0; m < M; m++) {
        body(n, m);
    }
}
```

After loop interchange, the outer loop runs over m first. For M = 2 and N = 3, what is the sequence of (n,m) pairs visited in the **interchanged** version?
A. (0,0), (1,0), (2,0), (0,1), (1,1), (2,1)
B. (0,0), (0,1), (0,2), (1,0), (1,1), (1,2)
C. (0,0), (0,1), (1,0), (1,1), (2,0), (2,1)
D. (0,0), (0,1), (1,1), (1,2), (0,2), (1,0)
Answer: A
Explanation: With m outermost, we hold m=0 and sweep n=0..2, then set m=1 and sweep n=0..2, giving (0,0),(0,1),(0,2),(1,0),(1,1),(1,2).
Question: A CUDA kernel assigns each thread a unique n index:

```cpp
int n = blockIdx.x * FHD_THREADS_PER_BLOCK + threadIdx.x;
float xn = x[n];
for (int m = 0; m < M; m++) {
    float term = kx[m] * xn;
    // ... uses ky[m], kz[m], rMu[m], iMu[m]
}
```

Which statement best explains why caching kx[n] in a register would be useless for this kernel?
A. kx[n] equals zero for all n, so caching offers no benefit.
B. Threads need kx values indexed by m inside the loop, not by n, so kx[n] is never referenced.
C. kx is stored in shared memory already, making registers redundant.
D. Registers cannot hold floating-point values across loop iterations.
Answer: B
Explanation: The loop repeatedly accesses kx[m] for m = 0..M-1; the value kx[n] is never used, so loading it would waste instructions and registers.
Question: The host routine below launches a gather kernel in chunks of constant memory. Assume `CHUNK_SIZE = 256`.

```cpp
void cenergyParallelGather(float* energygrid, dim3 grid_dim, float gridspacing, float z,
                           const float* host_atoms, int numatoms) {
    int num_chunks = (numatoms + CHUNK_SIZE - 1) / CHUNK_SIZE;
    for (int chunk = 0; chunk < num_chunks; ++chunk) {
        int start_atom = chunk * CHUNK_SIZE;
        int atoms_in_chunk = (start_atom + CHUNK_SIZE <= numatoms) ? CHUNK_SIZE
                                                                   : (numatoms - start_atom);
        // cudaMemcpyToSymbol atoms_in_chunk elements and launch kernel
    }
}
```

If `numatoms = 1000`, how many times will the body of the loop (and thus the constant-memory copy) execute? Give the integer count.
Answer: 4
Explanation: num_chunks = ceil(1000 / 256) = (1000 + 255) / 256 = 1255 / 256 = 4 when using integer division with the bias term.
Question: For a coarsening factor of 8, the README compares one iteration of the original gather kernel (original gather kernel) to one iteration of the coarsened kernel (coarsened kernel). The counts per outer iteration are:
- original gather kernel: 32 memory loads, 88 floating-point operations, 8 branch evaluations.
- coarsened kernel: 11 memory loads, 61 floating-point operations, 17 branch evaluations.

Which statement best summarizes the trade-off introduced by thread coarsening?
A. Memory loads decrease, arithmetic decreases, but branch count rises.
B. Memory loads increase, arithmetic increases, and branch count drops.
C. Memory loads and arithmetic both decrease to zero, so only branches remain.
D. All three categories (loads, arithmetic, branches) grow because more work is assigned to each thread.
Answer: A
Explanation: Coarsening reduces global loads (32->11) and arithmetic (88->61) but increases the number of branch evaluations (8->17).
Question: Section 18.3 notes two drawbacks of increasing the work per CUDA thread (high thread coarsening). Which pair captures those concerns?
A. Higher register usage can cut occupancy, and excessive coarsening can leave many cores idle.
B. Device memory becomes read-only, and shared memory can no longer be allocated.
C. Warp size expands beyond 32, and thread blocks stop synchronizing correctly.
D. Atomic operations become mandatory, and constant memory broadcasts no longer function.
Answer: A
Explanation: More work per thread generally consumes more registers (reducing occupancy) and risks underutilizing parallelism if too few threads remain active.
Question: A 25-point stencil operates on a 64x64x2048 grid that is decomposed along z across 17 MPI ranks: 16 compute ranks plus 1 data server. Each compute rank receives 128 consecutive z-slices (ignoring halos). During Stage 2 of the described two-stage exchange (Stage 1: 4-slice boundaries; Stage 2: interior slices) the internal compute ranks process the interior z-slices after the four-slice boundaries are done in Stage 1. How many interior grid points does one internal compute rank update in Stage 2? Provide the integer count.
Answer: 376320
Explanation: Stage 1 covers four front and four back slices, leaving 120 interior slices. Interior work = 56x56x120 = 376,320 points per internal rank.
Question: Using the same 64x64x2048 grid split evenly over 16 compute ranks (plus 1 data server), how many halo grid points must an internal compute rank exchange in Stage 2 of the described two-stage exchange (Stage 1: 4-slice boundaries; Stage 2: interior slices)? Assume four halo slices on each side and single-precision floats.
A. 16,384 points (65,536 bytes)
B. 32,768 points (131,072 bytes)
C. 65,536 points (262,144 bytes)
D. 524,288 points (2,097,152 bytes)
Answer: B
Explanation: Each side requires 4x64x64 = 16,384 points; two sides double that to 32,768 points ~ 131 KB when using 4-byte floats.
Question: The call `MPI_Send(ptr_a, 1000, MPI_FLOAT, 2000, 4, MPI_COMM_WORLD)` transmits 4,000 bytes. What is the size of each element?
A. 1 byte
B. 2 bytes
C. 4 bytes
D. 8 bytes
Answer: C
Explanation: 4,000 byte total / 1,000 elements = 4 bytes per element, consistent with MPI_FLOAT.
Question: Which MPI statement is correct?
A. MPI_Send is the only nonblocking send routine.
B. MPI_Recv blocks until a matching message arrives.
C. MPI messages must be at least 128 bytes.
D. Separate MPI ranks share the same global memory space.
Answer: B
Explanation: MPI_Recv is a blocking receive; the other statements are false.
Question: The parent kernel below launches a child kernel for each Bezier line.

```cpp
typedef float2 float2;
struct BezierLine {
    float2 CP[3];
    float2 *vertexPos;
    int nVertices;
};
__global__ void computeBezierLines_parent(BezierLine *lines, int nLines) {
    int lidx = threadIdx.x + blockDim.x * blockIdx.x;
    if (lidx < nLines) {
        float curvature = 1.0f; // assume curvature chosen elsewhere
        lines[lidx].nVertices = min(max((int)(curvature * 16.0f), 4), 1024);
        cudaMalloc((void**)&lines[lidx].vertexPos, lines[lidx].nVertices * sizeof(float2));
        int childBlocks = (lines[lidx].nVertices + 31) / 32;
        computeBezierLine_child<<<childBlocks, 32>>>(lidx, lines, lines[lidx].nVertices);
    }
}
```
If `nLines = 1024` and the parent is launched with `computeBezierLines_parent<<<16, 64>>>(...)`, how many child kernels are launched in total?
A. 16
B. 32
C. 256
D. 1024
Answer: D
Explanation: Each of the 1024 parent threads (16 blocks x 64 threads) passes the `lidx < nLines` check and launches exactly one child grid, so 1024 child kernels execute.
Question: For a launch with (`nLines = 1024`, `<<<16,64>>>`), suppose each parent thread creates a non-blocking CUDA stream before launching `computeBezierLine_child` and destroys it afterwards. How many streams are created across the entire parent launch? Provide the integer count.
Answer: 1024
Explanation: One stream is created per parent thread, and 16x64 = 1024 parent threads execute, so 1024 streams are created and later destroyed.
Question: A quadtree recursively subdivides a square region until each leaf contains at most one of 64 equidistant points. Including the root node, what is the maximum depth of the quadtree?
A. 4
B. 8
C. 16
D. 64
Answer: A
Explanation: Depth 0 holds all 64 points. Each subdivision splits into 4 equal tiles. After three splits (depths 1-3) there are 64 leaves with one point each, so the maximum depth including the root is 4.
Question: In that quadtree, every node at depths 0, 1, and 2 launches a child kernel for each of its four quadrants. How many parent nodes issue a child-kernel launch in total?
A. 4
B. 16
C. 21
D. 64
Answer: C
Explanation: Launching parents include the root (1 node), all depth-1 nodes (4 nodes), and all depth-2 nodes (16 nodes); 1 + 4 + 16 = 21 parent launches.