
~28 tok/s에서 ~54 tok/s. 같은 GPU, 같은 모델인데 설정 두 가지만 바꿨다. 양자화 포맷과 CUDA Graph, 이 두 축이 vLLM 서빙 속도를 결정한다.
RTX 5090 32GB에서 Qwen3.5-27B를 서빙하고 있었다. 처음에는 AWQ-4bit 양자화 모델(cyankiwi/Qwen3.5-27B-AWQ-4bit)을 사용했고, CUDA Graph 관련 에러 때문에 --enforce-eager 옵션으로 CUDA Graph를 꺼둔 상태였다. 동작은 하지만 ~28 tok/s는 체감상 느렸다.
두 가지를 동시에 바꾸기로 했다. 양자화 포맷을 NVFP4로, 그리고 CUDA Graph를 다시 켜는 것.
NVFP4(Kbenkhaled/Qwen3.5-27B-NVFP4)는 NVIDIA의 FP4 양자화 포맷이다. AWQ-4bit보다 VRAM을 더 사용하지만(14GB -> 18.4GB), 벤치마크 복원율이 99.1%로 원본 모델에 가까운 품질을 유지한다. RTX 5090 32GB에서는 여유가 있으니까 품질을 선택했다.
속도 향상의 진짜 핵심은 CUDA Graph 활성화다. CUDA Graph는 GPU 커널 호출 시퀀스를 캡처해서 재생하는 최적화 기법으로, 커널 launch overhead를 크게 줄여준다.
그런데 Qwen3.5에는 Mamba 레이어가 있다. 이 Mamba 레이어가 CUDA Graph 캡처 시 문제를 일으킨다. conv_state 캐시 크기가 실제 배치 크기보다 작으면 assertion이 실패하면서 서버가 크래시한다(vLLM issue #34094).
그래서 처음에 --enforce-eager로 CUDA Graph를 통째로 껐던 것이다. 하지만 이건 속도를 절반으로 깎는 것이나 마찬가지였다.
assertion이 "배치 크기 > conv_state 캐시"일 때 터지니까, 배치 크기를 제한하면 된다. --max-num-seqs 128로 동시 처리 시퀀스 수를 제한하면 CUDA Graph가 정상적으로 캡처되고, assertion도 발생하지 않는다.
# Before: AWQ-4bit + enforce-eager (~28 tok/s)
vllm serve "cyankiwi/Qwen3.5-27B-AWQ-4bit" \
--enforce-eager \
--max-model-len 200000
# After: NVFP4 + CUDA Graph + prefix caching (~54 tok/s)
vllm serve "Kbenkhaled/Qwen3.5-27B-NVFP4" \
--max-num-seqs 128 \
--enable-prefix-caching \
--max-model-len 200000 \
--kv-cache-dtype fp8 \
--gpu-memory-utilization 0.94변경된 옵션을 정리하면 이렇다.
--enforce-eager 제거: CUDA Graph 활성화--max-num-seqs 128 추가: Mamba assertion 우회--enable-prefix-caching 추가: 동일 prefix 재사용으로 추가 속도 향상--kv-cache-dtype fp8: KV 캐시 메모리 절약--gpu-memory-utilization 0.94: RTX 5090 32GB의 실질적 상한 (0.95는 OOM)모델을 교체하면 API에서 참조하는 모델 이름이 바뀐다. 클라이언트(Mastra Playground 등)에서 모델 이름을 하드코딩하고 있으면 모델 교체 시마다 클라이언트도 수정해야 한다.
--served-model-name my_model 옵션을 사용하면 실제 모델이 뭐든 간에 API에서는 항상 my_model이라는 이름으로 접근할 수 있다. 모델을 AWQ에서 NVFP4로, 또는 다른 모델로 교체해도 클라이언트 코드는 변경할 필요가 없다.
NVFP4로 교체하면서 vLLM도 0.16.0에서 nightly(0.16.1rc1.dev107)로 업그레이드해야 했다. 이전 버전에는 RMSNormGated 클래스에서 self.activation = "swish" 라인이 누락된 버그가 있어서 Qwen3.5의 Mamba 레이어가 동작하지 않았다.
nightly 버전을 쓰는 건 항상 리스크가 있다. 이번에는 수동 패치까지 고려했지만 다행히 dev107 이후 버전에서 수정이 들어왔다. nightly를 쓸 때는 특정 커밋 해시를 고정해두고, 잘 되는 버전을 찾으면 함부로 올리지 않는 게 좋다.
최종 구성에서 ~54 tok/s를 안정적으로 달성했다. 체감 속도가 확실히 다르다. 특히 긴 응답을 생성할 때 대기 시간이 크게 줄었다.
양자화 포맷 선택은 VRAM 여유와 품질 요구사항의 트레이드오프다. VRAM이 빠듯하면 AWQ-4bit, 여유가 있으면 NVFP4가 낫다. CUDA Graph는 vLLM 서빙에서 거의 필수인데, Mamba 같은 새로운 아키텍처와의 호환성 문제가 아직 남아있다.
gpu-memory-utilization 값은 GPU마다 경험적으로 찾아야 한다. RTX 5090 32GB에서는 0.94가 한계였고, 0.95에서는 OOM이 발생했다. 이런 미세한 차이는 문서에 나오지 않는다.
NVFP4는 VRAM을 더 사용하지만(14GB에서 18.4GB) 벤치마크 복원율이 99.1%로 품질이 우수하다. CUDA Graph와 조합하면 AWQ-4bit 대비 약 2배의 처리 속도를 얻을 수 있다.
Qwen3.5의 Mamba 레이어가 CUDA Graph 캡처 시 conv_state 캐시 크기가 배치 크기보다 작으면 assertion이 실패한다. --max-num-seqs로 배치 크기를 제한하면 이 문제를 우회할 수 있다.
0.94가 실질적인 상한이다. 0.95로 설정하면 OOM이 발생하며, KV cache와 모델 가중치를 합산한 메모리 사용량이 32GB에 딱 맞아떨어지는 지점이 0.94다.