fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • claude-code 페이지로 이동
    • harness 페이지로 이동
    • llm 페이지로 이동
    • ops 페이지로 이동
    • practice 페이지로 이동
  • algorithm 페이지로 이동
    • Alias Method: 가중치 랜덤 선택을 전처리 O(n), 추출 O(1)로 바꾸기
    • Welford's Online Algorithm: 값을 저장하지 않고 평균과 분산 계산하기
  • architecture 페이지로 이동
    • distributed-systems 페이지로 이동
    • domain 페이지로 이동
    • evolution 페이지로 이동
    • patterns 페이지로 이동
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • postgresql 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • gaps and islands: SQL 창 함수로 이어진 구간 찾기
    • MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 한국어 형태소 분석기 Nori vs Lindera — OpenSearch에서 Milvus로 갈 때 어휘 검색은 유지되나
    • 정규화와 역정규화: 무결성과 조회 비용 사이의 선택
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 5종을 실제로 벤치마크했다 — 같은 recall에서 QPS는 얼마나 갈리나
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
    • 종료 신호는 어디서 멈추는가
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS와 TLS: 핸드셰이크, 인증서, 종료 지점
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • Alpine 이미지의 Java 에서 링크 바꿔치기(TOCTOU)를 막지 못하는 이유
    • Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • 로그에 traceId 남기기 — MDC 부터 OpenTelemetry 까지
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 클러스터 아키텍처: Broker, KRaft Controller와 복제
    • Kafka 기본 개념: Topic, Partition, Offset과 Segment
    • Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • Kafka 파티션·리밸런스·컨슈머 지연 운영
    • Kafka를 로그로 이해하기: 복제, 재처리, 상태와 데이터 통합
    • Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • SSH forced command 로 배포 키가 실행할 수 있는 명령 제한하기
  • mlops 페이지로 이동
    • llm-serving 페이지로 이동
    • model-router 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • observability 페이지로 이동
    • Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • K8s 위 Spring Boot 앱의 메트릭 수집
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS가 줄지 않는 이유: CPython, gc.collect(), malloc_trim
  • rabbitmq 페이지로 이동
    • RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • resume 페이지로 이동
    • 경험 및 경력기술서
    • 김병태 경력기술서
    • 김병태 포트폴리오
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • personal-projects 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • thinking 페이지로 이동
    • 자기주도 학습: 질문에서 시작해 글로 검증하기
    • 좋은 일을 넘어 중요한 일을 하는 법
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • Glossary↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/mlops/모델을 골라주는 계층이 대신 가져오는 제약
mlops

모델을 골라주는 계층이 대신 가져오는 제약

라우터를 붙이면 애플리케이션은 모델 이름을 몰라도 된다. 그런데 모델 이름을 모른다는 것이 그냥 편해지는 것으로 끝나지 않는다. 어느 모델이 응답할지 미리 알 수 없다는 상태가 네 곳에서 제약으로 돌아온다. 요청을 보내기 전에 지켜야 하는 한도가 가장 좁은 쪽에 맞춰진다 대상 모델 목록이 나도 모르게 바뀔 수 있다 응답이 이상했을 때 어느 모델이 응답했는지...

2026.09.08·7 min read·81 views·SERIES · Model Router · 3/8

Model Router 시리즈 3번이다. 앞 글 어느 모델로 보낼지 무엇으로 정하는가에서 판단 방식 넷과 그 판단에 드는 시간을 다뤘다. 이 글은 그 판단을 라우터에 맡긴 대가로 새로 생기는 제약을 다룬다.

라우터를 붙이면 애플리케이션은 모델 이름을 몰라도 된다. 그런데 모델 이름을 모른다는 것이 그냥 편해지는 것으로 끝나지 않는다. 어느 모델이 응답할지 미리 알 수 없다는 상태가 네 곳에서 제약으로 돌아온다.

  • 요청을 보내기 전에 지켜야 하는 한도가 가장 좁은 쪽에 맞춰진다
  • 대상 모델 목록이 나도 모르게 바뀔 수 있다
  • 응답이 이상했을 때 어느 모델이 응답했는지 알 방법이 필요하다
  • 자유롭게 고르는 것이 캐시 적중을 떨어뜨린다

