sinatras/pmpp-eval huggingface dataset:
Question: A CUDA kernel is launched with 1000 thread blocks, each with 512 threads. If a variable is declared as a local (per-thread) variable inside the kernel, how many distinct instances of this variable are created during execution?
Answer: 512,000
Explanation: Local variables are per-thread. Total threads = 1000 blocks x 512 threads/block = 512,000 instances.
Question: A CUDA kernel is launched with 1000 thread blocks, each with 512 threads. if a variable is declared in shared memory, how many distinct instances of this variable are created during execution?
Answer: 1,000
Explanation: Shared memory is per-block. There is exactly one instance per block -> 1000 instances total.
Question: For multiplying two NxN matrices without tiling, how many times is each input element requested from global memory?
Answer: N times.
Explanation: In the naive kernel each output element recomputes its dot product by reloading the same row/column elements. Each input element participates in N different outputs along the corresponding dimension, leading to N separate loads.
Question: For multiplying two NxN matrices with TxT tiling (ideal reuse within tiles), how many times is each input element requested from global memory?
Answer: N/T times.
Explanation: Each input element is fetched once per tile-stripe it participates in along the multiply dimension. Tiling reduces redundant loads by a factor of T, so loads per element drop from N to N/T.
Question: A CUDA kernel performs 36 floating-point operations and 7 global 32-bit (4-byte) memory accesses per thread. On a GPU with peak 200 GFLOP/s compute throughput and 100 GB/s memory bandwidth, is the kernel compute-bound or memory-bound? Justify briefly using a roofline-style argument.
Answer: Memory-bound.
Explanation: Arithmetic intensity = 36 FLOPs / (7x4 B) = 36/28 ~ 1.286 FLOP/B. Machine balance = 200 GFLOP/s ÷ 100 GB/s = 2.0 FLOP/B. Since 1.286 < 2.0, performance is limited by memory bandwidth.
Question: A CUDA kernel performs 36 floating-point operations and 7 global 32-bit (4-byte) memory accesses per thread. On a GPU with peak 300 GFLOP/s compute throughput and 250 GB/s memory bandwidth, is the kernel compute-bound or memory-bound? Justify briefly using a roofline-style argument.
Answer: Compute-bound.
Explanation: Arithmetic intensity = 36 FLOPs / (7x4 B) = 36/28 ~ 1.286 FLOP/B. Machine balance = 300 GFLOP/s ÷ 250 GB/s = 1.2 FLOP/B. Since 1.286 > 1.2, performance is limited by compute throughput.
Question: You are given this tile-transpose kernel (abbrev.):

```cpp
dim3 blockDim(BLOCK_WIDTH,BLOCK_WIDTH);
dim3 gridDim(A_width/blockDim.x, A_height/blockDim.y);
BlockTranspose<<<gridDim, blockDim>>>(A, A_width, A_height);

__global__ void BlockTranspose(float* A_elements, int A_width, int A_height) {
    __shared__ float blockA[BLOCK_WIDTH][BLOCK_WIDTH];
    int baseIdx = blockIdx.x * BLOCK_WIDTH + threadIdx.x;
    baseIdx += (blockIdx.y * BLOCK_WIDTH + threadIdx.y) * A_width;
    blockA[threadIdx.y][threadIdx.x] = A_elements[baseIdx];
    // (no barrier here)
    A_elements[baseIdx] = blockA[threadIdx.x][threadIdx.y];
}
```
For which BLOCK_WIDTH values does this execute correctly?
Answer: Only BLOCK_WIDTH = 1.
Explanation: Without a barrier, some threads can read from shared memory before peers have written their elements. With a 1x1 block, there is only one thread and no race; for any larger tile, a race exists.
Question: You are given this tile-transpose kernel (abbreviated):

