[LLMSO] LLM Serving은 어디까지 똑똑해지는가: Semantic Routing부터 RL 서빙까지

xgro·어제

LLMSO

목록 보기
10/10
post-thumbnail

📌 Notice

LLM Serving and Optimization Study (=LLMSO)
직접 실습을 통해 LLM 서비스 제공 및 최적화에 대한 지식에 대한 학습 및 실습 내용을 정리한 블로그입니다.

CloudNet@에서 스터디를 진행하고 있습니다.

EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.


Model을 실행하던 Engine이 결정을 내리기 시작했다

앞선 장에서는 GPU Memory를 나누고, Request를 Batch로 묶고, 여러 GPU에 Model을 배치하는 방법을 살펴봤습니다.
그때 Serving Engine의 중심 질문은 대체로 하나였습니다.
주어진 Model을 어떻게 더 빠르고 안정적으로 실행할 것인가였습니다.

CH10에서는 질문의 범위가 넓어집니다.
들어온 요청을 Cache로 끝낼지, 어느 Model과 Tool로 보낼지, Image 전처리를 어디에서 할지,
Edge와 Cloud 중 어디에서 실행할지를 Serving System이 판단하기 시작합니다.

Multi-LoRA에서는 어떤 Adapter를 활성화할지가 Scheduling 단위가 됩니다.
Reinforcement Learning에서는 Serving 결과가 다시 Training Data가 되고,
어떤 Actor Weight로 만든 Sample인지까지 추적해야 합니다.

이 기능들을 하나의 최신 기술이라는 이름으로 묶으면 공통점이 잘 보이지 않습니다.
공통점은 결정의 범위가 넓어진다는 데 있습니다.
결정이 늘어날수록 잘못된 선택의 비용과 설명해야 할 Evidence도 함께 늘어납니다.

Semantic Cache의 False Hit는 느린 Miss보다 위험할 수 있습니다.
Gateway의 Retry는 성공률을 높이는 대신 중복 비용과 Side effect를 만들 수 있습니다.
Edge Offloading은 Latency를 줄이면서도 Battery와 Thermal 상태를 악화시킬 수 있습니다.

그래서 이 장은 여러 주제를 나열하는 순서로 읽지 않습니다.
각 기술이 어떤 결정을 새로 맡는지,
그 결정이 틀렸을 때 무엇이 깨지는지를 따라갑니다.

처음 접하는 내용이라면 고객지원 Assistant에 요청 하나가 들어왔다고 생각하면 됩니다.

"사진 속 상품이 주문한 색상과 다른데 교환할 수 있나요?"

먼저 이전 답변을 재사용해도 되는지 판단합니다.
재사용할 수 없다면 어느 Model과 Tool이 답할지 정하고,
Gateway가 사용자 권한과 예산을 확인합니다.

사진이 들어왔으므로 Text뿐 아니라 Image 전처리와 Vision Model도 실행해야 합니다.
느리다면 Queue와 CPU, GPU 중 어디에서 시간이 쓰였는지 좁혀야 합니다.
Device에서 먼저 처리할지 Cloud로 보낼지도 선택합니다.

쇼핑몰이나 Tenant마다 다른 말투와 정책이 필요하면 LoRA Adapter가 달라질 수 있습니다.
나중에 사용자 Feedback으로 Model을 개선한다면,
어떤 Actor Version이 만든 답변인지 RL Training까지 이어서 추적해야 합니다.

Step 01부터 이 요청이 통과하는 결정 지점을 하나씩 넓혀 갑니다.
앞의 Step에서 배운 결정이 다음 Step의 입력이 되므로,
처음에는 제품 이름보다 요청이 어디로 이동하는지를 따라가면 됩니다.



🚀 Step 01. 의미가 비슷하면 같은 답을 돌려줘도 될까?

문자열 Cache는 질문이 정확히 같을 때 강합니다.
하지만 사용자는 같은 의도를 여러 문장으로 표현합니다.
비밀번호를 잊었어요로그인 정보를 재설정하고 싶어요는 Token이 달라도 같은 업무로 이어질 수 있습니다.

Semantic Cache는 Prompt를 Embedding Vector로 바꾸고,
이전에 저장한 Prompt와 가까운 항목을 Vector Search로 찾습니다.
Similarity가 Threshold를 넘으면 Model을 다시 호출하지 않고 저장된 응답을 반환할 수 있습니다.

여기서 Prefix Cache와 Semantic Cache를 구분해야 합니다.
Prefix Cache는 동일한 Token Prefix의 KV 계산을 재사용하지만,
Semantic Cache는 표현이 다른 요청에도 이전의 최종 응답을 재사용할 수 있습니다.
둘 다 Cache라는 이름을 쓰지만 잘못된 Hit가 만드는 피해는 전혀 다릅니다.

Exact Cache    : 같은 Key이면 같은 결과를 반환
Prefix Cache   : 같은 Token Prefix의 계산을 재사용하고 답변은 새로 생성
Semantic Cache : 의미가 가깝다고 판단하면 이전 답변 자체를 재사용

Embedding은 문장을 고정 길이 Vector로 바꾸고,
검색 단계에서는 주로 Query Vector와 저장 Vector 사이의 유사도를 계산합니다.
Cosine similarity를 쓴다면 값은 다음 관계에서 나옵니다.

cosine_similarity(q, d) = (q · d) / (||q|| × ||d||)

이 값은 두 문장의 각도가 얼마나 가까운지를 나타낼 뿐,
둘에게 같은 답을 줘도 안전하다는 보증은 아닙니다.
어떤 Embedding Model을 썼는지와 Domain에 따라 같은 Threshold의 의미도 달라집니다.

여기서 중요한 질문은 얼마나 비슷한가보다 같은 답을 재사용해도 되는가입니다.
두 문장이 가까워도 Tenant, 사용자 권한, 지역, 시간, Model Version이 다르면 답의 유효 범위가 달라질 수 있습니다.

예를 들어 배송 상태나 계좌 잔액처럼 계속 바뀌는 답은 의미가 같다는 이유만으로 재사용할 수 없습니다.
Tool 실행 결과가 포함된 응답도 같은 문제가 있습니다.
Cache Key와 Policy에는 적어도 Tenant, ACL, Model·Prompt Version, Tool 결과 Version, TTL과 Invalidation 조건이 필요합니다.

Threshold를 낮추면 Hit는 늘지만 False Hit도 늘어납니다.
False Miss는 Model을 한 번 더 호출하는 비용을 만들지만,
False Hit는 틀리거나 권한 밖의 답을 즉시 반환할 수 있습니다.

두 오류의 비용이 비대칭이라는 점이 Threshold를 정하는 출발점입니다.
FAQ처럼 틀리면 Model을 다시 호출하면 되는 영역과,
결제·권한·의료 안내처럼 잘못된 재사용이 큰 피해를 만드는 영역을 같은 Threshold로 운영하면 안 됩니다.

현재 로컬 계산 Harness에는 설명용 Sample 8개가 들어 있습니다.
Threshold를 0.90으로 두면 Precision 1.00, Recall 0.50이고,
0.80으로 내리면 Precision과 Recall이 모두 0.75가 됩니다.

이 숫자는 Embedding을 실행한 결과가 아니라 Threshold의 교환 관계를 보여 주는 합성 예시입니다.
실제 Threshold는 서비스 Prompt를 사람이 동등·비동등으로 Labeling한 Evaluation set에서 다시 구해야 합니다.

그래서 Semantic Cache를 평가할 때는 Hit rate만 보면 안 됩니다.
Precision, Recall, False Hit의 심각도, Cache Lookup latency, 실제로 피한 Model 비용을 함께 봅니다.

처음 보는 용어라면 Precision과 Recall은 다음처럼 읽으면 됩니다.
Precision은 Cache가 재사용 가능하다고 판단한 응답 중 실제로 안전했던 비율입니다.
Recall은 실제로 재사용할 수 있었던 응답 중 Cache가 찾아낸 비율입니다.

Precision이 낮으면 위험한 False Hit가 늘고,
Recall이 낮으면 안전하게 아낄 수 있었던 LLM 호출을 놓칩니다.
정확성과 절감률 사이의 균형을 Threshold로 조절하는 셈입니다.

Cache Hit 전에 통과해야 할 경계

Similarity Search 결과가 나왔다고 바로 응답을 반환하지 않습니다.
검색 결과와 현재 요청이 다음 범위를 공유하는지 먼저 확인합니다.

경계다르면 생기는 문제
Tenant와 사용자 권한다른 조직이나 역할의 응답 노출
Model·Prompt revision새로운 정책이 이전 답변에 반영되지 않음
Tool과 Data version오래된 조회 결과를 현재 상태처럼 반환
Locale과 지역 정책언어·규정·가격 범위가 다른 답변 재사용
TTL과 Invalidation이미 변경된 사실을 계속 반환

