수색…


비고

CUDA는 GPU 용 독점 NVIDIA 병렬 컴퓨팅 기술 및 프로그래밍 언어입니다.

GPU는 수천 개의 경량 스레드를 병렬로 실행할 수있는 고도의 병렬 시스템입니다. 각 GPU 스레드는 일반적으로 실행 속도가 느리고 컨텍스트가 더 작습니다. 다른 한편, GPU는 수천 개의 스레드를 병렬로 그리고 훨씬 더 동시에 실행할 수 있습니다 (정확한 수는 실제 GPU 모델에 따라 다릅니다). CUDA는 NVIDIA GPU 아키텍처를 위해 특별히 설계된 C ++ 언어입니다. 그러나 아키텍처의 차이로 인해 대부분의 알고리즘은 일반 C ++에서 단순히 복사하여 붙여 넣을 수 없습니다. 실행되지만 매우 느릴 수 있습니다.

술어

  • 호스트 - 정상적인 CPU 기반 하드웨어 및 해당 환경에서 실행되는 일반 프로그램을 나타냅니다.
  • 장치 - CUDA 프로그램이 실행되는 특정 GPU를 나타냅니다. 단일 호스트가 여러 장치를 지원할 수 있습니다.
  • 커널 - 호스트 코드에서 호출 할 수있는 장치에있는 함수.

물리적 프로세서 구조

CUDA 지원 GPU 프로세서의 물리적 구조는 다음과 같습니다.

  • - GPU의 전체 프로세서 일부 GPU에는 그 중 두 가지가 있습니다.
  • 스트리밍 멀티 프로세서 (SM) - 각 칩에는 모델에 따라 최대 100 개의 SM이 포함됩니다. 각 SM은 서로간에 거의 독립적으로 작동하며 글로벌 메모리 만 사용하여 서로 통신합니다.
  • CUDA 코어 - SM의 단일 스칼라 연산 단위입니다. 정확한 숫자는 아키텍처에 따라 다릅니다. 각 코어는 빠른 연속 (CPU의 하이퍼 스레딩과 유사)에서 동시에 실행되는 몇 가지 스레드를 처리 할 수 ​​있습니다.

또한 각 SM에는 하나 이상의 워프 스케줄러가 있습니다. 각 스케줄러는 여러 개의 CUDA 코어에 단일 명령어를 전달합니다. 이로 인해 SM은 32 와이드 SIMD 모드에서 효과적으로 작동합니다.

CUDA 실행 모델

GPU의 물리적 구조는 장치에서 커널을 실행하는 방법과 CUDA에서 커널을 프로그래밍하는 방법에 직접적인 영향을줍니다. 커널은 얼마나 많은 병렬 스레드가 생성되는지를 지정하는 호출 구성으로 호출됩니다.

  • 그리드 - 커널 호출시 생성되는 모든 스레드를 나타냅니다. 그것은 블록의 하나 개 또는 두 개의 dimentional 세트로 지정
  • 블록 -은 세미 독립형 스레드 세트입니다. 각 블록은 단일 SM에 할당됩니다. 따라서 블록은 전역 메모리를 통해서만 통신 할 수 있습니다. 블록은 어떤 방식으로도 동기화되지 않습니다. 블록이 너무 많으면 다른 블록 다음에 순차적으로 실행할 수 있습니다. 반면에 리소스가 허용되면 하나 이상의 블록이 동일한 SM에서 실행될 수 있지만 프로그래머는 성능 향상을 제외하고는 그로부터 이익을 얻을 수 없습니다.
  • thread - 단일 CUDA 코어에 의해 실행되는 스칼라 명령 시퀀스. 쓰레드는 컨텍스트가 최소화 된 '가벼운'것이므로 하드웨어가 빠르게 교체 할 수 있습니다. 숫자가 많기 때문에 CUDA 스레드는 할당 된 몇 개의 레지스터와 매우 짧은 스택으로 작동합니다 (전혀 사용하지 않는 것이 좋습니다!). 이러한 이유 때문에 CUDA 컴파일러는 모든 함수 호출을 인라인하여 커널을 평탄화하여 정적 점프 및 루프 만 포함하도록합니다. 함수 폰터 (ponter) 호출과 가상 메서드 호출은 대부분의 새로운 장치에서 지원되지만 일반적으로 성능에 심각한 영향을줍니다.

각 스레드는 블록 인덱스 blockIdx 와 블록 threadIdx 내의 스레드 인덱스로 식별됩니다. 이 숫자는 실행중인 스레드가 언제든지 확인할 수 있으며 스레드를 다른 스레드와 구별 할 수있는 유일한 방법입니다.

