공통 · 2026-09-14
여러 GPU로 확장하기
여러 GPU의 연산 장치와 메모리를 살펴보고, 용량·응답 시간·처리량의 목표를 구분하며, 통신과 동기화에 따른 대기가 전체 실행 시간에 미치는 영향을 설명합니다.
지난 글에서는 한 연산을 개선한 뒤 모델 전체를 다시 측정하며, 다음 병목을 찾아가는 과정을 살펴봤습니다. 같은 GPU를 더 효율적으로 사용하는 것과 함께, 필요한 메모리 용량이나 성능 목표에 따라 자원을 늘리는 선택도 생각해볼 수 있었습니다.
GPU를 추가하면 연산 장치와 메모리가 늘어납니다. 하지만 늘어난 자원을 활용하려면 데이터와 계산을 어디에 맡길지 정해야 합니다. 지금까지는 한 GPU 안의 메모리 이동과 연산을 중심으로 실행 시간을 살펴봤습니다. 여러 GPU에 계산을 나누면, 필요한 데이터를 주고받고 다른 GPU의 결과가 준비되기를 기다리는 시간도 고려해야 합니다. GPU 내부의 메모리 이동과 연산에 더해, GPU 사이의 통신과 대기도 전체 성능에 영향을 줍니다.
이번 글에서는 먼저 여러 GPU의 연산 장치와 메모리가 어떻게 나뉘어 있는지 살펴봅니다. 이어서 더 큰 모델을 실행하는 것, 한 요청을 빨리 끝내는 것, 더 많은 요청을 처리하는 것을 구분합니다. 마지막으로 나눈 계산을 연결하는 통신과 대기를 통해, GPU 수만큼 속도가 늘어나지는 않는 이유를 알아보겠습니다.
각 GPU의 연산 장치와 메모리
지금까지는 한 GPU 안에서 데이터를 메모리에서 가져와 계산하고, 결과를 저장하는 과정을 봤습니다. GPU를 두 개 사용하면 이러한 연산 장치와 메모리가 각각 있습니다. GPU 0에 저장한 데이터와 GPU 1에 저장한 데이터를 구별하고, 각 GPU가 수행할 계산에 필요한 입력이 어디에 있는지 확인해야 합니다.
그림 1에서 파란 화살표는 익숙한 GPU 내부의 메모리 이동입니다. 보라 화살표는 다른 GPU와 데이터를 주고받는 경로입니다. GPU 1의 다음 계산에 GPU 0이 만든 결과가 필요하다면, 그 결과를 GPU 1이 사용할 수 있도록 전달해야 합니다. 통신도 데이터 이동이며, 여기서는 그 범위가 GPU 사이로 넓어집니다.
두 GPU의 메모리 용량이 늘었다고 해서, 기존 실행이 자동으로 하나의 큰 메모리를 사용하게 되는 것은 아닙니다. 어떤 데이터를 어느 GPU에 저장하고, 다른 GPU가 필요로 하는 데이터를 언제 전달할지 실행 소프트웨어가 관리해야 합니다. 그림은 이 관계를 보여주기 위해 GPU 내부의 캐시·공유 메모리·레지스터를 생략하고, 메모리와 연산 장치를 크게 묶었습니다.
GPU를 연결하는 경로도 실행 조건의 일부입니다. 같은 서버 안에서 연결된 GPU와 서로 다른 서버에 있는 GPU는 통신 경로가 다를 수 있습니다. 예를 들어 NVIDIA의 GPU 통신 라이브러리인 NCCL은 PCIe·NVLink와 서버 사이의 네트워크 등 여러 연결 방식을 지원합니다. 구체적인 통신 방식에 앞서, 계산할 데이터의 위치와 그 데이터가 이동할 경로를 함께 보는 것이 출발점입니다.
여러 GPU를 사용하는 세 가지 목적
GPU를 늘리는 목적에 따라 배치할 데이터와 나눌 계산이 달라집니다. 그림 2는 같은 두 GPU를 사용하더라도 무엇을 얻으려 하는지 구분합니다.
더 큰 모델 실행
한 GPU에 모델 전체를 저장하기 어렵다면, 모델을 여러 GPU에 나누어 배치할 수 있습니다. 그림 2의 첫 번째 패널에서는 모델의 일부 A를 GPU 0에, 일부 B를 GPU 1에 저장합니다. 각 GPU에는 맡은 계산에 필요한 중간값과 작업 공간도 있어야 하므로, 가중치 크기뿐 아니라 실행 중의 메모리 사용량을 함께 확인해야 합니다.
이때 얻으려는 것은 한 GPU에 담기 어려웠던 모델을 실행할 수 있는 용량입니다. 저장 공간을 나누었다는 사실만으로 계산이 동시에 진행되거나 한 요청이 빨라지는 것은 아닙니다. A의 결과가 있어야 B를 계산할 수 있다면, 그 의존성에 맞춰 전달하고 실행해야 합니다.
한 요청의 시간 단축
한 요청이 끝나는 시간을 줄이려면, 그 요청에 필요한 계산 중 함께 수행할 수 있는 부분을 나눠 맡깁니다. 두 번째 패널에서는 GPU 0과 GPU 1이 계산 A와 B를 나누어 수행하고, 결과를 연결해 요청을 마칩니다.
각 GPU가 맡은 계산이 줄어도 입력을 나누거나 결과를 모으는 시간이 추가될 수 있습니다. 따라서 계산에서 절약한 시간이 추가된 통신과 대기보다 커야 요청을 더 빨리 끝낼 수 있습니다. 무엇을 나눌 수 있고 어떤 결과를 모아야 하는지는 원래 연산의 의존성에 따라 달라집니다.
더 많은 요청 처리
모델이 GPU 한 장에 들어간다면, 세 번째 패널처럼 각 GPU에 같은 모델을 올리고 서로 다른 요청을 맡길 수 있습니다. GPU 0은 요청 A를, GPU 1은 요청 B를 처리합니다. 한 요청의 계산을 둘로 나눈 경우와 달리, 각 GPU가 자신의 요청에 필요한 모델 계산을 수행합니다.
이때의 목적은 같은 시간에 완료하는 요청 수를 늘리는 것입니다. 각 요청의 계산 시간이 그대로여도 두 GPU가 다른 요청을 함께 처리하면 전체 처리량을 늘릴 수 있습니다. 이 방식은 한 요청 안의 중간 결과를 GPU 사이에서 모을 필요가 없지만, 어떤 요청을 어느 GPU에 맡길지는 정해야 합니다.
세 목적은 함께 나타날 수도 있습니다. 예를 들어 한 모델을 여러 GPU에 나누어 실행하고, 그런 GPU 묶음을 여러 개 두어 서로 다른 요청을 처리할 수 있습니다. 먼저 필요한 용량과 응답 시간, 처리량의 목표를 구분하면 어떤 배치가 필요한지 판단하기 쉬워집니다.
나눈 계산을 연결하는 통신
모델과 계산을 나눈 다음에는, 다음 계산이 어떤 형태의 입력을 필요로 하는지 확인해야 합니다. GPU별 결과가 있다는 사실만으로 모델의 다음 단계가 바로 실행되는 것은 아닙니다.
예를 들어 출력 벡터의 앞부분과 뒷부분을 두 GPU가 각각 완성했다면, 다음 계산은 그 조각을 모은 벡터가 필요할 수 있습니다. 반면 하나의 내적에 들어가는 곱들을 둘로 나눴다면, 각 GPU가 얻은 것은 같은 출력값에 기여하는 부분합입니다. 이때는 두 값을 더해야 최종 출력을 얻습니다. 조각을 이어 붙이는 것과 같은 위치의 부분합을 더하는 것은 서로 다른 작업입니다.
또한 최종 결과를 한 GPU만 사용할지, 여러 GPU가 모두 사용할지에 따라서도 전달 방식이 달라집니다. 이런 차이를 다루는 통신 연산에는 데이터를 모으는 연산과 부분 결과를 더해 분배하는 연산 등이 있습니다. NCCL의 통신 연산도 이러한 데이터 교환의 의미를 구별합니다. 각 연산의 이름과 구체적인 흐름은 다음 글에서 살펴보겠습니다.
데이터와 계산의 배치는 통신량에도 영향을 줍니다. 어떤 결과를 그대로 다음 계산에 사용할 수 있는 배치와, 매번 다른 GPU의 결과를 가져와야 하는 배치는 필요한 이동이 다릅니다. 그래서 계산을 나누는 경계와 데이터를 주고받는 지점을 함께 정해야 합니다. 통신에는 데이터를 옮기는 시간뿐 아니라 통신을 시작하고 참여 GPU의 준비를 맞추는 비용도 있으므로, 주고받는 양과 횟수를 함께 봐야 합니다.
통신과 대기가 더해진 실행 시간
계산을 나누면 실제 완료 시간이 어떻게 달라질까요? 그림 3은 한 GPU에서 100ms가 걸리던 같은 작업을 두 GPU에 나누는 예시입니다. 수치는 설명용이며, 각 GPU의 연산 속도가 같고 계산을 마친 뒤 통신·취합을 수행한다고 가정합니다. 이 예시에서는 계산과 통신을 겹치지 않습니다.
계산을 고르게 나눈 경우
그림 3의 가운데에서는 두 GPU가 각각 50ms 분량의 계산을 동시에 수행합니다. 계산만 보면 100ms를 50ms로 줄였습니다. 하지만 결과를 전달하고 취합하는 데 10ms가 추가되므로 전체 완료 시간은 50 + 10 = 60ms입니다. GPU가 두 개가 되어도 전체 속도 향상은 100 ÷ 60 ≈ 1.67배입니다.
두 GPU 행에 있는 보라색 10ms 구간은 같은 통신·취합에 참여하는 시간을 나타냅니다. 한 GPU의 10ms가 끝난 뒤 다른 GPU의 10ms가 이어지는 것이 아닙니다. GPU별 시간을 더하는 대신, 같은 시간 축 위에서 계산과 전달이 언제 끝나는지를 봐야 합니다.
계산이 한쪽에 몰린 경우
그림 3의 아래에서는 GPU 0에 30ms, GPU 1에 70ms 분량의 계산을 맡깁니다. GPU 0은 먼저 계산을 끝내지만, 다음 단계에 필요한 GPU 1의 결과가 아직 준비되지 않았습니다. 그래서 40ms를 기다린 뒤 함께 통신·취합에 참여합니다. 이처럼 다음 단계에 필요한 작업이 끝났는지 확인하고 작업의 진행 순서를 맞추는 것이 동기화입니다. 다른 GPU의 계산이나 데이터 전달이 아직 끝나지 않았다면, 동기화 과정에서 대기가 생길 수 있습니다.
전체 작업은 더 늦게 끝나는 계산의 70ms와 통신·취합 10ms를 합쳐 80ms에 완료됩니다. GPU 0의 대기 40ms는 GPU 1이 계산하는 시간과 겹칩니다. 따라서 이 대기를 80ms에 다시 더하지 않습니다. 한 GPU가 일찍 끝나더라도, 다음 단계가 다른 GPU의 결과를 기다려야 한다면 전체 완료는 늦어집니다.
이 대기를 줄이려면 작업량을 더 고르게 나누거나, 기다리는 결과에 의존하지 않는 다른 작업을 수행할 수 있는지 살펴봐야 합니다. 통신도 마찬가지입니다. 필요한 데이터가 준비되었다면 통신을 시작하고, 그 통신 결과를 아직 필요로 하지 않는 계산을 함께 진행할 여지가 있습니다. 다만 전달이 끝나야 실행할 수 있는 계산은 그 완료를 기다려야 합니다.
여러 GPU를 사용하는 실행에서는 연산, GPU 내부의 메모리 이동, GPU 사이의 통신을 함께 살펴보게 됩니다. 이 세 시간을 항상 따로 더하는 것이 아니라, 무엇이 겹쳐 진행되고 무엇이 다음 작업을 기다리게 하는지 확인해야 합니다. 통신이 내부 메모리 접근과 자원을 함께 사용할 수도 있어, 겹쳐 실행했을 때의 효과 역시 전체 완료 시간으로 판단해야 합니다.
앞선 글에서 한 부분을 개선한 뒤 전체 병목을 다시 확인했던 것처럼, GPU를 늘린 뒤에도 관찰은 이어집니다. 계산 시간이 줄어든 대신 통신의 비중이 커질 수도 있고, 특정 GPU에 몰린 작업이 다음 개선 대상이 될 수도 있습니다. 다음 글에서는 GPU 사이에서 데이터를 보내고 모으는 기본 통신 방식을 살펴보며, 각 GPU가 어떤 데이터를 가지고 시작해 어떤 결과를 얻는지 구체적으로 따라가겠습니다.


