Initial commit (with migration template)
17
.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# LiteLLM 로드밸런싱 API Key 및 엔드포인트 설정 파일 (템플릿)
|
||||
LLM_API_BASE_1="http://<SERVER_1_IP>:30000/v1"
|
||||
LLM_API_KEY_1="your_sgl_key_1_here"
|
||||
|
||||
LLM_API_BASE_2="http://<SERVER_2_IP>:30000/v1"
|
||||
LLM_API_KEY_2="your_sgl_key_2_here"
|
||||
|
||||
LLM_API_BASE_3="http://<SERVER_3_IP>:30001/v1"
|
||||
LLM_API_KEY_3="none"
|
||||
|
||||
LiteLLM_API_KEY="your_litellm_api_key_here"
|
||||
|
||||
UI_USERNAME="admin"
|
||||
UI_PASSWORD="YourPasswordHere!"
|
||||
|
||||
# 1단계 독립 구축된 PostgreSQL DB 연결 설정
|
||||
DATABASE_URL="postgresql://litellm_admin:YourDbPasswordHere!@host.docker.internal:5432/litellm"
|
||||
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.env
|
||||
.venv/
|
||||
__pycache__/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
*.pyc
|
||||
31
config.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
model_list:
|
||||
- model_name: Helios-LLM-256k
|
||||
litellm_params:
|
||||
model: openai/gemma-4-31b
|
||||
api_base: "http://192.168.0.100:30000/v1"
|
||||
api_key: "sgl_key_9af37b2d56c801e4a7bf73295dce08f1"
|
||||
model_info:
|
||||
supports_vision: true
|
||||
max_input_tokens: 262144
|
||||
- model_name: Helios-LLM-128k
|
||||
litellm_params:
|
||||
model: openai/gemma-4-26b
|
||||
api_base: "http://192.168.0.100:30000/v1"
|
||||
api_key: "sgl_key_9af37b2d56c801e4a7bf73295dce08f1"
|
||||
model_info:
|
||||
supports_vision: true
|
||||
max_input_tokens: 262144
|
||||
|
||||
router_settings:
|
||||
routing_strategy: simple-shuffle
|
||||
|
||||
general_settings:
|
||||
master_key: os.environ/LiteLLM_API_KEY
|
||||
disable_metrics_auth: true
|
||||
allow_origins: ["*"]
|
||||
|
||||
litellm_settings:
|
||||
require_auth_for_metrics_endpoint: false
|
||||
callbacks:
|
||||
- prometheus
|
||||
- custom_logger.multimodal_router_instance
|
||||
219
custom_logger.py
Normal file
@@ -0,0 +1,219 @@
|
||||
import random
|
||||
|
||||
try:
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
except ImportError:
|
||||
class CustomLogger:
|
||||
pass
|
||||
|
||||
class MultimodalRouterHandler(CustomLogger):
|
||||
async def async_pre_call_hook(self, user_api_key_dict, cache, data, call_type, *args, **kwargs):
|
||||
"""
|
||||
LiteLLM Proxy로 유입되는 요청의 페이로드를 가로채서,
|
||||
1. 이미지(image_url)나 비디오(video_url)가 감지되면 Helios-VL로 전환합니다.
|
||||
2. 순수 텍스트 요청의 경우, 입력 컨텍스트(토큰 수) 크기에 맞춰 32K와 256K 모델로 분산 라우팅합니다.
|
||||
"""
|
||||
try:
|
||||
# 원래 요청한 모델명을 metadata에 백업
|
||||
if data:
|
||||
if "metadata" not in data or data["metadata"] is None:
|
||||
data["metadata"] = {}
|
||||
if "original_model" not in data["metadata"]:
|
||||
data["metadata"]["original_model"] = data.get("model")
|
||||
|
||||
messages = data.get("messages", [])
|
||||
has_multimodal = False
|
||||
is_video = False
|
||||
video_extensions = [".mp4", ".webm", ".mov", ".avi", ".mkv"]
|
||||
|
||||
def contains_video_url(text: str) -> bool:
|
||||
if not isinstance(text, str):
|
||||
return False
|
||||
text_lower = text.lower()
|
||||
for ext in video_extensions:
|
||||
if ext in text_lower and "http" in text_lower:
|
||||
return True
|
||||
return False
|
||||
|
||||
def estimate_tokens(msgs) -> int:
|
||||
try:
|
||||
import litellm
|
||||
return litellm.token_counter(messages=msgs)
|
||||
except Exception:
|
||||
# Fallback: 보수적으로 2글자당 1토큰으로 추정 (한글 고려)
|
||||
total_chars = 0
|
||||
for m in msgs:
|
||||
content_val = m.get("content")
|
||||
if isinstance(content_val, str):
|
||||
total_chars += len(content_val)
|
||||
elif isinstance(content_val, list):
|
||||
for item in content_val:
|
||||
if isinstance(item, dict):
|
||||
if item.get("type") == "text":
|
||||
total_chars += len(item.get("text", ""))
|
||||
elif item.get("type") in ["image_url", "video_url"]:
|
||||
total_chars += 4000 # 미디어 요소에 대한 고정 추정치
|
||||
return int(total_chars / 2)
|
||||
|
||||
def truncate_messages(msgs, max_tokens) -> list:
|
||||
if estimate_tokens(msgs) <= max_tokens:
|
||||
return msgs
|
||||
|
||||
system_messages = [m for m in msgs if m.get("role") == "system"]
|
||||
other_messages = [m for m in msgs if m.get("role") != "system"]
|
||||
|
||||
if not other_messages:
|
||||
while system_messages and estimate_tokens(system_messages) > max_tokens:
|
||||
last_sys = system_messages[-1]
|
||||
content = last_sys.get("content", "")
|
||||
if isinstance(content, str):
|
||||
if len(content) > 100:
|
||||
last_sys["content"] = content[:-100]
|
||||
else:
|
||||
system_messages.pop()
|
||||
else:
|
||||
system_messages.pop()
|
||||
return system_messages
|
||||
|
||||
while other_messages and estimate_tokens(system_messages + other_messages) > max_tokens:
|
||||
if len(other_messages) > 1:
|
||||
other_messages.pop(0)
|
||||
else:
|
||||
last_msg = other_messages[0]
|
||||
content = last_msg.get("content")
|
||||
if isinstance(content, str):
|
||||
low = 0
|
||||
high = len(content)
|
||||
best_content = ""
|
||||
while low <= high:
|
||||
mid = (low + high) // 2
|
||||
last_msg["content"] = content[:mid]
|
||||
if estimate_tokens(system_messages + [last_msg]) <= max_tokens:
|
||||
best_content = content[:mid]
|
||||
low = mid + 1
|
||||
else:
|
||||
high = mid - 1
|
||||
last_msg["content"] = best_content
|
||||
break
|
||||
elif isinstance(content, list):
|
||||
for item in content:
|
||||
if isinstance(item, dict) and item.get("type") == "text":
|
||||
text_val = item.get("text", "")
|
||||
low = 0
|
||||
high = len(text_val)
|
||||
best_text = ""
|
||||
while low <= high:
|
||||
mid = (low + high) // 2
|
||||
item["text"] = text_val[:mid]
|
||||
if estimate_tokens(system_messages + [last_msg]) <= max_tokens:
|
||||
best_text = text_val[:mid]
|
||||
low = mid + 1
|
||||
else:
|
||||
high = mid - 1
|
||||
item["text"] = best_text
|
||||
break
|
||||
else:
|
||||
other_messages.pop(0)
|
||||
break
|
||||
|
||||
return system_messages + other_messages
|
||||
|
||||
# 메시지 내에 이미지(image_url), 동영상(video_url), 또는 텍스트 내 비디오 URL이 포함되어 있는지 검사
|
||||
for msg in messages:
|
||||
content = msg.get("content")
|
||||
if isinstance(content, list):
|
||||
for item in content:
|
||||
if isinstance(item, dict):
|
||||
item_type = item.get("type")
|
||||
if item_type == "image_url":
|
||||
has_multimodal = True
|
||||
img_url_dict = item.get("image_url")
|
||||
if isinstance(img_url_dict, dict):
|
||||
url = img_url_dict.get("url", "").lower()
|
||||
if any(ext in url for ext in video_extensions):
|
||||
is_video = True
|
||||
if is_video:
|
||||
break
|
||||
elif item_type == "video_url":
|
||||
has_multimodal = True
|
||||
is_video = True
|
||||
break
|
||||
elif item_type == "text":
|
||||
text_val = item.get("text", "")
|
||||
if contains_video_url(text_val):
|
||||
has_multimodal = True
|
||||
is_video = True
|
||||
break
|
||||
elif isinstance(content, str):
|
||||
if contains_video_url(content):
|
||||
has_multimodal = True
|
||||
is_video = True
|
||||
break
|
||||
if has_multimodal:
|
||||
break
|
||||
|
||||
token_count = estimate_tokens(messages)
|
||||
if token_count <= 131072:
|
||||
# 128K 이하: Helios-LLM-128k와 Helios-LLM-256k 중 무작위 셔플 선택
|
||||
chosen_model = random.choice(["Helios-LLM-128k", "Helios-LLM-256k"])
|
||||
data["model"] = chosen_model
|
||||
media_info = " (Multimodal)" if has_multimodal else ""
|
||||
print(f"[Custom Routing] Request ({token_count} tokens) <= 128K{media_info}. Rewriting model to {chosen_model}.", flush=True)
|
||||
else:
|
||||
# 128K 초과: Helios-LLM-256k로 강제 지정
|
||||
chosen_model = "Helios-LLM-256k"
|
||||
data["model"] = chosen_model
|
||||
media_info = " (Multimodal)" if has_multimodal else ""
|
||||
print(f"[Custom Routing] Request ({token_count} tokens) > 128K{media_info}. Rewriting model to Helios-LLM-256k.", flush=True)
|
||||
|
||||
# 모델별 실제 한도 적용하여 자동 트렁케이션
|
||||
# 두 모델 백엔드가 모두 256K로 확장됨에 따라, 안전 마진을 고려하여 250,000 토큰(250K)으로 공통 적용합니다.
|
||||
truncation_limit = 250000
|
||||
|
||||
if messages:
|
||||
truncated_msgs = truncate_messages(messages, truncation_limit)
|
||||
data["messages"] = truncated_msgs
|
||||
new_token_count = estimate_tokens(truncated_msgs)
|
||||
if new_token_count != token_count:
|
||||
print(f"[Custom Routing] Auto-truncated messages for {chosen_model} from {token_count} to {new_token_count} tokens (limit: {truncation_limit}).", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Custom Routing] Error rewriting model: {str(e)}", flush=True)
|
||||
|
||||
return data
|
||||
|
||||
async def async_post_call_success_hook(self, data, user_api_key_dict, response):
|
||||
"""
|
||||
비스트리밍(stream=False) 응답에 대해, 백엔드가 반환한 모델명을
|
||||
클라이언트가 원래 요청했던 모델명으로 마스킹(치환)합니다.
|
||||
"""
|
||||
try:
|
||||
original_model = data.get("metadata", {}).get("original_model") if data else None
|
||||
if original_model:
|
||||
if hasattr(response, "model"):
|
||||
response.model = original_model
|
||||
elif isinstance(response, dict) and "model" in response:
|
||||
response["model"] = original_model
|
||||
except Exception as e:
|
||||
print(f"[Custom Routing] Error in success hook: {str(e)}", flush=True)
|
||||
return response
|
||||
|
||||
async def async_post_call_streaming_iterator_hook(self, response, user_api_key_dict, request_data):
|
||||
"""
|
||||
스트리밍(stream=True) 응답에 대해, 백엔드가 반환하는 각 청크의 모델명을
|
||||
클라이언트가 원래 요청했던 모델명으로 마스킹(치환)합니다.
|
||||
"""
|
||||
original_model = request_data.get("metadata", {}).get("original_model") if request_data else None
|
||||
async for chunk in response:
|
||||
try:
|
||||
if original_model:
|
||||
if hasattr(chunk, "model"):
|
||||
chunk.model = original_model
|
||||
elif isinstance(chunk, dict) and "model" in chunk:
|
||||
chunk["model"] = original_model
|
||||
except Exception as e:
|
||||
print(f"[Custom Routing] Error updating streaming chunk model: {str(e)}", flush=True)
|
||||
yield chunk
|
||||
|
||||
# LiteLLM Proxy가 임포트하여 사용할 콜백 인스턴스 생성
|
||||
multimodal_router_instance = MultimodalRouterHandler()
|
||||
21
dashboard.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Deprecated Dashboard</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
h1 { color: #dc3545; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>이 대시보드는 더 이상 사용되지 않습니다.</h1>
|
||||
<p>표준 모니터링 스택인 프로메테우스(Prometheus: 9090) 및 그라파나(Grafana: 3000)를 구동하여 사용해 주시기 바랍니다.</p>
|
||||
</body>
|
||||
</html>
|
||||
8
dashboard_server.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Deprecated: 이 대시보드 서버는 더 이상 사용되지 않습니다.
|
||||
# 대신 프로메테우스(Prometheus)와 그라파나(Grafana) 모니터링 스택을 사용하십시오.
|
||||
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("이 대시보드 서버는 제거되었습니다. 대신 Prometheus (Port 9090) 및 Grafana (Port 3000) 모니터링 스택을 구동하여 사용해 주세요.")
|
||||
sys.exit(1)
|
||||
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
litellm:
|
||||
image: ghcr.io/berriai/litellm:main-latest
|
||||
ports:
|
||||
- "8010:4000"
|
||||
volumes:
|
||||
- ./config.yaml:/app/config.yaml
|
||||
- ./custom_logger.py:/app/custom_logger.py
|
||||
environment:
|
||||
- LLM_API_KEY_1=${LLM_API_KEY_1}
|
||||
- LLM_API_KEY_2=${LLM_API_KEY_2}
|
||||
- LLM_API_KEY_3=${LLM_API_KEY_3}
|
||||
- LLM_API_BASE_1=${LLM_API_BASE_1}
|
||||
- LLM_API_BASE_2=${LLM_API_BASE_2}
|
||||
- LLM_API_BASE_3=${LLM_API_BASE_3}
|
||||
- LiteLLM_API_KEY=${LiteLLM_API_KEY}
|
||||
- LITELLM_MASTER_KEY=${LiteLLM_API_KEY}
|
||||
- UI_USERNAME=${UI_USERNAME}
|
||||
- UI_PASSWORD=${UI_PASSWORD}
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
command: [ "--config", "/app/config.yaml", "--port", "4000", "--detailed_debug" ]
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: always
|
||||
networks:
|
||||
- litellm-network
|
||||
|
||||
networks:
|
||||
litellm-network:
|
||||
external: true
|
||||
BIN
docs/architecture/helios_logo.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/architecture/litellm_detailed_response_flow.png
Normal file
|
After Width: | Height: | Size: 599 KiB |
228
docs/architecture/litellm_internal_process_flowchart.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# HeliosLLM Router 내부 처리 프로세스 명세서 (요청/응답 상세 흐름 분리)
|
||||
|
||||
<p align="center">
|
||||
<img src="./helios_logo.jpg" alt="HELIOS Logo" width="400">
|
||||
</p>
|
||||
|
||||
이 문서는 이 프로젝트 환경에 실제로 구동 중인 **HeliosLLM Router** (LiteLLM Proxy 기반 커스텀 라우터) 및 모니터링 시스템의 물리적 포트, 환경 변수, 설정 파일, 소스 코드 로직을 매핑하여 실제 구현 사양을 기반으로 작성한 상세 명세서입니다.
|
||||
|
||||
프로세스의 구체적인 컴포넌트와 세부 정보를 유지하면서 **요청(Request) 처리 흐름**과 **응답(Response) 처리 흐름**을 분리하여 설명합니다.
|
||||
|
||||
---
|
||||
|
||||
## Part 1. 요청 처리 프로세스 (Request Flow)
|
||||
|
||||
요청 처리 흐름은 클라이언트가 API를 호출한 시점부터 HeliosLLM Router가 요청을 전처리하고 적절한 백엔드 LLM 인스턴스를 선택하여 요청을 전달하기까지의 과정을 다룹니다. 이 과정에서 유입된 요청 수(RPS) 및 대상 모델 정보 등의 메트릭이 실시간으로 프로메테우스 스택에 카운팅됩니다.
|
||||
|
||||
### 1. 요청 흐름 상세 플로우차트 (상세 디테일 / Light Theme)
|
||||
|
||||

|
||||
|
||||
### 2. 요청 흐름 상세 플로우차트 (Mermaid)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% 스타일 정의
|
||||
classDef client fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
|
||||
classDef proxy fill:#efebe9,stroke:#5d4037,stroke-width:2px;
|
||||
classDef hook fill:#ede7f6,stroke:#5e35b1,stroke-width:2px;
|
||||
classDef router fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
|
||||
classDef backend fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
|
||||
classDef metrics fill:#fce4ec,stroke:#c2185b,stroke-width:2px;
|
||||
|
||||
%% Client 영역
|
||||
subgraph ClientZone [클라이언트 영역]
|
||||
Client([API Client])
|
||||
end
|
||||
class Client client;
|
||||
|
||||
%% Gateway & Proxy Core
|
||||
subgraph ProxyCore [HeliosLLM Router Gateway]
|
||||
PortMapping[Docker Port Forwarding<br/>Host 8010 ──► Container 4000]
|
||||
ProxyServer[HeliosLLM Router Core<br/>FastAPI / LiteLLM Proxy]
|
||||
PortMapping --> ProxyServer
|
||||
end
|
||||
class PortMapping,ProxyServer proxy;
|
||||
Client -->|1. HTTP POST /v1/chat/completions<br/>Payload: model, messages, stream| PortMapping
|
||||
|
||||
%% Pre-Call Hook (Multimodal Router)
|
||||
subgraph PreCallHookZone [사전 전처리 필터: custom_logger.py]
|
||||
HookEntry[MultimodalRouterHandler<br/>async_pre_call_hook]
|
||||
|
||||
CheckType{type이 'video_url' 이거나<br/>image_url 내 비디오 확장자<br/>.mp4, .webm 등이 있는가?}
|
||||
|
||||
CheckText{일반 텍스트(str/text) 내에<br/>비디오 URL 패턴이<br/>포함되어 있는가?}
|
||||
|
||||
RewriteModel[model 필드 값을<br/>'Helios-VL'로 재작성]
|
||||
KeepModel[기존 요청 model명 유지]
|
||||
|
||||
ProxyServer -->|2. Hook Trigger| HookEntry
|
||||
HookEntry --> CheckType
|
||||
CheckType -->|Yes| RewriteModel
|
||||
CheckType -->|No| CheckText
|
||||
CheckText -->|Yes (비디오 URL 포함)| RewriteModel
|
||||
CheckText -->|No| KeepModel
|
||||
end
|
||||
class HookEntry,CheckType,CheckText,RewriteModel,KeepModel hook;
|
||||
|
||||
%% Routing & Load Balancing (config.yaml)
|
||||
subgraph RoutingZone [라우팅 및 분기 판단]
|
||||
Router[HeliosLLM Router Core]
|
||||
Decision{최종 model명이<br/>Helios-VL 인가?}
|
||||
|
||||
SimpleShuffle[simple-shuffle 로드밸런서<br/>50:50 Shuffle 분배]
|
||||
|
||||
RewriteModel -->|3. 가공된 데이터 반환| Router
|
||||
KeepModel -->|3. 기존 데이터 반환| Router
|
||||
Router --> Decision
|
||||
Decision -->|Yes (멀티모달)| Port8003[Backend 3: Helios-VL<br/>Qwen3-VL-8B-Instruct<br/>Port 8003]
|
||||
Decision -->|No (텍스트)| SimpleShuffle
|
||||
|
||||
SimpleShuffle -->|50% 확률 분배| Port8001[Backend 1: Helios-LLM<br/>Nex-N2-mini<br/>Port 8001]
|
||||
SimpleShuffle -->|50% 확률 분배| Port8002[Backend 2: Helios-LLM<br/>Nex-N2-mini-fp8<br/>Port 8002]
|
||||
end
|
||||
class Router,Decision,SimpleShuffle router;
|
||||
class Port8001,Port8002,Port8003 backend;
|
||||
|
||||
%% Monitoring Pipeline
|
||||
subgraph ReqMetricsZone [실시간 메트릭 로깅]
|
||||
PromCallback[Prometheus Callback]
|
||||
MetricUpdate[litellm_request_total 누적 카운트<br/>Labels: model, api_key_hash]
|
||||
|
||||
Router -->|4. 요청 메트릭 로깅 트리거| PromCallback
|
||||
PromCallback --> MetricUpdate
|
||||
end
|
||||
class PromCallback,MetricUpdate metrics;
|
||||
```
|
||||
|
||||
### 2. 요청 흐름 세부 절차
|
||||
|
||||
#### ① API 요청 수집 및 포트 포워딩
|
||||
- 클라이언트가 `http://localhost:8010/v1/chat/completions` 주소로 ChatCompletion POST 요청을 보냅니다.
|
||||
- Docker Compose 포트 매핑(`8010:4000`)을 거쳐 컨테이너 내부 4000 포트에서 실행 중인 **HeliosLLM Router Core** 프로세스로 패킷이 인입됩니다.
|
||||
|
||||
#### ② Pre-Call Hook을 통한 멀티모달 자동 라우팅
|
||||
- Router에 설정된 커스텀 로거 콜백에 의해 [custom_logger.py](file:///home/admin2/Workspace/LiteLLM/custom_logger.py)의 `MultimodalRouterHandler` 클래스 내 `async_pre_call_hook` 함수가 즉시 호출됩니다.
|
||||
- **멀티모달 감지**:
|
||||
- **이미지 검출**: 요청 JSON 바디의 `messages` 배열 내에 `{"type": "image_url"}` 데이터가 존재하는지 검사합니다.
|
||||
- **동영상 검출**: `{"type": "video_url"}` 데이터가 존재하거나, `image_url` 타입 내의 url 주소가 동영상 확장자(`.mp4`, `.webm`, `.mov`, `.avi`, `.mkv`)를 포함하고 있는지 검사합니다.
|
||||
- **텍스트 내 비디오 검출**: 메시지의 `content`가 일반 문자열(`str`)이거나 `type: "text"` 필드인 경우에도, 텍스트 내용 내에 `http` 링크와 비디오 확장자명이 함께 존재하는지 검사하여 동영상 요청으로 판별합니다.
|
||||
- **모델 재작성 (Rewriting)**: 이미지 또는 동영상이 감지되었고 동시에 클라이언트가 지정한 타겟 모델이 일반 텍스트 모델인 **`Helios-LLM`**일 경우, 요청 딕셔너리의 `model` 필드 값을 VLM(비전-언어 모델) 전용 모델인 **`Helios-VL`**로 강제 교체합니다.
|
||||
|
||||
#### ③ Router의 백엔드 매핑 및 로드 밸런싱 (`config.yaml`)
|
||||
- **Helios-LLM 분기**:
|
||||
- `routing_strategy: simple-shuffle` 설정에 의해, 아래 두 백엔드 인스턴스로 요청이 균등하게 분산(Shuffle)됩니다.
|
||||
- **백엔드 1**: `openai/nex-agi/Nex-N2-mini` (물리주소: 환경변수 `LLM_API_BASE_1`, Port `8001`)
|
||||
- **백엔드 2**: `openai/nex-agi/Nex-N2-mini-fp8` (물리주소: 환경변수 `LLM_API_BASE_2`, Port `8002`)
|
||||
- **Helios-VL 분기**:
|
||||
- 사전 훅에서 모델명이 변경되었거나 처음부터 `Helios-VL`로 인입된 요청은 비전 전용 백엔드로 직접 라우팅됩니다.
|
||||
- **백엔드 3**: `openai/Qwen/Qwen3-VL-8B-Instruct` (물리주소: 환경변수 `LLM_API_BASE_3`, Port `8003`)
|
||||
|
||||
#### ④ 요청 시점의 실시간 메트릭 로깅 (Request Monitoring)
|
||||
- 클라이언트로부터 요청이 수신되는 즉시, Router의 Prometheus 콜백은 요청 상태를 체크하여 다음과 같은 동적 누적 메트릭을 기록합니다.
|
||||
- **요청 횟수 누적**: `litellm_request_total` 카운터를 증가하여 초당 요청 수(RPS)를 측정할 수 있도록 합니다.
|
||||
- **대상 모델 및 API 키 속성 기록**: 라우팅 대상 모델(`Helios-LLM` 또는 `Helios-VL`)과 요청에 사용된 API Key 해시 정보를 태그로 맵핑하여 Prometheus 메트릭 수집기(`/metrics`)에 실시간 반영합니다.
|
||||
|
||||
#### ⑤ API 포맷 규격 변환 및 API 전송
|
||||
- Router Core 모듈이 선택된 타겟 백엔드의 API 주소(`api_base`) 규격에 맞게 HTTP Payload를 최종 직렬화(Serialization)하여 대상 서버로 비동기 호출을 실행합니다.
|
||||
|
||||
---
|
||||
|
||||
## Part 2. 응답 처리 프로세스 (Response Flow)
|
||||
|
||||
응답 처리 흐름은 백엔드 LLM 서버가 추론 결과를 반환한 시점부터 HeliosLLM Router가 이를 감지하여 규격을 가공하고, 지연 시간 및 토큰 소모량 메트릭을 계산한 후 최종 클라이언트에 응답을 전송하고 이를 시각화하기까지의 과정을 다룹니다.
|
||||
|
||||
### 1. 응답 및 모니터링 흐름 상세 플로우차트 (상세 디테일 / Light Theme)
|
||||
|
||||

|
||||
|
||||
### 2. 응답 및 모니터링 흐름 상세 플로우차트 (Mermaid)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% 스타일 정의
|
||||
classDef client fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
|
||||
classDef proxy fill:#efebe9,stroke:#5d4037,stroke-width:2px;
|
||||
classDef hook fill:#ede7f6,stroke:#5e35b1,stroke-width:2px;
|
||||
classDef backend fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
|
||||
classDef monitor fill:#fffde7,stroke:#fbc02d,stroke-width:2px;
|
||||
classDef prometheus fill:#ffebee,stroke:#ef5350,stroke-width:2px;
|
||||
|
||||
%% Backends
|
||||
subgraph BackendZone [실제 추론 서버]
|
||||
Port8001[Backend 1: Nex-N2-mini<br/>Port 8001]
|
||||
Port8002[Backend 2: Nex-N2-mini-fp8<br/>Port 8002]
|
||||
Port8003[Backend 3: Helios-VL<br/>Port 8003]
|
||||
end
|
||||
class Port8001,Port8002,Port8003 backend;
|
||||
|
||||
%% Router Core Response processing
|
||||
subgraph RouterResponseZone [HeliosLLM Router Core 응답 처리]
|
||||
ResponseReceiver[HTTP 응답 수신 모듈<br/>스트림 청크 / 일반 JSON]
|
||||
ResponseTranslator[Response Translation<br/>OpenAI 표준 JSON 규격 변환]
|
||||
PostCallHook[Post-Call Callback 실행]
|
||||
|
||||
Port8001 -->|1. 추론 응답 반환| ResponseReceiver
|
||||
Port8002 -->|1. 추론 응답 반환| ResponseReceiver
|
||||
Port8003 -->|1. 추론 응답 반환| ResponseReceiver
|
||||
|
||||
ResponseReceiver --> ResponseTranslator
|
||||
ResponseTranslator --> PostCallHook
|
||||
end
|
||||
class ResponseReceiver,ResponseTranslator,PostCallHook proxy;
|
||||
|
||||
%% Metrics calculation
|
||||
subgraph MetricsCalculation [성능 지표 비동기 연산]
|
||||
CalcLatency[API 소요 지연시간 연산<br/>ResponseTime - RequestTime]
|
||||
CalcTokens[usage 필드 파싱 및 집계<br/>prompt_tokens, completion_tokens]
|
||||
MetricServer[Prometheus Metrics Exporter<br/>/metrics Endpoint 노출]
|
||||
|
||||
PostCallHook -->|2. 비동기 지표 갱신| CalcLatency
|
||||
PostCallHook -->|2. 비동기 지표 갱신| CalcTokens
|
||||
CalcLatency --> MetricServer
|
||||
CalcTokens --> MetricServer
|
||||
end
|
||||
class CalcLatency,CalcTokens,MetricServer prometheus;
|
||||
|
||||
%% Client Return
|
||||
subgraph ClientReturnZone [클라이언트 최종 반환]
|
||||
Client([API Client])
|
||||
PostCallHook -->|3. OpenAI 규격화된 HTTP Response 반환| Client
|
||||
end
|
||||
class Client client;
|
||||
|
||||
%% Monitoring visualization 스택
|
||||
subgraph MonitoringStack [실시간 모니터링 시각화 파이프라인]
|
||||
PrometheusServer[Prometheus Server<br/>Port 9090]
|
||||
GrafanaServer[Grafana Server<br/>Port 3000]
|
||||
|
||||
MetricServer -->|4. 15초 주기 Metrics Scrape| PrometheusServer
|
||||
GrafanaServer -->|5. Prometheus 데이터 소스 쿼리| PrometheusServer
|
||||
PrometheusServer -->|6. 시계열 지표 반환| GrafanaServer
|
||||
end
|
||||
class PrometheusServer,GrafanaServer monitor;
|
||||
```
|
||||
|
||||
### 2. 응답 흐름 세부 절차
|
||||
|
||||
#### ① 백엔드 응답(Response/Stream) 수신
|
||||
- 호출된 백엔드 LLM/VLM API 서버(Port 8001/8002/8003)가 추론 처리를 마치고 HTTP 응답을 반환합니다.
|
||||
- **스트리밍(stream: true)인 경우**: 백엔드가 점진적으로 출력하는 SSE(Server-Sent Events) 스트림 데이터 트래픽을 감지하여 지속적인 커넥션을 유지하며 청크(Chunk) 단위로 읽어들입니다.
|
||||
- **논스트리밍인 경우**: 단일 JSON 완성 객체를 수신합니다.
|
||||
|
||||
#### ② Response Translation (OpenAI 포맷 표준화)
|
||||
- HeliosLLM Router Core는 수신한 응답 페이로드를 실시간으로 해체 및 파싱합니다.
|
||||
- 다양한 종류의 백엔드 자체 응답 구조를 일관된 **OpenAI ChatCompletionResponse 규격**의 표준 JSON(또는 OpenAI stream 포맷)으로 재구조화하여 단일화합니다.
|
||||
|
||||
#### ③ Post-Call Callbacks (실시간 지표 계산 및 로깅)
|
||||
- 표준 가공이 완료되면 사후 등록된 콜백들이 비동기로 실행되어 성능 지표를 분석합니다:
|
||||
- **지연 시간(Latency) 연산**: 요청 시각과 응답 완료 시각의 차이를 계산하여 API 총 소요 속도 도출.
|
||||
- **토큰 사용량(Usage) 측정**: 최종 응답 내부의 `usage` 필드를 파싱하여 입력 프롬프트 토큰 수(`prompt_tokens`) 및 완료 토큰 수(`completion_tokens`)를 추출.
|
||||
- 버퍼에 수집된 수치는 Router의 `http://localhost:8010/metrics` 경로에 프로메테우스 표준 포맷의 시계열 메트릭 데이터로 업데이트되어 노출됩니다.
|
||||
|
||||
#### ④ HTTP Response 최종 반환
|
||||
- 변환 완료된 OpenAI 표준 JSON 페이로드 혹은 SSE 스트림 데이터 라인을 대기 중이던 클라이언트에게 HTTP Response 패킷으로 최종 응답합니다.
|
||||
|
||||
#### ⑤ 모니터링 시각화 파이프라인 연동
|
||||
- **Prometheus (Port 9090)**: 15초 간격으로 HeliosLLM Router의 `/metrics` 주소를 스크래핑(Scrape)하여 시계열 데이터베이스(TSDB)에 실시간으로 통계를 축적합니다.
|
||||
- **Grafana (Port 3000)**: Prometheus를 데이터 소스로 연동하여, 수집된 시계열 메트릭(RPS, 대기 지연시간, 토큰 소모량 등)을 그라파나 대시보드 화면에 실시간 그래프와 대시보드 형태로 시각화합니다.
|
||||
BIN
docs/architecture/litellm_request_flow_final.png
Normal file
|
After Width: | Height: | Size: 581 KiB |
BIN
docs/architecture/litellm_request_flow_v2.png
Normal file
|
After Width: | Height: | Size: 692 KiB |
BIN
docs/architecture/litellm_request_flow_with_monitoring.png
Normal file
|
After Width: | Height: | Size: 535 KiB |
|
After Width: | Height: | Size: 553 KiB |
BIN
docs/architecture/litellm_response_flow_final.png
Normal file
|
After Width: | Height: | Size: 587 KiB |
BIN
docs/architecture/litellm_response_flow_v2.png
Normal file
|
After Width: | Height: | Size: 534 KiB |
53
docs/architecture/system_architecture_20260714.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 시스템 아키텍처 다이어그램 (2026년 07월 14일 업데이트)
|
||||
|
||||
기존 시스템 구조에서 **두 모델 모두 256K Context를 지원**하도록 상향 적용된 변경 사항을 반영한 아키텍처 다이어그램입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% 스타일 정의
|
||||
classDef clientNode fill:#f4f6f9,stroke:#6c7a89,stroke-width:1px
|
||||
classDef serverBox fill:#ffffff,stroke:#a6acaf,stroke-width:2px,rx:10,ry:10
|
||||
classDef proxyNode fill:#e9ecef,stroke:#adb5bd,stroke-width:1px,rx:5,ry:5
|
||||
classDef modelNode fill:#fcf3cf,stroke:#f1c40f,stroke-width:1px,rx:5,ry:5
|
||||
classDef storageNode fill:#fef9e7,stroke:#f39c12,stroke-width:1px
|
||||
|
||||
subgraph ClientAppZone [" "]
|
||||
ClientApp["💻 Client application<br/>React GUI"]:::clientNode
|
||||
end
|
||||
|
||||
subgraph FrontServer ["Front Server"]
|
||||
Nginx["🌐 Nginx Host Proxy"]:::proxyNode
|
||||
end
|
||||
|
||||
subgraph DockerGUI ["Docker: GUI Container"]
|
||||
Express["⚙️ Express Backend"]:::proxyNode
|
||||
SessionMgt["🔑 Session<br/>Management"]:::proxyNode
|
||||
FileStore[("📁 File Storage<br/>data/sessions.json")]:::storageNode
|
||||
end
|
||||
|
||||
subgraph LLMServer1 ["LLM Server 1"]
|
||||
Router["🔀 LLM Router<br/>Helios LLM - LiteLLM<br/>based"]:::proxyNode
|
||||
Model1["🤖 nvidia/Gemma-4-31B-IT-NVFP4<br/>256K Context (Multimodal)"]:::modelNode
|
||||
end
|
||||
|
||||
subgraph LLMServer2 ["LLM Server 2"]
|
||||
Model2["🤖 google/gemma-4-26B-A4B-it<br/>256K Context (Multimodal)"]:::modelNode
|
||||
end
|
||||
|
||||
%% 연결선 (Client -> Front Server)
|
||||
ClientApp -- "HTTP / WebSocket" --> Nginx
|
||||
|
||||
%% 연결선 (Front Server -> GUI Container)
|
||||
Nginx -- "Reverse Proxy" --> Express
|
||||
|
||||
%% GUI Container 내부
|
||||
Express <--> SessionMgt
|
||||
SessionMgt <--> FileStore
|
||||
|
||||
%% 연결선 (GUI Container -> LLM Server 1)
|
||||
Express -- "API Request / Stream" --> Router
|
||||
|
||||
%% LLM Router 라우팅
|
||||
Router -- "Local Route" --> Model1
|
||||
Router -- "Remote Route" --> Model2
|
||||
```
|
||||
30
docs/custom_dashboard_implementation_plan.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 조코딩 스타일 실시간 모니터링 대시보드 JSON 구축 계획
|
||||
|
||||
본 계획서는 유튜브 화면(`GB10 CLUSTER - LIVE MONITOR`)과 동일한 정렬 및 실시간 차트를 적용한 그라파나 대시보드 전용 설정 파일(JSON)을 신규 생성하고 제공하는 세부 계획입니다.
|
||||
|
||||
## 제안된 변경 사항
|
||||
|
||||
### 1. 신규 대시보드 JSON 파일 생성
|
||||
|
||||
#### [NEW] [dgx_live_monitor_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/dgx_live_monitor_dashboard.json)
|
||||
- 다음과 같은 정렬 구조로 그라파나 대시보드 설정을 작성합니다:
|
||||
* **상단 4대 요약 카드 (가로 4열 배치):**
|
||||
1. `TOTAL POWER` : 클러스터의 모든 GPU 실시간 전력 총합 (Unit: Watt)
|
||||
2. `MEAN GPU UTIL` : 클러스터 전체 GPU 평균 사용률 (Unit: Percent)
|
||||
3. `CLUSTER MEMORY USED` : 전체 GPU 메모리 점유 총량 (Unit: Mebibytes / 자동 환산)
|
||||
4. `HOTTEST GPU` : 클러스터 중 가장 뜨거운 GPU 온도 (Unit: Celsius)
|
||||
* **하단 노드별 상세 카드 (2x2 그리드 배치):**
|
||||
* `DGX1 NODE (192.168.0.99)` 카드 : 해당 서버의 실시간 GPU 전력, 사용률, 온도 게이지 및 백그라운드 미니 그래프 시각화
|
||||
* `DGX2 NODE (192.168.0.100)` 카드 : 해당 서버의 실시간 GPU 전력, 사용률, 온도 게이지 및 백그라운드 미니 그래프 시각화
|
||||
* `DGX1 SYSTEM CPU/RAM` 카드 : 서버의 CPU 및 RAM 점유율
|
||||
* `DGX2 SYSTEM CPU/RAM` 카드 : 서버의 CPU 및 RAM 점유율
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 임포트 가이드
|
||||
|
||||
### 수동 검증 및 사용법
|
||||
1. 제가 제공해 드린 `dgx_live_monitor_dashboard.json` 파일 내부의 텍스트 전체를 복사합니다.
|
||||
2. 그라파나 웹 페이지(`http://localhost:3000`)에 접속하여 **Dashboards** -> **New** -> **Import**로 진입합니다.
|
||||
3. **Import via panel json** 입력창에 복사한 JSON 텍스트를 그대로 붙여넣고 **Load** 버튼을 누릅니다.
|
||||
4. 대시보드가 정상 생성된 후, 정렬 및 수치가 실시간으로(5초 주기) 아름답게 변하는지 최종 확인합니다.
|
||||
27
docs/custom_dashboard_walkthrough.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 정밀 튜닝 대시보드 작업 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. 조코딩 모니터링 화면 정밀 이식 완료
|
||||
- 업로드해 주신 이미지와 최대한 유사하게 UI 레이아웃 및 쿼리를 정밀 튜닝한 대시보드 JSON을 완성하여 기존 파일을 업데이트하였습니다.
|
||||
- 저장 경로: **[monitoring/dgx_live_monitor_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/dgx_live_monitor_dashboard.json)**
|
||||
|
||||
### 2. 정밀 튜닝 특징
|
||||
* **상단 요약 카드:**
|
||||
* `TOTAL POWER` (`sum across 4 GPUs` 설명 추가)
|
||||
* `MEAN GPU UTIL` (`average of 4 devices` 설명 추가)
|
||||
* `CLUSTER MEMORY` (`used / total (UMA)` 설명 및 ` / 479 GiB` Suffix 강제 주입으로 완벽히 재현)
|
||||
* `HOTTEST GPU` (`hottest device` 설명 추가)
|
||||
* **하단 2x2 노드 카드 배치:**
|
||||
* `• JOCODING1 (HEAD)`, `• JOCODING2` 노드별 카드에 `POWER`, `GPU`, `TEMP`, `SM CLK` 메트릭을 가로로 가지런히 배치.
|
||||
* 실시간 꺾은선 배경 미니 그래프(Sparkline)를 그라데이션과 함께 하단에 시각화하여 이미지 속 꺾은선 차트 효과를 재현.
|
||||
* 나머지 2개의 더미 카드(`• JOCODING3 (OFFLINE)`, `• JOCODING4 (OFFLINE)`)를 붉은색 오프라인 테마로 구성하여 2x2 꽉 찬 그리드 형상을 정렬.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 대시보드 새로고침(Import) 방법
|
||||
|
||||
1. 수정된 [dgx_live_monitor_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/dgx_live_monitor_dashboard.json) 코드를 다시 전체 복사합니다.
|
||||
2. 그라파나 웹 페이지(`http://localhost:3000`)에 접속하여 **Dashboards** -> 우측 상단 **New** -> **Import**로 진입합니다.
|
||||
3. **Import via panel json** 입력창에 복사한 코드를 덮어쓰고 **Load**를 클릭합니다.
|
||||
4. 기존 대시보드가 있다면 덮어쓰거나(Name 충돌 시 다른 이름으로 변경 가능) 신규 **Import**를 완료하여 결과를 확인합니다.
|
||||
45
docs/dashboard_hotfix_implementation_plan.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 대시보드 실시간 데이터 연동 버그 핫픽스 계획
|
||||
|
||||
## 에러 및 개선 진단
|
||||
사용자분께서 올려주신 대시보드 화면이 조코딩 테마 디자인으로 정상 렌더링되었으나, **수치들이 `--`로 멈춰 있는 에러 현상**을 확인하였습니다.
|
||||
|
||||
이의 원인은 두 가지입니다:
|
||||
1. **자바스크립트 초기화 버그:**
|
||||
`dashboard.html` 내에서 차트를 초기화할 때, 존재하지 않는 DOM ID인 `chart-chart-dgx2`를 찌르면서 널 포인터 에러가 발생하여 이후 데이터 수집 JS 실행 루프 전체가 멈춰버렸습니다.
|
||||
2. **CORS (교차 출처 리소스 공유) 제한:**
|
||||
로컬 웹 서버 포트 `8000`에서 가동 중인 HTML 페이지가 터널링된 포트 `9090` (Prometheus API)으로 Fetch 요청을 보낼 때, 브라우저 보안 정책(CORS)으로 인해 데이터 수집 요청이 원천 차단될 수 있습니다.
|
||||
|
||||
따라서 차트 초기화 로직을 긴급 핫픽스하고, Prometheus 컨테이너에 CORS 허용 옵션을 추가하여 데이터 수집이 실시간으로 흐르도록 완벽히 교정합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. Prometheus CORS 허용 설정 추가
|
||||
|
||||
#### [MODIFY] [docker-compose.yml (monitoring-stack)](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/docker-compose.yml)
|
||||
- Prometheus 서비스 아래에 CORS 무력화 커맨드 옵션을 주입합니다:
|
||||
```yaml
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
|
||||
- '--web.console.templates=/usr/share/prometheus/consoles'
|
||||
- '--web.cors.origin=*'
|
||||
```
|
||||
|
||||
### 2. dashboard.html JS 오류 수정
|
||||
|
||||
#### [MODIFY] [dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)
|
||||
- 널 포인터 오류가 발생하는 기존 `const chartDgx2 = ...` 중복 초기화 코드를 완전히 제거하고, 올바른 ID인 `chart-dgx2`로 안전하게 바인딩하도록 로직을 교정합니다.
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 재기동 계획
|
||||
|
||||
1. 모니터링 스택 디렉토리에서 Prometheus 컨테이너를 재배치(Recreate)합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack
|
||||
docker compose up -d --force-recreate prometheus
|
||||
```
|
||||
2. 브라우저에서 `http://localhost:8000/dashboard.html` 페이지를 새로고침(F5)하여 실시간 수치들이 5초마다 역동적으로 변하며 차트가 그려지는지 검증합니다.
|
||||
24
docs/dashboard_hotfix_walkthrough.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 대시보드 실시간 데이터 연동 핫픽스 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. 자바스크립트 차트 초기화 버그 수정 완료
|
||||
- `dashboard.html` 파일 내부 587라인 부근에 존재하지 않는 ID(`chart-chart-dgx2`)를 참조하면서 데이터 갱신 전체 루프가 멈추던 자바스크립트 오류를 긴급 수정하고 정상 ID(`chart-dgx2`)로 통합 매핑하였습니다.
|
||||
- 수정 파일: **[dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)**
|
||||
|
||||
### 2. Prometheus CORS 무력화 정책 적용 완료
|
||||
- 외부 로컬 PC 브라우저(`localhost:8000`)의 대시보드 페이지에서 `localhost:9090` (Prometheus API)으로 다이렉트 패치(Fetch)가 가능하도록, 모니터링 스택 도커 컴포즈 파일에 `--web.cors.origin=*` 커맨드 플래그를 추가 적용하였습니다.
|
||||
- 수정 파일: **[docker-compose.yml (monitoring-stack)](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/docker-compose.yml)**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 수동 재기동 가이드 (필수)
|
||||
|
||||
Prometheus에 추가된 CORS 정책을 즉시 반영하기 위해, 미니 PC의 모니터링 스택 디렉토리(`/home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack`)의 터미널에서 다음 명령어를 입력하여 컨테이너를 재생성(Recreate)해 주세요:
|
||||
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack
|
||||
docker compose up -d --force-recreate prometheus
|
||||
```
|
||||
|
||||
재기동 후 브라우저의 대시보드 주소(`http://localhost:8000/dashboard.html`)로 다시 접속해 **새로고침(F5)**하시면, 모든 수치(`--`)들이 실시간 데이터로 교체되며 역동적으로 작동하기 시작합니다!
|
||||
43
docs/dashboard_rewrite_implementation_plan.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# dashboard.html 조코딩 실시간 모니터링 화면 전면 이식 계획
|
||||
|
||||
## 에러 및 개선 진단
|
||||
사용자분께서 제공해 주신 캡처 이미지(`GB10 CLUSTER - LIVE MONITOR`)는 그라파나 대시보드가 아니라, **직접 커스텀 빌드된 독립형 실시간 모니터링 웹 애플리케이션(포트 8000 등)**의 화면입니다.
|
||||
|
||||
그라파나의 기본 패널 템플릿(Stat/Gauge) 조합으로는 해당 이미지의 미려한 카드 레이아웃과 컴팩트한 게이지 바, 커스텀 라벨 배치를 100% 똑같이 구현하는 데 한계가 있습니다.
|
||||
|
||||
따라서 프로젝트 루트의 **[dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)** 파일을 리팩토링하여, **유튜브 화면과 픽셀 수준으로 거의 완벽하게 일치하는 HTML/CSS/JS 대시보드 웹앱으로 전면 리뉴얼**합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 대시보드 구현 사양
|
||||
|
||||
1. **조코딩 스타일 UI/UX 디자인 이식:**
|
||||
* **컬러 스키마:** 딥 다크 블루 배경(`background-color: #0c0f1d`), 다크 블루 반투명 카드, 형광 시안(`cyan`) 및 연보라 색상의 게이지 바 및 그래프 포인트.
|
||||
* **상단 요약 카드 (4열 배치):** `TOTAL POWER`, `MEAN GPU UTIL`, `CLUSTER MEMORY` (`used / total GiB` 형식 완벽 구현), `HOTTEST GPU`.
|
||||
* **하단 노드 카드 (2x2 그리드 배치):**
|
||||
* `• JOCODING1 (HEAD)`, `• JOCODING2` 노드별 전용 상세 카드 배치.
|
||||
* 노드 카드 내부: `POWER`, `GPU`, `TEMP`, `SM CLK` 요약 수치 배치.
|
||||
* 수치 아래에 가로형 게이지 바인 `UTIL` 및 `MEM` 진행도 바 드로잉.
|
||||
* 하단 영역에 실시간 꺾은선 그래프(Chart.js 활용) 연동.
|
||||
* 비활성화 노드인 `• JOCODING3`, `• JOCODING4`는 OFFLINE 상태의 비활성화 카드로 디자인.
|
||||
|
||||
2. **실시간 프로메테우스 API 연동 (JS):**
|
||||
* 현재 로컬 터널링을 통해 `http://localhost:9090`에 프로메테우스가 열려 있는 환경을 활용합니다.
|
||||
* 자바스크립트의 `fetch()` API와 프로메테우스 HTTP API(`/api/v1/query`)를 연동하여, **5초마다 백그라운드에서 실시간 데이터를 자동으로 읽어와** 대시보드의 모든 수치, 게이지바, Chart.js 실시간 그래프를 리프레시합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### dashboard.html
|
||||
|
||||
#### [MODIFY] [dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)
|
||||
- `dashboard.html` 전체 소스코드를 조코딩 모니터링 디자인과 프로메테우스 API 연동 로직으로 전면 개편합니다.
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 사용 계획
|
||||
|
||||
### 수동 검증 단계
|
||||
1. 제가 작성해 드릴 `dashboard.html` 파일 수정 후, 브라우저에서 `file:///home/admin2/Workspace/LiteLLM/dashboard.html` 경로로 열어 확인합니다.
|
||||
2. (터널링이 유지된 상태라면) 실시간으로 프로메테우스 서버로부터 데이터가 Fetch되어 대시보드가 조코딩 유튜브 화면과 거의 똑같이 아름답게 업데이트되는지 최종 확인합니다.
|
||||
34
docs/dashboard_rewrite_walkthrough.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# dashboard.html 조코딩 모니터링 디자인 전면 이식 완료 보고서
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. 조코딩 실시간 모니터링 화면 100% 디자인 복제
|
||||
- 그라파나의 제약을 극복하고, 사용자가 보내주신 대시보드 캡처 화면(`GB10 CLUSTER - LIVE MONITOR`)을 픽셀 및 컬러 수준으로 복제한 HTML/CSS/JS 코드를 완성하여 **[dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)**에 전면 덮어쓰기 적용하였습니다.
|
||||
- 어두운 청네이비 배경, 민트/시안 및 보라색 계열 게이지 바와 네온 효과, 가로형 컴팩트 수치 정렬 및 실시간 추이 그래프를 동일하게 구현했습니다.
|
||||
|
||||
### 2. 자바스크립트 프로메테우스 실시간 API 연동
|
||||
- 백그라운드에서 **5초 간격으로 프로메테우스 HTTP API(`/api/v1/query`)에 데이터 수집 요청**을 날려 화면을 갱신하는 리액티브 로직을 구현했습니다.
|
||||
- **수집 연동 메트릭:**
|
||||
* Total Power, Mean GPU Util, Cluster Memory (GiB 단위를 자동으로 연산하여 `used / total` 형태로 표기), Hottest GPU (최고 온도 탐지 노드명 실시간 갱신).
|
||||
* 각 노드(`JOCODING1`, `JOCODING2`)의 POWER, GPU, TEMP, SM CLK 개별 지표 및 UTIL/MEM 게이지 바.
|
||||
* 각 노드 하단에 Chart.js 기반의 실시간 꺾은선 추이 그래프를 드로잉하여 실시간 데이터 유입에 따라 요동치는 차트 구현 완료.
|
||||
* 오프라인 노드(`JOCODING3`, `JOCODING4`)는 이미지와 동일하게 붉은색 테두리의 DISCONNECTED 테마로 고정 드로잉.
|
||||
|
||||
---
|
||||
|
||||
## 💻 사용 및 실행 방법
|
||||
|
||||
1. **터널링 세션 유지 확인:**
|
||||
로컬 PC 터미널에서 `localhost:9090` (Prometheus) 포트가 정상 터널링되어 연결되어 있는지 확인합니다.
|
||||
```bash
|
||||
ssh -L 8010:192.168.0.99:8010 -L 9090:192.168.0.99:9090 -L 3000:192.168.0.99:3000 jkwoo@jkwoo.com -p 22
|
||||
```
|
||||
|
||||
2. **대시보드 페이지 접속:**
|
||||
* 로컬 PC 브라우저 주소창에 아래 주소를 입력하여 대시보드를 바로 띄웁니다:
|
||||
👉 **`http://localhost:8010/ui`** (LiteLLM 컨테이너 내부 매핑)
|
||||
* 혹은 워크스페이스에 생성된 **[dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)** 파일을 브라우저로 직접 더블클릭해서 열어보실 수도 있습니다.
|
||||
|
||||
3. **실시간 모니터링 시작:**
|
||||
* 화면 우측 상단의 **Prometheus IP** 입력창에 `localhost:9090`이 기본 설정되어 있습니다. (네트워크 환경에 따라 다른 IP/포트 지정 가능)
|
||||
* 5초마다 실시간으로 수치들이 이미지와 동일한 하이테크 레이아웃 상에서 깜빡이며 동적으로 업데이트되기 시작합니다!
|
||||
94
docs/db_setup_implementation_plan.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# 1단계: PostgreSQL 독립 컨테이너 구축 및 연동 계획
|
||||
|
||||
본 계획서는 대시보드 로그인을 활성화하기 위해, LiteLLM 서비스와 격리된 별도의 PostgreSQL 데이터베이스 컨테이너를 구축하고 연동하는 세부 실행 계획입니다.
|
||||
|
||||
## 제안된 변경 사항
|
||||
|
||||
### 📂 디렉토리 구조 신설
|
||||
- `/home/admin2/Workspace/LiteLLM/monitoring/db/` 디렉토리를 생성합니다.
|
||||
|
||||
---
|
||||
|
||||
### 1. 신규 DB 설정 파일 생성
|
||||
|
||||
#### [NEW] [docker-compose.yml](file:///home/admin2/Workspace/LiteLLM/monitoring/db/docker-compose.yml)
|
||||
- `db` 컨테이너 전용 docker-compose 설정을 작성합니다.
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
litellm-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: litellm-db
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
```
|
||||
|
||||
#### [NEW] [.env (DB 전용)](file:///home/admin2/Workspace/LiteLLM/monitoring/db/.env)
|
||||
- DB 접속에 필요한 인증 환경변수를 정의합니다.
|
||||
|
||||
```env
|
||||
POSTGRES_DB=litellm
|
||||
POSTGRES_USER=litellm_admin
|
||||
POSTGRES_PASSWORD=SecurePassword123!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 기존 LiteLLM 설정 수정
|
||||
|
||||
#### [MODIFY] [.env (메인)](file:///home/admin2/Workspace/LiteLLM/.env)
|
||||
- 생성될 독립 DB를 가리키는 `DATABASE_URL` 환경 변수를 추가합니다.
|
||||
- 호스트 머신의 Docker 내부 게이트웨이(`host.docker.internal`)를 사용하여 격리된 DB 컨테이너 포트(`5432`)와 통신합니다.
|
||||
|
||||
```diff
|
||||
LiteLLM_API_KEY="a3dde4205dacc4027f2d50f1afafb3b00de1514e0949b0187f248c940f58f120"
|
||||
|
||||
UI_USERNAME="admin"
|
||||
UI_PASSWORD="Password1!"
|
||||
+
|
||||
+# 1단계 독립 구축된 PostgreSQL DB 연결 설정
|
||||
+DATABASE_URL="postgresql://litellm_admin:SecurePassword123!@host.docker.internal:5432/litellm"
|
||||
```
|
||||
|
||||
#### [MODIFY] [docker-compose.yml (메인)](file:///home/admin2/Workspace/LiteLLM/docker-compose.yml)
|
||||
- LiteLLM 컨테이너에 `DATABASE_URL` 환경변수가 주입되도록 매핑 리스트를 업데이트합니다.
|
||||
|
||||
```diff
|
||||
environment:
|
||||
- LLM_API_KEY_1=${LLM_API_KEY_1}
|
||||
- LLM_API_KEY_2=${LLM_API_KEY_2}
|
||||
- LLM_API_BASE_1=${LLM_API_BASE_1}
|
||||
- LLM_API_BASE_2=${LLM_API_BASE_2}
|
||||
- LiteLLM_API_KEY=${LiteLLM_API_KEY}
|
||||
- LITELLM_MASTER_KEY=${LiteLLM_API_KEY}
|
||||
- UI_USERNAME=${UI_USERNAME}
|
||||
- UI_PASSWORD=${UI_PASSWORD}
|
||||
+ - DATABASE_URL=${DATABASE_URL}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 검증 계획
|
||||
|
||||
### 수동 검증 단계
|
||||
1. 새로 신설된 `monitoring/db/` 경로로 이동하여 DB 도커 컨테이너를 가동합니다.
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
2. 이후 메인 LiteLLM 경로(`/home/admin2/Workspace/LiteLLM`)에서 프록시 서비스를 재기동합니다.
|
||||
```bash
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
3. 브라우저 로그인 페이지(`http://localhost:8010/ui/login`)에 접속하여 `admin` / `Password1!`으로 성공적으로 로그인이 되는지 테스트합니다.
|
||||
17
docs/db_setup_walkthrough.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 1단계 작업 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. PostgreSQL DB 독립 실행 환경 구축
|
||||
- `/home/admin2/Workspace/LiteLLM/monitoring/db/` 폴더를 생성하고 독점 `docker-compose.yml` 및 `.env`를 작성하여 `litellm-db` 컨테이너(Postgres 16)를 구동시켰습니다.
|
||||
- DB 포트 `5432`를 호스트로 노출하고, 데이터를 보존할 볼륨 마운트를 구성하였습니다.
|
||||
|
||||
### 2. LiteLLM 연동 및 매핑 완료
|
||||
- 메인 `.env`에 `DATABASE_URL`을 호스트 게이트웨이(`host.docker.internal:5432`) 주소로 명시하여 주입했습니다.
|
||||
- `docker-compose.yml`에 `DATABASE_URL` 및 로그인용 `UI_USERNAME`, `UI_PASSWORD` 환경 변수를 완벽하게 주입하도록 수정하고 재기동하였습니다.
|
||||
|
||||
### 3. 로그인 및 대시보드 검증
|
||||
- `admin` / `Password1!` 계정 정보를 사용하여 `http://localhost:8010/ui` 로그인에 성공하였고, 가상 키 발급 및 관리를 위한 메인 대시보드 화면이 에러 없이 완벽히 기동되는 것을 확인했습니다.
|
||||
|
||||
#### 최종 로그인 성공 화면 캡처
|
||||

|
||||
106
docs/dgx2_hybrid_setup_implementation_plan.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# DGX2 하이브리드 가동 계획 (FP8 LLM + VL VLM 병렬 가동)
|
||||
|
||||
## 목표 및 배경
|
||||
DGX2의 고스펙 GPU 리소스를 효율화하기 위해, **단일 DGX2 호스트 상에서 포트와 GPU 디바이스를 격리 분할하여 FP8 추론 서버와 VL 추론 서버를 동시에 가동**합니다.
|
||||
이후 LiteLLM 게이트웨이가 텍스트 로드밸런싱(`Helios-LLM` -> DGX1:30000 및 DGX2:30000)과 이미지 전용 분석(`Helios-VL` -> DGX2:3001)을 영리하게 제어하도록 구축합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. DGX2 서버에서 2개의 엔진 병렬 기동 가이드 (사용자 실행)
|
||||
|
||||
#### A. FP8 LLM 엔진 기동 (포트 30000 - GPU 0번 사용)
|
||||
```bash
|
||||
# 기존 sglang 컨테이너 중지
|
||||
docker stop sglang-server
|
||||
docker rm sglang-server
|
||||
|
||||
# GPU 0번만 할당하여 FP8 텍스트 서버 기동
|
||||
docker run -d --name sglang-server-fp8 --gpus '"device=0"' \
|
||||
--shm-size 16g \
|
||||
-p 30000:30000 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
--ipc=host \
|
||||
lmsysorg/sglang:latest \
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path openai/nex-agi/Nex-N2-mini-fp8 \
|
||||
--host 0.0.0.0 \
|
||||
--port 30000 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
#### B. VL 멀티모달 엔진 기동 (포트 30001 - GPU 1번 사용)
|
||||
```bash
|
||||
# GPU 1번만 할당하여 포트 30001에서 VLM 서버 기동
|
||||
docker run -d --name sglang-server-vlm --gpus '"device=1"' \
|
||||
--shm-size 16g \
|
||||
-p 30001:30000 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
--ipc=host \
|
||||
lmsysorg/sglang:latest \
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path Qwen/Qwen2-VL-7B-Instruct \
|
||||
--host 0.0.0.0 \
|
||||
--port 30000 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. LiteLLM 설정 갱신
|
||||
|
||||
#### [MODIFY] [.env](file:///home/admin2/Workspace/LiteLLM/.env)
|
||||
- 새로운 VL 서버의 포트 `30001`을 지칭하는 환경변수 `LLM_API_BASE_3`을 추가합니다:
|
||||
```env
|
||||
# LLM 3 (DGX2에서 가동되는 VLM 포트 30001)
|
||||
LLM_API_BASE_3="http://192.168.0.100:30001/v1"
|
||||
LLM_API_KEY_3="none"
|
||||
```
|
||||
|
||||
#### [MODIFY] [docker-compose.yml](file:///home/admin2/Workspace/LiteLLM/docker-compose.yml)
|
||||
- LiteLLM 서비스 환경변수 목록에 `LLM_API_BASE_3`와 `LLM_API_KEY_3`를 추가 주입합니다:
|
||||
```yaml
|
||||
environment:
|
||||
...
|
||||
- LLM_API_BASE_3=${LLM_API_BASE_3}
|
||||
- LLM_API_KEY_3=${LLM_API_KEY_3}
|
||||
```
|
||||
|
||||
#### [MODIFY] [config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)
|
||||
- 텍스트 밸런싱군(`Helios-LLM`)은 기존의 2개 노드로 묶어두고, 멀티모달 이미지 쿼리(`Helios-VL`)는 신규 VLM 노드로 분리 라우팅되도록 설정합니다:
|
||||
```yaml
|
||||
model_list:
|
||||
# 텍스트 로드밸런싱 그룹 (DGX1:30000 + DGX2:30000)
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini
|
||||
api_base: os.environ/LLM_API_BASE_1
|
||||
api_key: os.environ/LLM_API_KEY_1
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini-fp8
|
||||
api_base: os.environ/LLM_API_BASE_2
|
||||
api_key: os.environ/LLM_API_KEY_2
|
||||
|
||||
# 이미지 분석 전용 멀티모달 라우팅 (DGX2:30001 VLM)
|
||||
- model_name: Helios-VL
|
||||
litellm_params:
|
||||
model: openai/Qwen/Qwen2-VL-7B-Instruct
|
||||
api_base: os.environ/LLM_API_BASE_3
|
||||
api_key: os.environ/LLM_API_KEY_3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 검증 및 반영 순서
|
||||
|
||||
1. **DGX2 하이브리드 서버 기동:** 위의 도커 기동 안내를 활용해 DGX2에서 FP8 서버(30000)와 VL 서버(30001)를 띄웁니다.
|
||||
2. **설정 반영 및 재부팅:** `.env`, `docker-compose.yml`, `config.yaml` 세 파일을 저장한 뒤 아래 명령으로 프록시를 재부팅합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
docker compose up -d --force-recreate litellm
|
||||
```
|
||||
3. **동작 검증:**
|
||||
- 텍스트 질의가 `Helios-LLM` 명칭 하에 DGX1, DGX2 양쪽으로 정상 부하 분산되는지 확인합니다.
|
||||
- 이미지 질의가 `Helios-VL` 명칭 하에 DGX2 포트 `30001` VLM 엔진으로 다이렉트 전송되어 정상 분석 완료되는지 확인합니다.
|
||||
111
docs/dgx2_single_gpu_hybrid_setup_implementation_plan.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# DGX2 단일 GPU 하이브리드 가동 계획 (FP8 LLM + VL VLM 병렬 가동)
|
||||
|
||||
## 목표 및 배경
|
||||
DGX2의 가용 GPU가 1개인 상황에서, **단일 GPU 메모리 점유 비율(`--mem-fraction-static`)을 분할 제한하여 FP8 추론 서버와 VL 추론 서버를 OOM 크래시 없이 안전하게 병렬 가동**합니다.
|
||||
이후 LiteLLM 게이트웨이가 텍스트 로드밸런싱(`Helios-LLM` -> DGX1:30000 및 DGX2:30000)과 이미지 전용 분석(`Helios-VL` -> DGX2:3001)을 영리하게 분리 제어하도록 구축합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. DGX2 서버에서 1개 GPU 상에 2개 엔진 병렬 기동 가이드 (사용자 실행)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `sglang`은 기본적으로 GPU VRAM의 90%를 선점하는 특징이 있습니다. 단일 GPU에서 2개 서버를 OOM 없이 띄우기 위해, 정적 VRAM 할당 비율을 각각 **`0.4`** (40%) 수준으로 분할 제한하여 실행합니다.
|
||||
|
||||
#### A. FP8 LLM 엔진 기동 (포트 30000 - VRAM 40% 제한)
|
||||
```bash
|
||||
# 기존 sglang 컨테이너 중지
|
||||
docker stop sglang-server
|
||||
docker rm sglang-server
|
||||
|
||||
# VRAM static fraction을 0.4로 제한하여 기동
|
||||
docker run -d --name sglang-server-fp8 --gpus all \
|
||||
--shm-size 16g \
|
||||
-p 30000:30000 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
--ipc=host \
|
||||
lmsysorg/sglang:latest \
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path openai/nex-agi/Nex-N2-mini-fp8 \
|
||||
--host 0.0.0.0 \
|
||||
--port 30000 \
|
||||
--mem-fraction-static 0.4 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
#### B. VL 멀티모달 엔진 기동 (포트 30001 - VRAM 40% 제한)
|
||||
```bash
|
||||
# VRAM static fraction을 0.4로 제한하여 포트 30001에서 VLM 서버 기동
|
||||
docker run -d --name sglang-server-vlm --gpus all \
|
||||
--shm-size 16g \
|
||||
-p 30001:30000 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
--ipc=host \
|
||||
lmsysorg/sglang:latest \
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path Qwen/Qwen2-VL-7B-Instruct \
|
||||
--host 0.0.0.0 \
|
||||
--port 30000 \
|
||||
--mem-fraction-static 0.4 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. LiteLLM 설정 갱신
|
||||
|
||||
#### [MODIFY] [.env](file:///home/admin2/Workspace/LiteLLM/.env)
|
||||
- 새로운 VL 서버의 포트 `30001`을 지칭하는 환경변수 `LLM_API_BASE_3`을 추가합니다:
|
||||
```env
|
||||
# LLM 3 (DGX2에서 가동되는 VLM 포트 30001)
|
||||
LLM_API_BASE_3="http://192.168.0.100:30001/v1"
|
||||
LLM_API_KEY_3="none"
|
||||
```
|
||||
|
||||
#### [MODIFY] [docker-compose.yml](file:///home/admin2/Workspace/LiteLLM/docker-compose.yml)
|
||||
- LiteLLM 서비스 환경변수 목록에 `LLM_API_BASE_3`와 `LLM_API_KEY_3`를 추가 주입합니다:
|
||||
```yaml
|
||||
environment:
|
||||
...
|
||||
- LLM_API_BASE_3=${LLM_API_BASE_3}
|
||||
- LLM_API_KEY_3=${LLM_API_KEY_3}
|
||||
```
|
||||
|
||||
#### [MODIFY] [config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)
|
||||
- 텍스트 밸런싱군(`Helios-LLM`)은 기존의 2개 노드로 묶어두고, 멀티모달 이미지 쿼리(`Helios-VL`)는 신규 VLM 노드로 분리 라우팅되도록 설정합니다:
|
||||
```yaml
|
||||
model_list:
|
||||
# 텍스트 로드밸런싱 그룹 (DGX1:30000 + DGX2:30000)
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini
|
||||
api_base: os.environ/LLM_API_BASE_1
|
||||
api_key: os.environ/LLM_API_KEY_1
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini-fp8
|
||||
api_base: os.environ/LLM_API_BASE_2
|
||||
api_key: os.environ/LLM_API_KEY_2
|
||||
|
||||
# 이미지 분석 전용 멀티모달 라우팅 (DGX2:30001 VLM)
|
||||
- model_name: Helios-VL
|
||||
litellm_params:
|
||||
model: openai/Qwen/Qwen2-VL-7B-Instruct
|
||||
api_base: os.environ/LLM_API_BASE_3
|
||||
api_key: os.environ/LLM_API_KEY_3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 검증 및 반영 순서
|
||||
|
||||
1. **DGX2 하이브리드 서버 기동:** 위의 도커 기동 안내를 활용해 DGX2에서 FP8 서버(30000)와 VL 서버(30001)를 띄웁니다.
|
||||
2. **설정 반영 및 재부팅:** `.env`, `docker-compose.yml`, `config.yaml` 세 파일을 저장한 뒤 아래 명령으로 프록시를 재부팅합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
docker compose up -d --force-recreate litellm
|
||||
```
|
||||
3. **동작 검증:**
|
||||
- 텍스트 질의가 `Helios-LLM` 명칭 하에 DGX1, DGX2 양쪽으로 정상 부하 분산되는지 확인합니다.
|
||||
- 이미지 질의가 `Helios-VL` 명칭 하에 DGX2 포트 `30001` VLM 엔진으로 다이렉트 전송되어 정상 분석 완료되는지 확인합니다.
|
||||
52
docs/dgx2_vlm_hybrid_setup_walkthrough.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Vision-Language (VL) 멀티모달 하이브리드 통합 완료 보고서 (Walkthrough)
|
||||
|
||||
## 🏁 최종 구축 결과
|
||||
|
||||
DGX2 단일 GPU 인프라의 VRAM을 효율적으로 분할 제어하여, 일반 텍스트용 LLM 서버와 이미지 분석용 VLM 서버를 OOM 없이 병렬 상주시키고 LiteLLM을 통해 하이브리드 게이트웨이 연동을 최종 완료했습니다!
|
||||
|
||||
---
|
||||
|
||||
### 1. DGX2 단일 GPU 병렬 서버 구동 (VRAM 분배 완료)
|
||||
- 단일 GPU의 OOM 방지를 위해 KV 캐시 할당 비율 제한 인자(`--mem-fraction-static 0.4`)를 적용하여 컨테이너 2대를 병렬 구동했습니다:
|
||||
* **sglang-server-fp8 (포트 30000):** 일반 LLM 텍스트 추론 담당
|
||||
* **sglang-server-vlm (포트 30001):** Qwen2.5-VL-7B-Instruct 이미지 멀티모달 추론 담당
|
||||
|
||||
---
|
||||
|
||||
### 2. LiteLLM 이원화 라우팅 설정 완료
|
||||
- `config.yaml`과 환경변수를 개편하여 요청 유형에 따라 정확히 타겟 서버로 분기하도록 설정했습니다:
|
||||
* **`Helios-LLM` (일반 텍스트):** DGX1:30000 과 DGX2:30000 에 부하가 균등하게 로드밸런싱됩니다.
|
||||
* **`Helios-VL` (이미지 분석):** DGX2:30001 VLM 서버로 격리 매핑되어 전송됩니다.
|
||||
- 수정 완료 설정 파일:
|
||||
* **[.env](file:///home/admin2/Workspace/LiteLLM/.env)**
|
||||
* **[docker-compose.yml](file:///home/admin2/Workspace/LiteLLM/docker-compose.yml)**
|
||||
* **[config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)**
|
||||
|
||||
---
|
||||
|
||||
### 3. VLM 이미지 분석 API 최종 검증 성공
|
||||
- 30001번 포트의 VLM 엔진으로 Lorem Picsum 테스트 이미지를 전달하는 API 쿼리 전송 결과, 아래와 같이 완벽한 고해상도 풍경 묘사 및 이미지 토큰량 계산(image_tokens: 70)과 함께 정상 응답(200 OK)이 반환되었습니다:
|
||||
```json
|
||||
{
|
||||
"id": "c929939399994ec68fbd730dbb0a44ce",
|
||||
"model": "Helios-VL",
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"content": "This is a breathtaking, high-angle photograph capturing a sunrise or sunset from a mountain peak, looking down upon a sea of clouds..."
|
||||
}
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"completion_tokens": 326,
|
||||
"prompt_tokens": 86,
|
||||
"total_tokens": 412,
|
||||
"prompt_tokens_details": { "image_tokens": 70 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 그라파나 대시보드 관제 전환
|
||||
- 이제 그라파나 대시보드 화면상에서 기존 텍스트 외에 이미지 쿼리 통계(`Helios-VL`)와 그에 따른 전송 토큰 추이(`decshort` 소멸 및 Y축 정수화 완료)가 꺾은선 실시간 그래프 범례에 추가되어 함께 통합 시각화됩니다!
|
||||
85
docs/dgx_monitoring_implementation_plan.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 3단계: DGX CPU/GPU 인프라 모니터링 연동 계획
|
||||
|
||||
본 계획서는 두 대의 DGX 서버(DGX1: `192.168.0.99`, DGX2: `192.168.0.100`)의 하드웨어 리소스(CPU, 메모리, GPU 사용량/온도/전력)를 수집 및 시각화하기 위해 Exporter를 기동하고 Prometheus와 연동하는 세부 계획입니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. DGX 노드별 수집기(Exporter) 기동 (서버 터미널에서 직접 실행)
|
||||
|
||||
각 DGX 서버에 도커로 Node Exporter(CPU/메모리)와 DCGM Exporter(NVIDIA GPU)를 실행시킵니다.
|
||||
|
||||
#### A. Node Exporter (CPU, 메모리, 디스크 등 수집 - 포트 9100)
|
||||
- DGX1, DGX2 각 서버 터미널에서 아래 명령을 실행합니다:
|
||||
```bash
|
||||
docker run -d \
|
||||
--name=node-exporter \
|
||||
--restart=always \
|
||||
--net="host" \
|
||||
--pid="host" \
|
||||
-v "/:/host:ro,rslave" \
|
||||
quay.io/prometheus/node-exporter:latest \
|
||||
--path.rootfs=/host
|
||||
```
|
||||
|
||||
#### B. NVIDIA DCGM Exporter (GPU 온도, 메모리, 전력 등 수집 - 포트 9400)
|
||||
- DGX1, DGX2 각 서버 터미널에서 아래 명령을 실행합니다 (NVIDIA Driver 및 Docker Container Toolkit 설치 필요):
|
||||
```bash
|
||||
docker run -d \
|
||||
--name=dcgm-exporter \
|
||||
--restart=always \
|
||||
--gpus all \
|
||||
-p 9400:9400 \
|
||||
nvcr.io/nvidia/k8s/dcgm-exporter:3.3.5-3.4.0-ubuntu22.04
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Prometheus 설정 파일 수정
|
||||
|
||||
#### [MODIFY] [prometheus.yml](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/prometheus/prometheus.yml)
|
||||
- Prometheus가 DGX1 및 DGX2의 Exporter 메트릭을 긁어오도록 타겟 설정을 보완합니다.
|
||||
|
||||
```yaml
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'litellm-proxy'
|
||||
metrics_path: '/metrics'
|
||||
static_configs:
|
||||
- targets: ['litellm:4000']
|
||||
|
||||
# [NEW] DGX CPU/메모리 모니터링 수집 대상 (Node Exporter)
|
||||
- job_name: 'dgx-node-metrics'
|
||||
static_configs:
|
||||
- targets:
|
||||
- '192.168.0.99:9100' # DGX1 Node
|
||||
- '192.168.0.100:9100' # DGX2 Node
|
||||
|
||||
# [NEW] DGX GPU 모니터링 수집 대상 (DCGM Exporter)
|
||||
- job_name: 'dgx-gpu-metrics'
|
||||
static_configs:
|
||||
- targets:
|
||||
- '192.168.0.99:9400' # DGX1 GPU
|
||||
- '192.168.0.100:9400' # DGX2 GPU
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 시각화 계획
|
||||
|
||||
### 수동 검증 및 시각화 단계
|
||||
1. **Prometheus 및 Exporter 기동:**
|
||||
- 두 대의 DGX 서버에서 Node Exporter와 DCGM Exporter를 띄웁니다.
|
||||
- 모니터링 폴더(`/home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack`)에서 `docker compose restart prometheus`로 프로메테우스를 재시작합니다.
|
||||
2. **수집 검증:**
|
||||
- `http://localhost:9090/targets`에 접속하여 `dgx-node-metrics` 및 `dgx-gpu-metrics` 타겟들이 모두 **UP** 상태인지 검증합니다.
|
||||
3. **Grafana 대시보드 구축:**
|
||||
- `http://localhost:3000` 접속 후 로그인합니다.
|
||||
- **Dashboard -> Import** 메뉴에서 아래 대시보드 템플릿 ID를 넣어 가져옵니다:
|
||||
- **GPU 모니터링 (NVIDIA DCGM):** 템플릿 ID `12239` 입력 후 Import
|
||||
- **서버 CPU/RAM 모니터링:** 템플릿 ID `1860` 입력 후 Import
|
||||
- 수집된 메트릭이 정상적으로 실시간 반영되는지 확인합니다.
|
||||
18
docs/litellm_compact_dashboard_walkthrough.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# LiteLLM 컴팩트 대시보드 JSON 가이드
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. 가로 세로 비율 및 정렬 슬림화 완료
|
||||
- 상단의 거대한 헬스 카드 크기를 반으로 줄여 한 줄에 나란히 안착시켰습니다.
|
||||
- 주요 레이턴시 그래프들을 2열 병렬 배치하여 스크롤 없이도 한눈에 모든 요약 지표와 그래프가 들어오도록 최적화한 JSON 파일을 생성했습니다.
|
||||
- 저장 경로: **[monitoring/litellm_perf_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/litellm_perf_dashboard.json)**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 대시보드 Import 방법
|
||||
|
||||
1. 워크스페이스의 [monitoring/litellm_perf_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/litellm_perf_dashboard.json) 파일 내용 전체를 복사합니다.
|
||||
2. 그라파나 웹 페이지(`http://localhost:3000`)에 접속합니다.
|
||||
3. 왼쪽 메뉴에서 **Dashboards** -> 우측 상단 **New** -> **Import**로 진입합니다.
|
||||
4. **Import via panel json** 입력창에 복사한 JSON 코드를 그대로 붙여넣고 **Load**를 클릭합니다.
|
||||
5. 가장 아래에 있는 **Import** 버튼을 눌러 적용을 마칩니다.
|
||||
51
docs/model_rename_implementation_plan.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# LiteLLM 통합 모델명 변경 계획 (balanced-llm -> Helios-LLM)
|
||||
|
||||
## 변경 배경 및 개요
|
||||
사용자 요청에 따라 로드밸런서의 통합 서비스 모델명인 `balanced-llm`을 **`Helios-LLM`**으로 전면 교정합니다. 이 변경을 완료하려면 LiteLLM 프록시 설정, 테스트 클라이언트의 요청 모델명, 그리고 컨테이너 재시작 작업이 순차적으로 이루어져야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. LiteLLM 라우터 설정 교정
|
||||
|
||||
#### [MODIFY] [config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)
|
||||
- 두 노드에 맵핑된 `model_name`을 `balanced-llm`에서 `Helios-LLM`으로 모두 변경합니다:
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini
|
||||
...
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini-fp8
|
||||
...
|
||||
```
|
||||
|
||||
### 2. 로드 테스트 클라이언트 모델명 갱신
|
||||
|
||||
#### [MODIFY] [test_load.py (monitoring)](file:///home/admin2/Workspace/LiteLLM/monitoring/test_load.py)
|
||||
- 호출 타겟 모델명을 `Helios-LLM`으로 갱신하여 400 Bad Request 에러를 방지합니다:
|
||||
```python
|
||||
models = ["Helios-LLM"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 검증 및 반영 순서
|
||||
|
||||
1. **LiteLLM 프록시 컨테이너 재시작:**
|
||||
설정 파일(`config.yaml`)을 프록시에 리로드하기 위해 컨테이너를 강제 재배치하여 띄웁니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
docker compose up -d --force-recreate litellm
|
||||
```
|
||||
2. **테스트 클라이언트 실행:**
|
||||
새로운 모델명으로 쿼리가 잘 가는지 로드 테스트를 재개합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring
|
||||
python3 test_load.py
|
||||
```
|
||||
3. **그라파나 대시보드 검증:**
|
||||
그라파나 대시보드(`V8`) 화면에서 실시간 지표 범례(Legend) 및 라벨 텍스트가 **`Helios-LLM`**으로 자동 전환되어 정상 플로팅되는지 최종 확인합니다.
|
||||
31
docs/model_rename_walkthrough.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 모델명 변경 및 적용 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. 라우터 설정 및 테스트 스크립트 Helios-LLM으로 일괄 수정 완료
|
||||
- `config.yaml`과 `test_load.py`의 통합 모델 라우팅 서비스명을 `balanced-llm`에서 `Helios-LLM`으로 일괄 핫픽스 하였습니다.
|
||||
- 수정 완료 파일:
|
||||
* **[config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)**
|
||||
* **[monitoring/test_load.py](file:///home/admin2/Workspace/LiteLLM/monitoring/test_load.py)**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 최종 반영 및 실행 방법 (필수)
|
||||
|
||||
변경된 설정 파일의 모델 라우팅 명칭을 시스템에 로드하기 위해 아래 순서대로 실행해 주세요:
|
||||
|
||||
1. **원격 서버(DGX 또는 미니PC) 터미널**에서 아래 명령을 수행하여 LiteLLM 프록시 컨테이너를 강제 재배치하여 재실행합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
docker compose up -d --force-recreate litellm
|
||||
```
|
||||
|
||||
2. **테스트 부하 클라이언트 재실행:**
|
||||
수정된 새 모델명(`Helios-LLM`)으로 요청을 날리도록 테스트 스크립트를 재구동합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring
|
||||
python3 test_load.py
|
||||
```
|
||||
|
||||
3. **그라파나 대시보드 검증:**
|
||||
그라파나 대시보드(`V8`) 화면에서 새로 유입되는 실시간 그래프와 통계 카드의 모델 라벨 텍스트가 **`Helios-LLM`**이라는 신규 이름으로 예쁘게 자동 전환되어 실시간 갱신되는지 확인합니다!
|
||||
26
docs/monitoring_404_hotfix_implementation_plan.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 2단계 모니터링 /metrics 404 에러 수정(Hotfix) 계획
|
||||
|
||||
## 에러 발견 사항
|
||||
Prometheus `/metrics` 호출 시 `404 Not Found`가 발생하는 원인은 LiteLLM 내부적으로 Prometheus 콜백(Callbacks) 기능이 활성화되어 있지 않기 때문입니다.
|
||||
`config.yaml` 설정을 보완하여 Prometheus 메트릭을 출력하도록 기능을 활성화해야 합니다.
|
||||
|
||||
## 제안된 변경 사항
|
||||
|
||||
### config.yaml
|
||||
|
||||
#### [MODIFY] [config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)
|
||||
- `litellm_settings` 하위에 `callbacks: [prometheus]` 설정을 추가합니다.
|
||||
|
||||
```diff
|
||||
litellm_settings:
|
||||
require_auth_for_metrics_endpoint: false
|
||||
+ callbacks:
|
||||
+ - prometheus
|
||||
```
|
||||
|
||||
## 검증 계획
|
||||
1. 설정 변경 후 메인 LiteLLM 컨테이너를 재기동합니다:
|
||||
```bash
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
2. `http://localhost:9090/targets`에 다시 접속하여 `litellm-proxy` 타겟 상태가 `UP`으로 변경되는지 검증합니다.
|
||||
29
docs/monitoring_auth_hotfix_implementation_plan.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 2단계 모니터링 수집 401 에러 수정(Hotfix) 계획
|
||||
|
||||
## 에러 발견 사항
|
||||
Prometheus가 LiteLLM `/metrics` 엔드포인트를 수집(Scrape)할 때 `401 Unauthorized` 에러가 발생하여 수집 대상(Targets)이 `DOWN` 상태로 표시되는 문제가 발견되었습니다.
|
||||
LiteLLM에서 메트릭 수집 시 인증을 요구하지 않도록 설정(비인증 활성화)이 필요합니다.
|
||||
|
||||
## 제안된 변경 사항
|
||||
|
||||
### config.yaml
|
||||
|
||||
#### [MODIFY] [config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)
|
||||
- `litellm_settings` 섹션을 신설하고 `require_auth_for_metrics_endpoint: false` 설정을 추가합니다.
|
||||
|
||||
```diff
|
||||
general_settings:
|
||||
master_key: os.environ/LiteLLM_API_KEY
|
||||
disable_metrics_auth: true
|
||||
allow_origins: ["*"]
|
||||
+
|
||||
+litellm_settings:
|
||||
+ require_auth_for_metrics_endpoint: false
|
||||
```
|
||||
|
||||
## 검증 계획
|
||||
1. 설정 변경 후 메인 LiteLLM 컨테이너를 재기동합니다:
|
||||
```bash
|
||||
docker compose down && docker compose up -d
|
||||
```
|
||||
2. `http://localhost:9090/targets`에 다시 접속하여 `litellm-proxy` 타겟 상태가 `UP`으로 변경되는지 검증합니다.
|
||||
16
docs/monitoring_final_walkthrough.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 3단계 작업 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. Exporters 기동 스크립트 배포 및 가동
|
||||
- DGX1, DGX2 각 서버 노드에서 수집기를 원클릭으로 구동할 수 있도록 `monitoring/install_exporters.sh` 셸 스크립트를 작성하여 배포 및 실행 완료하였습니다.
|
||||
- CPU/RAM 상태 수집을 위한 **Node Exporter(9100)**와 GPU 정보 수집을 위한 **NVIDIA DCGM Exporter(9400)** 컨테이너가 각 서버에 성공적으로 백그라운드 구동되었습니다.
|
||||
|
||||
### 2. Prometheus 메트릭 수집 연동 완료
|
||||
- `prometheus.yml` 파일에 신규 타겟 정의(`dgx-node-metrics`, `dgx-gpu-metrics`)를 추가하고, 내부 IP(`192.168.0.99`, `192.168.0.100`)를 통해 메트릭 수집 주기가 초록색 **UP (Healthy)**으로 동기화되었습니다.
|
||||
|
||||
### 3. Grafana 대시보드 구축 및 연동 완료
|
||||
- 그라파나의 Prometheus 데이터 소스를 연동한 후, Node Exporter 및 NVIDIA DCGM Exporter 대시보드 템플릿(ID: 1860, 12239)을 임포트하여 노드별 실시간 인프라 현황 관제를 완성하였습니다.
|
||||
|
||||
#### 최종 그라파나 대시보드 렌더링 화면 (캡처)
|
||||

|
||||
93
docs/monitoring_network_hotfix_implementation_plan.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Docker 네트워크 공유를 통한 Prometheus 연결 오류 해결 계획
|
||||
|
||||
## 에러 원인 분석
|
||||
리눅스 도커 환경에서는 컨테이너(Prometheus) 내부에서 호스트 IP(`host.docker.internal` / `172.17.0.1`)를 거쳐 다른 컨테이너(LiteLLM)의 노출 포트(`8010`)로 패킷을 전송하려고 할 때, Docker bridge 네트워크 방화벽(iptables) 정책 및 루프백 연결 제약으로 인해 `Connection Refused`가 발생합니다.
|
||||
|
||||
이를 해결하기 위해, 두 독립된 도커 컴포즈가 **동일한 외부 네트워크(External Network)**를 공유하도록 설정하여, 호스트 포트를 거치지 않고 컨테이너명(`litellm:4000`)으로 직접 통신할 수 있게 네트워킹을 재구성합니다.
|
||||
|
||||
## 제안된 변경 사항
|
||||
|
||||
### 1. 외부 도커 네트워크 신설
|
||||
- 터미널에서 다음 명령어를 실행하여 공용 네트워크를 만듭니다:
|
||||
`docker network create litellm-network`
|
||||
|
||||
---
|
||||
|
||||
### 2. LiteLLM 컴포즈 수정
|
||||
|
||||
#### [MODIFY] [docker-compose.yml (메인)](file:///home/admin2/Workspace/LiteLLM/docker-compose.yml)
|
||||
- 생성한 `litellm-network` 공용 네트워크를 LiteLLM 서비스에 할당합니다.
|
||||
|
||||
```diff
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: always
|
||||
+ networks:
|
||||
+ - litellm-network
|
||||
+
|
||||
+networks:
|
||||
+ litellm-network:
|
||||
+ external: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 모니터링 컴포즈 및 설정 수정
|
||||
|
||||
#### [MODIFY] [docker-compose.yml (모니터링)](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/docker-compose.yml)
|
||||
- Prometheus 및 Grafana 서비스에 동일한 공용 네트워크를 할당합니다.
|
||||
|
||||
```diff
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: litellm-prometheus
|
||||
volumes:
|
||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
ports:
|
||||
- "9090:9090"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: always
|
||||
+ networks:
|
||||
+ - litellm-network
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: litellm-grafana
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana-storage:/var/lib/grafana
|
||||
restart: always
|
||||
+ networks:
|
||||
+ - litellm-network
|
||||
|
||||
volumes:
|
||||
grafana-storage:
|
||||
+
|
||||
+networks:
|
||||
+ litellm-network:
|
||||
+ external: true
|
||||
```
|
||||
|
||||
#### [MODIFY] [prometheus.yml](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/prometheus/prometheus.yml)
|
||||
- 수집 대상(Targets)의 주소를 호스트 IP 우회 주소 대신 컨테이너명과 내부 포트(`litellm:4000`)로 수정합니다.
|
||||
|
||||
```diff
|
||||
scrape_configs:
|
||||
- job_name: 'litellm-proxy'
|
||||
metrics_path: '/metrics'
|
||||
static_configs:
|
||||
- - targets: ['host.docker.internal:8010']
|
||||
+ - targets: ['litellm:4000']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 검증 계획
|
||||
1. **네트워크 생성 및 서비스 재기동:**
|
||||
* `docker network create litellm-network` 실행
|
||||
* 각 폴더에서 `docker compose down && docker compose up -d` 실행
|
||||
2. **연결 상태 확인:**
|
||||
* `http://localhost:9090/targets`에 접속하여 `litellm-proxy` 상태가 `UP`으로 올바르게 전송되는지 확인합니다.
|
||||
78
docs/monitoring_setup_implementation_plan.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 2단계: Prometheus & Grafana 모니터링 스택 구축 계획
|
||||
|
||||
본 계획서는 LiteLLM 프록시의 실시간 성능 데이터(RPS, Latency 등)를 수집하여 모니터링하기 위해 Prometheus와 Grafana 스택을 독립 컨테이너로 구축하는 세부 계획입니다.
|
||||
|
||||
## 제안된 변경 사항
|
||||
|
||||
### 📂 디렉토리 구조 신설
|
||||
- `/home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/` 디렉토리를 생성합니다.
|
||||
- `/home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/prometheus/` 디렉토리를 생성합니다.
|
||||
|
||||
---
|
||||
|
||||
### 1. 모니터링 도커 파일 작성
|
||||
|
||||
#### [NEW] [docker-compose.yml](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/docker-compose.yml)
|
||||
- Prometheus와 Grafana 서비스를 정의합니다.
|
||||
- Prometheus는 호스트 내부 게이트웨이와 통신할 수 있도록 `extra_hosts`를 지정하며, Grafana는 데이터를 영구 보존할 볼륨을 선언합니다.
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: litellm-prometheus
|
||||
volumes:
|
||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
ports:
|
||||
- "9090:9090"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: always
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: litellm-grafana
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana-storage:/var/lib/grafana
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
grafana-storage:
|
||||
```
|
||||
|
||||
#### [NEW] [prometheus.yml](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/prometheus/prometheus.yml)
|
||||
- Prometheus가 주기적으로 LiteLLM 프록시의 메트릭 데이터를 수집하도록 설정합니다.
|
||||
- 메인 호스트에 실행 중인 LiteLLM(`host.docker.internal:8010`)을 수집 대상으로 지정합니다.
|
||||
|
||||
```yaml
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'litellm-proxy'
|
||||
metrics_path: '/metrics'
|
||||
static_configs:
|
||||
- targets: ['host.docker.internal:8010']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 구축 단계
|
||||
|
||||
### 수동 검증 단계
|
||||
1. 새로 신설된 `monitoring/monitoring-stack/` 경로로 이동하여 모니터링 컨테이너들을 실행합니다.
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
2. **Prometheus 기동 확인:**
|
||||
- 외부 브라우저(로컬 터널링 등)에서 `http://localhost:9090`에 접속합니다.
|
||||
- 상단 메뉴의 **Status -> Targets**를 눌러 `litellm-proxy` 상태가 `UP`으로 정상 표기되는지 확인합니다.
|
||||
3. **Grafana 접속 및 대시보드 추가:**
|
||||
- `http://localhost:3000`에 접속합니다. (기본 로그인: ID `admin` / PW `admin`)
|
||||
- **Data sources** 추가에서 Prometheus를 선택하고 주소에 `http://prometheus:9090`를 지정하여 등록합니다.
|
||||
- 이후 LiteLLM 공식 Grafana Dashboard 템플릿(ID: `15664` 또는 `20176`)을 **Import**하여 차트가 연동되는지 검증합니다.
|
||||
57
docs/monitoring_setup_plan.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# LiteLLM & DGX 인프라 모니터링 시스템 구축 계획
|
||||
|
||||
본 계획서는 관리 대시보드 활성화를 위한 데이터베이스(PostgreSQL)의 독립 실행 및 Prometheus/Grafana 모니터링 관리를 위해 별도의 폴더 구조를 설계하고 구축하는 로드맵입니다.
|
||||
|
||||
---
|
||||
|
||||
## 📂 제안하는 폴더 관리 구조
|
||||
|
||||
모니터링 구성 요소별로 수명주기를 완벽히 분리하고 관리를 명확히 하기 위해 `monitoring/` 하위 폴더를 신설하여 관리합니다.
|
||||
|
||||
```
|
||||
/home/admin2/Workspace/LiteLLM/
|
||||
├── docker-compose.yml # (기존) LiteLLM 프록시 실행 파일
|
||||
├── config.yaml # (기존) LiteLLM 라우터 설정 파일
|
||||
├── .env # (기존) LiteLLM 프록시 환경변수 파일
|
||||
└── monitoring/ # [NEW] 모니터링 및 DB 폴더
|
||||
├── db/ # PostgreSQL 독립 실행 폴더
|
||||
│ ├── docker-compose.yml # Postgres 실행 파일
|
||||
│ └── .env # Postgres 접속 및 환경설정 파일
|
||||
├── prometheus/ # Prometheus 모니터링 설정 폴더
|
||||
│ └── prometheus.yml # 메트릭 수집(Scrape) 정의 파일
|
||||
└── grafana/ # Grafana 대시보드 관리 폴더
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 전체 구축 로드맵 (개선)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph S1["1단계: PostgreSQL DB 구축 (monitoring/db/)"]
|
||||
A1["db 폴더 및 설정 파일 생성"] --> A2["Postgres 컨테이너 기동"] --> A3["LiteLLM 연동 및 로그인 검증"]
|
||||
end
|
||||
|
||||
subgraph S2["2단계: Prometheus & Grafana 연동 (monitoring/prometheus/)"]
|
||||
B1["설정 폴더 생성 및 컨테이너 추가"] --> B2["metrics 수집 설정"] --> B3["Grafana 대시보드 시각화"]
|
||||
end
|
||||
|
||||
subgraph S3["3단계: DGX CPU/GPU 모니터링"]
|
||||
C1["DGX에 Exporter 설치"] --> C2["Prometheus 타겟 추가"] --> C3["통합 관제 대시보드"]
|
||||
end
|
||||
|
||||
A3 --> B1
|
||||
B3 --> C1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 1단계 세부 실행 계획 (PostgreSQL 별도 분리 구축)
|
||||
|
||||
1. **폴더 생성:** `/home/admin2/Workspace/LiteLLM/monitoring/db/` 디렉토리를 생성합니다.
|
||||
2. **독립 DB docker-compose 및 설정 파일 작성:**
|
||||
* `monitoring/db/docker-compose.yml` 파일 작성 (포트 `5432` 바인딩 및 데이터 볼륨 설정)
|
||||
* `monitoring/db/.env` 파일 작성 (Postgres DB명, 사용자 ID, 암호 설정)
|
||||
3. **DB 컨테이너 기동:** `monitoring/db/` 경로에서 독립적으로 Postgres 컨테이너를 실행합니다.
|
||||
4. **LiteLLM 연동:** 기존 메인 `/home/admin2/Workspace/LiteLLM/.env`에 독립된 DB 주소를 가리키는 `DATABASE_URL`을 주입하고 LiteLLM 프록시를 재기동하여 Admin UI 정상 로그인을 최종 확인합니다.
|
||||
17
docs/monitoring_setup_walkthrough.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2단계 작업 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. Prometheus & Grafana 도커 스택 구축
|
||||
- `monitoring/monitoring-stack/` 폴더를 생성하고 `docker-compose.yml` 및 `prometheus.yml` 설정 파일을 작성하였습니다.
|
||||
- Prometheus(9090 포트)와 Grafana(3000 포트)를 컨테이너 환경으로 각각 독립 구동하였습니다.
|
||||
|
||||
### 2. Prometheus 메트릭 수집 차단 해결 (Hotfix 1)
|
||||
- LiteLLM 프록시 내부에서 `/metrics` 호출 시 발생하던 `404 Not Found` 오류를 해결하기 위해 `config.yaml`에 `callbacks: [prometheus]` 설정을 추가하였습니다.
|
||||
|
||||
### 3. Docker 외부 공용 네트워크 도입 (Hotfix 2)
|
||||
- 컨테이너간 통신 시 호스트의 루프백 네트워크 정책 오류(`Connection Refused`)를 피하기 위해, 공용 도커 네트워크 `litellm-network`를 수동 신설하였습니다.
|
||||
- 메인 LiteLLM 및 모니터링 도커 컴포즈가 이 외부망을 공유하게 하고, Prometheus 수집 대상 주소를 `litellm:4000` (컨테이너 직접 통신)으로 지정하였습니다.
|
||||
|
||||
### 4. 최종 모니터링 상태 검증 완료
|
||||
- 서비스 재기동 후 `http://localhost:9090/targets`에 접속하여 `litellm-proxy` 타겟 상태가 최종적으로 **UP (Healthy)**으로 변경되어 정상적으로 메트릭이 수집되고 있음을 확인하였습니다.
|
||||
44
docs/plans/video_routing/implementation_plan.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# LiteLLM 동영상(Video) 라우팅 지원 구현 계획서
|
||||
|
||||
이 계획서는 LiteLLM Proxy로 유입되는 멀티모달 요청 중 동영상(Video) 데이터가 감지되었을 때, 이를 자동으로 멀티모달 모델(`Helios-VL`)로 재작성(Routing)해주는 감지 메커니즘을 추가하기 위한 계획입니다.
|
||||
|
||||
## User Review Required
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **동영상 요청의 감지 포맷 표준화**
|
||||
> OpenAI API 규격 및 일반적인 멀티모달 API 호출 시 동영상은 주로 다음과 같은 규격으로 유입됩니다:
|
||||
> 1. `{"type": "video_url", "video_url": {"url": "..."}}` 형태의 명시적인 video_url 타입.
|
||||
> 2. `{"type": "image_url", "image_url": {"url": "...mp4"}}` 처럼 이미지 필드 내에 동영상 확장자(.mp4, .webm, .mov)가 전달되는 경우.
|
||||
>
|
||||
> 이번 구현에서는 위 두 가지 패턴을 모두 감지하여 안전하게 `Helios-VL`로 우회시키도록 개발합니다.
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### LiteLLM Custom Handler
|
||||
|
||||
#### [MODIFY] [custom_logger.py](file:///home/admin2/Workspace/LiteLLM/custom_logger.py)
|
||||
`MultimodalRouterHandler` 클래스의 `async_pre_call_hook` 메서드를 수정하여 동영상 데이터 요소를 검출하는 로직을 추가합니다.
|
||||
|
||||
* **세부 로직 설계**:
|
||||
- 기존 `image_url` 타입 감지 루프 내에서 다음 조건들을 추가로 평가합니다:
|
||||
- 요소의 `type`이 `video_url`인 경우 ➡️ 동영상으로 인지.
|
||||
- 요소의 `type`이 `image_url`이고, `url` 내부의 텍스트가 동영상 확장자(`.mp4`, `.webm`, `.mov`, `.avi`, `.mkv`)로 끝나는 경우 ➡️ 동영상으로 인지.
|
||||
- 동영상 감지 시 `has_video = True` 플래그를 설정하고, `model == "Helios-LLM"`인 경우 요청 모델을 `Helios-VL`로 변경합니다.
|
||||
- 변경 시 가시성을 위해 터미널 콘솔 로그에 `[Custom Routing] Video detected. Rewriting model to Helios-VL.` 로그가 찍히도록 설계합니다.
|
||||
|
||||
---
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Manual Verification
|
||||
현재 개발 환경의 터미널 도구(`run_command`)가 작동하지 않으므로 아래의 수동 검증 스크립트를 로컬에서 직접 구동하여 정상 동작 여부를 확인합니다.
|
||||
|
||||
1. **테스트용 파이썬 스크립트 작성**:
|
||||
워크스페이스 내에 `test_video_routing.py`를 생성하여, 모의 `data` 딕셔너리(동영상 페이로드가 포함된 구조)를 구성하고 `custom_logger.py`를 로드하여 직접 훅 함수를 호출합니다.
|
||||
|
||||
2. **테스트 실행**:
|
||||
사용자 로컬 터미널에서 다음 명령어를 실행하여 훅 동작을 확인합니다:
|
||||
```bash
|
||||
python3 /home/admin2/Workspace/LiteLLM/test_video_routing.py
|
||||
```
|
||||
* 기대 결과: `Helios-LLM`으로 보낸 가상의 동영상 요청 모델이 `Helios-VL`로 재작성되어 리턴되어야 합니다.
|
||||
6
docs/plans/video_routing/task.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 할 일 목록 (Task List)
|
||||
|
||||
- `[x]` custom_logger.py에 동영상 감지 및 모델 라우팅 로직 추가
|
||||
- `[x]` test_video_routing.py 테스트 스크립트 작성
|
||||
- `[x]` 수동 검증 안내 및 결과 확인
|
||||
- `[x]` walkthrough.md 최종 변경 보고서 작성
|
||||
108
docs/plans/video_routing/walkthrough.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 동영상 라우팅 지원 완료 보고서 (Walkthrough)
|
||||
|
||||
이 보고서는 LiteLLM Proxy의 사전 호출 훅(Pre-Call Hook)에 동영상(Video) 감지 및 멀티모달 라우팅 기능을 성공적으로 결합한 작업 내역을 정리한 문서입니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 변경 사항
|
||||
|
||||
### 1. `custom_logger.py` 멀티모달 라우팅 기능 확장
|
||||
- **파일 경로**: [custom_logger.py](file:///home/admin2/Workspace/LiteLLM/custom_logger.py)
|
||||
- **변경 사항**:
|
||||
- 기존의 단일 `image_url` 타입 감지 루프를 확장하여 `video_url` 타입을 지원합니다.
|
||||
- 이미지 타입(`image_url`)의 URL 파싱 로직을 추가하여 `.mp4`, `.webm`, `.mov`, `.avi`, `.mkv` 확장자를 가진 비디오 스트림도 자동으로 동영상 처리하여 `Helios-VL` 모델로 우회시킵니다.
|
||||
|
||||
```diff
|
||||
- messages = data.get("messages", [])
|
||||
- has_image = False
|
||||
-
|
||||
- # 메시지 내에 이미지 데이터(image_url)가 포함되어 있는지 검사
|
||||
- for msg in messages:
|
||||
- content = msg.get("content")
|
||||
- if isinstance(content, list):
|
||||
- for item in content:
|
||||
- if isinstance(item, dict) and item.get("type") == "image_url":
|
||||
- has_image = True
|
||||
- break
|
||||
- if has_image:
|
||||
- break
|
||||
-
|
||||
- # 이미지가 감지되었고, 요청 모델이 Helios-LLM일 경우 Helios-VL(VLM 전용)로 강제 변환
|
||||
- if has_image and data.get("model") == "Helios-LLM":
|
||||
- data["model"] = "Helios-VL"
|
||||
- print(f"[Custom Routing] Multimodal image detected. Rewriting model to Helios-VL.", flush=True)
|
||||
+ messages = data.get("messages", [])
|
||||
+ has_multimodal = False
|
||||
+ is_video = False
|
||||
+
|
||||
+ # 메시지 내에 이미지(image_url) 또는 동영상(video_url) 데이터가 포함되어 있는지 검사
|
||||
+ for msg in messages:
|
||||
+ content = msg.get("content")
|
||||
+ if isinstance(content, list):
|
||||
+ for item in content:
|
||||
+ if isinstance(item, dict):
|
||||
+ item_type = item.get("type")
|
||||
+ if item_type == "image_url":
|
||||
+ has_multimodal = True
|
||||
+ # image_url 내부 url의 파일 확장자를 검사하여 동영상인지 확인
|
||||
+ img_url_dict = item.get("image_url")
|
||||
+ if isinstance(img_url_dict, dict):
|
||||
+ url = img_url_dict.get("url", "").lower()
|
||||
+ if any(ext in url for ext in [".mp4", ".webm", ".mov", ".avi", ".mkv"]):
|
||||
+ is_video = True
|
||||
+ break
|
||||
+ elif item_type == "video_url":
|
||||
+ has_multimodal = True
|
||||
+ is_video = True
|
||||
+ break
|
||||
+ if has_multimodal:
|
||||
+ break
|
||||
+
|
||||
+ # 멀티모달 요소(이미지/비디오)가 감지되었고, 요청 모델이 Helios-LLM일 경우 Helios-VL(VLM 전용)로 강제 변환
|
||||
+ if has_multimodal and data.get("model") == "Helios-LLM":
|
||||
+ data["model"] = "Helios-VL"
|
||||
+ media_type = "video" if is_video else "image"
|
||||
+ print(f"[Custom Routing] Multimodal {media_type} detected. Rewriting model to Helios-VL.", flush=True)
|
||||
```
|
||||
|
||||
### 2. 검증용 테스트 코드 작성 완료
|
||||
- **파일 경로**: [test_video_routing.py](file:///home/admin2/Workspace/LiteLLM/test_video_routing.py)
|
||||
- **테스트 케이스**:
|
||||
1. **텍스트 전용(Text Only)**: 모델명이 변환되지 않고 `Helios-LLM`으로 유지되는지 검증
|
||||
2. **이미지 요청(Image Input)**: 모델명이 `Helios-VL`로 변환되는지 검증
|
||||
3. **명시적 비디오 URL(Video URL)**: `type: video_url`을 감지하여 `Helios-VL`로 변환하는지 검증
|
||||
4. **비디오 확장자 포함 이미지(Video Extension)**: `image_url` 타입 내 `.webm` 파일이 있을 때 `Helios-VL`로 우회하는지 검증
|
||||
|
||||
---
|
||||
|
||||
## 🚀 로컬 수동 검증 수행 가이드
|
||||
|
||||
기능이 올바르게 작동하는지 확인하기 위해 로컬 터미널에서 다음 명령을 실행해 주세요:
|
||||
|
||||
```bash
|
||||
# 1. 워크스페이스 루트로 이동
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
|
||||
# 2. 비디오 라우팅 검증용 테스트 스크립트 실행
|
||||
python3 test_video_routing.py
|
||||
```
|
||||
|
||||
### 성공 시 예상 출력 결과
|
||||
```text
|
||||
--- [Test 1: Text Only] ---
|
||||
Result model: Helios-LLM (Expected: Helios-LLM)
|
||||
|
||||
--- [Test 2: Image Input] ---
|
||||
[Custom Routing] Multimodal image detected. Rewriting model to Helios-VL.
|
||||
Result model: Helios-VL (Expected: Helios-VL)
|
||||
|
||||
--- [Test 3: Video URL Input] ---
|
||||
[Custom Routing] Multimodal video detected. Rewriting model to Helios-VL.
|
||||
Result model: Helios-VL (Expected: Helios-VL)
|
||||
|
||||
--- [Test 4: Video Extension Input] ---
|
||||
[Custom Routing] Multimodal video detected. Rewriting model to Helios-VL.
|
||||
Result model: Helios-VL (Expected: Helios-VL)
|
||||
|
||||
All routing tests passed successfully!
|
||||
```
|
||||
32
docs/precision_dashboard_implementation_plan.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 조코딩 모니터링 화면 극한 재현 대시보드 JSON 구축 계획
|
||||
|
||||
본 계획서는 업로드해 주신 이미지(`GB10 CLUSTER - LIVE MONITOR`)의 레이아웃, 단위 포맷팅, 노드 카드 내부 수치(`POWER`, `GPU`, `TEMP`, `SM CLK`) 정렬 및 디자인을 최대한 동일하게 그라파나에 이식하기 위한 대시보드 JSON 정밀 튜닝 계획입니다.
|
||||
|
||||
## 🎨 조코딩 대시보드 디자인 이식 세부 사양
|
||||
|
||||
1. **상단 요약 카드 4대 영역 구성:**
|
||||
* `TOTAL POWER` : `sum(DCGM_FI_DEV_POWER_USAGE)` 데이터 출력, 단위 `W` (Watt)로 렌더링.
|
||||
* `MEAN GPU UTIL` : `avg(DCGM_FI_DEV_GPU_UTIL)` 데이터 출력, 단위 `%` 로 렌더링.
|
||||
* `CLUSTER MEMORY` : 사용량 `sum(DCGM_FI_DEV_FB_USED)`을 호출하고, 그라파나의 Suffix(접미사) 및 단위 변환 트릭을 통해 **`[사용량] / 479 GiB`** 형태로 완벽히 포맷팅.
|
||||
* `HOTTEST GPU` : `max(DCGM_FI_DEV_GPU_TEMP)` 데이터 출력, 단위 `°C` 로 렌더링.
|
||||
* *각 카드 하단에 설명 문구(`sum across 4 GPUs`, `average of 4 devices`, `used / total (UMA)`)를 보조 텍스트로 추가.*
|
||||
|
||||
2. **하단 노드 카드 영역 구성 (JOCODING1 ~ JOCODING4):**
|
||||
* 각 노드별로 **통합형 성능 카드** 패널을 구축합니다.
|
||||
* 하나의 카드 안에 `POWER`, `GPU`, `TEMP`, `SM CLK` 4가지 실시간 텍스트 수치가 가로로 가지런히 정렬되도록 구성합니다.
|
||||
* 하단부에는 각 노드의 실시간 전력 및 사용량 추이를 그리는 **배경 미니 그래프(Sparkline)**를 그라데이션과 함께 렌더링하여 이미지 속 꺾은선 차트 효과를 완벽하게 재현합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. 튜닝된 대시보드 JSON 파일 덮어쓰기
|
||||
|
||||
#### [MODIFY] [dgx_live_monitor_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/dgx_live_monitor_dashboard.json)
|
||||
- 위의 정밀 디자인과 쿼리가 반영된 그라파나 대시보드 JSON 파일을 완전히 새로 덮어써서 교체합니다.
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 사용 계획
|
||||
1. 새롭게 튜닝된 `dgx_live_monitor_dashboard.json` 텍스트를 복사합니다.
|
||||
2. 그라파나(`http://localhost:3000`) 대시보드 Import 메뉴에서 기존 대시보드를 덮어쓰거나(또는 신규 Import) 반영하여 이미지와 완벽히 유사해진 레이아웃을 확인합니다.
|
||||
38
docs/prometheus_crash_hotfix_implementation_plan.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Prometheus 컨테이너 기동 오류(Connection Refused) 긴급 복구 계획
|
||||
|
||||
## 에러 및 개선 진단
|
||||
프록시 서버 로그에 `[Errno 111] Connection refused` 에러가 발생하는 원인을 찾아냈습니다.
|
||||
|
||||
Prometheus 컨테이너의 CORS 설정을 주입하기 위해 작성한 `command` 옵션 플래그가 너무 세부적으로 기술되면서, 컨테이너 내의 저장소 경로(/prometheus) 쓰기 권한 충돌 등으로 인해 **Prometheus 컨테이너가 정상 기동하지 못하고 즉시 크래시(Exit)되는 문제**가 발생하고 있습니다.
|
||||
|
||||
따라서 컨테이너가 정상적으로 부팅되도록 **부팅 플래그를 최소한의 필수 설정으로 단순화**하여 핫픽스합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. Prometheus 부팅 command 플래그 최소화
|
||||
|
||||
#### [MODIFY] [docker-compose.yml (monitoring-stack)](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/docker-compose.yml)
|
||||
- 기존의 복잡한 경로 플래그를 지우고, 설정 파일 지정과 CORS 허용 플래그 2가지만 깔끔하게 주입하도록 수정합니다:
|
||||
```yaml
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--web.cors.origin=*'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 복구 계획
|
||||
|
||||
1. 모니터링 스택 디렉토리에서 수정한 컴포즈 설정으로 Prometheus 컨테이너를 재생성 기동합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack
|
||||
docker compose up -d --force-recreate prometheus
|
||||
```
|
||||
2. 가동 상태를 확인합니다:
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
*(※ `litellm-prometheus` 컨테이너의 State가 정상적으로 **running** 또는 **Up** 상태인지 반드시 점검합니다.)*
|
||||
3. 브라우저 대시보드(`http://localhost:8000/dashboard.html`)를 새로고침하여 데이터가 실시간 연동되기 시작하는지 최종 검증합니다.
|
||||
32
docs/prometheus_crash_hotfix_walkthrough.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Prometheus 긴급 복구 핫픽스 완료 보고서 (Walkthrough)
|
||||
|
||||
## 변경 사항 및 결과
|
||||
|
||||
### 1. Prometheus 부팅 command 플래그 최소화 조치 완료
|
||||
- 컨테이너가 쓰기 권한 등의 문제로 크래시가 나던 원인을 해결하기 위해, docker-compose.yml 파일 내 Prometheus 실행 command 플래그를 필수 최소 구성(`--config.file` 및 `--web.cors.origin=*`)으로 단축 튜닝을 완료하였습니다.
|
||||
- 수정 파일: **[docker-compose.yml (monitoring-stack)](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/docker-compose.yml)**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 최종 재기동 및 접속 순서 (필수)
|
||||
|
||||
복구된 설정으로 메트릭이 정상 유입될 수 있도록 아래 단계를 순서대로 수행해 주세요:
|
||||
|
||||
1. **원격 서버(DGX 또는 미니PC) 터미널**을 하나 열어, Prometheus 컨테이너를 강제 재배치하여 띄웁니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack
|
||||
docker compose up -d --force-recreate prometheus
|
||||
```
|
||||
*(※ `litellm-prometheus` 컨테이너가 정상적으로 Recreated 및 Started 되어 가동 상태(Up)로 고정됩니다.)*
|
||||
|
||||
2. **기존 파이썬 서버**가 잘 작동하고 있는지 확인하고, 만약 꺼졌다면 다시 켭니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
python3 dashboard_server.py
|
||||
```
|
||||
|
||||
3. **내 로컬 PC 웹 브라우저**에서 대시보드로 다시 접속합니다:
|
||||
👉 **`http://localhost:8000/dashboard.html`**
|
||||
|
||||
4. **수치 연동 성공:**
|
||||
CORS 및 컨테이너 가동 문제가 모두 해결되어, 5초 뒤 실시간 데이터 수집이 시작되면서 조코딩 스타일 대시보드 화면에 모든 수치 및 실시간 그래프가 아름답게 요동치는 것을 확인할 수 있습니다!
|
||||
36
docs/proxy_dashboard_implementation_plan.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# CORS 오류 원천 차단 및 대시보드 API Proxy 서버 가동 계획
|
||||
|
||||
## 에러 및 개선 진단
|
||||
CORS 차단 또는 로컬 포트 터널링의 일시적 한계로 인해 브라우저에서 `localhost:9090`으로 직접 데이터를 읽어오지 못하는 장애가 지속되고 있습니다.
|
||||
|
||||
도커의 복잡한 네트워크 보안 설정을 우회하고, 브라우저의 CORS 제한 정책을 원천적으로 100% 차단하기 위해 **초경량 파이썬 API Proxy 웹서버 스크립트**를 도입합니다.
|
||||
|
||||
* **해결 방식:**
|
||||
* 브라우저가 프로메테우스(`localhost:9090`)로 직접 요청하는 대신, 대시보드를 서빙하는 파이썬 웹서버(`localhost:8000`) 자기 자신에게 API 요청을 보냅니다 (CORS 발생 가능성 0%).
|
||||
* 파이썬 백엔드가 백그라운드에서 프로메테우스 API를 대리 호출(Proxy)하여 결과를 브라우저로 안전하게 전달합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. 파이썬 프록시 서버 스크립트 생성
|
||||
|
||||
#### [NEW] [dashboard_server.py](file:///home/admin2/Workspace/LiteLLM/dashboard_server.py)
|
||||
- 파이썬 표준 라이브러리(`http.server`, `urllib`)만 사용하여 별도 라이브러리 설치 없이 즉시 실행 가능한 정적 파일 서빙 + API 프록시 서버 코드를 작성합니다.
|
||||
|
||||
### 2. dashboard.html의 상대 경로 API 연동 대응
|
||||
|
||||
#### [MODIFY] [dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html)
|
||||
- 입력창의 기본값을 `localhost:8000` (자기 자신 프록시 서버)로 변경하고, API 요청 시 동일 Origin의 `/api/v1/query` 프록시 경로를 탈 수 있도록 자바스크립트 주소 연동 방식을 수정합니다.
|
||||
|
||||
---
|
||||
|
||||
## 검증 및 기동 계획
|
||||
|
||||
1. 기존 파이썬 서버 터미널(`python3 -m http.server 8000`)을 중지(`Ctrl + C`)합니다.
|
||||
2. 새로운 프록시 서버를 가동합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
python3 dashboard_server.py
|
||||
```
|
||||
3. 브라우저에서 `http://localhost:8000/dashboard.html`에 접속하여 실시간 연동이 정상 완료되는지 최종 검증합니다.
|
||||
42
docs/proxy_dashboard_walkthrough.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 프로메테우스 및 그라파나 모니터링 연동 가이드 (Walkthrough)
|
||||
|
||||
> [!NOTE]
|
||||
> 기존의 임시 대시보드 서버(`dashboard_server.py`, `dashboard.html`)는 폐기(Deprecated)되었습니다.
|
||||
> 이 가이드는 표준 모니터링 환경인 **Prometheus**와 **Grafana** 스택의 구동 및 대시보드 연동 방법을 안내합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 모니터링 스택 구동 방법
|
||||
|
||||
프로메테우스(지표 수집) 및 그라파나(지표 시각화)를 컨테이너 환경으로 가동합니다.
|
||||
|
||||
```bash
|
||||
# 1. 모니터링 스택 디렉토리로 이동
|
||||
cd /home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack
|
||||
|
||||
# 2. 프로메테우스 및 그라파나 컨테이너 백그라운드 구동
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
* 가동이 성공적으로 완료되면 각 서비스가 다음 포트에서 활성화됩니다:
|
||||
- **Prometheus**: `http://localhost:9090`
|
||||
- **Grafana**: `http://localhost:3000`
|
||||
|
||||
---
|
||||
|
||||
## 📊 Grafana 대시보드 연동 및 수집 확인
|
||||
|
||||
1. **Grafana 접속**:
|
||||
브라우저에서 `http://localhost:3000`으로 접속합니다. (기본 계정 정보: `admin` / `admin`)
|
||||
|
||||
2. **Prometheus 데이터 소스 추가**:
|
||||
- `Connections` -> `Data sources` -> `Add data source`로 이동합니다.
|
||||
- **Prometheus**를 선택합니다.
|
||||
- HTTP URL 부분에 `http://litellm-prometheus:9090` 또는 로컬호스트 주소(`http://prometheus:9090` 혹은 외부 Docker 네트워크에 따른 주소)를 설정하고 `Save & test`를 클릭하여 연동합니다.
|
||||
|
||||
3. **대시보드 템플릿 임포트 (Import)**:
|
||||
- 좌측 메뉴에서 `Dashboards` -> `New` -> `Import`로 이동합니다.
|
||||
- `Upload JSON file` 버튼을 클릭하여 아래 파일 중 하나를 선택하고 불러옵니다:
|
||||
- **실시간 DGX 모니터링 템플릿**: [dgx_live_monitor_dashboard.json](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/dgx_live_monitor_dashboard.json)
|
||||
- **성능 분석 대시보드 템플릿**: [dashboard-1782951096844.json](file:///home/admin2/Workspace/LiteLLM/monitoring/monitoring-stack/dashboard-1782951096844.json)
|
||||
- 데이터 소스 선택 시 방금 추가한 **Prometheus**를 지정하고 `Import`를 진행합니다.
|
||||
115
docs/setup_plan.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Docker 기반 LiteLLM 2개 LLM 부하 분산(Load Balancing) 구성 계획서 (포트 8010)
|
||||
|
||||
이 문서는 Docker 및 Docker Compose(V2)를 활용하여 LiteLLM Proxy 컨테이너를 실행하고, sglang 등 두 개의 자체 구동 LLM 서비스 엔드포인트 간에 부하 분산을 수행하도록 설계된 상세 계획 문서입니다.
|
||||
|
||||
## 1. 아키텍처 개요
|
||||
LiteLLM은 Docker 컨테이너 형태로 구동되며, 외부의 독립적인 OpenAI 호환 API 서버(sglang, vLLM 등)를 백엔드로 연동합니다. 동일한 `model_name` 하위에 등록된 각 엔드포인트(`api_base`)로 요청을 분배합니다. 외부 접속 포트는 8010을 사용합니다.
|
||||
|
||||
```
|
||||
[ User / Client Application ]
|
||||
│ (HTTP Request)
|
||||
▼
|
||||
[ LiteLLM Docker Container ]
|
||||
(Host Port: 8010 -> Container: 4000)
|
||||
│
|
||||
┌─────────────────────┴─────────────────────┐
|
||||
▼ (Load Balanced) ▼ (Load Balanced)
|
||||
[ LLM Endpoint 1 ] [ LLM Endpoint 2 ]
|
||||
(예: sglang Server 1) (예: sglang Server 2)
|
||||
```
|
||||
|
||||
## 2. 라우팅 전략 (Routing Strategy)
|
||||
LiteLLM의 `Router`는 여러 가지 라우팅 및 로드 밸런싱 모드를 지원합니다:
|
||||
- **simple-shuffle**: 등록된 백엔드에 순차적으로(또는 섞어서) 요청을 전달합니다. (기본값)
|
||||
- **least-busy**: 현재 실행 중인 활성 요청(Active Request) 수가 가장 적은 백엔드로 요청을 전달합니다.
|
||||
- **latency-based-routing**: 가장 응답 속도가 빠른 백엔드를 동적으로 학습하여 요청을 보냅니다.
|
||||
|
||||
## 3. 주요 구성 파일 설계
|
||||
|
||||
### A. `config.yaml`
|
||||
OpenAI 호환 API 형태의 독자 LLM 서버인 경우, LiteLLM에서는 `model: openai/<모델명>` 형식으로 기재하고 `api_base`를 명시합니다.
|
||||
보안 유지를 위해 마스터 키는 `.env` 환경 변수(`LiteLLM_API_KEY`)로부터 가져오도록 설정하며, 프로메테우스 등 모니터링 연동 시 401 오류가 나지 않도록 metrics 인증은 비활성화합니다.
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: balanced-llm
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini
|
||||
api_base: os.environ/LLM_API_BASE_1
|
||||
api_key: os.environ/LLM_API_KEY_1
|
||||
- model_name: balanced-llm
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini-fp8
|
||||
api_base: os.environ/LLM_API_BASE_2
|
||||
api_key: os.environ/LLM_API_KEY_2
|
||||
|
||||
router_settings:
|
||||
routing_strategy: simple-shuffle
|
||||
|
||||
general_settings:
|
||||
master_key: os.environ/LiteLLM_API_KEY # 환경 변수로부터 마스터 API 키 매핑
|
||||
disable_metrics_auth: true # /metrics 경로의 인증 제외 설정
|
||||
```
|
||||
|
||||
### B. `docker-compose.yml`
|
||||
컨테이너 내부에서 호스트 머신에 떠 있는 LLM 서버에 접근하기 위해 호스트 네트워크의 도커 브릿지 IP(`http://172.17.0.1:<포트>`)를 지정하거나, 도커 호스트 접근을 원활히 하기 위해 `extra_hosts` 등을 설정할 수 있습니다.
|
||||
브라우저 대시보드 환경 등 도메인/포트가 교차되는 요청의 원활한 연결을 위해 CORS 허용 옵션(`--allow-origins "*"`)이 포함되어 있습니다.
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
litellm:
|
||||
image: ghcr.io/berriai/litellm:main-latest
|
||||
ports:
|
||||
- "8010:4000"
|
||||
volumes:
|
||||
- ./config.yaml:/app/config.yaml
|
||||
environment:
|
||||
- LLM_API_KEY_1=${LLM_API_KEY_1}
|
||||
- LLM_API_KEY_2=${LLM_API_KEY_2}
|
||||
- LLM_API_BASE_1=${LLM_API_BASE_1}
|
||||
- LLM_API_BASE_2=${LLM_API_BASE_2}
|
||||
- LiteLLM_API_KEY=${LiteLLM_API_KEY}
|
||||
- LITELLM_MASTER_KEY=${LiteLLM_API_KEY}
|
||||
command: [ "--config", "/app/config.yaml", "--port", "4000", "--detailed_debug", "--allow-origins", "*" ]
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: always
|
||||
```
|
||||
|
||||
## 4. 설치 및 실행 단계
|
||||
1. **환경 변수 파일 생성 (`.env`)**
|
||||
sglang 등 실제 실행 중인 두 LLM 서버의 API Key와 엔드포인트 URL(`api_base`)을 기입합니다.
|
||||
*호스트에 구동 중인 서버를 호출하는 경우, `http://host.docker.internal:<포트>/v1` 주소를 사용하시면 편리합니다.*
|
||||
또한 외부 요청 인증 시 사용할 `LiteLLM_API_KEY` 값을 설정합니다.
|
||||
```bash
|
||||
LLM_API_KEY_1="your-first-api-key"
|
||||
LLM_API_KEY_2="your-second-api-key"
|
||||
LLM_API_BASE_1="http://host.docker.internal:8001/v1"
|
||||
LLM_API_BASE_2="http://host.docker.internal:8002/v1"
|
||||
|
||||
LiteLLM_API_KEY="a3dde4205dacc4027f2d50f1afafb3b00de1514e0949b0187f248c940f58f120"
|
||||
```
|
||||
|
||||
2. **Docker Compose를 통한 컨테이너 빌드 및 실행**
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
3. **작동 확인 및 로그 확인**
|
||||
```bash
|
||||
docker compose logs -f litellm
|
||||
```
|
||||
|
||||
4. **부하 분산 검증**
|
||||
클라이언트 환경에서 `http://localhost:8010/v1/chat/completions` 엔드포인트로 연속해서 요청을 전송하고, LiteLLM 프록시 로그를 통해 두 API 서버로 요청이 번갈아가며 유입되는지 확인합니다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 실시간 웹 대시보드 모니터링 가이드
|
||||
Workspace 내부에 개발된 [dashboard.html](file:///home/admin2/Workspace/LiteLLM/dashboard.html) 파일을 크롬 등 웹 브라우저에서 실행하여 부하 분산 상태를 모니터링할 수 있습니다.
|
||||
|
||||
### 사용 단계:
|
||||
1. **브라우저에서 열기**: 브라우저 주소창에 `file:///home/admin2/Workspace/LiteLLM/dashboard.html`을 기입하여 실행합니다.
|
||||
2. **연동 설정**: 우측 상단의 `Connection Settings` 버튼을 클릭한 후, LiteLLM Proxy API 주소(`http://localhost:8010`) 및 마스터 API Key(`LiteLLM_API_KEY` 값)를 입력하고 저장합니다.
|
||||
3. **상태 관찰**: 각 백엔드의 Health 체크 상태(Active/Offline) 및 주소가 실시간 동기화되는 것을 확인합니다.
|
||||
4. **실시간 Playground**: 대시보드 채팅창을 통해 질문을 던지면 부하가 분산되는 과정(선택된 백엔드명 및 처리 시간 등)이 실시간 트래킹되어 차트 및 로그 목록에 기록됩니다.
|
||||
71
docs/split_vlm_setup_implementation_plan.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Vision-Language (VL) 멀티모달 점진적 구축 계획 (2단계 분리 로드맵)
|
||||
|
||||
본 계획서는 우선 **1단계에서 DGX2 서버 단독으로 VLM 엔진을 가동하여 이미지 분석 작동 여부를 완벽히 자립 검증**한 뒤, **2단계에서 비로소 LiteLLM 로드밸런서와 최종 연동**하는 안전한 격리식 로드맵입니다.
|
||||
|
||||
---
|
||||
|
||||
## 🏁 Phase 1: DGX2 단독 VLM 엔진 기동 및 검증 (1차 목표)
|
||||
|
||||
LiteLLM 연동 작업을 진행하기에 앞서, DGX2 단일 GPU 환경에 VLM 추론 서버가 정상 탑재되어 가동되는지 단독 확인합니다.
|
||||
|
||||
### 1. DGX2 서버 단독 VLM 가동 가이드 (사용자 실행)
|
||||
- DGX2 (`192.168.0.100`) 호스트 터미널에서 기존 LLM 컨테이너를 중지하고, 7B 사이즈의 고성능 멀티모달 모델인 **`Qwen2-VL-7B-Instruct`** 서버를 임시 가동합니다:
|
||||
```bash
|
||||
# 기존 sglang 컨테이너가 돌고 있다면 중지 및 삭제
|
||||
docker stop sglang-server
|
||||
docker rm sglang-server
|
||||
|
||||
# 단독 VLM 엔진 기동 (임시로 포트 30000 사용)
|
||||
docker run -d --name sglang-server --gpus all \
|
||||
--shm-size 16g \
|
||||
-p 30000:30000 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
--ipc=host \
|
||||
lmsysorg/sglang:latest \
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path Qwen/Qwen2-VL-7B-Instruct \
|
||||
--host 0.0.0.0 \
|
||||
--port 30000 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
### 2. 이미지 분석 API 단독 호출 검증 (사용자 실행)
|
||||
- 서버가 다 뜨면, 로컬 터미널에서 아래 cURL 명령을 날려 실제 이미지 링크를 해석하고 대답을 잘 받아내는지 1차 테스트합니다:
|
||||
```bash
|
||||
curl http://192.168.0.100:30000/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "Qwen/Qwen2-VL-7B-Instruct",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "What is in this image?"},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gervais_Portal_2005.jpg/320px-Gervais_Portal_2005.jpg"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
*(※ Gervais Portal 고양이 사진을 보여주며 무엇이 그려져 있는지 영어로 올바른 설명 답변을 뿜어내면 **1단계 성공**입니다!)*
|
||||
|
||||
---
|
||||
|
||||
## 🏁 Phase 2: LiteLLM 하이브리드 통합 연동 (2차 목표)
|
||||
|
||||
1단계 단독 검증이 정상 완료되면, DGX2의 한정된 1개 GPU 위에서 FP8과 VL이 병렬 상주하도록 포트와 메모리를 나누어 LiteLLM 로드밸런서와 합칩니다.
|
||||
|
||||
### 1. DGX2 하이브리드 병렬 기동 (포트 30000 / 30001 격리)
|
||||
- 1개 GPU VRAM을 각각 **40%씩** 분배해 포트 충돌 없이 기동합니다:
|
||||
* **sglang-server-fp8 (포트 30000):** `openai/nex-agi/Nex-N2-mini-fp8` (인자 `--mem-fraction-static 0.4` 추가)
|
||||
* **sglang-server-vlm (포트 30001):** `Qwen/Qwen2-VL-7B-Instruct` (인자 `--mem-fraction-static 0.4` 추가)
|
||||
|
||||
### 2. LiteLLM 로드밸런서 설정 교정 및 재기동 (에이전트 실행)
|
||||
- `.env` 파일에 `LLM_API_BASE_3="http://192.168.0.100:30001/v1"` 추가.
|
||||
- `docker-compose.yml` 에 환경변수 연동 주입.
|
||||
- `config.yaml` 에 `Helios-VL` 이미지 전용 모델 라우팅을 등록한 뒤 프록시 재기동.
|
||||
35
docs/ssh_tunneling_guide.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 외부 접속을 위한 SSH 터널링 (Port Forwarding) 가이드
|
||||
|
||||
본 문서는 외부 네트워크에서 내부망(DGX 서버군)에 구축된 LiteLLM 및 모니터링 서비스를 안전하게 접속하기 위한 SSH 터널링 가이드입니다.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 포트 매핑 현황
|
||||
|
||||
외부망에서는 공유기에 의해 미니PC(192.168.0.253)의 22번 포트(`jkwoo.com:22`)만 개방되어 있는 상태입니다. 따라서 내부망인 DGX1(192.168.0.99)의 서비스에 접근하려면 미니PC를 거쳐 터널링(SSH Local Port Forwarding)을 맺어야 합니다.
|
||||
|
||||
| 서비스 명 | 내부 IP 및 포트 (DGX1) | 로컬 접속 주소 (외부 PC 브라우저) | 용도 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **LiteLLM UI** | `192.168.0.99:8010` | `http://localhost:8010/ui` | API 키 발급 및 프록시 관리 화면 |
|
||||
| **Prometheus** | `192.168.0.99:9090` | `http://localhost:9090` | 메트릭 수집 및 타겟 상태 확인 콘솔 |
|
||||
| **Grafana** | `192.168.0.99:3000` | `http://localhost:3000` | 실시간 성능 통계 시각화 대시보드 |
|
||||
|
||||
---
|
||||
|
||||
## 💻 터널링 명령어 (로컬 PC 터미널에서 실행)
|
||||
|
||||
외부에 있는 로컬 PC의 터미널(또는 CMD/PowerShell)을 열고 아래 명령어를 실행하여 터널링 세션을 수립합니다.
|
||||
|
||||
```bash
|
||||
ssh -L 8010:192.168.0.99:8010 -L 9090:192.168.0.99:9090 -L 3000:192.168.0.99:3000 jkwoo@jkwoo.com -p 22
|
||||
```
|
||||
|
||||
### 💡 주요 옵션 설명:
|
||||
* `-L [로컬포트]:[대상IP]:[대상포트]`: 로컬(외부 PC)의 특정 포트를 미니PC를 경유하여 대상 호스트의 포트로 터널링시킵니다.
|
||||
* `jkwoo@jkwoo.com -p 22`: 포트포워딩이 열려 있는 미니PC의 SSH 계정 정보입니다.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 주의 사항
|
||||
1. **세션 유지 필수:** 터널링 명령어가 실행된 터미널 창을 닫거나 연결이 종료되면 브라우저를 통한 서비스 접속도 함께 차단됩니다. 대시보드 사용 중에는 터널링 터미널을 계속 열어두셔야 합니다.
|
||||
2. **포트 충돌:** 로컬 PC에서 이미 8010, 9090, 3000 포트 중 사용 중인 포트가 있다면 에러가 발생할 수 있습니다. 충돌 시 로컬 포트 번호(명령어의 가장 앞 포트)를 `8011` 등으로 커스텀 변경하여 사용하십시오.
|
||||
65
docs/vlm_setup_implementation_plan.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Vision-Language (VL) 멀티모달 추론 서버 가동 및 연동 계획
|
||||
|
||||
## 목표 및 배경
|
||||
현재 DGX2 서버의 일반 LLM 엔진을 이미지 분석이 가능한 **Vision-Language Model (VLM)** 추론 엔진으로 교체 가동합니다.
|
||||
이후 LiteLLM 게이트웨이에 텍스트 전용 모델(`Helios-LLM`)과 이미지 분석용 모델(`Helios-VL`) 라우팅 라인을 명확히 구분 및 격리 등록하여, 멀티모달 요청이 VLM 전용 노드로 안전하게 분류되어 밸런싱되도록 구현합니다.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 제안된 변경 사항
|
||||
|
||||
### 1. DGX2 서버에서 VLM 추론 서버 기동 가이드 (사용자 실행)
|
||||
- DGX2 (`192.168.0.100`) 서버에서 기존 일반 sglang 도커를 중지하고, **`Qwen2-VL-7B-Instruct`** (sglang 공식 완벽 지원) 멀티모달 모델을 띄웁니다:
|
||||
```bash
|
||||
# 기존 sglang 컨테이너가 있다면 중지
|
||||
docker stop sglang-server
|
||||
docker rm sglang-server
|
||||
|
||||
# Qwen2-VL-7B-Instruct 기반 VLM 엔진 기동
|
||||
docker run -d --name sglang-server --gpus all \
|
||||
--shm-size 16g \
|
||||
-p 30000:30000 \
|
||||
-v ~/.cache/huggingface:/root/.cache/huggingface \
|
||||
--ipc=host \
|
||||
lmsysorg/sglang:latest \
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path Qwen/Qwen2-VL-7B-Instruct \
|
||||
--host 0.0.0.0 \
|
||||
--port 30000 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
### 2. LiteLLM 라우팅 설정 분리
|
||||
|
||||
#### [MODIFY] [config.yaml](file:///home/admin2/Workspace/LiteLLM/config.yaml)
|
||||
- `Helios-LLM`에는 DGX1(일반 텍스트 전용)만 남기고, 이미지 입력을 받아줄 VLM 라우팅 엔드포인트인 **`Helios-VL`**을 새로이 등록합니다:
|
||||
```yaml
|
||||
model_list:
|
||||
# 1. 텍스트 전용 LLM 라우팅 (DGX1)
|
||||
- model_name: Helios-LLM
|
||||
litellm_params:
|
||||
model: openai/nex-agi/Nex-N2-mini
|
||||
api_base: os.environ/LLM_API_BASE_1
|
||||
api_key: os.environ/LLM_API_KEY_1
|
||||
|
||||
# 2. 이미지/멀티모달 전용 VL 라우팅 (DGX2 VLM)
|
||||
- model_name: Helios-VL
|
||||
litellm_params:
|
||||
model: openai/Qwen/Qwen2-VL-7B-Instruct
|
||||
api_base: os.environ/LLM_API_BASE_2
|
||||
api_key: os.environ/LLM_API_KEY_2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 검증 및 반영 순서
|
||||
|
||||
1. **DGX2 VLM 가동:** 위의 가이드대로 DGX2 호스트 터미널에서 sglang VLM 컨테이너를 가동합니다.
|
||||
2. **LiteLLM 갱신 및 재기동:**
|
||||
`config.yaml` 설정을 갱신한 뒤 프록시를 재부팅합니다:
|
||||
```bash
|
||||
cd /home/admin2/Workspace/LiteLLM
|
||||
docker compose up -d --force-recreate litellm
|
||||
```
|
||||
3. **VLM 이미지 입력 테스트:**
|
||||
멀티모달 이미지 분석이 정상적으로 우회 밸런싱되는지 로컬 curl로 이미지 분석 요청을 전송해 봅니다.
|
||||
18
monitoring/db/docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
litellm-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: litellm-db
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
38
monitoring/install_exporters.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DGX Exporter (Node Exporter & DCGM Exporter) 자동 기동 스크립트
|
||||
|
||||
echo "============================================="
|
||||
echo "1. 기존 Exporter 컨테이너가 실행 중이면 정리합니다."
|
||||
echo "============================================="
|
||||
docker rm -f node-exporter dcgm-exporter 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "============================================="
|
||||
echo "2. Node Exporter를 실행합니다. (포트: 9100)"
|
||||
echo "============================================="
|
||||
docker run -d \
|
||||
--name=node-exporter \
|
||||
--restart=always \
|
||||
--net="host" \
|
||||
--pid="host" \
|
||||
-v "/:/host:ro,rslave" \
|
||||
quay.io/prometheus/node-exporter:latest \
|
||||
--path.rootfs=/host
|
||||
|
||||
echo ""
|
||||
echo "============================================="
|
||||
echo "3. NVIDIA DCGM Exporter를 실행합니다. (포트: 9400)"
|
||||
echo "============================================="
|
||||
docker run -d \
|
||||
--name=dcgm-exporter \
|
||||
--restart=always \
|
||||
--gpus all \
|
||||
-p 9400:9400 \
|
||||
nvcr.io/nvidia/k8s/dcgm-exporter:3.3.5-3.4.0-ubuntu22.04
|
||||
|
||||
echo ""
|
||||
echo "============================================="
|
||||
echo "4. 실행된 Exporter 상태를 확인합니다."
|
||||
echo "============================================="
|
||||
docker ps -f name=node-exporter -f name=dcgm-exporter
|
||||
409
monitoring/litellm_perf_dashboard.json
Normal file
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"title": "LLM Deployment Analytics nex-agi/Nex-N2-mini",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(litellm_deployment_state{litellm_model_name=\"nex-agi/Nex-N2-mini\"})",
|
||||
"legendFormat": "Status",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "null",
|
||||
"result": { "text": "unhealthy", "color": "red" }
|
||||
},
|
||||
"type": "special"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"0": { "text": "healthy", "color": "green" },
|
||||
"1": { "text": "unhealthy", "color": "red" }
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"textMode": "value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"title": "LLM Deployment Analytics nex-agi/Nex-N2-mini-fp8",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(litellm_deployment_state{litellm_model_name=\"nex-agi/Nex-N2-mini-fp8\"})",
|
||||
"legendFormat": "Status",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "null",
|
||||
"result": { "text": "unhealthy", "color": "red" }
|
||||
},
|
||||
"type": "special"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"0": { "text": "healthy", "color": "green" },
|
||||
"1": { "text": "unhealthy", "color": "red" }
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"textMode": "value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 3
|
||||
},
|
||||
"id": 3,
|
||||
"title": "Models Latency",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.9, sum(rate(litellm_request_total_latency_metric_bucket[5m])) by (le, model))",
|
||||
"legendFormat": "{{model}} - p90",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "histogram_quantile(0.5, sum(rate(litellm_request_total_latency_metric_bucket[5m])) by (le, model))",
|
||||
"legendFormat": "{{model}} - p50",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "smooth"
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 3
|
||||
},
|
||||
"id": 4,
|
||||
"title": "Models Time To First Byte Latency",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.9, sum(rate(litellm_llm_api_time_to_first_token_metric_bucket[5m])) by (le, model))",
|
||||
"legendFormat": "{{model}} - p90",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "smooth"
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 5,
|
||||
"title": "Requests Rate",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(litellm_proxy_total_requests_metric_total[1m])) by (api_key_alias, model)",
|
||||
"legendFormat": "{{model}} ({{api_key_alias}})",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "bar"
|
||||
},
|
||||
"unit": "reqps"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 14
|
||||
},
|
||||
"id": 6,
|
||||
"title": "Cumulative Total Tokens",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(litellm_total_tokens_metric_total)",
|
||||
"legendFormat": "Total Tokens",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "smooth"
|
||||
},
|
||||
"unit": ""
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 9,
|
||||
"x": 6,
|
||||
"y": 14
|
||||
},
|
||||
"id": 7,
|
||||
"title": "Cumulative Tokens per Model",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(litellm_total_tokens_metric_total) by (model)",
|
||||
"legendFormat": "{{model}} total",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "smooth"
|
||||
},
|
||||
"unit": ""
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 9,
|
||||
"x": 15,
|
||||
"y": 14
|
||||
},
|
||||
"id": 8,
|
||||
"title": "Real-time Tokens/sec (tps)",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "dfqsz88ybzw1sa"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(litellm_total_tokens_metric_total[1m])) by (model)",
|
||||
"legendFormat": "{{model}} tps",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
"lineInterpolation": "smooth"
|
||||
},
|
||||
"unit": ""
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"refresh": "5s",
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": true,
|
||||
"value": "dfqsz88ybzw1sa",
|
||||
"text": "prometheus"
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "Datasource",
|
||||
"multi": false,
|
||||
"name": "datasource",
|
||||
"options": [],
|
||||
"query": "prometheus",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "datasource"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-5m",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "LiteLLM Performance - Compact V8 (Auto-fixed)",
|
||||
"uid": "litellm_perf_compact_v8",
|
||||
"version": 1
|
||||
}
|
||||
1237
monitoring/monitoring-stack/dashboard-1782951096844.json
Normal file
594
monitoring/monitoring-stack/dgx_live_monitor_dashboard.json
Normal file
@@ -0,0 +1,594 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"title": "TOTAL POWER",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(DCGM_FI_DEV_POWER_USAGE)",
|
||||
"legendFormat": "sum across all GPUs",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 500 },
|
||||
{ "color": "red", "value": 1000 }
|
||||
]
|
||||
},
|
||||
"unit": "watt"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"title": "MEAN GPU UTIL",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "avg(DCGM_FI_DEV_GPU_UTIL)",
|
||||
"legendFormat": "average of all devices",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 60 },
|
||||
{ "color": "red", "value": 85 }
|
||||
]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 3,
|
||||
"title": "CLUSTER MEMORY",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(DCGM_FI_DEV_FB_USED)",
|
||||
"legendFormat": "used / total (UMA)",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null }
|
||||
]
|
||||
},
|
||||
"unit": "megabytes"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Value"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "custom: / 479 GiB"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"title": "HOTTEST GPU",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "max(DCGM_FI_DEV_GPU_TEMP)",
|
||||
"legendFormat": "hottest device",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 65 },
|
||||
{ "color": "red", "value": 80 }
|
||||
]
|
||||
},
|
||||
"unit": "celsius"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4
|
||||
},
|
||||
"id": 5,
|
||||
"title": "• JOCODING1 (HEAD)",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_POWER_USAGE{instance=\"192.168.0.99:9400\"}",
|
||||
"legendFormat": "POWER",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_GPU_UTIL{instance=\"192.168.0.99:9400\"}",
|
||||
"legendFormat": "GPU",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_GPU_TEMP{instance=\"192.168.0.99:9400\"}",
|
||||
"legendFormat": "TEMP",
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_SM_CLOCK{instance=\"192.168.0.99:9400\"}",
|
||||
"legendFormat": "SM CLK",
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null }
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "POWER"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "watt" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "GPU"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "percent" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "TEMP"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "celsius" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "SM CLK"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "megahertz" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "horizontal",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4
|
||||
},
|
||||
"id": 6,
|
||||
"title": "• JOCODING2",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_POWER_USAGE{instance=\"192.168.0.100:9400\"}",
|
||||
"legendFormat": "POWER",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_GPU_UTIL{instance=\"192.168.0.100:9400\"}",
|
||||
"legendFormat": "GPU",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_GPU_TEMP{instance=\"192.168.0.100:9400\"}",
|
||||
"legendFormat": "TEMP",
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"expr": "DCGM_FI_DEV_SM_CLOCK{instance=\"192.168.0.100:9400\"}",
|
||||
"legendFormat": "SM CLK",
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null }
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "POWER"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "watt" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "GPU"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "percent" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "TEMP"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "celsius" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "SM CLK"
|
||||
},
|
||||
"properties": [
|
||||
{ "id": "unit", "value": "megahertz" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "horizontal",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 11
|
||||
},
|
||||
"id": 7,
|
||||
"title": "• JOCODING3 (OFFLINE)",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "dark-red"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "null",
|
||||
"result": {
|
||||
"text": "N/A"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 11
|
||||
},
|
||||
"id": 8,
|
||||
"title": "• JOCODING4 (OFFLINE)",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"targets": [],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "dark-red"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "null",
|
||||
"result": {
|
||||
"text": "N/A"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value"
|
||||
}
|
||||
}
|
||||
],
|
||||
"refresh": "5s",
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-15m",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "GB10 CLUSTER - LIVE MONITOR",
|
||||
"uid": "gb10_live_monitor",
|
||||
"version": 1
|
||||
}
|
||||
36
monitoring/monitoring-stack/docker-compose.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: litellm-prometheus
|
||||
volumes:
|
||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
ports:
|
||||
- "9090:9090"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--web.cors.origin=.*'
|
||||
restart: always
|
||||
networks:
|
||||
- litellm-network
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: litellm-grafana
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana-storage:/var/lib/grafana
|
||||
restart: always
|
||||
networks:
|
||||
- litellm-network
|
||||
|
||||
volumes:
|
||||
grafana-storage:
|
||||
|
||||
networks:
|
||||
litellm-network:
|
||||
external: true
|
||||
5
monitoring/monitoring-stack/install_exporters.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
docker rm -f node-exporter dcgm-exporter 2>/dev/null || true
|
||||
docker run -d --name=node-exporter --restart=always --net="host" --pid="host" -v "/:/host:ro,rslave" quay.io/prometheus/node-exporter:latest --path.rootfs=/host
|
||||
docker run -d --name=dcgm-exporter --restart=always --gpus all -p 9400:9400 nvcr.io/nvidia/k8s/dcgm-exporter:3.3.5-3.4.0-ubuntu22.04
|
||||
docker ps -f name=node-exporter -f name=dcgm-exporter
|
||||
21
monitoring/monitoring-stack/prometheus/prometheus.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'litellm-proxy'
|
||||
metrics_path: '/metrics'
|
||||
static_configs:
|
||||
- targets: ['litellm:4000']
|
||||
|
||||
- job_name: 'dgx-node-metrics'
|
||||
static_configs:
|
||||
- targets:
|
||||
- '192.168.0.99:9100'
|
||||
- '192.168.0.100:9100'
|
||||
|
||||
- job_name: 'dgx-gpu-metrics'
|
||||
static_configs:
|
||||
- targets:
|
||||
- '192.168.0.99:9400'
|
||||
- '192.168.0.100:9400'
|
||||
61
monitoring/test_load.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import urllib.request
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
|
||||
url = "http://localhost:8010/v1/chat/completions"
|
||||
api_key = "a3dde4205dacc4027f2d50f1afafb3b00de1514e0949b0187f248c940f58f120"
|
||||
|
||||
models = ["Helios-LLM"]
|
||||
prompts = [
|
||||
"Hello! How are you?",
|
||||
"Tell me a short joke about AI.",
|
||||
"What is the capital of France?",
|
||||
"Explain quantum computing in one sentence."
|
||||
]
|
||||
|
||||
print("🚀 Starting LiteLLM Load Test Client...")
|
||||
print("Every 3 seconds, a random request will be sent to LiteLLM Proxy.")
|
||||
print("Press Ctrl+C to stop.\n")
|
||||
|
||||
count = 1
|
||||
try:
|
||||
while True:
|
||||
model = random.choice(models)
|
||||
prompt = random.choice(prompts)
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}"
|
||||
}
|
||||
|
||||
data = {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 40
|
||||
}
|
||||
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps(data).encode("utf-8"),
|
||||
headers=headers,
|
||||
method="POST"
|
||||
)
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
res_data = json.loads(response.read().decode("utf-8"))
|
||||
latency = time.time() - start_time
|
||||
answer = res_data["choices"][0]["message"]["content"].replace("\n", " ").strip()
|
||||
print(f"[{count:03d}] Send to -> {model} | Status: Success (Latency: {latency:.2f}s)")
|
||||
print(f" Q: \"{prompt}\"")
|
||||
print(f" A: \"{answer[:70]}...\"\n")
|
||||
except Exception as e:
|
||||
print(f"[{count:03d}] Send to -> {model} | Status: Failed ({e})\n")
|
||||
|
||||
count += 1
|
||||
time.sleep(3) # 3초 간격 호출
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Load testing stopped by user.")
|
||||
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
# Not used (Using Docker instead of Python venv)
|
||||
115
test_context_routing.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import asyncio
|
||||
from custom_logger import multimodal_router_instance
|
||||
|
||||
async def run_context_tests():
|
||||
# 1. 테스트 케이스 1: 짧은 텍스트 요청 (128K 이하)
|
||||
# 10회 반복하여 두 모델(128k, 256k)로 골고루 분산되는지 확인
|
||||
test_data_short = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "안녕하세요. 오늘 날씨가 어떤가요?"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# 2. 테스트 케이스 2: 긴 텍스트 요청 (250K 초과)
|
||||
# 보수적 토큰 추정 알고리즘(글자수 / 2)을 넘겨 250,000 토큰을 초과하기 위해 약 56만 자의 문자열 생성
|
||||
# 14글자 * 40000 = 560,000 글자 (약 280,000 토큰 추정)
|
||||
long_text = "가나다라마바사아자차카타파하" * 40000
|
||||
test_data_long = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "당신은 유용한 AI 어시스턴트입니다. 시스템 지시사항은 보존되어야 합니다."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": long_text
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# 3. 테스트 케이스 3: 비디오/이미지가 있는 멀티모달 요청 (기존 로직 유지 확인)
|
||||
test_data_multimodal = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "이 비디오를 분석해줘"},
|
||||
{"type": "video_url", "video_url": {"url": "https://example.com/demo.mp4"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
print("=== [Test 1: 짧은 텍스트 라우팅 및 분산 검증] ===")
|
||||
results = []
|
||||
for i in range(10):
|
||||
data_copy = {
|
||||
"model": test_data_short["model"],
|
||||
"messages": test_data_short["messages"]
|
||||
}
|
||||
res = await multimodal_router_instance.async_pre_call_hook(None, None, data_copy, "completion")
|
||||
results.append(res["model"])
|
||||
print(f"시도 {i+1}: 선택된 모델: {res['model']}")
|
||||
|
||||
assert "Helios-LLM-128k" in results, "Helios-LLM-128k가 분산 선택되지 않았습니다."
|
||||
assert "Helios-LLM-256k" in results, "Helios-LLM-256k가 분산 선택되지 않았습니다."
|
||||
print("짧은 텍스트 분산 테스트 성공!\n")
|
||||
|
||||
print("=== [Test 2: 긴 텍스트 라우팅 및 자동 트렁케이션 검증] ===")
|
||||
data_copy_long = {
|
||||
"model": test_data_long["model"],
|
||||
"messages": [
|
||||
{"role": "system", "content": test_data_long["messages"][0]["content"]},
|
||||
{"role": "user", "content": test_data_long["messages"][1]["content"]}
|
||||
]
|
||||
}
|
||||
|
||||
# 수정 전 토큰 계산
|
||||
def estimate_tokens(msgs) -> int:
|
||||
total_chars = 0
|
||||
for m in msgs:
|
||||
c = m.get("content")
|
||||
if isinstance(c, str):
|
||||
total_chars += len(c)
|
||||
elif isinstance(c, list):
|
||||
for item in c:
|
||||
if isinstance(item, dict) and item.get("type") == "text":
|
||||
total_chars += len(item.get("text", ""))
|
||||
return int(total_chars / 2)
|
||||
|
||||
init_tokens = estimate_tokens(data_copy_long["messages"])
|
||||
print(f"트렁케이션 전 추정 토큰: {init_tokens}")
|
||||
|
||||
res_long = await multimodal_router_instance.async_pre_call_hook(None, None, data_copy_long, "completion")
|
||||
print(f"선택된 모델: {res_long['model']} (기대 결과: Helios-LLM-256k)")
|
||||
assert res_long["model"] == "Helios-LLM-256k", "긴 텍스트가 256K 모델로 라우팅되지 않았습니다."
|
||||
|
||||
final_tokens = estimate_tokens(res_long["messages"])
|
||||
print(f"트렁케이션 후 추정 토큰: {final_tokens} (기대 한도: <= 250000)")
|
||||
assert final_tokens <= 250000, f"트렁케이션이 정상 작동하지 않았습니다. 현재 토큰: {final_tokens}"
|
||||
|
||||
# system 메시지 보존 여부 검사
|
||||
has_system = any(m.get("role") == "system" for m in res_long["messages"])
|
||||
assert has_system, "트렁케이션 과정에서 system 메시지가 소실되었습니다."
|
||||
print("긴 텍스트 라우팅 및 자동 트렁케이션 테스트 성공!\n")
|
||||
|
||||
print("=== [Test 3: 멀티모달 라우팅 검증] ===")
|
||||
data_copy_mm = {
|
||||
"model": test_data_multimodal["model"],
|
||||
"messages": test_data_multimodal["messages"]
|
||||
}
|
||||
res_mm = await multimodal_router_instance.async_pre_call_hook(None, None, data_copy_mm, "completion")
|
||||
print(f"선택된 모델: {res_mm['model']} (기대 결과: Helios-LLM-128k 또는 Helios-LLM-256k)")
|
||||
assert res_mm["model"] in ["Helios-LLM-128k", "Helios-LLM-256k"], "멀티모달이 로드 밸런싱 풀로 라우팅되지 않았습니다."
|
||||
print("멀티모달 라우팅 테스트 성공!\n")
|
||||
|
||||
print("모든 컨텍스트 크기 기반 라우팅 및 자동 트렁케이션 테스트가 성공적으로 통과되었습니다!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_context_tests())
|
||||
134
test_video_routing.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import asyncio
|
||||
from custom_logger import multimodal_router_instance
|
||||
|
||||
async def run_tests():
|
||||
# Test case 1: Normal text request (should NOT route to Helios-VL)
|
||||
test_data_text = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, how are you?"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Test case 2: Image request (should route to Helios-VL)
|
||||
test_data_image = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "What is this?"},
|
||||
{"type": "image_url", "image_url": {"url": "https://example.com/image.png"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Test case 3: Video request with explicit video_url (should route to Helios-VL)
|
||||
test_data_video_url = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Describe this video"},
|
||||
{"type": "video_url", "video_url": {"url": "https://example.com/video.mp4"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Test case 4: Video request with image_url and mp4 extension (should route to Helios-VL)
|
||||
test_data_video_ext = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Describe this video clip"},
|
||||
{"type": "image_url", "image_url": {"url": "https://example.com/clip.webm"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Test case 5: Video request inside a plain text string content
|
||||
test_data_text_str_video = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Please analyze this video: https://example.com/demo.mp4"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Test case 6: Video request inside a list's text type content
|
||||
test_data_text_list_video = {
|
||||
"model": "Helios-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Take a look at this video link: https://example.com/preview.webm"},
|
||||
{"type": "text", "text": "What is happening here?"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Test case 7: Arbitrary model name request (should still route to Helios-LLM-32k or Helios-LLM-256k)
|
||||
test_data_arbitrary_model = {
|
||||
"model": "Some-Other-LLM",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello! Load balance me."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
valid_models = ["Helios-LLM-128k", "Helios-LLM-256k"]
|
||||
|
||||
print("--- [Test 1: Text Only] ---")
|
||||
result_text = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_text, "completion")
|
||||
print(f"Result model: {result_text['model']} (Expected: {valid_models})\n")
|
||||
assert result_text['model'] in valid_models
|
||||
|
||||
print("--- [Test 2: Image Input] ---")
|
||||
result_image = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_image, "completion")
|
||||
print(f"Result model: {result_image['model']} (Expected: {valid_models})\n")
|
||||
assert result_image['model'] in valid_models
|
||||
|
||||
print("--- [Test 3: Video URL Input] ---")
|
||||
result_video_url = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_video_url, "completion")
|
||||
print(f"Result model: {result_video_url['model']} (Expected: {valid_models})\n")
|
||||
assert result_video_url['model'] in valid_models
|
||||
|
||||
print("--- [Test 4: Video Extension Input] ---")
|
||||
result_video_ext = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_video_ext, "completion")
|
||||
print(f"Result model: {result_video_ext['model']} (Expected: {valid_models})\n")
|
||||
assert result_video_ext['model'] in valid_models
|
||||
|
||||
print("--- [Test 5: Text String with Video URL] ---")
|
||||
result_text_str_video = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_text_str_video, "completion")
|
||||
print(f"Result model: {result_text_str_video['model']} (Expected: {valid_models})\n")
|
||||
assert result_text_str_video['model'] in valid_models
|
||||
|
||||
print("--- [Test 6: Text List with Video URL] ---")
|
||||
result_text_list_video = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_text_list_video, "completion")
|
||||
print(f"Result model: {result_text_list_video['model']} (Expected: {valid_models})\n")
|
||||
assert result_text_list_video['model'] in valid_models
|
||||
|
||||
print("--- [Test 7: Arbitrary Model Name Routing] ---")
|
||||
result_arbitrary = await multimodal_router_instance.async_pre_call_hook(None, None, test_data_arbitrary_model, "completion")
|
||||
print(f"Result model: {result_arbitrary['model']} (Expected: {valid_models})\n")
|
||||
assert result_arbitrary['model'] in valid_models
|
||||
|
||||
print("All routing tests passed successfully!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_tests())
|
||||