[vLLM] GGUF: llama.cpp 양자화 포맷 지원
들어가며
GGUF(GGML Unified Format)는 llama.cpp에서 사용하는 양자화 포맷이다. HuggingFace에서 GGUF 파일을 직접 다운로드하여 vLLM으로 서빙할 수 있으며, Q2_K부터 Q8_0, IQ4_XS까지 다양한 양자화 타입을 지원한다. 이번 글에서는 vLLM이 GGUF를 어떤 구조로 지원하는지 살펴본다.
공식 문서
vLLM 공식 문서: GGUF Quantization
핵심 구조/코드 분석
양자화 타입 분류
vLLM은 GGUF의 양자화 타입을 네 가지 카테고리로 분류한다:
STANDARD_QUANT_TYPES = {
WeightType.Q4_0, WeightType.Q4_1, WeightType.Q5_0,
WeightType.Q5_1, WeightType.Q8_0, WeightType.Q8_1,
}
KQUANT_TYPES = {
WeightType.Q2_K, WeightType.Q3_K, WeightType.Q4_K,
WeightType.Q5_K, WeightType.Q6_K,
}
IMATRIX_QUANT_TYPES = {
WeightType.IQ1_M, WeightType.IQ1_S, WeightType.IQ2_XXS,
WeightType.IQ2_XS, WeightType.IQ2_S, WeightType.IQ3_XXS,
WeightType.IQ3_S, WeightType.IQ4_XS, WeightType.IQ4_NL,
}
이 분류에 따라 어떤 커널을 사용할지가 결정된다:
- MMVQ (Matrix-Matrix Vector Quantized): 소규모 배치(batch <= 2~16)에서 사용. 표준+K-양자화+I-Matrix 모두 지원.
- MMQ (Matrix-Matrix Quantized): 대규모 배치에서 사용. 표준+K-양자화만 지원.
- Dequant 폴백: MMQ 미지원 타입은 역양자화 후 FP16 GEMM으로 처리.
커널 선택 전략
_fused_mul_mat_gguf에서 입력 크기에 따라 최적의 커널을 선택한다:
def _fused_mul_mat_gguf(x, qweight, qweight_type):
if qweight_type in IMATRIX_QUANT_TYPES:
mmvq_safe = 8 if qweight.shape[0] > 5120 else 16
else:
mmvq_safe = 2 if qweight.shape[0] > 5120 else 6
if qweight_type in UNQUANTIZED_TYPES:
return x @ qweight.T
if x.shape[0] <= mmvq_safe and qweight_type in MMVQ_QUANT_TYPES:
y = ops.ggml_mul_mat_vec_a8(qweight, x, qweight_type, qweight.shape[0])
elif qweight_type in MMQ_QUANT_TYPES:
y = ops.ggml_mul_mat_a8(qweight, x, qweight_type, qweight.shape[0])
elif qweight_type in DEQUANT_TYPES:
block_size, type_size = gguf.GGML_QUANT_SIZES[qweight_type]
shape = (qweight.shape[0], qweight.shape[1] // type_size * block_size)
weight = ops.ggml_dequantize(qweight, qweight_type, *shape, x.dtype)
y = x @ weight.T
return y
가중치 크기(5120 기준)에 따라 MMVQ 임계값을 동적으로 조정하는 것이 눈에 띈다. 큰 행렬일수록 MMVQ 대신 MMQ가 유리하기 때문이다.
MoE 전용 GGUF 커널
GGUF MoE는 독자적인 _fused_moe_gguf 커스텀 연산을 사용한다:
def _fused_moe_gguf(x, w1, w2, topk_weights, topk_ids,
qweight_type, qweight_type2, activation):
if (qweight_type2 in MMQ_QUANT_TYPES
and qweight_type in MMQ_QUANT_TYPES
and x.shape[0] > 64):
# 대규모 배치: ggml_moe_a8 사용
sorted_token_ids, expert_ids, num_tokens_post_padded = \
moe_align_block_size(topk_ids, BLOCK_SIZE, E)
out = ops.ggml_moe_a8(x, w1, sorted_token_ids, ...)
elif qweight_type2 in MMVQ_QUANT_TYPES:
# 소규모 배치: ggml_moe_a8_vec 사용
out = ops.ggml_moe_a8_vec(x, w1, topk_ids, ...)
배치 크기 64를 기준으로 MMQ/MMVQ를 선택하며, 둘 다 사용할 수 없는 경우 전문가별 순차 처리로 폴백한다.
GGUFUninitializedParameter
GGUF 가중치는 양자화 타입에 따라 shape이 달라지므로, UninitializedParameter를 상속한 특수 파라미터를 사용한다:
class GGUFUninitializedParameter(UninitializedParameter):
cls_to_become = Parameter
data_container: list[torch.Tensor]
가중치 로딩 시점에서 실제 데이터를 data_container에 수집한 뒤, 패딩된 텐서로 병합하여 CUDA Graph 호환성을 확보한다.
왜 이 설계인가
-
llama.cpp 생태계 활용: GGUF는 커뮤니티에서 가장 널리 사용되는 양자화 포맷이다. TheBloke 등의 제공자가 대부분의 모델을 GGUF로 제공하므로, 이를 지원하면 사용 가능한 모델 풀이 크게 넓어진다.
-
3단계 커널 폴백: MMVQ -> MMQ -> Dequant로 이어지는 폴백 체인은 모든 양자화 타입에서 동작을 보장하면서도, 가능한 최적의 커널을 사용하도록 한다.
-
Blackwell 주의: SM100(Blackwell)에서는 bf16 정밀도 이슈가 있어 fp16만 지원하도록 경고를 출력한다. GGUF 역양자화 커널이 내부적으로 fp16을 사용하기 때문이다.
참고 자료
관련 포스트
vLLM 의 다른글
- 이전글 [vLLM] Marlin Kernels: 양자화 고속 GEMM 커널
- 현재글 : [vLLM] GGUF: llama.cpp 양자화 포맷 지원
- 다음글 [vLLM] Compressed Tensors: 양자화+희소성 통합 프레임워크
댓글