GPUFlightAll performance labs

Embed GPUFlight: Own the Profiling Lifecycle

The starter already has a complete CUDA vector-add and its result check. Your job is to wire in the GPUFlight profiling lifecycle yourself: initialize the SDK before the GPU work, capture kernel activity with the Trace engine, print a session report, and shut the SDK down cleanly. Follow the three TODO markers in main().

Sign in to run this lab. GPU availability and credits are shown in the app.

Languages: CUDA C++

Lesson

Launcher-mode labs attach GPUFlight from outside the program. This lab moves the profiling lifecycle into your own main.cu: your process chooses the log directory, selects the profiling engine, calls gpufl::init before the CUDA work, and calls gpufl::shutdown only after the last GPU operation has completed.

The worker compiles this file as a standalone application linked with the GPUFlight SDK. A successful exit is not enough: the evidence panel checks the emitted session for job_start and shutdown lifecycle records, confirms the resolved Trace engine, and verifies that at least one kernel launch was captured. The program output must also retain the deterministic VERIFY PASS marker, and gpufl::generateReport() after shutdown prints the session report the evidence panel looks for.

The three profiling calls are yours to add - the starter marks each with a TODO. Order matters: gpufl::init before the first launch, gpufl::shutdown after the last GPU op, then gpufl::generateReport() to read the finalized log.

For your own application, copy the starter lifecycle in order: initialize the SDK, begin capture around the intended work, synchronize that work, finish capture, then shut down. Keep setup outside the measured region.

What you will learn

  • Initialize GPUFlight from an application instead of relying on the gpufl launcher.
  • Choose a writable worker-provided log directory without embedding credentials or deployment paths.
  • Place init and shutdown around the GPU work so the session is complete and uploadable.
  • Use session evidence, not process exit alone, to confirm that profiling actually captured a kernel.
  • Print an on-exit profiling report with gpufl::generateReport() and confirm it reached the output.

Starter code

#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cuda_runtime.h>
#include <gpufl/gpufl.hpp>

[[maybe_unused]] static const char* requiredEnv(const char* name) {
    const char* value = std::getenv(name);
    if (value == nullptr || value[0] == '\0') {
        std::fprintf(stderr, "ERROR: required environment variable %s is missing\n", name);
        std::exit(2);
    }
    return value;
}

static void checkCuda(cudaError_t result, const char* operation) {
    if (result != cudaSuccess) {
        std::fprintf(stderr, "ERROR: %s failed: %s\n", operation, cudaGetErrorString(result));
        std::exit(3);
    }
}

__global__ void vectorAdd(const float* a, const float* b, float* out, int n) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < n) out[i] = a[i] + b[i];
}

int main() {
    constexpr int n = 1 << 20;
    constexpr size_t bytes = n * sizeof(float);

    // TODO 1 - Start the GPUFlight profiling session BEFORE any CUDA work below.
    //   Declare a gpufl::InitOptions and set: app_name, log_path (point it at
    //   requiredEnv("GPUFL_LAB_LOG_DIR")), and
    //   profiling_engine = gpufl::ProfilingEngine::Trace. Then call
    //   gpufl::init(options) and return 1 if it returns false. Without init no
    //   session is ever created and the run reports "no session directory".

    std::vector<float> a(n, 1.25f);
    std::vector<float> b(n, 2.75f);
    std::vector<float> out(n, 0.0f);
    float *deviceA = nullptr, *deviceB = nullptr, *deviceOut = nullptr;
    checkCuda(cudaMalloc(&deviceA, bytes), "cudaMalloc A");
    checkCuda(cudaMalloc(&deviceB, bytes), "cudaMalloc B");
    checkCuda(cudaMalloc(&deviceOut, bytes), "cudaMalloc output");
    checkCuda(cudaMemcpy(deviceA, a.data(), bytes, cudaMemcpyHostToDevice), "copy A");
    checkCuda(cudaMemcpy(deviceB, b.data(), bytes, cudaMemcpyHostToDevice), "copy B");

    constexpr int blockSize = 256;
    vectorAdd<<<(n + blockSize - 1) / blockSize, blockSize>>>(deviceA, deviceB, deviceOut, n);
    checkCuda(cudaGetLastError(), "vectorAdd launch");
    checkCuda(cudaDeviceSynchronize(), "vectorAdd synchronize");
    checkCuda(cudaMemcpy(out.data(), deviceOut, bytes, cudaMemcpyDeviceToHost), "copy output");

    bool correct = true;
    for (int i = 0; i < n; ++i) {
        if (out[i] != 4.0f) { correct = false; break; }
    }

    cudaFree(deviceOut);
    cudaFree(deviceB);
    cudaFree(deviceA);

    // TODO 2 - Close the session as the LAST GPUFlight lifecycle call, only
    //   after the final GPU operation above. Call gpufl::shutdown() so the
    //   session records a shutdown and becomes complete and uploadable.

    // TODO 3 - Print the profiling report. After shutdown, call
    //   gpufl::generateReport() with no argument; it reads the finalized log
    //   and writes the "GPU Flight Session Report" to stdout.

    if (!correct) {
        std::fprintf(stderr, "VERIFY FAIL\n");
        return 4;
    }
    std::printf("VERIFY PASS\n");
    return 0;
}

Sign in to run