주요 컨텐츠로 이동

LLM 추론 성능 엔지니어링: 모범 사례


이 포스트 공유하기
LLM Inference Performance Engineering: Best Practices

이 블로그 게시물에서는 MosaicML 엔지니어링 팀이 널리 사용되는 오픈 소스 대규모 언어 모델(LLM)을 프로덕션 환경에서 활용하는 방법에 대한 모범 사례를 공유합니다. 또한 모델 선택 및 배포 하드웨어 선택 시 사용자를 지원하기 위해 이러한 모델을 기반으로 구축된 추론 서비스 배포에 대한 지침도 제공합니다. 프로덕션 환경에서 여러 PyTorch 기반 백엔드를 사용해 왔으며, 이러한 지침은 FasterTransformers, vLLM, NVIDIA에서 곧 출시할 TensorRT-LLM 등을 사용한 경험을 바탕으로 작성되었습니다.

LLM 텍스트 생성 이해

대규모 언어 모델(LLM)은 2단계 프로세스로 텍스트를 생성합니다. 첫 번째는 입력 프롬프트의 토큰이 병렬로 처리되는 "프리필(prefill)"이고, 두 번째는 텍스트가 자기 회귀 방식으로 한 번에 하나의 '토큰'씩 생성되는 "디코딩(decoding)"입니다. 생성된 각 토큰은 입력에 추가되어 다음 토큰을 생성하기 위해 모델에 다시 공급됩니다. LLM이 특수 중지 토큰을 출력하거나 사용자 정의 조건이 충족되면(예: 최대 토큰 수가 생성된 경우) 생성이 중지됩니다. LLM이 디코더 블록을 사용하는 방법에 대한 자세한 배경 정보는 이 블로그 게시물을 참조하세요.

토큰은 단어 또는 하위 단어일 수 있습니다. 텍스트를 토큰으로 분할하는 정확한 규칙은 모델마다 다릅니다. 예를 들어 Llama 모델이 텍스트를 토큰화하는 방법OpenAI 모델이 텍스트를 토큰화하는 방법을 비교할 수 있습니다. LLM 추론 제공업체는 종종 토큰 기반 메트릭(예: 토큰/초)으로 성능을 이야기하지만, 이러한 숫자는 이러한 차이점을 고려할 때 모델 유형 간에 항상 비교할 수 있는 것은 아닙니다. 구체적인 예로, Anyscale 팀은 Llama 2 토큰화가 ChatGPT 토큰화보다 19% 더 길다는 것을 발견했습니다(하지만 여전히 전체 비용은 훨씬 저렴합니다). 또한 HuggingFace의 연구원들은 Llama 2가 GPT-4와 동일한 양의 텍스트를 학습하는 데 약 20% 더 많은 토큰이 필요하다는 것을 발견했습니다.

LLM 서비스 제공을 위한 중요 지표

그렇다면 추론 속도에 대해 어떻게 생각해야 할까요?

저희 팀은 LLM 서비스 제공에 대해 다음과 같은 네 가지 주요 지표를 사용합니다.

  1. TTFT(Time To First Token): 사용자가 쿼리를 입력한 후 모델의 출력을 보기 시작하는 데 걸리는 시간입니다. 응답에 대한 짧은 대기 시간은 실시간 상호 작용에서 필수적이지만 오프라인 워크로드에서는 덜 중요합니다. 이 지표는 프롬프트를 처리한 다음 첫 번째 출력 토큰을 생성하는 데 필요한 시간에 따라 결정됩니다.
  2. TPOT(Time Per Output Token): 시스템을 쿼리하는 사용자에 대해 출력 토큰을 생성하는 데 걸리는 시간입니다. 이 지표는 각 사용자가 모델의 "속도"를 인식하는 방식에 해당합니다. 예를 들어 TPOT가 100밀리초/tok이면 사용자당 초당 10개의 토큰 또는 분당 ~450단어이므로 일반적인 사람이 읽을 수 있는 속도보다 빠릅니다.
  3. 대기 시간: 모델이 사용자에 대한 전체 응답을 생성하는 데 걸리는 전체 시간입니다. 전체 응답 대기 시간은 이전 두 지표를 사용하여 계산할 수 있습니다. 대기 시간 = (TTFT) + (TPOT) * (생성할 토큰 수)
  4. 처리량: 추론 서버가 모든 사용자와 요청에서 생성할 수 있는 초당 출력 토큰 수입니다.

저희의 목표는 무엇일까요? 첫 번째 토큰까지의 가장 빠른 시간, 가장 높은 처리량, 출력 토큰당 가장 빠른 시간입니다. 즉, 모델이 가능한 한 많은 사용자를 지원할 수 있도록 가능한 한 빨리 텍스트를 생성하기를 원합니다.