또한 스레드는 정확히 32 개의 스레드를 포함하는 워프 로 구성됩니다. 단일 워프 내의 스레드는 SIMD fahsion에서 완벽한 동기화를 실행합니다. 다른 warps의 스레드는 동일한 블록 내에서 임의의 순서로 실행할 수 있지만 프로그래머가 강제로 동기화 할 수 있습니다. 다른 블록의 스레드는 어떤 식 으로든 직접 동기화되거나 상호 작용할 수 없습니다.

메모리 조직

일반적인 CPU 프로그래밍에서 메모리 조직은 대개 프로그래머에게 숨겨져 있습니다. 일반적인 프로그램은 마치 RAM이있는 것처럼 작동합니다. 레지스터 관리, L1- L2- L3- 캐싱 사용, 디스크 스와핑 등과 같은 모든 메모리 연산은 컴파일러, 운영 체제 또는 하드웨어 자체에서 처리합니다.

이것은 CUDA의 경우가 아닙니다. 새로운 GPU 모델은 부분적으로 CUDA 6의 Unified Memory 를 통해 부담을 숨기고 있지만 성능상의 이유로 조직을 이해할 가치가 있습니다. 기본적인 CUDA 메모리 구조는 다음과 같습니다.

  • 호스트 메모리 - 일반 RAM. 주로 호스트 코드에서 사용되지만 최신 GPU 모델에서도이 코드에 액세스 할 수 있습니다. 커널이 호스트 메모리에 액세스 할 때 GPU는 대개 PCIe 커넥터를 통해 마더 보드와 통신해야하므로 상대적으로 속도가 느립니다.
  • 장치 메모리 / 전역 메모리 - 모든 스레드에서 사용할 수있는 GPU의 메인 오프 칩 메모리입니다.
  • 공유 메모리 - 각 SM에있어 전역보다 훨씬 빠른 액세스가 가능합니다. 공유 메모리는 각 블록에 전용입니다. 단일 블록 내의 스레드는 통신용으로 사용할 수 있습니다.
  • 레지스터 - 각 스레드의 가장 빠른, 개인, 주소없는 메모리. 일반적으로 이들은 통신에 사용할 수 없지만 몇 가지 고유 기능을 사용하여 워프 내에서 내용을 섞을 수 있습니다.
  • 로컬 메모리 - 주소 스레드의 개인 메모리. 이것은 레지스터 유출 및 변수 인덱스가있는 로컬 배열에 사용됩니다. 물리적으로 이들은 전역 메모리에 상주합니다.
  • 텍스처 메모리 (Texture memory), 상수 메모리 (Constant memory) - 커널에 대해 불변으로 표시되는 전역 메모리의 일부. 이렇게하면 GPU가 특수 목적 캐시를 사용할 수 있습니다.
  • L2 캐시 - 온 - 칩, 모든 스레드에서 사용 가능. 스레드의 양이 주어지면 각 캐시 라인의 예상 수명은 CPU보다 훨씬 낮습니다. 주로 보조 정렬 오류 및 부분적으로 임의 메모리 액세스 패턴을 사용합니다.
  • L1 캐시 - 공유 메모리와 동일한 공간에 위치합니다. 다시 말하지만, 사용량이 많을수록 양은 다소 적기 때문에 데이터가 오래 머무를 것으로 기대하지 마십시오. L1 캐싱을 비활성화 할 수 있습니다.

버전

컴퓨팅 기능 건축물 GPU 코드 이름 출시일
1.0 테슬라 G80 2006-11-08
1.1 테슬라 G84, G86, G92, G94, G96, G98, 2007-04-17
1.2 테슬라 GT218, GT216, GT215 2009-04-01
1.3 테슬라 GT200, GT200b 2009-04-09
2.0 페르미 GF100, GF110 2010-03-26
2.1 페르미 GF104, GF106 GF108, GF114, GF116, GF117, GF119 2010-07-12
3.0 케플러 GK104, GK106, GK107 2012-03-22
3.2 케플러 GK20A 2014-04-01
3.5 케플러 GK110, GK208 2013-02-19
3.7 케플러 GK210 2014-11-17
5.0 맥스웰 GM107, GM108 2014-02-18
5.2 맥스웰 GM200, GM204, GM206 2014-09-18
5.3 맥스웰 GM20B 2015-04-01
6.0 파스칼 GP100 2016-10-01
6.1 파스칼 GP102, GP104, GP106 2016-05-27