```cpp
dim3 blockDim(BLOCK_WIDTH, BLOCK_WIDTH);
dim3 gridDim(A_width / blockDim.x, A_height / blockDim.y);
BlockTranspose<<<gridDim, blockDim>>>(A, A_width, A_height);

__global__ void BlockTranspose(float* A_elements, int A_width, int A_height) {
    __shared__ float blockA[BLOCK_WIDTH][BLOCK_WIDTH];
    int baseIdx = blockIdx.x * BLOCK_WIDTH + threadIdx.x;
    baseIdx += (blockIdx.y * BLOCK_WIDTH + threadIdx.y) * A_width;
    blockA[threadIdx.y][threadIdx.x] = A_elements[baseIdx];
    // (no barrier here)
    A_elements[baseIdx] = blockA[threadIdx.x][threadIdx.y];
}
```
Explain the root cause of incorrect execution for BLOCK_WIDTH > 1 and give a minimal fix that makes it correct for any BLOCK_WIDTH >= 1. Give a single token naming the minimal synchronization fix.
Answer: __syncthreads()
Explanation: All threads must complete their writes to shared memory before any thread reads `blockA[tx][ty]`. Without a barrier, reads can see stale values (race). Adding `__syncthreads();` between the store and the load enforces correctness for any tile size.
Question: Consider the following CUDA code:

```cpp
__global__ void foo_kernel(float* a, float* b) {
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    float x[4];
    __shared__ float y_s;
    __shared__ float b_s[128];
    for (unsigned int j = 0; j < 4; ++j) {
        x[j] = a[j * blockDim.x * gridDim.x + i];
    }
    if (threadIdx.x == 0) { y_s = 7.4f; }
    b_s[threadIdx.x] = b[i];
    __syncthreads();
    b[i] = 2.5f*x[0] + 3.7f*x[1] + 6.3f*x[2] + 8.5f*x[3]
         + y_s*b_s[threadIdx.x] + b_s[(threadIdx.x + 3) % 128];
}

void foo(float* a_d, float* b_d) {
    unsigned int N = 1024;
    foo_kernel<<<(N + 128 - 1) / 128, 128>>>(a_d, b_d);
}
```
How many distinct instances of variable `i` exist during execution?
Answer: 1,024
Explanation: Blocks = (1024 + 127) / 128 = 8; threads per block = 128; total threads = 8 x 128 = 1,024. `i` is per-thread.
Question: Consider the following CUDA code:

```cpp
__global__ void foo_kernel(float* a, float* b) {
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    float x[4];
    __shared__ float y_s;
    __shared__ float b_s[128];
    for (unsigned int j = 0; j < 4; ++j) {
        x[j] = a[j * blockDim.x * gridDim.x + i];
    }
    if (threadIdx.x == 0) { y_s = 7.4f; }
    b_s[threadIdx.x] = b[i];
    __syncthreads();
    b[i] = 2.5f*x[0] + 3.7f*x[1] + 6.3f*x[2] + 8.5f*x[3]
         + y_s*b_s[threadIdx.x] + b_s[(threadIdx.x + 3) % 128];
}

void foo(float* a_d, float* b_d) {
    unsigned int N = 1024;
    foo_kernel<<<(N + 128 - 1) / 128, 128>>>(a_d, b_d);
}
```
How many distinct instances of the local array `x[4]` are created during execution?
Answer: 1,024
Explanation: `x` is a per-thread local array. With 1,024 threads total, there are 1,024 instances.
Question: Consider the following CUDA code:

```cpp
__global__ void foo_kernel(float* a, float* b) {
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    float x[4];
    __shared__ float y_s;
    __shared__ float b_s[128];
    for (unsigned int j = 0; j < 4; ++j) {
        x[j] = a[j * blockDim.x * gridDim.x + i];
    }
    if (threadIdx.x == 0) { y_s = 7.4f; }
    b_s[threadIdx.x] = b[i];
    __syncthreads();
    b[i] = 2.5f*x[0] + 3.7f*x[1] + 6.3f*x[2] + 8.5f*x[3]
         + y_s*b_s[threadIdx.x] + b_s[(threadIdx.x + 3) % 128];
}

void foo(float* a_d, float* b_d) {
    unsigned int N = 1024;
    foo_kernel<<<(N + 128 - 1) / 128, 128>>>(a_d, b_d);
}
```
How many distinct instances of the shared variable `y_s` are created during execution?
Answer: 8
Explanation: Shared memory is per-block. Blocks = (1024 + 127) / 128 = 8 -> 8 instances of `y_s`.
Question: Consider the following CUDA code:

```cpp
__global__ void foo_kernel(float* a, float* b) {
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    float x[4];
    __shared__ float y_s;
    __shared__ float b_s[128];
    for (unsigned int j = 0; j < 4; ++j) {
        x[j] = a[j * blockDim.x * gridDim.x + i];
    }
    if (threadIdx.x == 0) { y_s = 7.4f; }
    b_s[threadIdx.x] = b[i];
    __syncthreads();
    b[i] = 2.5f*x[0] + 3.7f*x[1] + 6.3f*x[2] + 8.5f*x[3]
         + y_s*b_s[threadIdx.x] + b_s[(threadIdx.x + 3) % 128];
}

void foo(float* a_d, float* b_d) {
    unsigned int N = 1024;
    foo_kernel<<<(N + 128 - 1) / 128, 128>>>(a_d, b_d);
}
```
How many distinct instances of the shared array `b_s[128]` are created during execution?
Answer: 8
Explanation: Shared arrays are also per-block. With 8 blocks, there are 8 instances of `b_s`.
Question: Consider the following CUDA code:

```cpp
__global__ void foo_kernel(float* a, float* b) {
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    float x[4];
    __shared__ float y_s;
    __shared__ float b_s[128];
    for (unsigned int j = 0; j < 4; ++j) {
        x[j] = a[j * blockDim.x * gridDim.x + i];
    }
    if (threadIdx.x == 0) { y_s = 7.4f; }
    b_s[threadIdx.x] = b[i];
    __syncthreads();
    b[i] = 2.5f*x[0] + 3.7f*x[1] + 6.3f*x[2] + 8.5f*x[3]
         + y_s*b_s[threadIdx.x] + b_s[(threadIdx.x + 3) % 128];
}

void foo(float* a_d, float* b_d) {
    unsigned int N = 1024;
    foo_kernel<<<(N + 128 - 1) / 128, 128>>>(a_d, b_d);
}
```
What is the amount of shared memory used per block (in bytes)? Give an integer (bytes); do not include units.
Answer: 516
Explanation: `y_s`: 1 float = 4 bytes; `b_s[128]`: 128 floats = 512 bytes; total = 516 bytes.
Question: Consider the following CUDA code and compute the floating-point-operations-per-byte (OP/B) ratio per thread with respect to global memory traffic (assume 4-byte floats, count both reads and the final write):

```cpp
__global__ void foo_kernel(float* a, float* b) {
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    float x[4];
    __shared__ float y_s;
    __shared__ float b_s[128];
    for (unsigned int j = 0; j < 4; ++j) {
        x[j] = a[j * blockDim.x * gridDim.x + i];
    }
    if (threadIdx.x == 0) { y_s = 7.4f; }
    b_s[threadIdx.x] = b[i];
    __syncthreads();
    b[i] = 2.5f*x[0] + 3.7f*x[1] + 6.3f*x[2] + 8.5f*x[3]
         + y_s*b_s[threadIdx.x] + b_s[(threadIdx.x + 3) % 128];
}
```
What is the OP/B value? Round to 3 decimals; provide only the numeric value.
Answer: 0.417
Explanation: Per thread FLOPs = 5 mul + 5 add = 10. Global traffic: 4 reads from `a` (16 B) + 1 read from `b` (4 B) + 1 write to `b` (4 B) = 24 B. OP/B = 10 / 24 ~ 0.417.
Question: GPU limits: 2048 threads/SM, 32 blocks/SM, 65,536 registers/SM, 96 KB shared memory/SM. Kernel uses 64 threads/block, 27 registers/thread, and **4 KB shared memory per block**. Can it reach full occupancy (2048 threads/SM)? If not, what limits it and what is the achieved occupancy? Provide a tuple (Yes|No,limiting_resource,threads) with limiting_resource in {none,shared_memory,blocks,registers}.
Answer: (No,shared_memory,1536)
Explanation: Max blocks by threads = 2048/64 = 32 (OK). Registers: 27x2048 = 55,296 < 65,536 (OK). Shared memory: at 4 KB per block, 96 KB/4 KB = 24 blocks fit, so threads = 24x64 = 1,536 -> 75% occupancy. Limiting factor: shared memory per SM.
Question: GPU limits: 2048 threads/SM, 32 blocks/SM, 65,536 registers/SM, 96 KB shared memory/SM. Kernel uses 256 threads/block, 31 registers/thread, and **8 KB shared memory per block**. Can it reach full occupancy (2048 threads/SM)? If not, what limits it and what is the achieved occupancy? Provide a tuple (Yes|No,limiting_resource,threads) with limiting_resource in {none,shared_memory,blocks,registers}.
Answer: (Yes,none,2048)
Explanation: Max blocks by threads = 2048/256 = 8. Registers: 31x2048 = 63,488 < 65,536 (OK). Shared memory: 8 blocks x 8 KB = 64 KB < 96 KB (OK). Blocks/SM limit is 32 (not binding). All constraints allow 8 blocks x 256 threads = 2048.
Question: A 2D tiled GEMM uses a BLOCK_SIZExBLOCK_SIZE thread block. Threads are indexed (ty, tx). In each phase, threads cooperatively load: M[row, ph*BLOCK_SIZE + tx] and (corner-turned) N[ph*BLOCK_SIZE + ty, col], where row = blockIdx.y*BLOCK_SIZE + ty and col = blockIdx.x*BLOCK_SIZE + tx. Arrays are row-major 4-byte floats. Warps have 32 lanes and lanes vary along x (a warp spans 32 consecutive tx at fixed ty). Which BLOCK_SIZE guarantees that both the M and N loads of every warp are fully coalesced into a single contiguous segment?
A. 8
B. 16
C. 32
D. Any power of two
Answer: C
Explanation: With warps laid out along x, coalescing requires each warp to cover 32 consecutive tx at fixed ty so addresses are contiguous for both loads. BLOCK_SIZE=32 aligns one warp per row; 8 or 16 split a warp across multiple rows.
Question: Using the setup described in a BLOCK_SIZE x BLOCK_SIZE tiled GEMM with corner-turned N load, row-major floats, and 32-lane warps spanning x (BLOCK_SIZExBLOCK_SIZE tiled GEMM, corner-turned N load, row-major floats, warps span x with 32 lanes), if BLOCK_SIZE=16, what is the coalescing behavior of a warp's global loads for M and N?
A. Fully coalesced into a single contiguous segment per warp
B. Two contiguous segments per warp (warp spans two 16-wide rows)
C. Uncoalesced/random access
D. Depends only on base address alignment
Answer: B
Explanation: A 32-lane warp spans two 16-wide rows at fixed ty, so both M and N loads become two 16-element contiguous segments per warp rather than one 32-element segment.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: a[i].
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: A
Explanation: Consecutive threads in a warp access consecutive elements a[i]; this is the canonical coalesced pattern.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: a_s[threadIdx.x], where a_s is declared in __shared__ memory.
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: C
Explanation: Coalescing applies to global memory transactions. Shared memory has different banking rules; coalescing classification is not applicable.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: b[j*blockDim.x*gridDim.x + i] with j fixed inside the loop.
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: A
Explanation: For a fixed j, threads in a warp access consecutive indices offset by a constant base; addresses are contiguous across the warp.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: c[i*4 + j], with j fixed inside the loop.
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: B
Explanation: Across a warp, i increases by 1 so addresses stride by 4 elements (16 B) per thread, leading to multiple memory transactions (not contiguous).
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: bc_s[j*256 + threadIdx.x], where bc_s is __shared__ memory.
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: C
Explanation: Shared memory access; global-memory coalescing classification does not apply.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: a_s[threadIdx.x] (read), where a_s is __shared__ memory.
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: C
Explanation: Shared memory read; coalescing is a global-memory concept.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: d[i + 8] (global write).
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: A
Explanation: The +8 is a constant offset; adjacent threads still write consecutive locations, so the warp issues contiguous transactions.
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: bc_s[threadIdx.x*4] (read), where bc_s is __shared__ memory.
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: C
Explanation: Shared memory read; coalescing classification is not applicable (banking rules apply instead).
Question: Classify the following memory access for coalescing on modern CUDA GPUs. Assume: 1D launch; i = blockIdx.x*blockDim.x + threadIdx.x; warps are 32 threads along x; arrays are float* (4 B) in global memory unless noted; shared memory accesses are not subject to global coalescing. Access: e[i*8] (global write).
A. Coalesced
B. Uncoalesced
C. Not applicable (shared memory)
D. Unaligned but still fully coalesced due to caching
Answer: B
Explanation: Thread addresses stride by 8 elements (32 B) per lane across the warp; not a single contiguous segment, so it's uncoalesced.
Question: Arithmetic intensity (FLOP/B) for naive GEMM: One thread computes P[i,j] with a loop over k=0..n-1, doing one multiply and one add per k (2 FLOPs) and reading M[i,k] and N[k,j] from global memory each iteration. Assume 4-byte floats and ignore output writes. What is the arithmetic intensity?
Answer: 0.25
Explanation: Per output: 2n FLOPs and 2n reads x 4 B = 8n B -> (2n)/(8n) = 0.25 FLOP/B.
Question: Arithmetic intensity (FLOP/B) for tiled GEMM with BLOCK_SIZE=T: Each phase loads one TxT tile of M and one TxT tile of N from global memory and reuses them from shared memory to produce a TxT output tile. Assume 4-byte floats and ignore output writes. For T=32, what is the arithmetic intensity?
Answer: 8
Explanation: Global reads per output = 2n/T elements -> 8n/T bytes; FLOPs per output = 2n. Intensity = (2n)/(8n/T) = T/4 = 8 for T=32.
Question: Arithmetic intensity (FLOP/B) for tiled GEMM with thread coarsening factor C=4: The M tile loaded from global memory is reused across 4 adjacent output tiles; N tiles are not further reused beyond standard tiling. Assume 4-byte floats and ignore output writes. For T=32, what is the arithmetic intensity?
Answer: 12.8
Explanation: Reads per output: M contributes n/(4T), N contributes n/T -> (5n)/(4T) elements -> (5n)/T bytes. FLOPs per output = 2n. Intensity = (2n)/((5n)/T) = 2T/5 = 12.8 for T=32.
Question: Perform 1D discrete convolution with zero-padding (output length equals input length): N = {4, 1, 3, 2, 3}, F = {2, 1, 4}. Use P[i] = sum_{k=0..2} F[k] * N[i - 1 + k], treating out-of-bounds N as 0. What is P?
A. [8, 21, 13, 20, 7]
B. [4, 12, 17, 14, 9]
C. [2, 9, 12, 15, 11]
D. [0, 8, 21, 13, 20]
Answer: A
Explanation: Zero-padding with radius r=1 yields P = [8, 21, 13, 20, 7].
Question: In 1D discrete convolution with zero-padding, what operation does the filter [0, 1, 0] primarily perform on a signal x?
A. Identity (pass-through): y[i] = x[i]
B. Right shift by 1: y[i] = x[i-1]
C. Left shift by 1: y[i] = x[i+1]
D. 3-point moving average
Answer: A
Explanation: [0,1,0] preserves the center sample, acting as an identity under the stated assumptions.
Question: In 1D discrete convolution with zero-padding, what operation does the filter [0, 0, 1] primarily perform?
A. Identity (pass-through)
B. Right shift by 1 sample
C. Left shift by 1 sample
D. 3-point moving average
Answer: B
Explanation: With the conventions used here, [0,0,1] produces a right shift by 1 (y[i] ~ x[i-1]).
Question: In 1D discrete convolution with zero-padding, what operation does the filter [1, 0, 0] primarily perform?
A. Right shift by 1 sample
B. Left shift by 1 sample
C. Identity (pass-through)
D. High-pass smoothing
Answer: B
Explanation: With the conventions used here, [1,0,0] produces a left shift by 1 (y[i] ~ x[i+1]).
Question: In 1D discrete convolution, what is the primary effect of the filter [-1/2, 0, 1/2]?
A. Low-pass smoothing
B. First-derivative (edge detection)
C. Identity
D. Right shift by 1
Answer: B
Explanation: It approximates a first derivative, responding to rapid changes (edges).
Question: In 1D discrete convolution, what is the primary effect of the filter [1/3, 1/3, 1/3]?
A. High-pass edge enhancer
B. Left shift by 1
C. 3-point moving average (smoothing)
D. Identity
Answer: C
Explanation: Equal weights average the local neighborhood, smoothing noise.
Question: 1D convolution on an array of length N with an odd-sized filter of length M = 2r+1 (r = (M-1)/2). How many ghost (zero-padded) cells are there in total?
A. r
B. 2r
C. M - 1
D. N + M
Answer: C
Explanation: Total ghost cells = r on the left + r on the right = 2r = M - 1.
Question: 1D convolution on array length N with odd-sized filter M (using zero-padding, counting multiplications even when reading zeros). How many total multiplications are performed?
A. N × M
B. N × (M - 1)
C. (N - M) × M
D. 2N × M
Answer: A
Explanation: Each of N outputs multiplies M taps (zeros included) -> NxM.
Question: 2D convolution on an NxN image with an odd-sized MxM filter (M = 2r+1). With zero-padding, how many ghost cells surround the image in total?
A. 4Nr
B. 2N(M-1)
C. 4r(N + r)
D. N^2 - (N - 2r)^2
Answer: C
Explanation: Padding adds r rows/cols around; total ghost cells = 4r(N + r).
Question: 2D convolution on an NxN image with an MxM filter (zero-padding, counting multiplications even on zeros). How many total multiplications are performed?
A. N^2 × M^2
B. N × M
C. (N - M + 1)^2 × M^2
D. 2N × M
Answer: A
Explanation: Each of N^2 outputs multiplies M^2 taps -> N^2xM^2.
Question: 2D convolution on an N1xN2 image with an odd-sized M1xM2 filter. Let r1=(M1-1)/2 and r2=(M2-1)/2. With zero-padding, how many ghost cells are there in total?
A. 2(N1 r2 + N2 r1)
B. 2(N1 r2 + N2 r1) + 4 r1 r2
C. (N1 + N2)(r1 + r2)
D. N1 N2 (r1 + r2)
Answer: B
Explanation: Edges contribute 2(N1 r2 + N2 r1); corners add 4 r1 r2.
Question: 2D convolution on an N1xN2 image with an M1xM2 filter (zero-padding, counting multiplications even on zeros). How many total multiplications are performed?
A. N1 N2 M1 M2
B. N1 N2 (M1 + M2)
C. (N1 - M1 + 1)(N2 - M2 + 1) M1 M2
D. 2 N1 N2 M1
Answer: A
Explanation: Each of N1xN2 outputs multiplies M1xM2 taps -> N1 N2 M1 M2.
Question: A 2D tiled convolution uses an output tile of size TxT and an odd-sized filter with radius r=(M-1)/2. Input tiles are (T+2r)x(T+2r) due to halo. For an NxN output, how many thread blocks are needed?
A. ceil(N/T) × ceil(N/T)
B. (N/T) × (N/T) (truncate)
C. N × N
D. ceil(N/(T+2r)) × ceil(N/(T+2r))
Answer: A
Explanation: Each block produces a TxT output tile; tiling the output requires ceil(N/T) in each dimension.
Question: In a tiled 2D convolution setup where the output tile is TxT and the halo radius is r, the block loads a (T+2r)x(T+2r) input tile into shared memory. Assuming one thread per input-tile element, how many threads are needed per block?
A. T^2
B. (T+r)^2
C. (T+2r)^2
D. 2T(T+r)
Answer: C
Explanation: One thread per input-tile element -> (T+2r)^2 threads per block.