컨텍스트 한도가 가장 작은 모델에 맞춰진다

가장 먼저 걸리는 제약이다.

Azure AI Foundry 의 model router 는 유효 컨텍스트 한도가 라우팅 대상 중 가장 작은 모델의 한도로 내려앉는다. 이유는 구조상 어쩔 수 없다. 요청을 받는 시점에 어느 모델로 갈지 아직 정해지지 않았으므로, 가장 좁은 모델로 갈 가능성을 배제할 수 없다. 그 모델에 들어가지 않는 입력을 받아 두면 라우팅 결과에 따라 실패한다.

이 제약이 실무에서 걸리는 순서는 이렇다.

용도걸리는가
짧은 질의응답, 분류, 요약대체로 걸리지 않는다
긴 문서를 그대로 넣는 용도먼저 걸린다
대화 이력이 계속 쌓이는 용도이력이 한도에 다다르는 시점에 걸린다

대상 목록에 작은 모델을 하나 넣으면 전체 한도가 그 모델에 묶인다는 것이 핵심이다. 싼 모델을 후보에 넣는 이유가 비용을 줄이는 것인데, 그 모델의 컨텍스트 한도가 좁으면 긴 요청 전체가 막힌다. 비용 후보를 늘리는 판단과 긴 입력을 받는 요건이 정면으로 충돌한다.

자체 구축으로 넘어가면 이 제약을 다르게 풀 수 있다. 입력 토큰 수를 먼저 세고 그 길이에 들어가는 모델만 후보로 남기는 것이다. LiteLLM 은 이것을 두 장치로 제공한다.

장치동작
enable_pre_call_checks: true호출 전에 컨텍스트 초과 여부를 확인한다
context_window_fallbackslitellm.ContextWindowExceededErrors 에만 발동하는 별도 fallback 목록

앞의 것은 실패를 막고, 뒤의 것은 이미 실패한 요청을 더 큰 모델로 넘긴다. 이 둘의 설정 상세는 LiteLLM 로 라우팅 정책을 설정하는 방법에서 다룬다.

판단이 보는 것과 받는 것이 다르다

Azure model router 는 이미지 입력을 받는다. 그런데 라우팅 판단은 텍스트만 본다. 이미지가 붙은 요청도 텍스트 부분만 읽고 모델을 고른다는 뜻이다. 오디오는 받지 않는다.

여기서 읽을 것은 이 상품 하나의 사양이 아니다. 라우터가 요청의 일부만 읽고 판단한다는 구조가 일반적이라는 점이다. 직접 만들 때도 같은 선택을 하게 된다. 이미지를 판단에 넣으려면 그 이미지를 읽는 모델을 라우팅 경로에 하나 더 두어야 하고, 그 호출 시간이 모든 요청에 얹힌다.

요청의 어느 부분을 판단에 쓰지 않는지를 문서에 적어 두어야 한다. 적어 두지 않으면 이미지 위주의 요청이 계속 잘못된 모델로 가는 것을 나중에 지표로 발견하게 된다.

라우팅 대상 목록을 고정한다

관리형 라우터는 대상 목록을 고정하는 장치를 제공한다. Azure model router 의 경우다.

  • 라우팅 대상 목록을 명시적으로 지정할 수 있다
  • 새 모델은 명시적으로 넣지 않으면 편입되지 않는다
  • fallback 대상은 그 목록이 겸한다

마지막 항목이 중요하다. fallback 목록을 따로 두지 않고 라우팅 대상 목록을 그대로 쓰는 이유는, 승인하지 않은 모델로 요청이 넘어가는 것을 막기 위해서다. 장애가 났을 때 자동으로 다른 모델로 넘기는 기능과, 검토하지 않은 모델에 사내 데이터를 보내지 않는 요건이 충돌한다. 대상 목록 하나로 둘을 함께 잠근 것이 그 충돌에 대한 답이다.

