DeepSeek OCR 2 사용법

Ashley Innocent

Ashley Innocent

27 January 2026

DeepSeek OCR 2 사용법

문서 처리는 오랫동안 AI의 가장 실용적인 응용 분야 중 하나였습니다. 하지만 대부분의 OCR 솔루션은 정확성과 효율성 사이에서 불편한 타협을 강요합니다. Tesseract와 같은 기존 시스템은 광범위한 전처리를 필요로 합니다. 클라우드 API는 페이지당 요금을 부과하고 지연 시간을 추가합니다. 심지어 최신 비전-언어 모델조차 고해상도 문서 이미지에서 발생하는 토큰 폭발 문제로 어려움을 겪습니다.

DeepSeek-OCR 2는 이러한 상황을 완전히 바꿉니다. 버전 1의 "Contexts Optical Compression" 접근 방식을 기반으로, 새로운 버전은 "Visual Causal Flow"를 도입했습니다. 이 아키텍처는 단순히 문자를 인식하는 것을 넘어 시각적 관계와 맥락을 이해하며, 사람이 실제로 문서를 읽는 방식대로 문서를 처리합니다. 그 결과, 이미지를 64개의 토큰으로 압축하면서도 97%의 정확도를 달성하여, 단일 GPU에서 하루 200,000페이지 이상의 처리량을 가능하게 하는 모델이 탄생했습니다.

이 가이드는 기본적인 설정부터 프로덕션 배포에 이르기까지 모든 것을 다룹니다. 바로 복사하여 붙여넣고 실행할 수 있는 실용적인 코드도 함께 제공됩니다.

💡
DeepSeek-OCR 2를 테스트할 준비가 되셨나요? Apidog를 다운로드하여 API를 시각적으로 실험해보세요. Apidog를 사용하면 상용구 코드를 작성할 필요 없이 문서 이미지를 업로드하고, OCR 요청을 구성하고, 마크다운 출력을 검사하고, 다양한 해상도 모드에서 결과를 비교할 수 있습니다.

DeepSeek-OCR 2란 무엇인가요?

DeepSeek-OCR 2는 문서 이해 및 텍스트 추출을 위해 특별히 설계된 오픈 소스 비전-언어 모델입니다. DeepSeek AI가 2026년 1월에 출시한 이 모델은 원본 DeepSeek-OCR을 기반으로 새로운 "Visual Causal Flow" 아키텍처를 적용했습니다. 이 아키텍처는 문서 내 시각적 요소들이 서로 인과적으로 어떻게 관련되는지를 모델링합니다. 예를 들어, 테이블 헤더가 아래 셀의 해석 방식을 결정하거나, 그림 캡션이 위에 있는 차트를 설명한다는 것을 이해하는 식입니다.

이 모델은 두 가지 주요 구성 요소로 이루어져 있습니다:

  1. DeepEncoder: 지역적 세부 정보 추출(SAM 기반, 8천만 매개변수)과 전역적 레이아웃 이해(CLIP 기반, 3억 매개변수)를 결합한 듀얼 비전 트랜스포머
  2. DeepSeek3B-MoE 디코더: 압축된 시각적 표현에서 구조화된 출력(마크다운, LaTeX, JSON)을 생성하는 전문가 혼합 언어 모델

DeepSeek-OCR 2의 차별점:

주요 기능 및 아키텍처

Visual Causal Flow

버전 2의 주요 기능은 "Visual Causal Flow"입니다. 이는 단순한 OCR을 넘어 문서를 이해하는 새로운 접근 방식입니다. 페이지를 평평한 문자 그리드로 취급하는 대신, 모델은 시각적 요소들 간의 인과 관계를 학습합니다.

DeepEncoder 아키텍처

DeepEncoder는 마법이 일어나는 곳입니다. 관리 가능한 토큰 수를 유지하면서 고해상도 이미지를 처리합니다.

Input Image (1024×1024)
    ↓
SAM-base Block (80M params)
    - Windowed attention for local detail
    - Extracts fine-grained features
    ↓
CLIP-large Block (300M params)
    - Global attention for layout
    - Understands document structure
    ↓
Convolution Block
    - 16× token reduction
    - 4,096 patches → 256 tokens
    ↓
Output: Compressed Vision Tokens

압축률 vs. 정확성 트레이드오프

압축률비전 토큰정확성
4배1,02499% 이상
10배25697%
16배16092%
20배128~60%

대부분의 애플리케이션에서 가장 적합한 지점은 10배 압축률입니다. 이는 97%의 정확도를 유지하면서 프로덕션 배포를 실용적으로 만드는 높은 처리량을 가능하게 합니다.