여기서 Tenant는 같은 System을 나눠 쓰는 고객이나 조직의 경계입니다.
ACL은 어떤 사용자가 어떤 Data를 읽거나 실행할 수 있는지 나타내는 Access Control List이고,
TTL은 Cache Entry를 유효하게 둘 시간입니다.
Invalidation은 시간이 남아 있더라도 원본 Data나 정책이 바뀌었을 때 Entry를 무효화하는 과정입니다.

Cache Entry에는 원문 Prompt와 답변만 넣는 것이 아니라,
이 경계를 판별할 Metadata와 답변이 만들어진 시각,
검색에 사용한 Embedding Model revision을 함께 남겨야 합니다.

개인화가 필요한 요청과 Tool Side effect가 있는 요청은 기본적으로 Cache 대상에서 제외하는 편이 안전합니다.
주문을 취소해 줘 같은 요청을 이전의 성공 응답으로 대체하면 실제 취소는 일어나지 않습니다.

도입 초기에는 Hit 결과를 사용자에게 바로 돌려주지 않고 Shadow mode로 비교할 수 있습니다.
Router가 선택한 Cache Entry와 실제 Model 응답을 함께 평가하면,
False Hit가 어떤 Prompt 집단에서 생기는지 확인한 뒤 적용 범위를 좁힐 수 있습니다.

Semantic Routing은 Cache Miss 다음의 경로도 바꿉니다.
가벼운 질문을 작은 Model로 보내고,
복잡한 추론이나 특정 Domain이 필요한 요청만 더 비싼 Model로 보낼 수 있습니다.

이때 Router는 하나의 Label만 출력하는 분류기로 끝나지 않습니다.
선택한 Endpoint와 이유,
적용한 Policy version,
후보별 Score와 Fallback 가능 여부를 다음 단계가 읽을 수 있어야 합니다.

예를 들어 simple, domain, reasoning 세 Route를 둔다면 Offline 정확도만 보지 않습니다.
Route별 요청 비율과 비용,
품질 Gate 통과율,
Router 자체의 p95 latency,
잘못된 고가 Route와 잘못된 저가 Route의 비율을 함께 봅니다.

작은 Model로 너무 공격적으로 보내면 비용은 줄지만 품질이 떨어질 수 있습니다.
반대로 애매한 요청을 모두 큰 Model로 보내면 Router를 추가한 목적이 사라집니다.
따라서 Router도 Model과 마찬가지로 Quality와 Cost가 함께 있는 운영 대상입니다.

Embedding Search로 Tool 후보를 줄이는 방식도 같은 흐름입니다.
Tool Schema가 많을 때 관련 후보만 Prompt에 넣으면 Context와 선택 부담을 줄일 수 있습니다.
하지만 후보 검색은 Authorization이 아닙니다.
실제 Tool 호출 직전에는 사용자와 Resource에 대한 권한을 다시 검사해야 합니다.

PII Masking 역시 Cache와 Log 앞의 중요한 단계가 될 수 있습니다.
다만 Masking Model이나 정규식이 모든 민감정보를 잡는다고 가정해서는 안 됩니다.
원문 저장 여부, 복원 Mapping의 보관 위치, Embedding과 Trace의 Retention까지 하나의 Data flow로 확인해야 합니다.

PII는 이름과 주소,
전화번호처럼 개인을 식별할 수 있는 정보입니다.
NER은 문장에서 사람·장소·조직 같은 Entity의 위치와 종류를 찾는 방식이며,
정규식만으로 잡기 어려운 PII 후보를 찾는 데 사용할 수 있습니다.

현재 vLLM Semantic Router 프로젝트는 Route, Signal, Policy를 조합하는 별도 Layer를 제공합니다.
특정 Model이나 정확도 향상을 자동으로 보장하는 기능으로 읽기보다,
Route 품질과 Policy 실패를 따로 측정해야 하는 구성 요소로 보는 편이 안전합니다.

Production Stack과 연결하는 공식 가이드는 현재 Preview로 표시되어 있습니다.
Helm 값과 API 경계가 바뀔 수 있으므로,
발행이나 실습 시점에는 고정한 Release의 문서를 다시 확인해야 합니다.

실제 요청 Trace에는 최소한 다음 연결이 남아야 합니다.

request_id
→ masking policy와 결과 상태
→ embedding·router revision
→ cache candidate와 최종 hit/miss 이유
→ 선택한 endpoint·adapter·tool 후보
→ 최종 latency·token·cost·quality 결과

이 연결이 없으면 Model 비용이 줄었다는 사실은 볼 수 있어도,
어떤 판단이 품질 저하나 정보 노출을 만들었는지는 되짚을 수 없습니다.

Semantic Routing의 핵심은 큰 Model을 덜 호출하는 데만 있지 않습니다.
같은 요청이라도 어떤 Evidence를 근거로 어느 경로를 선택했는지 남기는 것까지가 Serving의 책임이 됩니다.

이제 의미에 따라 경로를 골랐다고 해도,
그 요청을 실제로 허용하고 비용과 실패를 통제하는 Layer가 필요합니다.
다음 Step에서는 이 역할을 맡는 AI Gateway를 따라갑니다.

참고한 1차 자료



🚀 Step 02. Gateway는 Model 호출 앞에서 무엇을 통제할까?

Semantic Router가 요청의 의미를 보고 경로를 고른다면,
AI Gateway는 그 선택이 운영 정책 안에서 실행되도록 감쌉니다.
Client가 여러 Provider의 차이를 직접 알지 않게 API 형식을 정규화하고,
Key와 Budget, Rate limit, Timeout, Retry, Fallback을 한곳에서 적용합니다.

LiteLLM은 SDK와 Proxy 방식으로 여러 Model Provider를 공통 Interface 뒤에 둘 수 있는 대표적인 Open Source Gateway입니다.
여기서는 제품 기능을 전부 익히기보다 한 요청의 생명주기를 보는 편이 중요합니다.

Gateway와 Router, Load balancer도 같은 말로 뭉개지 않습니다.
Load balancer는 같은 역할의 Replica 사이에서 부하를 나누는 데 가깝고,
Semantic Router는 요청 의미에 따라 Model 종류를 고릅니다.
Gateway는 그 선택을 인증과 예산,
호출 제한과 감사 기록 같은 운영 정책 안에서 실행합니다.

한 Component가 세 역할을 모두 구현할 수는 있습니다.
그래도 장애를 분석할 때는 왜 이 Model을 골랐는가,
왜 이 Replica로 보냈는가,
왜 이 호출을 허용했는가를 서로 다른 결정으로 추적해야 합니다.

요청이 들어오면 먼저 누가 어떤 Budget으로 호출하는지 확인합니다.
그다음 RPM, TPM, 동시성 제한을 적용하고,
사용 가능한 Deployment 가운데 정책에 맞는 Endpoint를 고릅니다.

RPM과 TPM은 같은 제한이 아닙니다.
짧은 요청을 아주 많이 보내면 RPM을 먼저 소진하고,
긴 Context나 긴 답변은 요청 수가 적어도 TPM을 먼저 소진할 수 있습니다.
Streaming 연결이 오래 유지되면 별도의 동시성 제한도 필요합니다.

RPM은 1분 동안 허용하는 Request 수이고,
TPM은 1분 동안 처리하는 Token 수입니다.
동시성은 같은 순간에 실행 중인 요청 수이므로,
세 값은 서로 다른 형태의 과부하를 막습니다.

따라서 Rate limit 상태에는 적어도 다음 축이 필요합니다.

  • 사용자와 Tenant별 Request budget
  • Input과 Output Token budget
  • Provider·Deployment별 제한
  • 동시에 실행하거나 대기할 수 있는 요청 수
  • 일·월 단위 비용 상한

요청을 Queue에 넣을 것인지 즉시 거절할 것인지도 정책입니다.
무한 Queue는 성공률을 잠시 높여 보이게 하지만 Deadline이 지난 요청과 Memory 점유를 쌓습니다.

응답 뒤에는 사용량과 비용, 선택한 Deployment, Retry와 Fallback 이유를 기록합니다.
이 정보가 하나의 Request ID로 연결되어야 성공했다보다 더 구체적인 질문에 답할 수 있습니다.

Retry와 Fallback은 신뢰성을 높여 보이지만 무료가 아닙니다.
한 Provider에서 Timeout이 난 뒤 다른 Provider로 넘어가면 Tail latency와 Token 비용이 함께 늘어납니다.
첫 요청이 실제로 처리됐지만 응답만 잃은 경우에는 같은 작업이 두 번 실행될 수도 있습니다.

