matlab - 편집 - 매트랩 특수문자




Matlab 스크립트에서 gpuArray를 사용할 때 a*b*a가(a*b)보다 오래 걸리는 이유는 무엇입니까? (2)

GPU에서 두 개의 희소 행렬을 곱하는 문제가있는 것 같습니다. 스파 스 전체 매트릭스에 대한 시간은 스파 스에 의한 스파 스보다 1000 배 이상 빠릅니다. 간단한 예 :

str={'sparse*sparse','sparse*full'};
for ii=1:2
    rng(1);
    a=sprand(3000,3000,0.1);
    b=sprand(3000,3000,0.1);
    if ii==2
        b=full(b);
    end
    a=gpuArray(a);
    b=gpuArray(b);
    tic
    c=a*b;
    disp(['time for ',str{ii},': ' , num2str(toc),'s'])
end

당신의 맥락에서, 그것을하는 마지막 곱셈입니다. 내가 a를 duplicate c로 대체 하고 , 한 번은 희소로, 한 번은 full 행렬로 두 번 곱한다.

str={'a*b*a','a*b*full(a)'};
for ii=1:2
    %rng('default');
    rng(1)
    a=sprand(3000,3000,0.1);
    b=rand(3000,3000);
    rng(1)
    c=sprand(3000,3000,0.1);
    if ii==2
        c=full(c);
    end
    a=gpuArray(a);
    b=gpuArray(b);
    c=gpuArray(c);
    tic;
    c1{ii}=a*b*c;
    disp(['time for ',str{ii},': ' , num2str(toc),'s'])
end
disp(['error = ',num2str(max(max(abs(c1{1}-c1{2}))))])

나는 틀릴 수도 있지만, 결론적으로 a * b * a 는 두 개의 희소 행렬 ( aa )의 곱셈을 포함하며 잘 처리되지 않는다. 반면 transpose () 접근법을 사용하면 프로세스가 두 단계 곱셈으로 나뉘어진다. 여기에는 두 개의 희소 행렬이 있습니다.

아래 코드는 두 가지 방법으로 gpuArrays ab 에서 동일한 작업을 수행합니다. 첫 번째 부분은 (a'*(a*b)')' 계산 a*b*a 반면 두 번째 부분은 a*b*a 계산 a*b*a . 그 결과는 동일하게 검증됩니다.

%function test
clear
rng('default');rng(1);
a=sprand(3000,3000,0.1);
b=rand(3000,3000);
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

rng('default');
rng(1)
a=sprand(3000,3000,0.1);
b=rand(3000,3000);
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))))])

%end

그러나 컴퓨팅 (a'*(a*b)')'a*b*a 계산 a*b*a 것보다 약 4 배 빠릅니다. 다음은 N20에있는 R2018a의 NBIDIA K20에서 출력 한 스크립트입니다 (비슷한 동작을하는 여러 GPU와 다른 버전을 시도했습니다).

>> test
time for (a'*(a*b)')': 0.43234s
time for a*b*a: 1.7175s
error = 2.0009e-11

이상하게도, 위의 스크립트의 첫 번째 줄과 마지막 줄의 주석을 제거하면 (함수로 바꾸기 위해) 두 함수 모두 더 긴 시간 (~ 0.4 초 대신 ~ 1.7 초)을 사용합니다. 다음은이 경우의 출력입니다.

>> test
time for (a'*(a*b)')': 1.717s
time for a*b*a: 1.7153s
error = 1.0914e-11

나는이 행동을 일으키는 것이 무엇인지, 그리고 더 짧은 시간 (예 : ~ 0.4s)보다는 a*b*a 또는 (a'*(a*b)')' 1.7s) 스크립트 내부보다는 matlab 함수 내부.


Mathworks 기술 지원 부서에 연락을하고 Rylan이 마침내이 문제에 관해 밝혀 냈습니다. (Rylan에게 감사드립니다!) 그의 전체 답변은 아래에 있습니다. 함수 대 스크립트 문제는 특정 최적화와 관련이있는 것처럼 보입니다. matlab은 예상대로 작동하지 않는 함수 (스크립트가 아닌)에 자동으로 적용됩니다.

Rylan의 응답 :

이 문제에 대해 양해 해 주셔서 감사합니다. 필자는이 점을 더 잘 이해하기 위해 MATLAB GPU 컴퓨팅 개발자와상의했습니다.

이 문제는 행렬 - 행렬 곱셈 및 전치와 같은 특정 연산이 발생할 때 MATLAB에서 수행 한 내부 최적화로 인해 발생합니다. 이러한 최적화 중 일부는 스크립트가 아닌 MATLAB 함수 (또는 익명 함수)를 실행할 때 특별히 활성화 될 수 있습니다.

초기 코드가 스크립트에서 실행될 때 특정 행렬 변환 최적화가 수행되지 않아 'res1'표현식보다 'res2'표현식이 빠릅니다.

  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'파일을 참고하십시오. 나는 또한 stack_overflow 포스트에 'test.m'함수의 수정 인 'test_v2.m'을 첨부했다.

이 문제를 저에게 신고 해 주셔서 감사합니다. 이 문제로 인해 불편을 끼쳐 드려 사과드립니다. 이 문제에 대해 MATLAB 개발자에게 알리기 위해 내부 버그 보고서를 만들었습니다. MATLAB의 향후 릴리스에서 이러한 문제를 해결할 수 있습니다.

'gputimeit'과 'tic'및 'toc'를 사용하여 GPU 코드의 성능을 비교하는 것에 대한 추가 질문이 있었기 때문에 MATLAB GPU 컴퓨팅 개발자가 앞에서 언급 한 제안 사항 하나를 제공하고자했습니다. 일반적으로 'tic'문 앞에 'wait (gpuDevice)'를 호출하여 이전 행의 GPU 연산이 다음 행의 측정에서 겹치지 않도록하는 것이 좋습니다. 예를 들어, 다음 행에서 :

  b=gpuArray(rand(n));
  tic; res1=a*b*a; wait(gpuDevice); toc  

'tic'전에 'wait (gpuDevice)'가 호출되지 않으면 이전 행에서 'b'배열을 만드는 데 걸린 시간 중 일부가 겹쳐져 'res1'표현을 실행하는 데 걸리는 시간이 계산됩니다. 대신 다음을 선호합니다.

  b=gpuArray(rand(n));
  wait(gpuDevice); tic; res1=a*b*a; wait(gpuDevice); toc  

이 외에도 'tic'및 'toc'기능을 사용하는 방식에서 특정 문제가 표시되지 않습니다. 그러나 GPU 관련 프로파일 링에 'tic'및 'toc'를 직접 사용하는 것보다 'gputimeit'을 사용하는 것이 일반적으로 권장됩니다.

지금 당장이 사례를 종료하겠습니다. 그러나 이에 대해 더 궁금한 점이 있으면 알려 주시기 바랍니다.

%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




linear-algebra