ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • LLM의 KV 캐시를 CPU로 옮기면 어떤 대가가 생길까
    AI Agent 2026. 9. 22. 08:36
    728x90
    반응형

    작은 트레이와 큰 바구니 사이의 좁은 다리 위에 놓인 나무 블록
    추가 저장 공간을 쓰기 위해 데이터가 이동하는 AI 생성 개념 이미지입니다.

    KV 캐시 offloading은 GPU 메모리 부족을 줄일 선택으로 검토하되 CPU 메모리와 데이터 이동에 드는 시간을 함께 측정하세요.

    짧은 질문에는 답하던 로컬 LLM이 긴 문서를 넣으면 GPU 메모리 부족으로 멈춥니다. 모델 가중치는 이미 올라갔는데 생성 도중 부족해진다면 KV 캐시가 커지는 상황도 살펴볼 수 있습니다. 이때 캐시를 CPU 메모리로 옮기는 offloading이 후보가 됩니다.

    다만 “CPU로 옮기면 같은 속도로 더 긴 입력을 처리한다”는 보장은 없습니다. 저장 위치를 바꾸는 대신 데이터를 옮기는 비용이 생기기 때문입니다. 이 글은 2026년 9월 13일 확인한 Transformers v5.17.0 문서 기준의 설명입니다. 메모리 수치는 가상 구성의 산술이며 실제 모델 실행이나 벤치마크 결과가 아닙니다.

    KV 캐시는 모델 가중치와 별개의 데이터입니다

    자기회귀 언어 모델은 앞의 토큰을 바탕으로 다음 토큰을 생성합니다. attention에서 이전 토큰의 key와 value를 매번 다시 계산하지 않도록 저장하는 것이 KV 캐시입니다. 새 토큰의 K/V를 더하면서 이전 계산을 재사용합니다. KV 캐시가 재사용하는 것

    가중치는 학습된 모델의 매개변수이고, KV 캐시는 현재 처리하는 입력과 생성 과정에서 생기는 상태입니다. 같은 모델을 쓰더라도 입력 길이, 생성 길이, 배치와 생성 방식에 따라 필요한 캐시가 달라질 수 있습니다.

    따라서 가중치를 GPU에 올릴 공간이 있다는 사실만으로 긴 문서의 추론까지 가능하다고 판단하면 안 됩니다. 캐시 외에도 중간 계산과 실행용 메모리가 필요합니다. 반대로 가중치를 불러오는 단계부터 실패한다면 KV 캐시 offloading만으로 해결할 문제라고 보기 어렵습니다.

    모델 가중치를 여러 장치에 배치하는 device_map이나 CPU·디스크 가중치 offloading은 다른 기능입니다. 같은 offloading이라는 표현을 쓰더라도 무엇을 옮기는지 구분해야 합니다. 가중치 배치를 다루는 Big Model Inference

    단순한 가상 구성으로 캐시의 크기를 계산해 봅니다

    일반적인 K/V 캐시는 레이어별로 배치, 헤드, 시퀀스 길이, 헤드 차원의 텐서를 저장합니다. 이 구조를 이용하면 실제 할당 전에도 어느 조건이 크기를 늘리는지 대략 살펴볼 수 있습니다. 캐시 텐서의 구조

    다음은 모든 레이어가 같은 크기의 전체 문맥 K/V를 저장한다고 가정한 계산입니다. 특정 모델의 사양이 아니며, 여기서 헤드 수는 실제로 저장되는 K/V 텐서의 헤드 수입니다.

    layers = 32
    batch = 1
    cached_tokens = 8192
    kv_heads = 8
    head_dim = 128
    bytes_per_value = 2
    
    kv_bytes = (
        2 * layers * batch * cached_tokens
        * kv_heads * head_dim * bytes_per_value
    )
    assert kv_bytes == 1_073_741_824
    assert kv_bytes / (1024 ** 3) == 1
    

    맨 앞의 2는 K와 V 두 종류를 합친 것입니다. 이 조건의 순수 K/V 데이터는 1GiB입니다. 1GB라고 바꿔 적지 않는 것은 단위 기준이 다르기 때문입니다.

    같은 단순 가정에서 캐시 토큰 수를 두 배로 만들면 2GiB가 됩니다. 배치를 두 배로 해도 같은 방향으로 커집니다. 하지만 이 값을 해당 모델 실행의 GPU 최대 메모리라고 보고하면 안 됩니다. 가중치, 중간 텐서, 메모리 할당 여유분과 전송 과정의 공간은 계산에 들어 있지 않습니다.

    sliding window나 chunked attention을 사용하는 레이어는 특정 길이 뒤 캐시가 계속 같은 방식으로 커지지 않을 수 있습니다. 레이어마다 구조가 다른 모델이나 별도 상태 캐시를 쓰는 모델도 있습니다. 단순식은 조건의 영향을 이해하는 예시이고, 실제 모델은 설정과 캐시 구현을 확인해야 합니다. 모델별 캐시 차이

    GPU 공간을 줄이는 대신 전송 시간이 듭니다

    Transformers의 캐시 offloading은 레이어 캐시를 주로 CPU에 두고 현재 계산에 필요한 캐시를 GPU로 가져오는 방식입니다. 다음 레이어의 캐시를 미리 가져오고 계산한 레이어의 캐시를 CPU로 되돌리는 동작도 포함됩니다. Cache offloading

    GPU에 모든 레이어의 캐시를 계속 두는 것보다 공간을 줄일 수 있지만, 데이터를 저장할 CPU 메모리가 필요합니다. GPU 메모리 부족을 CPU 메모리 부족으로 옮겼다면 여전히 운영 가능한 구성이 아닙니다.

    또한 CPU와 GPU 사이의 이동 시간이 생깁니다. 이 비용이 얼마나 드러나는지는 문맥 길이, 생성 설정, 하드웨어와 실행 방식에 따라 달라집니다. 문서의 일반적인 설명을 내 장비의 초당 토큰 수나 일정한 감소율로 바꿔 쓰면 안 됩니다.

    앞의 1GiB 계산을 그대로 “GPU 메모리 1GiB 절감”이라고 주장할 수도 없습니다. 현재 레이어의 계산과 미리 가져오는 데이터, 다른 실행 메모리가 함께 존재하므로 최대 사용량은 실제로 측정해야 합니다. 옮길 데이터의 총량과 절감된 최대 할당량은 다른 값입니다.

    옵션을 바꾸기 전에 비교할 기준을 고정합니다

    v5.17.0 문서는 동적 캐시의 경우 cache_implementation을 offloaded로, 정적 캐시는 offloaded_static으로 설정하는 경로를 안내합니다. 다음은 이미 준비된 모델과 입력에 적용할 호출 형태입니다. 모델 다운로드·로딩을 포함한 전체 실행 스크립트는 아닙니다.

    output_ids = model.generate(
        **inputs,
        do_sample=False,
        max_new_tokens=64,
        use_cache=True,
        cache_implementation="offloaded",
    )
    

    model과 inputs는 서로 맞는 모델·토크나이저·입력 형식으로 준비돼 있어야 하며, 대상 모델과 장치에서 해당 캐시 구현이 지원되는지 확인해야 합니다. 옵션 이름 하나로 모든 모델과 런타임의 호환성이 생기는 것은 아닙니다. 캐시 전략 지정

    비교에서는 같은 모델 파일과 버전, 같은 입력 토큰, 같은 생성 한도와 디코딩 설정을 유지합니다. offloading을 켜면서 입력을 줄였거나 배치를 바꿨다면 어느 변화가 메모리를 줄였는지 분리하기 어렵습니다.

    기본 캐시에서 OOM이 났다면 그 조건을 실패로 기록합니다. 실패한 실행의 생성 속도를 0으로 두고 성공한 offloaded 실행과 배수 비교를 만드는 방식은 적절하지 않습니다. 한쪽은 완료하지 못했으므로 우선 “이 조건에서 완료 가능한가”를 비교해야 합니다.

    짧은 입력에서는 두 방식이 모두 완료되는 기준 사례를 두고, 긴 입력에서는 한계가 어디서 나타나는지 별도로 봅니다. 실제 운영에서 자주 쓰는 입력 분포가 무엇인지가 중요하며 가장 긴 한 건만으로 모든 요청의 기본값을 정하지 않습니다.

    메모리 한 줄보다 네 가지 결과가 필요합니다

    첫째는 완료 여부와 실제 생성한 토큰 수입니다. 생성 한도를 64로 설정했다고 항상 64토큰을 출력한 것은 아닙니다. 종료 토큰 등으로 일찍 끝났다면 그 길이를 그대로 기록해야 합니다.

    둘째는 GPU와 CPU 메모리입니다. 장치 도구가 보여 주는 메모리와 프레임워크의 할당·예약 메모리는 측정 범위가 다를 수 있으므로 같은 항목을 전후에 비교합니다. CPU 쪽에서는 운영체제와 다른 프로세스가 쓸 여유도 남아야 합니다.

    셋째는 입력을 처리하고 첫 토큰을 받기까지의 시간과 이후 생성 처리량입니다. 전체 실행 시간을 모두 같은 의미의 “속도”로 부르지 않습니다. 긴 입력 처리 비용과 토큰별 생성 비용이 다른 양상으로 나타날 수 있습니다.

    넷째는 반복 간 변동과 실제 출력입니다. 모델 로딩과 준비 시간을 포함했는지, 준비 실행과 측정 실행을 어떻게 나눴는지 적습니다. 캐시 방식만 바꿨다는 이유로 출력의 동등성을 확인하지 않고 전제하지는 않습니다.

    이 항목들은 측정 설계이지 이 글에서 얻은 결과가 아닙니다. 숫자를 채우기 전에는 절감량과 지연이 미확인 상태라는 점을 유지해야 합니다.

    sliding 레이어와 양자화는 같은 선택이 아닙니다

    DynamicCache나 StaticCache를 직접 만들 때는 offload_only_non_sliding 옵션으로 sliding 계열 레이어까지 옮길지 제어할 수 있습니다. v5.17.0 API에서 기본값은 DynamicCache가 False, StaticCache가 True입니다. 짧은 캐시까지 옮기는 비용을 고려하는 선택입니다. DynamicCache와 StaticCache API

    따라서 offloading=True만 비교했다고 두 캐시 클래스가 같은 레이어를 옮긴다고 가정하지 않습니다. 직접 생성한 캐시라면 클래스와 옵션을 함께 기록해야 합니다. 기본값이 다른데 클래스 이름만 지워 비교하면 재현 조건이 사라집니다.

    KV 캐시 양자화는 값의 정밀도를 낮춰 저장량을 줄이는 별도 전략입니다. CPU로 저장 위치를 옮기는 것과 동일하지 않으며 지원 조합도 따로 확인해야 합니다. 가중치 양자화, 캐시 양자화, 캐시 offloading을 모두 “모델을 작게 했다”로 묶지 않는 편이 좋습니다.

    입력 길이·배치·생성 길이를 업무 요구 안에서 줄이는 선택도 있습니다. 다만 필요한 문서를 잘라 놓고 메모리 문제가 해결됐다고 보고하면 원래 작업을 달성한 것은 아닙니다. 어떤 요구를 유지하고 무엇을 포기했는지 함께 적어야 합니다.

    완료 가능성과 기다릴 만한 시간을 함께 봅니다

    KV 캐시 offloading은 긴 입력에서 GPU 메모리 한계를 넘기 위한 유용한 후보입니다. 하지만 기본 캐시로 충분히 처리되고 지연이 중요한 상황이라면 CPU 전송 비용을 추가할 이유가 있는지 먼저 따져야 합니다.

    최종 선택은 동일한 작업이 완료되는지, CPU 메모리 여유가 있는지, 실제 사용자가 기다릴 수 있는 시간인지에 달려 있습니다. GPU 사용량이 줄었다는 한 줄에서 멈추지 말고 캐시가 어디로 갔고 그 이동에 어떤 비용을 냈는지까지 확인해야 합니다.

    728x90
    반응형
Designed by Tistory.