Retry에는 시간과 비용 예산이 필요하다

Client가 허용한 전체 Deadline이 10초인데,
첫 Endpoint의 Timeout을 8초로 두고 같은 요청을 두 번 Retry하면 Fallback이 실행될 시간은 남지 않습니다.

total deadline
>= attempt 1 timeout
 + retry backoff
 + attempt 2 timeout
 + fallback reserve
 + response streaming margin

시도 횟수가 늘면 부하도 증폭됩니다.
실패한 요청 100개를 각각 두 번 더 시도하면 Backend에는 최대 300번의 호출이 생길 수 있습니다.
Provider 장애 때 모든 Client가 동시에 Retry하면 원래 장애보다 큰 Retry storm을 만들 수도 있습니다.

Jitter가 있는 Backoff와 전체 Deadline,
Deployment별 Cooldown이나 Circuit breaking이 함께 필요한 이유입니다.
LiteLLM 문서도 오류 종류별 Retry policy와 Deployment별 Cooldown을 별도로 구성할 수 있게 설명합니다.

Backoff는 재시도 사이에 기다리는 시간이고,
Jitter는 모든 Client가 같은 순간 다시 몰리지 않도록 대기 시간을 조금씩 다르게 만드는 값입니다.
Circuit breaker는 실패가 반복되는 Endpoint로 요청을 잠시 보내지 않는 장치입니다.
Cooldown이 끝난 뒤 작은 Traffic으로 회복을 확인하고 정상 Pool에 돌려놓습니다.

그래서 모든 오류를 같은 방식으로 Retry하면 안 됩니다.
재시도 가능한 오류, 최대 시도 횟수, 전체 Deadline, Backoff, 비용 상한을 구분합니다.
Tool이나 외부 System에 Side effect가 있는 요청은 Idempotency Key나 중복 방지 계약이 없으면 자동 Retry하지 않습니다.

Idempotency Key는 같은 업무 요청이 다시 도착했는지 식별하는 값입니다.
결제나 주문 취소처럼 한 번만 실행되어야 하는 작업은,
같은 Key가 다시 오면 이전 실행 결과를 돌려주거나 중복 실행을 거절할 수 있어야 합니다.

인증 실패나 잘못된 Request 형식은 다른 Endpoint로 보내도 성공할 가능성이 낮습니다.
반면 일시적인 Rate limit과 연결 실패는 Backoff나 다른 Deployment가 의미가 있을 수 있습니다.
오류 Code만 보지 말고 요청이 Backend에 도달했을 가능성과 Side effect 여부도 판단해야 합니다.

Fallback Model도 단순한 대체품이 아닙니다.
Context 길이와 Tool Calling, Safety Policy, Data residency, 출력 형식이 달라질 수 있습니다.
Gateway가 200 응답을 만들었더라도 원래 요청의 품질 계약을 지키지 못했을 수 있습니다.

Fallback 후보를 등록할 때는 다음 호환성을 미리 검사합니다.

계약확인할 내용
InputContext 길이, Image·Audio 지원, Chat template
OutputJSON Schema, Tool calling, Streaming event 형식
PolicySafety와 금칙어, 지역·Tenant별 허용 범위
DataProvider Region, Retention, 학습 사용 여부
CostInput·Output 단가와 Retry 포함 상한

호환되지 않는 Fallback은 성공률을 높이는 것이 아니라 실패 형태를 바꿉니다.
응답 Header나 Trace에 실제로 선택된 Provider와 Model revision을 남겨야 Client와 운영자가 이를 구분할 수 있습니다.

Image URL을 Gateway가 대신 내려받아 다른 형식으로 바꾸는 기능도 같은 관점으로 봅니다.
편리하지만 Server-Side Request Forgery, 큰 Payload, Redirect, Content-Type 위장이라는 새 공격면이 생깁니다.
허용 Domain, Private·Link-local 주소 차단, 크기와 시간 제한, Credential 전달 금지가 필요합니다.

SSRF는 사용자가 준 URL을 Server가 대신 요청하게 만들어,
외부에서 접근할 수 없는 내부 주소나 Cloud Metadata에 접근시키는 공격입니다.
Image URL을 받아준다는 편의 기능이 Network 권한을 대신 사용하는 기능이 될 수 있다는 뜻입니다.

정책 적용 순서도 중요합니다.
외부 URL을 내려받거나 Embedding을 만들기 전에 인증과 크기 제한을 적용해야,
거절될 요청이 Network와 Compute를 먼저 소비하지 않습니다.
PII Masking 뒤의 원문과 Masking된 Prompt 중 무엇을 Cache key와 Audit log에 쓸지도 명시합니다.

관찰성도 Prompt와 Response를 전부 남긴다는 뜻이 아닙니다.
PII와 Secret을 제거하고,
필요한 Route·Latency·Token·오류 Metadata만 정해진 Retention으로 보관해야 합니다.

Gateway Dashboard에서는 단순 성공률보다 다음 흐름이 보여야 합니다.

  • 최초 시도와 최종 성공의 비율
  • Retry·Fallback이 추가한 p95 latency와 Token 비용
  • Deployment별 Rate limit, Timeout, Cooldown 상태
  • Policy rejection과 Budget exhaustion
  • 요청한 Model과 실제 실행한 Model의 차이

M1 Local 실습에서는 같은 Loopback Endpoint 앞에 Gateway를 붙이기 전과 후를 비교합니다.
Model 품질을 비교하는 실험이 아니라,
Gateway Hop과 Logging이 TTFT·E2E에 더한 비용과 실패 정책을 확인하는 실험입니다.

좋은 Gateway는 모든 실패를 숨겨 성공 응답으로 바꾸는 System이 아닙니다.
어떤 정책이 요청을 허용하거나 거절했고,
어떤 비용으로 어느 Endpoint가 결과를 만들었는지 설명할 수 있는 Control point입니다.

정책이 올바르게 동작해도 사용자는 여전히 느리다고 느낄 수 있습니다.
다음 Step에서는 Gateway와 Queue,
CPU와 GPU 중 어디에서 시간이 사용됐는지 Evidence를 따라 내려갑니다.

참고한 1차 자료



🚀 Step 03. 병목을 어디까지 내려가며 확인할까?

처리량이 낮거나 TTFT가 길다고 바로 CUDA Kernel을 열어보면 조사 범위만 넓어집니다.
먼저 사용자가 겪는 증상과 재현 Traffic을 고정하고,
증거가 가리키는 Layer까지 한 단계씩 내려가는 편이 빠릅니다.

첫 Layer는 Serving입니다.
Throughput, TTFT, TPOT, ITL, E2E Latency, Error와 Queue 길이를 함께 봅니다.
GPU Utilization은 단서지만 Root cause가 아닙니다.

TTFT는 요청을 보낸 뒤 첫 Token을 받을 때까지의 시간입니다.
TPOT는 첫 Token 이후 Output Token 하나를 만드는 평균 시간이고,
ITL은 Streaming 중 실제 Token이나 Chunk 사이의 간격입니다.
E2E는 요청 시작부터 마지막 응답까지의 전체 시간입니다.

이 지표를 먼저 보는 이유는 사용자가 기다린 구간과 내부 Profile을 연결하기 위해서입니다.
Kernel이 10% 빨라져도 Queue가 대부분을 차지한다면 TTFT는 거의 달라지지 않을 수 있습니다.

한 요청의 시간을 다음처럼 분해하면 조사 시작점을 잡기 쉽습니다.

E2E
= network
+ gateway·queue
+ tokenize·multimodal preprocessing
+ prefill
+ repeated decode
+ streaming·postprocess

TTFT만 길면 Queue와 Input path, Prefill을 먼저 의심하고,
첫 Token 이후만 느리면 Decode와 Memory bandwidth를 먼저 봅니다.
처리량이 낮으면서 GPU Timeline에 빈 구간이 많다면 Host가 일을 제때 공급하지 못하는지 확인합니다.

관찰된 현상먼저 확인할 증거아직 단정할 수 없는 것
Queue가 길고 GPU는 바쁨Admission, Batch budget, KV 여유특정 Kernel이 느리다는 결론
GPU에 긴 Idle gapCPU 전처리, Dispatch, I/O, 동기화GPU가 부족하다는 결론
GPU는 계속 바쁘고 TPOT가 큼Decode Kernel, HBM traffic, Batch shapeCPU 증설 효과
Memory copy가 직렬화됨Stream과 copy direction, Tensor 이동Model 연산 자체의 비효율

Nsight Systems는 CPU Thread, CUDA API, Kernel, Memory copy가 시간축에서 어떻게 겹치는지 보는 System-wide Timeline 도구입니다.
GPU 사이에 긴 Gap이 있다면 Host Dispatch, 전처리, 동기화, I/O를 의심할 수 있습니다.
GPU가 바쁘더라도 짧은 Kernel이 지나치게 많이 끊겨 있거나 Copy가 겹치지 않을 수 있습니다.

