Я связался со службой технической поддержки Mathworks, и Рилан наконец-то пролил свет на эту проблему. (Спасибо, Рилан!) Его полный ответ ниже. Проблема функции и скрипта, по-видимому, связана с определенными оптимизациями, которые Matlab применяет автоматически к функциям (но не к скриптам), которые не работают должным образом
Ответ Райлана:
Спасибо за ваше терпение по этому вопросу. Я посоветовался с разработчиками вычислений на GPU MATLAB, чтобы лучше это понять.
Эта проблема вызвана внутренней оптимизацией, выполняемой MATLAB при обнаружении некоторых специфических операций, таких как умножение матрицы на матрицу и транспонирование. Некоторые из этих оптимизаций могут быть включены специально при выполнении функции MATLAB (или анонимной функции), а не сценария.
Когда ваш исходный код выполнялся из скрипта, определенная оптимизация транспонирования матрицы не выполняется, в результате чего выражение 'res2' быстрее, чем выражение 'res1':
n = 2000;
a=gpuArray(sprand(n,n,0.01));
b=gpuArray(rand(n));
tic;res1=a*b*a;wait(gpuDevice);toc % Elapsed time is 0.884099 seconds.
tic;res2=transpose(transpose(a)*transpose(a*b));wait(gpuDevice);toc % Elapsed time is 0.068855 seconds.
Однако, когда приведенный выше код помещен в файл функции MATLAB, выполняется дополнительная оптимизация времени транспонирования матрицы, которая заставляет выражение 'res2' проходить через другой путь кода (и другой вызов функции библиотеки CUDA) по сравнению с той же строкой, вызываемой из скрипта. Поэтому эта оптимизация генерирует более медленные результаты для строки 'res2' при вызове из файла функции.
Чтобы эта проблема не возникала в функциональном файле, операции транспонирования и умножения должны были бы быть разделены таким образом, чтобы MATLAB не применял эту оптимизацию. Кажется, для этого достаточно разделить каждое предложение внутри оператора 'res2':
tic;i1=transpose(a);i2=transpose(a*b);res3=transpose(i1*i2);wait(gpuDevice);toc % Elapsed time is 0.066446 seconds.
В приведенной выше строке 'res3' генерируется из двух промежуточных матриц: 'i1' и 'i2'. Производительность (в моей системе) кажется такой же, как у выражения «res2» при выполнении из скрипта; Кроме того, выражение «res3» также показывает аналогичную производительность при выполнении из файла функций MATLAB. Однако обратите внимание, что дополнительная память может использоваться для хранения транспонированной копии исходного массива. Пожалуйста, дайте мне знать, если вы видите различное поведение производительности в вашей системе, и я могу исследовать это дальше.
Кроме того, операция «res3» показывает более высокую производительность при измерении с помощью функции «gputimeit». Пожалуйста, обратитесь к приложенному файлу «testscript2.m» для получения дополнительной информации об этом. Я также прикрепил 'test_v2.m', который является модификацией функции 'test.m' в вашем посте переполнения стека.
Спасибо, что сообщили мне об этой проблеме. Я хотел бы извиниться за любые неудобства, вызванные этой проблемой. Я создал внутренний отчет об ошибках, чтобы уведомить разработчиков MATLAB об этом поведении. Они могут исправить это в будущем выпуске MATLAB.
Поскольку у вас возник дополнительный вопрос о сравнении производительности кода GPU с использованием «gputimeit» и «tic» и «toc», я просто хотел дать одно предложение, которое разработчики вычислительных машин на GPU MATLAB упоминали ранее. , Обычно хорошо также вызывать «wait (gpuDevice)» перед операторами «tic», чтобы убедиться, что операции GPU из предыдущих строк не перекрываются при измерении для следующей строки. Например, в следующих строках:
b=gpuArray(rand(n));
tic; res1=a*b*a; wait(gpuDevice); toc
если «wait (gpuDevice)» не вызывается перед «tic», некоторое время, затрачиваемое на создание массива «b» из предыдущей строки, может перекрываться и подсчитываться за время, необходимое для выполнения выражение 'res1'. Это было бы предпочтительным вместо:
b=gpuArray(rand(n));
wait(gpuDevice); tic; res1=a*b*a; wait(gpuDevice); toc
Кроме этого, я не вижу особых проблем в том, как вы используете функции 'tic' и 'toc'. Однако обратите внимание, что использование 'gputimeit' обычно рекомендуется по сравнению с использованием 'tic' и 'toc' непосредственно для профилирования, связанного с GPU.
Я пока продолжу и закрою это дело, но, пожалуйста, дайте мне знать, если у вас есть дополнительные вопросы по этому поводу.
%testscript2.m
n = 2000;
a = gpuArray(sprand(n, n, 0.01));
b = gpuArray(rand(n));
gputimeit(@()transpose_mult_fun(a, b))
gputimeit(@()transpose_mult_fun_2(a, b))
function out = transpose_mult_fun(in1, in2)
i1 = transpose(in1);
i2 = transpose(in1*in2);
out = transpose(i1*i2);
end
function out = transpose_mult_fun_2(in1, in2)
out = transpose(transpose(in1)*transpose(in1*in2));
end
.
function test_v2
clear
%% transposed expression
n = 2000;
rng('default');rng(1);
a = sprand(n, n, 0.1);
b = rand(n, n);
a = gpuArray(a);
b = gpuArray(b);
tic;
c1 = gather(transpose( transpose(a) * transpose(a * b) ));
disp(['time for (a''*(a*b)'')'': ' , num2str(toc),'s'])
clearvars -except c1
%% non-transposed expression
rng('default');
rng(1)
n = 2000;
a = sprand(n, n, 0.1);
b = rand(n, n);
a = gpuArray(a);
b = gpuArray(b);
tic;
c2 = gather(a * b * a);
disp(['time for a*b*a: ' , num2str(toc),'s'])
disp(['error = ',num2str(max(max(abs(c1-c2))))])
%% sliced equivalent
rng('default');
rng(1)
n = 2000;
a = sprand(n, n, 0.1);
b = rand(n, n);
a = gpuArray(a);
b = gpuArray(b);
tic;
intermediate1 = transpose(a);
intermediate2 = transpose(a * b);
c3 = gather(transpose( intermediate1 * intermediate2 ));
disp(['time for split equivalent: ' , num2str(toc),'s'])
disp(['error = ',num2str(max(max(abs(c1-c3))))])
end