geonuchoi

tiny-vllm 씹뜯맛즐 - (2)

<시리즈 - tiny-vllm 씹뜯맛즐>

2.0. LLM 성능을 정상적으로 끌어올리기

생각해보니 내가 가진 모델이 baseline 모델일 수 있다는 생각이 들어서, 확인해보니 진짜였다.

Instruct 모델로 변경한다.

https://huggingface.co/meta-llama/Llama-3.2-1B-Instruct

Llama-3.2-1B-Instruct

프롬프트도 간단하게, 권장 시스템 프롬프트와 함께 빌드되도록 템플릿을 수정한다.

결과가 달라졌는지 확인해 보자

before :

before

after :

after

(…)

Codex diagnosis

코덱스가 이렇게 주장하고 있다. RoPE에 대해 조금 공부해볼 필요가 있겠다.

RoPE가 무엇인지에 대한 간단한 설명

RoPE(Rotary Positional Embedding)는 토큰의 위치 정보를 Query와 Key 벡터의 회전 각도로 넣는 방식입니다.

Llama 3.2 1B에서는:

  • 토큰 위치가 멀어질수록 벡터를 더 많이 회전시킵니다.
  • Attention에서 두 벡터의 각도 차이를 통해 토큰 사이의 상대적 거리를 알 수 있습니다.
  • 별도의 위치 임베딩을 더하지 않고, Q/K에 직접 적용합니다.
  • Llama 3 방식의 RoPE scaling을 사용해 짧은 문맥 성능을 유지하면서 최대 128K 컨텍스트를 처리합니다. (Hugging Face)

개념적으로는 다음과 같습니다.

\[q'_m = R(m\theta)q,\qquad k'_n = R(n\theta)k\]

따라서 Attention 계산에는 절대 위치 $(m,n)$보다 상대 위치 $(m-n)$가 자연스럽게 반영됩니다.

그래서 왜 문제가 생긴 것일까

1. RoPE pairing

RoPE pairing

2. RoPE Scaling

RoPE Scaling

결론은 이해했으나… 최대한 기존 코드를 안 건드리기 위해, 앞단을 수정해달라고 요청해 보았다.

Scaling setup

(대충 Scaling을 위한 셋업 하는 부분.)

Scaling implementation

(대충 실제 Scaling하는 부분. 스케일링 작업을 쪼개서 분배하고 있다.)

Permutation reorder

(permutation을 재정렬하고 있다. 다소 낭비같지만 추후에 개선해보려고 한다.)

해결

Resolution test 1

오 된건가.

Resolution test 2

nice. 이제 진짜로 다음으로 넘어갈 수 있겠다.

2.1. LLM API 서비스의 성능 메트릭에 대해 공부해보기

LLM API 서비스의 핵심 성능 메트릭

LLM API 서버는 일반 웹 API의 응답시간·RPS·오류율만으로는 성능을 제대로 평가하기 어렵습니다. LLM은 prefill → decode 단계로 나뉘고, 요청마다 입력·출력 토큰 길이가 다르기 때문에 아래 지표를 함께 봐야 합니다.

핵심 성능 메트릭

메트릭 의미 주로 영향을 받는 요소
TTFT 요청 후 첫 토큰이 도착할 때까지의 시간 큐 대기, 입력 길이, prefill 성능
ITL 연속된 출력 토큰 사이의 평균 시간 decode 성능, GPU 메모리 대역폭, 동시 요청 수
TPOT 출력 토큰 하나를 생성하는 데 걸리는 평균 시간 ITL과 거의 같은 용도로 사용
E2E Latency 요청부터 마지막 토큰까지 전체 시간 TTFT + 출력 길이 × TPOT
Token Throughput 서버 전체가 초당 처리하는 토큰 수 배치 크기, GPU 수, 모델 크기
Request Throughput 초당 완료하는 요청 수 요청 토큰 길이 분포에 크게 의존
Queue Time 스케줄러에서 실행을 기다린 시간 서버 포화 여부, 배치 정책
KV Cache Usage KV 캐시 메모리 사용률 동시 요청 수, 컨텍스트 길이
Running/Waiting Requests 실행 중·대기 중인 요청 수 현재 부하와 포화 상태
Error Rate 실패·타임아웃·OOM 요청 비율 안정성, 용량 부족

vLLM은 TTFT, inter-token latency, queue time, request별 output-token 시간, KV cache 사용량 등을 Prometheus 메트릭으로 제공합니다. NVIDIA GenAI-Perf 역시 TTFT, ITL, 요청 처리량 및 출력 토큰 처리량을 핵심 LLM 벤치마크 지표로 사용합니다. (vLLM)


실무용 최소 대시보드

최소한 다음 12개는 보는 것이 좋습니다.