Timeline에서 보는 핵심은 가장 긴 막대 하나가 아닙니다.
CPU 작업이 끝나야 GPU가 시작하는지,
여러 Stream이 실제로 겹치는지,
Collective가 모든 Rank를 기다리게 하는지처럼 Stage 사이의 의존 관계를 봅니다.

CUDA Kernel은 GPU에서 실행되는 하나의 병렬 작업 단위입니다.
Stream은 Kernel과 Memory copy를 순서대로 제출하는 Queue에 가깝고,
조건이 맞으면 서로 다른 Stream의 작업이 겹쳐 실행될 수 있습니다.
Collective는 여러 GPU가 결과를 모으거나 교환하는 통신 연산입니다.

PyTorch Profiler는 Framework Operator에 시간을 귀속하는 데 유용합니다.
어떤 matmul, Attention, Data movement, Python 구간이 비싼지 좁혀 갑니다.
CPU와 CUDA Activity를 함께 수집할 수 있지만,
수집 범위와 Schedule을 넓힐수록 Overhead와 Trace 크기도 커집니다.

Operator 이름에 시간이 많이 잡혔다고 그 Operator만 고치면 되는 것은 아닙니다.
비동기 CUDA 호출에서는 CPU가 기록한 시간과 GPU가 실제 실행한 시간이 다를 수 있고,
동기화 지점이 앞선 여러 연산의 대기 시간을 대신 떠안을 수도 있습니다.
CPU Self time, CUDA time, 호출 횟수와 Shape를 함께 확인합니다.

Nsight Compute는 특정 CUDA Kernel의 Occupancy, Memory workload, Compute pipeline을 더 깊게 볼 때 사용합니다.
System Timeline에서 의심할 Kernel이 정해지기 전에 모든 Kernel을 자세히 Profile하면 비용만 커질 수 있습니다.

Kernel이 Compute-bound인지 Memory-bound인지도 이름만으로 판단하지 않습니다.
연산 장치가 충분히 차 있는지,
Memory throughput이 한계에 가까운지,
작은 Shape 때문에 Launch와 Scheduling 비용이 상대적으로 큰지를 Counter로 구분합니다.
Occupancy가 높다는 사실만으로 유용한 연산량이나 사용자 성능이 좋다고 볼 수도 없습니다.

Compute-bound는 계산 장치의 처리량이 한계가 된 상태이고,
Memory-bound는 계산보다 Weight와 Activation을 읽고 쓰는 속도가 한계가 된 상태입니다.
Occupancy는 GPU가 동시에 유지할 수 있는 작업 중 실제로 활성화된 비율을 이해하는 단서입니다.
어느 하나만 높다고 전체 Model Serving이 최적이라는 뜻은 아닙니다.

이 순서는 절대적인 도구 순위가 아닙니다.
현상이 Host 쪽에 명확하면 PyTorch Profiler부터 볼 수 있고,
이미 특정 Kernel regression이 재현되면 Nsight Compute로 바로 들어갈 수도 있습니다.
중요한 것은 각 도구가 답하는 질문을 구분하는 일입니다.

원본에는 전체 100ms 가운데 Attention 55ms, Matmul 25ms, LayerNorm 5ms, 기타 15ms라는 예시가 있습니다.
합계와 비율은 계산할 수 있지만 실제 실행에서 얻은 숫자는 아닙니다.
따라서 Attention을 먼저 최적화해야 한다는 결론도 예시 안에서만 성립합니다.

병목은 개선한 뒤 이동한다

Pipeline Capacity는 가장 느린 Stage의 처리율에 묶입니다.
설명용으로 CPU 전처리가 초당 18건,
GPU가 초당 30건을 처리한다면 전체 Capacity는 초당 18건입니다.

pipeline_capacity = min(stage_1_rps, stage_2_rps, ...)

CPU Worker를 늘려 전처리가 초당 36건이 되면 Capacity는 초당 30건으로 올라갑니다.
이제 병목은 GPU로 이동했으므로 CPU를 더 늘려도 처리량은 커지지 않습니다.

이 값은 로컬 계산 Harness의 설명용 가정이며 실제 Server 측정값이 아닙니다.
중요한 것은 개선 전후에 같은 Layer만 계속 보는 것이 아니라,
전체 Timeline에서 새 Critical path를 다시 찾는 과정입니다.

비동기 Queue도 병목을 제거하지는 않습니다.
짧은 Burst를 흡수하고 CPU와 GPU를 겹쳐 실행할 수는 있지만,
평균 입력률이 가장 느린 Stage의 처리율보다 높으면 Queue는 결국 계속 자랍니다.

Trace를 수집할 때 먼저 고정할 것

Profiler Run에는 다음 정보를 같이 남깁니다.

  • Model과 Runtime, Driver, Hardware
  • Prompt·Output 길이와 요청 도착 Schedule
  • Warm-up 뒤 수집한 정확한 시간 구간
  • Profiler 종류와 수집한 Counter
  • Trace Overhead를 측정한 Control run
  • Trace 파일과 Benchmark 결과를 연결하는 Run ID

긴 시간 전체를 수집하기보다 재현되는 짧은 구간을 잡습니다.
Warm-up과 Compile을 보고 싶은 Run,
Steady state 병목을 보는 Run,
부하 급증과 OOM을 보는 Run은 서로 분리합니다.

Profiler로 발견한 개선은 일반 Benchmark에서 다시 확인합니다.
Instrumentation을 끄고 같은 Model, Traffic, Warm-up, Concurrency를 재생합니다.
평균뿐 아니라 Tail latency, 실패율, 출력 품질과 비용이 함께 좋아졌을 때만 변경을 채택합니다.

M1 Air에서는 CUDA용 Nsight Systems와 Nsight Compute를 그대로 사용할 수 없습니다.
Local Track에서는 Client timing과 Activity Monitor,
macOS Instruments 같은 Host 관찰 도구로 Queue·CPU·Memory pressure를 공부할 수 있습니다.
이 결과를 CUDA Kernel 분석과 같은 Evidence로 표현하지 않는 것이 중요합니다.

Profiling은 화려한 Timeline을 만드는 일이 아닙니다.
증상에서 가설로 내려가고,
한 가지 변경 뒤 사용자 지표가 실제로 나아졌는지 돌아오는 Evidence loop입니다.

Text 요청의 경로를 이해했다면 다음에는 입력 자체가 복잡해지는 경우를 볼 수 있습니다.
Image가 추가되면 GPU Kernel 앞에 Download와 Decode,
Vision 전처리가 생기면서 방금 배운 병목 이동이 더 분명해집니다.

참고한 1차 자료



🚀 Step 04. Image 입력은 Serving 경로를 어떻게 바꿀까?

Text-only 요청에서는 Prompt Token 수와 Output 길이가 Request Shape를 많이 설명합니다.
Image가 들어오면 Image 수와 해상도, Decode 형식, Resize와 Normalize 비용도 함께 기록해야 합니다.

같은 Text Prompt라도 512×512 Image 한 장과 4K Image 여덟 장은 같은 요청이 아닙니다.
Multimodal Benchmark에서는 Request Shape를 다음과 같은 묶음으로 봅니다.

request_shape
= text input tokens
+ image count·resolution·encoded bytes
+ video frame count·sampling policy
+ processor configuration
+ expected output tokens

평균 Image 수만 남기면 큰 입력 몇 개가 만든 Tail latency와 Memory peak가 가려질 수 있습니다.
Text 길이와 Image 크기의 p50·p95·최대값을 따로 기록합니다.

Vision-Language Model은 보통 Image를 Processor로 전처리하고 Vision feature를 만듭니다.
그 Feature를 Language Model이 소비할 수 있는 표현으로 Projection한 뒤 Text 표현과 함께 처리합니다.
구체적인 Placeholder Token과 Chat Template은 Model마다 다르므로 하나의 Token ID 예시를 모든 VLM에 일반화하면 안 됩니다.

Processor는 Image 크기와 색상 값을 Model이 학습한 형식에 맞게 바꾸는 전처리기입니다.
Vision Encoder는 Image를 작은 Patch 단위로 읽어 의미를 담은 Vector로 바꾸고,
Projector는 그 Vector를 Language Model이 사용하는 표현 공간에 맞춥니다.
그 뒤에야 Text Token과 Image 표현을 함께 Attention에 넣을 수 있습니다.

요청 한 건은 대략 다음 경로를 지납니다.

URL·upload 수신
→ media fetch와 byte limit
→ image decode·resize·normalize
→ vision encoder
→ projector·multimodal embedding
→ language-model prefill
→ text decode와 streaming

