내용 요약
빠른 LLM 추론 엔진 구축
- 이 글은 C++와 CUDA를 사용하여 라이브러리 없이 LLM 추론 엔진을 구축하는 방법에 대한 것임.
- 이를 통해 LLM 추론의 전체 스택을 이해하고, 다양한 최적화가 추론 속도에 미치는 영향을 실감할 수 있음.
- 주요 목표는 소비자 장치에서 단일 프롬프트로 빠르게 실행할 수 있는 프로그램을 만드는 것임.
1. LLM 아키텍처 및 추론 개요
- 대부분의 주요 LLM은 연속적인 트랜스포머 블록을 사용하는 동일한 아키텍처를 따름.
- 모델 로딩은 커스터마이즈 가능한 트랜스포머 블록 클래스를 정의하고 이를 시퀀스로 구성하여 safetensors 가중치로 초기화하는 것임.
- 추론은 주로 단일 배치로 이루어지며, "디코드 단계"가 실행의 대부분을 차지함.
1.1 추론 개요
- 추론은 주어진 프롬프트 토큰을 모델에 전달하고, 이를 통해 KV 캐시를 채우는 프리필 단계와 반복적으로 모델을 전달하여 토큰을 생성하는 디코드 단계로 나뉨.
- 모델의 포워드 패스는 임베딩 테이블을 사용하여 토큰 ID를 임베딩 벡터로 매핑하고, 트랜스포머 블록 시퀀스를 통해 상태를 변형시킴.
1.2 병목 현상 및 벤치마크
- 추론은 현대 하드웨어에서 메모리 대역폭에 의해 제한됨.
- 모델 양자화는 추론 속도를 개선하는 데 효과적임.
- 이론적인 최대 토큰 처리량은 하드웨어에 따라 다르며, 실제 성능은 여러 인퍼런스 엔진을 통해 확인할 수 있음.
2. CPU에서의 추론
- CPU에서의 초기 구현은 단일 스레드로 이루어지며, FP32 가중치만 지원함.
- 멀티스레딩을 통해 코드 병렬화를 시작하고, SIMD를 사용하여 성능을 향상시킬 수 있음.
2.1 멀티스레딩
- OpenMP를 사용하여 코드의 병렬화를 통해 성능을 개선할 수 있음.
- 멀티헤드 어텐션 계산을 병렬화하여 성능을 더욱 향상시킬 수 있음.
2.2 가중치 양자화 및 SIMD
- SIMD를 사용하여 벡터화된 연산을 통해 성능을 향상시킬 수 있음.
- 가중치를 FP16으로 양자화하여 메모리 대역폭을 줄이고 성능을 개선할 수 있음.
3. GPU에서의 추론
- 모델을 FP16으로 양자화하여 RTX 4090에 로드하고 GPU 추론 구현을 시작할 수 있음.
- CUDA를 사용하여 C++ 함수(커널)를 GPU에서 병렬로 실행할 수 있음.
3.1 CUDA로의 단순 포팅
- CPU 연산을 1-1로 CUDA 커널로 변환하여 GPU 백엔드를 구현할 수 있음.
- CUDA 커널은 비동기적으로 실행되지만, 동일한 스트림에서는 순차적으로 실행됨.
3.2 더 나은 행렬 곱셈
- 행렬 곱셈은 CPU에서 큰 런타임을 차지하며, OpenMP를 통해 최적화할 수 있음.
- GPU에서는 블록당 1개의 행을 처리하도록 하여 스레드 활용도를 높일 수 있음.
3.3 커널 융합 및 더 나은 행렬 곱셈
- 커널을 융합하여 성능을 향상시킬 수 있음.
- 메모리 읽기 및 쓰기를 최적화하여 성능을 더욱 개선할 수 있음.