20장. LLM 평가와 모니터링
모니터링 지표
자동차 계기판에는 속도계, 엔진 경고등, 연료계가 있습니다. 하나만 보면 차 상태를 알 수 없습니다. LLM 서비스의 계기판도 속도, 고장, 품질, 비용을 함께 봅니다.
그림의 네 카드가 지표 묶음입니다. 속도는 응답 지연의 p95, 고장은 오류율, 품질은 모름 비율과 싫어요 비율, 비용은 답 토큰 수입니다. 속도계가 지연, 경고등이 오류율, 연료계가 비용입니다. 품질은 자동차에 딱 맞는 계기가 없어, 손님의 표정을 보는 일에 가깝습니다.
지표 정의
| 지표 | 계산 | 운영 기록의 칸 |
|---|---|---|
| 질문 수 | 하루에 들어온 요청 수 | 시각 |
| 지연 p95 | 응답 시간의 95% 자리 값 | 지연초 |
| 오류율 | 오류 수 ÷ 요청 수 | 오류 |
| 모름 비율 | "자료에 없다"고 답한 비율 | 모름 |
| 싫어요 비율 | 싫어요를 누른 비율 | 피드백 |
| 답 토큰 | 답의 평균 토큰 수(비용) | 답토큰 |
평균과 p95
import numpy as np
# 요청 열 개의 응답 시간(초). 하나가 아주 느리다
lat = np.array([1.8, 2.0, 1.9, 2.1, 1.7, 2.2, 1.9, 2.0, 1.8, 9.5])
# 결과: 평균은 느린 하나에 끌려가고, 중앙값은 그것을 못 보고, p95 는 꼬리를 드러낸다
print(f"평균 {lat.mean():.2f}초 · 중앙값 {np.median(lat):.2f}초 · p95 {np.quantile(lat, 0.95):.2f}초")코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
np.array([...]) | 응답 시간 열 개를 NumPy 배열로 만든다 |
lat.mean() | 평균이다. 모두 더해 개수로 나눈다 |
np.median(lat) | 중앙값(median, 가운데 값)이다. 작은 차례로 늘어놓았을 때 한가운데 값이다 |
np.quantile(lat, 0.95) | 분위수(quantile)다. 작은 차례로 늘어놓았을 때 95% 자리의 값, 곧 p95 다 |
f"{x:.2f}" | 소수 둘째 자리까지 찍는다 |
이 코드가 뜻하는 것
택배 열 개의 도착 시간을 적어 둔 표와 같습니다. 아홉 개는 비슷한 때에 왔는데, 하나는 길을 잃어 아주 늦게 왔습니다. 여기서 택배 하나가 요청 하나, 도착 시간이 응답 시간입니다. "거의 모든 택배가 이 안에 온다"는 선이 p95 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
lat = np.array([... 9.5]) | 응답 시간 열 개, 마지막 하나가 느리다 | 늦게 온 택배 하나 |
lat.mean() | 평균 | 늦은 하나 때문에 모두 늦게 온 것처럼 보인다 |
np.median(lat) | 중앙값 | 늦은 하나를 못 본다 |
np.quantile(lat, 0.95) | p95 | 끝에 있는 늦은 택배를 드러낸다 |
평균 2.69초는 열 개 가운데 어느 요청과도 맞지 않는 값입니다. 중앙값 1.95초는 느린 요청이 없는 것처럼 보이게 합니다. p95 6.21초가 "느린 쪽 꼬리가 있다"는 것을 알려 줍니다.
작게 줄여서 눈으로 보기
값이 열 개뿐인데 p95 가 6.21 처럼 어디에도 없는 숫자로 나오는 까닭을 봅니다.
import numpy as np
lat = np.sort(np.array([1.8, 2.0, 1.9, 2.1, 1.7, 2.2, 1.9, 2.0, 1.8, 9.5]))
print(lat)
pos = 0.95 * (len(lat) - 1) # 95% 자리가 몇 번째 칸인지(0부터 센다)
print(f"자리 {pos:.2f}")
print(f"9번째 칸과 10번째 칸 사이: {lat[8] + (pos - 8) * (lat[9] - lat[8]):.2f}")
print(f"np.quantile: {np.quantile(lat, 0.95):.2f}")[1.7 1.8 1.8 1.9 1.9 2. 2. 2.1 2.2 9.5]
자리 8.55
9번째 칸과 10번째 칸 사이: 6.21
np.quantile: 6.21| 결과 | 읽는 법 |
|---|---|
[1.7 1.8 ... 9.5] | 작은 차례로 늘어놓았다. np.sort 는 정렬한 새 배열을 돌려준다 |
자리 8.55 | 0번부터 9번까지 10칸에서 95% 자리는 0.95 × 9 = 8.55 번 칸이다. 0부터 세면 8번과 9번 칸 사이다 |
9번째 칸과 10번째 칸 사이: 6.21 | 1부터 세면 9번째(2.2)와 10번째(9.5) 사이를 0.55 만큼 간 값이다. 2.2 + 0.55 × 7.3 ≈ 6.21 |
np.quantile: 6.21 | np.quantile 도 기본값으로 두 칸 사이를 곧게 이어 같은 값을 낸다 |
값이 적으면 p95 는 맨 끝 몇 개에 크게 휘둘립니다. 운영 기록처럼 하루에 요청이 많을 때 p95 가 믿을 만해집니다.
주의할 점
- 사용자의 피드백은 일부만 누릅니다. 비율이 작아 흔들리므로 한 주 단위로 보거나 이동 평균을 씁니다.
- 질문 원문을 기록에 남길 때는 개인정보가 섞이지 않게 가립니다(21장).