AI & Tools

[AI 컨텍스트 #1] AI 컨텍스트 윈도우(Context Window)의 정체, 에이전트는 왜 지시를 까먹을까 (토큰·어텐션·KV 캐시)

Claude Code 같은 코딩 에이전트를 쓰다 보면 화면 아래에 "Context left until auto-compact: 8%" 같은 경고가 뜹니다. 채팅형 AI에서는 본 적 없는 표시죠. 그런데 이 숫자가 왜 줄어드는지, 0이 되면 무슨 일이 벌어지는지 설명할 수 있는…

이석우iOS Developer7분 읽기
[AI 컨텍스트 #1] AI 컨텍스트 윈도우(Context Window)의 정체, 에이전트는 왜 지시를 까먹을까 (토큰·어텐션·KV 캐시) 대표 이미지

Claude Code 같은 코딩 에이전트를 쓰다 보면 화면 아래에 “Context left until auto-compact: 8%” 같은 경고가 뜹니다. 채팅형 AI에서는 본 적 없는 표시죠. 그런데 이 숫자가 왜 줄어드는지, 0이 되면 무슨 일이 벌어지는지 설명할 수 있는 분은 의외로 적습니다. 세션 초반에 “테스트는 건드리지 마세요”라고 못 박아 뒀는데 한 시간 뒤 에이전트가 태연히 테스트 파일을 고치고 있다면, 십중팔구 이 숫자와 관련이 있어요.

이 글은 AI 에이전트의 컨텍스트를 원리부터 실전까지 다루는 연재의 1편입니다. /compact와 /clear를 언제 써야 하는지, 서브에이전트로 왜 작업을 쪼개는지 같은 실전 요령은 전부 “컨텍스트 윈도우는 유한하다”는 제약 위에 서 있습니다. 그래서 첫 편은 가장 밑바닥부터 시작합니다. 컨텍스트 윈도우가 정확히 무엇이고, 왜 무한정 키울 수 없는지를 토큰, 어텐션, KV 캐시라는 세 개의 열쇠로 풀어봅니다.

에이전트 화면의 컨텍스트 잔량 경고, 이 숫자의 정체부터 알아봅니다
에이전트 화면의 컨텍스트 잔량 경고, 이 숫자의 정체부터 알아봅니다

토큰, AI가 글을 세는 단위

컨텍스트 윈도우의 단위는 글자도 단어도 아닌 토큰(token)입니다. LLM은 텍스트를 통째로 읽지 못하고, 토크나이저(tokenizer)가 잘게 쪼갠 조각의 나열로 받아들여요. 쪼개는 기준은 BPE(Byte Pair Encoding) 계열 알고리즘인데 원리는 단순합니다. 학습 데이터에서 자주 붙어 다니는 글자 조합일수록 한 덩어리로 묶습니다. 영어의 “the”나 “ing”처럼 흔한 조합은 토큰 하나가 되고 희귀한 단어는 여러 조각으로 쪼개져요.

여기서 한국어 사용자에게 중요한 사실이 하나 나옵니다. 토크나이저 학습 데이터는 영어 비중이 압도적이라, 같은 내용이라도 한국어는 영어보다 토큰을 대체로 1.5~2배 더 씁니다. “컨텍스트 윈도우 20만 토큰”이라는 스펙이 영어 문서 기준으로는 장편소설 두 권 분량이지만, 한국어 문서를 넣으면 체감 용량이 한참 줄어드는 이유예요.

컨텍스트 윈도우는 이 토큰 기준으로 “모델이 한 번에 읽을 수 있는 최대량”입니다. 시스템 프롬프트, 대화 이력, 첨부 문서, 그리고 모델이 지금 생성 중인 답변까지 전부 이 한도 안에 들어가야 합니다.

에이전트는 왜 컨텍스트가 유독 빨리 찰까

LLM을 두고 가장 먼저 알아야 할 사실은 따로 있습니다. 모델은 상태가 없는(stateless) 기계입니다. 요청 하나를 처리하고 나면 그 내용을 어디에도 저장하지 않아요. 어제 대화는커녕 직전 턴의 대화도, 모델 입장에서는 존재한 적이 없습니다.

그럼 대화는 어떻게 이어질까요. 답은 김빠질 만큼 단순합니다. 앱이 매 턴 시스템 프롬프트부터 지금까지의 이력 전체를 통째로 다시 보냅니다. 열 번째 답변을 쓰는 모델은 앞선 아홉 번의 대화를 “기억”한 게 아니라 방금 처음부터 다시 읽은 거예요.

채팅형 AI라면 이 이력이 사람이 타이핑한 문장과 모델의 답변뿐이라 천천히 쌓입니다. 에이전트는 사정이 다릅니다. 에이전트의 한 턴에는 도구 호출이 따라붙어요. 파일 하나를 읽으면 그 파일 내용 전체가, 테스트를 돌리면 실패 로그 수백 줄이, 검색을 하면 검색 결과가 전부 컨텍스트에 쌓입니다. 사용자는 “버그 고쳐줘” 한 줄을 입력했을 뿐인데 에이전트가 파일 열 개를 읽고 빌드를 세 번 돌리면, 그 턴 하나로 수만 토큰이 사라지는 겁니다. 코딩 에이전트 화면에 컨텍스트 잔량 표시가 붙어 있는 이유가 이것입니다. 채팅에서는 며칠 걸릴 소진이 에이전트에서는 한두 시간이면 일어나거든요.

한 턴의 도구 호출만으로 수만 토큰이 쌓이고, 매 턴 전체가 다시 전송됩니다
한 턴의 도구 호출만으로 수만 토큰이 쌓이고, 매 턴 전체가 다시 전송됩니다

한도가 차면 앱은 뭔가를 버려야 합니다. 오래된 턴부터 잘라내거나 요약본으로 압축하는데, 세션 초반에 정한 “테스트 건드리지 마세요” 같은 지시가 이 과정에서 밀려나면 모델은 그런 약속을 한 적 없는 상태로 다음 턴을 시작합니다. 기억력이 나빠진 게 아니라, 애초에 기억이란 게 없고 읽을 범위 밖으로 밀려났을 뿐이에요.

유한한 이유 하나, 어텐션의 제곱 비용

그럼 윈도우를 1억 토큰쯤으로 키우면 되지 않을까요. 트랜스포머의 핵심 연산인 어텐션(attention)이 여기서 발목을 잡습니다.

어텐션은 새 토큰 하나를 처리할 때 그 토큰이 앞선 모든 토큰과 얼마나 관련 있는지를 전부 계산하는 메커니즘입니다. 코드에서 user라는 변수가 나오면 3천 줄 앞의 선언부와 연결 짓는 능력이 여기서 나와요. LLM이 문맥을 이해하는 것처럼 보이는 비결이자, 동시에 비용의 근원입니다.

모든 토큰이 모든 토큰을 바라보므로 계산량은 길이의 제곱에 비례합니다. 컨텍스트가 10배 길어지면 어텐션 연산은 100배로 뜁니다. 희소 어텐션, 슬라이딩 윈도우, FlashAttention 같은 최적화가 쌓인 덕에 100만 토큰 모델까지 나왔지만 “문맥 전체를 참조하는 능력에는 길이에 따라 가파르게 커지는 값이 붙는다”는 본질은 그대로입니다.

유한한 이유 둘, KV 캐시라는 메모리 청구서

연산만 문제가 아닙니다. 메모리 쪽에는 KV 캐시(Key-Value cache)라는 청구서가 따로 날아옵니다.

모델이 답변을 한 토큰씩 생성할 때 매번 문맥 전체를 처음부터 다시 계산하면 낭비가 큽니다. 그래서 토큰마다 계산해 둔 어텐션 중간 결과물(Key·Value 벡터)을 GPU 메모리에 쌓아두고 재사용하는데, 이게 KV 캐시입니다. 문제는 이 캐시가 레이어마다, 어텐션 헤드마다 따로 쌓여 컨텍스트 길이에 정비례해 커진다는 점이에요.

감을 잡아 보면, 700억 파라미터급 오픈소스 모델은 메모리 절약 기법(GQA)을 적용하고도 토큰당 KV 캐시가 300KB 안팎입니다. 컨텍스트를 12만 8천 토큰 채우면 캐시만 약 40GB로, 모델 가중치와 별개로 고급 GPU 한 장의 메모리를 통째로 차지합니다. 컨텍스트 윈도우 확장이 소프트웨어 설정이 아니라 하드웨어와 돈의 문제인 이유죠.

이 비용은 사용자 요금에 그대로 반영됩니다. LLM API는 입력 토큰 수에 비례해 과금하고, 매 턴 이력 전체를 다시 보내는 구조와 합치면 긴 세션이 뒤로 갈수록 비싸고 느려지는 것까지 설명이 끝납니다.

stateless 모델의 기억은 매 턴 처음부터 다시 읽기라서, 길수록 느리고 비쌉니다
stateless 모델의 기억은 매 턴 처음부터 다시 읽기라서, 길수록 느리고 비쌉니다

정리

  • 컨텍스트 윈도우는 모델이 한 번에 읽을 수 있는 최대 토큰 수입니다. 한국어는 영어보다 토큰을 1.5~2배 더 쓰니 스펙 숫자를 그대로 믿으면 안 됩니다.
  • LLM은 stateless입니다. 대화의 기억은 매 턴 이력 전체를 다시 보내는 재읽기이고, 에이전트는 도구 호출 결과까지 쌓이므로 채팅보다 컨텍스트가 훨씬 빨리 찹니다.
  • 윈도우가 유한한 이유는 어텐션 연산이 길이의 제곱으로 늘고, KV 캐시가 길이에 정비례해 GPU 메모리를 차지하기 때문입니다. 컨텍스트는 능력이면서 동시에 비용입니다.

다음 편에서는 통념 하나를 깹니다. 컨텍스트가 크면 클수록 좋은 것 아니냐는 생각인데, 실제로는 컨텍스트가 길어질수록 모델 성능이 떨어지는 현상이 여러 연구로 확인돼 있습니다. lost in the middle과 context rot, 그리고 스펙 숫자와 실효 용량이 다른 이유를 다룹니다.

이어서 읽기