개발 기록

Mac에서 로컬 LLM 최적화하기 / EP01

로컬 LLM은 왜 느릴까? — Prefill과 Decode

로컬 LLM의 첫 답변 대기와 생성 속도는 왜 다를까? Prefill, Decode, TTFT와 KV Cache를 정리하고 Mac Studio에서 비교할 조건을 잡아봤다.

이 글의 내용

느리다는 말부터 나눠보기로 했다

지난 글에서 8GB iMac으로 로컬 Qwen을 돌리다 포기한 얘기를 했다. 답이 나오는 속도가 답답했고, 메모리도 빠듯했다.

그런데 지금 돌아보면 당시 기록은 거의 “느리다” 하나다. 질문을 보내고 첫 답이 나올 때까지 오래 걸렸는지, 답이 나온 뒤에도 한참 기다렸는지. 각각 얼마나 걸렸는지는 남겨두지 않았다.

Mac Studio는 주문했고 아직 기다리는 중이다. M5 Max에 통합메모리 128GB, SSD 1TB. 도착하면 다시 돌려볼 생각인데, 이번에도 느낌만 적으면 비슷한 얘기를 반복할 것 같았다.

그래서 먼저 두 가지를 구분해보기로 했다. 답변을 시작하기까지의 대기와, 시작한 답변이 이어지는 속도.

Prefill은 입력을 읽고, Decode는 답을 이어간다

보통의 autoregressive 언어 모델은 다음 토큰을 예측한다. 여기서 토큰은 글자나 단어와 꼭 일치하지 않는다. 같은 문장이라도 모델의 tokenizer에 따라 잘리는 방식이 다르다.

질문을 처음 받으면 모델은 주어진 입력을 처리하고 첫 출력 토큰을 고를 준비를 한다. 이 단계가 Prefill이다. 입력은 이미 전부 주어져 있으므로, 각 레이어에서 여러 입력 위치의 계산을 병렬로 묶을 수 있다. 미래 위치를 못 보게 하는 causal mask는 있지만, 질문을 한 토큰씩 생성하듯 읽어야 한다는 뜻은 아니다.

첫 토큰을 고른 뒤에는 그 토큰까지 포함해 다음 토큰을 예측하고, 다시 그다음 토큰을 예측한다. 이것이 Decode다. 일반적인 생성에서는 앞서 뽑은 토큰이 다음 단계의 입력이 되니 출력 전체를 미리 한꺼번에 계산하기 어렵다. 이 구분은 Transformer 추론을 분석한 논문에서도 출발점으로 삼는다.

이미 주어진 입력을 Prefill로 처리한 뒤 첫 토큰을 고르고, 새 토큰을 순차적으로 생성하는 Decode 흐름
질문을 처리하는 단계와 답을 이어가는 단계. 첫 토큰은 Prefill의 마지막 출력에서 고를 수 있다. 박스 크기와 간격은 실제 소요 시간을 나타내지 않는다.

실제 서버는 입력을 작은 구간으로 나눠 처리하거나 다른 요청과 섞을 수 있다. 위 그림은 한 요청을 이해하기 위한 단순한 흐름이다. Speculative Decoding처럼 순차 생성의 비용을 줄이는 방법은 EP03에서 따로 보려고 한다.

첫 답이 늦는 것과 답이 천천히 나오는 것은 다르다

TTFT(Time To First Token)는 요청을 보내고 첫 출력 토큰을 받기까지의 시간이다. 이 글에서는 내용 없는 연결 알림이나 역할 표시를 첫 토큰으로 세지 않는다. NVIDIA의 지표 문서도 이런 빈 응답을 제외한다.

이 시간에는 입력 처리 외에 대기열, 토큰화, 출력 전송 등의 시간이 들어간다. 요청하면서 모델을 불러오는 방식이라면 로딩 시간도 끼어든다. 그래서 TTFT를 Prefill 시간과 같다고 보면 안 된다.