1. Request rate
2. Input tokens/s
3. Output tokens/s
4. TTFT p50 / p95 / p99
5. ITL 또는 TPOT p50 / p95 / p99
6. E2E latency p50 / p95 / p99
7. Queue time p50 / p95 / p99
8. Running requests
9. Waiting requests
10. KV cache utilization
11. GPU utilization / GPU memory
12. Error 및 timeout rate

평균값보다는 p50·p95·p99를 봐야 합니다. 평균 TTFT가 괜찮더라도 p99가 수십 초라면 일부 사용자는 심각한 지연을 경험합니다.

한 줄로 분류하면

사용자 체감 성능: TTFT, ITL, E2E latency
서버 처리 용량: output tokens/s, requests/s
포화 상태: queue time, waiting requests, KV cache usage
하드웨어 효율: GPU utilization, HBM bandwidth, memory usage
운영 안정성: error rate, timeout, OOM, restart

가장 대표적인 LLM 서버 성능 축은 결국 TTFT–ITL–Token Throughput의 균형입니다. 일반적으로 처리량을 높이려고 배치를 크게 만들면 TTFT와 ITL이 나빠지는 경향이 있으므로, 최대 처리량보다는 목표 SLO를 만족하는 최대 처리량을 찾아야 합니다.

아니 너무 많은데 초보자가 보면 좋은 구성만

초보자용 대시보드 구성

처음에는 이렇게만 만들어도 됩니다.

[트래픽]
- 요청 수 / 초
- 동시 처리 중 요청 수
- 대기 중 요청 수

[속도]
- TTFT p50 / p95
- 전체 응답시간 p50 / p95
- 출력 tokens/sec

[안정성]
- 오류율
- 타임아웃 수
- OOM 수

[자원]
- GPU 사용률
- GPU 메모리 사용률
- CPU 사용률
- RAM 사용률

초보자 기준 알람을 건다면

처음에는 이런 식으로 단순하게 잡으면 됩니다.

오류율 > 1%
TTFT p95 > 5초
전체 응답시간 p95 > 30초
대기 중 요청 수가 계속 증가
GPU 메모리 사용률 > 90%
OOM 발생

정확한 기준은 서비스 성격마다 다르지만, 초반 운영에서는 이 정도만 있어도 문제를 빨리 발견할 수 있습니다.

서비스 운영에서는 이런 부분들이 있을 수 있겠다는 생각이 든다. 고려대상이다.

2.2. 관리 / 개선할 주요 메트릭 정하고 측정 추가하기

지금은 실 운영이 아니라 속도 깎기를 위주로 공부해보고 싶으므로, 통제된 환경 아래에서 다음 데이터 위주로 보겠다.

TTFT
output tokens / sec
쿼리 당 전체 응답시간

(p50 / p99)
─────────────────────────────────────────────────────────────────────────────────────────

• 커밋 완료했습니다.

  - 커밋:   36547fe Add inference benchmark metrics
  - TTFT, output tokens/s, 전체 응답시간 계측
  - p50/p99 집계 Python 벤치마크 러너 추가
  - 동일 프로세스 warm-up/측정 라운드 지원
  - JSON/CSV 결과 저장 및 README 문서화
  - C++ 빌드, Python 검사, 실제 GPU 실행 검증 완료
  - 작업 트리 clean 상태입니다.

────────────────────────────────────

2.3. TEST suite 셋업하기

https://openrouter.ai/benchmarks/gpqa-diamond

test는 gpqa diamond랑 mmlu 중 하나를 사용해 보기로 했다.

qpqa diamond는 항목이 200개가 좀 안되는 벤치마크라 부담없이 돌려볼 수 있을 것 같음.

mmlu도 full로 돌리기엔 부담이 있지만 (16000여개) 실제로 smoke test 목적으로는 input-output token 수의 distribution을 보고 나만의 subset을 생성하는 방법론을 사용하면 될 것 같다.

gpqa-diamond의 특성을 좀 보자면,

💡

gpqa-diamond length describe & length histogram

>>> df["question"].str.len().describe()
count     198.000000
mean      623.505051
std       481.565001
min       119.000000
25%       361.750000
50%       567.000000
75%       756.750000
max      5623.000000

gpqa-diamond length histogram

💡

mmlu length describe & length histogram

>>> df["question"].str.len().describe()
count    14042.000000
mean       274.539809
std        383.306202
min          4.000000
25%         69.000000
50%        117.000000
75%        304.000000
max       4671.000000
Name: question, dtype: float64

mmlu length histogram

llama3.2를 위해 토크나이징 되었을 때는 당연히 이것보다 작다.

Tokenized comparison 1

Tokenized comparison 2

최대 토큰 수는 4K 잡으면 될듯 (4096)

다만 GPU vram을 가득 쓰면 생길 수 있는 이슈 때문에, 실제 운용 가능한 context window인지 확인이 필요하다.

Context window check 1

Context window check 2

매우 정상적으로 잘 작동하는듯.

이제 dataset을 돌려본다.