ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [개념 정리] 딥러닝 클러스터 - 1. GPU Cluster, Parallelism
    Big Data/Distributed Deep Learning 2022. 5. 5. 13:45
    728x90
    개인적인 공부를 위해 초고성능 딥러닝 클러스터 구축하기를 정리한 것임을 미리 밝힙니다.
    기업에서 진행한 딥러닝 클러스터 구축에 대한 경험을 자세히 공유해주신 글로
    자세한 내용이 궁금하신 분들께는 위의 포스트 직접 읽어보시는 것을 추천드립니다.

     

    GPU Cluster

    딥러닝 학습 인프라의 de facto standard(사실상의 표준)으로, 다수의 GPU가 장착된 딥러닝 서버들을 대역폭이 일반 1G 회선의 10배~200배인 고속 네트워크로 엮은 분산 처리(distributed processing) 시스템

     

    멀티 노드 분산 학습

    • 딥러닝 학습에 필요한 계산을 수십~수백 개의 GPU에 나누어 동시에 처리, 고속 네트워크 통해 결과를 합산하는 기법
    • 모델 학습의 병렬성(parallelism)을 최대한 활용, 시간과 공간의 제약 극복

     

    병렬성

    1. Data parallelism

    • 여러 프로세서가 같은 task를 다른 data에 대해 수행
    • 요구되는 계산량(data)이 증가함에 따라 병렬성도 증가
    • 확장성(scalability) 좋음
    • 계산 순서에서 상대적으로 자유, 동기화에 따른 성능 저하도 적음

    예시)

    for i in range(3):
        a[i] += b[i]

    - 각 스텝( i )에 따라 배열의 다른 부분(data)에 대해 수행, 각 스텝이 서로 의존성이 없기 때문에 순서 상관없음, 동시 수행 가능

    thread[0]: a[0] += b[0]
    thread[1]: a[1] += b[1]
    thread[2]: a[2] += b[2]

    - 위 루프에서 다루는 데이터의 크기가 3에서 3000으로 1000배 증가하며, 병렬성도 1000배 증가
    - 하드웨어가 받쳐준다면 thread를 3개에서 3000개로 1000배 증가 가능

    • 계산 자원이 많이 필요한 딥러닝 도메인에서는 data parallelism을 활용할 수 있는 여지가 많음
    • 이러한 data parallelism을 잘 활용하는 프로세서가 GPU

     

    2. GPU

    동시에 똑같은 연산을 수행하는 코어가 수천 개씩 들어있는 구조로, 각 코어에 데이터의 다른 부분을 할당하여 병렬로 처리 가능

    • 각 GPU가 다른 샘플에 대해 같은 파라미터를 가지고 같은 방식으로 학습 → data parallelism
    • 코드를 크게 변경하지 않고, 배치의 크기와 GPU를 선형적으로 증가 가능
    ImageNet 데이터셋에서 총 8192개의 샘플 뽑아 256개의 Tesla P100 GPU에 각 32개씩 나누어주어 ResNet-50 1시간 학습(분산 학습하여 AllReduce)한 결과와 1개 GPU에서 32개 샘플 씩 256번 반복 학습(더 많은 시간 필요)한 결과와 비슷
    - Tesla P100 GPU와 50G Ethernet 네트워크의 결과 : 1시간
    - V100, 100G Ethernet 네트워크 사용 시 수 분만에 학습 가능

     

    3. AllReduce

    • 딥러닝 학습은 매 스텝마다 gradient를 계산해서 모델 prameter를 반복적으로 업데이트하는 과정
    • Data parallel하게 진행되는 분산 학습에서는 여러 GPU에서 동일한 모델 파라미터와 다른 입력을 가지고 서로 다른 gradient 계산, 이를 다 더한 값을 가지고 각자의 모델 parameter를 똑같이 업데이트 → AllReduce 통신 패턴
    def AllReduce(id, data_send: List[T]]) -> List[T]:
        data_recv = []
        
        # 1. 각 GPU에서 계산된 그라디언트(data_send)를 모두 한 GPU에 모음
        Gather(from=id, to=0, src=data_send, dst=data_recv)	#blocking
        
        # 2. 한 GPU에 모인 그라디언트(data_recv)를 합산
        data_send = sum(data_recv) if id == 0
        
        # 3. 합산된 그라디언트(data_send)를 모든 GPU로 보내줌
        Broadcast(from=0, to=id, src=data_send, dst=data_recv)	#blocking
        
        return data_recv

    - 모든 GPU와 한 GPU 사이의 통신 필요
    - 데이터가 몰리는 GPU 쪽 트래픽에서 병목 발생
    - 메모리 사용량도 너무 많아 OOM(OutOfMemory) 에러 발생 가능
    - 더 효율적으로 통신하는 알고리즘 이용 Ring-AllReduce

    def RingAllReduce(id, data: List[T]) -> List[T]:
        # 1. (GPU 개수 - 1)만큼 반복
        for n in range(N-1):
        	
            # 1.1. 합산할 데이터 일부를 다음 GPU로 보냄
            Send(to=(id+1), src=data([id-n])	#nonblocking
            
            # 1.2. 이전 GPU에서 보내준 데이터 받음
            Receive(from=(id-1), dst=data[id-n])	#blocking
            
            # 1.3. 받은 데이터를 보낼 데이터에 합산
            data[id-1-n] += data[id-n]
           
        # 2. (GPU 개수 - 1)만큼 반복
        for n in range(N-1):
        	
            # 2.1. 합산된 데이터 일부를 다음 GPU로 보냄
            Send(to=(id+1), src=data[id+1-n])	#nonblocking
            
            # 2.2. 이전 GPU에서 보내준 데이터 받음
            Receive(from=(id-1), dst=data[id-n])	#blocking
            
    	return data

    - 그림을 통한 더 자세한 설명은 GPU 분산 학습 기법:ALL-Reduce 참고
    - Ring-AllReduce 알고리즘 : 모든 GPU가 고르게 데이터 주고받음, 트래픽 병목 X, 하드웨어의 양방향 통신 기능을 이용해 주고받는 과정을 동시에 처리하도록 최적화 가능

     

    4. Task parallelism

    여러 프로세서가 다른 task를 같은 data 또는 다른 data에 대해 수행

    • 계산의 양(data)이 아니라 프로그램 특성(task)에 병렬성이 결정
    • 아래 루프에서 다루는 데이터의 크기를 3에서 3000으로 늘리더라도 동시에 연산할 수 있는 thread 수는 2개가 한계
      - 각 스텝(f(),g())이 서로 의존성이 있기 때문
    • 의존성에 의해 동기화 오버헤드( | )가 발생
    • 확장성 또한 프로그램 특성에 의해 제한

    예시)

    for i in range(3):
        b[i] = f(a[i])	# thread-unsafe function f()
        c[i] = g(b[i])	# thread-unsafe function g()

    - 루프에서 같은 배열(data)을 이용하여 다른 계산(task)을 차례로 수행, Pipelining 패턴으로 병렬화할 수 있는 예시

    thread[0]: b[0] = f(a[0]) | b[1] = f(a[1]) | b[2] = f(b[2]) |
    thread[1]:                | c[0] = g(b[0]) | c[1] = g(b[1]) | c[2] = g(b[2])

     

    5. Model parallelism

    모든 병렬 처리 패턴은 data parallelism과 task parallelism의 조합 / GPU 레이어를 쪼개고 이에 대응하는 모델 파라미터를 여러 GPU로 나누는 패턴인 model parallelism도 그중 하나

    • 각 디바이스에 ML 모델 학습에 필요한 연산(task)을 쪼개어 할당
    • 그에 따라 파라미터도 나누어 할당
    • 같은 입력(data)에 대해 연산의 다른 부분(task)을 처리하고
    • 각 결과를 합산(reduction/concat)하여 출력을 만드는 병렬화 방식

    모델의 크기가 커지면 그에 따라 병렬성이 커지기 때문에(더 많은 디바이스에 나누어줄 수 있음) data parallelism의 측면이 있고, 처리하는 데이터가 많아지더라도 병렬성이 변함없기 때문에(모든 디바이스에 같은 입력 전달) task parallelism의 측면도 있음

    • 파라미터가 엄청나게 많은 모델은 model parallelism이용해야 파라미터를 GPU 메모리에 들어갈 정도로 나눌 수 있음
    • Transformer 발표 이후, 신경망 이용한 language model은 크기를 계속 키우는 방향으로 발전
      • GPT-3 : 파라미터 수 1746억개 / 미리 학습된 GPT-3 모델로 추론만 하려고 해도 350GB 이상 메모리 필요, 학습을 위해 몇 배 더 많은 메모리 필요 / GPU 메모리 용량은 많아야 10~40G(2020.12 기준), model parallelism 필요, 대용량 데이터 학습 위해 data parallelism도 함께 활용 (GPT-3 학습 위해 MS Azure에서 OpenAI 계산용 GPU 10000대를 서버 당 400Gbps 고속 네트워크로 연결한 슈퍼 컴퓨터 제공..)

     

    728x90

    댓글

Designed by Tistory.