Generative AI

CUDA 그래프로 llama.cpp AI 추론 최적화하기

Reading Time: 5 minutes

오픈 소스인 llama.cpp 코드 베이스는 원래 2023년에 출시된 가볍지만 효율적인 프레임워크로서 Meta Llama 모델에 대한 추론을 수행하기 위한 것입니다. 전년도에 출시된 GGML 라이브러리를 기반으로 구축된 Llama.cpp는 복잡한 종속성 없이 C/C++에 초점을 맞춘 덕분에 많은 사용자와 개발자(특히 개인용 워크스테이션에서 사용하기에 적합)에게 빠르게 인기를 얻었습니다.

초기 릴리스 이후, llama.cpp는 다양한 모델, 양자화 등을 지원할 뿐만 아니라 NVIDIA CUDA 지원 GPU를 포함한 여러 백엔드도 지원하도록 확장했습니다. 8월 7일 기준, llama.cpp는 전체 GitHub 리포지토리의 스타 순위에서 123위, 전체 C++ GitHub 리포지토리의 11위에 랭크되어 있습니다.

뛰어난 성능과 에너지 효율로 AI 추론의 기반이 되는 연산을 수행할 수 있고 소비자 디바이스 및 데이터센터에 널리 보급되어 있는 NVIDIA GPU에서 llama.cpp로 AI 추론을 수행하면 이미 상당한 이점을 얻을 수 있습니다. NVIDIA와 Llama.cpp 개발자 커뮤니티는 성능을 더욱 향상시키기 위해 지속적으로 협력하고 있습니다. 이 게시물은 최근 Llama.cpp에 CUDA 그래프 기능을 도입하여 달성한 개선 사항에 대해 설명합니다.

CUDA 그래프

GPU는 새로운 세대가 나올 때마다 계속 속도가 빨라지고 있으며, 커널이나 메모리 복사 등 GPU의 각 활동이 매우 빠르게 완료되는 경우가 많습니다. 과거에는 각 활동을 CPU에서 개별적으로 스케줄링(실행)해야 했고, 관련 오버헤드가 누적되어 성능 병목 현상이 발생할 수 있었습니다.

CUDA 그래프 기능은 여러 GPU 활동을 하나의 계산 그래프로 예약할 수 있게 함으로써 이 문제를 해결합니다. 이전 게시물인 CUDA 그래프 시작하기에서 CUDA 그래프를 소개하고 시작하는 방법을 설명했습니다. 후속 포스팅인 GROMACS 2023의 CUDA 그래프 가이드에서는 CUDA 그래프를 GROMACS 생체 분자 시뮬레이션 과학 소프트웨어 패키지에 어떻게 성공적으로 적용했는지 설명합니다.

기존 스트림 모델을 사용할 때는 각 GPU 활동이 개별적으로 스케줄링되는 반면, CUDA 그래프를 사용하면 여러 GPU 활동을 한꺼번에 스케줄링할 수 있습니다. 따라서 스케줄링 오버헤드가 줄어듭니다. 기존 스트림 기반 코드를 그래프를 사용하도록 변경하는 것은 비교적 간단합니다. 이 기능은 몇 가지 추가 CUDA API 호출을 통해 스트림 실행을 그래프로 ‘캡처’합니다.

본 게시물에서는 이 기능을 활용하여 기존 Llama.cpp 코드가 스트림 대신 그래프를 사용하여 실행되도록 하는 방법을 설명합니다.

Llama.cpp에서 CUDA 그래프 구현하기

섹션에서는 기존 코드의 오버헤드를 강조하고 이러한 오버헤드를 줄이기 위해 CUDA 그래프를 도입한 방법을 설명합니다.

기존 코드의 오버헤드