설치 및 설정

필수 구성 요소

방법 1: vLLM 설치 (권장)

vLLM은 프로덕션 배포를 위한 최고의 성능을 제공합니다:

# 가상 환경 생성
python -m venv deepseek-ocr-env
source deepseek-ocr-env/bin/activate

# CUDA 지원 vLLM 설치
pip install vllm>=0.8.5

# 최적의 성능을 위한 flash attention 설치
pip install flash-attn==2.7.3 --no-build-isolation

방법 2: Transformers 설치

개발 및 실험용:

pip install transformers>=4.40.0
pip install torch>=2.6.0 torchvision>=0.21.0
pip install accelerate
pip install flash-attn==2.7.3 --no-build-isolation

방법 3: Docker (프로덕션)

FROM nvidia/cuda:11.8-devel-ubuntu22.04

RUN pip install vllm>=0.8.5 flash-attn==2.7.3

# 모델 사전 다운로드
RUN python -c "from vllm import LLM; LLM(model='deepseek-ai/DeepSeek-OCR-2')"

EXPOSE 8000
CMD ["vllm", "serve", "deepseek-ai/DeepSeek-OCR-2", "--port", "8000"]

설치 확인

import torch
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")

import vllm
print(f"vLLM version: {vllm.__version__}")

Python 코드 예시

vLLM을 사용한 기본 OCR

문서 이미지에서 텍스트를 추출하는 가장 간단한 방법입니다:

from vllm import LLM, SamplingParams
from vllm.model_executor.models.deepseek_ocr import NGramPerReqLogitsProcessor
from PIL import Image

# 모델 초기화
llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    enable_prefix_caching=False,
    mm_processor_cache_gb=0,
    logits_processors=[NGramPerReqLogitsProcessor],
    trust_remote_code=True,
)

# 문서 이미지 로드
image = Image.open("document.png").convert("RGB")

# 프롬프트 준비 - "Free OCR."은 표준 추출을 트리거합니다.
prompt = "<image>\nFree OCR."

model_input = [{
    "prompt": prompt,
    "multi_modal_data": {"image": image}
}]

# 샘플링 매개변수 구성
sampling_params = SamplingParams(
    temperature=0.0,  # OCR을 위한 확정적 결과
    max_tokens=8192,
    extra_args={
        "ngram_size": 30,
        "window_size": 90,
        "whitelist_token_ids": {128821, 128822},  # 테이블을 위한 <td>, </td>
    },
    skip_special_tokens=False,
)

# 출력 생성
outputs = llm.generate(model_input, sampling_params)

# 마크다운 텍스트 추출
markdown_text = outputs[0].outputs[0].text
print(markdown_text)

여러 문서 일괄 처리

단일 배치로 여러 문서를 효율적으로 처리합니다:

from vllm import LLM, SamplingParams
from vllm.model_executor.models.deepseek_ocr import NGramPerReqLogitsProcessor
from PIL import Image
from pathlib import Path

def batch_ocr(image_paths: list[str], llm: LLM) -> list[str]:
    """단일 배치로 여러 이미지 처리."""

    # 모든 이미지 로드
    images = [Image.open(p).convert("RGB") for p in image_paths]

    # 배치 입력 준비
    prompt = "<image>\nFree OCR."
    model_inputs = [
        {"prompt": prompt, "multi_modal_data": {"image": img}}
        for img in images
    ]

    sampling_params = SamplingParams(
        temperature=0.0,
        max_tokens=8192,
        extra_args={
            "ngram_size": 30,
            "window_size": 90,
            "whitelist_token_ids": {128821, 128822},
        },
        skip_special_tokens=False,
    )

    # 한 번의 호출로 모든 출력 생성
    outputs = llm.generate(model_inputs, sampling_params)

    return [out.outputs[0].text for out in outputs]


# 사용법
llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    enable_prefix_caching=False,
    mm_processor_cache_gb=0,
    logits_processors=[NGramPerReqLogitsProcessor],
)

image_files = list(Path("documents/").glob("*.png"))
results = batch_ocr([str(f) for f in image_files], llm)

for path, text in zip(image_files, results):
    print(f"--- {path.name} ---")
    print(text[:500])  # 처음 500자
    print()

Transformers 직접 사용하기

추론 프로세스를 더 잘 제어하려면:

import torch
from transformers import AutoModel, AutoTokenizer
from PIL import Image

# GPU 설정
device = "cuda:0"

