SparseEngine: 긴 에이전트 대화를 '잊기' 설계로 다시 짠 추론 엔진

서로 다른 희소 방법 15개를 한 엔진에 담은 SparseEngine이 긴 에이전트 대화의 느려짐을 어떻게 건드리는지, 그리고 '무엇을 잊을지'가 왜 우리 숙제로 남는지.
English  ·  日本語  ·  한국어
← 목록으로
온
온틀 리서치
2026년 10월 10일 수정 2026.10.17 읽기 17분 조회 2

온틀의 AI PM은 고객 프로젝트 하나를 며칠씩 끌고 간다. 요청을 받고, 파일을 열고, DB를 뒤지고, 빌드 로그를 읽고, 중간 결정을 남긴다. 이 모든 게 하나의 긴 대화로 쌓인다. 그러다 사흘째쯤 되면 묘한 일이 벌어진다. 비슷한 질문인데 답이 점점 뜸해진다. 처음엔 몇 초 만에 오던 답이, 대화가 길어질수록 눈에 띄게 느려진다.

그 느려짐의 정체가 이 글의 출발점이다. 최근 공개된 추론 엔진 SparseEngine은 바로 이 문제를 겨눈다. 서로 다른 희소 어텐션(sparse attention, 과거를 전부 보지 않고 일부만 골라 보는 방식) 방법 15개를 하나의 엔진 안에서 돌리는 '희소 우선' 엔진이다. 속도가 몇 배씩 빨라졌다는 숫자가 먼저 눈에 띄지만, 정작 흥미로운 건 그 숫자가 아니다. 이 엔진은 '어떻게 잊을지'는 풀어주면서, '무엇을 잊을지'는 고스란히 우리 손에 남겨 놓는다. 온틀에게 진짜 일거리는 거기서 시작한다. 그 길을 따라가 보자.

긴 대화는 왜 점점 느려지나

LLM은 한 번 묻고 한 번 답하는 도구를 넘어선 지 오래다. 이제는 여러 턴에 걸쳐 스스로 도구를 쓰고 추론을 이어가는 '에이전트'로 일한다. 온틀 PM이 바로 그렇게 움직인다. 그렇게 일할수록 대화 기록은 도구 호출 결과와 추론 과정을 타고 빠르게 불어난다.

핵심은 모델이 그 기록을 어디에 쌓느냐다. 모델은 대화가 길어져도 맥락을 놓치지 않으려고 지금까지 오간 내용을 전부 쥐고 있다. 이 '쥐고 있는 기억'이 KV 캐시다. 과거의 단어 하나하나에 대해 미리 계산해 둔 값이고, 통째로 GPU 메모리에 올라가 있다.

문제는 이 기억이 공짜가 아니라는 데 있다. 대화가 길어지면 두 가지가 동시에 나빠진다. 첫째, 기억이 GPU 메모리를 채운다. 꽉 차면 새 프로젝트를 시작할 자리조차 없다. 둘째, 답을 한 글자 만들 때마다 모델은 그 긴 기억을 처음부터 끝까지 다시 훑는다. 기억이 길수록 한 글자가 느려진다. 온틀 PM의 답이 사흘째 뜸해진 건 이 두 번째 때문이다. 똑똑함을 유지하려는 기억이, 그대로 비용이 된다.

희소 어텐션 계열은 이 비용을 줄이려는 시도다. 이름은 어렵지만 발상은 단순하다. 똑똑함을 지키자고 쌓아둔 기억의 비용을 깎자는 것. 어떤 방법은 기억을 다 쥔 채 지금 질문에 필요한 부분만 골라 보고, 어떤 방법은 쓸모없어진 기억을 아예 버리고, 어떤 방법은 숫자를 더 거친 눈금으로 저장해(이를 양자화quantization라 한다) 용량을 줄인다. 공통점은 '비용을 줄인다' 하나뿐, 줄이는 길은 제각각이다. 앞의 둘은 어떤 토큰을 볼지 고르거나 아예 버리는 쪽이고, 양자화나 Palu 같은 압축은 토큰을 하나도 버리지 않은 채 표현의 크기만 줄인다. 논문도 이 둘, 곧 선택·제거와 압축·양자화를 서로 다른 갈래로 나눈다.

