Оптимизация CalculateConvolutionOutputTensor__im2col - PullRequest
0 голосов
/ 09 мая 2020

Запрос

Я пишу, чтобы запросить рекомендации по оптимизации моего решения / метода «CalculateConvolutionOutputTensor__im2col». Я хотел бы помочь определить лучшую стратегию выхода за рамки моего наивного подхода; предложения интуиции о любых соответствующих процессах GPU и о том, как они применяются (например, банковские конфликты); и помогите интерпретировать приведенный выше профиль с точки зрения того, что я могу настроить.

Первый запуск метода занимает 0,774 секунды с использованием GeForce 2080 Ti. Я приложил скриншот профиля Nsight Compute единственного ядра CUDA C ++, которое я написал: im2col.

enter image description here

Вещи, которые я мог сделать

Я мог бы сделать так, чтобы каждый поток GPU имел доступ к общей памяти вместо глобальной памяти. Я мог бы передать переменные "кучи" графического процессора в "стек" ядра вместо разыменования для каждого потока и итерации в ядре для-l oop итерации. Я мог помещать небольшие параметры в массивы в памяти графического процессора и передавать на эти массивы одиночные указатели. Я мог бы использовать более сложную версию im2col.

Вещи, которые я пробовал

Я бы предпочел не использовать cuDNN 7.6.5; когда я использую cuDNN 7.6.5 и пишу оператор «cudnnCreate (& cudnnHandle);», Nsight Compute предлагает, чтобы метод cuModuleGetFunction возвращал CUDA_ERROR_NOT_FOUND.

Восстановление решения

Процедура, которую я использовал для создания этого проекта было создать новый проект среды выполнения CUDA 10.2 с помощью Visual Studio Community 2019, переименовать исходный файл по умолчанию в «main.cu», заменить все содержимое первым блоком кода ниже, добавить «CalculateConvolutionOutputTensor__im2col.h» в мой проект, добавить второй блок кода ниже, добавьте «CalculateConvolutionOutputTensor__im2col.cu» в мой проект, добавьте третий блок кода ниже и добавьте «cublas.lib;» в Project Properties -> Linker -> Input -> Additional Dependencies .

main.cu

// Allow use of cudaMalloc.
#include <cuda_runtime.h>

// Allow use of time(NULL) as a seed.
#include <ctime>

// Allow construction of a default_random_engine.
#include <random>

// Allow use of CalculateConvolutionOutputTensor__im2col.
#include "CalculateConvolutionOutputTensor__im2col.h"