궁금한 것 기록할 지표 측정 구간
첫 답은 언제 오는가? TTFT 요청 전송부터 첫 내용 토큰까지
입력을 얼마나 빨리 처리하는가? Prefill throughput 엔진의 입력 처리 토큰 수와 해당 처리 시간
답변이 얼마나 빨리 이어지는가? Decode throughput 첫 토큰 이후의 생성 구간

벤치마크를 만들 때는 계산식도 적어두려고 한다.

TTFT = first_token_time - request_sent_time
prefill_tokens_per_second = processed_prompt_tokens / engine_prefill_seconds
decode_tokens_per_second = (generated_tokens - 1) / (last_token_time - first_token_time)

마지막 식은 첫 토큰을 제외한 구간의 평균이다. 출력이 한 토큰뿐이거나, 첫 토큰과 마지막 토큰이 같은 응답에 담겨 도착 시간 차이가 없으면 계산하지 않는다. 스트리밍 응답 한 덩어리가 여러 토큰을 담을 수도 있으니, 응답 덩어리 수를 토큰 수로 세는 것도 피해야 한다.

서버 내부 처리량과 사용자가 받는 속도는 따로 기록한다. 추론 모델이 reasoning 토큰을 먼저 생성하는 경우에도 첫 생성 토큰과 첫 사용자 답변은 구분할 생각이다.

같은 조건에서 캐시를 재사용하지 않는다면 긴 입력은 Prefill의 일을 늘린다. 하지만 첫 답이 늦었다고 이후 생성까지 반드시 느린 것은 아니다. 반대로 짧은 질문에 바로 답을 시작해도 큰 모델이 답을 이어가는 속도는 답답할 수 있다.

GPU가 할 일이 많을 때와, 데이터를 기다릴 때

속도 문제를 찾아보면 compute-bound와 memory-bandwidth-bound라는 말이 자주 나온다. 계산 능력이 제한하는 경우와, 계산에 필요한 데이터를 메모리에서 가져오는 속도가 제한하는 경우다.

Prefill은 여러 입력 위치를 묶어 계산하면서 가중치를 재사용하기 좋다. 충분히 큰 입력에서는 계산 장치를 바쁘게 쓰기 유리하다. 작은 배치의 dense 모델 Decode는 상황이 다르다. 토큰을 하나 만들려고 가중치를 읽는 일이 반복되는데, 읽어온 데이터당 할 계산이 상대적으로 적을 수 있다.

이 때문에 “GPU 연산 성능이 높으니 생성도 그만큼 빨라지겠지”라는 예상이 빗나갈 수 있다. 다만 Prefill은 무조건 계산 병목이고 Decode는 무조건 메모리 병목이라는 공식은 아니다. 입력 길이, 동시 요청 수, 양자화 방식, attention 구현, MoE 여부가 조건을 바꾼다. 긴 컨텍스트에서는 가중치뿐 아니라 KV Cache를 읽는 비용도 커질 수 있다. 추론 분석 논문의 하드웨어는 TPU이므로 거기에 나온 성능 수치를 내 Mac에 옮겨 쓰지는 않는다.

Apple Silicon에서는 CPU와 GPU가 통합메모리를 공유한다. MLX 문서는 CPU와 GPU가 공유 메모리의 배열을 데이터 복사 없이 사용할 수 있다고 설명한다. 그렇다고 GPU가 가중치를 읽는 일이 사라지는 건 아니다.

128GB는 우선 올려둘 수 있는 데이터의 양에 관한 선택이다. 그 데이터를 얼마나 빨리 읽고 처리하는가는 다른 문제다. 예전 iMac에서 정확히 무엇이 발목을 잡았는지도 아직 확인하지 않았다.

Mac과 NVIDIA GPU에서는 뭐가 다를까

PC용 NVIDIA 외장 GPU와도 용량과 속도를 나눠 봐야겠다.

Prefill은 입력을 병렬 처리해 연산·GPU 활용률·대역폭·커널 최적화가 중요하다. NVIDIA는 Tensor Core와 CUDA 최적화가 강점이고, Mac은 모델과 Metal·MLX 구현에 따라 다르다. 늘 계산 병목은 아니다.