이 제각각이 엔진 입장에선 골칫거리다. 기억을 어떻게 표현하고 언제 갱신하는지가 방법마다 다른데, 이들은 모두 같은 서빙 인프라 위에서 돌아야 한다. 서빙 인프라란 요청을 줄 세워 처리하고(스케줄링), 이미 계산한 걸 다시 쓰는(재사용) 공용 살림이다. 그런데 엔진이 '기억은 이렇게 저장하고 이렇게 갱신한다'고 미리 굳혀두는 순간, 그 틀에 안 맞는 방법은 담기 어려워진다. 저자들은 이걸 프로그래밍 언어에 빗댄다. 어떤 언어든 특정 계산은 쉽게 해주는 대신 다른 계산은 어렵게 만든다고. 추론 엔진도 똑같다.

그래서 질문은 하나로 모인다. 모두가 함께 쓰는 공용 살림과, 방법마다 다른 기억 제어. 그 경계선을 어디에 그어야 모두를 담을 수 있나.

엔진이 바꾸는 것: '어떻게 저장하나'가 아니라 '언제 끼어드나'

기존 엔진들은 대부분 경계선을 '저장'에 그었다. 기억을 페이지로 쪼개 관리하거나, GPU와 CPU에 나눠 담거나, 토큰 단위로 알뜰히 회수하거나. 저마다 자기 저장 방식에 맞는 방법엔 강했지만, 그 바깥은 담기 어려웠다.

SparseEngine은 경계선을 다른 데 그었다. '저장을 어떻게 하나'가 아니라 '언제 끼어드나'다. 트랜스포머 모델이 레이어를 하나씩 거치며 계산하는 순서는 어떤 희소 방법을 쓰든 바뀌지 않는다. 그 고정된 실행 경로 위에, 끼어들 수 있는 지점을 촘촘히 깔아둔 것이다. 프롬프트를 한 번에 처리하는 단계(프리필), 답을 한 글자씩 만드는 단계(디코딩), 레이어가 끝나는 지점, 한 스텝이 끝나는 지점. 각 방법은 자기가 필요한 지점에 코드를 걸어, 언제 기억을 추리고 언제 버릴지를 스스로 정한다. 모델 코드 자체는 한 줄도 안 건드린다. 덕분에 서로 이질적인 방법 15개가 한 엔진 안에서 돌아간다.

여기서 엔진이 분명히 갈라놓는 구분이 하나 있다. 이게 뒤에서 성능 숫자를 읽을 때 중요해진다. 기억을 '버리는 것'과 '안 보는 것'은 다르다. 저장 공간을 실제로 비워 GPU에 여유를 돌려주는 건 물리적 제거다. 저장은 그대로 둔 채 이번에 읽을 범위만 좁히는 건 논리적 선택이다. 공간을 비우는 일과 보는 범위를 줄이는 일. 효과가 전혀 다르다.

이틀째 대화를 처음부터 다시 읽지 않으려면

여기까지가 한 번의 대화 안에서 벌어지는 일이다. 그 위에 저자들은 요청과 요청 사이를 잇는 장치를 둘 더 얹었다. 온틀 PM의 하루를 따라가면 왜 필요한지 바로 보인다.

PM이 긴 빌드 로그를 읽는다. 수백 줄을 훑고 '원인은 의존성 버전 충돌'이라는 한 줄로 요약한다. 이 시점부터 로그 원문 수백 줄을 통째로 기억에 쥐고 있을 이유는 옅어진다. 쓸 만한 결론은 이미 뽑았으니까.

보통의 엔진이라면 이걸 버리기 어렵다. 다음 턴에 대화를 이어가려고 '앞부분이 그대로 남아 있다'는 전제 위에서 앞서 계산한 걸 재사용하기 때문이다(이 재사용을 프리픽스 캐싱prefix caching이라 한다). 그런데 기억을 버리는 방법은 바로 그 전제를 깬다. 앞부분을 버렸으니, 재사용이 통하지 않는다. 그래서 버리는 방법을 쓰면 다음 턴마다 긴 히스토리를 처음부터 다시 계산하는 손해를 본다.