그림 1은 Linux를 사용하여 NVIDIA A100 GPU에서 Llama 7B Q4 추론을 수행하는 CUDA 그래프 도입 전 기존 코드의 프로파일 스니펫을 보여줍니다. 이 데이터는 NVIDIA Nsight 시스템을 사용하여 얻었습니다. 그림의 상단 프로필에 있는 각 GPU 활동 청크는 단일 토큰의 평가에 해당하며, 확대하면 두 개의 전체 토큰이 평가되는 것을 볼 수 있도록 설정되어 있습니다. 프로필에서 각 토큰의 평가 사이에 간격이 있음을 알 수 있으며, 이는 샘플링 및 컴퓨팅 그래프 준비와 관련된 CPU 활동에 해당합니다. (이 부분은 포스트 마지막 부분에서 다시 다룰 예정입니다.)

그림 1. 프로파일링 타임라인에서 GPU가 유휴 상태인 섹션을 통해 관찰된 GPU 추론과 관련된 GPU 활동과 관련된 오버헤드

그림 1의 아래쪽은 동일한 프로파일을 보여 주지만 확대하여 토큰 평가 내의 여러 활동을 확인할 수 있습니다. 토큰 평가 내에서 커널 사이에 보이는 간격은 런타임 오버헤드 때문입니다. CUDA 그래프를 통해 이러한 오버헤드를 제거하면 성능이 크게 향상됩니다. 강조 표시된 이벤트는 CPU(왼쪽 하단)에서 커널을 실행하는 것과 GPU(오른쪽 상단)에서 해당 커널을 실행하는 것으로, CPU는 GPU에서 실행되기 전에 실행하면 훨씬 성공적으로 실행할 수 있습니다.

따라서 CPU 측 실행 오버헤드는 여기서 결정적인 경로가 아닙니다. 대신, 오버헤드는 각 커널 실행과 관련된 GPU 측 활동으로 인해 발생합니다. 이 동작은 모델과 하드웨어에 따라 다를 수 있지만, CUDA 그래프는 CPU 및/또는 GPU 실행 오버헤드를 줄이는 데 적용할 수 있습니다.

오버헤드 감소를 위한 CUDA 그래프 소개

Llama.cpp는 이미 GGML 형식의 “그래프” 개념을 사용하고 있습니다. 각 토큰의 생성에는 다음 단계가 포함됩니다:

  • 사용 중인 모델을 기반으로 GGML 그래프 구조를 준비합니다.
  • 사용 중인 백엔드(이 경우 NVIDIA GPU)에서 구조를 평가하여 다음 토큰의 어휘에 대한 로그 확률 분포인 ‘로짓(logits)’을 얻습니다.
  • 로그를 사용하여 어휘에서 토큰을 선택하기 위해 CPU에서 샘플링을 수행합니다.

GPU 그래프 평가 단계를 가로채서 CUDA 그래프를 도입했습니다. 기존 스트림을 그래프로 캡처하고, 캡처한 그래프를 실행 가능한 그래프로 인스턴스화한 후, 이를 GPU로 실행하여 단일 토큰의 평가를 수행하는 코드가 추가되었습니다.

적절한 효율을 위해서는 동일한 그래프를 여러 번 재사용해야 하며, 그렇지 않으면 캡처와 인스턴스화에 새로 도입되는 오버헤드가 이점을 능가하게 됩니다. 그러나 그래프는 추론이 진행됨에 따라 동적으로 진화합니다. 문제는 전반적인 이점에 도달하기 위해 토큰 전체에 걸쳐 그래프를 약간(오버헤드가 적은) 조정할 수 있는 메커니즘을 개발하는 것이었습니다.

추론이 진행됨에 따라 컨텍스트 크기에 따라 연산 길이가 단계적으로 증가하여 계산 그래프에 상당한(그러나 드물게) 변화가 생깁니다. GGML 그래프는 검사되고 필요한 경우에만 다시 캡처됩니다. cudaGraphExecUpdate는 전체 재인스턴스보다 훨씬 낮은 오버헤드로 이전에 인스턴스화된 실행 가능한 그래프를 업데이트하는 데 사용됩니다.

