DGX Spark 하나에 두 개의 Qwen3 모델 구동하기
DGX Spark(GB10) 단일 하드웨어에 vLLM과 LiteLLM을 활용해 대형 모델(Qwen3-Next-80B)과 소형 모델(Qwen3-4B)을 동시에 띄우는 고군분투기를 다룹니다. 단순 OOM 문제부터 vLLM의 메모리 할당 방식, 그리고 Qwen3 모델의 도구 호출 에이전트 연동 시 발생하는 치명적인 파싱 및 추론 모드 문제를 해결하는 과정을 담고 있습니다.
DGX Spark 한 대에 두 개의 Qwen3 모델 구동하기: 로컬 LLM 환경 구축을 위한 메모리 계산법
상주(Residency) 연산, gpu_memory_utilization 함정, 그리고 가장 먼저 확인해야 할 사항들. 로컬 LLM 실험 과정에서 얻은 노하우입니다. Devashish, 2026년 6월 16일
내 에이전트 스택은 Hermes와 함께 워크스테이션에서 돌아갑니다. 모델은 같은 LAN 망에 있는 DGX Spark에서 실행됩니다. 이러한 분리는 의도된 것입니다. 워크스테이션은 가벼운 상태를 유지하고, Spark가 GPU 작업을 처리하며, 둘은 HTTP 프록시를 통해 통신합니다.
Clawrium를 통해 에이전트 플릿(함대)을 관리하기 시작한 이후, Hermes 인스턴스 수가 계속 늘어났습니다. 더 많은 호스트에 더 많은 에이전트가 생기고 동시 트래픽이 증가하며, 모두 같은 Spark로 몰리게 되었습니다. 예전에는 노트북 한 대에서 모델 하나만 구동하던 설정이 이제는 단일 백엔드를 바라보는 소규모 클러스터가 되었습니다. 그리고 이러한 부하의 형태는 기존 단일 모델 서버가 감당할 수 없는 구조입니다.
devashish.me을 읽어주셔서 감사합니다! 새로운 게시물을 받아보고 제 작업을 지원해주세요. 구독하기
수개월 동안 Spark는 ollama를 통해 모델을 서빙했습니다. 잘 작동했습니다. 모델 하나를 띄우고, 단일 설정을 유지하며, 쉽게 내리는 것이 가능했습니다. 하지만 ollama는 GPU 카드를 독점합니다. 프로세스별 메모리 예산도, gpu_memory_utilization 조절 기능도 없으며, 무거운 추론용 모델과 빠른 응답용 모델을 함께 상주시킬(Coresident) 간단한 방법이 없습니다. KV 캐시 관리는 기저의 llama.cpp 백엔드가 제공하는 기본 수준에 불과했습니다. PagedAttention도 지원하지 않았습니다.
vLLM은 이 모든 문제를 해결해 줍니다. PagedAttention은 KV 블록을 연속으로 고정하지 않고 유연하게 회수합니다. gpu_memory_utilization 설정은 컨테이너별로 메모리 예산을 할당해 줍니다. 하나의 Spark(GB10, 119.67 GiB 통합 메모리)는 :4000 포트의 LiteLLM 프록시 뒤에서 여러 vLLM 컨테이너를 실행할 수 있으며, Hermes는 단일 URL을 통해 두 모델 중 하나로 라우팅할 수 있습니다.
여기서 목표는 명확했습니다. 무거운 작업을 위해 Qwen3-Next-80B-Instruct-FP8을, 빠른 응답을 위해 Qwen3-4B-Instruct-2507을 상주시키되, 단일 엔드포인트에서 두 모델 모두에 접근할 수 있게 하는 것입니다. 이것이 '왜' 해야 하는가에 대한 이유입니다. 아래는 이 목표를 실제로 이루기 위해 겪은 과정입니다. 수치만 잘 맞는다면 Spark 하드웨어도 두 개의 Qwen3 모델을 거뜬히 감당할 수 있습니다. 하지만 며칠 동안은 수치가 맞지 않았습니다. 지난 주말이 그렇게 날아갔습니다.
시도 1: 목표치를 맹신하다 처음 80B 모델 설정은 gpu_memory_utilization: 0.75, max_model_len: 65536, max_num_seqs: 4 였습니다. vLLM의 KV 캐시 초기화가 "캐시 블록에 사용할 수 있는 메모리가 없습니다(No available memory for the cache blocks)"라는 에러를 내며 충돌했습니다. Qwen3-Next는 대부분 Mamba 기반이며, 블록별 페이지 정렬로 인해 가중치 로딩 후 남은 약 14 GiB의 메모리보다 더 많은 KV 풀을 요구했습니다.
할당량을 0.85로 올렸습니다. 이번에는 가용 메모리 확인 단계에서 충돌했습니다. "장치의 여유 메모리(98.51/119.67 GiB)가 원하는 GPU 메모리 사용량(0.85, 101.72 GiB)보다 적습니다." 4B 모델이 이미 약 16 GiB를 점유하고 있었습니다. 80B 모델의 0.85 목표량은 '남은 여유 메모리'가 아닌 '카드 전체 메모리'를 기준으로 계산되고 있었습니다.
이것이 첫 번째 교훈입니다. gpu_memory_utilization은 여유 메모리가 아닌 전체 GPU 메모리 대비 비율입니다. 두 개의 vLLM 프로세스를 동시에 상주시키려면 CUDA 프레임워크 오버헤드를 위해 각각의 비율을 합쳐 0.95 미만으로 설정해야 합니다. 만약 남은 여유 메모리를 기준으로 계산한다면 OOM(메모리 부족)과 알 수 없는 KV 부족 현상 사이에서 무한히 고통받게 될 것입니다.
결국 80B 모델은 0.80 / 32k / 2로 설정하니 해결되었습니다. 가중치 로딩 후 KV 풀에 약 20.8 GiB가 할당되어 정상적으로 실행되었습니다.
시도 2: Hermes를 연동하다
문제 없이 모델이 로드되어 Hermes 에이전트를 붙였더니 이번에는 도구 호출(tool calls)이 일반 텍스트로 반환되는 문제가 발생했습니다.
하루 종일 파서(parser) 문제를 추적해 봤지만 해결책이 없었습니다. hermes_tool_parser.py와 qwen3xml_tool_parser.py 모두 단수형인
tool_choice를 "required"로 강제하면 작동했습니다. 하지만 "auto"로 설정하면 tool_calls는 비어있고, content도 비어있는 채로,
Qwen의 공식 모델 카드는 이에 대해 명확히 설명하고 있습니다. Qwen3-Next-80B-Thinking 모델은 '생각(Thinking) 모드'만 지원합니다. enable_thinking: false 설정은 이 모델 체크포인트에서는 구조적으로 아무런 작동도 하지 않습니다(no-op). 프롬프트에 /no_think를 넣어도 무시됩니다. 모델은
기본적으로 tool_choice: "auto"를 사용하는 모든 에이전트 SDK 환경에서 이것은 복구 불가능한(unrecoverable) 치명적인 실패입니다. 결국 이 문제는 파서 플래그 교체만으로는 해결할 수 없는 문제였고, 근본적인 우회 방법이 필요했습니다.