GitHub 트렌딩 딥다이브 · 2026-06-23

Unlimited-OCR 딥다이브
— 수백 페이지 문서를 '한 번의 forward pass'로 읽어 내는 무한 길이 OCR 모델

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)

목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 (이미지 한 장이 무한 길이 텍스트로 풀리기까지)
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

"OCR 모델은 '한 페이지를 읽을' 줄은 안다. Unlimited-OCR은 '책 한 권을 끊지 않고 끝까지' 읽는다."

한 줄로

Unlimited-OCR은 출력이 길어질수록 무거워지는 디코더의 '기억 부담(KV 캐시)'을 상수로 묶어, 수백 페이지를 단번에 읽어 내는 OCR 엔진이다.

보통의 디코더형 OCR(글자를 한 토큰씩 뱉는 LLM 기반 OCR)은 글을 길게 쓸수록 자기가 지금까지 쓴 모든 토큰을 기억(KV 캐시)해야 해서, 출력이 길어질수록 메모리가 선형으로 불어나고 속도가 떨어집니다. 그래서 기존 모델들은 "한 페이지 읽고 → 메모리 비우고 → 다음 페이지" 식으로 for 루프를 돌며 강제로 끊어 처리했습니다. Unlimited-OCR은 디코더의 어텐션 방식을 R-SWA로 바꿔, 아무리 길게 출력해도 기억 부담을 일정하게 유지합니다. 그 결과 책 전체를 한 번에 연속으로 읽으면서도, 벤치마크 정확도(OmniDocBench)는 베이스라인 DeepSeek-OCR보다 오히려 올라갔습니다.

용어
OCR · 문서 파싱 (Document Parsing)
OCR(Optical Character Recognition, 광학 문자 인식)은 사진·스캔 이미지 속 글자를 컴퓨터가 읽을 수 있는 텍스트로 바꾸는 기술입니다. 문서 파싱은 거기서 한 발 더 나아가, 단순히 글자만 뽑는 게 아니라 표·수식·읽기 순서·레이아웃까지 구조를 통째로 복원하는 일을 말합니다. Unlimited-OCR은 1024×1024 PDF 이미지를 입력받아 마크다운으로 떨어뜨리며, 표는 표 구조로, 수식은 수식으로, 글 좌표는 0~1000으로 정규화해 함께 내보냅니다.
용어
end-to-end VLM OCR vs 파이프라인 OCR
파이프라인 OCR(예: PaddleOCR)은 "글자 영역 검출 → 여러 인식기 → 휴리스틱 후처리"처럼 여러 모듈을 줄줄이 엮은 방식입니다. 반면 end-to-end VLM OCR은 이미지를 통째로 하나의 비전-언어 모델(Vision-Language Model)에 넣어 마크다운을 바로 뽑는 단일 통합 모델입니다. Unlimited-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을 처음 공부하는 사람에게 살아 있는 교본입니다.

2왜 주목받는가

"긴 문서 OCR의 진짜 적은 '글자를 못 읽는 것'이 아니라 '길어질수록 기억이 폭증하는 것'이다. Unlimited-OCR은 그 지점을 정조준한다."

먼저, 디코더형 OCR은 어디서 무너지는가

LLM 디코더를 쓰는 OCR은 글자를 한 토큰씩 자기회귀적으로 뱉습니다. 이때 디코더는 지금까지 만든 모든 토큰의 키/값(KV)을 캐시해 두고 다음 토큰을 예측합니다. 문제는 출력 토큰 수 T가 커질수록 KV 캐시도 선형으로 커진다는 것 — 책 한 권을 마크다운으로 풀면 수만~수십만 토큰이 나오므로, 메모리가 폭증하고 속도가 급락합니다. 그래서 기존 모델들의 우회책은 "페이지마다 for 루프로 끊고 메모리를 리셋"하는 것이었고, 이건 페이지 경계의 문맥(앞 페이지에서 이어지는 표·문단)을 잃게 만듭니다.

함정
KV 캐시의 선형 폭증 — "길게 쓸수록 무거워진다"