직접 만들 때 이것을 그대로 옮길 수 있다. 후보 목록과 fallback 목록을 따로 관리하면 fallback 쪽을 갱신하지 않고 지나쳐 승인 범위를 넘게 된다.

새 모델이 자동으로 편입되지 않는 쪽이 기본값이어야 하는 이유도 같다. 모델이 새로 나오면 품질과 단가와 응답 형식이 다르고, 그중 응답 형식이 다르면 애플리케이션이 파싱에 실패한다. 편입 여부를 사람이 정하게 두면 그 검토가 강제된다.

결정을 사후에 되짚을 수 있어야 한다

사용자가 "답이 이상하다" 고 할 때 어느 모델이 응답했는지 모르면 원인을 좁힐 수 없다. 모델을 고정해 두던 때는 이 정보가 필요하지 않았다. 라우터를 붙인 순간부터 요청마다 답이 달라지므로 기록해야 한다.

Azure model router 는 요청별로 시도한 모델 순서와 fallback 발생 여부를 조회하는 별도 API 를 제공한다.

LiteLLM 은 응답 헤더로 준다.

헤더담는 것
x-litellm-model-id실제로 응답한 배포의 식별자
x-litellm-model-api-base그 배포의 endpoint
x-litellm-response-cost그 호출의 비용
x-litellm-call-id호출 식별자

직접 만드는 라우터도 최소한 이 넷에 해당하는 값을 남겨야 한다. 호출 식별자가 있어야 애플리케이션 로그와 라우터 로그를 이어 볼 수 있고, 비용이 요청 단위로 남아야 팀별 집계가 가능하다.

집계 지표도 별도로 필요하다. LiteLLM 이 노출하는 것을 기준으로 보면 갈래가 셋이다.

갈래지표
배포별 결과litellm_deployment_success_responses, litellm_deployment_failure_responses, litellm_deployment_total_requests
fallback 발생litellm_deployment_successful_fallbacks, litellm_deployment_failed_fallbacks, litellm_deployment_cooled_down
남은 예산litellm_remaining_team_budget_metric, litellm_remaining_api_key_budget_metric 계열

요청 단위 기록과 집계 지표는 역할이 다르다. 전자는 특정 요청 하나를 되짚을 때 쓰고, 후자는 "fallback 이 평소보다 자주 나고 있다" 를 알아채는 데 쓴다. 전자만 있으면 문제가 났다는 사실 자체를 늦게 안다.

Uber 의 GenAI Gateway 는 이 기록을 감사 로그 하나로 통합해 비용 귀속과 보안 감사와 품질 평가에 함께 쓴다. 같은 로그를 세 목적에 쓰려면 처음부터 무엇을 남길지 정해야 한다는 뜻이기도 하다.

자유롭게 고르는 것이 캐시 적중을 떨어뜨린다

이 절이 라우터 설계에서 가장 반직관적인 부분이다.

외부 제공자의 prompt caching 은 값이 셋이다. 아래는 2026년 공개 자료 기준이고, 제공자마다 다르며 단가는 바뀐다.

값크기
cache write기본 입력 단가에 25% 프리미엄
cache read기본 입력 단가의 10% 수준
유효 시간제공자마다 다르다

Claude Haiku 4.5 를 예로 들면 100만 토큰당 cache read 0.10대기본입력1.00 이다.

같은 앞부분에 적중이 2회 이상 나면 손익이 넘어간다. 한 번 쓰고 버리는 앞부분에 캐시를 붙이면 25% 프리미엄만 내고 끝난다.

여기서 라우팅과 충돌이 생긴다.

캐시는 같은 모델의 같은 앞부분에만 붙는다. 그런데 라우터는 요청마다 가장 싼 모델을 고르려 한다. 요청이 모델 사이를 오가면 각 모델에서 캐시가 쌓이지 못하고, cache write 프리미엄만 반복해서 낸다.

라우팅 방식캐시 상태
요청마다 가장 싼 모델앞부분이 같아도 모델이 갈려 적중이 나지 않는다
같은 앞부분은 같은 모델로 고정적중이 쌓이지만 그 모델이 그 요청에 최선이 아닐 수 있다