Image가 외부 URL이라면 Network Download와 Redirect도 첫 Token 전 시간에 들어갑니다.
그래서 Multimodal TTFT는 Language Model의 Prefill 시간보다 훨씬 넓은 지표입니다.

새로운 병목은 Model 앞에서 생길 수 있습니다.
Image Decode, Resize, Crop, Color conversion, Tensor 변환이 요청마다 직렬로 실행되면 CPU가 GPU에 일을 공급하지 못합니다.
GPU가 빠를수록 오히려 이 공급 부족이 더 선명하게 보일 수 있습니다.

Vision Encoder도 항상 Language Model과 같은 병목 특성을 가지지 않습니다.
Image 해상도와 Patch 수에 따라 Vision 쪽 계산과 Activation이 커지고,
Text Decode가 길면 이후에는 다시 Language Model의 Decode가 지배할 수 있습니다.
한 요청 안에서도 Critical path가 Stage마다 이동합니다.

이때 GPU Utilization이 낮다는 이유만으로 Batch size를 늘리면 문제가 해결되지 않습니다.
CPU Queue가 더 길어지고 Memory pressure만 커질 수도 있습니다.
전처리 Stage의 처리율과 Queue time, GPU idle gap을 함께 확인해야 합니다.

vLLM V1 문서는 API Server와 Input processing을 Engine Core에서 분리한 구조를 설명합니다.
핵심은 CPU 쪽 전처리와 요청 처리가 GPU Scheduling loop를 직접 막지 않도록 경계를 만드는 데 있습니다.
구현 세부와 성능 효과는 Version과 Workload에 따라 달라지므로,
V1이면 GPU가 항상 완전히 사용된다고 말할 수는 없습니다.

현재 Architecture 문서에서는 API Server가 HTTP와 Tokenization,
Multimodal Data loading을 처리하고 Engine Core가 Scheduling과 KV Cache를 맡는 구조를 설명합니다.
이 분리는 CPU 작업과 GPU Scheduling의 실패 경계를 나누지만,
두 Process 사이 Queue와 Serialization이라는 새 Handoff 비용도 만듭니다.

비동기 분리는 병목을 없애지 않습니다.
CPU Capacity가 Request rate보다 작으면 Queue는 계속 자랍니다.
Worker 수를 늘리거나 Input 크기를 제한하고,
Backpressure와 Timeout으로 무한 대기를 막아야 합니다.

Backpressure는 뒤 Stage가 감당할 수 없을 때 앞 Stage가 요청을 계속 밀어 넣지 못하게 제한하는 방식입니다.
Queue 길이 제한과 Admission rejection,
Client의 Retry-After 응답이 여기에 포함될 수 있습니다.

Queue는 하나보다 Stage별로 구분해서 보는 편이 좋습니다.

  • Download 대기와 실패
  • Decode·Resize Worker 대기
  • Vision Encoder 대기
  • Language Model Scheduler 대기
  • Streaming 중단과 Client disconnect

모든 대기를 TTFT가 길다로만 보면 CPU Worker를 늘릴지,
GPU를 늘릴지,
큰 Image를 거절할지 결정할 수 없습니다.

전처리 Capacity가 초당 18건이고 GPU가 초당 30건을 받을 수 있다면 전체는 초당 18건에 묶입니다.
CPU Worker를 늘려 전처리가 초당 36건이 되면 GPU의 초당 30건이 새 병목이 됩니다.
이는 설명용 계산이며 실제 vLLM 결과가 아닙니다.

Multimodal Cache도 Text Cache보다 Key가 복잡합니다.
원본 Image hash뿐 아니라 Resize policy, Processor와 Model revision, Prompt Template이 바뀌면 Feature가 달라질 수 있습니다.
Image URL만 Key로 쓰면 내용 변경과 접근 권한을 놓칠 수 있습니다.

Cache 대상도 나눕니다.
Download한 원본 Byte,
Decode된 Image,
Processor 결과,
Vision feature는 크기와 재사용 조건이 서로 다릅니다.
어느 Layer를 Cache했는지 알아야 줄어든 시간과 Memory 비용을 설명할 수 있습니다.

Tenant가 다른 Image feature를 공유하지 않도록 Access boundary를 Key에 포함합니다.
Cache eviction 뒤 Memory가 실제로 반환되는지와,
Processor revision 변경 시 이전 Entry가 무효화되는지도 확인합니다.

외부 Image 입력은 성능 문제이면서 보안 문제입니다.
Private·Link-local 주소를 통한 SSRF,
압축 해제 뒤 크기가 폭증하는 Image,
위장된 Content-Type과 지나치게 긴 Download를 입력 경계에서 막아야 합니다.

Streaming도 두 구간으로 나눠 봅니다.
첫 Token 이전에는 Download와 Decode, Vision Encoder, Prefill이 들어갈 수 있습니다.
첫 Token 이후에는 Text Decode가 이어집니다.
TTFT 하나만 보고 어느 Stage가 느린지 확정하지 않는 이유입니다.

측정 표에는 전체 TTFT와 함께 Media download,
Processor queue·service time,
Vision Encoder time,
Language Model queue와 Decode 지표를 넣습니다.
오류도 지원하지 않는 Format, 크기 제한, Download timeout, OOM처럼 Stage별로 나눕니다.

M1 Air에서는 작은 Vision Model과 제한된 Image로 이 경로를 학습할 수 있습니다.
다만 Unified Memory를 CPU와 GPU가 공유하므로,
Image Buffer와 Model Weight가 함께 Memory pressure와 Swap을 만드는지 봐야 합니다.
작은 Local 성공을 대규모 GPU Multimodal Capacity로 일반화하지 않습니다.

Multimodal Serving에서 배워야 할 핵심은 Image도 Token처럼 처리된다는 비유가 아닙니다.
입력 Modal이 늘어나면 Critical path와 Capacity를 결정하는 Resource도 바뀔 수 있다는 점입니다.

Image 처리를 Server 안에서 이해했다면 다음 질문은 실행 위치입니다.
사진을 Device에서 먼저 처리하면 Network와 개인정보 노출을 줄일 수 있지만,
Battery와 발열이라는 새로운 제약을 만나게 됩니다.
다음 Step에서는 이 선택을 Edge와 Cloud 사이의 정책으로 확장합니다.

참고한 1차 자료



🚀 Step 05. Edge와 Cloud 중 어디에서 실행할까?

Edge AI는 Cloud Model을 작게 만들어 Device에 넣는 문제만은 아닙니다.
응답 시간, Data locality, Network 상태, Battery, 발열이라는 제약 안에서 실행 위치를 계속 선택하는 문제입니다.

Edge는 사용자와 Sensor 가까이에 있는 Phone,
Laptop,
Camera,
Vehicle 같은 실행 환경을 뜻합니다.
Data locality는 원본 Data를 만들어진 위치 가까이에서 처리해 불필요한 이동을 줄이는 성질입니다.

처음부터 Local이 빠르다 또는 Cloud가 정확하다고 결론 내리지 않습니다.
요청마다 지켜야 할 Deadline과 전송 가능한 Data,
필요한 품질,
Device의 현재 상태를 먼저 계약으로 만듭니다.

Local 실행이 필요한 가장 분명한 이유는 Network 왕복을 기다릴 수 없는 경우입니다.
Robot 제어와 실시간 Camera 처리처럼 Deadline이 짧은 작업은 연결 상태에 결과가 좌우되면 안 됩니다.
다만 특정 Millisecond 수치를 모든 Network와 Application의 기준으로 사용할 수는 없습니다.

Cloud 경로의 시간은 Model 연산 하나로 끝나지 않습니다.

cloud_e2e
= local preprocess
+ upload
+ network RTT
+ gateway·queue
+ cloud inference
+ download·postprocess

local_e2e
= local preprocess
+ on-device inference
+ local postprocess

설명용 계산에서는 Local이 42ms이고,
보통 Network의 Cloud 경로가 11 + 24 + 18 = 53ms입니다.
Network가 좋아져 Upload 4ms와 왕복 8ms가 되면 Cloud는 30ms로 더 빨라집니다.

이 숫자는 실제 M1이나 Mobile Device 측정이 아닙니다.
Network 조건 하나가 실행 위치 결정을 뒤집을 수 있다는 민감도 예시입니다.

민감한 Raw Data를 Device 밖으로 보내지 않는 것도 중요한 이유입니다.
하지만 On-device라는 위치만으로 Privacy나 Compliance가 끝나지는 않습니다.
Local Log, Crash dump, Telemetry, Backup과 Cloud Sync가 원본을 다시 내보낼 수 있습니다.

