김서준지금까지는 프리필과 디코딩을 나눠 최적화해왔잖아요. 두 단계의 작업 특성부터 보면 기존 인프라가 어떤 전제 위에 설계됐는지 이해할 수 있을까요?
신정규네. 요청 하나가 들어와 답이 끝날 때까지 따라가보죠. KV 캐시를 쓰는 일반적인 자기회귀 트랜스포머를 기준으로 보면 돼요. Astra의 비공개 구조는 저도 모르지만, 기존 모델이 텍스트를 만드는 과정은 설명할 수 있습니다.
사용자가 “이 보고서를 읽고 핵심 위험을 정리해줘”라고 요청했다고 해보죠. 서비스는 질문뿐 아니라 시스템 지시, 필요한 대화 이력과 보고서를 입력으로 구성합니다. 이를 토큰 ID로 바꾸고, 실행할 서버에 작업을 배정해요. 서버가 바쁘면 대기 시간도 생깁니다. 이 준비 과정까지 전부 프리필이라고 부르지는 않습니다.
그다음이 프리필입니다. 모델이 입력 토큰들을 처리하면서 내부 표현을 계산하고, 이후 생성에서 참고할 Key와 Value를 KV 캐시에 저장해요. 캐시는 앞부분의 계산을 매번 처음부터 되풀이하지 않게 해주는 중간 결과 저장소입니다. 입력은 이미 주어져 있으니 많은 위치의 계산을 함께 처리할 수 있습니다. 다만 각 위치가 미래 토큰을 보지 못하게 하는 제약과 레이어 사이의 계산 순서는 여전히 있어요.
프리필의 마지막 위치에서 나온 출력으로 첫 번째 새 토큰의 확률을 계산하고, 생성 규칙에 따라 하나를 선택합니다. 프리필이 보고서를 읽는 단계라면 여기서 답변의 첫 조각이 정해지는 거죠. 아직 완성된 답을 머릿속에 만들어두었다가 조금씩 꺼내는 방식은 아닙니다.
김서준그러면 디코딩은 미리 정해둔 답을 화면에 풀어주는 과정이 아니라, 앞서 만든 답에 이어 무엇을 쓸지 매번 새로 계산하는 과정이군요. 그때 보고서 전체도 다시 계산하나요?
신정규앞서 처리한 입력의 Key와 Value는 재사용합니다. 방금 선택한 토큰을 모델에 넣고, 이전에 저장한 정보를 참고해서 다음 토큰의 확률을 계산해요. 새 토큰의 Key와 Value도 캐시에 추가합니다. 이렇게 선택한 토큰이 다시 다음 계산의 입력이 됩니다.
이 반복이 디코딩입니다. 일반적인 생성에서는 앞 토큰이 정해져야 다음 토큰을 계산할 수 있어서 한 답변 안에 순차적인 의존성이 있어요. 그렇다고 GPU 전체가 한 번에 토큰 하나만 처리한다는 뜻은 아닙니다. 여러 사용자의 요청을 묶어 함께 실행할 수 있고, 추측 디코딩처럼 후보 토큰 여러 개를 제안한 뒤 검증하는 최적화도 있습니다.
종료 토큰이 나오거나 설정한 길이 제한, 사용자 중지 같은 조건에 도달하면 생성을 끝냅니다. 서비스는 생성된 토큰을 텍스트로 바꿔 전달하고, 더 이상 필요 없는 요청별 메모리를 정리해요. 스트리밍이면 생성 도중부터 일부 내용을 보내고, 그렇지 않으면 결과를 모아 전달합니다. 화면에 보이는 글자 묶음과 모델이 내부에서 처리하는 토큰의 단위는 정확히 일치하지 않을 수 있습니다.
김서준사용자가 느끼는 “첫 답이 늦다”와 “답이 천천히 이어진다”는 서로 다른 문제겠네요. 각각 어떤 시간을 봐야 하나요?
신정규첫 토큰까지 걸리는 시간을 TTFT, Time to First Token이라고 부릅니다. 측정 구간에 따라 요청 전송과 대기, 입력 준비, 프리필, 첫 토큰 선택까지의 시간이 포함될 수 있어요. 따라서 TTFT가 길다고 곧바로 프리필이 느리다고 단정하면 안 됩니다. 추론 토큰을 먼저 생성하는 모델에서는 첫 내부 토큰과 사용자가 보는 첫 답변 사이에도 차이가 생길 수 있고요.
생성이 시작된 뒤에는 토큰 사이의 간격, 즉 ITL이나 초당 생성 토큰 수를 봅니다. ITL은 Inter-Token Latency예요. 긴 보고서를 넣고 짧은 답을 받는 요청은 입력 처리의 비중이 클 수 있고, 짧은 질문에 긴 글을 쓰게 하면 디코딩의 누적 시간이 커질 수 있습니다. 사용자가 기다리는 전체 시간에는 두 단계와 대기, 전송 등이 함께 들어갑니다.
김서준그렇다면 같은 긴 문서를 두고 후속 질문을 할 때도 매번 처음부터 프리필을 해야 하나요? 캐시가 비용 구조를 바꾸는 지점도 궁금합니다.
신정규서버가 같은 입력 앞부분의 KV 캐시를 재사용할 수 있도록 구현돼 있다면 이미 처리한 구간의 계산을 줄일 수 있습니다. 이를 프리픽스 캐싱이라고 부르죠. 새로 추가된 입력은 처리해야 하고, 캐시가 남아 있는지, 모델과 입력이 일치하는지 같은 조건도 맞아야 합니다. 대화 화면에 이전 내용이 보인다고 서버가 반드시 그 캐시를 유지하는 것은 아닙니다.
캐시를 오래 보관하면 재계산은 줄지만 메모리를 점유합니다. 더 많은 요청을 동시에 받을 공간과 재사용의 이익을 비교해야 하죠. 결국 요청 접수, 프리필, 디코딩, 캐시 관리와 종료까지 묶어서 운영해야 실제 비용과 응답 속도를 이해할 수 있습니다.
김서준같은 GPU에서 실행해도 입력 처리와 토큰 생성의 병목이 다른 이유는 무엇인가요? 계산량과 데이터 이동량의 차이로 봐야 할까요?
신정규병목은 전체 속도를 가장 강하게 제한하는 부분이에요. 주방으로 비유하면, 재료는 충분한데 요리가 오래 걸릴 수도 있고, 요리사는 빠른데 재료가 늦게 도착할 수도 있죠.
프리필은 많은 입력을 함께 처리하므로 계산 능력이 중요해지는 경우가 많습니다. 디코딩은 토큰을 순서대로 만들면서 필요한 데이터를 메모리에서 가져오는 속도가 중요해지는 경우가 많고요. 전자를 컴퓨트 바운드, 후자를 메모리 대역폭 병목이라고 부릅니다. 다만 배치 크기나 모델 설계에 따라 달라져서 절대적인 구분은 아닙니다.
김서준내부에서 루프를 돌리면 그 병목의 위치도 바뀔 수 있다는 말씀이군요.
신정규네. 어떤 계산을 반복하고 어떤 데이터를 다시 읽는지가 달라지니까요. 기존에 무엇을 빠르게 만드는 데 최적화했는지부터 다시 봐야 합니다.