같은 GPU, 같은 모델인데 최적 설정이 정반대였습니다
GPU 한 장에 올린 같은 모델을, 두 곳에서 서로 다르게 세팅해야 했습니다. 추론 서버를 돌리면서 “동시 접속을 몇으로 잡아야 하나”를 고민해 보셨다면 이 글이 답을 드립니다. 결론부터 말씀드리면 그 숫자는 정해진 값이 아니라 여러분의 요청 길이가 정합니다.
글의 핵심 개념을 형상화했습니다.
쉽게 말하면
추론 서버의 메모리를 주차장이라고 생각하시면 쉽습니다. 모델이 대화 내용을 기억해 두는 공간이 주차장 면적이고 요청 하나가 차 한 대입니다. 동시에 몇 대까지 받을지 정하는 설정이 따로 있는데, 그것이 오늘의 주인공입니다.
여기에 함정이 있습니다. 면적이 같아도 경차만 오는 주차장과 대형버스가 섞여 오는 주차장은 받을 수 있는 대수가 다릅니다. 그런데 많은 팀이 이 설정을 차 종류와 무관한 고정값으로 둡니다.
즉, 사람 말로는 동시 접속 설정을 남의 값에서 베껴 오면 안 된다는 뜻입니다.

무엇을 해봤나
엔비디아 B200 한 장에 270억 개짜리 모델을 4비트로 압축해 올렸습니다. 압축을 양자화(quantization)라고 부르는데, 무게를 줄여 카드 한 장에 들어가게 만드는 작업입니다.
이 모델은 층마다 성격이 다릅니다. 64개 층 가운데 16개 층만 대화가 길어질수록 기억 공간, 즉 KV 캐시를 더 씁니다. 나머지 48개 층은 길이와 무관하게 일정한 공간만 씁니다. 덕분에 100만 토큰짜리 문맥이 카드 한 장에 들어갑니다. 모든 층이 기억 공간을 쓰는 보통 구조였다면 137기가바이트가 필요해서 한 장으로는 불가능합니다.
여기에 초안 작성기(drafter)를 붙였습니다. 작은 모델이 다음 토큰을 미리 여러 개 제안하고 큰 모델이 한 번에 검증하는 방식입니다. 저희 실측에서 제안한 토큰 열 개 중 세 개가 채택됐고 한 번 계산할 때마다 평균 3.4개 토큰이 나왔습니다.
그리고 두 엔드포인트에서 동시 접속, 즉 max_num_seqs를 1부터 256까지 올려가며 쟀습니다. 설정은 한 번에 하나씩만 바꿨습니다. 그래야 무엇이 무엇을 바꿨는지 말할 수 있습니다.
나온 결과
문맥 길이를 늘려도 메모리를 더 쓰지 않았습니다. 이게 제일 뜻밖이었습니다. 100만 토큰으로 띄운 쪽의 작업 공간이 24만 토큰으로 띄운 쪽보다 오히려 작았고 기억 공간은 더 컸습니다. 긴 문맥을 광고하는 것 자체는 공짜입니다. 대가는 첫 응답까지 걸리는 시간에서 나옵니다.
동시 입차 대수를 늘려도 주차장이 줄지 않았습니다. 설정값을 8에서 32로 네 배 올렸는데 기억 공간이 0.03퍼센트밖에 안 움직였습니다. 공간을 미리 잡아두기 때문이 아닙니다. 대수에 따라 변하는 부분이 전체의 0.5퍼센트뿐이라 눈에 안 띄는 것입니다.
그래서 병목은 워크로드마다 달랐습니다. 짧은 요청이 오는 쪽은 주차장이 남아돌아서 입차 대수가 병목이었고 늘리자 처리량이 크게 올랐습니다. 반대로 긴 요청이 오는 쪽은 요청 하나가 대형버스라 주차장이 먼저 찼습니다. 이쪽은 대수를 늘리면 오히려 손해였습니다.
주차장의 99.5퍼센트를 한 종류가 쓰고 있습니다. 무엇이 기억 공간을 차지하는지 세어 봤습니다. 대화가 길어질수록 늘어나는 16개 층이 전체의 99.5퍼센트였습니다. 나머지 48개 층은 0.5퍼센트뿐입니다. 어디를 건드려야 하는지가 이 한 줄로 정해집니다.

그래서 그 한 종류를 반으로 줄였더니 주차장이 두 배가 됐습니다. 기억 공간을 담는 숫자 형식을 절반 크기로 바꿨습니다. 주차 칸을 반으로 줄여 같은 면적에 두 배를 받는 것과 같습니다. 한쪽은 210만 토큰에서 454만 토큰이 됐고 다른 쪽은 146만에서 272만이 됐습니다. 100만 토큰짜리 요청을 동시에 받는 수도 2.1개에서 4.5개로 늘었습니다.
미리 제안하는 초안 작성기는 멀쩡했습니다. 형식을 바꾸면 초안의 채택률이 떨어질까 걱정했습니다. 바꾼 뒤에도 채택률은 30에서 37퍼센트였고 한 번에 나오는 토큰도 3.1에서 3.6개였습니다. 바꾸기 전과 같습니다.
남는 메모리는 안전 마진입니다. 저희는 그것을 한 번 어겼습니다. 엔진은 gpu_memory_utilization을 근거로 “남은 공간을 다 쓰라”며 정확한 숫자까지 알려줍니다. 저장 형식을 바꾼 뒤 그 숫자를 그대로 넣었더니 서버가 못 뜨고 재시작만 반복했습니다. 1기가바이트가 모자랐습니다. 엔진이 준 그 숫자는 형식을 바꾸기 전에 잰 값이었습니다. 형식이 바뀌면 다른 부분이 조금 더 필요해집니다. 결국 6기가바이트를 남기는 값으로 정착했습니다.
즉, 사람 말로는 설정을 최대로 밀어붙이는 것과 최적으로 맞추는 것은 다른 일이라는 뜻입니다.