Feature만 Cloud로 보내는 분할 실행도 자동으로 안전하지 않습니다.
중간 Embedding이나 Activation에서 원본 속성을 추론할 가능성과,
전송 구간의 인증·암호화·Retention을 별도로 검토해야 합니다.

실행 형태는 크게 다음처럼 나눌 수 있습니다.

  • 모든 Stage를 Device에서 실행
  • 작은 Local Model로 먼저 답하고 어려운 요청만 Cloud로 승격
  • 전처리·Feature extraction은 Local, 큰 Model은 Cloud
  • 연결이 없을 때 기능을 줄인 Local fallback 사용

각 방식은 Latency뿐 아니라 품질 일관성과 Version 관리 방법도 다릅니다.

Edge Hardware는 Peak 처리량만 볼 수 없습니다.
팬이 없는 Device에서는 지속 부하가 온도를 올리고 Clock을 낮출 수 있습니다.
짧은 Benchmark의 최고 속도보다 일정 시간 동안 유지되는 Latency와 Power, Thermal throttling을 함께 측정합니다.

처음 30초의 p50 latency가 좋아도 15분 뒤 Clock이 내려가고 p95가 커질 수 있습니다.
Cold start,
Steady state,
Thermal saturation,
회복 구간을 나눠 기록합니다.
Battery 잔량과 충전 여부도 같은 Run 안에서 고정합니다.

CPU, NPU, GPU는 잘하는 일이 다릅니다.
분기와 Memory 조작이 많은 전처리는 CPU가,
지원되는 정밀도의 Tensor 연산은 NPU가,
Rendering이나 일부 범용 Parallel 작업은 GPU가 맡을 수 있습니다.

NPU의 높은 TOPS 숫자가 현재 Model의 성능을 보장하지는 않습니다.
지원하지 않는 Operator가 CPU로 Fallback되거나,
Stage 사이 Tensor copy가 많으면 Accelerator의 이점이 줄어듭니다.
Compile report와 Runtime trace에서 실제로 어느 Operator가 어디서 실행됐는지 확인합니다.

NPU는 Neural Network 연산에 특화된 저전력 Accelerator이고,
TOPS는 초당 수행할 수 있는 연산량을 나타내는 이론적 지표입니다.
실제 성능은 Model의 정밀도와 Operator 지원,
Memory bandwidth에 따라 달라지므로 서로 다른 조건의 TOPS를 그대로 비교하지 않습니다.

이 배치는 고정 공식이 아닙니다.
Operator 지원 범위와 Memory copy, 동기화 비용 때문에 한 Accelerator로 옮긴 결과가 더 느릴 수도 있습니다.
Runtime이 Graph를 나눌 때는 Stage별 시간뿐 아니라 경계 사이의 Data 이동을 확인합니다.

Thermal-aware Scheduling은 성능을 잃는 실패가 아니라 안정성을 지키는 선택일 수 있습니다.
온도가 임계치에 가까워지면 작은 Model로 바꾸고,
Frame rate나 Sampling 빈도를 낮추며,
필요하면 일부 작업을 Cloud로 보냅니다.

온도 하나만 기준으로 경로를 계속 왕복하면 오히려 불안정해집니다.
진입과 복귀 Threshold를 다르게 두는 Hysteresis,
최소 유지 시간,
최근 Network와 Battery 상태를 함께 보는 정책이 필요합니다.

예를 들어 85도에서 저품질 Mode로 들어갔다면,
84도로 내려오자마자 원래 Mode로 복귀하지 않고 75도 아래에서 일정 시간 유지될 때 복귀할 수 있습니다.
이처럼 Hysteresis는 경계 근처에서 실행 경로가 계속 흔들리는 현상을 줄입니다.

Adaptive Offloading은 매 요청에서 Latency와 Privacy, Network, Battery, Thermal 상태를 평가합니다.
Cloud가 더 정확하더라도 Raw Data 전송이 금지된 요청은 Local 경로만 허용할 수 있습니다.
반대로 큰 Model이 필요한 요청은 연결과 Deadline이 허용할 때 Cloud로 보낼 수 있습니다.

정책 결과는 다음 정도의 Evidence로 남깁니다.

request_id
decision = local | cloud | degraded
reason = privacy | deadline | network | thermal | quality
device_state = battery·temperature·memory pressure
model_revision과 runtime
observed latency·energy·quality result

이 기록이 있어야 Cloud 사용률이 떨어진 이유가 비용 정책인지,
Network 장애인지,
Thermal guardrail인지 설명할 수 있습니다.

M1 Air 16GB는 Mobile NPU 환경을 그대로 대표하지는 않습니다.
하지만 Local Endpoint와 Cloud 모의 Endpoint 사이의 경로 선택,
Unified Memory pressure,
지속 실행 전후 Latency 변화는 개인 Laptop에서도 학습할 수 있습니다.
Battery와 발열 결과는 전원 상태와 실내 조건을 함께 남깁니다.

따라서 Edge와 Cloud는 경쟁하는 두 배포 방식이 아닙니다.
정책과 현재 상태에 따라 역할을 나누는 하나의 Serving System이며,
선택한 경로와 품질 단계가 관찰 가능해야 합니다.

최종 선택은 평균 Latency 하나가 아니라 Deadline 준수율,
품질 Gate,
전송 금지 위반 0건,
지속 성능과 Energy budget을 함께 통과해야 합니다.

실행 위치를 나눴다면 같은 Base Model을 여러 고객에게 다르게 제공하는 문제도 남습니다.
다음 Step에서는 Full Model을 고객마다 복제하지 않고,
작은 LoRA Adapter를 선택해 공유하는 방식을 살펴봅니다.

참고한 1차 자료



🚀 Step 06. 여러 LoRA를 하나의 Base Model 위에서 언제 공유할까?

Tenant나 Domain마다 Full Model을 하나씩 띄우면 Base Weight가 반복해서 GPU Memory를 차지합니다.
LoRA는 Base Weight를 고정하고 작은 Low-rank Delta를 적용하므로,
여러 Customization이 하나의 Base Model을 공유할 가능성을 만듭니다.

Base Model은 여러 업무가 공통으로 사용하는 원래 Model입니다.
Adapter는 Finance나 Legal처럼 특정 업무에 맞게 학습한 작은 추가 Weight이고,
Tenant는 같은 Serving System을 나눠 쓰는 고객이나 조직을 뜻합니다.

LoRA의 Forward 계산은 개념적으로 다음처럼 볼 수 있습니다.

y = W·x + scale × B·(A·x)

W는 공유하는 Base Weight이고,
AB가 Adapter별 Low-rank Weight입니다.
Rank가 작으면 Full Weight를 Adapter마다 복제하는 것보다 저장 공간과 학습 Parameter를 줄일 수 있습니다.

Rank는 큰 Weight 변화량을 더 작은 두 Matrix로 표현할 때 사용하는 중간 차원입니다.
Rank가 작을수록 Adapter는 작아지지만,
표현할 수 있는 변화의 범위와 품질도 함께 검증해야 합니다.

하지만 Adapter 파일이 작다는 사실과 Serving 비용이 거의 없다는 말은 같지 않습니다.
요청마다 다른 A, B를 선택하고 Memory에서 찾아 연산 결과를 Base 출력에 더해야 합니다.

Multi-LoRA Serving에서는 요청이 어떤 Adapter를 사용할지 지정합니다.
Serving Engine은 사용할 Adapter를 준비하고,
서로 다른 Adapter의 요청을 Continuous Batch 안에서 처리합니다.

Continuous Batching은 먼저 들어온 요청이 모두 끝날 때까지 Batch 전체를 기다리지 않습니다.
완료된 요청을 빼고 새 요청을 빈 자리에 넣어 GPU가 계속 일하도록 만듭니다.
Multi-LoRA에서는 이 Batch 안에 서로 다른 Adapter의 Token이 함께 들어갈 수 있습니다.

Adapter 상태는 단순히 Loaded와 Unloaded 두 개가 아닙니다.

  • GPU에 상주해 바로 실행할 수 있는 Hot Adapter
  • CPU Memory에 있어 GPU로 옮겨야 하는 Warm Adapter
  • Disk나 Registry에서 검증하고 읽어야 하는 Cold Adapter
  • Base revision과 맞지 않아 실행할 수 없는 Adapter

Cold load가 Request path에 들어오면 첫 요청의 TTFT가 크게 늘 수 있습니다.
어떤 Adapter를 미리 올릴지는 최근 사용량뿐 아니라 예상 Burst와 Tenant 우선순위를 함께 봅니다.

일반 GEMM은 Batch 전체가 같은 Weight를 쓴다고 가정하기 쉽습니다.
Multi-LoRA에서는 같은 Base 연산 위에 요청마다 다른 Delta가 붙습니다.
Punica가 제안한 Segmented Gather Matrix-Vector multiplication 같은 방식은 이 불규칙한 Adapter 연산을 효율적으로 묶는 아이디어를 제공합니다.

