Интеграция CUDA в Qt-приложение: архитектура и пример матричного умножения

Автор: | 10 февраля, 2026

Зачем подключать CUDA к Qt

Типичные сценарии:

  • компьютерное зрение
  • фильтрация сигналов
  • рендеринг
  • ML-инференс
  • физические симуляции
  • обработка больших матриц

Qt отвечает за:

  • GUI
  • события
  • визуализацию
  • настройки

CUDA — за:

  • тяжёлые вычисления
  • массовый параллелизм
  • работу с GPU-памятью

Ключевой принцип архитектуры:

UI не знает о CUDA напрямую.
GPU-код изолирован в backend-библиотеке.

Архитектура проекта

qt-cuda-app/
├── CMakeLists.txt
├── app/              # Qt GUI
│   ├── main.cpp
│   ├── mainwindow.*
│   └── CMakeLists.txt
├── gpu_backend/      # CPU-wrapper
│   ├── gpu_api.h
│   ├── gpu_api.cpp
│   └── CMakeLists.txt
├── cuda_kernels/     # CUDA
│   ├── matmul.cu
│   ├── matmul.h
│   └── CMakeLists.txt

Разделение ролей

МодульОтветственность
appQt GUI
gpu_backendC++ API, вызываемый из Qt
cuda_kernelskernels + device-код

GUI вызывает обычный C++ интерфейс — без __global__.

Backend API

gpu_backend/gpu_api.h

#pragma once
#include <vector>

namespace gpu {

std::vector<float> multiplyMatrices(
    const std::vector<float>& A,
    const std::vector<float>& B,
    int M, int K, int N);

}

gpu_backend/gpu_api.cpp

#include "gpu_api.h"
#include "matmul.h"

namespace gpu {

std::vector<float> multiplyMatrices(
    const std::vector<float>& A,
    const std::vector<float>& B,
    int M, int K, int N)
{
    std::vector<float> C(M * N);

    cudaMatMul(A.data(), B.data(),
               C.data(), M, K, N);

    return C;
}

}

CUDA kernel

cuda_kernels/matmul.h

#pragma once

void cudaMatMul(const float* A,
                const float* B,
                float* C,
                int M, int K, int N);

cuda_kernels/matmul.cu

#include "matmul.h"
#include <cuda_runtime.h>
#include <stdexcept>

#define CUDA_CHECK(x) \
    do { \
        cudaError_t err = x; \
        if (err != cudaSuccess) \
            throw std::runtime_error(cudaGetErrorString(err)); \
    } while (0)

constexpr int TILE = 16;

__global__ void matMulKernel(const float* A,
                             const float* B,
                             float* C,
                             int M, int K, int N)
{
    __shared__ float sA[TILE][TILE];
    __shared__ float sB[TILE][TILE];

    int row = blockIdx.y * TILE + threadIdx.y;
    int col = blockIdx.x * TILE + threadIdx.x;

    float acc = 0.f;

    for (int t = 0; t < (K + TILE - 1) / TILE; ++t) {

        int aCol = t * TILE + threadIdx.x;
        int bRow = t * TILE + threadIdx.y;

        sA[threadIdx.y][threadIdx.x] =
            (row < M && aCol < K)
                ? A[row * K + aCol]
                : 0.f;

        sB[threadIdx.y][threadIdx.x] =
            (bRow < K && col < N)
                ? B[bRow * N + col]
                : 0.f;

        __syncthreads();

        for (int k = 0; k < TILE; ++k)
            acc += sA[threadIdx.y][k] *
                   sB[k][threadIdx.x];

        __syncthreads();
    }

    if (row < M && col < N)
        C[row * N + col] = acc;
}

void cudaMatMul(const float* A,
                const float* B,
                float* C,
                int M, int K, int N)
{
    size_t sizeA = M * K * sizeof(float);
    size_t sizeB = K * N * sizeof(float);
    size_t sizeC = M * N * sizeof(float);

    float *dA, *dB, *dC;

    CUDA_CHECK(cudaMalloc(&dA, sizeA));
    CUDA_CHECK(cudaMalloc(&dB, sizeB));
    CUDA_CHECK(cudaMalloc(&dC, sizeC));

    CUDA_CHECK(cudaMemcpy(dA, A, sizeA,
                           cudaMemcpyHostToDevice));
    CUDA_CHECK(cudaMemcpy(dB, B, sizeB,
                           cudaMemcpyHostToDevice));

    dim3 threads(TILE, TILE);
    dim3 blocks((N + TILE - 1) / TILE,
                (M + TILE - 1) / TILE);

    matMulKernel<<<blocks, threads>>>(dA, dB, dC,
                                      M, K, N);

    CUDA_CHECK(cudaDeviceSynchronize());

    CUDA_CHECK(cudaMemcpy(C, dC, sizeC,
                           cudaMemcpyDeviceToHost));

    cudaFree(dA);
    cudaFree(dB);
    cudaFree(dC);
}

Qt GUI вызывает GPU

app/mainwindow.cpp

#include "mainwindow.h"
#include "gpu_api.h"

void MainWindow::runGpuDemo()
{
    constexpr int M = 256;
    constexpr int K = 256;
    constexpr int N = 256;

    std::vector<float> A(M * K, 1.f);
    std::vector<float> B(K * N, 2.f);

    auto C = gpu::multiplyMatrices(A, B, M, K, N);

    ui->resultLabel->setText(
        QString("C[0] = %1").arg(C[0]));
}

GPU-код запускается из кнопки Qt — прозрачно для UI.

CMake-интеграция

Root CMakeLists.txt

cmake_minimum_required(VERSION 3.24)

project(QtCuda LANGUAGES CXX CUDA)

add_subdirectory(cuda_kernels)
add_subdirectory(gpu_backend)
add_subdirectory(app)

cuda_kernels/CMakeLists.txt

add_library(cuda_kernels STATIC matmul.cu)

set_target_properties(cuda_kernels PROPERTIES
    CUDA_SEPARABLE_COMPILATION ON
)

set_property(TARGET cuda_kernels PROPERTY
    CUDA_ARCHITECTURES native)

target_compile_features(cuda_kernels PUBLIC cxx_std_17)

gpu_backend/CMakeLists.txt

add_library(gpu_backend STATIC gpu_api.cpp)

target_include_directories(gpu_backend PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_SOURCE_DIR}/cuda_kernels)

target_link_libraries(gpu_backend PUBLIC cuda_kernels)

app/CMakeLists.txt

find_package(Qt6 REQUIRED COMPONENTS Widgets)

qt_add_executable(app
    main.cpp
    mainwindow.cpp
)

target_link_libraries(app PRIVATE
    Qt6::Widgets
    gpu_backend)

Порядок выполнения:

1️⃣ Qt GUI → gpu_backend
2️⃣ gpu_backend → CUDA wrapper
3️⃣ kernel запускается
4️⃣ GPU считает
5️⃣ результат возвращается
6️⃣ GUI обновляет интерфейс