작은 배치 Decode는 가중치 읽기가 병목이 되기 쉽다. NVIDIA의 전용 VRAM, Mac의 공유 메모리 대역폭이 중요하지만 KV Cache·문맥 길이·배치·MoE에 따라 달라진다.

모델·KV Cache 등이 VRAM에 들어가면 고성능 NVIDIA GPU는 두 단계 모두 빠를 수 있다. 넘치면 CPU offloading·여러 GPU가 필요할 수 있고, PCIe 전송 비용도 고려해야 한다.

대용량 Mac은 일부 큰 모델을 한 메모리 공간에 두되 OS·개발 도구 몫도 남겨야 한다. 연산·대역폭까지 우월하단 뜻은 아니다. 부족하면 swap이나 별도 SSD streaming의 비용·지원 문제도 생긴다.

비교 항목 Apple Silicon NVIDIA 외장 GPU
메모리 구조 Unified Memory VRAM + System RAM
Prefill 병목 연산·대역폭·구현 연산·대역폭·구현
작은 배치 Decode 공유 메모리 대역폭 중요 VRAM 대역폭 중요
대형 모델 실행 통합메모리 용량 활용 VRAM·오프로딩 전략
실행 도구 MLX·Metal·llama.cpp CUDA·TensorRT-LLM·vLLM 등

KV Cache는 지난 계산 일부를 남겨둔다

매번 답변 처음부터 다시 계산하면 아깝다. 일반적인 Transformer의 KV Cache는 attention에 쓰는 과거 토큰의 Key와 Value를 레이어별로 저장해 재사용한다. 새 토큰의 K와 V를 계산해서 캐시에 더하는 식이다. Hugging Face의 설명이 이 과정을 잘 보여준다.

과거 토큰의 Key와 Value를 캐시에 보관하고 새 토큰의 Key와 Value를 더해 다음 attention 계산에서 재사용하는 개념도
KV Cache를 사용하는 attention 레이어의 개념도. 과거 K와 V를 다시 만드는 일은 줄지만, attention에 필요한 캐시를 읽는 일은 남는다. 실제 크기와 메모리 사용량을 그린 그림은 아니다.

캐시가 있다고 이전 문맥을 공짜로 보는 것은 아니다. 일반적인 full attention에서는 문맥이 길어질수록 참조할 캐시도 늘어난다. 모델에 따라 sliding window나 다른 attention 구조를 쓰기도 하므로, 캐시 크기가 늘어나는 방식까지 모두 같다고 볼 수는 없다.

한 응답 안에서 쓰는 KV Cache와, 여러 요청의 공통 입력을 재사용하는 prefix cache도 구분해야 한다. MLX-LM은 prompt cache 재사용 기능을 제공한다. 같은 긴 질문을 두 번째 보냈을 때 빨라졌다면, 엔진이 더 빠른 것인지 이미 계산한 입력을 재사용한 것인지 먼저 확인해야겠다.

Mac Studio가 오면 이렇게 비교해볼 생각이다

먼저 고정할 조건

처음부터 모든 모델과 엔진을 비교하면 조건부터 뒤섞일 것 같다. 우선 한 모델, 한 런타임, 한 요청으로 시작한다. 모델 파일과 tokenizer, chat template, 양자화 설정, 런타임 버전을 고정한다. 기존에 써본 Qwen 계열을 후보로 두되, 설치할 시점에 지원되는 정확한 체크포인트를 다시 확인할 예정이다.

입력은 chat template까지 적용한 뒤 실제 토큰 수를 기준으로 짧게, 중간, 길게 나눈다. 계획한 길이는 512·2,048·8,192토큰이다. 출력 상한은 256토큰으로 맞추고 실제 생성 수와 종료 이유도 남긴다. 모델의 지원 컨텍스트를 넘는 조건은 실행하지 않는다.