# 모델 및 토크나이저 로드
model_name = "deepseek-ai/DeepSeek-OCR-2"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModel.from_pretrained(
    model_name,
    _attn_implementation="flash_attention_2",
    trust_remote_code=True,
    use_safetensors=True,
)
model = model.eval().to(device).to(torch.bfloat16)

# 이미지 로드 및 전처리
image = Image.open("document.png").convert("RGB")

# 다른 작업을 위한 다른 프롬프트
prompts = {
    "ocr": "<image>\nFree OCR.",
    "markdown": "<image>\n<|grounding|>Convert the document to markdown.",
    "table": "<image>\nExtract all tables as markdown.",
    "math": "<image>\nExtract mathematical expressions as LaTeX.",
}

# 선택한 프롬프트로 처리
prompt = prompts["markdown"]
inputs = tokenizer(prompt, return_tensors="pt").to(device)

# 입력에 이미지 추가 (모델별 전처리)
with torch.no_grad():
    outputs = model.generate(
        **inputs,
        images=[image],
        max_new_tokens=4096,
        do_sample=False,
    )

result = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(result)

높은 처리량을 위한 비동기 처리

import asyncio
from vllm import AsyncLLMEngine, AsyncEngineArgs, SamplingParams
from vllm.model_executor.models.deepseek_ocr import NGramPerReqLogitsProcessor
from PIL import Image

async def process_document(engine, image_path: str, request_id: str):
    """단일 문서를 비동기적으로 처리합니다."""
    image = Image.open(image_path).convert("RGB")

    prompt = "<image>\nFree OCR."
    sampling_params = SamplingParams(
        temperature=0.0,
        max_tokens=8192,
        extra_args={
            "ngram_size": 30,
            "window_size": 90,
            "whitelist_token_ids": {128821, 128822},
        },
    )

    results = []
    async for output in engine.generate(prompt, sampling_params, request_id):
        results.append(output)

    return results[-1].outputs[0].text

async def main():
    # 비동기 엔진 초기화
    engine_args = AsyncEngineArgs(
        model="deepseek-ai/DeepSeek-OCR-2",
        enable_prefix_caching=False,
        mm_processor_cache_gb=0,
    )
    engine = AsyncLLMEngine.from_engine_args(engine_args)

    # 여러 문서를 동시에 처리
    image_paths = ["doc1.png", "doc2.png", "doc3.png"]
    tasks = [
        process_document(engine, path, f"req_{i}")
        for i, path in enumerate(image_paths)
    ]

    results = await asyncio.gather(*tasks)

    for path, text in zip(image_paths, results):
        print(f"{path}: {len(text)} 문자 추출됨")

asyncio.run(main())

프로덕션 환경에서 vLLM 사용

OpenAI 호환 서버 시작하기

DeepSeek-OCR 2를 API 서버로 배포하기:

vllm serve deepseek-ai/DeepSeek-OCR-2 \
    --host 0.0.0.0 \
    --port 8000 \
    --logits_processors vllm.model_executor.models.deepseek_ocr:NGramPerReqLogitsProcessor \
    --no-enable-prefix-caching \
    --mm-processor-cache-gb 0 \
    --max-model-len 16384 \
    --gpu-memory-utilization 0.9

OpenAI SDK로 서버 호출하기

from openai import OpenAI
import base64

# 로컬 서버를 가리키는 클라이언트 초기화
client = OpenAI(
    api_key="EMPTY",  # 로컬 서버에는 필요 없음
    base_url="http://localhost:8000/v1",
    timeout=3600,
)

def encode_image(image_path: str) -> str:
    """이미지를 base64로 인코딩합니다."""
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

def ocr_document(image_path: str) -> str:
    """OCR API를 사용하여 문서에서 텍스트를 추출합니다."""
    base64_image = encode_image(image_path)

    response = client.chat.completions.create(
        model="deepseek-ai/DeepSeek-OCR-2",
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:image/png;base64,{base64_image}"
                        }
                    },
                    {
                        "type": "text",
                        "text": "Free OCR."
                    }
                ]
            }
        ],
        max_tokens=8192,
        temperature=0.0,
        extra_body={
            "skip_special_tokens": False,
            "vllm_xargs": {
                "ngram_size": 30,
                "window_size": 90,
                "whitelist_token_ids": [128821, 128822],
            },
        },
    )

    return response.choices[0].message.content

# 사용법
result = ocr_document("invoice.png")
print(result)

URL과 함께 사용하기

response = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-OCR-2",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/document.png"
                    }
                },
                {
                    "type": "text",
                    "text": "Free OCR."
                }
            ]
        }
    ],
    max_tokens=8192,
    temperature=0.0,
)