int main()
{
    // --------------------------------------------------------------------------
    // Declare and define parameters of CalculateConvolutionOutputTensor__im2col.
    // --------------------------------------------------------------------------
    float* convolutionOutputTensor;
    cudaMalloc(&convolutionOutputTensor, 6 * 3 * 19 * 19 * 4 * sizeof(float));

    int elementsInFilter = 3 * 590 * 590;

    int elementsInChannelOfOutputTensor = 19 * 19;

    int imagesInSubdivision = 4;

    int channelsInFilter_host = 3;
    int* channelsInFilter_GPU;
    cudaMalloc(&channelsInFilter_GPU, sizeof(int));
    cudaMemcpy(channelsInFilter_GPU, &channelsInFilter_host, sizeof(int), cudaMemcpyHostToDevice);

    int widthOfFilter_host = 590;
    int* widthOfFilter_GPU;
    cudaMalloc(&widthOfFilter_GPU, sizeof(int));
    cudaMemcpy(widthOfFilter_GPU, &widthOfFilter_host, sizeof(int), cudaMemcpyHostToDevice);

    int heightOfOutputTensor_host = 19;
    int* heightOfOutputTensor_GPU;
    cudaMalloc(&heightOfOutputTensor_GPU, sizeof(int));
    cudaMemcpy(heightOfOutputTensor_GPU, &heightOfOutputTensor_host, sizeof(int), cudaMemcpyHostToDevice);

    int widthOfOutputTensor_host = 19;
    int* widthOfOutputTensor_GPU;
    cudaMalloc(&widthOfOutputTensor_GPU, sizeof(int));
    cudaMemcpy(widthOfOutputTensor_GPU, &widthOfOutputTensor_host, sizeof(int), cudaMemcpyHostToDevice);

    int elementsInChannelOfOutputTensor_host = 19 * 19;
    int* elementsInChannelOfOutputTensor_GPU;
    cudaMalloc(&elementsInChannelOfOutputTensor_GPU, sizeof(int));
    cudaMemcpy(
        elementsInChannelOfOutputTensor_GPU,
        &elementsInChannelOfOutputTensor_host,
        sizeof(int),
        cudaMemcpyHostToDevice);

    int channelsInFilter_times_elementsInChannelOfOutputTensor_host = 3 * 19 * 19;
    int* channelsInFilter_times_elementsInChannelOfOutputTensor_GPU;
    cudaMalloc(&channelsInFilter_times_elementsInChannelOfOutputTensor_GPU, sizeof(int));
    cudaMemcpy(
        channelsInFilter_times_elementsInChannelOfOutputTensor_GPU,
        &channelsInFilter_times_elementsInChannelOfOutputTensor_host,
        sizeof(int),
        cudaMemcpyHostToDevice);

    int elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_host = 3 * 590 * 19 * 19;
    int* elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_GPU;
    cudaMalloc(&elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_GPU, sizeof(int));
    cudaMemcpy(
        elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_GPU,
        &elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_host,
        sizeof(int),
        cudaMemcpyHostToDevice);

    int elementsInInputTensor = 3 * 608 * 608 * 4;
    float* inputTensor_host = new float[elementsInInputTensor];
    for (int i = 0; i < elementsInInputTensor; ++i) {
        inputTensor_host[i] = ((float)(i % 255)) / 255.0;
    }
    float* inputTensor_GPU;
    cudaMalloc(&inputTensor_GPU, elementsInInputTensor * sizeof(float));
    cudaMemcpy(
        inputTensor_GPU,
        inputTensor_host,
        elementsInInputTensor * sizeof(float),
        cudaMemcpyHostToDevice);
    delete[] inputTensor_host;

    int horizontalFilterStride_host = 1;
    int* horizontalFilterStride_GPU;
    cudaMalloc(&horizontalFilterStride_GPU, sizeof(int));
    cudaMemcpy(
        horizontalFilterStride_GPU,
        &horizontalFilterStride_host,
        sizeof(int),
        cudaMemcpyHostToDevice);

    int channelsInImage_host = 3;
    int* channelsInImage_GPU;
    cudaMalloc(&channelsInImage_GPU, sizeof(int));
    cudaMemcpy(channelsInImage_GPU, &channelsInImage_host, sizeof(int), cudaMemcpyHostToDevice);

    int verticalFilterStride_host = 1;
    int* verticalFilterStride_GPU;
    cudaMalloc(&verticalFilterStride_GPU, sizeof(int));
    cudaMemcpy(
        verticalFilterStride_GPU,
        &verticalFilterStride_host,
        sizeof(int),
        cudaMemcpyHostToDevice);

    int elementsInCrossSectionOfImage_host = 3 * 608;
    int* elementsInCrossSectionOfImage_GPU;
    cudaMalloc(&elementsInCrossSectionOfImage_GPU, sizeof(int));
    cudaMemcpy(
        elementsInCrossSectionOfImage_GPU,
        &elementsInCrossSectionOfImage_host,
        sizeof(int),
        cudaMemcpyHostToDevice);

    int elementsInImage_host = 3 * 608 * 608;
    int* elementsInImage_GPU;
    cudaMalloc(&elementsInImage_GPU, sizeof(int));
    cudaMemcpy(elementsInImage_GPU, &elementsInImage_host, sizeof(int), cudaMemcpyHostToDevice);

    int filters = 6 * 3;

    int elementsInFilterTensor = 6 * 3 * 3 * 590 * 590;
    float* filterTensor_host = new float[elementsInFilterTensor];
    std::default_random_engine randomNumberGenerator(time(NULL));
    std::normal_distribution<float> normalDistribution(0.0, 1.0);
    for (int i = 0; i < elementsInFilterTensor; ++i) {
        filterTensor_host[i] = normalDistribution(randomNumberGenerator) / sqrt((float)elementsInFilterTensor);
    }
    float* filterTensor_GPU;
    cudaMalloc(&filterTensor_GPU, elementsInFilterTensor * sizeof(float));
    cudaMemcpy(
        filterTensor_GPU,
        filterTensor_host,
        elementsInFilterTensor * sizeof(float),
        cudaMemcpyHostToDevice);
    delete[] filterTensor_host;

    int elementsInOutputSubtensor = 6 * 3 * 19 * 19;


    // -------------------------------------------------
    // Execute CalculateConvolutionOutputTensor__im2col.
    // -------------------------------------------------
    CalculateConvolutionOutputTensor__im2col(
        convolutionOutputTensor,
        elementsInFilter,
        elementsInChannelOfOutputTensor_host,
        imagesInSubdivision,
        channelsInFilter_GPU,
        widthOfFilter_GPU,
        heightOfOutputTensor_GPU,
        widthOfOutputTensor_GPU,
        elementsInChannelOfOutputTensor_GPU,
        channelsInFilter_times_elementsInChannelOfOutputTensor_GPU,
        elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_GPU,
        inputTensor_GPU,
        horizontalFilterStride_GPU,
        channelsInImage_GPU,
        verticalFilterStride_GPU,
        elementsInCrossSectionOfImage_GPU,
        elementsInImage_GPU,
        filters,
        filterTensor_GPU,
        elementsInOutputSubtensor);

    cudaFree(channelsInFilter_GPU);
    cudaFree(widthOfFilter_GPU);
    cudaFree(heightOfOutputTensor_GPU);
    cudaFree(widthOfOutputTensor_GPU);
    cudaFree(elementsInChannelOfOutputTensor_GPU);
    cudaFree(channelsInFilter_times_elementsInChannelOfOutputTensor_GPU);
    cudaFree(elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_GPU);
    cudaFree(inputTensor_GPU);
    cudaFree(horizontalFilterStride_GPU);
    cudaFree(channelsInImage_GPU);
    cudaFree(verticalFilterStride_GPU);
    cudaFree(elementsInCrossSectionOfImage_GPU);
    cudaFree(elementsInImage_GPU);
    cudaFree(filterTensor_GPU);

    // --------------------------------------------------
    // Make sure that convolutionOutputTensor is correct.
    // --------------------------------------------------
    float* convolutionOutputTensor_test = new float[6 * 3 * 19 * 19 * 4];
    cudaMemcpy(
        convolutionOutputTensor_test,
        convolutionOutputTensor,
        6 * 3 * 19 * 19 * 4 * sizeof(float),
        cudaMemcpyDeviceToHost);
    printf("convolutionOutputTensor_test: {");
    for (int i = 0; i < 18; ++i) {
        printf("%f, ", convolutionOutputTensor_test[i]);
    }
    printf("...}\n");
    delete[] convolutionOutputTensor_test;

    cudaFree(convolutionOutputTensor);

    return 0;
}