릴리스 날짜는 주어진 컴퓨팅 기능을 지원하는 첫 번째 GPU의 출시를 나타냅니다. 대략적인 날짜입니다 (예 : 3.2 카드가 2014 년 2 분기에 출시 됨).

선결 요건

CUDA로 프로그래밍을 시작하려면 CUDA 툴킷과 개발자 드라이버를 다운로드하여 설치하십시오. 이 툴킷에는 nvcc , NVIDIA CUDA Compiler 및 CUDA 응용 프로그램 개발에 필요한 기타 소프트웨어가 포함되어 있습니다. 드라이버는 GPU 프로그램이 CUDA 가능 하드웨어 에서 올바르게 실행되도록 보장합니다.

명령 줄에서 nvcc --version 을 실행하여 CUDA 툴킷이 컴퓨터에 올바르게 설치되었는지 확인할 수 있습니다. 예를 들어, 리눅스 머신에서,

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Tue_Jul_12_18:28:38_CDT_2016
Cuda compilation tools, release 8.0, V8.0.32

컴파일러 정보를 출력합니다. 이전 명령이 성공적이지 않은 경우 CUDA 툴킷이 설치되지 않았거나 nvcc (Windows 시스템의 경우 C:\CUDA\bin , POSIX OS의 경우 /usr/local/cuda/bin ) 경로는 사용자의 일부가 아닙니다 PATH 환경 변수.

또한 nvcc 를 사용하여 CUDA 프로그램을 컴파일하고 빌드하는 호스트 컴파일러가 필요합니다. Windows의 경우 Microsoft Visual Studio와 함께 제공되는 Microsoft 컴파일러 인 cl.exe 입니다. POSIX OS에서는 gccg++ 포함한 다른 컴파일러를 사용할 수 있습니다. 공식 CUDA Quick Start Guide 는 특정 플랫폼에서 지원되는 컴파일러 버전을 알려줍니다.

모든 것이 올바르게 설정되었는지 확인하려면, 모든 도구가 올바르게 작동하는지 확인하기 위해 간단한 CUDA 프로그램을 컴파일하고 실행합시다.

__global__ void foo() {}

int main()
{
  foo<<<1,1>>>();

  cudaDeviceSynchronize();
  printf("CUDA error: %s\n", cudaGetErrorString(cudaGetLastError()));

  return 0;
}

이 프로그램을 컴파일하려면 test.cu 파일에 복사하고 명령 행에서 컴파일하십시오. 예를 들어, Linux 시스템에서는 다음이 작동합니다.

$ nvcc test.cu -o test
$ ./test
CUDA error: no error

프로그램이 오류없이 성공하면 코딩을 시작하십시오!

CUDA로 두 배열 합계

이 예제는 두 개의 int 배열을 CUDA로 더하는 간단한 프로그램을 만드는 법을 보여줍니다.

CUDA 프로그램은 이기종이며 CPU와 GPU 모두에서 실행됩니다.

CUDA를 사용하는 프로그램의 주요 부분은 CPU 프로그램과 유사하며

  • GPU에서 사용될 데이터에 대한 메모리 할당
  • 호스트 메모리에서 GPU 메모리로 데이터 복사
  • 커널 함수를 호출하여 데이터 처리하기
  • 결과를 CPU 메모리로 복사

장치 메모리를 할당하기 위해 우리는 cudaMalloc 함수를 사용합니다. 장치와 호스트간에 데이터를 복사하려면 cudaMemcpy 기능을 사용할 수 있습니다. cudaMemcpy 의 마지막 인수는 복사 작업의 방향을 지정합니다. 가능한 유형은 5 가지입니다.

  • cudaMemcpyHostToHost - 호스트 -> 호스트
  • cudaMemcpyHostToDevice - 호스트 -> 장치
  • cudaMemcpyDeviceToHost - 기기 -> 호스트
  • cudaMemcpyDeviceToDevice - 장치 -> 장치
  • cudaMemcpyDefault - 기본 기반 통합 가상 주소 공간

다음으로 커널 함수가 호출됩니다. 트리플 셰브론 사이의 정보는 실행 구성이며 커널을 병렬로 실행하는 장치 스레드의 수를 나타냅니다. 첫 번째 숫자 (예 : 2 )는 블록 수와 두 번째 숫자 (예 : (size + 1) / 2 ) - 블록의 스레드 수를 지정합니다. 이 예제에서는 크기에 1을 더하여 하나의 스레드가 두 개의 요소를 담당하는 대신 하나의 추가 스레드를 요청합니다.