Apidog로 테스트하기

OCR API를 효과적으로 테스트하려면 입력 문서와 추출된 출력을 모두 시각화해야 합니다. Apidog는 DeepSeek-OCR 2를 실험하기 위한 직관적인 인터페이스를 제공합니다.

OCR 엔드포인트 설정

1단계: 새 요청 생성

  1. Apidog를 열고 새 프로젝트를 생성합니다.
  2. http://localhost:8000/v1/chat/completions에 POST 요청을 추가합니다.

2단계: 헤더 구성

Content-Type: application/json

3단계: 요청 본문 구성

{
  "model": "deepseek-ai/DeepSeek-OCR-2",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "data:image/png;base64,{{base64_image}}"
          }
        },
        {
          "type": "text",
          "text": "Free OCR."
        }
      ]
    }
  ],
  "max_tokens": 8192,
  "temperature": 0,
  "extra_body": {
    "skip_special_tokens": false,
    "vllm_xargs": {
      "ngram_size": 30,
      "window_size": 90,
      "whitelist_token_ids": [128821, 128822]
    }
  }
}

다양한 문서 유형 테스트

일반적인 문서 유형에 대해 저장된 요청을 생성합니다:

  1. 송장 추출 - 구조화된 데이터 추출 테스트
  2. 학술 논문 - LaTeX 수학 처리 테스트
  3. 손글씨 메모 - 필기 인식 테스트
  4. 다단 레이아웃 - 읽기 순서 추론 테스트

해상도 모드 비교

다양한 모드를 빠르게 테스트하려면 환경 변수를 설정하세요:

모드해상도토큰사용 사례
tiny512×51264빠른 미리보기
small640×640100간단한 문서
base1024×1024256표준 문서
large1280×1280400조밀한 텍스트
gundam동적가변복잡한 레이아웃

해상도 모드 및 압축

DeepSeek-OCR 2는 각각 다른 사용 사례에 최적화된 다섯 가지 해상도 모드를 지원합니다:

Tiny 모드 (64 토큰)

최적: 빠른 텍스트 감지, 간단한 양식, 저해상도 입력

# tiny 모드 구성
os.environ["DEEPSEEK_OCR_MODE"] = "tiny"  # 512×512

Small 모드 (100 토큰)

최적: 깔끔한 디지털 문서, 단일 열 텍스트

Base 모드 (256 토큰) - 기본값

최적: 대부분의 표준 문서, 송장, 편지

Large 모드 (400 토큰)

최적: 조밀한 학술 논문, 법률 문서

Gundam 모드 (동적)

최적: 다양한 레이아웃을 가진 복잡한 다중 페이지 문서

# 건담 모드는 여러 뷰를 결합합니다
# - 세부 사항을 위한 n × 640×640 지역 타일
# - 구조를 위한 1 × 1024×1024 전역 뷰

올바른 모드 선택

def select_mode(document_type: str, page_count: int) -> str:
    """문서 특성에 따라 최적의 해상도 모드를 선택합니다."""

    if document_type == "simple_form":
        return "tiny"
    elif document_type == "digital_document" and page_count == 1:
        return "small"
    elif document_type == "academic_paper":
        return "large"
    elif document_type == "mixed_layout" or page_count > 1:
        return "gundam"
    else:
        return "base"  # 기본값

# 예시: 1024x1024 이미지와 A100-40G
batch_size = optimal_batch_size(40, (1024, 1024))
print(f"Recommended batch size: {batch_size}")  # ~10

PDF 및 문서 처리

PDF를 이미지로 변환

import fitz  # PyMuPDF
from PIL import Image
import io

def pdf_to_images(pdf_path: str, dpi: int = 150) -> list[Image.Image]:
    """PDF 페이지를 PIL 이미지로 변환합니다."""
    doc = fitz.open(pdf_path)
    images = []

    for page_num in range(len(doc)):
        page = doc[page_num]
        # 지정된 DPI로 렌더링
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        pix = page.get_pixmap(matrix=mat)

        # PIL 이미지로 변환
        img_data = pix.tobytes("png")
        img = Image.open(io.BytesIO(img_data))
        images.append(img)

    doc.close()
    return images

# 사용법
images = pdf_to_images("report.pdf", dpi=200)
print(f"추출된 페이지 수: {len(images)} 페이지")

전체 PDF 처리 파이프라인

from vllm import LLM, SamplingParams
from vllm.model_executor.models.deepseek_ocr import NGramPerReqLogitsProcessor
from pathlib import Path
import fitz
from PIL import Image
import io

