Протестируйте A\b в сравнении с эталоном при помощи GPU Coder

В этом примере показано, как протестировать решения в сравнении с эталоном линейной системы путем генерации кода графического процессора. Используйте матричное левое деление, также известное как mldivide или оператор обратной косой черты (\), чтобы решить систему линейных уравнений A*x = b для x (то есть, вычислите x = A\b).

Необходимые условия

  • CUDA® включил NVIDIA®, графический процессор с вычисляет возможность 3.5 или выше.

  • NVIDIA инструментарий CUDA и драйвер.

  • Переменные окружения для компиляторов и библиотек. Для получения информации о поддерживаемых версиях компиляторов и библиотек, смотрите Стороннее Оборудование. Для подготовки переменных окружения смотрите Подготовку Необходимых как условие продуктов.

Проверьте среду графического процессора

Чтобы проверить, что компиляторы и библиотеки, необходимые для выполнения этого примера, настраиваются правильно, используйте coder.checkGpuInstall функция.

envCfg = coder.gpuEnvConfig('host');
envCfg.BasicCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);

Определите максимальный размер данных

Выберите соответствующий матричный размер для расчетов путем определения суммы системной памяти в Гбайт, доступном для центрального процессора и графического процессора. Значение по умолчанию базируется только на объеме памяти, доступном на графическом процессоре. Можно задать значение, которое подходит для системы.

g = gpuDevice;
maxMemory = 0.25*g.AvailableMemory/1024^3;

Функция сравнительного тестирования

Этот пример тестирует матричного левого деления в сравнении с эталоном (\) включая стоимость передачи данных между центральным процессором и графическим процессором, чтобы получить четкое представление общего времени приложения при использовании GPU Coder™. Время приложения, профилируя не должно включать время, чтобы создать демонстрационные входные данные. genData.m функция разделяет генерацию тестовых данных от функции точки входа, которая решает линейную систему.

type getData.m
function [A, b] = getData(n, clz)

%   Copyright 2017-2019 The MathWorks, Inc.

    fprintf('Creating a matrix of size %d-by-%d.\n', n, n);
    A = rand(n, n, clz) + 100*eye(n, n, clz);
    b = rand(n, 1, clz);
end

Функция точки входа обратной косой черты

backslash.m функция точки входа инкапсулирует (\) операция, для которой вы хотите сгенерировать код.

type backslash.m
function [x] = backslash(A,b)
%#codegen

%   Copyright 2017-2019 The MathWorks, Inc.

    coder.gpu.kernelfun();
    x = A\b;
end

Сгенерируйте код графического процессора

Создайте функцию, чтобы сгенерировать MEX-функцию графического процессора на основе конкретного размера входных данных.

type genGpuCode.m
function [] = genGpuCode(A, b)

%   Copyright 2017-2019 The MathWorks, Inc.

    cfg = coder.gpuConfig('mex');
    evalc('codegen -config cfg -args {A,b} backslash');
end

Выберите проблемный размер

Эффективность параллельных алгоритмов, которые решают линейную систему, зависит значительно от матричного размера. Этот пример сравнивает эффективность алгоритма для различных матричных размеров.

% Declare the matrix sizes to be a multiple of 1024.
sizeLimit = inf;
if ispc
    sizeLimit = double(intmax('int32'));
end
maxSizeSingle = min(floor(sqrt(maxMemory*1024^3/4)),floor(sqrt(sizeLimit/4)));
maxSizeDouble = min(floor(sqrt(maxMemory*1024^3/8)),floor(sqrt(sizeLimit/8)));
step = 1024;
if maxSizeDouble/step >= 10
    step = step*floor(maxSizeDouble/(5*step));
end
sizeSingle = 1024:step:maxSizeSingle;
sizeDouble = 1024:step:maxSizeDouble;
numReps = 5;

Сравните эффективность: ускорение

Используйте общее прошедшее время в качестве меры эффективности, потому что это позволяет вам сравнить эффективность алгоритма для различных матричных размеров. Учитывая матричный размер, функция сравнительного тестирования создает матричный A и правая сторона b однажды, и затем решает A\b несколько раз, чтобы получить точную меру времени это берет.

type benchFcnMat.m
function time = benchFcnMat(A, b, reps)

%   Copyright 2017-2019 The MathWorks, Inc.

    time = inf;
    % Solve the linear system a few times and take the best run
    for itr = 1:reps
        tic;
        matX = backslash(A, b);
        tcurr = toc;
        time = min(tcurr, time);
    end
end

Создайте различную функцию для выполнения кода графического процессора, которое вызывает сгенерированную MEX-функцию графического процессора.

type benchFcnGpu.m
function time = benchFcnGpu(A, b, reps)

%   Copyright 2017-2019 The MathWorks, Inc.

    time = inf;
    gpuX = backslash_mex(A, b);
    for itr = 1:reps
        tic;
        gpuX = backslash_mex(A, b);
        tcurr = toc;
        time = min(tcurr, time);
    end
end

Выполните сравнительные тесты

Когда вы выполняете сравнительные тесты, расчеты могут занять много времени, чтобы завершиться. Распечатайте некоторую промежуточную информацию о статусе, когда вы завершаете сравнительное тестирование для каждого матричного размера. Инкапсулируйте цикл свыше всех матричных размеров в функции, чтобы протестировать в сравнении с эталоном один - и расчеты с двойной точностью.

Фактические времена выполнения могут варьироваться через различные аппаратные конфигурации. Это сравнительное тестирование было сделано при помощи MATLAB R2020a на машине с 6 ядрами, центральном процессоре Intel® Xeon® на 3.5 ГГц и TITAN Xp NVIDIA графический процессор.

type executeBenchmarks.m
function [timeCPU, timeGPU] = executeBenchmarks(clz, sizes, reps)