커널 호출은 비동기 함수이기 때문에 실행이 완료 될 때까지 대기하도록 cudaDeviceSynchronize 가 호출됩니다. 결과 배열은 호스트 메모리에 복사되고 장치에 할당 된 모든 메모리는 cudaFree 로 해제됩니다.

함수를 커널로 정의하려면 __global__ 선언 지정자가 사용됩니다. 이 함수는 각 스레드에 의해 호출됩니다. 각 스레드가 결과 배열의 요소를 처리하도록하려면 각 스레드를 구별하고 식별하는 방법이 필요합니다. CUDA는 blockDim , blockIdxthreadIdx 변수를 정의합니다. 미리 정의 된 변수 인 blockDim 에는 커널 실행을위한 두 번째 실행 구성 매개 변수에 지정된대로 각 스레드 블록의 크기가 포함됩니다. 미리 정의 된 변수 인 threadIdxblockIdx 에는 각각 해당 스레드 블록 내의 스레드 및 격자 내의 스레드 블록의 인덱스가 포함됩니다. 잠재적으로 배열의 요소보다 하나 이상의 스레드를 요청할 것이므로 어레이의 끝을 지나서 액세스하지 못하도록 size 를 전달해야합니다.

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>

__global__ void addKernel(int* c, const int* a, const int* b, int size) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < size) {
        c[i] = a[i] + b[i];
    }
}

// Helper function for using CUDA to add vectors in parallel.
void addWithCuda(int* c, const int* a, const int* b, int size) {
    int* dev_a = nullptr;
    int* dev_b = nullptr;
    int* dev_c = nullptr;

    // Allocate GPU buffers for three vectors (two input, one output)
    cudaMalloc((void**)&dev_c, size * sizeof(int));
    cudaMalloc((void**)&dev_a, size * sizeof(int));
    cudaMalloc((void**)&dev_b, size * sizeof(int));

    // Copy input vectors from host memory to GPU buffers.
    cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);

    // Launch a kernel on the GPU with one thread for each element.
    // 2 is number of computational blocks and (size + 1) / 2 is a number of threads in a block
    addKernel<<<2, (size + 1) / 2>>>(dev_c, dev_a, dev_b, size);
    
    // cudaDeviceSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaDeviceSynchronize();

    // Copy output vector from GPU buffer to host memory.
    cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);

    cudaFree(dev_c);
    cudaFree(dev_a);
    cudaFree(dev_b);
}

int main(int argc, char** argv) {
    const int arraySize = 5;
    const int a[arraySize] = {  1,  2,  3,  4,  5 };
    const int b[arraySize] = { 10, 20, 30, 40, 50 };
    int c[arraySize] = { 0 };

    addWithCuda(c, a, b, arraySize);

    printf("{1, 2, 3, 4, 5} + {10, 20, 30, 40, 50} = {%d, %d, %d, %d, %d}\n", c[0], c[1], c[2], c[3], c[4]);

    cudaDeviceReset();

    return 0;
}

CUDA 스레드를 하나 시작하여 인사 해보자.

이 간단한 CUDA 프로그램은 GPU (일명 "장치")에서 실행할 함수를 작성하는 방법을 보여줍니다. CPU 또는 "호스트"는 "커널"이라는 특수 함수를 호출하여 CUDA 스레드를 생성합니다. CUDA 프로그램은 추가 구문이있는 C ++ 프로그램입니다.

어떻게 작동하는지 보려면 hello.cu 파일에 다음 코드를 입력하십시오.

#include <stdio.h>

// __global__ functions, or "kernels", execute on the device
__global__ void hello_kernel(void)
{
  printf("Hello, world from the device!\n");
}

int main(void)
{
  // greet from the host
  printf("Hello, world from the host!\n");

  // launch a kernel with a single thread to greet from the device
  hello_kernel<<<1,1>>>();

  // wait for the device to finish so that we see the message
  cudaDeviceSynchronize();

  return 0;
}

(장치에서 printf 함수를 사용하려면 최소한 2.0의 계산 기능을 갖춘 장치가 필요합니다. 자세한 내용은 버전 개요 를 참조하십시오.)

이제 NVIDIA 컴파일러를 사용하여 프로그램을 컴파일하고 실행 해 봅시다.

$ nvcc hello.cu -o hello
$ ./hello
Hello, world from the host!
Hello, world from the device!