표준 멀티헤드 어텐션(MHA)에서 출력 토큰 t는 이전의 모든 토큰을 본다(causal full attention). 그래서 KV 캐시 크기는 prefix 길이 L_m에 출력 길이 T를 더한 L_m + TT에 비례해 무한히 증가합니다. 6,000 토큰만 뽑아도 메모리·연산이 부담스럽고, 책 한 권 분량(수만 토큰)이면 단일 forward pass는 사실상 불가능했습니다. 이게 "페이지마다 끊어 처리"라는 우회책이 나온 근본 이유입니다.

해결
R-SWA — KV 캐시를 '상수'로 묶는다

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.0730.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 같은 파이프라인과 무엇이 다른가

PaddleOCR류는 "검출 + 다중 인식기 + 휴리스틱 후처리"를 엮은 파이프라인이라, 모듈마다 따로 튜닝하고 단계 사이 오차가 쌓입니다. Unlimited-OCR은 단일 통합 VLM이라 이미지를 넣으면 마크다운이 바로 나옵니다 — 검출기도, 후처리 규칙도 없습니다. 대신 모델 하나에 모든 부담이 실리므로 모델 구조(어텐션·인코더 압축)의 효율이 곧 전체 효율입니다. R-SWA가 바로 그 효율의 열쇠입니다.

'벤치마크 수치'를 곧이곧대로 믿어도 되나

솔직하게 짚겠습니다. 위 수치들은 개발팀이 자체 측정했을 가능성이 있는 값이고, 이 레포는 2026-06-22 공개된 공개 1일차 신생 프로젝트입니다. 스타 수와 추론에 필요한 정확한 VRAM은 아직 공개·검증되지 않았습니다. 또 모델 정의 코드·가중치는 레포 안이 아니라 HuggingFace 원격에 있고, README에는 kernels 패키지 버전이 본문(0.11.7)과 설명(0.9.0)에서 서로 다르게 적힌 자체 모순도 있습니다. 방향성("어텐션을 고쳐 KV 캐시를 상수로")은 매우 설득력 있는 정공법이지만 — 구체 수치는 제3자 재현 전까지는 참고치로 보시는 게 안전합니다.

3기술 스택 전체 지도

"언어는 Python이지만 진짜 핵심은 '인코더의 16배 압축'과 'R-SWA로 다시 짠 디코더 어텐션'에 있다."

Unlimited-OCR은 비전-언어 모델(VLM)이라 추론에 GPU가 필요한 무거운 모델이지만, 레포 자체는 의외로 단순합니다. 모델 정의·가중치는 HuggingFace에 있고, 레포에는 주로 추론 스크립트·기술 리포트·SGLang 패치 휠이 들어 있습니다. 무게중심은 두 곳 — 이미지를 256 토큰으로 압축하는 DeepEncoderKV 캐시를 상수로 묶는 R-SWA 디코더입니다.

① 모델 아키텍처 (DeepEncoder + MoE 디코더)

구성역할한 줄 설명
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로 갈아끼웠다.
용어
MoE · 활성 파라미터 (3B-A0.5B)
MoE(Mixture-of-Experts, 전문가 혼합)는 모델 안에 여러 '전문가' 서브네트워크를 두고, 토큰마다 일부 전문가만 골라 쓰는 구조입니다. 3B-A0.5B는 "총 파라미터는 3B인데, 한 번 추론할 때 실제로 켜지는(active) 파라미터는 0.5B(500M)"라는 뜻입니다. 덕분에 큰 모델의 표현력은 가지면서 추론 비용은 작은 모델 수준으로 유지합니다. "활성 파라미터"는 추론 속도·메모리를 좌우하는 실질적 크기입니다.
용어
DeepEncoder · 16배 토큰 압축
고해상도 이미지를 ViT(Vision Transformer)에 그냥 넣으면 패치(토큰)가 수천 개가 되어 디코더가 감당 못 합니다. DeepEncoder는 SAM-ViT(국소적인 윈도우 어텐션으로 세밀한 글자)와 CLIP-ViT(전역 어텐션으로 전체 맥락)를 순서대로(캐스케이드) 통과시키되, 그 사이에 16배 압축 브리지를 끼워 토큰 수를 1/16로 줄입니다. 그래서 1024×1024 이미지가 단 256개의 visual token으로 디코더에 전달됩니다 — 적은 토큰에 이미지 정보를 농축하는 게 이 인코더의 미덕입니다.

② 추론 프레임워크 (가벼운 단일 vs 대규모 멀티)

경로언제 쓰나핵심
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)로 같은 구절이 무한 반복되는 것을 막는다.
용어
SGLang · 로짓 프로세서 (logit processor)
SGLang은 LLM을 빠르게 서빙(추론 제공)하기 위한 고성능 추론 엔진입니다. Unlimited-OCR은 자기 모델에 맞게 패치한 SGLang 휠을 레포에 직접 넣어 둡니다. 로짓 프로세서는 모델이 다음 토큰을 고르기 직전, 각 후보 토큰의 점수(logit)를 조작하는 후크입니다. 여기서는 "최근 window_size=128 토큰 안에서 ngram_size=35짜리 같은 구절이 또 나오면 점수를 깎아" 무한 반복을 막습니다 — 긴 문서를 한 번에 뽑을 때 흔한 'OCR이 같은 줄을 계속 반복하는' 고질병을 차단하는 장치입니다.

③ 의존성 지도 (검증 환경: py3.12.3 + CUDA 12.9)

패키지버전역할
torch2.10.0딥러닝 코어. bfloat16 추론.
torchvision0.25.0이미지 변환·전처리.
transformers4.57.1HF 추론 경로(AutoModel).
pymupdf1.27.2.2PDF → 300DPI PNG 변환. 멀티페이지 입력의 관문.
Pillow12.1.1이미지 입출력(PNG 등).
einops0.8.2텐서 차원 재배열(어텐션 구현에 자주 쓰임).
addict / easydict2.4.0 / 1.13설정 객체를 점(.) 접근으로 다루는 유틸.
kernels0.11.7 ⚠️커스텀 CUDA 커널. README가 본문(0.11.7)·설명(0.9.0)에서 버전이 어긋남 — 본 문서는 0.11.7로 통일하되 모순이 있음을 명시.
psutil / matplotlib리소스 모니터링 / 시각화.
함정
README 자체에 버전 모순이 있다

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멀티페이지·PDF1024×1024 고정, crop 없음. 멀티페이지는 base 모드만 지원한다.

4아키텍처 심화 분석 (이미지 한 장이 무한 길이 텍스트로 풀리기까지)

"핵심은 단 하나의 발상 — '항상 봐야 할 것(원본 이미지)'과 '잠깐만 기억하면 되는 것(방금 쓴 글)'을 갈라, 후자만 흘려보낸다."

전체 파이프라인 한 장

입력: 이미지 또는 PDF │ (PDF면 PyMuPDF로 300DPI PNG 변환, 페이지 사이에 <page> 구분자) ▼ ┌──────────────────────────────────────────────┐ │ DeepEncoder (비전 인코더) │ │ ① SAM-ViT ── 윈도우 어텐션 (세밀한 글자) │ │ ② 16× 압축 브리지 ── 토큰 수 1/16 │ │ ③ CLIP-ViT ── 글로벌 어텐션 (전체 맥락) │ │ 1024×1024 → 단 256 visual tokens │ └───────────────────────┬──────────────────────┘ ▼ ╔═══════════════════════════════════════════════╗ ║ Prefix P = [ visual tokens + 프롬프트 ] ║ ║ 고정 길이 L_m · 디코딩 내내 불변 · 항상 가시 ║ ◀ 절대 안 잊음 ╚═══════════════════════╤═══════════════════════╝ ▼ ┌──────────────────────────────────────────────┐ │ MoE LLM 디코더 (3B / 활성 0.5B) │ │ 전 레이어 어텐션 = R-SWA │ │ 출력 토큰 t 가 보는 것 = P ∪ 직전 n=128토큰 │ │ ┌─────────────────────────────────────────┐ │ │ │ KV 캐시 = 큐(용량 m+n) · 새 토큰마다 1개 │ │ │ │ 가장 오래된 출력 KV를 evict │ │ │ └─────────────────────────────────────────┘ │ └───────────────────────┬──────────────────────┘ ▼ 출력: 마크다운 (좌표 0–1000 정규화 · 표 · 수식 · 읽기순서) └ 수십~수백 페이지를 단일 forward pass(one-shot)로

