baidu/Unlimited-OCR. DeepSeek-OCR을 베이스라인으로 삼아, 디코더의 모든 어텐션을 R-SWA(Reference Sliding Window Attention)로 교체해 출력이 아무리 길어져도 KV 캐시를 일정한 크기로 유지하는 end-to-end 비전-언어(VLM) OCR 모델입니다. 기존 디코더형 OCR이 페이지마다 루프를 돌며 메모리를 리셋해야 했던 것과 달리, 수십~수백 페이지짜리 책 한 권을 단 한 번의 forward pass(one-shot)로 끝까지 파싱합니다. 게다가 메모리·속도만 아낀 게 아니라 정확도까지 올라간 '공짜 점심(free lunch)'입니다. (저장소: baidu/Unlimited-OCR · 언어 Python · 라이선스 MIT · ★ 미공개 · 2026-06-22 공개된 신생 레포(공개 1일차) · TrendShift Weekly #10)
"OCR 모델은 '한 페이지를 읽을' 줄은 안다. Unlimited-OCR은 '책 한 권을 끊지 않고 끝까지' 읽는다."
보통의 디코더형 OCR(글자를 한 토큰씩 뱉는 LLM 기반 OCR)은 글을 길게 쓸수록 자기가 지금까지 쓴 모든 토큰을 기억(KV 캐시)해야 해서, 출력이 길어질수록 메모리가 선형으로 불어나고 속도가 떨어집니다. 그래서 기존 모델들은 "한 페이지 읽고 → 메모리 비우고 → 다음 페이지" 식으로 for 루프를 돌며 강제로 끊어 처리했습니다. Unlimited-OCR은 디코더의 어텐션 방식을 R-SWA로 바꿔, 아무리 길게 출력해도 기억 부담을 일정하게 유지합니다. 그 결과 책 전체를 한 번에 연속으로 읽으면서도, 벤치마크 정확도(OmniDocBench)는 베이스라인 DeepSeek-OCR보다 오히려 올라갔습니다.
좀 더 구체적으로, Unlimited-OCR은 두 갈래의 추론 프레임워크를 제공합니다. 가벼운 단일 추론은 HuggingFace Transformers로(AutoModel.from_pretrained(..., trust_remote_code=True) → model.infer()), 멀티페이지·PDF·대규모 처리는 SGLang 서버로(레포에 패치된 휠 동봉, OpenAI 호환 /v1/chat/completions 스트리밍) 돌립니다. PDF를 넣으면 PyMuPDF가 300DPI PNG로 변환하고, 페이지 사이에 <page> 구분자를 끼워 한 번에 흘려보냅니다.
이 문서가 파고드는 건 사용법보다 그 안의 설계입니다 — "왜 KV 캐시가 문제인가", "R-SWA는 정확히 무엇을 빼고 무엇을 남기나", "왜 visual 토큰만큼은 절대 잊으면 안 되나(progressive blurring 방지)". 트랜스포머 어텐션·KV 캐시·VLM OCR을 처음 공부하는 사람에게 살아 있는 교본입니다.
"긴 문서 OCR의 진짜 적은 '글자를 못 읽는 것'이 아니라 '길어질수록 기억이 폭증하는 것'이다. Unlimited-OCR은 그 지점을 정조준한다."
LLM 디코더를 쓰는 OCR은 글자를 한 토큰씩 자기회귀적으로 뱉습니다. 이때 디코더는 지금까지 만든 모든 토큰의 키/값(KV)을 캐시해 두고 다음 토큰을 예측합니다. 문제는 출력 토큰 수 T가 커질수록 KV 캐시도 선형으로 커진다는 것 — 책 한 권을 마크다운으로 풀면 수만~수십만 토큰이 나오므로, 메모리가 폭증하고 속도가 급락합니다. 그래서 기존 모델들의 우회책은 "페이지마다 for 루프로 끊고 메모리를 리셋"하는 것이었고, 이건 페이지 경계의 문맥(앞 페이지에서 이어지는 표·문단)을 잃게 만듭니다.
표준 멀티헤드 어텐션(MHA)에서 출력 토큰 t는 이전의 모든 토큰을 본다(causal full attention). 그래서 KV 캐시 크기는 prefix 길이 L_m에 출력 길이 T를 더한 L_m + T로 T에 비례해 무한히 증가합니다. 6,000 토큰만 뽑아도 메모리·연산이 부담스럽고, 책 한 권 분량(수만 토큰)이면 단일 forward pass는 사실상 불가능했습니다. 이게 "페이지마다 끊어 처리"라는 우회책이 나온 근본 이유입니다.
Unlimited-OCR은 디코더의 모든 어텐션을 R-SWA(Reference Sliding Window Attention)로 교체합니다. 핵심은 출력 토큰이 보는 대상을 ① 전체 prefix P(이미지+프롬프트, 항상 가시) + ② 직전 n개 출력 토큰(기본 128)만으로 제한하는 것. 그 결과 KV 캐시 크기는 L_m + min(n, T) ≤ L_m + n으로 상한이 고정됩니다. 출력이 1만 토큰이든 10만 토큰이든 기억 부담이 같으니, 책 전체를 단번에 흘릴 수 있습니다.
보통 "메모리를 아끼려고 어텐션을 자르면" 성능이 떨어질 것 같지만, Unlimited-OCR은 정확도까지 향상됐습니다. OmniDocBench라는 표준 문서 파싱 벤치마크에서의 수치입니다.
| 지표 (OmniDocBench) | DeepSeek-OCR (베이스라인) | Unlimited-OCR | 변화 |
|---|---|---|---|
| Overall (v1.5) | 87.01% | 93.23% | +6.22%p |
| Text Edit Distance ↓ (낮을수록 좋음) | 0.073 | 0.038 | 거의 절반 |
| Table TEDS ↑ | 기준 | — | +5.96%p |
| Overall (v1.6) | — | 93.92% | end-to-end SOTA |
| 출력 속도(TPS) @ 6,000 토큰 | 기준 | — | +35% (길수록 격차↑) |
v1.6 기준 Overall 93.92%로, dots.ocr·HunyuanOCR·Qwen3-VL 같은 다른 end-to-end 모델들을 앞서는 SOTA(State-of-the-art)를 주장합니다. 속도도 6,000 토큰 출력 시 TPS(초당 토큰)가 35% 빠르고, 문서가 길수록 격차가 더 벌어집니다 — 표준 어텐션은 길어질수록 느려지는데 R-SWA는 일정하기 때문입니다. 이렇게 "메모리↓ + 속도↑ + 정확도↑"가 동시에 일어나서 개발팀은 이를 'free lunch(공짜 점심)'라 부릅니다.
표준 디코더 = 받아쓰기하며 노트 전체를 매번 다시 읽는 학생. 문장을 하나 쓸 때마다 지금까지 쓴 노트 전체를 처음부터 훑어 맥락을 확인합니다. 노트가 길어질수록 한 문장 쓰는 데 점점 오래 걸리고, 결국 책 한 권은 한 번에 못 씁니다.
R-SWA = '방금 쓴 몇 줄 + 책상 위에 펼쳐 둔 원본 사진'만 보는 학생. 전체 노트를 다시 안 읽고, 바로 앞 128 토큰과 항상 펼쳐 둔 원본 이미지(prefix)만 봅니다. 그래서 얼마를 쓰든 한 문장 쓰는 비용이 일정합니다. 원본 사진을 절대 안 치우는 게 핵심 — 사진을 치우면(visual 토큰을 잊으면) 뒤로 갈수록 흐릿하게 베껴 쓰는 'progressive blurring'이 생깁니다.
PaddleOCR류는 "검출 + 다중 인식기 + 휴리스틱 후처리"를 엮은 파이프라인이라, 모듈마다 따로 튜닝하고 단계 사이 오차가 쌓입니다. Unlimited-OCR은 단일 통합 VLM이라 이미지를 넣으면 마크다운이 바로 나옵니다 — 검출기도, 후처리 규칙도 없습니다. 대신 모델 하나에 모든 부담이 실리므로 모델 구조(어텐션·인코더 압축)의 효율이 곧 전체 효율입니다. R-SWA가 바로 그 효율의 열쇠입니다.
솔직하게 짚겠습니다. 위 수치들은 개발팀이 자체 측정했을 가능성이 있는 값이고, 이 레포는 2026-06-22 공개된 공개 1일차 신생 프로젝트입니다. 스타 수와 추론에 필요한 정확한 VRAM은 아직 공개·검증되지 않았습니다. 또 모델 정의 코드·가중치는 레포 안이 아니라 HuggingFace 원격에 있고, README에는 kernels 패키지 버전이 본문(0.11.7)과 설명(0.9.0)에서 서로 다르게 적힌 자체 모순도 있습니다. 방향성("어텐션을 고쳐 KV 캐시를 상수로")은 매우 설득력 있는 정공법이지만 — 구체 수치는 제3자 재현 전까지는 참고치로 보시는 게 안전합니다.
"언어는 Python이지만 진짜 핵심은 '인코더의 16배 압축'과 'R-SWA로 다시 짠 디코더 어텐션'에 있다."
Unlimited-OCR은 비전-언어 모델(VLM)이라 추론에 GPU가 필요한 무거운 모델이지만, 레포 자체는 의외로 단순합니다. 모델 정의·가중치는 HuggingFace에 있고, 레포에는 주로 추론 스크립트·기술 리포트·SGLang 패치 휠이 들어 있습니다. 무게중심은 두 곳 — 이미지를 256 토큰으로 압축하는 DeepEncoder와 KV 캐시를 상수로 묶는 R-SWA 디코더입니다.
| 구성 | 역할 | 한 줄 설명 |
|---|---|---|
| DeepEncoder (SAM-ViT + CLIP-ViT) | 비전 인코더 | SAM-ViT(윈도우 어텐션)와 CLIP-ViT(글로벌 어텐션)를 캐스케이드로 잇고, 그 사이 16배 토큰 압축 브리지를 둔다. 1024×1024 이미지를 단 256 visual tokens로 압축. |
| MoE LLM 디코더 (3B-A0.5B) | 텍스트 생성 | 총 파라미터 3B, 활성 파라미터 500M(Mixture-of-Experts). 전 레이어 어텐션이 R-SWA로 교체됨. 마크다운을 자기회귀로 생성. |
| R-SWA (Reference Sliding Window Attention) | 어텐션 방식 | 출력 토큰이 전체 prefix(P) + 직전 n=128 토큰만 본다. KV 캐시를 상수로 묶는 이 모델의 심장. |
| 베이스라인 | 출발점 | DeepSeek-OCR의 구조를 베이스로, 디코더 어텐션만 R-SWA로 갈아끼웠다. |
3B-A0.5B는 "총 파라미터는 3B인데, 한 번 추론할 때 실제로 켜지는(active) 파라미터는 0.5B(500M)"라는 뜻입니다. 덕분에 큰 모델의 표현력은 가지면서 추론 비용은 작은 모델 수준으로 유지합니다. "활성 파라미터"는 추론 속도·메모리를 좌우하는 실질적 크기입니다.| 경로 | 언제 쓰나 | 핵심 |
|---|---|---|
| HuggingFace Transformers | 단일 이미지·간단 추론 | AutoModel.from_pretrained(..., trust_remote_code=True, torch_dtype=torch.bfloat16). model.infer()(단일) / model.infer_multi()(멀티·PDF). |
| SGLang | 멀티페이지·PDF·대규모 | 레포에 패치된 휠 동봉(wheel/sglang-...whl). OpenAI 호환 /v1/chat/completions 스트리밍 서버. --attention-backend fa3(FlashAttention v3). |
| 반복 억제 | (SGLang 경로) | 커스텀 로짓 프로세서 DeepseekOCRNoRepeatNGramLogitProcessor(ngram_size=35, window_size=128)로 같은 구절이 무한 반복되는 것을 막는다. |
| 패키지 | 버전 | 역할 |
|---|---|---|
| torch | 2.10.0 | 딥러닝 코어. bfloat16 추론. |
| torchvision | 0.25.0 | 이미지 변환·전처리. |
| transformers | 4.57.1 | HF 추론 경로(AutoModel). |
| pymupdf | 1.27.2.2 | PDF → 300DPI PNG 변환. 멀티페이지 입력의 관문. |
| Pillow | 12.1.1 | 이미지 입출력(PNG 등). |
| einops | 0.8.2 | 텐서 차원 재배열(어텐션 구현에 자주 쓰임). |
| addict / easydict | 2.4.0 / 1.13 | 설정 객체를 점(.) 접근으로 다루는 유틸. |
| kernels | 0.11.7 ⚠️ | 커스텀 CUDA 커널. README가 본문(0.11.7)·설명(0.9.0)에서 버전이 어긋남 — 본 문서는 0.11.7로 통일하되 모순이 있음을 명시. |
| psutil / matplotlib | — | 리소스 모니터링 / 시각화. |
README는 kernels 패키지 버전을 한 곳에서는 0.11.7, 다른 설명에서는 0.9.0으로 적어 문서 스스로 모순됩니다. 신생 1일차 레포라 흔히 있는 일입니다. 본 문서는 본문 표기에 맞춰 0.11.7로 통일했지만, 실제 설치 시 두 버전 모두 시도해 볼 필요가 있을 수 있습니다. 이런 사소한 불일치는 빠르게 정정될 가능성이 높으니, 최신 README를 꼭 확인하세요.
입력은 이미지 또는 PDF입니다. PDF는 PyMuPDF로 300DPI PNG로 변환한 뒤 페이지 사이에 <page> 구분자를 넣어 한 번에 처리합니다. 해상도는 두 가지 모드가 있습니다.
| 모드 | 용도 | 설정 |
|---|---|---|
| gundam | 단일 이미지 (dynamic resolution) | base_size=1024, image_size=640, crop_mode=True. 해상도를 동적으로 맞춘다. |
| base | 멀티페이지·PDF | 1024×1024 고정, crop 없음. 멀티페이지는 base 모드만 지원한다. |
"핵심은 단 하나의 발상 — '항상 봐야 할 것(원본 이미지)'과 '잠깐만 기억하면 되는 것(방금 쓴 글)'을 갈라, 후자만 흘려보낸다."
흐름을 따라가면 이렇습니다. ① 이미지(PDF면 PNG로 변환)가 DeepEncoder에 들어가 SAM→16배 압축→CLIP을 거쳐 256개 visual token으로 농축됩니다. ② 이 visual token과 프롬프트가 합쳐져 Prefix P가 되는데, P는 길이가 고정(L_m)이고 디코딩 내내 변하지 않으며 항상 보입니다. ③ MoE 디코더가 마크다운을 한 토큰씩 생성하되, 모든 레이어 어텐션이 R-SWA라 출력 토큰은 P와 직전 128개 토큰만 봅니다. ④ 그 결과 KV 캐시는 큐처럼 동작해 새 토큰이 들어올 때마다 가장 오래된 출력 KV를 밀어내, 크기가 일정하게 유지됩니다.
가장 핵심적인 발상입니다. R-SWA에서 출력 토큰 t가 보는 어텐션 집합 N(t)은 두 부분의 합집합입니다.
// R-SWA: 출력 토큰 t 가 어텐션하는 대상 N(t)
N(t) = P ∪ D_n(t)
P = 전체 prefix (visual + 프롬프트) // 항상 가시, 이미지 충실도 유지
D_n(t) = 직전 n개 출력 토큰 (기본 n=128) // causal 슬라이딩 윈도우
// 그래서 KV 캐시는 '용량 (m + n) 짜리 큐'
새 토큰마다 -> (m+1)번째로 오래된 출력 KV 를 evict
KV 캐시 크기 C_R-SWA(T) = L_m + min(n, T) ≤ L_m + n // 상수 상한!
여기서 L_m은 prefix 길이(고정), n은 슬라이딩 윈도우 크기(기본 128)입니다. 출력 길이 T가 아무리 커져도 캐시는 L_m + n을 넘지 않습니다. 비교하면 표준 어텐션과의 차이가 선명합니다.
그냥 슬라이딩 윈도우 어텐션(vanilla SWA)을 쓰면 안 되나? 안 됩니다. 일반 SWA는 visual 토큰도 윈도우가 지나가면 잊어버립니다. 그러면 디코더가 뒤로 갈수록 원본 이미지를 점점 못 보게 되어, 뒷부분을 흐릿하게 베껴 쓰는 progressive blurring(점진적 흐려짐)이 생깁니다.
평범한 슬라이딩 윈도우는 "최근 n개 토큰만" 보므로, 출력이 윈도우 크기를 넘어가는 순간 맨 앞에 있던 visual 토큰(원본 이미지)이 윈도우 밖으로 밀려나 사라집니다. 모델이 원본을 못 보고 자기가 방금 쓴 텍스트만 보며 이어 쓰니, 뒤로 갈수록 추측이 누적돼 글자가 흐려집니다. 책 한 권을 한 번에 읽는 작업에서 이건 치명적입니다.
R-SWA의 결정적 차별점은 visual 토큰(prefix P)을 슬라이딩 윈도우의 이동 대상에서 빼내, "항상 보이는 고정 참조(reference)"로 못 박는 것입니다. 슬라이딩하는 건 오직 출력 텍스트 토큰뿐입니다. 그래서 디코더는 마지막 페이지를 쓰는 순간에도 첫 페이지와 똑같은 선명도로 원본 이미지를 봅니다. 이름의 'Reference'가 바로 이 "항상 참조하는 고정 prefix"를 가리킵니다.
R-SWA는 "최근 출력 토큰은 잊어도 되지만(soft forgetting), 원본 이미지는 절대 안 잊는다"는 선택적 망각을 구현합니다. 글을 쓸 때 방금 쓴 몇 문장의 정확한 표현은 굳이 다 기억할 필요가 없지만, '무엇을 보고 베끼는지(원본)'는 끝까지 봐야 한다는 직관과 정확히 일치합니다. 이 비대칭이 메모리를 아끼면서도 정확도를 지키는 비결입니다.
필사(筆寫)하는 서기를 떠올리세요. 원본 책(visual 토큰)은 책상 위에 펼쳐 둔 채 절대 덮지 않습니다. 반면 자기가 방금 옮겨 적은 종이는 몇 줄만 눈에 두고, 더 앞쪽은 옆으로 치워 책상을 비웁니다(KV evict). 그래서 100페이지를 베끼든 1,000페이지를 베끼든 책상 위 종이의 양(메모리)은 일정하고, 원본을 늘 보니 마지막 장도 첫 장만큼 정확합니다.
"레포는 놀랍도록 단순하다 — 모델은 HF 원격에 있고, 여기엔 추론기·기술 리포트·SGLang 휠뿐. 깊이의 90%는 동봉 PDF에 있다."
trust_remote_code=True를 주면, 모델 저장소에 들어 있는 파이썬 코드(모델 구조 정의)까지 내려받아 실행합니다. Unlimited-OCR의 R-SWA·DeepEncoder 같은 커스텀 구조는 표준 transformers에 없으므로, 이 플래그로 HF의 원격 정의 코드를 가져와야 동작합니다. 그래서 이 깃 레포 안에는 모델 본체 코드가 없고 추론 스크립트만 있는 것입니다. (보안상 신뢰하는 출처에만 켜야 하는 옵션이기도 합니다.)레포의 거의 유일한 '읽을 코드'인 infer.py(~320줄)는 추론 오케스트레이터입니다. SGLang 서버를 자동으로 띄우고, 여러 입력을 ThreadPoolExecutor로 동시에 던지며, 서버가 아직 안 떠서 502를 주면 지수 백오프로 재시도(MAX_RETRIES=5)하고, 처리량(TPS)과 디코딩 시간을 측정합니다. SGLang 기동 플래그가 이 모델의 운영 노하우를 압축해 보여 줍니다.
# infer.py 가 SGLang 서버를 띄울 때 거는 플래그(요약)
--attention-backend fa3 # FlashAttention v3 (Hopper급 GPU)
--page-size 1
--mem-fraction-static 0.8 # GPU 메모리의 80%를 정적 할당
--context-length 32768 # 최대 길이 32K
--enable-custom-logit-processor # 반복 억제 프로세서 사용
--disable-overlap-schedule
--skip-server-warmup
이 레포는 '완성차 + 설명서'가 아니라 '시동 키 + 정비 매뉴얼'에 가깝습니다. 자동차 본체(모델 가중치·구조)는 HuggingFace라는 차고에 있고, 깃 레포에는 차에 시동을 거는 키(infer.py)와 두꺼운 정비 매뉴얼(Unlimited-OCR.pdf 기술 리포트)이 들어 있습니다. 그래서 아키텍처를 제대로 이해하려면 README가 아니라 동봉된 PDF를 읽어야 합니다 — 깊이의 90%가 거기 있습니다.
"Unlimited-OCR 한 레포 안에 VLM OCR 구조·어텐션 변형·추론 최적화가 모두 응축돼 있다."
배울 것: 고해상도 이미지를 어떻게 적은 토큰으로 농축하는가. SAM-ViT(윈도우)와 CLIP-ViT(글로벌)를 캐스케이드로 잇고 그 사이 16배 압축을 두는 DeepEncoder 설계. "세밀한 글자 + 전체 맥락"을 둘 다 잡으면서 토큰을 줄이는 트레이드오프를 이해하는 게 핵심입니다.
실습: 작은 ViT 두 개(국소/전역)를 캐스케이드로 잇고 사이에 풀링/컨볼루션 기반 압축 레이어를 끼워, 입력 패치 수가 1/N로 줄어드는 미니 인코더를 만들어 보기. 압축률을 바꿔 가며 재구성 품질이 어떻게 변하는지 관찰.
배울 것: 표준 causal attention의 KV 캐시가 L_m + T로 증가하는 원리, 슬라이딩 윈도우가 이를 어떻게 묶는지, 그리고 vanilla SWA의 progressive blurring을 R-SWA가 "prefix 고정"으로 어떻게 푸는지. 어텐션 마스크 한 장으로 메모리 곡선이 바뀌는 걸 손으로 느끼는 게 목표입니다.
실습: PyTorch로 (a) full causal mask, (b) vanilla sliding window, (c) prefix를 항상 포함하는 R-SWA 마스크 세 가지를 직접 만들어, 출력 길이를 늘릴 때 각 방식의 KV 캐시 크기와 토큰별 가시 범위를 출력해 비교.
배울 것: 고성능 LLM 서빙(SGLang), bfloat16·FlashAttention v3, mem-fraction-static 같은 메모리 운영 플래그, 그리고 커스텀 로짓 프로세서로 OCR의 고질병인 'n-gram 무한 반복'을 막는 법. 모델만큼이나 "어떻게 빠르고 안정적으로 띄우는가"가 실전임을 배웁니다.
실습: 작은 LLM을 SGLang(또는 vLLM)으로 띄워 OpenAI 호환 엔드포인트로 스트리밍 호출해 보고, no-repeat-ngram 로짓 프로세서를 붙였을 때/뗐을 때 반복되는 출력이 어떻게 달라지는지 비교.
배울 것: PDF를 PyMuPDF로 300DPI PNG로 변환하고, 페이지 사이에 <page> 같은 구분 토큰을 넣어 한 시퀀스로 흘려보내는 설계. 그리고 infer.py의 ThreadPoolExecutor 동시 배치·지수 백오프·TPS 측정 같은 실전 러너 패턴.
실습: PyMuPDF로 여러 페이지 PDF를 PNG 리스트로 변환하고, ThreadPoolExecutor로 동시에 추론을 던지는 미니 러너를 작성. 서버가 502를 줄 때 지수 백오프로 재시도하는 로직을 직접 구현.
"R-SWA의 셀링 포인트는 바로 이것 — 출력이 길어져도 메모리가 일정하다. 단, 추론 VRAM 구체 수치는 아직 공개되지 않았다."
| 항목 | 요구사항 / 메모 |
|---|---|
| GPU | NVIDIA CUDA 12.9 + FlashAttention v3(fa3). fa3는 Hopper급(H100/H800)에서 동작 → 사실상 Hopper급 권장. |
| 연산 정밀도 | bfloat16 추론. |
| 최대 길이 | context-length 32768 (32K 토큰). |
| 추론 VRAM | ⚠️ 구체 수치 미명시. 다만 R-SWA 덕에 출력 길이와 무관하게 메모리가 일정하다는 점이 핵심 셀링 포인트. |
| 활성 파라미터 | 3B 중 0.5B만 활성(MoE) → 추론 비용은 작은 모델 수준. |
| 의존성 환경(검증) | Python 3.12.3 + CUDA 12.9, torch 2.10.0 / transformers 4.57.1 등. |
직접 학습할 일은 드물지만, 규모 감을 위해 기록합니다(기술 리포트 기준).
| 항목 | 값 |
|---|---|
| 하드웨어 | 128× A800 |
| 배치 · 스텝 | global batch 256 · 4,000 steps |
| 데이터 | 약 2M 문서 (단일:멀티 = 9:1, 멀티는 2~50p 합성 20만) |
| 학습 전략 | DeepEncoder는 freeze, LLM만 학습 |
| 프레임워크 | Megatron-LM + DeepEP, EP=4 |
| 옵티마이저 | AdamW + cosine 스케줄(lr 1e-4) |
이 모델은 FlashAttention v3가 필요해 사실상 Hopper급(H100/H800) GPU를 전제합니다. 게다가 추론에 필요한 VRAM의 구체 수치가 README에 명시돼 있지 않습니다(공개 1일차 신생 레포). R-SWA 덕에 "출력이 길어져도 메모리는 일정"한 건 맞지만, 그 일정한 값이 정확히 몇 GB인지는 아직 확인 불가입니다. 일반 소비자용 GPU에서의 동작 여부는 단정할 수 없으니, 실제 사용 전 직접 측정이 필요합니다.
"개념 이해(어텐션 그리기) → 추론 돌려보기 → R-SWA 직접 구현 → 멀티페이지 러너, 단계별로."
표준 causal attention과 R-SWA의 KV 캐시 크기 공식(L_m + T vs L_m + min(n, T))을 코드로 옮겨, 출력 길이 T를 1부터 10,000까지 늘릴 때 두 곡선을 matplotlib로 그린다. R-SWA가 어디서 평평해지는지 눈으로 확인.
목표: "왜 긴 문서에서 표준 어텐션이 무너지는가"를 그래프 한 장으로 체득. 윈도우 크기 n을 바꿔 가며 상한선이 어떻게 움직이는지 관찰.
(GPU 환경에서) AutoModel.from_pretrained(..., trust_remote_code=True, torch_dtype=torch.bfloat16)로 모델을 불러 model.infer()로 한 장의 문서 이미지를 마크다운으로 변환한다. gundam 모드 설정(base_size=1024 등)도 바꿔 본다.
목표: end-to-end VLM OCR의 입출력 감 잡기 — 이미지 한 장이 표·수식 포함 마크다운으로 어떻게 떨어지는지 확인. (Hopper급 GPU 필요)
PyTorch로 (a) full causal, (b) vanilla sliding window, (c) R-SWA(prefix 항상 포함 + 출력만 슬라이딩) 마스크를 만든다. 작은 시퀀스에서 각 토큰이 무엇을 보는지 마스크 히트맵으로 시각화하고, (b)에서 visual 토큰이 언제 사라지는지(progressive blurring의 원인)를 확인.
목표: R-SWA의 핵심 차별점("prefix를 슬라이딩에서 제외")을 마스크 차원에서 이해. (b)와 (c)의 단 한 가지 차이가 전부임을 깨닫기.
PyMuPDF로 PDF를 300DPI PNG 리스트로 변환하고, 페이지 사이에 구분 토큰을 넣어 SGLang(OpenAI 호환) 엔드포인트로 스트리밍 호출하는 러너를 작성. infer.py처럼 ThreadPoolExecutor 동시 처리·502 지수 백오프·TPS 측정까지 붙인다.
목표: "한 번의 호출로 책 한 권"을 흘려보내는 멀티페이지 파이프라인을 손으로 재현. 동시성·재시도·계측이 실전 추론의 절반임을 체감.
작은 트랜스포머 디코더 한 레이어의 어텐션을 R-SWA로 직접 구현한다 — prefix(고정) KV는 항상 유지하고, 출력 KV는 용량 n짜리 큐로 관리해 새 토큰마다 가장 오래된 것을 evict. 같은 입력에 대해 full attention 대비 출력이 얼마나 비슷한지, 메모리는 얼마나 줄었는지 측정.
목표: R-SWA를 '읽는' 단계를 넘어 '짜는' 단계로. KV 큐 관리와 prefix 분리가 코드로 어떻게 표현되는지가 핵심. no-repeat-ngram 로짓 프로세서를 붙여 반복 억제까지 실험.
"트랜스포머 어텐션 기초에서 출발해 KV 캐시·VLM·추론 서빙까지, Unlimited-OCR을 길잡이 삼아."
| 주차 | 주제 | 핵심 학습 + Unlimited-OCR 연결점 |
|---|---|---|
| 1주 | 트랜스포머 어텐션 · KV 캐시 | self-attention, causal mask, KV 캐시가 L_m+T로 증가하는 원리 → R-SWA가 푸는 바로 그 문제 |
| 2주 | 효율적 어텐션 변형 | sliding window·FlashAttention·sink token, vanilla SWA의 한계 → R-SWA(prefix 고정 + 출력 슬라이딩) |
| 3주 | ViT · 비전 인코더 | 패치 임베딩, 윈도우 vs 글로벌 어텐션, 토큰 압축 → DeepEncoder(SAM+CLIP 16× 압축) |
| 4주 | VLM · 멀티모달 OCR | 이미지→토큰→텍스트 디코딩, prefix(visual+prompt) 설계 → end-to-end VLM OCR vs 파이프라인 OCR |
| 5주 | MoE · 추론 서빙 | 전문가 라우팅·활성 파라미터, SGLang/vLLM, bfloat16·메모리 플래그 → 3B-A0.5B + SGLang |
| 6주 | 문서 파싱 평가 | OmniDocBench, TEDS·Edit Distance·Formula CDM·Distinct-n, 반복 억제 → 벤치마크 표 재현·해석 |
이 로드맵은 "어텐션 기초 → 효율화 → 비전 인코더 → 멀티모달 → 서빙 → 평가"를 한 줄로 꿴 코스입니다. 'OCR 라이브러리 사용법'이 아니라 '트랜스포머의 메모리 병목을 어떻게 구조적으로 푸는가'를 배우는 길이라, 긴 컨텍스트가 일상이 된 2026년 LLM 시대의 핵심 엔지니어링을 실전 모델로 복습하는 셈입니다.
"이 문서와 저장소에서 반복되는 용어를 한곳에 모았다."
| 용어 | 의미 |
|---|---|
| R-SWA (Reference Sliding Window Attention) | 출력 토큰이 전체 prefix(P) + 직전 n개 출력 토큰만 보게 해 KV 캐시를 상수로 묶는 어텐션. 이 모델의 심장 |
| Constant KV cache (상수 KV 캐시) | 출력 길이 T와 무관하게 KV 캐시 크기가 L_m+n 상한을 넘지 않음. 무한 길이 디코딩의 열쇠 |
| soft forgetting (선택적 망각) | 오래된 출력 토큰은 잊되(KV evict), 원본 이미지(prefix)는 절대 안 잊는 비대칭 망각 |
| One-shot Long-horizon Parsing | 수십~수백 페이지 문서를 단일 forward pass로 끝까지 파싱하는 능력 |
| DeepEncoder | SAM-ViT(윈도우)+CLIP-ViT(글로벌)을 캐스케이드로 잇고 16배 압축해 1024² 이미지를 256 visual token으로 |
| visual tokens | 이미지를 인코딩한 토큰(256개). R-SWA에서 prefix에 고정돼 항상 가시 |
| prefix P · L_m | visual+프롬프트로 이뤄진 고정 길이(L_m) 접두부. 디코딩 내내 불변, 항상 어텐션 대상 |
| MoE 3B-A0.5B | 총 3B 파라미터 중 추론 시 0.5B만 활성(Mixture-of-Experts). 큰 표현력 + 작은 추론 비용 |
| 활성 파라미터 (active parameters) | MoE에서 한 번 추론할 때 실제로 켜지는 파라미터 수. 속도·메모리를 좌우하는 실질 크기 |
| vanilla SWA · progressive blurring | 평범한 슬라이딩 윈도우는 visual 토큰까지 잊어, 뒤로 갈수록 흐릿하게 베끼는 현상. R-SWA가 prefix 고정으로 방지 |
| end-to-end VLM OCR | 검출·인식·후처리를 한 비전-언어 모델로 통합. 파이프라인 OCR(PaddleOCR류)의 반대 |
| DeepSeek-OCR | Unlimited-OCR의 베이스라인 모델. 디코더 어텐션만 R-SWA로 교체됨 |
| OmniDocBench | 문서 파싱 표준 벤치마크. Overall·TEDS(표)·Edit Distance(텍스트)·Formula CDM(수식)·Distinct-n 등으로 평가 |
| SGLang · fa3 | 고성능 LLM 서빙 엔진(패치 휠 동봉) / FlashAttention v3(Hopper급 GPU 어텐션 백엔드) |
| no-repeat-ngram 로짓 프로세서 | DeepseekOCRNoRepeatNGramLogitProcessor(ngram 35, window 128) — OCR의 무한 반복을 억제 |
| gundam / base 모드 | gundam=단일 이미지(dynamic res, crop) / base=멀티·PDF(1024² 고정, crop 없음). 멀티는 base만 |
| trust_remote_code | HF에서 모델 정의 코드까지 내려받아 실행하는 옵션. 커스텀 구조(R-SWA 등) 로딩에 필수 |
Unlimited-OCR.pdf (아키텍처·R-SWA·학습의 1차 출처. 깊이의 90%)