위의 예에 대한 몇 가지 추가 정보 :

  • nvcc 는 "NVIDIA CUDA Compiler"의 약자입니다. 소스 코드를 호스트 및 장치 구성 요소로 분리합니다.
  • __global__ 은 함수 선언에 사용 된 CUDA 키워드로, 함수가 GPU 장치에서 실행되고 호스트에서 호출됨을 나타냅니다.
  • 삼각 괄호 ( <<< , >>> )는 호스트 코드에서 장치 코드 ( "커널 시작"이라고도 함)로 호출을 표시합니다. 이 괄호 안의 숫자는 병렬로 실행될 횟수와 스레드 수를 나타냅니다.

샘플 프로그램 컴파일 및 실행

NVIDIA 설치 가이드는 CUDA 툴킷의 설치를 확인하기 위해 샘플 프로그램을 실행하는 것으로 끝나지 만, 어떻게 명시 적으로 언급하지는 않습니다. 먼저 모든 전제 조건을 확인하십시오. 샘플 프로그램의 기본 CUDA 디렉토리를 확인하십시오. 존재하지 않는다면 공식 CUDA 웹 사이트에서 다운로드 할 수 있습니다. 예제가있는 디렉토리로 이동하십시오.

$ cd /path/to/samples/
$ ls

다음과 비슷한 결과가 나타납니다.

0_Simple     2_Graphics  4_Finance      6_Advanced       bin     EULA.txt
1_Utilities  3_Imaging   5_Simulations  7_CUDALibraries  common  Makefile

Makefile 이이 디렉토리에 있는지 확인하십시오. UNIX 기반 시스템의 make 명령은 모든 샘플 프로그램을 빌드합니다. 또는 다른 Makefile 이있는 하위 디렉토리로 이동하여 거기에서 make 명령을 실행하여 해당 샘플 만 빌드하십시오.

두 개의 제안 된 샘플 프로그램 ( deviceQuerybandwidthTest 실행하십시오.

$ cd 1_Utilities/deviceQuery/
$ ./deviceQuery 

결과물은 아래와 같습니다.

./deviceQuery Starting...

 CUDA Device Query (Runtime API) version (CUDART static linking)

Detected 1 CUDA Capable device(s)

Device 0: "GeForce GTX 950M"
  CUDA Driver Version / Runtime Version          7.5 / 7.5
  CUDA Capability Major/Minor version number:    5.0
  Total amount of global memory:                 4096 MBytes (4294836224 bytes)
  ( 5) Multiprocessors, (128) CUDA Cores/MP:     640 CUDA Cores
  GPU Max Clock rate:                            1124 MHz (1.12 GHz)
  Memory Clock rate:                             900 Mhz
  Memory Bus Width:                              128-bit
  L2 Cache Size:                                 2097152 bytes
  Maximum Texture Dimension Size (x,y,z)         1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096)
  Maximum Layered 1D Texture Size, (num) layers  1D=(16384), 2048 layers
  Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384), 2048 layers
  Total amount of constant memory:               65536 bytes
  Total amount of shared memory per block:       49152 bytes
  Total number of registers available per block: 65536
  Warp size:                                     32
  Maximum number of threads per multiprocessor:  2048
  Maximum number of threads per block:           1024
  Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
  Max dimension size of a grid size    (x,y,z): (2147483647, 65535, 65535)
  Maximum memory pitch:                          2147483647 bytes
  Texture alignment:                             512 bytes
  Concurrent copy and kernel execution:          Yes with 1 copy engine(s)
  Run time limit on kernels:                     Yes
  Integrated GPU sharing Host Memory:            No
  Support host page-locked memory mapping:       Yes
  Alignment requirement for Surfaces:            Yes
  Device has ECC support:                        Disabled
  Device supports Unified Addressing (UVA):      Yes
  Device PCI Domain ID / Bus ID / location ID:   0 / 1 / 0
  Compute Mode:
     < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 7.5, CUDA Runtime Version = 7.5, NumDevs = 1, Device0 = GeForce GTX 950M
Result = PASS

최종 Result = PASS 는 모든 것이 올바르게 작동 함을 나타냅니다. 이제 비슷한 방법으로 다른 권장 샘플 프로그램 인 bandwidthTest 를 실행하십시오. 출력은 다음과 유사합니다.

[CUDA Bandwidth Test] - Starting...
Running on...

 Device 0: GeForce GTX 950M
 Quick Mode

 Host to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432            10604.5

 Device to Host Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432            10202.0

 Device to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432            23389.7

Result = PASS

NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled.

다시, Result = PASS 문은 모든 것이 제대로 실행되었음을 나타냅니다. 다른 모든 샘플 프로그램은 유사한 방식으로 실행될 수 있습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow