1. 배경: 8GB 안에 모델 3종을 올려야 하는 제약
로브로스 인턴십에서 휴머노이드 안내로봇에 대화 기능을 넣는 과제를 맡았습니다. 사람이 말을 걸면 대답하고, "저기 저거 뭐야?" 처럼 눈앞의 물체를 가리키며 묻는 질문에도 답해야 했습니다. 즉 언어만으로는 안 되고 시각이 붙어야 했습니다.
제약이 하나 있었습니다. 인터넷 없이 로봇 안에서 돌아가야 한다는 것이었습니다. 전시장이나 현장에서 네트워크를 보장할 수 없고, 카메라로 사람 얼굴을 찍은 이미지를 외부 API로 보내는 것도 부담이었습니다. 결국 Jetson Orin AGX 한 대 위에서 전부 처리해야 했고, 쓸 수 있는 VRAM은 8GB였습니다.
2. 문제 정의: 단일 프로세스의 메모리 한계
처음에는 단순하게 접근했습니다. 파이썬 프로세스 하나에 시각 모델(VLM)과 대화 모델(LLM)을 함께 로드하는 방식입니다. 결과는 OOM(Out of Memory)이었습니다.
FP16 기준으로 대화 모델 하나가 3.21GB를 차지했습니다. 여기에 4B급 VLM을 얹으면 가중치만으로 이미 8GB에 근접했고, 추론 중 생기는 KV 캐시와 이미지 텐서까지 더해지면 여지가 없었습니다.
메모리만 문아니었습니다. 한 프로세스 안에 있으니 추론 한 건이 전체를 블로킹했습니다. 시각 질의 처리 중 VLM이 이미지를 처리하는 동안, 다음 사람이 던진 단순한 텍스트 질문까지 대기 상태로 밀렸습니다. 다수 이용자가 동시에 접근하는 환경에서는 서비스 품질에 직접 영향을 주는 조건이었습니다.
3. 기술적 난점
되짚어보면 세 가지 제약이 동시에 걸려 있었습니다.
첫째, 메모리 총량이 고정입니다. 서버라면 GPU를 추가하거나 인스턴스를 키우면 되지만 온디바이스에서는 그 선택지가 없습니다. 8GB 안에서 끝내야 합니다.
둘째, 모델을 줄일 수도 없었습니다. 시각 이해와 페르소나 대화는 성격이 다른 작업이라 하나의 모델로 합치면 양쪽 다 나빠집니다. 특히 페르소나 일관성은 파인튜닝으로 주입해야 했는데, 이를 범용 모델과 하나로 묶으면 표현 다양성이 죽었습니다.
셋째, 실시간이어야 합니다. 로봇이 응답까지 3초가 소요되면 대화형 상호작용으로 성립하지 않습니다. 메모리를 아끼려고 모델을 그때그때 로드/언로드하는 방식은 응답 지연 때문에 쓸 수 없었습니다.
4. 대안 검토
1안. 서버로 넘기고 로봇은 클라이언트만
가장 쉬운 길이었습니다. RTX 서버에 모델을 올리고 로봇은 요청만 보냅니다.
- 장점: 메모리 제약이 사라지고, 더 큰 모델을 쓸 수 있습니다.
- 단점: 오프라인 요구사항을 못 지킵니다. 네트워크가 끊기면 로봇이 응답할 수 없게 되고, 카메라 이미지가 외부로 나가는 프라이버시 문제도 그대로입니다.
2안. 모델을 하나로 합치기
VLM 하나로 시각과 대화를 모두 처리하는 방식입니다.
- 장점: 메모리를 확실히 아낍니다.
- 단점: 페르소나 일관성이 무너졌습니다. 그리고 텍스트만 있는 질문에도 무거운 시각 모델을 태우게 되어 오히려 느려졌습니다.
3안. 프로세스를 쪼개고 양자화하기
모델별로 독립 프로세스를 띄우고, 각각을 양자화해 메모리를 줄이는 방식입니다.
- 장점: 메모리도 줄고 장애도 격리됩니다.
- 단점: 프로세스 간 통신과 오케스트레이션을 직접 만들어야 합니다. 구조가 복잡해집니다.
5. 설계 결정 및 근거
3안을 골랐습니다. 오프라인 제약이 협상 불가능한 조건이었기 때문에 1안은 처음부터 탈락이었고, 2안은 품질을 포기하는 선택이라 받아들이기 어려웠습니다.
구체적으로는 두 가지를 함께 적용했습니다.
첫째, GGUF Q4_K_M 양자화. FP16 가중치를 4비트로 줄였습니다. 이것만으로 모델 하나가 3.21GB에서 1.03GB로 내려갔습니다. 68% 절감입니다. 품질 저하가 걱정이었는데, 실제 대화 응답을 비교해보니 이 크기대의 모델에서는 체감할 만한 열화가 없었습니다.
둘째, llama.cpp 기반 독립 프로세스 분리. 모델 3종을 각각 별도 포트로 띄웠습니다.
:8080파인튜닝 LLM, 페르소나 주입, temperature 0.3 (일관성):8081VLM (Qwen3-VL-4B), 시각 질의에만 조건부 호출:8082범용 LLM (Qwen3-1.7B), temperature 0.7 (표현 다양성), CPU 배치
여기서 중요한 판단이 하나 있었습니다. 세 모델을 전부 GPU에 올릴 필요가 없다는 것입니다. 범용 모델은 호출 빈도가 낮고 지연에 덜 민감해서 CPU로 내렸습니다. 연산 특성에 따라 자원을 차등 배치했습니다.
앞단에는 FastAPI 게이트웨이를 두되, 게이트웨이에는 추론 로직을 전혀 넣지 않았습니다. 어떤 모델로 보낼지 고르는 라우팅만 담당하게 했습니다. 이렇게 하니 특정 모델이 죽거나 느려져도 그 장애가 전체로 번지지 않았습니다.
전체 서빙 구조의 변경 전후는 다음과 같습니다.
5.1 계층형 라우팅
프로세스를 나누고 나니 자연스럽게 다음 질문이 생겼습니다. 모든 질문을 LLM에 보내야 하나?
이에 따라 응답 경로를 계층으로 쌓고, 위에서 해결되면 바로 반환하도록 했습니다.
질의 → ① RAG 문서 검색 → ② 룰 기반 정형 질의 → ③ LLM 추론
(문서에 답 있으면 반환) (정해진 답이면 반환) (여기까지 오면 생성)"서비스 센터 번호 알려줘" 같은 정형 질의는 LLM을 아예 호출하지 않고 룰에서 끊었습니다. 이 경로는 0.003초에 끝났습니다. LLM 경로 대비 63배 빠릅니다.
이미지도 마찬가지입니다. 화면이나 사진 맥락이 실제로 포함된 질의에만 VLM을 부르는 조건부 분기를 넣어, 텍스트 질문이 무거운 시각 모델을 타지 않게 했습니다.
6. 실험 결과
Jetson Orin AGX 온디바이스 실측입니다.
| 측정 항목 | 값 | 비고 |
|---|---|---|
| 모델 메모리 | 3.21GB → 1.03GB | Q4_K_M 양자화, 68% 절감 |
| 동시 상주 | 5.9GB / 8GB | 모델 3종 동시 로드 성공 |
| 종단 응답 (RAG 포함) | 0.19초 | 중앙값 n=5 |
| 파인튜닝 LLM 단독 | 0.17초 | RAG 추가 비용 +0.02초 |
| 룰 기반 정형 질의 | 0.003초 | LLM 미호출, 63배 단축 |
| Hybrid 검색 | 36.8ms | Dense 39.5 / BM25 +0.1ms |
목표였던 "8GB 안에 3개 동시 상주"가 5.9GB로 달성됐고, 여유 공간도 남았습니다.
부수적으로 확보된 이점도 있습니다. 배포 구성을 YAML 프로파일로 분리해두었더니, 코드를 한 줄도 고치지 않고 RTX 서버 / Jetson 원격 / Jetson 단독 오프라인 등 4가지 구성으로 전환할 수 있었습니다. 성능이 필요하면 서버를, 프라이버시가 필요하면 온디바이스를 고르는 식입니다.
7. 결론 및 시사점
제약이 구조를 만듭니다. 8GB라는 제약이 없었다면 한 프로세스에 모델을 다 올린 채로 두었을 것입니다. 메모리 때문에 프로세스를 쪼갰는데, 그 결과로 장애 격리와 자원 차등 배치라는 더 나은 구조를 얻었습니다. 제약이 설계를 강제했고 그 설계가 더 좋았습니다.
모든 질문이 같은 비용을 치를 필요는 없습니다. 계층형 라우팅으로 얻은 63배는 모델을 최적화해서 얻은 게 아니라, 모델을 부르지 않아서 얻은 것입니다. LLM을 쓰는 시스템을 만들 때 "이 요청이 정말 LLM이 필요한가"를 먼저 묻는 습관이 생겼습니다.
양자화는 생각보다 비용 대비 효과가 큽니다. 68%를 줄이면서 체감 품질 손실이 거의 없었습니다. 리소스가 제한적인 환경이라면 모델을 줄이기 전에 양자화를 먼저 시도검토할 만합니다.
이 프로젝트는 인턴십으로 끝났지만, 이후 개인적으로 웹 데모를 붙여 확장했습니다. 웹캠 스트리밍과 음성 입력, 페르소나 제어 UI를 얹어 hera-hri.vercel.app 에서 직접 만져볼 수 있게 해두었습니다.