CalculateConvolutionOutputTensor__im2col.h

void CalculateConvolutionOutputTensor__im2col(
    float* convolutionOutputTensor_child,
    int elementsInFilter_child,
    int elementsInChannelOfOutputTensor_host_child,
    int imagesInSubdivision_child,
    int* channelsInFilter,
    int* widthOfFilter,
    int* heightOfOutputTensor,
    int* widthOfOutputTensor,
    int* elementsInChannelOfOutputTensor_GPU_child,
    int* channelsInFilter_times_elementsInChannelOfOutputTensor,
    int* elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor,
    float* inputTensor_child,
    int* horizontalFilterStride,
    int* channelsInImage,
    int* verticalFilterStride,
    int* elementsInCrossSectionOfImage,
    int* elementsInImage,
    int filters_child,
    float* filterTensor,
    int elementsInOutputSubtensor_child);

CalculateConvolutionOutputTensor__im2col.cu

// Allow use of __global__.
#include <cuda_runtime.h>

// Allow declaration of cublasHandle.
#include "cublas_v2.h"

// Allow use of blockIdx.x, blockDim.x, and threadIdx.x.
#include <device_launch_parameters.h>


__global__
void im2col(
    float* col_child,
    int* channelsInFilter_child,
    int* widthOfFilter_child,
    int* heightOfOutputTensor_child,
    int* widthOfOutputTensor_child,
    int* elementsInChannelOfOutputTensor_child,
    int* channelsInFilter_times_elementsInChannelOfOutputTensor_child,
    int* elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_child,
    float* inputTensor_child_child,
    int* horizontalFilterStride_child,
    int* channelsInImage_child,
    int* verticalFilterStride_child,
    int* elementsInCrossSectionOfImage_child,
    int* image_child,
    int* elementsInImage_child);