모델 가중치·양자화, 실제 입출력 토큰 수·컨텍스트 길이는 맞춘다. 배치(우선 1)·warm-up·런타임/커널 버전·오프로딩도 기록한다.

엔진 처리와 요청 대기를 따로 재기

엔진 자체의 처리는 llama.cpp의 llama-bench 같은 도구로 보고, TTFT는 별도의 로컬 스트리밍 요청에서 잰다. 예를 들면 아래는 아직 실행하지 않은 기본 테스트 명령이다. 모델 파일은 실제 사용할 파일로 바꿔야 한다.

./llama-bench -m MODEL.gguf -p 512 -n 256 -r 5 -o json

이 명령의 기본 prompt 처리 테스트와 생성 테스트는 별개다. 이것만으로 “512토큰 문맥에서의 생성 속도”나 사용자 TTFT를 측정했다고 하면 안 된다. 입력 길이에 따른 생성 비교는 해당 문맥을 실제로 Prefill한 요청에서 따로 한다.

결과표는 아직 비워둔다

측정 조건은 다음처럼 나눠두려고 한다.

조건 확인할 것 결과
프로세스 시작과 모델 로딩 모델 로딩 시간, 요청 시 로딩 포함 여부 미측정
모델은 메모리에 있고 입력 캐시 재사용은 없음 입력 길이별 TTFT, Prefill, Decode 미측정
동일한 입력의 캐시 재사용 캐시 사용 여부와 재사용된 토큰 수 미측정

조건마다 준비 실행은 따로 빼고, 우선 다섯 번 반복해 중앙값과 범위를 남긴다. 소수의 반복으로 정밀한 꼬리 지연까지 설명하지는 않으려고 한다. 통합메모리 사용량 피크와 시스템 메모리 압박, swap 변화도 함께 기록한다. 측정 가능하면 GPU 활용률·전력 소비도 보되 GPU만인지 시스템 전체인지 구분한다. CPU와 GPU가 같은 메모리를 공유하므로 서로 겹치는 메모리 지표를 더해 총사용량이라고 부르지 않는다.

런타임을 바꿀 때도 둘 다 “4bit”라고 적혀 있다는 이유만으로 동일 조건이라고 하지는 않을 생각이다. 모델 변환·양자화 형식·커널이 다르면 엔진만 비교한 실험이 아니다. NVIDIA 실측 없이 직접 비교했다고 쓰지 않는다.

아직 모르는 것

지금 확실한 건 입력 처리와 출력 생성이 다른 일이라는 점이다. 기다리는 이유를 알려면 둘의 시간을 나눠 봐야 한다.

내 Mac에서 어느 단계가 더 느릴지, 메모리 여유가 생기면 체감이 얼마나 달라질지, 개발 도구를 같이 켜면 어떨지는 아직 모른다. 이번 글에는 다른 사람의 벤치마크 수치를 내 예상 결과처럼 넣지 않았다. 두 그림도 동작을 설명하기 위한 도식이다.

다음 글은 「128GB Mac에서도 메모리가 부족할 수 있는 이유」다. 모델 파일 외에 무엇이 메모리를 차지하는지, 큰 모델 하나와 여러 모델을 함께 쓰는 방식 사이에서 어떤 선택이 필요한지 정리해보려 한다.

같은 예산이면 Mac Studio일까, RTX GPU PC일까? 대형 모델 실행과 Prefill·Decode, 이미지·영상 생성까지 보면 어느 쪽이 효율적일까?

사양과 전체 구축 비용은 EP05에서 따져보려 한다.

참고 자료

아래 자료는 2026년 10월 8일 확인했다. 논문의 실험 장비와 내 주문 장비는 다르며, 프로젝트 기능은 설치할 때 다시 확인할 예정이다.

이 글은 Mac Studio M5 Max 128GB가 도착하기 전에 정리한 기술 조사 기록입니다. 실제 성능은 추후 동일한 조건에서 측정하고 별도의 실험 글로 공개할 예정입니다.