Cómo usar DeepSeek-OCR 2: Guía paso a paso

Ashley Innocent

Ashley Innocent

27 January 2026

Cómo usar DeepSeek-OCR 2: Guía paso a paso

El procesamiento de documentos ha sido durante mucho tiempo una de las aplicaciones más prácticas de la IA; sin embargo, la mayoría de las soluciones de OCR obligan a un incómodo compromiso entre precisión y eficiencia. Los sistemas tradicionales como Tesseract requieren un preprocesamiento extenso. Las API en la nube cobran por página y añaden latencia. Incluso los modelos modernos de visión-lenguaje luchan con la explosión de tokens que proviene de imágenes de documentos de alta resolución.

DeepSeek-OCR 2 cambia esta ecuación por completo. Basándose en el enfoque de "Compresión Óptica de Contextos" de la versión 1, la nueva versión introduce el "Flujo Causal Visual"—una arquitectura que procesa documentos de la manera en que los humanos realmente los leen, comprendiendo las relaciones visuales y el contexto en lugar de simplemente reconocer caracteres. El resultado es un modelo que logra un 97% de precisión mientras comprime imágenes a tan solo 64 tokens, lo que permite un rendimiento de más de 200,000 páginas por día en una sola GPU.

Esta guía cubre todo, desde la configuración básica hasta la implementación en producción, con código funcional que puedes copiar, pegar y ejecutar de inmediato.

💡
¿Listo para probar DeepSeek-OCR 2? Descarga Apidog para experimentar visualmente con la API. Apidog te permite subir imágenes de documentos, configurar solicitudes de OCR, inspeccionar la salida en markdown y comparar resultados en diferentes modos de resolución, todo sin escribir código repetitivo.

¿Qué es DeepSeek-OCR 2?

DeepSeek-OCR 2 es un modelo de visión-lenguaje de código abierto diseñado específicamente para la comprensión de documentos y la extracción de texto. Lanzado por DeepSeek AI en enero de 2026, se basa en el DeepSeek-OCR original con una nueva arquitectura de "Flujo Causal Visual" que modela cómo los elementos visuales en los documentos se relacionan causalmente entre sí, entendiendo que un encabezado de tabla determina cómo deben interpretarse las celdas debajo de él, o que una leyenda explica el gráfico que tiene encima.

El modelo consta de dos componentes principales:

  1. DeepEncoder: Un transformador de visión dual que combina la extracción de detalles locales (basado en SAM, 80M de parámetros) con la comprensión global del diseño (basado en CLIP, 300M de parámetros)
  2. Decodificador DeepSeek3B-MoE: Un modelo de lenguaje de mezcla de expertos que genera una salida estructurada (Markdown, LaTeX, JSON) a partir de la representación visual comprimida

Lo que hace diferente a DeepSeek-OCR 2:

Características Clave y Arquitectura

Flujo Causal Visual

La característica principal de la versión 2 es el "Flujo Causal Visual"—un nuevo enfoque para comprender documentos que va más allá del simple OCR. En lugar de tratar una página como una cuadrícula plana de caracteres, el modelo aprende relaciones causales entre los elementos visuales:

Arquitectura de DeepEncoder

El DeepEncoder es donde ocurre la magia. Procesa imágenes de alta resolución mientras mantiene un recuento de tokens manejable:

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

Compromiso entre Compresión y Precisión

Relación de CompresiónTokens de VisiónPrecisión
1.02499%+
10×25697%
16×16092%
20×128~60%

El punto óptimo para la mayoría de las aplicaciones es la relación de compresión de 10×, que mantiene una precisión del 97% mientras permite el alto rendimiento que hace práctica la implementación en producción.

Instalación y Configuración

Requisitos Previos

Método 1: Instalación de vLLM (Recomendado)

vLLM proporciona el mejor rendimiento para implementaciones de producción:

# Create virtual environment
python -m venv deepseek-ocr-env
source deepseek-ocr-env/bin/activate

# Install vLLM with CUDA support
pip install vllm>=0.8.5

# Install flash attention for optimal performance
pip install flash-attn==2.7.3 --no-build-isolation

