Dev Stories

새 모델 하나 올리는데 왜 이렇게 오래 걸릴까: 버전 호환성 트러블슈팅 두 가지

안녕하세요, KT IT부문 AX플랫폼본부에서 LLM 및 Azure OpenAI 모델 서빙/회수를 담당하고 있는 최원석입니다.

모델 서빙 파트에서는 그동안 사내에서 서빙되는 모든 모델의 배포를 직접 담당해왔습니다. 그런데 최근 셀프서빙 체계가 도입되면서, 앞으로는 각 팀이 필요한 모델을 직접 서빙 요청하고 운영하는 구조로 점차 바뀌어 갈 예정입니다. 이 변화를 앞두고 그동안 저희가 모델을 서빙하며 실제로 부딪혔던 문제들을 정리해 공유해보려 합니다.

오늘은 그중에서도 "버전 호환성" 문제로 겪었던 두 사례를 공유합니다.


들어가며


모델 서빙에 쓰이는 vLLM 같은 서빙 프레임워크는 몇 주 단위로 새 버전이 나올 만큼 빠르게 발전하고 있습니다. 반대로 HuggingFace 등에 올라오는 모델은 각자 다른 시점, 다른 프레임워크 버전을 기준으로 배포됩니다. 문제는 이 둘 사이의 "시점 차이"입니다. 모델을 만들 당시 기준으로 작성된 모델카드와, 지금 우리가 실제로 쓰는 서빙 프레임워크 버전이 서로 다른 전제를 깔고 있다 보니, 모델카드에 적힌 옵션을 그대로 넣어도 안 되는 경우가 생각보다 많습니다.

이미 검증되어 서빙 중인 모델이라면 이런 어긋남을 이미 다 걸러낸 상태이기 때문에, 기존 서빙 옵션과 이미지 버전을 그대로 재사용하면 됩니다 — 이 경우는 셀프서빙으로 전환되어도 큰 어려움이 없습니다. 문제는 신규 모델을 처음 서빙할 때입니다. 아무도 아직 이 모델과 지금 버전의 프레임워크 조합을 검증해본 적이 없기 때문에, 그 어긋남을 처음 발견하고 해결하는 사람이 매번 새로 나와야 합니다.


사례 1. transformers v5가 망가뜨린 서빙 — Dolphin-Mistral-24B


환경 개요

항목
내용
모델
dphn/Dolphin-Mistral-24B-Venice-Edition
아키텍처
mistral3 (Mistral-Small-3.1-24B 기반, Pixtral 비전 포함)
GPU
H100 NVL × 1, tp=1
vLLM
0.25.1
transformers
5.12.1


에러 1: is_fast AttributeError — tokenizer 백엔드 교체의 여파

​AttributeError: 'CachedMistralCommonBackend' object has no attribute 'is_fast'
1

vLLM은 tokenizer를 초기화할 때 is_fast 속성으로 fast/slow tokenizer 분기를 결정합니다. 그런데 transformers v5.x에서 mistral3 계열 모델의 tokenizer 백엔드가 CachedMistralCommonBackend로 교체됐습니다. 이 백엔드는 Mistral 공식 mistral_common 라이브러리 위에서 동작하는 독립 구현체라 HuggingFace tokenizer 클래스 계층을 상속하지 않고, 결과적으로 is_fast 속성 자체가 없습니다.


여기서 중요한 점은 transformers 버전을 내려서 해결할 수 없다는 겁니다. vLLM 0.24.0부터 transformers v4를 공식 차단했기 때문에, v4로 내리면 vLLM 자체가 기동을 거부합니다. v5 어느 버전을 써도 mistral3 tokenizer 백엔드 문제는 동일하게 재현됩니다.


해결: --tokenizer_mode mistral --config_format mistral 두 플래그를 추가하면 vLLM이 tokenizer를 HuggingFace 경로가 아닌 mistral_common 전용 경로로 초기화합니다. HF 모델카드에는 없는 옵션이지만, transformers v5 환경에서 mistral3 아키텍처를 올릴 때는 사실상 필수입니다.


에러 2: model weights not found — 포맷 불일치

에러 1을 잡고 재기동하니 이번엔 No model weights found. Checked formats: consolidated.safetensors, consolidated.00.pth가 났습니다.


HuggingFace 모델카드에는 --load_format mistral 옵션이 명시되어 있는데, 이건 Mistral 공식 릴리즈 포맷(consolidated.safetensors 등)만 탐색하는 옵션입니다. 문제는 Dolphin-Mistral-24B가 HuggingFace 표준 샤딩 포맷(model-00001-of-00010.safetensors)으로 배포된 모델이라는 점입니다. --load_format mistral은 이 파일 패턴을 인식하지 못합니다.


해결: --load_format mistral을 제거하면 vLLM 기본 HuggingFace 로더가 샤딩된 safetensors를 정상 인식합니다.


정리하면 HF 모델카드 대비 빼야 할 옵션과 넣어야 할 옵션이 정반대였던 게 이 케이스의 핵심입니다. 모델카드는 Mistral 공식 체크포인트 기준으로 작성되어 있었는데, 실제로 받은 건 HF Hub 재배포 버전이었기 때문입니다.


사례 2. 커스텀 Tool Parser가 vLLM 버전업을 두 단계나 건너뛴 결과


두 번째는 이미 서빙 중이던 멀티모달 모델(Midm-K-200-multimodal)에 Tool Calling 기능을 추가하려다 만난 문제입니다. Midm 모델은 OpenAI 호환 tool_call 포맷이 아니라 자체 <tool_call>... 포맷을 쓰는데, vLLM 기본 내장 tool-parser 목록(hermes, llama3, mistral 등) 어디에도 이 포맷을 지원하는 게 없었습니다.


커스텀 파서를 가져왔지만 인자가 반영되지 않는다

Midm 공식 튜토리얼에 있던 IBM Granite 파서 기반 커스텀 코드를 가져와 서빙 커맨드에 세 옵션(--enable-auto-tool-choice, --tool-parser-plugin, --tool-call-parser)을 추가했습니다. 그런데 배포 후 확인해보니 옵션이 전혀 반영되지 않았습니다.


원인은 저희 서빙 이미지 내부에서 vLLM 실행을 감싸는 쉘 스크립트가 추가 인자($@)를 vllm 프로세스로 전달하지 않는 구조였기 때문입니다. 스크립트 내부의 실행 라인을 sed로 직접 찾아 옵션을 삽입하는 방식으로 우회했는데, 이 과정에서 작은따옴표로 감싸지 않으면 $PORT 같은 변수가 스크립트 실행 전에 조기 확장되어 버리는 함정도 있었습니다.


AnyTokenizer가 사라졌다 — vLLM 0.16.0의 조용한 이름 변경

인자 문제를 풀고 재배포하니 이번엔 ImportError: cannot import name AnyTokenizer from vllm.transformers_utils.tokenizer로 죽었습니다. 원본 파서 코드가 참조한 튜토리얼은 vLLM 0.11.0 기준이었는데, 저희가 쓰던 이미지는 0.16.0이었습니다. 그사이 해당 모듈이 deprecation shim으로 바뀌어 AnyTokenizer는 완전히 사라지고, 새 위치(vllm.tokenizers.TokenizerLike)로 이름까지 바뀌어 있었습니다.


tool_parsers 네임스페이스 전체 이전 — 한 번 더

이걸 고치고 재배포했더니 이번엔 ModuleNotFoundError: No module named vllm.entrypoints.openai.tool_parsers. vllm.entrypoints.openai.tool_parsers.* 네임스페이스 전체가 vllm.tool_parsers.*로 옮겨간 것이었습니다.


이때 정상 파드는 이미 크래시루프 상태라 kubectl exec가 먹히지 않았는데, GPU 없는 1회성 디버그 파드를 --command 플래그로 ENTRYPOINT를 덮어써서 띄우고, 그 안에서 새 네임스페이스 구조를 직접 열어봤습니다. 문서화된 마이그레이션 가이드가 없는 내부 리팩터링일수록, 소스를 직접 열어보는 게 가장 빠른 길이었습니다.


세 문제 모두 고치고 나니 실제 파서 코드에서 바뀐 건 상단 import 세 줄뿐이었습니다.

# 기존
from vllm.transformers_utils.tokenizer import AnyTokenizer
from vllm.entrypoints.openai.tool_parsers.abstract_tool_parser import ToolParserManager
from vllm.entrypoints.openai.tool_parsers.granite_20b_fc_tool_parser import Granite20bFCToolParser

# 수정
from vllm.tokenizers import TokenizerLike as AnyTokenizer
from vllm.tool_parsers import ToolParserManager
from vllm.tool_parsers.granite_20b_fc_tool_parser import Granite20bFCToolParser​

최종적으로는 실제 호출에서 tool_calls가 정상 구조화되어 반환되는 것까지 확인했습니다.


마치며


두 사례 모두 공통점이 있습니다. 서빙 프레임워크(vLLM)와 모델/코드가 서로 다른 시점의 버전을 전제로 만들어졌을 때, 그 간극에서 문제가 생긴다는 점입니다. 모델카드나 튜토리얼은 특정 시점 기준으로 작성되고, 그 이후 프레임워크가 계속 리팩터링되면서 옵션명이나 모듈 경로가 조용히 바뀝니다. 문서화가 안 된 변경일수록 추측보다는 실제 배포된 이미지 안에 직접 들어가서 확인하는 편이 빨랐습니다.


셀프서빙 환경에서 새 모델을 처음 올리실 때도 마찬가지일 겁니다. 모델카드에 적힌 옵션이 그대로 안 먹힐 수 있고, 에러 메시지 하나가 실은 프레임워크 버전 차이 때문일 수 있습니다. 막히면 "왜 안 되지"보다 "지금 쓰는 버전에서는 뭐가 바뀌었지"부터 확인해보시길 권합니다.


읽어주셔서 감사합니다!

최원석

KT IT부문 AX플랫폼본부에서 LLM 및 Azure OpenAI 모델의 서빙/회수 운영을 담당하고 있습니다.

새 모델 하나 올리는데 왜 이렇게 오래 걸릴까: 버전 호환성 트러블슈팅 두 가지 - kode