특히 처리량과 출력 토큰당 시간 사이에는 상충 관계가 있습니다. 16개의 사용자 쿼리를 동시에 처리하면 쿼리를 순차적으로 실행하는 것보다 처리량이 높아지지만 각 사용자에 대해 출력 토큰을 생성하는 데 더 오래 걸립니다.

전반적인 추론 대기 시간 목표가 있는 경우 모델을 평가하는 데 유용한 몇 가지 경험적 방법은 다음과 같습니다.

  • 출력 길이가 전체 응답 대기 시간을 지배합니다. 평균 대기 시간의 경우 일반적으로 예상/최대 출력 토큰 길이를 가져와 모델의 전체 평균 출력 토큰당 시간을 곱하면 됩니다.
  • 입력 길이는 성능에 중요하지 않지만 하드웨어 요구 사항에는 중요합니다. 512개의 입력 토큰을 추가하면 MPT 모델에서 8개의 추가 출력 토큰을 생성하는 것보다 대기 시간이 덜 늘어납니다. 그러나 긴 입력을 지원해야 할 필요성으로 인해 모델을 서비스하기가 더 어려워질 수 있습니다. 예를 들어 최대 컨텍스트 길이가 2048 토큰인 MPT-7B를 서비스하려면 A100-80GB(또는 최신)를 사용하는 것이 좋습니다.
  • 전체 대기 시간은 모델 크기에 따라 선형 이하로 조정됩니다. 동일한 하드웨어에서 더 큰 모델은 더 느리지만 속도 비율이 반드시 매개 변수 수 비율과 일치하지는 않습니다. MPT-30B 대기 시간은 MPT-7B 대기 시간의 ~2.5배입니다. Llama2-70B 대기 시간은 Llama2-13B 대기 시간의 ~2배입니다.

저희는 종종 잠재 고객으로부터 평균 추론 대기 시간을 제공해 달라는 요청을 받습니다. 특정 대기 시간 목표("토큰당 20ms 미만이 필요합니다.")에 얽매이기 전에 예상되는 입력 및 원하는 출력 길이를 파악하는 데 시간을 할애하는 것이 좋습니다.

LLM 추론의 과제

LLM 추론 최적화는 다음과 같은 일반적인 기술의 이점을 얻습니다.

  • 연산자 융합: 서로 다른 인접 연산자를 함께 결합하면 대기 시간이 더 짧아지는 경우가 많습니다.
  • 양자화: 활성화 및 가중치가 압축되어 더 적은 수의 비트를 사용합니다.
  • 압축: 희소성 또는 증류.
  • 병렬 처리: 여러 장치에서 텐서 병렬 처리 또는 더 큰 모델을 위한 파이프라인 병렬 처리.

이러한 방법 외에도 많은 중요한 Transformer 관련 최적화가 있습니다. 이에 대한 주요 예는 KV(키-값) 캐싱입니다. 디코더 전용 Transformer 기반 모델의 어텐션 메커니즘은 계산적으로 비효율적입니다. 각 토큰은 이전에 본 모든 토큰을 어텐션하므로 각 새 토큰이 생성될 때마다 동일한 값을 많이 다시 계산합니다. 예를 들어 N번째 토큰을 생성하는 동안 (N-1)번째 토큰은 (N-2)번째, (N-3)번째 … 1번째 토큰을 어텐션합니다. 마찬가지로 (N+1)번째 토큰을 생성하는 동안 N번째 토큰에 대한 어텐션은 (N-1)번째, (N-2)번째, (N-3)번째, … 1번째 토큰을 다시 살펴봐야 합니다. 어텐션 계층에 대한 중간 키/값을 저장하는 KV 캐싱은 나중에 재사용할 수 있도록 이러한 결과를 보존하여 반복적인 계산을 방지하는 데 사용됩니다.

메모리 대역폭이 중요합니다.

LLM의 계산은 주로 행렬-행렬 곱셈 연산에 의해 지배됩니다. 작은 차원의 이러한 연산은 일반적으로 대부분의 하드웨어에서 메모리 대역폭에 의해 제한됩니다. 자기 회귀 방식으로 토큰을 생성할 때 활성화 행렬 차원 중 하나(배치 크기 및 시퀀스의 토큰 수로 정의됨)는 작은 배치 크기에서 작습니다. 따라서 속도는 로드된 데이터에 대한 계산 속도보다는 GPU 메모리에서 로컬 캐시/레지스터로 모델 매개 변수를 얼마나 빨리 로드할 수 있는지에 따라 달라집니다. 추론 하드웨어에서 사용 가능하고 달성된 메모리 대역폭은 피크 컴퓨팅 성능보다 토큰 생성 속도를 더 잘 예측합니다.

