
PPT 만든다고 11pm에 퇴근하는 나 ㅋ

- 서빙 효율성 판단 척도
- 비용
- 처리량
- 지연시간
- 단순 GPU 연산 능력으로 평가하는 건 무리가 있음.
- 비효율적으로 돌아가는데 단순히 열심히 계산하다고 MFU 높고, Bandwidth 문제인데 GPU 활용도가 떨어진다고 측정될 수 있음. -> 그래서 MBU 개념이 등장함
- 목표는 처리량 높이고 지연시간 낮추기. 그런데 어떻게?

- 추론의 2단계
- Prefill: 사전 계산
- Decoding: 자기회귀 방식으로 token을 순차적 생성
- decoding 단계와 다르게 prefill은 병렬 처리가 가능하고, 그렇게 해야 효율적
- 덕분에 둘의 병목 원인이 다름.
- 여기서 우리는 크게 세 가지 부분으로 나누어 추론 효율성과 성능을 높일 아이디어를 찾을 수 있음
- prefill: 메모리 잘 써서 연산량 줄이기
- model: 모델을 압축시키거나 파라미터 양자화해서 줄이기
- decoding: 연산량은 얼마 안 되는데 bandwidth가 병목이니까 이 문제를 해결해보자

- CPU Bound, I/O Bound를 알고 있다면 무슨 의미인지 금방 알 듯.

- 매번 동일한 토큰 선형 변환 낭비 -> 캐싱을 하자

- 근데 캐시가 너무 비효율적임 (KV cache mem 계산)
- KV cache size 계산
- KV 캐시는 키, 값 캐시 각각 2개 저장하므로 2 곱함.
- 모델이 사용하는 데이터 타입 크기에 맞게 곱함
- self attention 결과는 attention layer 수만큼 생기므로 layer 수 곱함
- token embedding을 표현하는 dimension만큼 또 곱함
- 메모리 미리 확보하기 위해 최대 sequence 길이 곱함
- 배치 크기 커질수록 저장하는 데이터 많아지니까 배치 크기도 곱함
- KV Cache 공간을 미리 계산해서 memory 할당해줬더니 batch 크기 늘릴 공간이 부족해짐
- e.g. LLama 2 13B 모델은 40 layers, 5,120 dimension인데, 32 batch size, 2,048 sequence length, 2 bytes per value로 단순 계산하면, 2 * 32 * 2,048 * 40 * 5,120 * 2 = 54GB
- KV cache size 계산

- multi-head attention
- Attention is All you need에서 여러 head에 attention 연산을 수행했었음.
- Q, K 사이 다양한 측면 관련성 반영 가능하고 성능은 높였으나, KV cache에 더 많은 메모리가 필요하고 로드하는데 memory bound.
- multi-query attention
- 모든 head가 단일 K, V 공유
- Q만 head 독립적
- K, V projection param이 1/h로 감소
- grouped-query attention
- head를 g개 그룹으로 나누고, 그룹 내에서 K, V 공유
- MQA와 MHA 사이의 중간 지점
- g=1이면 MQA, g=h면 MHA와 동일

- OS의 virtual memory 개념과 유사함.

- 모델 자체의 용량 줄여서 HW 요구 사항을 줄이자
- FP16, BF16, ...
- W4A16(Weight 4bits and Activation 16bits) 같은 것도 쓴다
- 양자화 수행 시점
- PTQ
- bits-and-bytes: QLoRA 저자가 썼던 논문
- GPTQ: 양자화할 때 모델도 업데이트 하자
- AWQ: 양자화할 파라미터를 선별하자
- GGUF: CPU에서도 양자화가 된다면?
- QAT
- PTQ
- PTQ는 vision 했을 때 빼놓고는 잘 안 쓸 듯. LLM 스케일이 압도적으로 커서 함부로 양자화하면 성능이 어떻게 떨어질지 알 수 없다.

- 작은 모델을 똑똑하게 만들자
- Knowledge Distillation (이미 앞에서 배웠으므로 패스)
- 지식을 더 작은 모델로 이전
- 희소성(Sparsification)
- 희소 행렬은 많은 요소가 0인 행렬로, 전체 밀도 행렬보다 공간을 덜 차지하는 압축된 형태로 표현
- 가장 흔하게 신경망 구성 요소를 선택적으로 제거(pruning)하는 방식 사용

- batch 없을 때 문제점
- GPU는 테라플롭, 심지어 페타플롭 범위 연산 속도를 자랑함.
- 그런데 model parameter 로드하느라 memory bandwidth bound 걸려서 툭하면 포화상태 도달
- naive batching = static batching
- 힘들게 모델 파라미터 로드했는데, 굳이 요청 하나만 처리할거냐? 그냥 한 번에 많이 받아서 처리해버리자.
- 그런데 실제 환경에서 요청 언제 들어올 줄 알고 한 번에 처리할 거냐. N개 쌓일 때까지 기다릴 거임??
- 그리고 token sequence 차이가 많이 날 수록 짧은 질문한 애들은 손해 + GPU도 놀고 있음.

- dynamic batching
- 조금 기다려서 최대한 많은 요청을 한 번에 처리하자
- continuous batching
- Continuous Batching = Ragged Batching + Dynamic Scheduling + Chunked Prefill
- Ragged Batching: 패딩 없이 여러 시퀀스를 처리할 수 있게 함
- Dynamic Scheduling: 완료된 시퀀스를 즉시 새 시퀀스로 교체
- Chunked Prefill: 긴 프롬프트를 메모리 제약에 맞게 분할
- Continuous Batching = Ragged Batching + Dynamic Scheduling + Chunked Prefill

- Decoding할 때 memory bandwidth 병목 좀 제거하자
- kernel fusion
- 반복적 수행하는 연산(e.g. 메모리 로드/저장)은 하나로 묶어서 오버헤드 줄이자.
- flash attention이 대표적인 예시
- MS에서 21년에 공개한 deep fusion이란 것도 있음



- tiling으로 NxN 행렬을 BLOCK로 나눠 SRAM에 저장
- kernel fusion으로 쪼갠 block 연산들을 하나의 kernel로 합침
- 기존에도 행렬 곱셈이긴 한데, HBM 왔다갔다하는 게 병목점 -> SRAM만 쓰자
- AI 개발자가 할 수 있는 영역은 아님
- CUDA: NVIDIA GPU 프로그래밍하기 위한 platform/language
- CUDA 개발자가 NVIDIA LIB 수준에서 attention 전체를 하나로 합친 커스텀 CUDA kernel을 추가해줘야 함.
- softmax 연산을 분할하는 트릭을 보는 게 묘미.

- GPU는 행렬 곱셈 연산은 빠른데, 비행렬 곱셈 연산은 16배 정도 느림.
- 비행렬 곱셈이 빈번하게 발생하는 곳이 flash attention에서 softmax 분할하다가 생겼는데, 정규화를 마지막에 한 번만 수행하는 것으로 횟수 자체를 줄여버림.
- 역전파를 위한 상태 저장도 m, l이 아니라, L만 계산해서 저장해두면 됨.

- batch size가 작거나, 어텐션 헤드 수가 작아서 충분한 수의 thread block을 모으지 못하면, GPU의 SM을 충분히 활용하기 어려움
- 그러니 입력 시퀀스로도 병렬화를 추가해버리자.

사실 이게 효과가 있는 게 맞나 의심스러워서 안 찾아보다가, 여기저기서 언급되길래 호다닥 추가한 내용. (나도 잘 몰라..)