void CalculateConvolutionOutputTensor__im2col(
    float* convolutionOutputTensor_child,
    int elementsInFilter_child,
    int elementsInChannelOfOutputTensor_host_child,
    int imagesInSubdivision_child,
    int* channelsInFilter,
    int* widthOfFilter,
    int* heightOfOutputTensor,
    int* widthOfOutputTensor,
    int* elementsInChannelOfOutputTensor_GPU_child,
    int* channelsInFilter_times_elementsInChannelOfOutputTensor,
    int* elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor,
    float* inputTensor_child,
    int* horizontalFilterStride,
    int* channelsInImage,
    int* verticalFilterStride,
    int* elementsInCrossSectionOfImage,
    int* elementsInImage,
    int filters_child,
    float* filterTensor,
    int elementsInOutputSubtensor_child)
{
    // -----------------------------------------
    // Define and declare parameters for im2col.
    // -----------------------------------------
    // Define parameters for the execution configuration of im2col.
    int threads_per_block_for_im2col = 885;
    int blocks_for_im2col =
        (elementsInFilter_child + threads_per_block_for_im2col - 1) / threads_per_block_for_im2col;

    // Declare col.
    float* col;

    // The following statement is required to
    // prevent automatic casting of a product to an eight-byte integer.
    int elementsInFilter_times_elementsInChannelOfOutputTensor =
        elementsInFilter_child * elementsInChannelOfOutputTensor_host_child;

    cudaMalloc(&col, elementsInFilter_times_elementsInChannelOfOutputTensor * sizeof(float));


    // -----------------------------------------------------------------------------
    // Define parameters for calculating the matrix product of filterTensor and col.
    // -----------------------------------------------------------------------------
    // Define a cublasHandle_t object called cublasHandle.
    // Declaring cublasHandle requires '#include "cublas_v2.h"'.
    // Defining cublasHandle requires adding "cublas.lib" to
    // Properties -> Linker -> Input -> Additional Dependencies.
    cublasHandle_t cublasHandle;
    cublasCreate(&cublasHandle);

    // Define parameters for (not) including
    // a portion of a third matrix in product_filterTensor_and_col.
    float one = 1.0;
    float zero = 0.0;


    // ------------------------------------------------------------
    // For each image in subdivision,
    // sculpt image into matrix col.
    // Calculate the matrix product of filterTensor and col and
    // store the product as a subtensor of convolutionOutputTensor.
    // ------------------------------------------------------------
    // The following statement is required to
    // prevent automatic casting of a product to an eight-byte integer.
    int image_times_elementsInOutputSubtensor;

    int* image_GPU;
    cudaMalloc(&image_GPU, sizeof(int));
    for (int image_host = 0; image_host < imagesInSubdivision_child; ++image_host) {
        cudaMemcpy(image_GPU, &image_host, sizeof(int), cudaMemcpyHostToDevice);

        im2col<<<blocks_for_im2col, threads_per_block_for_im2col>>>
            (col,
                channelsInFilter,
                widthOfFilter,
                heightOfOutputTensor,
                widthOfOutputTensor,
                elementsInChannelOfOutputTensor_GPU_child,
                channelsInFilter_times_elementsInChannelOfOutputTensor,
                elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor,
                inputTensor_child,
                horizontalFilterStride,
                channelsInImage,
                verticalFilterStride,
                elementsInCrossSectionOfImage,
                image_GPU,
                elementsInImage);
        cudaDeviceSynchronize();

        // The following statement is required to
        // prevent automatic casting of a product to an eight-byte integer.
        image_times_elementsInOutputSubtensor = image_host * elementsInOutputSubtensor_child;

        cublasSgemm(
            cublasHandle,
            CUBLAS_OP_N,
            CUBLAS_OP_N,
            elementsInChannelOfOutputTensor_host_child,
            filters_child,
            elementsInFilter_child,
            &one,
            col,
            elementsInChannelOfOutputTensor_host_child,
            filterTensor,
            elementsInFilter_child,
            &zero,
            convolutionOutputTensor_child + image_times_elementsInOutputSubtensor,
            elementsInChannelOfOutputTensor_host_child);
    }

    cudaFree(col);
    cudaFree(image_GPU);
}