추론 하드웨어 활용률은 서비스 제공 비용 측면에서 매우 중요합니다. GPU는 비싸고 가능한 한 많은 작업을 수행해야 합니다. 공유 추론 서비스는 여러 사용자의 워크로드를 결합하고 개별 격차를 채우고 겹치는 요청을 일괄 처리하여 비용을 낮게 유지할 것을 약속합니다. Llama2-70B와 같은 대규모 모델의 경우 큰 배치 크기에서만 우수한 비용/성능을 달성할 수 있습니다. 큰 배치 크기에서 작동할 수 있는 추론 서비스 제공 시스템을 갖추는 것은 비용 효율성에 매우 중요합니다. 그러나 큰 배치는 더 큰 KV 캐시 크기를 의미하며, 이는 모델을 서비스하는 데 필요한 GPU 수를 늘립니다. 여기에는 줄다리기가 있으며 공유 서비스 운영자는 일부 비용 절충을 하고 시스템 최적화를 구현해야 합니다.

MBU(모델 대역폭 활용률)

LLM 추론 서버는 얼마나 최적화되어 있을까요?

앞서 간략하게 설명했듯이 더 작은 배치 크기, 특히 디코딩 시 LLM에 대한 추론은 장치 메모리에서 컴퓨팅 장치로 모델 매개 변수를 얼마나 빨리 로드할 수 있는지에 따라 병목 현상이 발생합니다. 메모리 대역폭은 데이터 이동이 얼마나 빨리 발생하는지 결정합니다. 기본 하드웨어의 활용률을 측정하기 위해 MBU(모델 대역폭 활용률)라는 새로운 지표를 도입합니다. MBU는 (달성된 메모리 대역폭) / (피크 메모리 대역폭)으로 정의되며, 여기서 달성된 메모리 대역폭은 ((총 모델 매개 변수 크기 + KV 캐시 크기) / TPOT)입니다.

예를 들어 16비트 정밀도로 실행되는 7B 매개 변수의 TPOT가 14ms인 경우 14ms 내에 14GB의 매개 변수를 이동하여 1TB/초 대역폭 사용량으로 변환합니다. 장비의 피크 대역폭이 2TB/초인 경우 MBU 50%로 실행됩니다. 간단히 하기 위해 이 예에서는 더 작은 배치 크기 및 더 짧은 시퀀스 길이에 대해 작은 KV 캐시 크기를 무시합니다. 100%에 가까운 MBU 값은 추론 시스템이 사용 가능한 메모리 대역폭을 효과적으로 활용하고 있음을 의미합니다. MBU는 정규화된 방식으로 서로 다른 추론 시스템(하드웨어 + 소프트웨어)을 비교하는 데에도 유용합니다. MBU는 컴퓨팅 바운드 설정에서 중요한 PaLM 논문에서 소개된 MFU(모델 Flops 활용률) 지표를 보완합니다.

그림 1 루프라인 플롯과 유사한 플롯에서 MBU의 그림 표현을 보여줍니다. 주황색 음영 영역의 실선 기울기는 메모리 대역폭이 100%로 완전히 포화된 경우 가능한 최대 처리량을 보여줍니다. 그러나 실제로는 낮은 배치 크기(흰색 점)의 경우 관찰된 성능이 최대값보다 낮습니다. 얼마나 낮은지는 MBU의 척도입니다. 큰 배치 크기(노란색 영역)의 경우 시스템은 컴퓨팅 바운드이며 피크 가능 처리량의 일부로 달성된 처리량은 MFU(모델 Flops 활용률)로 측정됩니다.

모델 대역폭 활용률
그림 1: MBU(모델 대역폭 활용률) 및 MFU(모델 Flops 활용률)를 보여줍니다. MBU 및 MFU는 각각 메모리 바운드 및 컴퓨팅 바운드 영역에서 달성된 피크의 일부입니다.

MBU 및 MFU는 주어진 하드웨어 설정에서 추론 속도를 얼마나 더 높일 수 있는지 결정합니다. 그림 2는 TensorRT-LLM 기반 추론 서버를 사용하여 다양한 수준의 텐서 병렬 처리에 대해 측정된 MBU를 보여줍니다. 큰 연속 메모리 청크를 전송할 때 피크 메모리 대역폭 활용률이 달성됩니다. MPT-7B와 같은 더 작은 모델이 여러 GPU에 분산되면 각 GPU에서 더 작은 메모리 청크를 이동하므로 MBU가 더 낮아집니다.

경험적으로 관찰된 MBU
그림 2: A100-40G GPU에서 TensorRT-LLM을 사용하여 다양한 수준의 텐서 병렬 처리에 대해 경험적으로 관찰된 MBU. 요청: 배치 크기가 1인 512개의 입력 토큰 시퀀스.