%   Copyright 2017-2019 The MathWorks, Inc.

    fprintf(['Starting benchmarks with %d different %s-precision ' ...
         'matrices of sizes\nranging from %d-by-%d to %d-by-%d.\n'], ...
            length(sizes), clz, sizes(1), sizes(1), sizes(end), ...
            sizes(end));
    timeGPU = zeros(size(sizes));
    timeCPU = zeros(size(sizes));   
    for i = 1:length(sizes)
        n = sizes(i);
        fprintf('Size : %d\n', n);
        [A, b] = getData(n, clz);
        genGpuCode(A, b);
        timeCPU(i) = benchFcnMat(A, b, reps);
        fprintf('Time on CPU: %f sec\n', timeCPU(i));
        timeGPU(i) = benchFcnGpu(A, b, reps);
        fprintf('Time on GPU: %f sec\n', timeGPU(i));
        fprintf('\n');
    end
end

Выполните сравнительные тесты в одинарной и двойной точности.

[cpu, gpu] = executeBenchmarks('single', sizeSingle, numReps);
results.sizeSingle = sizeSingle;
results.timeSingleCPU = cpu;
results.timeSingleGPU = gpu;
[cpu, gpu] = executeBenchmarks('double', sizeDouble, numReps);
results.sizeDouble = sizeDouble;
results.timeDoubleCPU = cpu;
results.timeDoubleGPU = gpu;
Starting benchmarks with 9 different single-precision matrices of sizes
ranging from 1024-by-1024 to 25600-by-25600.
Size : 1024
Creating a matrix of size 1024-by-1024.
Time on CPU: 0.010894 sec
Time on GPU: 0.012735 sec

Size : 4096
Creating a matrix of size 4096-by-4096.
Time on CPU: 0.256912 sec
Time on GPU: 0.056594 sec

Size : 7168
Creating a matrix of size 7168-by-7168.
Time on CPU: 0.859825 sec
Time on GPU: 0.138257 sec

Size : 10240
Creating a matrix of size 10240-by-10240.
Time on CPU: 2.190538 sec
Time on GPU: 0.276720 sec

Size : 13312
Creating a matrix of size 13312-by-13312.
Time on CPU: 4.265689 sec
Time on GPU: 0.481420 sec

Size : 16384
Creating a matrix of size 16384-by-16384.
Time on CPU: 8.167554 sec
Time on GPU: 0.771961 sec

Size : 19456
Creating a matrix of size 19456-by-19456.
Time on CPU: 12.814798 sec
Time on GPU: 1.150009 sec

Size : 22528
Creating a matrix of size 22528-by-22528.
Time on CPU: 19.060859 sec
Time on GPU: 1.671082 sec

Size : 25600
Creating a matrix of size 25600-by-25600.
Time on CPU: 27.237500 sec
Time on GPU: 2.318981 sec

Starting benchmarks with 6 different double-precision matrices of sizes
ranging from 1024-by-1024 to 16384-by-16384.
Size : 1024
Creating a matrix of size 1024-by-1024.
Time on CPU: 0.025851 sec
Time on GPU: 0.021405 sec

Size : 4096
Creating a matrix of size 4096-by-4096.
Time on CPU: 0.435886 sec
Time on GPU: 0.175009 sec

Size : 7168
Creating a matrix of size 7168-by-7168.
Time on CPU: 1.734958 sec
Time on GPU: 0.765464 sec

Size : 10240
Creating a matrix of size 10240-by-10240.
Time on CPU: 4.240617 sec
Time on GPU: 2.081403 sec

Size : 13312
Creating a matrix of size 13312-by-13312.
Time on CPU: 8.611123 sec
Time on GPU: 4.415243 sec

Size : 16384
Creating a matrix of size 16384-by-16384.
Time on CPU: 19.387437 sec
Time on GPU: 8.050974 sec

Постройте эффективность

Постройте результаты и сравните эффективность на центральном процессоре и графическом процессоре для одинарной и двойной точности.

Во-первых, посмотрите на эффективность оператора обратной косой черты в одинарной точности.

fig = figure;
ax = axes('parent', fig);
plot(ax, results.sizeSingle, results.timeSingleGPU, '-x', ...
     results.sizeSingle, results.timeSingleCPU, '-o')
grid on;
legend('GPU', 'CPU', 'Location', 'NorthWest');
title(ax, 'Single-Precision Performance')
ylabel(ax, 'Time (s)');
xlabel(ax, 'Matrix Size');
drawnow;

Теперь посмотрите на эффективность оператора обратной косой черты в двойной точности.

fig = figure;
ax = axes('parent', fig);
plot(ax, results.sizeDouble, results.timeDoubleGPU, '-x', ...
     results.sizeDouble, results.timeDoubleCPU, '-o')
legend('GPU', 'CPU', 'Location', 'NorthWest');
grid on;
title(ax, 'Double-Precision Performance')
ylabel(ax, 'Time (s)');
xlabel(ax, 'Matrix Size');
drawnow;

Наконец, посмотрите на ускорение оператора обратной косой черты при сравнении графического процессора с центральным процессором.

speedupDouble = results.timeDoubleCPU./results.timeDoubleGPU;
speedupSingle = results.timeSingleCPU./results.timeSingleGPU;
fig = figure;
ax = axes('parent', fig);
plot(ax, results.sizeSingle, speedupSingle, '-v', ...
     results.sizeDouble, speedupDouble, '-*')
grid on;
legend('Single-precision', 'Double-precision', 'Location', 'SouthEast');
title(ax, 'Speedup of Computations on GPU Compared to CPU');
ylabel(ax, 'Speedup');
xlabel(ax, 'Matrix Size');
drawnow;