GPU 컴퓨팅에는 어떤 문제가 있습니까?


84

그래서 나는 함께 일하는 문제가 직렬에서 가장 좋은 문제이며 병렬로 관리 할 수있는 좋은 머리를 가지고 있습니다. 그러나 지금 당장은 CPU 기반 계산으로 가장 잘 처리되는 것과 GPU로 오프로드해야 할 것에 대해 잘 모릅니다.

나는 기본적인 질문을 알고 있지만 검색의 많은 부분은 또는 다소 모호한 규칙을 정당화하지 않고 서로를 분명히 옹호하는 사람들에게 사로 잡 힙니다 . 더 유용한 답변을 찾으십시오.

답변:


63

GPU 하드웨어에는 두 가지 장점이 있습니다 : 원시 컴퓨팅 (FLOP) 및 메모리 대역폭. 가장 어려운 계산 문제는이 두 가지 범주 중 하나에 속합니다. 예를 들어, 밀도가 큰 선형 대수 (A * B = C 또는 Solve [Ax = y] 또는 Diagonalize [A] 등)는 시스템 크기에 따라 계산 / 메모리 대역폭 스펙트럼에 해당합니다. FFT (Fast Fourier Transforms)는 또한 높은 총 대역폭 요구 사항으로이 금형에 적합합니다. 다른 변환, 그리드 / 메시 기반 알고리즘, Monte Carlo 등도 마찬가지입니다. NVIDIA SDK 코드 예제 를 살펴보면 가장 일반적으로 해결되는 문제에 대해 느낄 수 있습니다.