GEMM은 Matrix끼리 곱하는 연산으로 Transformer 계산의 큰 부분을 차지합니다.
Segmented 연산은 Batch를 Adapter별 구간으로 인식해,
각 구간에 맞는 AB를 적용하면서 Base 연산은 공유할 수 있게 합니다.

예를 들어 한 Batch에 Finance 3건,
Legal 1건,
Medical 2건이 섞이면 Base Model 연산은 공유할 수 있지만 Adapter Delta는 요청 집단별로 다릅니다.
Specialized Kernel은 각 Token이 사용할 Adapter 구간을 알고 해당 Low-rank 연산을 묶습니다.

이 구조는 Adapter가 많을수록 무조건 효율적이라는 뜻이 아닙니다.
Batch 안의 Adapter 종류가 지나치게 많거나 Adapter별 Token 수가 너무 적으면,
불규칙한 Memory access와 작은 연산의 Scheduling 비용이 커질 수 있습니다.

실제 Runtime에서 Adapter가 GPU와 CPU에 얼마나 상주하고,
언제 Eviction과 Load가 일어나는지는 설정과 Version에 따라 달라집니다.
활성 Adapter는 항상 전부 GPU에 있다는 문장을 보편 규칙으로 사용하지 않습니다.

vLLM에서는 Server 시작 시 Adapter를 등록할 수 있고,
설정에 따라 한 Batch에서 처리할 LoRA 수와 CPU에 둘 Adapter 수 등을 제한합니다.
요청은 Adapter 이름을 Model처럼 지정하지만,
그 이름이 어떤 Base와 Adapter revision을 가리키는지는 Server가 통제해야 합니다.

Multi-LoRA가 잘 맞는 Traffic은 Adapter 수는 많지만 각각의 요청률은 낮은 경우입니다.
독립 Model로는 GPU가 대부분 비어 있을 때 Base Weight와 Batch를 공유하면 자원 효율을 높일 여지가 있습니다.

설명용으로 Adapter가 12개이고 각각 초당 0.8건만 들어오며,
독립 Instance 하나가 초당 10건을 처리한다고 가정해 봅니다.
독립 배포의 평균 사용률은 각각 8%지만,
요청을 하나의 Pool로 모으면 총 초당 9.6건이 되어 계산상 96%에 가까워집니다.

이는 공유 Engine의 실제 Capacity가 독립 Instance와 같다고 가정한 단순 계산입니다.
Adapter Kernel과 Load,
공정성 비용을 포함한 Benchmark가 아니므로 Multi-LoRA의 성능 결과로 사용하지 않습니다.

반대로 한 Adapter가 GPU를 꾸준히 채울 만큼 요청이 많다면 독립 배포가 단순할 수 있습니다.
Adapter를 Base에 Merge한 Model을 Replica로 확장하면 다른 Tenant와 Batch를 경쟁하지 않고,
장애와 Release도 분리할 수 있습니다.

공유와 독립 배포 사이에 혼합안도 있습니다.
긴 Tail에 있는 저트래픽 Adapter는 Shared Pool에 두고,
트래픽이 큰 몇 개 Adapter만 전용 Replica로 승격할 수 있습니다.
Promote와 Demote 기준에는 요청률뿐 아니라 p95 latency와 Cold load 빈도,
Tenant Isolation 요구를 넣습니다.

선택 기준은 총 TPS 하나가 아닙니다.
Adapter별 P95 Latency와 Throughput, Warm set 적중률, Cold load 시간, GPU·CPU Memory, 공정성을 함께 봅니다.
한 Tenant의 Burst가 다른 Tenant를 밀어내는지도 확인합니다.

Traffic과 운영 조건우선 검토할 방식
Adapter 수가 많고 각 요청률이 낮음Shared Multi-LoRA Pool
특정 Adapter가 지속적으로 GPU를 채움Merge 후 Dedicated Replica
강한 장애·보안 격리가 필요함독립 배포 또는 별도 Pool
Adapter 교체가 잦고 Cold load가 큼작은 Warm set과 명시적 승격 정책

공정성은 평균 Latency로는 보이지 않을 수 있습니다.
Adapter별 Queue time과 Token budget을 보고,
한 Tenant의 긴 Prompt가 다른 Adapter의 짧은 요청을 계속 미루는지 확인합니다.

보안 경계도 중요합니다.
Adapter ID를 Client가 자유롭게 지정할 수 있다면 Tenant가 다른 Adapter를 호출하지 못하도록 Authorization을 적용해야 합니다.
동적으로 Adapter를 내려받는 기능은 편리하지만 신뢰하지 않은 Source와 임의 Code path를 열 수 있습니다.

현재 vLLM 문서도 Runtime Adapter Update 기능은 보안 위험이 있으며,
격리된 완전 신뢰 환경이 아니라면 Production에서 사용하지 말라고 경고합니다.
동적 Loading이 필요하면 허용 Registry와 Checksum,
서명 검증,
Base compatibility,
승인된 Adapter 이름을 Control plane에서 관리합니다.

Base Model과 Adapter의 호환성도 Version으로 고정합니다.
Base revision, Adapter revision, Tokenizer, Quantization, Runtime을 함께 기록하지 않으면 같은 이름의 Adapter가 다른 결과를 만들 수 있습니다.

Release 중에는 기존 요청을 어느 Adapter revision으로 끝낼지와,
새 요청이 새 Revision으로 전환되는 시점을 정합니다.
Rollback할 때 이전 Adapter가 GPU나 CPU Cache에 남아 있는지도 확인합니다.

M1 Air에서는 작은 Base Model과 Adapter 하나의 기능 확인은 가능하지만,
CUDA Specialized Kernel과 많은 Adapter가 섞인 Batch 효율을 대신 측정할 수는 없습니다.
Local Track에서는 Version identity와 Load·Unload 경계,
권한이 없는 Adapter 이름의 거절을 우선 학습합니다.

Multi-LoRA의 질문은 GPU 한 장으로 몇 개를 띄울 수 있는가가 아닙니다.
공유로 절약하는 Memory와 Batch 기회가 Isolation, 공정성, Load 비용보다 큰 Traffic인가를 묻는 일입니다.

LoRA Adapter는 Release 때 가끔 바뀌는 Weight입니다.
강화학습에서는 Actor Weight가 Training Loop 안에서 계속 새로 만들어지고 Serving Fleet에 동기화됩니다.
다음 Step에서는 이 더 빠른 Version 전환과 재현성 문제를 살펴봅니다.

참고한 1차 자료



🚀 Step 07. Serving이 RL Training 안으로 들어가면 무엇이 달라질까?

Reinforcement Learning에서 Actor는 현재 Policy를 나타내는 Model입니다.
Actor Serving Engine은 Prompt Batch로부터 많은 Rollout을 만들고,
Reward나 Verifier가 그 Sample을 평가합니다.

Policy는 어떤 입력에서 어떤 Token을 선택할지 나타내는 현재 Model의 행동 규칙입니다.
Actor는 이 Policy로 실제 Candidate를 생성하는 역할이고,
Rollout은 Prompt부터 생성 결과와 평가 정보까지 묶은 학습 Sample입니다.

Reward Model은 Candidate가 얼마나 좋은지 점수로 평가하고,
Verifier는 수학 정답이나 Code 실행처럼 확인 가능한 조건을 검사할 수 있습니다.
Reference Model은 Actor가 원래 Model에서 지나치게 멀어지지 않도록 비교 기준으로 사용될 수 있습니다.

여기서 Rollout은 일반 사용자에게 보낼 답변 한 건과 목적이 다릅니다.
같은 Prompt에서 여러 Candidate를 Sampling하고,
각 Candidate의 Token과 Log probability,
종료 이유를 이후 학습 단계가 다시 사용합니다.
Serving API가 Text만 반환하면 Training에 필요한 Provenance가 부족할 수 있습니다.

Log probability는 Model이 실제로 선택한 Token에 얼마만큼의 확률을 두었는지를 Log 값으로 표현한 것입니다.
학습 단계는 이 값을 이용해 새 Policy에서 해당 행동의 가능성이 어떻게 달라졌는지 계산할 수 있습니다.

평가 결과로 Policy를 Update하면 새 Weight가 생깁니다.
새 Version을 Serving Replica에 배포한 뒤 다음 Rollout을 만들며 Loop가 이어집니다.

prompt batch
→ actor revision r17로 rollout 생성
→ reward·verifier revision v8로 평가
→ trainer가 policy r18 생성
→ serving fleet에 r18 동기화
→ activation barrier 뒤 다음 batch 시작