또한 각 토큰에 대해 특정 노드(KV 캐시 관련)의 커널 파라미터가 변경되는 컴퓨팅 그래프에 빈번하지만 매우 사소한 변경이 있습니다. NVIDIA는 재사용 가능한 CUDA 그래프에서 이러한 파라미터만 업데이트하는 메커니즘을 개발했습니다. 각 그래프가 시작되기 전에 CUDA 그래프 API 기능을 활용하여 업데이트가 필요한 부분을 식별하고 관련 파라미터를 수동으로 교체합니다.

CUDA 그래프는 현재 배치 크기 1 추론(Llama.cpp의 주요 사용 사례)으로 제한되어 있으며, 더 큰 배치 크기에 대한 추가 작업이 계획되어 있습니다. 이러한 개발 사항과 문제 및 제한 사항을 해결하기 위한 지속적인 작업에 대한 자세한 내용은 GitHub 이슈, Llama.cpp에서 CUDA 그래프를 사용하기 위한 NVIDIA의 새로운 최적화 및 여기에 링크된 풀 리퀘스트를 참조하세요.

CUDA 그래프가 오버헤드 감소에 미치는 영향

CUDA 그래프가 도입되기 전에는 그림 1의 하단 프로파일에서 볼 수 있듯이 GPU 측 런타임 오버헤드로 인해 커널 간에 상당한 간격이 존재했습니다.

그림 2는 CUDA 그래프가 도입된 후의 모습을 보여줍니다. 모든 커널이 동일한 계산 그래프의 일부로 GPU에 제출됩니다(단일 CUDA API 실행 호출로). 따라서 오버헤드가 크게 줄어들어 그래프 내 각 커널 간의 간격이 매우 작아집니다.

그림 2. CUDA 그래프를 사용하면 GPU 추론과 관련된 GPU 활동과 관련된 오버헤드가 크게 줄어듭니다.

성능 결과

그림 3은 Llama.cpp의 새로운 CUDA 그래프 기능의 이점을 보여줍니다. 측정된 속도 향상은 모델 크기와 GPU 변형에 따라 다르며, 모델 크기가 작아지고 GPU 성능이 증가함에 따라 이점이 증가합니다. 이는 CUDA 그래프를 사용하면 빠른 GPU에서 작은 문제와 관련된 오버헤드가 가장 많이 감소하기 때문에 예상과 일치하는 결과입니다. 가장 빠른 NVIDIA H100 GPU에서 가장 작은 Llama 7B 모델의 경우 최고 1.2배의 속도 향상을 달성했습니다. 모든 결과에는 Linux 시스템이 사용되었습니다.

이제 Llama.cpp의 메인 브랜치에서 일괄 크기 1 추론에 대해 CUDA 그래프가 기본으로 활성화됩니다.

그림 3. 다양한 크기의 여러 Llama 모델(모두 배치 크기 1)에 대해 기존 스트림 대비 CUDA 그래프를 사용하여 달성한 속도 향상, 여러 가지 NVIDIA GPU 변형에 대한 결과 포함

CPU 오버헤드 감소를 위한 지속적인 노력

그림 1의 상단 프로필은 타임라인에서 토큰 평가 사이의 간격(GPU가 유휴 상태인 경우)을 보여줍니다. 이는 GGML 그래프 준비 및 샘플링과 관련된 CPU 활동 때문입니다. 이러한 오버헤드를 줄이기 위한 작업은 이 GitHub 이슈와 여기에 연결된 풀 리퀘스트에 설명된 대로 진행 단계에 있습니다. 이 작업을 통해 최대 ~10%까지 개선될 것으로 예상됩니다.

요약

이 포스팅에서는 인기 있는 코드 베이스인 Llama.cpp에 CUDA 그래프를 도입하여 NVIDIA GPU에서 AI 추론 성능을 크게 개선한 방법을 보여드렸으며, 현재 진행 중인 작업을 통해 더 많은 개선이 이루어질 것으로 예상됩니다. 자체 AI 지원 워크플로우에 이 작업을 활용하려면 사용 지침을 따르세요.

Discuss (0)

Tags