SparseEngine의 첫 번째 장치, Chain Cache가 이걸 푼다. 세션마다 남겨둔 기억과 상태를 묶어 두었다가, 다음 턴이 들어오면 전체를 다시 계산하는 대신 남은 것에서 이어가게 한다. 일부 기억을 이미 버렸어도 '대화가 어디까지 왔는지'를 가리키는 표시는 남겨두기 때문에, 이어지는 요청은 아직 처리 안 된 뒷부분만 새로 채운다. 그래서 다음 날 같은 프로젝트 스레드가 다시 열려도, PM은 이틀치 히스토리를 처음부터 다시 읽지 않는다.

두 번째 장치는 제어 가능한 프리픽스 정리(Prefix-Cache Pruning)다. 앞의 빌드 로그로 돌아가자. 제품이 '이 구간은 이제 덜 중요하다'고 지정하면, 엔진은 그 구간에서 남길 부분만 골라내고 나머지 물리 기억을 비운다. 단, 대화의 흐름을 가리키는 표시는 건드리지 않는다. 그래서 로그 원문은 메모리에서 사라져도, 대화는 여전히 같은 스레드로 매끄럽게 이어진다. PM 한 명이 그만큼 가벼워지고, 같은 GPU로 더 많은 고객 프로젝트를 동시에 맡을 수 있게 된다.

어떤 조건에서 얼마나 유리한가

좋은 숫자는 조건을 떼면 거짓말이 된다. SparseEngine의 평가가 공들인 부분이 바로 이 조건을 갈라놓은 데 있다.

먼저 품질이다. 저자들은 기준점을 둘로 나눈다. 하나는 각 희소 방법의 원래 구현, 하나는 풀 어텐션(full attention, 과거를 하나도 안 버리고 전부 보는 방식)이다. 원래 구현과 견주면 'SparseEngine이 그 방법을 다시 구현하면서 원래 품질을 살렸나'를 재고, 풀 어텐션과 견주면 '일부만 보는 것 자체가 품질을 얼마나 깎나'를 잰다. 전혀 다른 두 질문을 섞지 않는다.

다시 구현한 쪽부터. 장문맥 벤치마크 LongBench에서 Llama-3.1-8B 기준, SparseEngine과 원래 구현의 차이는 대체로 -0.1에서 +0.8점 사이다. 사실상 같은 품질을 재현했다고 볼 만하다.

일부만 보는 비용은 어떤 방법을 고르냐에 따라 크게 갈린다. 같은 벤치마크에서 풀 어텐션이 50.7점인데, SnapKV는 50.2, OmniKV는 50.7로 거의 붙는다. 반면 StreamingLLM은 46.4, Palu는 41.8로 눈에 띄게 떨어진다. 그러니 '일부만 보면 품질이 나빠진다'가 아니다. '어떤 방법을 고르냐가 전부를 가른다'가 맞다.

고를 게 방법만은 아니다. 예산(budget), 곧 기억을 얼마나 남길지 정하는 한도도 결과를 흔든다. 추론 벤치마크 AIME에서 Qwen3-4B 모델의 풀 어텐션이 81.7%인데, SnapKV는 기억을 토큰 4천 개어치(4k)만 남겼을 때 58.3%로 주저앉았다가, 8천 개(8k)에서 80.0%, 1만6천 개(16k)에서 85.0%까지 올라온다. 남길 기억을 늘리자 풀 어텐션을 소폭 넘어서기까지 했다.

여기서 '85.0이 81.7보다 분명히 낫다'고 단정하면 성급하다. 이 점수는 30문항을 문항당 두 번, 총 60번 풀어 평균 낸 값이다. 같은 모델이라도 다음 글자를 뽑는 과정에 약간의 무작위성이 있어서, 돌릴 때마다 점수가 몇 점씩 흔들린다. 이 흔들림을 감안하면 85.0과 81.7의 차이는 오차 안에 있을 공산이 크다. 그러니 안전한 결론은 '더 좋아졌다'가 아니라 '더 나빠지지 않았다'이다. 같은 표에서 StreamingLLM은 18.3%까지 무너진다. 가속을 크게 가져갈수록 정확도 손실도 커지는 맞교환이 분명히 있다는 뜻이다.

