공통 · 하드웨어 · 2026-09-18
집합 통신의 기본 동작
Send/Recv와 집합 통신의 차이에서 출발해 Broadcast, Scatter, Gather, Reduce의 입력과 결과를 살펴봅니다.
GPU 사이의 통신 과정에서는 데이터를 준비하고, 보내고 받을 작업을 요청한 뒤, 완료된 결과를 사용하는 흐름을 살펴봤습니다. 이때 사용한 Send와 Recv는 각각 상대방을 지정해 데이터를 보내거나 받는 요청이었습니다.
그런데 여러 GPU가 함께 계산하다 보면 같은 데이터를 모두에게 전달하거나, 각 GPU가 만든 결과를 한곳에 모아야 할 때가 있습니다. 개별 전송을 하나씩 구성할 수도 있지만, 그룹 전체가 어떤 입력으로 어떤 결과를 만들 것인지를 직접 요청할 수도 있습니다. 이것이 집합 통신입니다.
이 글에서는 먼저 두 요청 방식의 차이를 살펴봅니다. 이어서 같은 데이터를 복사하는 Broadcast와 서로 다른 조각을 나누는 Scatter를 비교하고, 각자의 조각을 모으는 Gather와 같은 위치의 값을 합치는 Reduce를 구별합니다. 각 동작 전후에 어느 GPU가 어떤 데이터를 갖는지를 따라가는 것이 핵심입니다.
개별 전달에서 그룹의 동작으로
GPU 네 개가 함께 작업한다고 해보겠습니다. 현재 GPU 0만 [1, 2]를 갖고 있고, 나머지 GPU도 같은 데이터를 받아야 합니다.
Send/Recv로 이 일을 구성한다면 GPU 0에서 GPU 1, 2, 3으로 보내는 작업과, 각 상대편에서 GPU 0으로부터 받는 작업을 요청합니다. 개발자가 보내는 쪽과 받는 쪽의 관계를 하나씩 연결하는 방식입니다.
집합 통신에서는 먼저 함께 통신할 참여자들의 묶음인 통신 그룹을 정합니다. 각 참여자에게는 그룹 안에서 구별하기 위한 번호인 rank가 있습니다. 이 글에서는 프로세스 하나가 GPU 하나를 담당하고, rank 0부터 3이 각각 GPU 0부터 3을 담당한다고 가정합니다.
이 그룹에 “GPU 0의 데이터를 모두가 갖도록 복사하자”라는 동작을 요청하는 것이 Broadcast입니다. 여기서 원본 데이터를 제공하는 참여자를 root라고 부릅니다. 그림에서는 GPU 0이 root입니다.
그림의 왼쪽에서는 각 CPU 코드가 통신 상대를 지정합니다. 오른쪽에서는 네 CPU 코드가 같은 그룹의 Broadcast에 참여하고, 모두 root가 GPU 0이라는 데 맞춰 요청합니다. 그림에 적힌 이름은 요청의 종류입니다. 코드 문법을 이해할 필요 없이 누구에게 보내고 받을지를 요청하는지, 그룹이 함께 할 동작을 요청하는지를 비교하면 됩니다.
집합 통신이라고 해서 CPU 한 곳에서 요청 한 번만 하면 모든 GPU가 자동으로 참여하는 것은 아닙니다. 이 예시에서는 각 GPU를 담당하는 CPU 코드가 같은 집합 통신에 참여해야 합니다. 통신 종류와 root, 데이터 형식과 크기 등도 서로 맞아야 합니다. 한쪽은 Broadcast를, 다른 쪽은 Reduce를 기다리는 식으로 요청이 엇갈리면 의도한 통신을 진행할 수 없습니다. NCCL 집합 통신 안내
따라서 두 방식의 차이는 GPU의 개수에 있지 않습니다. GPU 두 개도 집합 통신을 할 수 있고, GPU가 많아도 Send/Recv로 개별 전달을 구성할 수 있습니다. Send/Recv를 조합해 Broadcast와 같은 결과를 만드는 것도 가능합니다. 집합 통신을 사용하면 개발자는 그룹 전체의 동작을 표현하고, 통신 라이브러리는 이를 수행할 구체적인 전달 방법을 정합니다. NCCL Send/Recv 사용 안내
또한 함께 참여한다는 말이 CPU들이 정확히 같은 시각에 함수를 호출하거나, GPU의 모든 작업을 멈춘다는 뜻은 아닙니다. 앞서 본 통신처럼 CPU의 요청과 GPU의 실제 완료를 구별해야 합니다. 결과를 사용하는 작업은 해당 통신의 완료 뒤에 실행되도록 순서를 보장해야 합니다. NCCL과 CUDA 스트림
이제 개별 요청의 형태보다 입력과 결과에 집중하겠습니다. 아래 그림들은 이해를 돕기 위해 입력과 출력을 별도의 버퍼로 표시합니다. 가운데 상자는 그룹의 동작을 나타내며, 데이터가 반드시 중앙의 장치를 거쳐 간다는 뜻은 아닙니다.
같은 데이터를 나누는 Broadcast
Broadcast는 root의 데이터를 모든 참여자가 갖게 하는 동작입니다. 예를 들어 하나의 GPU에 준비한 공통 설정이나 배열을 나머지 GPU에도 전달할 때 사용할 수 있습니다.
GPU 0에 [1, 2]가 있다고 해보겠습니다. 이 배열을 Broadcast하면 GPU 0, 1, 2, 3이 모두 [1, 2]를 갖습니다. 원소를 나눠 주는 것이 아니라, 배열 전체를 각 참여자에게 복사합니다.
그림에서 다른 GPU에 적힌 ‘입력 제공 없음’은 참여하지 않는다는 뜻이 아닙니다. 이번 동작에서 기준이 되는 데이터는 root가 제공하고, 나머지는 그 데이터를 받을 공간을 준비해 참여한다는 뜻입니다.
root도 그룹의 일원이며, 완료 후 같은 데이터를 갖습니다. GPU 0이 특별한 종류의 장치라서 root가 되는 것은 아닙니다. root는 해당 통신에서 맡는 역할입니다. 다른 GPU가 원본을 갖고 있다면 그 참여자를 root로 정할 수 있습니다.
이 예시에서는 root의 입력이 두 원소이므로, 모든 GPU의 결과도 각각 두 원소입니다. GPU가 네 개가 되어도 각자 받을 배열의 크기는 그대로입니다.
조각을 나누는 Scatter
모든 GPU가 같은 배열 전체를 필요로 하는 것은 아닙니다. 각 GPU가 서로 다른 부분을 처리한다면, 전체 입력을 조각으로 나눠 배분할 수 있습니다. root의 배열을 나눠 각 참여자에게 서로 다른 조각을 주는 동작이 Scatter입니다.
이번에는 GPU 0에 여덟 원소가 있습니다.
[1, 2, 10, 20, 100, 200, 1000, 2000]
두 원소씩 네 조각으로 나누면 [1, 2], [10, 20], [100, 200], [1000, 2000]입니다. 이를 rank 순서대로 하나씩 배분합니다.
GPU 0은 첫 조각, GPU 1은 두 번째 조각, GPU 2는 세 번째 조각, GPU 3은 네 번째 조각을 받습니다. root인 GPU 0도 자신에게 배정된 조각을 갖습니다. 그림의 입력은 읽기 쉽도록 여러 줄로 접어 그렸지만, 위에서 아래로 이어지는 하나의 배열입니다.
Broadcast와 Scatter는 모두 root에서 출발하지만 받는 내용이 다릅니다. 이 여덟 원소에 Broadcast를 적용하면 모든 GPU가 여덟 원소 전체를 갖습니다. Scatter를 적용하면 각 GPU는 자신에게 배정된 두 원소를 갖습니다.
여기서 ‘나눈다’는 것은 출력의 배치를 설명하는 말입니다. 이 그림처럼 입력과 출력 버퍼를 따로 사용하면 root의 원래 입력이 자동으로 지워지는 것은 아닙니다. 앞으로도 같은 크기의 조각을 나누는 기본 형태를 기준으로 살펴보겠습니다.
조각을 모으는 Gather
Scatter가 한곳의 배열을 나눠 주었다면, Gather는 각 참여자의 배열을 이어 붙여 root에 모으는 동작입니다. 각 GPU가 따로 처리한 결과를 한곳에서 순서대로 읽어야 할 때 생각할 수 있습니다.
GPU 0부터 3까지 각각 [1, 2], [10, 20], [100, 200], [1000, 2000]을 갖고 있다고 해보겠습니다. Gather의 결과는 이 배열을 rank 순서로 연결한 것입니다.
root인 GPU 0은 여덟 원소 배열 [1, 2, 10, 20, 100, 200, 1000, 2000]을 얻습니다. 자신의 입력 [1, 2]도 결과에 포함됩니다. 먼저 도착한 순서에 따라 배열을 붙이는 것이 아니라, 각 참여자의 rank에 해당하는 위치에 놓습니다.
Gather는 값을 더하지 않습니다. 원래의 원소를 유지한 채 배열의 길이를 늘려 연결합니다. 각 GPU가 두 원소씩 제공했으므로, 네 GPU의 조각을 모은 결과에는 여덟 원소가 있습니다.
다른 GPU에 적힌 ‘출력 대상 아님’은 완성된 여덟 원소 배열이 그곳에도 생기지는 않는다는 뜻입니다. 그림의 입력과 출력은 별도 버퍼이므로, 각 GPU의 기존 입력은 유지됩니다. 결과를 한곳에 모으는 것과 기존 데이터를 없애는 것은 다른 일입니다.
같은 위치의 값을 합치는 Reduce
각 GPU가 만든 값을 모은 뒤, 같은 위치끼리 더해야 할 수도 있습니다. 이때 필요한 것은 조각의 연결이 아니라 계산입니다. 각 참여자의 같은 위치에 있는 값들을 정해진 연산으로 합쳐 root에 결과를 두는 동작이 Reduce입니다.
Gather와 같은 입력을 사용하되, 이번에는 덧셈으로 합쳐보겠습니다. 각 배열의 첫째 원소끼리, 둘째 원소끼리 각각 더합니다.
첫째 원소의 합: 1 + 10 + 100 + 1000 = 1111
둘째 원소의 합: 2 + 20 + 200 + 2000 = 2222
root인 GPU 0이 갖는 결과는 [1111, 2222]입니다. 입력 배열 하나가 두 원소였고, 결과도 두 원소입니다. Gather처럼 여덟 원소를 만드는 것도, 배열의 모든 값을 더해 숫자 하나를 만드는 것도 아닙니다. 대응하는 위치마다 하나의 결과를 만듭니다.
이 글에서는 덧셈을 사용했지만, Reduce에는 최댓값이나 최솟값처럼 다른 연산도 사용할 수 있습니다. 어느 연산을 사용할지는 함께 참여하는 쪽에서 맞춰야 합니다.
네 동작을 구별할 때는 데이터의 내용과 결과의 위치를 함께 보면 됩니다. Broadcast는 같은 배열을 모두에게 복사하고, Scatter는 서로 다른 조각을 배분합니다. Gather는 조각들을 이어 붙이고, Reduce는 대응하는 값들을 계산으로 합칩니다. 이 중 Gather와 Reduce의 완성된 결과는 root 한곳에 놓입니다.
그렇다면 모으거나 합친 결과가 모든 GPU에 필요할 때는 어떻게 할까요? 반대로 합친 결과 중 자신의 몫만 필요할 수도 있습니다. 집합 통신의 조합과 확장에서는 결과를 모두가 갖거나, 나눠 갖거나, 목적지에 맞게 교환하는 동작으로 이어가겠습니다.
![네 GPU가 모두 GPU 0의 배열 [1,2]를 갖게 하는 동일한 목표를 비교한다. 왼쪽은 GPU 0을 담당하는 CPU 코드가 GPU 1,2,3에 각각 Send를 요청하고 나머지 CPU 코드가 GPU 0을 상대방으로 Recv를 요청한다. GPU 0은 자신의 입력을 유지한다. 오른쪽은 각 GPU를 담당하는 CPU 코드가 root 0인 Broadcast에 참여하며 root는 입력을 제공하고 다른 GPU는 받는다. 두 방식 모두 최종적으로 네 GPU가 [1,2]를 갖는다. 행 배치는 실행 시각이나 실제 전달 경로를 뜻하지 않으며 코드 문법은 생략했다.](/images/collective-communication-basics/basics-01-requests.png)
![root인 GPU 0의 입력은 [1,2]이다. 나머지 GPU는 이 동작의 입력을 제공하지 않지만 수신에 참여한다. Broadcast 완료 후 네 GPU의 출력 버퍼에 같은 [1,2]가 있다. root는 고정된 물리 장치 종류가 아니라 통신 그룹 안의 역할이며 이 예시에서는 GPU 번호와 rank를 같게 두었다.](/images/collective-communication-basics/basics-02-broadcast.png)
![GPU 0의 입력 배열 [1,2,10,20,100,200,1000,2000]을 두 원소씩 네 조각으로 나눈다. 위에서 아래로 각 조각의 목적지가 GPU 0,1,2,3으로 표시되어 있다. 완료 후 GPU 0은 [1,2], GPU 1은 [10,20], GPU 2는 [100,200], GPU 3은 [1000,2000]을 별도의 출력 버퍼에 갖는다. 입력이 자동으로 지워지거나 이동하여 소멸하는 뜻이 아니다.](/images/collective-communication-basics/basics-03-scatter.png)
![각 GPU의 입력은 [1,2], [10,20], [100,200], [1000,2000]이다. Gather 완료 후 root인 GPU 0의 출력은 이 배열들을 rank 순서로 이어 붙인 [1,2,10,20,100,200,1000,2000]이다. 배열의 네 행은 원래 각 GPU의 두 원소짜리 조각이며 하나의 일차원 배열을 접어 표시했다. 다른 GPU는 출력 대상이 아니며 입력은 유지된다.](/images/collective-communication-basics/basics-04-gather.png)
![Gather와 동일한 네 입력 배열에서 첫째 원소끼리 더해 1111, 둘째 원소끼리 더해 2222를 만든다. Reduce sum 완료 후 root인 GPU 0만 결과 [1111,2222]를 출력 버퍼에 갖는다. 입력과 결과의 원소 수는 모두 두 개이며 입력 조각을 이어 붙인 Gather와 다르다. 다른 GPU의 입력도 유지된다.](/images/collective-communication-basics/basics-05-reduce.png)