그래서 토큰 단가만 보고 고른 "가장 싼 모델" 이 실제로 가장 싼 선택이 아닐 수 있다. 캐시가 이미 붙어 있는 비싼 모델이 더 싸게 끝나는 구간이 있다. DigitalOcean 은 자사 inference router 를 소개하며 가장 싼 모델이 가장 싸지 않은 이유를 이 캐시 문제로 설명한다.

정책에 넣으려면 값을 셋 알아야 한다.

  • 그 앞부분이 앞으로 몇 번 더 올 것으로 보는지
  • 각 후보 모델의 cache read 단가와 기본 입력 단가
  • 그 제공자의 캐시 유효 시간

앞의 것은 예측이므로 정확할 수 없다. 그래서 실무에서는 요청 성격으로 대신 판단한다. system prompt 가 긴 서비스, 같은 문서를 여러 질문이 참조하는 서비스는 앞부분 재사용이 많은 쪽으로 본다.

자체 호스팅 모델을 쓰면 같은 문제가 한 층 아래에서 다시 나온다. 인스턴스가 여럿일 때 어느 인스턴스로 보내는지가 prefix cache 적중을 바꾼다. 그 계층은 캐시를 보고 인스턴스를 고른다에서 다룬다.

정리

  • 라우터를 붙이면 유효 컨텍스트 한도가 라우팅 대상 중 가장 작은 모델에 맞춰진다. 싼 모델을 후보에 넣는 판단과 긴 입력을 받는 요건이 충돌한다.
  • 라우터는 요청의 일부만 읽고 판단한다. 무엇을 판단에 쓰지 않는지를 적어 두지 않으면 잘못된 라우팅을 지표로 늦게 발견한다.
  • 대상 목록을 고정하고 fallback 목록을 그것으로 겸하면, 검토하지 않은 모델로 요청이 넘어가는 것을 하나의 설정으로 막는다.
  • 요청 단위 기록과 집계 지표를 둘 다 남긴다. 앞의 것은 특정 요청을 되짚고, 뒤의 것은 문제가 나고 있다는 사실을 알아챈다.
  • 캐시는 같은 모델의 같은 앞부분에만 붙는다. 요청마다 가장 싼 모델을 고르면 cache write 프리미엄만 반복해서 낸다.

다음 편

다음 글에서는 자체 호스팅 모델이 여러 대일 때 어느 인스턴스로 보낼지를 다룬다. 위 계층의 캐시와 아래 계층의 캐시가 종류가 다르고, prefix aware 와 KV cache aware 가 판단하는 것이 다르다. 인스턴스를 늘리거나 줄일 때 적중이 무너지는 문제도 그 글에서 다룬다.

캐시를 보고 인스턴스를 고른다로 이어진다.

참고 링크

  • Azure AI Foundry model router
  • LiteLLM fallbacks
  • LiteLLM Logging
  • LiteLLM proxy 설정 값
  • prompt caching 가격 구조
  • DigitalOcean. cache-aware inference router
  • Uber GenAI Gateway
on this page
  • 01컨텍스트 한도가 가장 작은 모델에 맞춰진다
  • 02판단이 보는 것과 받는 것이 다르다
  • 03라우팅 대상 목록을 고정한다
  • 04결정을 사후에 되짚을 수 있어야 한다
  • 05자유롭게 고르는 것이 캐시 적중을 떨어뜨린다
  • 06정리
  • 07다음 편
  • 08참고 링크
tags
#Model Router#LLM 서빙#운영#study📚 Model Router
← PREVIOUS어느 모델로 보낼지 무엇으로 정하는가NEXT →캐시를 보고 인스턴스를 고른다

이런 글도

  • mlops

    같은 문제를 회사마다 다른 층에서 푼다

    2026.09.08
  • mlops

    첫 토큰이 나간 뒤에는 재시도할 수 없다

    2026.09.08
  • mlops

    캐시를 보고 인스턴스를 고른다

    2026.09.08
  • mlops

    어느 모델로 보낼지 무엇으로 정하는가

    2026.09.08

댓글 (0)