이제 속도다. 세 개의 숫자가 돌아다니는데, 셋은 각각 다른 실험의 다른 효과라 묶어 읽으면 안 된다. 첫째, 큰 배치에서 기억을 물리적으로 버리면 기존 엔진 vLLM 대비 집계 처리량이 10배를 넘는다. 기억을 버려 확보한 메모리 여유로 '동시에 처리하는 양'을 키운 결과다. 둘째, 같은 동시 처리량 조건에서는 답을 만드는 속도가 2.5배 이상 빨라진다. 어텐션 계산 자체를 줄인 결과다. 셋째, 기록해 둔 멀티턴 대화 전체를 종단end-to-end으로 재생하는 실험에서는 2배가량(최대 2.24배) 빨라진다. 다만 이 숫자는 실험 방식을 반드시 함께 봐야 한다. 이건 에이전트가 실제로 과제를 처음부터 끝까지 수행한 완료 시간을 잰 게 아니라, 모델의 출력은 미리 기록해 둔 값으로 고정하고 도구를 기다리는 시간만 모사해 넣은 '트레이스 재생' 실험이다. 게다가 최대 2.24배는 같은 조건에서 나온 게 아니라, 동시에 처리하는 요청 수를 32개에서 72개로 함께 늘려 끌어낸 값이다. 세 숫자는 같은 방향을 가리키지만, 재는 대상이 다르다. 그래서 '2배 빨라진다'를 실제 에이전트 작업의 완료 시간이 반으로 준다거나 긴 대화 전반의 보장처럼 읽으면 틀린다. 저 2배는 기록된 대화를 재생하면서 동시 처리량까지 키웠을 때 나온, 특정 조건의 값이다.

조건이 하나 더 있다. 이득은 컨텍스트가 길 때 커진다. AIME처럼 입력이 짧으면 전체 실행 시간에서 어텐션이 차지하는 비중이 작아, 아무리 어텐션 계산을 걷어내도 걷어낼 게 별로 없다. 그래서 AIME에서의 가속은 장문맥 벤치마크보다 작았다. 이 엔진은 긴 컨텍스트를 길게 디코딩하는 작업에서 빛나고, 그렇지 않으면 기대를 낮춰야 한다.

비교의 공정성도 짚어둘 값이 있다. GLM 모델에 아주 긴 입력(128K)을 넣으면 경쟁 시스템 Vortex는 배치 1에서도 메모리가 터져 아예 못 돌았고, 다른 두 시스템은 이 모델이 쓰는 특수한 기억 압축 구조를 지원하지 못했다. 일부 설정에선 경쟁자가 출발선에조차 못 섰다는 뜻이라, 비교표의 빈칸을 볼 땐 이 맥락을 깔고 봐야 한다. 거꾸로, 경쟁 시스템이 가장 잘하는 방식으로 맞붙여도 SparseEngine이 공통 측정 구간에서 앞섰다고 저자들은 보고한다. 범용성을 얻느라 효율을 내준 것 아니냐는 의심에 대한 답이다.

그래서 온틀의 숙제는 무엇인가

여기까지가 엔진이 해주는 일이다. 온틀 입장에서 진짜 일거리는 여기서 시작한다.

이 엔진은 '어떻게 잊을지'를 안전하게 집행하는 방법을 줄 뿐, '무엇을 잊어도 되는가'는 판단해 주지 않는다. 앞의 빌드 로그는 요약이 끝났으니 버려도 됐다. 하지만 전부 그렇게 깔끔하진 않다. 고객이 사흘 전에 흘리듯 말한 요구사항이 오늘 결정의 근거가 되기도 한다. 그걸 '덜 중요하다'고 지정해 버렸다면, PM은 중요한 맥락을 잃는다. 반대로 아까워서 아무것도 안 버리면, 애초에 얻으려던 메모리 여유가 사라진다.

그러니 온틀이 먼저 설계해야 할 건 엔진이 아니라 판단 기준이다. 프로젝트 기억 중 무엇이 핵심이고 무엇이 소모품인가. 요약이 끝난 도구 결과는 버려도 되는가. 어떤 사용자 발화는 몇 턴 뒤에 다시 불려 나오는가. 이 구분을 누가, 어느 시점에, 무슨 근거로 내리는가. 엔진은 이 판단 로직을 대신 짜주지 않는다. 그건 제품의 몫이다.

운영상의 함정도 하나 딸려 온다. Chain Cache는 자리가 필요하면 가장 오래 안 쓴 세션부터 회수한다. 그러니 한참 방치됐던 프로젝트 스레드가 다시 열리면, 상태가 이미 반환돼 처음부터 다시 채우는 비용을 치를 수 있다. 어떤 프로젝트를 '살아 있는 것'으로 붙들어 둘지도 결국 우리가 정해야 한다.