더 유용한 대답은`GPU가 실제로 어떤 종류의 문제입니까? '라는 질문에 대한 것이라고 생각합니다. 이 범주에 속하지 않는 대부분의 문제는 GPU에서 실행될 수 있지만 일부는 다른 것보다 더 많은 노력이 필요합니다.

제대로 매핑되지 않은 문제는 일반적으로 너무 작거나 예측할 수 없습니다. 아주 작은 문제는 GPU의 모든 스레드를 사용하는 데 필요한 병렬 처리가 부족하거나 CPU의 저수준 캐시에 적합하여 CPU 성능을 크게 향상시킵니다. 예측할 수없는 문제에는 의미있는 분기가 너무 많아 SIMD 패러다임 을 깨뜨려 GPU 메모리에서 코어로 데이터를 효율적으로 스트리밍하거나 병렬 처리를 줄일 수 있습니다 ( ' 분산 왜곡 ' 참조 ). 이러한 종류의 문제의 예는 다음과 같습니다.

  • 대부분의 그래프 알고리즘 (특히 메모리 공간에서 너무 예측할 수 없음)
  • 희소 선형 대수 (그러나 이것은 CPU에서도 좋지 않습니다)
  • 작은 신호 처리 문제 (예 : 1000 포인트보다 작은 FFT)
  • 검색
  • 종류

3
그럼에도 불구하고 이러한 "예측 불가능한"문제에 대한 GPU 솔루션 가능하지만 현재 불가능하지만 미래에는 중요 할 수 있습니다.
leftaroundabout

6
GPU 성능 차단기 목록에 분기를 구체적으로 추가하고 싶습니다. SIMD에서와 같이 모든 명령 (수백 개)이 동일한 명령을 실행하여 진정한 병렬 계산을 수행하려고합니다. 예를 들어, 명령 흐름 중 하나라도 분기와 만나 분기해야하는 경우 AMD 카드에서 모든 파면 (병렬 그룹)이 분기됩니다. 웨이브 프론트의 다른 유닛이 분기되지 않아야하는 경우 두 번째 패스를 수행해야합니다. 그것이 maxhutch가 예측 가능성이라는 의미입니다.
Violet Giraffe

2
@VioletGiraffe는 반드시 그런 것은 아닙니다. CUDA (즉, Nvidia GPU)에서 분기 분기는 현재 워프 (최대 32 개의 스레드)에만 영향을줍니다. 동일한 코드를 실행하더라도 다른 워프는 명시 적으로 동기화되지 않는 한 동기화되지 않습니다 (예 :와 함께 __synchtreads()).
Pedro

1
@Pedro : 사실이지만 일반적으로 분기하면 성능이 저하됩니다. 고성능 코드 (GPU 코드가 아닌)의 경우이를 고려하는 것이 거의 필수적입니다.
jvriesem

21

산술 강도가 높고 정기적 인 메모리 액세스 패턴이있는 문제는 일반적으로 GPU에서 구현하기 쉽고 더 효율적입니다.

고성능 GPU 코드를 사용하는 데있어 기본적인 어려움은 많은 코어가 있으며 가능한 한 최대한의 성능을 발휘하기를 원한다는 것입니다. 메모리 액세스 패턴이 불규칙하거나 산술 강도가 높지 않은 문제로 인해 결과를 전달하는 데 시간이 오래 걸리거나 메모리에서 항목을 가져 오는 데 시간이 오래 걸리고 시간이 오래 걸리고 숫자가 충분하지 않습니다. 물론 코드에서 동시성의 가능성은 GPU에서도 잘 구현 될 수있는 능력에 중요합니다.


정기적 인 메모리 액세스 패턴의 의미를 지정할 수 있습니까?
Fomite

1
maxhutch의 대답은 내 것보다 낫습니다. 규칙적인 액세스 패턴의 의미는 메모리가 시간적으로 공간적으로 로컬로 액세스된다는 것입니다. 즉, 반복적으로 메모리를 크게 뛰어 넘지 않습니다. 그것은 또한 내가 발견 한 패키지 거래의 일부입니다. 또한 분기 (코드의 조건문)가 최소화되도록 컴파일러 또는 프로그래머가 데이터 액세스 패턴을 미리 결정할 수 있음을 의미합니다.
Reid. Atcheson

15

이것은 독자적인 답변이 아니라 maxhutchReid.Atcheson 의 다른 답변에 추가 된 것 입니다.

GPU를 최대한 활용하려면 문제가 병렬화 될뿐만 아니라 GPU에서 실행될 핵심 알고리즘도 가능한 작아야합니다. 에서 의 OpenCL 기간이 대부분으로 불린다 커널 .

보다 정확하게, 커널은 GPU 의 각 멀티 프로세싱 유닛 (또는 컴퓨팅 유닛 )의 레지스터에 맞아야 합니다. 레지스터의 정확한 크기는 GPU에 따라 다릅니다.

커널이 충분히 작 으면 문제의 원시 데이터는 GPU의 로컬 메모리 (읽기 : 로컬 메모리 (OpenCL) 또는 공유 메모리 (CUDA))에 맞아야합니다 . 그렇지 않으면 GPU의 높은 메모리 대역폭조차도 처리 요소를 항상 바쁘게 유지할만큼 빠르지 않습니다 .
일반적으로이 메모리는 약 16 ~ 32 KiByte입니다 큰가 .


각 처리 장치의 로컬 / 공유 메모리가 단일 코어 클러스터 내에서 실행되는 수십 개의 스레드 사이에서 공유되지 않습니까? 이 경우 GPU에서 전체 성능을 얻기 위해 실제로 작업 데이터 세트를 크게 작게 유지할 필요가 없습니까?
Dan Neely

프로세싱 유닛의 로컬 / 공유 메모리는 컴퓨팅 유닛 자체에 의해서만 액세스 될 수 있고 따라서이 컴퓨팅 유닛의 프로세싱 요소에 의해서만 공유된다. 모든 처리 장치에서 그래픽 카드의 전역 메모리 (일반적으로 1GB)에 액세스 할 수 있습니다. 프로세싱 요소들과 로컬 / 공유 메모리 사이의 대역폭은 매우 빠르지 만 (> 1TB / s) 글로벌 메모리에 대한 대역폭은 훨씬 느리며 (~ 100GB / s) 모든 컴퓨팅 유닛들 사이에서 공유되어야합니다.
Torbjörn

메인 GPU 메모리에 대해서는 묻지 않았습니다. 온 다이 메모리는 개별 코어가 아닌 코어 레벨의 클러스터에서만 할당되었다고 생각했습니다. nVidia GF100 / 110 gpu의 경우 ex; 512 cuda 코어가 아닌 16 개의 SM 클러스터 각각에 대해 최대 32 개의 스레드를 병렬로 실행하도록 설계된 각 SM에서 GPU 성능을 최대화하려면 작업 세트를 1kb / 스레드 범위로 유지해야합니다.
Dan Neely

@Torbjoern 당신이 원하는 것은 모든 GPU 실행 파이프 라인을 바쁘게 유지하는 것입니다 .GPU는 다음 두 가지 방법을 달성합니다. 더 많은 활성 스레드를 가질 수있는 공유 자원); 아마도 더 나은 방법은 (2) 커널 내에서 명령 레벨 병렬 처리를 늘리는 것이므로 상대적으로 점유율이 낮은 큰 커널 (작은 수의 활성 스레드)을 가질 수 있습니다.
fcruz

11

아마도 이전 답변에 더 기술적으로 추가 된 CUDA (즉, Nvidia) GPU는 각각 32 개의 스레드에서 자율적으로 작동하는 일련의 프로세서로 설명 될 수 있습니다. 각 프로세서의 스레드는 잠금 단계로 작동합니다 (길이 32의 벡터를 가진 SIMD를 생각하십시오).

GPU로 작업하는 가장 유혹적인 방법은 모든 것이 절대적으로 잠금 단계로 실행되는 것처럼 가장하는 것이지만, 이것이 항상 가장 효율적인 방법은 아닙니다.

코드가 수백 / 수천 개의 스레드로 훌륭하게 / 자동으로 병렬화 되지 않으면 병렬화 잘되는 개별 비동기 작업으로 분류하고 잠금 단계에서 실행되는 32 개의 스레드 만있는 작업을 실행할 수 있습니다. CUDA는 뮤텍스 를 구현할 수있는 일련의 원자 적 명령어를 제공 하여 프로세서가 스레드 풀 패러다임 에서 작업 목록을 처리 할 수 ​​있도록합니다 . 그런 다음 코드는 멀티 코어 시스템에서와 같은 방식으로 작동하지만 각 코어에는 32 개의 스레드가 있습니다.

CUDA를 사용하는 방법에 대한 작은 예가 있습니다.

/* Global index of the next available task, assume this has been set to
   zero before spawning the kernel. */
__device__ int next_task;

/* We will use this value as our mutex variable. Assume it has been set to
   zero before spawning the kernel. */
__device__ int tasks_mutex;

/* Mutex routines using atomic compare-and-set. */
__device__ inline void cuda_mutex_lock ( int *m ) {
    while ( atomicCAS( m , 0 , 1 ) != 0 );
    }
__device__ inline void cuda_mutex_unlock ( int *m ) {
    atomicExch( m , 0 );
    }

__device__ void task_do ( struct task *t ) {

    /* Do whatever needs to be done for the task t using the 32 threads of
       a single warp. */
    }

__global__ void main ( struct task *tasks , int nr_tasks ) {

    __shared__ task_id;

    /* Main task loop... */
    while ( next_task < nr_tasks ) {

        /* The first thread in this block is responsible for picking-up a task. */
        if ( threadIdx.x == 0 ) {

            /* Get a hold of the task mutex. */
            cuda_mutex_lock( &tasks_mutex );

            /* Store the next task in the shared task_id variable so that all
               threads in this warp can see it. */
            task_id = next_task;

            /* Increase the task counter. */
            next_tast += 1;

            /* Make sure those last two writes to local and global memory can
               be seen by everybody. */
            __threadfence();

            /* Unlock the task mutex. */
            cuda_mutex_unlock( &tasks_mutex );

            }

        /* As of here, all threads in this warp are back in sync, so if we
           got a valid task, perform it. */
        if ( task_id < nr_tasks )
            task_do( &tasks[ task_id ] );

        } /* main loop. */

    }

그런 다음 커널을 호출하여 main<<<N,32>>>(tasks,nr_tasks)각 블록에 32 개의 스레드 만 포함되므로 단일 워프에 적합해야합니다. 이 예제에서는 또한 단순화를 위해 작업에 종속성 (예 : 하나의 작업이 다른 결과에 의존 함) 또는 충돌 (예 : 동일한 전역 메모리에서 작업)이 없다고 가정했습니다. 이 경우 작업 선택이 조금 더 복잡해 지지만 구조는 본질적으로 동일합니다.

물론 이것은 하나의 큰 셀 배치에서 모든 것을 수행하는 것보다 더 복잡하지만 GPU를 사용할 수있는 문제의 유형을 크게 넓 힙니다.


2
이것은 기술적으로 사실이지만 높은 메모리 대역폭을 얻으려면 높은 병렬 처리가 필요하며 비동기 커널 호출 수에는 제한이 있습니다 (현재 16). 또한 현재 릴리스의 스케줄링과 관련된 수많은 문서화되지 않은 동작입니다. 나는 당분간 성능을 향상시키기 위해 비동기 커널에 의존하지 말 것을 권고한다.
Max Hutchinson

2
내가 설명하는 것은 하나의 단일 커널 호출로 모두 수행 할 수 있습니다. 각각 32 개의 스레드로 N 개의 블록을 만들 수 있으므로 각 블록이 단일 날실에 적합합니다. 그런 다음 각 블록은 전역 작업 목록 (원자 / 뮤텍스를 사용하여 액세스 제어)에서 작업을 획득하고 32 개의 잠금 단계 스레드를 사용하여 계산합니다. 이 모든 것은 단일 커널 호출에서 발생합니다. 코드 예제를 원하면 알려 주시면 게시하겠습니다.
Pedro

4

아직까지 한 가지 요점은 현재 세대의 GPU가 단 정밀도 계산에서와 같이 배정도 부동 소수점 계산에서 잘 수행하지 못한다는 것입니다. 계산이 배정 밀도로 수행되어야하는 경우, 단일 정밀도보다 10 배 정도 런타임이 증가 할 것으로 예상 할 수 있습니다.


동의하지 않습니다. 대부분의 (또는 모든) 최신 GPU에는 기본 배정 밀도 지원이 있습니다. 거의 모든 GPU는 필요한 메모리 액세스 / 대역폭의 단순한 배가로 인해 단정도 속도의 약 절반 속도로 실행되는 배정도 계산을보고합니다.
Godric Seer 2016 년

1
최신 최고의 Nvidia Tesla 카드는 최고 단 정밀도 성능의 절반 인 최고 배정도 성능을 제공하는 것이 사실이지만, 일반적인 Fermi 아키텍처 소비자 급 카드의 비율은 8 대 1입니다.
Brian Borchers

@GodricSeer SP와 DP 부동 소수점의 2 : 1 비율은 대역폭과 거의 관련이 없으며 이러한 작업을 수행하기 위해 존재하는 하드웨어 장치 수와 거의 관련이 있습니다. SP 및 DP에 레지스터 파일을 재사용하는 것이 일반적이므로 부동 소수점 장치는 SP op를 DP op로 2 배 실행할 수 있습니다. 이 설계에는 IBM Blue Gene / Q와 같은 수많은 예외가 있습니다 (SP 로직이 없으므로 SP는 ~ 1.05x DP에서 실행 됨). 일부 GPU의 비율은 2 이외의 비율 (예 : 3과 5)입니다.
Jeff

이 답변을 작성한 지 4 년이 지났으며 NVIDIA GPU의 현재 상황은 GeForce 및 Quadro 라인의 DP / SP 비율이 이제 1/32입니다. NVIDIA의 Tesla GPU는 훨씬 강력한 배정도 성능을 갖지만 비용도 많이 듭니다. 반면 AMD는 같은 방식으로 Radeon GPU에서 배정도 성능을 약화시키지 않았습니다.
브라이언 Borchers

4

은유 적 관점에서 볼 때, GPU는 손톱에 누워있는 사람으로 볼 수 있습니다. 맨 위에 누워있는 사람은 데이터이며 각 손톱의 바닥에는 프로세서가 있으므로 손톱은 실제로 프로세서에서 메모리를 가리키는 화살표입니다. 모든 손톱은 격자처럼 규칙적인 패턴으로되어 있습니다. 몸이 잘 퍼지면 기분이 좋으며 (성능이 좋음) 몸이 손톱 침대의 일부에만 닿으면 통증이 심합니다 (성능이 좋지 않음).

이것은 위의 우수한 답변에 대한 보완적인 답변으로 취할 수 있습니다.


4

오래된 질문이지만 통계적 방법과 관련이 있지만 루프가 무엇인지 아는 사람에게는 일반화 할 수있는 2014 년 의이 답변 은 특히 ​​설명적이고 유익 하다고 생각합니다 .


2

GPU는 대기 시간이 긴 I / O를 가지므로 메모리를 포화시키기 위해 많은 스레드를 사용해야합니다. 날실을 바쁘게 유지하려면 많은 스레드가 필요합니다. 코드 경로가 10 클럭이고 I / O 레이턴시 320 클럭이면 32 개의 스레드가 워프를 포화 상태로 만들어야합니다. 코드 경로가 5 클럭이면 스레드를 두 배로 늘립니다.

코어가 천 개이면 GPU를 완전히 활용할 수있는 수천 개의 스레드를 찾으십시오.

메모리 액세스는 캐시 라인 (보통 32 바이트)입니다. 1 바이트를로드하는 데 드는 비용은 32 바이트입니다. 따라서 스토리지를 통합하여 사용의 지역성을 높입니다.

각 워프에는 많은 레지스터와 로컬 RAM이있어 이웃 공유가 가능합니다.

큰 세트의 근접 시뮬레이션은 잘 최적화되어야합니다.

임의 I / O 및 단일 스레딩은 죽이는 기쁨입니다 ...


이것은 진정으로 매혹적인 질문입니다. 나는 각 작업이 ~ 0.06 초가 걸리지 만 수행 할 ~ 180 만 개의 작업이있을 때 합리적으로 간단한 작업 (공중 이미지의 가장자리 감지)을 '병렬화'할 수 있는지 (또는 노력할 가치가 있는지) 나 자신과 논쟁하고 있습니다 ( 1 년에 6 년 분량의 데이터 : 작업은 확실히 분리 가능합니다.) 따라서 하나의 코어에서 ~ 7.5 일 분량의 컴퓨팅 시간. 각 계산이 GPU에서 더 빠르고 작업이 nPUPU 당 1 개 (n 작음)로 병렬화 될 수 있다면 실제로 작업 시간이 ~ 1 시간으로 떨어질 가능성이 있습니까? 있을 것 같지 않습니다.
GT.

0

Traveling Salesman과 같이 많은 무차별 대입으로 해결할 수있는 문제를 상상해보십시오. 그런 다음 각각 8 개의 스팬 키 비디오 카드가있는 서버 랙이 있고 각 카드에 3000 개의 CUDA 코어가 있다고 가정하십시오.

가능한 모든 영업 사원의 경로를 해결 한 다음 시간 / 거리 / 일부 메트릭별로 정렬하면됩니다. 물론 작업의 거의 100 %를 버리고 있지만 때로는 무차별 대입이 실행 가능한 솔루션입니다.


일주일 동안 4 대의 서버로 구성된 소규모 팜에 액세스 할 수 있었고 5 일 동안 이전 10 년보다 더 많은 distribution.net 블록을 사용했습니다.
Criggie

-1

많은 엔지니어링 아이디어를 연구하면서 gpu는 작업, 메모리 관리, 반복 가능한 계산에 중점을 둔 형태라고 말합니다.

많은 수학 공식은 작성하기는 쉽지만 행렬 수학과 같이 계산하기가 어려울 수 있습니다. 단일 답변은 없지만 많은 값을 얻습니다.

이것은 계산 된 값이 없으면 일부 수식을 실행할 수 없기 때문에 컴퓨터가 값을 계산하고 수식을 실행하는 속도로 계산하는 데 중요합니다 (따라서 속도가 느려짐). 컴퓨터는 이러한 프로그램에서 사용할 수식을 계산하거나 값을 계산할 순서를 잘 모릅니다. 그것은 주로 빠른 속도로 힘을 뚫고 공식을 척으로 분해하여 계산하지만 요즘 많은 프로그램에서 계산 된 척이 필요하고 대기열 (ques of ques and ques of ques)을 기다리고 있습니다.

예를 들어 충돌에서 가장 먼저 계산되어야하는 시뮬레이션 게임에서 충돌의 손상, 물체의 위치, 새로운 속도? 시간이 얼마나 걸리나요? CPU가 어떻게이로드를 처리 할 수 ​​있습니까? 또한 대부분의 프로그램은 데이터를 처리하는 데 더 많은 시간이 걸리는 매우 추상적이며 항상 멀티 스레딩을 위해 설계된 것은 아니며 추상적 프로그램에서 효과적으로 수행하기위한 좋은 방법이 아닙니다.

CPU가 나아지면서 더 나은 사람들이 프로그래밍을 느슨하게 만들었으므로 많은 다른 유형의 컴퓨터에도 프로그래밍해야합니다. GPU는 동시에 많은 간단한 계산을 통해 무력을 발휘하도록 설계되었습니다 (메모리 (2 차 / 램)는 물론 가열 냉각은 컴퓨팅의 주요 병목 현상입니다). CPU는 많은 수의 대기열을 동시에 관리하거나 여러 방향으로 끌어 당겨서 수행 할 수없는 작업을 파악하고 있습니다. (이것은 거의 인간이다)

GPU는 지루한 작업을 거친 작업자입니다. CPU는 완전한 혼란을 관리하고 있으며 모든 세부 사항을 처리 할 수는 없습니다.

그래서 우리는 무엇을 배우나요? GPU는 한 번에 지루한 작업을 자세히 설명하며 CPU는 너무 많은 작업에 집중할 수없는 멀티 태스킹 머신입니다. (주의 장애와 자폐증이 동시에있는 것처럼).

엔지니어링에는 아이디어, 디자인, 현실 및 많은 거친 작업이 있습니다.

내가 떠날 때, 단순하게 시작하고, 신속하고, 신속하게, 신속하고, 빠르게 시작하며, 시도를 멈추지 않는 것을 잊지 마십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.