그림 3은 NVIDIA H100 GPU에서 다양한 수준의 텐서 병렬 처리 및 배치 크기에 대해 경험적으로 관찰된 MBU를 보여줍니다. 배치 크기가 증가함에 따라 MBU가 감소합니다. 그러나 GPU를 확장함에 따라 MBU의 상대적 감소는 덜 중요합니다. 또한 더 큰 메모리 대역폭을 가진 하드웨어를 선택하면 더 적은 GPU로 성능을 향상시킬 수 있다는 점도 주목할 가치가 있습니다. 배치 크기 1에서 4xA100-40GB GPU(그림 2)에서 55%에 비해 2xH100-80GB에서 더 높은 MBU 60%를 달성할 수 있습니다.

텐서 병렬 처리 모드
그림 3: H100-80G GPU에서 다양한 배치 크기 및 텐서 병렬 처리 모드에 대해 경험적으로 관찰된 MBU. 요청: 512개의 입력 토큰 시퀀스

벤치마킹 결과

대기 시간

MPT-7B 및 Llama2-70B 모델에 대해 다양한 수준의 텐서 병렬 처리에서 TTFT(첫 번째 토큰까지의 시간) 및 TPOT(출력 토큰당 시간)를 측정했습니다. 입력 프롬프트가 길어짐에 따라 첫 번째 토큰을 생성하는 데 걸리는 시간이 총 대기 시간의 상당 부분을 차지하기 시작합니다. 여러 GPU에서 텐서 병렬 처리를 수행하면 이 대기 시간을 줄이는 데 도움이 됩니다.

모델 학습과 달리 더 많은 GPU로 확장하면 추론 대기 시간에 대한 수익이 크게 감소합니다. 예: Llama2-70B의 경우 4x에서 8x GPU로 이동하면 작은 배치 크기에서 대기 시간이 0.7배만 감소합니다. 이에 대한 한 가지 이유는 병렬 처리 수준이 높을수록 MBU가 낮아지기 때문입니다(앞서 설명한 대로). 또 다른 이유는 텐서 병렬 처리가 GPU 노드 간에 통신 오버헤드를 발생시키기 때문입니다.

  첫 번째 토큰까지의 시간(ms)
모델 1xA100-40GB 2xA100-40GB 4xA100-40GB 8xA100-40GB
MPT-7B 46(1배) 34(0.73배) 26(0.56배) -
Llama2-70B 맞지 않음 154(1배) 114(0.74배)

표 1: 입력 요청이 512 토큰 길이이고 배치 크기가 1인 경우 첫 번째 토큰까지의 시간. Llama2 70B와 같은 더 큰 모델은 메모리에 맞추려면 최소 4xA100-40B GPU가 필요합니다.

배치 크기가 클수록 텐서 병렬 처리 수준이 높을수록 토큰 대기 시간이 상대적으로 더 크게 감소합니다. 그림 4는 MPT-7B에 대해 출력 토큰당 시간이 어떻게 변하는지 보여줍니다. 배치 크기가 1인 경우 2x에서 4x로 이동하면 토큰 대기 시간이 ~12%만 줄어듭니다. 배치 크기가 16인 경우 4x의 대기 시간은 2x보다 33% 더 낮습니다. 이는 배치 크기 1에 비해 배치 크기 16에 대한 텐서 병렬 처리 수준이 높을수록 MBU의 상대적 감소가 더 작다는 이전 관찰과 일치합니다.

GPU 수 증가
그림 4: A100-40GB GPU에서 MPT-7B를 확장할 때 사용자당 출력 토큰당 시간. 대기 시간은 GPU 수 증가에 따라 선형적으로 확장되지 않습니다. 요청: 128개의 입력 및 64개의 출력 토큰 시퀀스

그림 5는 Llama2-70B에 대해 유사한 결과를 보여주지만 4x와 8x 간의 상대적 개선은 덜 두드러집니다. 또한 두 개의 다른 하드웨어에서 GPU 확장을 비교합니다. H100-80GB는 A100-40GB에 비해 2.15배의 GPU 메모리 대역폭을 가지므로 4x 시스템의 경우 배치 크기 1에서 대기 시간이 36% 더 낮고 배치 크기 16에서 52% 더 낮다는 것을 알 수 있습니다.

다중 GPU
그림 5: 다중 GPU에서 Llama-v2-70B를 확장할 때 사용자당 출력 토큰당 시간(입력 요청: 512 토큰 길이). Llama-v2-70B(float16)가 해당 시스템에 맞지 않기 때문에 1x40GB GPU, 2x40GB 및 1x80GB GPU 숫자는 여기에 없습니다.

처리량

요청을 함께 일괄 처리하여 처리량과 토큰당 시간을 절충할 수 있습니다. GPU 평가 중에 쿼리를 그룹화하면 쿼리를 순차적으로 처리하는 것보다 처리량이 증가하지만 각 쿼리를 완료하는 데 더 오래 걸립니다(대기열 효과는 무시).