__global__
void im2col(
    float* col_child,
    int* channelsInFilter_child,
    int* widthOfFilter_child,
    int* heightOfOutputTensor_child,
    int* widthOfOutputTensor_child,
    int* elementsInChannelOfOutputTensor_child,
    int* channelsInFilter_times_elementsInChannelOfOutputTensor_child,
    int* elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_child,
    float* inputTensor_child_child,
    int* horizontalFilterStride_child,
    int* channelsInImage_child,
    int* verticalFilterStride_child,
    int* elementsInCrossSectionOfImage_child,
    int* image,
    int* elementsInImage_child)
{
    int index = blockIdx.x * blockDim.x + threadIdx.x;
    int c_prime = index % (*channelsInFilter_child);
    int temp = (index - c_prime) / (*channelsInFilter_child);
    int w_prime = temp % (*widthOfFilter_child);
    int h_prime = temp / (*widthOfFilter_child);

    for (int h = 0; h < (*heightOfOutputTensor_child); ++h) {
        for (int w = 0; w < (*widthOfOutputTensor_child); ++w) {

            col_child[
                w +
                h * (*widthOfOutputTensor_child) +
                c_prime * (*elementsInChannelOfOutputTensor_child) +
                w_prime * (*channelsInFilter_times_elementsInChannelOfOutputTensor_child) +
                h_prime * (*elementsInCrossSectionOfFilter_times_elementsInChannelOfOutputTensor_child)] =
            inputTensor_child_child[
                c_prime +
                (w * (*horizontalFilterStride_child) + w_prime) * (*channelsInImage_child) +
                (h * (*verticalFilterStride_child) + h_prime) * (*elementsInCrossSectionOfImage_child) +
                (*image) * (*elementsInImage_child)];

        }
    }
}

1 Ответ

0 голосов
/ 12 мая 2020

Прочитав статьи NVIDIA, которые предоставил мне Роберт Кровелла, я переписал свое решение «CalculateConvolutionOutputTensor__im2col», чтобы потоки в каждом блоке загружались из непрерывной глобальной памяти. Я использовал меньше арифметики индексации c и меньше параметров. Я видел ускорение метода (1 метод / 0,445 с) / (1 метод / 0,774 с) = 1,7 и ускорение ядра im2col на (1 ядро ​​/ 35,27 мс) / (1 ядро ​​/ 128,15 мс) = 3.6. Спасибо, что указали мне на полезную специфику c чтение.