Método 2: Instalación de Transformers

Para desarrollo y experimentación:

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

Método 3: Docker (Producción)

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

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

# Pre-download model
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"]

Verificar Instalación

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__}")

Ejemplos de Código Python

OCR Básico con vLLM

Aquí está la forma más sencilla de extraer texto de una imagen de documento:

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

# Initialize the model
llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    enable_prefix_caching=False,
    mm_processor_cache_gb=0,
    logits_processors=[NGramPerReqLogitsProcessor],
    trust_remote_code=True,
)

# Load your document image
image = Image.open("document.png").convert("RGB")

# Prepare the prompt - "Free OCR." triggers standard extraction
prompt = "<image>\nFree OCR."

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

# Configure sampling parameters
sampling_params = SamplingParams(
    temperature=0.0,  # Deterministic for OCR
    max_tokens=8192,
    extra_args={
        "ngram_size": 30,
        "window_size": 90,
        "whitelist_token_ids": {128821, 128822},  # <td>, </td> for tables
    },
    skip_special_tokens=False,
)

# Generate output
outputs = llm.generate(model_input, sampling_params)

# Extract the markdown text
markdown_text = outputs[0].outputs[0].text
print(markdown_text)

Procesamiento por Lotes de Múltiples Documentos

Procesa múltiples documentos de manera eficiente en un solo lote:

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]:
    """Process multiple images in a single batch."""

    # Load all images
    images = [Image.open(p).convert("RGB") for p in image_paths]

    # Prepare batch input
    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,
    )

    # Generate all outputs in one call
    outputs = llm.generate(model_inputs, sampling_params)

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


# Usage
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])  # First 500 chars
    print()

Usando Transformers Directamente

Para un mayor control sobre el proceso de inferencia:

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

# Set GPU
device = "cuda:0"

# Load model and tokenizer
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)

# Load and preprocess image
image = Image.open("document.png").convert("RGB")

# Different prompts for different tasks
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.",
}

# Process with your chosen prompt
prompt = prompts["markdown"]
inputs = tokenizer(prompt, return_tensors="pt").to(device)

# Add image to inputs (model-specific preprocessing)
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)

Procesamiento Asíncrono para Alto Rendimiento

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):
    """Process a single document asynchronously."""
    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():
    # Initialize async engine
    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)

    # Process multiple documents concurrently
    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)} characters extracted")

asyncio.run(main())

Usando vLLM para Producción

Iniciando el Servidor Compatible con OpenAI

Despliega DeepSeek-OCR 2 como un servidor 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

Llamando al Servidor con el SDK de OpenAI

from openai import OpenAI
import base64

# Initialize client pointing to local server
client = OpenAI(
    api_key="EMPTY",  # Not required for local server
    base_url="http://localhost:8000/v1",
    timeout=3600,
)

def encode_image(image_path: str) -> str:
    """Encode image to base64."""
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