흐름을 따라가면 이렇습니다. 이미지(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: 어텐션 집합을 둘로 쪼갠다

가장 핵심적인 발상입니다. 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을 넘지 않습니다. 비교하면 표준 어텐션과의 차이가 선명합니다.

┌────────────────────────────────────────────────────────────┐ │ KV 캐시 크기 (출력 길이 T 가 커질 때) │ ├────────────────────────────────────────────────────────────┤ │ │ │ 표준 MHA : L_m + T │ │ ╱ │ │ ╱ ← T 에 비례해 무한히 증가 (메모리 폭증) │ │ ╱ │ │ ╱ │ │ ──────────────────────────────────────────── (T) │ │ │ │ R-SWA : L_m + min(n, T) ≤ L_m + n │ │ ───────────────────────────────── ← 일정 (상수 상한) │ │ │ └────────────────────────────────────────────────────────────┘

설계 패턴 ② — 왜 visual 토큰은 윈도우에서 빼내는가 (vanilla SWA와의 결정적 차이)

그냥 슬라이딩 윈도우 어텐션(vanilla SWA)을 쓰면 안 되나? 안 됩니다. 일반 SWA는 visual 토큰도 윈도우가 지나가면 잊어버립니다. 그러면 디코더가 뒤로 갈수록 원본 이미지를 점점 못 보게 되어, 뒷부분을 흐릿하게 베껴 쓰는 progressive blurring(점진적 흐려짐)이 생깁니다.

함정
vanilla SWA의 progressive blurring

평범한 슬라이딩 윈도우는 "최근 n개 토큰만" 보므로, 출력이 윈도우 크기를 넘어가는 순간 맨 앞에 있던 visual 토큰(원본 이미지)이 윈도우 밖으로 밀려나 사라집니다. 모델이 원본을 못 보고 자기가 방금 쓴 텍스트만 보며 이어 쓰니, 뒤로 갈수록 추측이 누적돼 글자가 흐려집니다. 책 한 권을 한 번에 읽는 작업에서 이건 치명적입니다.

해결
visual 토큰을 'state transition'에서 제외 → 항상 P에 고정

R-SWA의 결정적 차별점은 visual 토큰(prefix P)을 슬라이딩 윈도우의 이동 대상에서 빼내, "항상 보이는 고정 참조(reference)"로 못 박는 것입니다. 슬라이딩하는 건 오직 출력 텍스트 토큰뿐입니다. 그래서 디코더는 마지막 페이지를 쓰는 순간에도 첫 페이지와 똑같은 선명도로 원본 이미지를 봅니다. 이름의 'Reference'가 바로 이 "항상 참조하는 고정 prefix"를 가리킵니다.

설계 패턴 ③ — soft forgetting: 잊되, 중요한 건 안 잊는다

R-SWA는 "최근 출력 토큰은 잊어도 되지만(soft forgetting), 원본 이미지는 절대 안 잊는다"는 선택적 망각을 구현합니다. 글을 쓸 때 방금 쓴 몇 문장의 정확한 표현은 굳이 다 기억할 필요가 없지만, '무엇을 보고 베끼는지(원본)'는 끝까지 봐야 한다는 직관과 정확히 일치합니다. 이 비대칭이 메모리를 아끼면서도 정확도를 지키는 비결입니다.

비유

필사(筆寫)하는 서기를 떠올리세요. 원본 책(visual 토큰)은 책상 위에 펼쳐 둔 채 절대 덮지 않습니다. 반면 자기가 방금 옮겨 적은 종이는 몇 줄만 눈에 두고, 더 앞쪽은 옆으로 치워 책상을 비웁니다(KV evict). 그래서 100페이지를 베끼든 1,000페이지를 베끼든 책상 위 종이의 양(메모리)은 일정하고, 원본을 늘 보니 마지막 장도 첫 장만큼 정확합니다.

5디렉토리 구조 해부

"레포는 놀랍도록 단순하다 — 모델은 HF 원격에 있고, 여기엔 추론기·기술 리포트·SGLang 휠뿐. 깊이의 90%는 동봉 PDF에 있다."

최상위 한눈에

Unlimited-OCR/ ├── infer.py ← 추론 진입점(~320줄) │ · SGLang 서버 자동 기동 │ · ThreadPoolExecutor 동시 배치 러너 │ · 502 응답 시 지수 백오프(MAX_RETRIES=5) │ · TPS / decode_time 측정 ├── Unlimited-OCR.pdf ← 기술 리포트 (깊이의 90%가 여기!) ├── wheel/ │ └── sglang-...whl · 패치된 SGLang 휠 (~12MB) ├── assets/ ← 아키텍처 다이어그램 · 데모 gif · baidu.png ├── README.md ← 사용법 · 벤치마크 표 (단, kernels 버전 모순 있음) ├── CONTRIBUTING.md ← 영문/중문, PEP8 가이드 └── LICENSE ← MIT ※ 모델 정의 코드 · 가중치는 레포에 없음 → HuggingFace 원격 (AutoModel ... trust_remote_code=True 로 내려받아 실행)
용어
trust_remote_code · 원격 모델 정의
HuggingFace에서 모델을 불러올 때 trust_remote_code=True를 주면, 모델 저장소에 들어 있는 파이썬 코드(모델 구조 정의)까지 내려받아 실행합니다. Unlimited-OCR의 R-SWA·DeepEncoder 같은 커스텀 구조는 표준 transformers에 없으므로, 이 플래그로 HF의 원격 정의 코드를 가져와야 동작합니다. 그래서 이 깃 레포 안에는 모델 본체 코드가 없고 추론 스크립트만 있는 것입니다. (보안상 신뢰하는 출처에만 켜야 하는 옵션이기도 합니다.)

infer.py — 이 레포에서 실제로 읽을 코드

레포의 거의 유일한 '읽을 코드'인 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%가 거기 있습니다.

6학습 포인트 (기술별 — 배울 것 + 실습 아이디어)

"Unlimited-OCR 한 레포 안에 VLM OCR 구조·어텐션 변형·추론 최적화가 모두 응축돼 있다."

① VLM OCR 구조 — 인코더 압축 설계

배울 것: 고해상도 이미지를 어떻게 적은 토큰으로 농축하는가. SAM-ViT(윈도우)와 CLIP-ViT(글로벌)를 캐스케이드로 잇고 그 사이 16배 압축을 두는 DeepEncoder 설계. "세밀한 글자 + 전체 맥락"을 둘 다 잡으면서 토큰을 줄이는 트레이드오프를 이해하는 게 핵심입니다.

실습: 작은 ViT 두 개(국소/전역)를 캐스케이드로 잇고 사이에 풀링/컨볼루션 기반 압축 레이어를 끼워, 입력 패치 수가 1/N로 줄어드는 미니 인코더를 만들어 보기. 압축률을 바꿔 가며 재구성 품질이 어떻게 변하는지 관찰.

② 어텐션 변형 — 슬라이딩 윈도우 & KV 캐시

배울 것: 표준 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 캐시 크기와 토큰별 가시 범위를 출력해 비교.

③ 추론 최적화 — SGLang 서빙 & 반복 억제

배울 것: 고성능 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를 줄 때 지수 백오프로 재시도하는 로직을 직접 구현.

7하드웨어 / 시스템 요구사항

"R-SWA의 셀링 포인트는 바로 이것 — 출력이 길어져도 메모리가 일정하다. 단, 추론 VRAM 구체 수치는 아직 공개되지 않았다."

항목요구사항 / 메모
GPUNVIDIA 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 등.

학습(training) 설정 — 참고용

직접 학습할 일은 드물지만, 규모 감을 위해 기록합니다(기술 리포트 기준).

항목
하드웨어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)
함정
"내 GPU로 돌까?" — VRAM 수치가 없다