im2col раньше занимал 128,15 мс; теперь это занимает всего 32,12 мс. Sgemm теперь занимает 6,34 мс; наверное взял примерно такой же тогда. Их сумма составляет 38,46 мс. Пара запускается четыре раза, всего 153,84 мс. Интересно, как увеличить скорость im2col и уменьшить «накладные расходы» на 274,16 мс. * 590 * 19 * 19) блоки передают полусечения фильтрообразной части изображения последовательно в col. Я считаю, что каждый поток загружается из глобальной памяти, физически смежной с памятью, к которой обращается предыдущий поток, и что каждый поток хранится в глобальной памяти, физически смежной с памятью, хранящейся в предыдущем потоке. Я заметил, что 11 потоков в последней деформации в каждом блоке остались неиспользованными.

Думаю, я мог бы воспользоваться этим предложением и переместить этот поток оптимизации в Code Review. im2col с объединенной глобальной памятью загружает и сохраняет

enter image description here

main.cu

// Allow use of cudaMalloc.
#include <cuda_runtime.h>

// Allow use of structs in namespace chrono.
#include <ctime>

// Allow construction of a default_random_engine.
#include <random>

// Allow use of CalculateConvolutionOutputTensor__im2col.
#include "CalculateConvolutionOutputTensor__im2col.h"


int main()
{
    // --------------------------------------------------------------------------
    // Declare and define parameters of CalculateConvolutionOutputTensor__im2col.
    // --------------------------------------------------------------------------
    float* convolutionOutputTensor;
    cudaMalloc(&convolutionOutputTensor, 6 * 3 * 19 * 19 * 4 * sizeof(float));

    int elementsInFilter = 3 * 590 * 590;

    int elementsInChannelOfOutputTensor = 19 * 19;

    int imagesInSubdivision = 4;

    int elementsInInputTensor = 3 * 608 * 608 * 4;
    float* inputTensor_host = new float[elementsInInputTensor];
    for (int i = 0; i < elementsInInputTensor; ++i) {
        inputTensor_host[i] = ((float)(i % 255)) / 255.0;
    }
    float* inputTensor_GPU;
    cudaMalloc(&inputTensor_GPU, elementsInInputTensor * sizeof(float));
    cudaMemcpy(
        inputTensor_GPU,
        inputTensor_host,
        elementsInInputTensor * sizeof(float),
        cudaMemcpyHostToDevice);
    delete[] inputTensor_host;

    int heightOfFilter_host = 590;
    int* heightOfFilter_GPU;
    cudaMalloc(&heightOfFilter_GPU, sizeof(int));
    cudaMemcpy(heightOfFilter_GPU, &heightOfFilter_host, sizeof(int), cudaMemcpyHostToDevice);

    int channelsInImage_host = 3;
    int* channelsInImage_GPU;
    cudaMalloc(&channelsInImage_GPU, sizeof(int));
    cudaMemcpy(channelsInImage_GPU, &channelsInImage_host, sizeof(int), cudaMemcpyHostToDevice);

    int widthOfImage_host = 608;
    int* widthOfImage_GPU;
    cudaMalloc(&widthOfImage_GPU, sizeof(int));
    cudaMemcpy(widthOfImage_GPU, &widthOfImage_host, sizeof(int), cudaMemcpyHostToDevice);

    int widthOfOutputTensor_host = 19;
    int* widthOfOutputTensor_GPU;
    cudaMalloc(&widthOfOutputTensor_GPU, sizeof(int));
    cudaMemcpy(widthOfOutputTensor_GPU, &widthOfOutputTensor_host, sizeof(int), cudaMemcpyHostToDevice);

    int heightOfImage_host = 608;
    int* heightOfImage_GPU;
    cudaMalloc(&heightOfImage_GPU, sizeof(int));
    cudaMemcpy(heightOfImage_GPU, &heightOfImage_host, sizeof(int), cudaMemcpyHostToDevice);

    int filters = 6 * 3;

    int elementsInFilterTensor = 6 * 3 * 3 * 590 * 590;
    float* filterTensor_host = new float[elementsInFilterTensor];
    std::default_random_engine randomNumberGenerator(time(NULL));
    std::normal_distribution<float> normalDistribution(0.0, 1.0);
    for (int i = 0; i < elementsInFilterTensor; ++i) {
        filterTensor_host[i] = normalDistribution(randomNumberGenerator) / sqrt((float)elementsInFilterTensor);
    }
    float* filterTensor_GPU;
    cudaMalloc(&filterTensor_GPU, elementsInFilterTensor * sizeof(float));
    cudaMemcpy(
        filterTensor_GPU,
        filterTensor_host,
        elementsInFilterTensor * sizeof(float),
        cudaMemcpyHostToDevice);
    delete[] filterTensor_host;

    int elementsInOutputSubtensor = 6 * 3 * 19 * 19;


    // -------------------------------------------------
    // Execute CalculateConvolutionOutputTensor__im2col.
    // -------------------------------------------------   
    CalculateConvolutionOutputTensor__im2col(
        convolutionOutputTensor,
        elementsInFilter,
        elementsInChannelOfOutputTensor,
        imagesInSubdivision,
        inputTensor_GPU,
        heightOfFilter_GPU,
        channelsInImage_GPU,
        widthOfImage_GPU,
        widthOfOutputTensor_GPU,
        heightOfImage_GPU,
        filters,
        filterTensor_GPU,
        elementsInOutputSubtensor);

    cudaFree(inputTensor_GPU);
    cudaFree(heightOfFilter_GPU);
    cudaFree(channelsInImage_GPU);
    cudaFree(widthOfImage_GPU);
    cudaFree(widthOfOutputTensor_GPU);
    cudaFree(heightOfImage_GPU);
    cudaFree(filterTensor_GPU);


    // --------------------------------------------------
    // Make sure that convolutionOutputTensor is correct.
    // --------------------------------------------------
    float* convolutionOutputTensor_test = new float[6 * 3 * 19 * 19 * 4];
    cudaMemcpy(
        convolutionOutputTensor_test,
        convolutionOutputTensor,
        6 * 3 * 19 * 19 * 4 * sizeof(float),
        cudaMemcpyDeviceToHost);
    printf("convolutionOutputTensor_test: {");
    for (int i = 0; i < 18; ++i) {
        printf("%f, ", convolutionOutputTensor_test[i]);
    }
    printf("...}\n");
    delete[] convolutionOutputTensor_test;

    cudaFree(convolutionOutputTensor);


    return 0;
}