def ocr_document(image_path: str) -> str:
    """Extract text from document using 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

# Usage
result = ocr_document("invoice.png")
print(result)

Usando con URLs

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,
)

Probando con Apidog

Probar las APIs de OCR de manera efectiva requiere visualizar tanto los documentos de entrada como la salida extraída. Apidog proporciona una interfaz intuitiva para experimentar con DeepSeek-OCR 2.

Configurando el Endpoint de OCR

Paso 1: Crear una Nueva Solicitud

  1. Abre Apidog y crea un nuevo proyecto
  2. Añade una solicitud POST a http://localhost:8000/v1/chat/completions

Paso 2: Configurar Encabezados

Content-Type: application/json

Paso 3: Configurar Cuerpo de la Solicitud

{
  "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]
    }
  }
}

Probando Diferentes Tipos de Documentos

Crea solicitudes guardadas para tipos de documentos comunes:

  1. Extracción de facturas - Prueba de extracción de datos estructurados
  2. Artículo académico - Prueba de manejo de matemáticas en LaTeX
  3. Notas manuscritas - Prueba de reconocimiento de escritura a mano
  4. Diseño de varias columnas - Prueba de inferencia del orden de lectura

Comparando Modos de Resolución

Configura variables de entorno para probar rápidamente diferentes modos:

ModoResoluciónTokensCaso de Uso
tiny512×51264Vistas previas rápidas
small640×640100Documentos simples
base1024×1024256Documentos estándar
large1280×1280400Texto denso
gundamDinámicoVariableDiseños complejos

Modos de Resolución y Compresión

DeepSeek-OCR 2 soporta cinco modos de resolución, cada uno optimizado para diferentes casos de uso:

Modo Minúsculo (64 tokens)

Mejor para: Detección rápida de texto, formularios simples, entradas de baja resolución

# Configure for tiny mode
os.environ["DEEPSEEK_OCR_MODE"] = "tiny"  # 512×512

Modo Pequeño (100 tokens)

Mejor para: Documentos digitales limpios, texto de una sola columna

Modo Base (256 tokens) - Predeterminado

Mejor para: La mayoría de los documentos estándar, facturas, cartas

Modo Grande (400 tokens)

Mejor para: Artículos académicos densos, documentos legales

Modo Gundam (Dinámico)

Mejor para: Documentos complejos de varias páginas con diseños variados

# Gundam mode combines multiple views
# - n × 640×640 local tiles for detail
# - 1 × 1024×1024 global view for structure

Eligiendo el Modo Correcto

def select_mode(document_type: str, page_count: int) -> str:
    """Selecciona el modo de resolución óptimo según las características del documento."""

    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"  # Predeterminado

Procesando PDFs y Documentos

Convirtiendo PDFs a Imágenes

import fitz  # PyMuPDF
from PIL import Image
import io

def pdf_to_images(pdf_path: str, dpi: int = 150) -> list[Image.Image]:
    """Convierte páginas PDF a Imágenes PIL."""
    doc = fitz.open(pdf_path)
    images = []

    for page_num in range(len(doc)):
        page = doc[page_num]
        # Renderizar a la DPI especificada
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        pix = page.get_pixmap(matrix=mat)

        # Convertir a Imagen PIL
        img_data = pix.tobytes("png")
        img = Image.open(io.BytesIO(img_data))
        images.append(img)

    doc.close()
    return images

# Uso
images = pdf_to_images("report.pdf", dpi=200)
print(f"Se extrajeron {len(images)} páginas")

Pipeline Completo de Procesamiento de 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:
        """Procesa el PDF completo y devuelve el markdown combinado."""
        doc = fitz.open(pdf_path)
        all_text = []

        for page_num in range(len(doc)):
            # Convertir página a imagen
            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")))

            # Realizar OCR en la página
            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)

# Uso
processor = PDFProcessor()
markdown = processor.process_pdf("annual_report.pdf")

# Guardar en archivo
Path("output.md").write_text(markdown)

Rendimiento de Referencia

Benchmarks de Precisión

BenchmarkDeepSeek-OCR 2GOT-OCR2.0MinerU2.0
OmniDocBench94.2%91.8%89.5%
Tokens/página100-2562566.000+
Fox (compresión 10×)97%--
Fox (compresión 20×)60%--

Rendimiento de Producción

HardwarePáginas/DíaPáginas/Hora
A100-40G (único)200.000+~8.300
A100-40G × 2033M+~1.4M
RTX 4090~80.000~3.300
RTX 3090~50.000~2.100

Precisión en el Mundo Real por Tipo de Documento

Tipo de DocumentoPrecisiónNotas
PDFs Digitales98%+Mejor rendimiento
Documentos escaneados95%+Escaneos de buena calidad
Informes financieros92%Tablas complejas
Notas manuscritas85%Depende de la legibilidad
Documentos históricos80%Calidad degradada

Mejores Prácticas y Optimización

Preprocesamiento de Imagen

from PIL import Image, ImageEnhance, ImageFilter

def preprocess_document(image: Image.Image) -> Image.Image:
    """Preprocesar imagen de documento para un OCR óptimo."""

    # Convertir a RGB si es necesario
    if image.mode != "RGB":
        image = image.convert("RGB")

    # Redimensionar si es demasiado pequeña (mínimo 512px en el lado más corto)
    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)

    # Mejorar el contraste para documentos escaneados
    enhancer = ImageEnhance.Contrast(image)
    image = enhancer.enhance(1.2)

    # Afilar ligeramente
    image = image.filter(ImageFilter.SHARPEN)

    return image

Ingeniería de Prompts

# Diferentes prompts para diferentes tareas
PROMPTS = {
    # OCR estándar - el más rápido, bueno para la mayoría de los casos
    "ocr": "<image>\nFree OCR.",

    # Conversión a Markdown - mejor preservación de la estructura
    "markdown": "<image>\n<|grounding|>Convert the document to markdown.",

    # Extracción de tablas - optimizado para datos tabulares
    "table": "<image>\nExtract all tables in markdown format.",

    # Extracción de matemáticas - para documentos académicos/científicos
    "math": "<image>\nExtract all text and mathematical expressions. Use LaTeX for math.",

    # Campos específicos - para extracción de formularios
    "fields": "<image>\nExtract the following fields: name, date, amount, signature.",
}

Optimización de Memoria

# Para memoria de GPU limitada
llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    gpu_memory_utilization=0.8,  # Dejar margen
    max_model_len=8192,  # Reducir contexto máximo
    enable_chunked_prefill=True,  # Mejor eficiencia de memoria
)

Estrategia de Procesamiento por Lotes

def optimal_batch_size(gpu_memory_gb: int, avg_image_size: tuple) -> int:
    """Calcula el tamaño de lote óptimo basado en la memoria de la GPU."""

    # Memoria aproximada por imagen (en GB)
    pixels = avg_image_size[0] * avg_image_size[1]
    mem_per_image = (pixels * 4) / (1024**3)  # 4 bytes por píxel

    # Reservar el 60% de la memoria de la GPU para el modelo
    available = gpu_memory_gb * 0.4

    return max(1, int(available / mem_per_image))

# Ejemplo: A100-40G con imágenes de 1024x1024
batch_size = optimal_batch_size(40, (1024, 1024))
print(f"Tamaño de lote recomendado: {batch_size}")  # ~10

Solución de Problemas Comunes

Errores de Memoria Insuficiente

Problema: CUDA out of memory

Soluciones:

# 1. Reducir tamaño de lote
sampling_params = SamplingParams(max_tokens=4096)  # Reducir de 8192

# 2. Usar modo de resolución más pequeño
os.environ["DEEPSEEK_OCR_MODE"] = "small"

# 3. Habilitar optimización de memoria
llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    gpu_memory_utilization=0.7,
    enforce_eager=True,  # Deshabilitar gráficos CUDA
)

Mala Extracción de Tablas

Problema: Las tablas están desalineadas o faltan celdas

Soluciones:

# Asegurarse de que los tokens de la lista blanca estén configurados
sampling_params = SamplingParams(
    extra_args={
        "whitelist_token_ids": {128821, 128822},  # Crítico para tablas
    },
)

# Usar mayor resolución
os.environ["DEEPSEEK_OCR_MODE"] = "large"

Inferencia Lenta

Problema: El procesamiento tarda demasiado

Soluciones:

  1. Usa vLLM en lugar de Transformers (2-3 veces más rápido)
  2. Habilitar Flash Attention 2
  3. Usa procesamiento por lotes en lugar de secuencial
  4. Despliega en GPU con núcleos tensores (A100, H100)

Salida Distorsionada

Problema: La salida contiene disparates o caracteres repetidos

Soluciones:

# Asegurarse de que el procesador de logits esté habilitado
from vllm.model_executor.models.deepseek_ocr import NGramPerReqLogitsProcessor

llm = LLM(
    model="deepseek-ai/DeepSeek-OCR-2",
    logits_processors=[NGramPerReqLogitsProcessor],  # ¡Requerido!
)

# Usar temperature=0 para salida determinista
sampling_params = SamplingParams(temperature=0.0)

¿Listo para extraer texto de tus documentos? Descarga Apidog para probar las llamadas a la API de DeepSeek-OCR 2 con una interfaz visual, y luego despliega con confianza usando los patrones de producción de esta guía.

button

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs