0% found this document useful (0 votes)
37 views10 pages

GPU Programming Final Exam Overview

This document contains a 5-question, 100-point final exam for a GPU programming course. It covers topics like vertex and fragment shaders, CUDA kernel programming, and OpenGL concepts. Problem 1 (20 points) involves analyzing data amounts for vertex shaders processing chain simulations. Problem 2 (15 points) discusses lighting computations in vertex vs fragment shaders. Problem 3 (20 points) examines a CUDA kernel and optimizations using shared memory. Problem 4's (20 points) questions cover grid/block/warp sizes and constant memory. Problem 5 (25 points) asks about deprecated outputs, user-defined outputs, and swizzle operations. The exam total is out of 100 points and students are wished good luck.

Uploaded by

moien
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
37 views10 pages

GPU Programming Final Exam Overview

This document contains a 5-question, 100-point final exam for a GPU programming course. It covers topics like vertex and fragment shaders, CUDA kernel programming, and OpenGL concepts. Problem 1 (20 points) involves analyzing data amounts for vertex shaders processing chain simulations. Problem 2 (15 points) discusses lighting computations in vertex vs fragment shaders. Problem 3 (20 points) examines a CUDA kernel and optimizations using shared memory. Problem 4's (20 points) questions cover grid/block/warp sizes and constant memory. Problem 5 (25 points) asks about deprecated outputs, user-defined outputs, and swizzle operations. The exam total is out of 100 points and students are wished good luck.

Uploaded by

moien
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Name

GPU Programming
EE 4702-1
Final Examination
Tuesday, 9 December 2014 7:30–9:30 CST

Problem 1 (20 pts)


Problem 2 (15 pts)
Problem 3 (20 pts)
Problem 4 (20 pts)
Problem 5 (25 pts)

Alias Exam Total (100 pts)

Good Luck!
Problem 1: [20 pts] Part of the vertex shader from Homework 6 (which is similar to the vertex shader
in earlier assignments) appears below. For the questions below let c denote the chain length (the number
of balls), let s denote the value of opt_segments (the number of iterations of the t loop in the CPU code),
and let n = cs denote the number of “vertices” sent to the vertex shader.

in ivec2 gl_Vertex; // Vertex Shader Inputs

out Data_to_GS { // Interface block for vertex shader output / geometry shader input.
vec4 vertex_e[2][2]; // Vertex coordinates in eye space.
vec4 position[2][2]; // Vertex coordinates in clip space.
vec2 texCoord[2];
vec3 normal_e[2];
vec3 radial_e; // Normal for edge primitives.
};

void vs_main_lines() {
const int bidx = gl_Vertex.x;
const int ti = gl_Vertex.y;
const int radial_idx = bidx * opt_segments + ti;
const float delta_t = 1.0 / opt_segments;
const float t = float(ti) * delta_t;
const float theta = delta_t * radial_idx * omega;
vec3 pos1 = balls_pos[bidx-1].xyz; // Buffer object access.
vec3 pos2 = balls_pos[bidx].xyz; // Buffer object access.
vec3 v12 = [Link] - [Link];
// ...

(a) For an entire rendering pass (execution of glDrawArrays) how much data is sent from the CPU to the
GPU for this vertex shader? The answer should be in bytes and in terms of c, s, and n.

Amount of data from CPU to GPU:

(b) For an entire rendering pass (execution of glDrawArrays) how much data is output by the vertex shader
for the geometry shader? The answer should be in bytes and in terms of c, s, and n.

Amount of data output from vertex shader:

(c) For an entire rendering pass (execution of glDrawArrays) how much data is read by the geometry shader
(not shown) per frame? It’s important to remember that the input to the vertex shader is line strips and
the input to the geometry shader is lines. The answer should be in bytes and in terms of c, s, and n.

Amount of data input to geometry shader:

2
Problem 1, continued: Recall that balls_pos is an array of ball positions, something that changes each
frame. Note that the vertex shader is reading the positions from a buffer object.
It would be possible, though wasteful, to use a vertex shader input for the ball positions.
(d) Show the vertex shader input declarations that can be used for ball positions. Hint: This part is easy,
look at the code on the previous page.

Declarations:

(e) Compute the amount of data sent from the CPU to the GPU needed when the ball positions are read as
vertex shader inputs. The answer should be in bytes and in terms of c, s, and n.

Amount of data for ball positions as vertex shader inputs:

3
Problem 2: [15 pts] The fragment shader used in many class demonstrations and homework assignments
performs lighting calculations. This yields the best results, but is computationally expensive. The historic
location for computing lighting is the vertex shader.
(a) With the help of a diagram describe how fragment-shader lighting computation yields a more realistic
image than vertex-shader lighting computation.

Fragment shader lighting more realistic because:

(b) How many times more work does it take to compute lighting in the fragment shader? State any assump-
tions used for your answer.

Using the fragment shader for lighting requires x× more computation assuming:

(c) Doing lighting calculations in the fragment shader does not always make a big difference in appearance.
Consider a hybrid scheme in which a fragment-shader input determines whether the fragment shader should
do the lighting calculations or just use the color input. Which stage is best suited to determine if the
fragment shader should do lighting calculations?

Shader stage best suited to determine where to compute lighting?

Explain.

4
Problem 3: [20 pts] The CUDA kernel below computes an operation on all pairs of elements of array
d_pos, the number of array elements is chain_length. Let B denote the block size of the kernel launch.
The grid size (number of blocks) is (and must be) chain_length.

__global__ void force_pairs()


{
const int a_idx = blockIdx.x;
const float4 pos_a = d_pos[a_idx]; // Global Access A

float3 force = make_float3(0,0,0);

for ( int b_idx = threadIdx.x; b_idx < chain_length; b_idx += blockDim.x)


{
const float4 pos_b = d_pos[b_idx];
force += force_compute(pos_a,pos_b);
}

d_vel[a_idx] += delta_t * d_balls[a_idx].mass_inv * force; // Error here.


}

(a) Compute the amount of data read for the entire kernel due to the line Global Access A. The size of a
float4 is 16 bytes and the minimum request size is 32 bytes. Compute this number in terms of B (block
size) and c (chain_length). Note: In the original exam n was used for chain length.

Amount of data read due to Access A.

(b) Modify the code so that it uses shared memory to reduce the amount of data read due to Global Access
A.

Modify to use shared memory to reduce data read for A.

5
Problem 3, continued: The CUDA code below is the same as the previous page. The line marked “Error
here” has two problems.

__global__ void force_pairs()


{
const int a_idx = blockIdx.x;
const float4 pos_a = d_pos[a_idx]; // Global Access A

float3 force = make_float3(0,0,0);

for ( int b_idx = threadIdx.x; b_idx < chain_length; b_idx += blockDim.x)


{
const float4 pos_b = d_pos[b_idx];
force += force_compute(pos_a,pos_b);
}

d_vel[a_idx] += delta_t * d_balls[a_idx].mass_inv * force; // Error here.


}

(c) Due to one problem the wrong value will be written into d_vel. Explain why.

Wrong value written to d_vel because . . ..

(d) The line marked “Error here” is also inefficient. Explain why and fix the problem. Hint: Use shared
memory.

Reason for inefficiency.

Fix with shared memory.

6
Problem 4: Answer each question below.
(a) [5 pts] A CUDA application launches a kernel with a grid size of 20 blocks. Describe a situation in which
that is inefficient and explain why.

Twenty blocks is inefficient when:

Because:

(b) [5 pts] A CUDA application launches a kernel with a block size of 19 threads. Why is that inefficient?

Nineteen threads is inefficient because:

7
Problem 4, continued:
(c) [5 pts] The loop body in the CUDA code below does one of five kinds of operations. Which is the biggest
concern for the code below, grid size, block size, or warp size?
const int tid = threadIdx.x + blockIdx.x * blockDim.x;
for ( int i = tid; i<size; i += num_threads ) {
int k = kind[i];
float4 d = data[i];
switch ( k ) {
case 0: operation_x(i,d); break;
case 1: operation_y(i,d); break;
case 2: operation_z(i,d); break;
case 3: operation_a(i,d); break;
case 4: operation_b(i,d); break;
}
}

Biggest concern for code is grid, block, or warp. (Circle one.)

Explain:

(d) [5 pts] For many classroom CUDA examples a structure is used to hold application related variables,
such as the one below. For the GPU, this had been declared in the __constant__ address space, but it
could have been declared in the global (device) address space (shown commented out below). What’s the
disadvantage of the global space for this?
struct App {
int num_threads, array_size;
Vector *v_in;
Vector *d_v_in;
};

App app; // In host address space.


__constant__ App d_app; // In device constant address space.
// __device__ App d_app; // In device global address space.

Disadvantage of global address space for the App structure?

8
Problem 5: Answer each question below.
(a) [5 pts] The vertex shader below uses the deprecated output gl_FrontColor to hold a lighted color.
Those setting the OpenGL standard would prefer that you define your own vertex shader output to hold
the lighted color. On the other hand, gl_Position is not deprecated and it must be written (if not by the
vertex shader, then by the geometry shader). Why?
void vs_main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

vec4 vertex_e = gl_ModelViewMatrix * gl_Vertex;


vec3 normal_e = normalize(gl_NormalMatrix * gl_Normal);
gl_FrontColor = generic_lighting(vertex_e,gl_Color,normal_e);
}