각 화살표는 Data나 Weight가 이동하는 Handoff입니다.
Inference GPU와 Training GPU를 분리하면 자원 경합은 줄일 수 있지만,
Weight 전송 시간과 Version 전환 대기가 새 비용으로 생깁니다.

이 구조에서는 Serving 출력이 곧 Training 입력입니다.
Serving이 느리면 Trainer가 Sample을 기다리고,
너무 오래된 Actor가 Rollout을 만들면 Update하려는 Policy와 Data 사이가 벌어질 수 있습니다.

Rollout throughput은 단순 Request/s보다 생성 Token/s와 Sample 완료율로 보는 편이 낫습니다.
Prompt마다 Candidate 수와 Output 길이가 다르면 같은 Request 수에서도 생성량이 크게 달라지기 때문입니다.

Training Worker idle time과 Rollout Queue age를 함께 보면,
문제가 생성 Capacity 부족인지 Weight Sync 대기인지 구분할 수 있습니다.
GPU Utilization만으로는 Trainer가 새 Sample을 기다리는지 알 수 없습니다.

처리량을 높이는 것만으로 충분하지 않습니다.
각 Sample에 Actor checkpoint, Tokenizer, Prompt fixture, Sampling parameter, Seed, Batch와 Parallelism 조건을 연결해야 합니다.
Reward와 Reference Model의 Version도 함께 남겨야 같은 Update를 설명할 수 있습니다.

최소 Provenance Record는 다음과 같이 생각할 수 있습니다.

sample_id
prompt_dataset_revision
actor_revision
reward·verifier·reference_revision
tokenizer와 chat_template_revision
temperature·top_p·max_tokens·seed
serving_runtime·hardware·batch context
generated_token_ids와 score

Text만 보관하고 Token ID나 Template Version을 잃으면,
나중에 같은 문장을 넣어도 동일한 Model 입력을 복원하지 못할 수 있습니다.

Weight Sync는 단순한 File copy가 아닙니다.
일부 Replica만 새 Weight를 읽은 상태에서 Sample이 섞이지 않도록 Version 경계를 정합니다.
Activation이 원자적인지,
진행 중인 Request를 어느 Version으로 끝낼지,
Rollback 시 어떤 Sample을 폐기할지를 결정합니다.

운영 상태를 나누면 다음과 같습니다.

prepared → transferred → loaded → validated → active
                                      ↘ rejected
active old revision → draining → retired

파일이 전송됐다는 사실은 새 Actor가 준비됐다는 뜻이 아닙니다.
모든 Shard가 같은 Revision을 읽었는지,
짧은 Validation Prompt가 통과했는지,
Router가 새 Batch를 보내기 시작했는지를 별도로 확인합니다.

전환 중인 Request를 강제로 끊기보다 기존 Revision으로 끝내고,
새 Batch부터 새 Revision으로 보내는 방법이 이해하기 쉽습니다.
어떤 전략을 쓰든 Sample에 실제 Actor revision이 기록되어야 섞인 Data를 탐지할 수 있습니다.

Determinism도 Seed를 고정하면 된다보다 복잡합니다.
Floating-point Reduction 순서, Batch shape, Kernel 선택, 분산 Scheduling이 바뀌면 작은 차이가 생길 수 있습니다.
Sampling 과정에서는 그 차이가 다음 Token 선택으로 확대될 수 있습니다.

Seed는 난수열의 시작점을 맞추지만 연산 순서를 고정하지는 않습니다.
같은 Seed라도 Batch 구성이나 Hardware,
Runtime Version이 달라지면 Logit의 작은 차이가 생길 수 있습니다.
확률이 비슷한 Token 사이에서는 그 차이가 다른 Sample 경로로 이어질 수 있습니다.

그래서 먼저 어떤 재현성을 원하는지 정의합니다.
같은 입력에서 Token이 완전히 같아야 하는지,
Reward 분포와 학습 지표가 허용 범위 안에서 반복되면 되는지를 구분합니다.

  • Token-level: 생성 Token ID가 모두 같습니다.
  • Score-level: Token이 조금 달라도 Reward와 주요 Score가 허용 오차 안입니다.
  • Run-level: 개별 Sample은 달라도 학습 Curve와 최종 Evaluation이 허용 범위 안입니다.

디버깅과 Weight Sync 검증에는 Token-level 재현성이 유용하고,
확률적 탐색 자체가 목적인 학습에서는 Run-level 통계가 더 중요한 경우도 있습니다.

Batch-invariant한 실행은 동시 요청 구성이 바뀌어도 결과를 안정시키려는 중요한 방향입니다.
하지만 특정 Engine과 Hardware 조합이 이를 완전히 보장한다고 일반화하지 않습니다.
현재 Runtime 문서와 제한을 확인하고 직접 반복해야 합니다.

현재 vLLM 문서는 기본 설정에서 결과 재현성을 보장하지 않으며,
같은 Hardware와 vLLM Version 안에서 별도의 재현성 설정을 사용해야 한다고 설명합니다.
Batch Invariance도 지원 Hardware와 Backend 조건이 있으므로 환경 이름만 보고 사용 가능하다고 판단하지 않습니다.

검증할 때는 같은 Prompt를 단독으로 실행한 결과와,
서로 다른 Batch 크기와 순서에 섞어 실행한 결과를 Token ID 단위로 비교합니다.
그다음 Replica와 Process 재시작,
Weight Sync 전후까지 범위를 넓힙니다.

원문에는 RLHF 시간의 대부분이 Sample 생성에 쓰인다는 외부 추정이 있습니다.
조건과 원문 근거가 고정되지 않았으므로 이 글에서는 비율을 사실로 사용하지 않습니다.
Rollout 생성이 큰 비용 중심이 될 수 있다는 구조적 설명만 남깁니다.

M1 Air에서는 실제 분산 RLHF와 NVIDIA Batch Invariance를 재현하기 어렵습니다.
대신 작은 Model의 반복 생성에서 Prompt와 Sampling 설정,
Model revision을 Record로 남기고 결과 차이를 비교하는 Provenance 실습은 가능합니다.
이 결과를 GPU 분산 결정론의 증거로 확장하지 않습니다.

RL 안에서 Serving Engine은 Online API의 뒷단이 아닙니다.
Data 생산기이자 Version 전환 지점이며,
Training 결과를 재현하기 위한 Provenance System의 일부가 됩니다.

이제 CH10의 일곱 Step이 하나로 연결됩니다.
요청의 의미를 판단하고,
정책 안에서 Route하며,
병목을 찾고,
입력과 실행 위치,
Adapter와 Actor Version까지 선택하는 것이 다음 세대 Serving System의 범위입니다.

참고한 1차 자료



빠른 Model보다 설명할 수 있는 결정을 Serving한다

CH10의 주제는 서로 멀어 보입니다.
Semantic Cache는 의미를 비교하고,
Profiler는 Kernel을 들여다보며,
Edge와 Multi-LoRA, RL은 전혀 다른 배포 환경처럼 보입니다.

하지만 모두 같은 방향으로 움직입니다.
Serving System이 단순한 Model 실행기에서 결정과 경계를 관리하는 System으로 확장됩니다.

Semantic Router는 Cache, Tool, Model을 고릅니다.
Gateway는 그 선택에 권한과 Budget, Retry policy를 적용합니다.
Multimodal Runtime은 CPU와 GPU 사이의 Critical path를 나누고,
Edge Scheduler는 Device와 Cloud 사이의 실행 위치를 정합니다.

Multi-LoRA는 Request마다 Adapter를 선택합니다.
RL Serving은 현재 Actor Version으로 Rollout을 만들고,
새 Weight가 활성화되는 경계를 관리합니다.

결정이 늘어나면 평균 TPS 하나로 System을 설명할 수 없습니다.
False Hit, 정책 위반, Retry 비용, Queue, Thermal 상태, Tenant 공정성, Sample Provenance를 함께 봐야 합니다.

이 장에서 남길 원칙은 새 Framework의 이름이 아닙니다.

  • Route에는 선택 이유와 실패 경로가 있어야 합니다.
  • Cache Hit는 권한과 최신성을 통과해야 합니다.
  • 병목은 Metric에서 Timeline, Operator, Kernel로 Evidence를 따라 좁힙니다.
  • 실행 위치와 Adapter 공유는 Traffic과 Resource 상태에 따라 다시 결정합니다.
  • Training에 들어가는 Serving 출력은 Version과 재현성 계약을 가져야 합니다.

Model과 Hardware, Runtime은 계속 바뀝니다.
그때도 변하지 않는 질문은 같습니다.
어떤 결정을 내렸고, 그 결정이 맞았다는 것을 무엇으로 증명할 것인가?

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글