이 모델은 FlashAttention v3가 필요해 사실상 Hopper급(H100/H800) GPU를 전제합니다. 게다가 추론에 필요한 VRAM의 구체 수치가 README에 명시돼 있지 않습니다(공개 1일차 신생 레포). R-SWA 덕에 "출력이 길어져도 메모리는 일정"한 건 맞지만, 그 일정한 값이 정확히 몇 GB인지는 아직 확인 불가입니다. 일반 소비자용 GPU에서의 동작 여부는 단정할 수 없으니, 실제 사용 전 직접 측정이 필요합니다.

8직접 해볼 수 있는 실습 과제

"개념 이해(어텐션 그리기) → 추론 돌려보기 → R-SWA 직접 구현 → 멀티페이지 러너, 단계별로."

과제 1난이도 ★☆☆☆☆ · 입문

KV 캐시 성장 곡선을 직접 그려 보기

표준 causal attention과 R-SWA의 KV 캐시 크기 공식(L_m + T vs L_m + min(n, T))을 코드로 옮겨, 출력 길이 T를 1부터 10,000까지 늘릴 때 두 곡선을 matplotlib로 그린다. R-SWA가 어디서 평평해지는지 눈으로 확인.

목표: "왜 긴 문서에서 표준 어텐션이 무너지는가"를 그래프 한 장으로 체득. 윈도우 크기 n을 바꿔 가며 상한선이 어떻게 움직이는지 관찰.