나온 숫자
설정을 맞춘 뒤 카드 한 장에서 나온 결과입니다. 초당 출력 토큰 기준이고 각 단계마다 다섯 번 재서 가운데 값을 썼습니다. 이 표는 저장 형식이 넓던 시절의 값입니다. 절반으로 줄인 뒤의 처리량은 아직 없습니다.
| 동시 접속 | 전체 처리량 | 요청당 속도 | 첫 응답 |
|---|---|---|---|
| 8 | 약 1,500 토큰/초 | 약 263 토큰/초 | 0.8초 |
| 32 | 약 2,500 토큰/초 | 약 120 토큰/초 | 5.1초 |
| 128 | 약 2,870 토큰/초 | 약 42 토큰/초 | 20.0초 |
| 256 | 약 2,900 토큰/초 | 약 30 토큰/초 | 30.1초 |
카드 한 장의 천장은 초당 약 2,900 토큰이었습니다. 그리고 “요청 하나가 초당 100 토큰 이상”이라는 기준을 세우면 동시 32명까지 지켜집니다. 128명에서는 약 42 토큰으로 무너집니다.
즉, 사람 말로는 이 카드 한 장으로 동시 32명에게 사람이 읽는 속도보다 빠른 응답을 줄 수 있다는 뜻입니다.

그래서 무엇을 바꾸면 되나
저희가 얻은 규칙은 한 줄입니다. 동시 입차 대수는 주차장 면적을 평균 차 크기로 나눈 값입니다. 남의 설정을 베끼거나 기본값을 그대로 두면 어느 쪽이든 틀립니다.
그러려면 먼저 여러분의 차 크기를 재야 합니다. 저희 서비스 트래픽을 실제로 세어 보니 10건 중 약 7건이 20만 토큰 이하였고 나머지 3건이 20만에서 50만 토큰 사이였습니다. 50만을 넘는 요청은 한 건도 없었습니다. 이 분포를 모르고는 설정을 정할 수가 없습니다.

기준은 요청당 속도로 잡으시길 권합니다. 전체 처리량은 동시 접속을 올리면 계속 올라가지만 그 사이 개별 사용자가 체감하는 속도는 무너집니다. 위 표에서 128명 구간이 정확히 그 상태입니다.
설정을 만지기 전에 무엇이 공간을 쓰는지부터 세어 보시길 권합니다. 층 개수에 한 층이 토큰마다 쓰는 바이트를 곱하면 나오는 값입니다. 구조를 짐작하면 엉뚱한 노브를 잡습니다. 이 곱셈 한 번이 어디를 건드려야 두 배가 되는지 알려 줍니다.
마지막으로 진단 요령을 하나 남깁니다. 동시 접속을 올렸는데 토큰 하나당 걸리는 시간이 그대로일 때가 있습니다. 그러면 껍데기 비용을 재는 중입니다. 저희는 그 신호를 두 번 봤고 두 번 다 설정 문제였습니다. 여러분의 엔진에서도 이 값부터 확인해 보시길 권합니다.

못 믿을 부분
요청 길이 분포는 네 시간 동안 693건을 센 값입니다. 표본이 더 쌓이면 달라질 수 있습니다.
낮은 동시 접속 구간은 반복 측정 사이의 흔들림이 컸습니다. 초안 작성기의 채택률이 질문마다 다르기 때문인데, 동시 32명 이상에서는 흔들림이 1~8퍼센트로 안정됩니다. 그래서 개별 크기는 그 구간에서만 인용하시는 편이 안전합니다.
저장 형식을 절반으로 줄인 뒤의 품질은 질문 다섯 개로만 확인했습니다. 하나는 글자까지 같았고 나머지는 표현만 갈렸습니다. 그래도 다섯 개로는 “안 깨졌다”까지만 말할 수 있습니다. 손실이 없다는 뜻이 아닙니다.
그리고 저희가 아직 재지 못한 것이 하나 있습니다. 100만 토큰 문맥은 모델이 원래 지원하는 26만 토큰을 늘려서 여는 방식인데, 이 늘리기가 보통 길이의 답변 품질을 깎는지 확인하지 않았습니다. 트래픽의 대부분이 짧은 쪽이라 이 값은 저희에게도 중요합니다. 다음 실험에서 다룰 예정입니다.
이 작업이 Metis에서 갖는 의미
Metis는 저희의 추론 서빙 계층입니다. 이 글이 보여드린 것은 설정을 워크로드에 맞추는 절차입니다. 특정 모델의 성능 수치가 아닙니다. 같은 카드, 같은 모델, 같은 엔진에서 권고값이 반대로 나왔다는 사실이 그 절차가 왜 필요한지를 보여줍니다.
그리고 이 튜닝은 수단입니다. Paxis로 업무를 자동화할 때 에이전트 한 번의 실행이 얼마나 걸리고 얼마가 드는지가 결국 이 계층에서 정해집니다. 토큰 단가를 낮추는 일과 사용자가 체감하는 속도를 지키는 일이 여기서 만납니다.
이 글의 수치는 모두 저희 저장소의 측정 원장에 기록돼 있으며 인용 가능 여부를 코드가 판정한 뒤에 실었습니다.