왜 단일 GPU로는 부족한가

70B 파라미터 모델을 FP16으로 올리면 가중치만 약 140GB가 필요하다. 여기에 KV 캐시와 활성값 메모리가 더해지면 80GB짜리 H100 한 장으로는 로드 자체가 불가능하다. 양자화로 크기를 줄일 수 있지만 정확도 손실과 처리량 저하를 감수해야 한다. 결국 모델을 여러 GPU에 쪼개 얹는 분할 전략이 필요하고, 그중 지연시간 관점에서 가장 널리 쓰이는 방식이 텐서 병렬화(Tensor Parallelism, TP)다.

텐서 병렬화의 동작 원리

파이프라인 병렬화가 레이어 단위로 모델을 세로로 자른다면, 텐서 병렬화는 하나의 행렬 곱을 가로로 쪼갠다. 예를 들어 어텐션의 QKV 프로젝션 가중치를 열(column) 방향으로 나눠 각 GPU가 일부 헤드만 계산하고, 출력 프로젝션에서 행(row) 방향으로 나눈 뒤 all-reduce로 결과를 합친다. 핵심은 레이어 하나를 처리할 때마다 GPU 간 통신이 발생한다는 점이다. 따라서 GPU들이 NVLink처럼 대역폭이 높은 링크로 묶여 있어야 성능이 나온다.

분할되는 지점

  • 어텐션: 헤드 단위로 분할, 출력에서 all-reduce
  • MLP: 첫 번째 FC는 column-parallel, 두 번째 FC는 row-parallel
  • 임베딩/LM head: vocab 차원으로 분할

vLLM으로 바로 서빙하기

실무에서는 직접 구현하기보다 vLLM 같은 엔진을 쓴다. tensor_parallel_size만 지정하면 된다. 아래는 4장의 GPU에 70B 모델을 얹는 예시다.

vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --dtype bfloat16 \
  --port 8000

tensor-parallel-size는 반드시 모델의 어텐션 헤드 수를 나눌 수 있는 값이어야 한다. 헤드가 64개라면 2, 4, 8은 되지만 6은 안 된다. gpu-memory-utilization은 KV 캐시가 쓸 여유를 남기되 OOM을 피하는 선에서 조정한다.

클러스터 배포 설정

쿠버네티스에 올릴 때는 GPU 요청 수와 TP 크기를 일치시키고, 통신을 위해 공유 메모리를 넉넉히 잡아야 한다. 이 부분을 놓치면 NCCL 초기화 단계에서 멈춘다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llama70b-vllm
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args: ["--model", "meta-llama/Llama-3.1-70B-Instruct",
                 "--tensor-parallel-size", "4"]
          resources:
            limits:
              nvidia.com/gpu: 4
          volumeMounts:
            - name: dshm
              mountPath: /dev/shm
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 16Gi

텐서 병렬화 vs 파이프라인 병렬화

항목텐서 병렬화(TP)파이프라인 병렬화(PP)
분할 방향레이어 내부(가로)레이어 사이(세로)
통신 빈도레이어마다 all-reduce스테이지 경계에서만
대역폭 요구높음(NVLink 필수)상대적으로 낮음
지연시간낮음버블로 인해 높을 수 있음
적합한 범위노드 내부노드 간 확장

실무 원칙은 명확하다. 노드 안에서는 TP, 노드를 넘어갈 때는 PP다. NVLink로 묶인 8장까지는 TP로 붙이고, 그 이상 규모는 노드 간 PP를 얹는 하이브리드가 표준이다.

실전에서 주의할 점

  • PCIe 환경 회피: NVLink 없이 PCIe로만 연결된 GPU에서 TP를 크게 잡으면 all-reduce 통신이 병목이 되어 오히려 느려진다. 이 경우 TP를 2 이하로 줄이는 편이 낫다.
  • TP는 무조건 크게가 아니다: 통신 오버헤드는 TP 크기에 비례해 늘어난다. 모델이 2장에 들어간다면 굳이 4장에 쪼개지 말 것.
  • NCCL 튜닝: 토폴로지 감지가 어긋나면 성능이 급락한다. 문제가 의심되면 NCCL_DEBUG=INFO로 링 구성을 확인한다.
  • GPU 동질성: TP는 모든 GPU가 동일 모델·동일 속도라고 가정한다. 서로 다른 세대를 섞으면 가장 느린 GPU에 전체가 맞춰진다.

정리하면 텐서 병렬화는 대형 모델을 낮은 지연시간으로 서빙하는 핵심 도구지만, 통신 대역폭이라는 물리적 제약 위에서만 제값을 한다. 하드웨어 토폴로지를 먼저 파악하고 TP 크기를 정하는 것이 성능 튜닝의 출발점이다.