tokens/sec 숫자만 믿다가 낭패 보는 이유 — TTFT(첫 토큰 지연)가 체감 속도를 결정한다

로컬 LLM을 처음 세팅하고 나서 가장 먼저 확인하게 되는 숫자가 tokens/sec다. “이 모델은 43 t/s 나온다”는 말을 들으면 빠르다는 인상을 받는다. 그런데 막상 써보면 질문을 입력하고 나서 한참 동안 커서만 깜빡이다가 갑자기 텍스트가 쏟아진다. 분명 숫자는 좋은데 체감은 둔하다. 이 불일치의 원인이 바로 TTFT, 즉 Time To First Token이다.

LLM 추론은 크게 두 단계로 나뉜다. 첫 번째는 Prefill, 두 번째는 Decode다. Prefill은 입력 프롬프트 전체를 모델이 한 번에 처리하는 단계다. “이 긴 글을 요약해줘”라고 2,000 토큰짜리 문서를 붙여 넣으면, 모델은 그 2,000개 토큰을 모두 읽고 내부 상태(KV 캐시)를 구성해야 한다. 이 작업이 끝나야 비로소 첫 번째 출력 토큰이 나온다. TTFT는 바로 이 Prefill 단계의 소요 시간이다.

Decode는 그다음이다. KV 캐시를 바탕으로 토큰을 하나씩 순차적으로 생성하는 단계인데, 우리가 흔히 말하는 tokens/sec는 이 Decode 속도를 측정한 값이다. 결국 벤치마크 리포트에 적힌 “43 t/s”는 Prefill이 완료된 이후, 즉 첫 토큰이 나온 다음부터의 속도다. Prefill에 얼마나 걸렸는지는 그 숫자 어디에도 담겨 있지 않다.

Prefill은 왜 느린가

Prefill과 Decode는 하드웨어 병목 지점이 다르다. Prefill은 입력 토큰 전체를 병렬로 처리하기 때문에 연산량(compute)이 집중된다. GPU나 Apple Silicon의 Neural Engine이 얼마나 많은 행렬 곱셈을 빠르게 소화하느냐에 달려 있다. 반면 Decode는 토큰 하나를 생성할 때마다 모델 가중치 전체를 메모리에서 불러와야 하므로 메모리 대역폭(memory bandwidth)이 병목이 된다.

이 차이 때문에 두 단계는 같은 하드웨어에서도 다른 방식으로 가속된다. Apple M4 Pro를 예로 들면, 통합 메모리 구조 덕분에 메모리 대역폭이 넓어 Decode 속도는 준수하다. 그런데 Prefill은 순수 연산 집약적이라 Neural Engine이나 GPU 코어 수가 절대적인 영향을 미친다. 같은 M4 Pro라도 14코어 GPU 모델과 20코어 GPU 모델 사이에 Prefill 속도 차이가 체감될 수 있는 이유가 여기에 있다.

Flash Attention은 이 Prefill 병목을 줄이기 위해 개발된 알고리즘이다. 전통적인 Attention 연산은 입력 길이의 제곱에 비례해 메모리를 쓰고, 불필요한 메모리 읽기·쓰기가 많다. Flash Attention은 Attention 행렬을 타일(tile) 단위로 나눠 연산을 묶어 처리함으로써 메모리 접근 횟수를 줄이고 캐시 효율을 높인다. llama.cpp나 MLX 같은 런타임이 Flash Attention을 지원하는지 여부가 긴 프롬프트 처리 속도에 직접적으로 영향을 준다.

성능 측정과 지연 시간 분석

이미지 출처: Unsplash

숫자로 느껴보기

짧은 질문, 가령 “파이썬에서 리스트 정렬하는 법 알려줘” 수준의 20~30 토큰짜리 입력은 Prefill 시간이 거의 없다. 이런 경우라면 tokens/sec 숫자가 체감과 대체로 일치한다. 문제는 프롬프트가 길어질 때다.

코드 파일이나 긴 문서를 첨부해 요약이나 리뷰를 요청하면 입력 토큰이 1,000개를 넘기 쉽다. RAG(검색 증강 생성) 파이프라인을 쓰거나 시스템 프롬프트가 길면 2,000~5,000 토큰도 흔하다. 이 상황에서 Prefill이 충분히 최적화되지 않은 환경이라면 TTFT가 3초, 5초, 심지어 10초를 넘을 수 있다. Decode tokens/sec가 아무리 높아도 첫 토큰이 나오기까지 10초를 기다려야 한다면 사용자는 “이 모델 느리다”고 판단한다. 그 판단은 숫자가 아니라 경험에서 나온다.

실제로 로컬 LLM 커뮤니티에서 “Ollama가 llama.cpp보다 왜 느리게 느껴지냐”는 질문이 자주 올라온다. 이유 중 하나가 Ollama의 기본 설정에서 Prefill 최적화가 런타임마다 다르게 적용되기 때문이다. 같은 모델, 같은 하드웨어에서도 어떤 런타임을 쓰느냐에 따라 TTFT가 두 배 이상 벌어질 수 있다.

제대로 된 벤치마크를 하려면

tokens/sec 하나만 측정하는 것은 마치 자동차 최고속도만 보고 시내 주행 편의성을 판단하는 것과 비슷하다. LLM 추론 성능을 제대로 평가하려면 세 가지를 모두 봐야 한다. 첫째, TTFT — 입력을 보낸 뒤 첫 토큰이 나오기까지 걸린 시간. 둘째, Prefill 속도 — 입력 1,000 토큰당 처리 시간(ms/tok 또는 tok/s). 셋째, Decode 속도 — 출력 생성 시의 tokens/sec.

그리고 이 세 가지를 짧은 프롬프트(32~128 토큰)와 긴 프롬프트(1K~8K 토큰) 두 조건에서 각각 측정해야 실제 사용 패턴을 반영한 수치가 나온다. llama.cpp의 경우 --prompt-cache 옵션이나 Flash Attention 플래그(-fa)를 켜고 끈 상태에서 비교해보면 Prefill 구간에서 체감 가능한 차이가 나타나는 경우가 많다.

결국 “어떤 모델이 빠른가”는 “어떤 용도로 쓰는가”와 분리할 수 없다. 짧은 질의응답 중심이라면 Decode tokens/sec가 체감에 가장 크게 기여한다. 반면 긴 문서를 자주 다루거나 RAG 파이프라인을 운용한다면 TTFT와 Prefill 속도가 사용자 경험을 좌우한다. 앞으로 LLM 런타임들이 더 정교한 Prefill 최적화와 스펙큘레이티브 디코딩(speculative decoding) 같은 기법을 더 적극적으로 통합해가면서 두 단계의 성능 격차가 좁혀지겠지만, 지금 당장은 벤치마크를 볼 때 tokens/sec 하나에 시선을 고정하지 않는 습관이 필요하다. 숫자의 이면에 숨어 있는 TTFT가 당신의 체감 속도를 조용히 깎아먹고 있을 수 있다.


출처

댓글 남기기