공부/인공지능

27B 로컬 LLM을 vLLM → llama.cpp로 갈아끼우며 동시성·속도 끌어올리기

TechToast 2026. 9. 9. 00:51
반응형

연구실에 공용 LLM 서버를 돌리면서 생긴 일입니다. 27B짜리 dense(hybrid linear-attn) 모델을 워크스테이션과 리모트 서버에 올려서, 교수와 학생 7명이 매일같이 붙들고 썼는데, 문제가 하나 있었습니다. vLLM으로 FP8 양자화를 걸면 안정적이긴 한데 응답 속도가 아쉽고, 요청이 몰리면 속도가 급격히 떨어졌습니다. 그래서 저는 하드웨어를 더 사는 대신, 서빙 레이어만 통째로 바꾸는 쪽을 택했습니다.

 

왜 갈아탔나 — vLLM의 벽

vLLM은 정말 잘 만들었습니다. 다만 이 모델이 hybrid linear-attention 구조라서, 카드 세 장을 TP=3으로 한 덩어리로 묶는 게 애초에 안 됩니다. "16 not divisible by 3"이라는 에러가 뜨면서 죽네요. 그래서 TP=2로 줄이고 MTP를 켜봤는데도 개선 폭이 제한적이었습니다. 서빙 프레임워크 자체의 구조적 한계라고밖에 설명할 수 없는 상황이었습니다.

 

문제는 요청이 한꺼번에 몰릴 때 두드러졌습니다. GPU는 계산할 게 없어서 놀고 있는데, 응답만 계속 늦어지는 거죠. 화면은 안 보면서 키보드만 두드리는 격이었습니다. GPU를 열심히 두드려도 정작 화면(응답)은 안 나오는 상황이었습니다.

llama.cpp Q4_K로 갈아끼우기

리모트 서버(L40)는 FP8까지만 되고, NVFP4 같은 FP4 네이티브는 못 씁니다. 그래서 4bit GGUF가 사실상 유일한 경로였습니다. 같은 프롬프트에 같은 토큰수로 단일 요청 벤치를 돌렸습니다.

 

구성 단일 tok/s
vLLM TP2+MTP 28.9
vLLM 단일 33
llama.cpp Q4_K_XL (2장) 35.8
llama.cpp Q4_K_M (1장) 37.8

 

재미있는 점은, 1장짜리 Q4_K_M이 도리어 2장짜리 Q4_K_XL보다 빨랐다는 겁니다. 한 장이면 충분한 일을 굳이 두 장으로 나눠서 노드 간 통신비만 끌어올린 셈이었습니다. 어쨌든 37.8 tok/s는 vLLM 단일(33)보다도 높은 수치입니다.

 

양자화는 Unsloth Dynamic 3.0 GGUF를 적용했습니다. 정확도가 +10%라는 주장이 있고, 커뮤니티에서도 나름대로 검증이 되어 있습니다. FP4를 못 쓰는 카드에서 정확도 손실을 아끼는 몇 안 되는 길입니다.

워크스테이션은 NVFP4 + MTP

반면 워크스테이션(Blackwell)은 FP4 네이티브를 쓸 수 있어서, NVFP4 + MTP 조합으로 단일 100 tok/s를 넘겼습니다. 여기서 진짜 핵심 이슈는 풀컨텍스트 262K를 유지하는 일이었습니다. 컨텍스트를 길게 잡으면 KV 캐시가 금방 커지는데, 시야각을 넓게 가져가려면 캐시를 압축하지 않으면 메모리가 못 버팁니다. --kv-unified-per-slot 262144를 박고 KV 캐시를 q8_0으로 돌리는 식으로 해결했습니다.

litellm으로 묶기, 유저별로 잠그기

노드가 세 군데라면, 그냥 따로 쓰면 어디가 여유 있고 어디가 막히는지 눈으로만 보면 안 됩니다. 그래서 litellm 프록시 하나로 세 개 업스트림(본진 하나 + 백업 둘)을 묶었습니다. least-busy로 요청을 나눠 보내고, 가동이 안 되는 노드는 fallback으로 넘어가게 했습니다.

 

그리고 유저별 토큰키를 발급했습니다. 교수와 학생 7명 몫입니다. 분당 토큰 제한(tpm)과 예산(budget)을 사람마다 다르게 걸어두었습니다. 이제 특정 키로 들어온 요청이 정확히 누구 것인지 알 수 있습니다.

 

서버 접근 자체도 api-key로 잠갔습니다. litellm만 업스트림에 접근할 수 있고, 학생이 서버 주소를 직접 쳐서 들어가면 401을 받습니다. 비용 산정은 openrouter 단가를 고정으로 박아서, 응답에 cost 헤더가 붙고 클라이언트는 그걸 읽어 추정 비용을 표시합니다.

사용량 대시보드

출력이 아니라 투명성이 목표라면, 숫자를 눈으로 보여줘야 합니다. OpenRouter 대시보드를 벤치마킹해서 우리 것도 만들었습니다. 지표카드, 토큰 종류(prompt·reasoning·completion), 캐싱(cached·uncached), 요청량, GPU 노드 분산까지 한 화면에 담았습니다.

 

 

유저별 가로바 비중클라이언트(Hermes·OpenCode 등) 구분을 넣고, 클릭하면 해당 유저가 하이라이트되게 했습니다. 마스터키나 헬스체크가 만들어내는 노이즈는 제거했고, 더미 데이터는 넣지 않았습니다.

결과와 정직한 한계

vLLM 시절보다 토큰속도가 올라갔고, 동시성도 안정적이었습니다 — 크래시 0. 7명이 함께 쓰면서 유저별 사용량이 투명해졌고, 클라이언트별 비용 추정까지 됩니다.

 

정직한 한계도 있습니다. L40은 FP4를 못 써서 4bit GGUF가 유일한데, 정확도가 조금 깎이는 게 아쉽습니다(근손실급이라 보시면 됩니다). MTP는 일부 단일 카드에서 크래시가 나서 구성마다 켜고 끕니다. 3장 full 병렬(TP=3)은 구조상 안 되니, 대신 노드 역할분담(본진/백업)으로 돌리고 있습니다. 후속으로 리모트 서버 B를 MiniMax H3로 교체해서 빈 슬롯을 활용하는 계획을 잡아두었습니다.

반응형