CalculateConvolutionOutputTensor__im2col.h

void CalculateConvolutionOutputTensor__im2col(
    float* convolutionOutputTensor_child,
    int elementsInFilter_child,
    int elementsInChannelOfOutputTensor_host_child,
    int imagesInSubdivision_child,
    float* inputTensor_child,
    int* heightOfFilter,
    int* channelsInImage,
    int* widthOfImage,
    int* widthOfOutputTensor,
    int* heightOfImage,
    int filters_child,
    float* filterTensor,
    int elementsInOutputSubtensor_child);

CalculateConvolutionOutputTensor__im2col.cu

// Allow use of __global__.
#include <cuda_runtime.h>

// Allow declaration of cublasHandle.
#include "cublas_v2.h"

// Allow use of blockIdx.x, blockDim.x, and threadIdx.x.
#include <device_launch_parameters.h>


__global__
void im2col(
    float* col_child,
    float* inputTensor_child_child,
    int* heightOfFilter_child,
    int* channelsInImage_child,
    int* widthOfImage_child,
    int* widthOfOutputTensor_child,
    int* image,
    int* heightOfImage_child);


void CalculateConvolutionOutputTensor__im2col(
    float* convolutionOutputTensor_child,
    int elementsInFilter_child,
    int elementsInChannelOfOutputTensor_host_child,
    int imagesInSubdivision_child,
    float* inputTensor_child,
    int* heightOfFilter,
    int* channelsInImage,
    int* widthOfImage,
    int* widthOfOutputTensor,
    int* heightOfImage,
    int filters_child,
    float* filterTensor,
    int elementsInOutputSubtensor_child)
{
    // -----------------------------------------
    // Define and declare parameters for im2col.
    // -----------------------------------------
    // Define parameters for the execution configuration of im2col.
    int threads_per_block_for_im2col = 3 * 590 / 2;
    int blocks_for_im2col = 2 * 590 * 19 * 19;

    // Declare col.
    float* col;

    // The following statement is required to
    // prevent automatic casting of a product to an eight-byte integer.
    int elementsInFilter_times_elementsInChannelOfOutputTensor =
        elementsInFilter_child * elementsInChannelOfOutputTensor_host_child;

    cudaMalloc(&col, elementsInFilter_times_elementsInChannelOfOutputTensor * sizeof(float));


    // -----------------------------------------------------------------------------
    // Define parameters for calculating the matrix product of filterTensor and col.
    // -----------------------------------------------------------------------------
    // Define a cublasHandle_t object called cublasHandle.
    // Declaring cublasHandle requires '#include "cublas_v2.h"'.
    // Defining cublasHandle requires adding "cublas.lib" to
    // Properties -> Linker -> Input -> Additional Dependencies.
    cublasHandle_t cublasHandle;
    cublasCreate(&cublasHandle);

    // Define parameters for (not) including
    // a portion of a third matrix in product_filterTensor_and_col.
    float one = 1.0;
    float zero = 0.0;


    // ------------------------------------------------------------
    // For each image in subdivision,
    // sculpt image into matrix col.
    // Calculate the matrix product of filterTensor and col and
    // store the product as a subtensor of convolutionOutputTensor.
    // ------------------------------------------------------------
    // The following statement is required to
    // prevent automatic casting of a product to an eight-byte integer.
    int image_times_elementsInOutputSubtensor;

    int* image_GPU;
    cudaMalloc(&image_GPU, sizeof(int));
    for (int image_host = 0; image_host < imagesInSubdivision_child; ++image_host) {
        cudaMemcpy(image_GPU, &image_host, sizeof(int), cudaMemcpyHostToDevice);

        im2col
            <<<blocks_for_im2col,
               threads_per_block_for_im2col>>>
            (col,
             inputTensor_child,
             heightOfFilter,
             channelsInImage,
             widthOfImage,
             widthOfOutputTensor,
             image_GPU,
             heightOfImage);
        cudaDeviceSynchronize();

        // The following statement is required to
        // prevent automatic casting of a product to an eight-byte integer.
        image_times_elementsInOutputSubtensor = image_host * elementsInOutputSubtensor_child;

        cublasSgemm(
            cublasHandle,
            CUBLAS_OP_N,
            CUBLAS_OP_N,
            filters_child,
            elementsInChannelOfOutputTensor_host_child,
            elementsInFilter_child,
            &one,
            filterTensor,
            filters_child,
            col,
            elementsInFilter_child,
            &zero,
            convolutionOutputTensor_child + image_times_elementsInOutputSubtensor,
            filters_child);

        float element = 0.0;

    }

    cudaFree(col);
    cudaFree(image_GPU);
}


__global__
void im2col(
    float* col_child,
    float* inputTensor_child_child,
    int* heightOfFilter_child,
    int* channelsInImage_child,
    int* widthOfImage_child,
    int* widthOfOutputTensor_child,
    int* image,
    int* heightOfImage_child)
{
    col_child[blockIdx.x * blockDim.x + threadIdx.x] =
        inputTensor_child_child[
            threadIdx.x +
            (blockIdx.x % 2) * blockDim.x +
            ((blockIdx.x % (2 * (*heightOfFilter_child))) / 2) * (*channelsInImage_child) * (*widthOfImage_child) +
            (blockIdx.x / (2 * (*heightOfFilter_child))) * (*channelsInImage_child) +
            (blockIdx.x / (2 * (*heightOfFilter_child) * (*widthOfOutputTensor_child))) * (*channelsInImage_child) * (*widthOfImage_child) +
            (*image) * (*channelsInImage_child) * (*widthOfImage_child) * (*heightOfImage_child)];
}
...