Cannot substitute gl_Position with a user-defined output because:

(b) [5 pts] It’s possible to define user-defined outputs for the vertex and geometry shaders, but not for the
fragment or compute shaders.

Why can’t there be user-defined fragment shader outputs?

Why can’t there be user-defined compute shader outputs?

9
(c) [5 pts] In the OpenGL shader code below b is assigned the value of a, but with the elements reversed.
It does so the hard way. Do it the easy way (using what we called swizzle operations).
vec4 a, b;
b.x = a.w; b.y = a.z; b.z = a.y; b.w = a.x;

Assign b the easy way.

(d) [5 pts] The fragment shader input’s smooth interpolation qualifier (which is the default for floating-point
values) indicates that the qualified variable (color in the example below) should be linearly interpolated
based on object-space coordinates. Linear interpolation is easy to do, so why is smooth interpolation expen-
sive?
in Data_GF {
smooth vec4 color;
}

smooth interpolation is expensive because . . .

(e) [5 pts] The statement below is correct but does not provide the full justification for GPUs. Complete
the statement.
Executing 3D graphics is computationally intensive and so many computers have GPUs to take
this computation load off the CPU.

To justify GPUs the statement should go on to say . . ..

10

Common questions

Powered by AI

Smooth interpolation is expensive because it requires per-pixel operations that leverage high-precision, perspective-corrected calculations. These operations involve additional computations to account for the perspective transformations and the interpolation might involve complex equations that consider the relative positions in 3D space, unlike simple linear interpolation in object space .

Using a block size of 19 threads is inefficient due to the nature of warp scheduling on NVIDIA GPUs, which typically operate on warps of 32 threads. This configuration results in partial warp utilization, where 13 threads in every warp remain idle, wasting resources and leading to reduced parallel efficiency and computational throughput .

Default interpolation requires complex arithmetic that computes interpolated values across fragments, significantly increasing the computational demands on the GPU. This process, coupled with the constant demands of rendering high-fidelity graphics, justifies the existence of GPUs, as their architecture is optimized to handle massive parallel computations, relieving CPUs from this burdensome task specifically in graphics processing .

gl_Position is retained as a standard output in OpenGL shaders because it is crucial in the fixed-function pipeline for determining vertex position in clip space and subsequent rasterization operations. Custom outputs do not integrate seamlessly with these core processes, making gl_Position indispensable .

Fragment shader lighting provides more realistic results because lighting is computed for each pixel, allowing fine details in the scene to be captured with high precision. This contrasts with vertex shader lighting, which interpolates lighting across vertices and may miss subtle details. However, this requires significantly more computational power because the lighting calculations are performed many times over, corresponding to each fragment rather than each vertex .

A grid size of 20 blocks would be inefficient in scenarios where the workload per block is not evenly distributed. For instance, if the problem size is not a multiple of 20 or significantly larger, many blocks may become idle after completing their tasks while others remain underutilized, leading to poor resource utilization and increased execution time .

The __constant__ address space is optimized for read-only variables frequently accessed by threads in a warp, and returns cached values after the first load, enhancing performance. Using global space can cause reduced efficiency because global memory is non-cached and requires higher latency accesses, potentially leading to slower overall execution due to frequent reads .

The vertex shader accesses the ball positions from a buffer object, which allows dynamic updates without re-uploading data from the CPU for each frame, improving efficiency. Using vertex shader inputs for ball positions would be inefficient because it requires transferring the positions from the CPU to the GPU every time they change, leading to excessive data transmission overhead .

The inefficiency in writing to d_vel is due to each computation being carried out independently without optimizing memory access patterns. Shared memory can be used to store intermediate computations, reducing redundant data reads from global memory and improving memory access efficiency. This optimization leverages the limited, faster shared memory within a block to retain data that would otherwise be repeatedly fetched from slower global memory .

The amount of data sent from the CPU to the GPU is determined by the number of vertices, calculated as n = cs, where c is the chain length and s is opt_segments. For each vertex, a specific amount of data needs to be transmitted based on its components (like positions and other attributes). The formula to calculate the amount of data in bytes involves multiplying the number of vertices (n) by the size of each vertex component .

You might also like