class PDFProcessor:
    def __init__(self, model_name: str = "deepseek-ai/DeepSeek-OCR-2"):
        self.llm = LLM(
            model=model_name,
            enable_prefix_caching=False,
            mm_processor_cache_gb=0,
            logits_processors=[NGramPerReqLogitsProcessor],
        )
        self.sampling_params = SamplingParams(
            temperature=0.0,
            max_tokens=8192,
            extra_args={
                "ngram_size": 30,
                "window_size": 90,
                "whitelist_token_ids": {128821, 128822},
            },
            skip_special_tokens=False,
        )

    def process_pdf(self, pdf_path: str, dpi: int = 150) -> str:
        """전체 PDF를 처리하고 결합된 마크다운을 반환합니다."""
        doc = fitz.open(pdf_path)
        all_text = []

        for page_num in range(len(doc)):
            # 페이지를 이미지로 변환
            page = doc[page_num]
            mat = fitz.Matrix(dpi / 72, dpi / 72)
            pix = page.get_pixmap(matrix=mat)
            img = Image.open(io.BytesIO(pix.tobytes("png")))

            # 페이지 OCR
            prompt = "<image>\nFree OCR."
            model_input = [{
                "prompt": prompt,
                "multi_modal_data": {"image": img}
            }]

            output = self.llm.generate(model_input, self.sampling_params)
            page_text = output[0].outputs[0].text

            all_text.append(f"## Page {page_num + 1}\n\n{page_text}")

        doc.close()
        return "\n\n---\n\n".join(all_text)

# 사용법
processor = PDFProcessor()
markdown = processor.process_pdf("annual_report.pdf")

# 파일로 저장
Path("output.md").write_text(markdown)

벤치마크 성능

정확성 벤치마크

벤치마크DeepSeek-OCR 2GOT-OCR2.0MinerU2.0
OmniDocBench94.2%91.8%89.5%
페이지당 토큰 수100-2562566,000+
Fox (10배 압축)97%
Fox (20배 압축)60%

처리량 성능

하드웨어일일 페이지 수시간당 페이지 수
A100-40G (단일)200,000+~8,300
A100-40G × 203,300만+~140만
RTX 4090~80,000~3,300
RTX 3090~50,000~2,100

문서 유형별 실제 정확도

문서 유형정확성비고
디지털 PDF98% 이상최상의 성능
스캔 문서95% 이상양질의 스캔
재무 보고서92%복잡한 테이블
손글씨 메모85%가독성에 따라 다름
역사 문서80%품질 저하

모범 사례 및 최적화

이미지 전처리

from PIL import Image, ImageEnhance, ImageFilter

def preprocess_document(image: Image.Image) -> Image.Image:
    """최적의 OCR을 위해 문서 이미지를 전처리합니다."""

    # 필요한 경우 RGB로 변환
    if image.mode != "RGB":
        image = image.convert("RGB")

    # 너무 작으면 크기 조정 (가장 짧은 변이 최소 512px)
    min_dim = min(image.size)
    if min_dim < 512:
        scale = 512 / min_dim
        new_size = (int(image.width * scale), int(image.height * scale))
        image = image.resize(new_size, Image.Resampling.LANCZOS)

    # 스캔 문서의 대비 강화
    enhancer = ImageEnhance.Contrast(image)
    image = enhancer.enhance(1.2)

    # 약간 선명하게
    image = image.filter(ImageFilter.SHARPEN)

    return image

프롬프트 엔지니어링

# 다른 작업을 위한 다른 프롬프트
PROMPTS = {
    # 표준 OCR - 가장 빠르고 대부분의 경우에 적합
    "ocr": "<image>\nFree OCR.",

    # 마크다운 변환 - 더 나은 구조 보존
    "markdown": "<image>\n<|grounding|>Convert the document to markdown.",

    # 테이블 추출 - 표 형식 데이터에 최적화
    "table": "<image>\nExtract all tables in markdown format.",

    # 수학 추출 - 학술/과학 문서용
    "math": "<image>\nExtract all text and mathematical expressions. Use LaTeX for math.",

    # 특정 필드 - 양식 추출용
    "fields": "<image>\nExtract the following fields: name, date, amount, signature.",
}

메모리 최적화

# 제한된 GPU 메모리용
llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    gpu_memory_utilization=0.8,  # 여유 공간 남기기
    max_model_len=8192,  # 최대 컨텍스트 줄이기
    enable_chunked_prefill=True,  # 더 나은 메모리 효율성
)

배치 전략

def optimal_batch_size(gpu_memory_

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요