온틀에게 이 논문의 값은 '빠른 엔진이 나왔다'가 아니다. 우리가 오래 미뤄둔 질문, 프로젝트 기억 중 무엇을 붙들고 무엇을 놓을 것인가를, 이제는 코드로 답해야 하는 숙제로 바꿔놓았다는 데 있다. 엔진이 '어떻게 잊을지'를 가져갔으니, 우리는 '무엇을 잊을지'에 집중하면 된다.

기술 박스: 등장하는 방법·모델·시스템 이름

희소 방법 15개는 네 계열로 묶인다. (1) 동적 희소 어텐션: 과거를 다 쥔 채 질문에 따라 볼 부분만 고름. (2) KV 제거: 쓸모없어진 기억을 실제로 버림. (3) KV 압축: 기억을 더 작게 줄여 저장. (4) KV 양자화: 숫자를 거친 눈금으로 저장. 등장 방법에는 SnapKV, H2O, Quest, OmniKV, DeltaKV, KIVI, StreamingLLM, Palu, KVzip 등이 있다.

끼어드는 지점과 콜백 몇 가지: 프리필 중 H2O는 청크(토막) 단위로 기억을 버리고, 마지막 프롬프트 청크 뒤에서 SnapKV가 선택·제거를 수행한다. 디코딩 중 Quest는 질의에 따라 읽을 페이지를 고르고, OmniKV·DeltaKV는 한 레이어에서 고른 인덱스를 다음 레이어들에서 재사용한다. KIVI는 양자화된 영역과 그렇지 않은 잔차 영역을 함께 관리한다. 이 모든 게 방법별 전용 모델 구현 없이 같은 모델 위에서 돌아간다.

평가 모델: Llama-3.1-8B-Instruct, Qwen3-4B-Thinking-2507(여러 쿼리가 키·값을 공유해 KV를 줄이는 GQA 계열), Qwen3-30B-A3B-Instruct-2507(필요한 전문가 일부만 켜는 MoE), GLM-4.7-Flash(KV를 저차원으로 압축해 들고 있는 MLA 구조), 선형 어텐션과 GQA를 섞은 Qwen3.6-27B. 하드웨어는 H100·H20·RTX PRO 6000·4090·5090.

벤치마크: LongBench V1·V2(장문맥), AIME 2024(추론), SWE-Bench Lite·Claw-Eval(멀티턴 에이전트). 비교 시스템과 각자의 저장 방식: Vortex(페이지 단위 라우팅), SPIN(GPU·CPU 계층 분담), Tangram(어텐션 헤드마다 보존량 예약 — 헤드는 모델이 문맥을 여러 각도로 쪼개 보는 단위), HiSparse(호스트 메모리에 두고 필요분만 GPU로), vToken(논리·물리 주소 분리로 토큰 단위 회수). 기준선 엔진은 vLLM(v0.26.0).

자주 묻는 질문

지금 바로 우리 서비스에 올릴 수 있나요?

코드는 공개돼 있습니다(github.com/CURRENTF/SparseEngine). 다만 이 글의 숫자는 특정 모델·하드웨어·벤치마크에서 나온 값입니다. 올리기 전에 자신의 모델과 실제 워크로드로 품질과 처리량을 다시 재보는 편이 안전합니다.

그럼 기존 엔진 vLLM을 걷어내야 한다는 뜻인가요?

성격이 다릅니다. vLLM은 희소성을 전제하지 않은 조밀(dense) 엔진이고, 그 재사용도 '기억이 고스란히 남아 있다'는 전통적 가정 위에 섭니다. SparseEngine은 희소성을 나중에 덧붙인 게 아니라 처음부터 기본값으로 깔고 바닥부터 다시 설계한 엔진입니다. 대체재라기보다 출발점이 다른 엔진으로 보는 편이 맞습니다.

벤치마크 점수를 서브태스크 하나까지 믿어도 되나요?

평균은 믿을 만하지만 세부는 조심해야 합니다. LongBench V2에서는 개별 서브태스크 점수가 더 흔들리는데, 저자들은 표본 수가 적고 다음 글자를 뽑는 과정에 무작위성이 있기 때문이라고 설명합니다. 품질 합격선은 서브태스크 하나가 아니라 평균 단위로 잡는 편이 안전합니다.

참고자료