과제 2난이도 ★★☆☆☆ · 초급

HuggingFace 경로로 단일 이미지 OCR 돌리기

(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 필요)

과제 3난이도 ★★★☆☆ · 중급

세 가지 어텐션 마스크를 만들어 가시 범위 비교

PyTorch로 (a) full causal, (b) vanilla sliding window, (c) R-SWA(prefix 항상 포함 + 출력만 슬라이딩) 마스크를 만든다. 작은 시퀀스에서 각 토큰이 무엇을 보는지 마스크 히트맵으로 시각화하고, (b)에서 visual 토큰이 언제 사라지는지(progressive blurring의 원인)를 확인.

목표: R-SWA의 핵심 차별점("prefix를 슬라이딩에서 제외")을 마스크 차원에서 이해. (b)와 (c)의 단 한 가지 차이가 전부임을 깨닫기.

과제 4난이도 ★★★★☆ · 중상

SGLang 멀티페이지 PDF 러너 흉내 내기

PyMuPDF로 PDF를 300DPI PNG 리스트로 변환하고, 페이지 사이에 구분 토큰을 넣어 SGLang(OpenAI 호환) 엔드포인트로 스트리밍 호출하는 러너를 작성. infer.py처럼 ThreadPoolExecutor 동시 처리·502 지수 백오프·TPS 측정까지 붙인다.

목표: "한 번의 호출로 책 한 권"을 흘려보내는 멀티페이지 파이프라인을 손으로 재현. 동시성·재시도·계측이 실전 추론의 절반임을 체감.

과제 5난이도 ★★★★★ · 고급

미니 R-SWA 디코더 한 레이어 구현하기

작은 트랜스포머 디코더 한 레이어의 어텐션을 R-SWA로 직접 구현한다 — prefix(고정) KV는 항상 유지하고, 출력 KV는 용량 n짜리 큐로 관리해 새 토큰마다 가장 오래된 것을 evict. 같은 입력에 대해 full attention 대비 출력이 얼마나 비슷한지, 메모리는 얼마나 줄었는지 측정.

목표: R-SWA를 '읽는' 단계를 넘어 '짜는' 단계로. KV 큐 관리와 prefix 분리가 코드로 어떻게 표현되는지가 핵심. no-repeat-ngram 로짓 프로세서를 붙여 반복 억제까지 실험.

9관련 기술 심화 학습 로드맵 (6주 플랜)

"트랜스포머 어텐션 기초에서 출발해 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 시대의 핵심 엔지니어링을 실전 모델로 복습하는 셈입니다.

10핵심 키워드 사전

"이 문서와 저장소에서 반복되는 용어를 한곳에 모았다."

용어의미
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로 끝까지 파싱하는 능력
DeepEncoderSAM-ViT(윈도우)+CLIP-ViT(글로벌)을 캐스케이드로 잇고 16배 압축해 1024² 이미지를 256 visual token으로
visual tokens이미지를 인코딩한 토큰(256개). R-SWA에서 prefix에 고정돼 항상 가시
prefix P · L_mvisual+프롬프트로 이뤄진 고정 길이(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-OCRUnlimited-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_codeHF에서 모델 정의 코드까지 내려받아 실행하는 옵션. 커스텀 구조(R-SWA 등) 로딩에 필수

11참고 링크