
Unlimited-OCR 2 en 16 GB: lee tan bien como Chandra 2 de Datalab, pero en mi tarjeta no es ilimitado
Probé Unlimited-OCR 2 de Baidu, publicado el 27 de octubre, contra Chandra 2 y Marker de Datalab, DeepSeek-OCR y Tesseract en una RTX 4070 Ti SUPER. En 20 páginas de un informe real del Banco Central de Chile empató arriba con Chandra 2: 0,12 % contra 0,31 % de error por carácter, una diferencia que no alcanza a ser significativa; DeepSeek-OCR quedó en 3,1 %. Pero la promesa de leer documentos sin límite no se cumplió en 16 GB: con transformers la memoria subió de 7,5 a 11,1 GB entre 10 y 40 páginas y a las 80 se quedó sin memoria; con SGLang el tiempo por token sí quedó plano, pero el modo por ventanas se colgó y una sola ventana devolvió 24 de 40 páginas sin dar ningún error.
El 27 de octubre Baidu publicó Unlimited-OCR 2 (el repositorio en Hugging Face existe desde el 26): 3,3B de parámetros, licencia MIT, y una promesa concreta. Con su atención dispersa SR-SWA, dice el README, la latencia por paso y la memoria “se mantienen casi constantes sin importar el largo del documento”. Eso se puede medir. Esa misma noche lo estaba ejecutando en mi RTX 4070 Ti SUPER de 16 GB, contra DeepSeek-OCR, el modelo del que deriva, contra su propia versión 1, contra Tesseract y contra lo que yo usaba para archivos enormes: Datalab, con su tubería Marker y su modelo Chandra 2, los dos en local.
Leyó tan bien como Chandra 2: en 20 páginas de un informe real del Banco Central de Chile, 0,12 % de error por carácter contra 0,31 %, un empate dentro del margen. Les ganó a Tesseract (0,51 %), a Marker (2,5 %) y a DeepSeek-OCR (3,1 %). La promesa de leer sin límite, en cambio, no la vi cumplirse en 16 GB. Con transformers la memoria crece con cada página y a las 80 se acaba. Con SGLang el tiempo por token sí quedó plano, pero el modo por ventanas se colgó, y con una sola ventana el servidor devolvió 24 de 40 páginas sin dar ningún error.
Qué medí y cómo
Antes de medir escribí una ficha con cuatro hipótesis y su regla de decisión, y la registré en git. Después le agregué cinco enmiendas, cada una registrada antes de la medición que regula; dos son post hoc y lo dicen, y la quinta sumó a Datalab. La segunda fijó también las páginas sueltas, cuya prueba de humo ya había empezado.
Las hipótesis: que la memoria y los milisegundos por token de Unlimited-OCR 2 crecen menos de 25 % entre 10 y 80 páginas en una sola petición (H1); que la versión 1, sin SR-SWA, crece más o se queda sin memoria antes (H2, el control positivo); que su error por página no es peor que el de DeepSeek-OCR y que ambos le ganan a Tesseract (H3); y que leer el documento entero de una vez no empeora el error frente a leerlo página por página (H4). La enmienda 5 agregó que no es peor que Marker ni que Chandra 2 (H6) y que esos dos pasan el informe completo, 74 páginas, sin quedarse sin memoria (H7).
Dos documentos. El primero, el comienzo del Quijote compuesto a PDF con PyMuPDF, sin partición silábica: el texto de referencia de cada página es exactamente lo impreso. El segundo, real: las 20 páginas con más texto y sin imágenes del Informe de Política Monetaria de septiembre de 2026 del Banco Central de Chile, con la capa de texto del PDF (pdftotext -layout) como referencia. Todas las páginas se renderizaron una vez a 144 ppp y todos los motores leyeron los mismos PNG. A Marker no le di el PDF: habría sacado la capa de texto, que es justamente la referencia.
La métrica es la tasa de error por carácter (CER): la distancia de Levenshtein entre lo que sale y la referencia, dividida por el largo de la referencia, después de normalizar igual para todos (Unicode NFKC, sin las cajas ni el marcado que emiten los modelos, minúsculas, espacios colapsados).
Cada modelo se ejecutó en su propio entorno con las versiones que fija su ficha: Unlimited-OCR 2 con transformers 4.52.1, la versión 1 con 4.57.1 y DeepSeek-OCR con 4.46.3 y PyTorch 2.6. Todo en BF16 y una petición a la vez, con un candado de GPU para que ningún otro trabajo de cómputo se cruzara.
Instalación, con sus tropiezos
Unlimited-OCR 2 bajó y cargó sin problemas, pero la primera página falló con FileNotFoundError: '/sbin/ldconfig'. Sus kernels de atención usan Triton, que busca la biblioteca de CUDA con ldconfig, y en NixOS ese programa no está en esa ruta. Se arregla diciéndole dónde está: TRITON_LIBCUDA_PATH=/run/opengl-driver/lib. La versión 1 pidió matplotlib, que su README no lista. DeepSeek-OCR anda sin flash-attn si se carga con atención eager.
Marker 2.0 tuvo su propio tropiezo. Para su modelo de OCR, Surya 2, levanta un servidor con docker run --runtime nvidia, y en NixOS la GPU llega a Docker por CDI, no por ese runtime. Lo resolví levantando yo el mismo servidor: llama-server en la imagen oficial de llama.cpp con la GPU por CDI, el GGUF de Surya 2 y los mismos argumentos que pasaría Surya, y Marker conectado por SURYA_INFERENCE_URL. En el camino, el contenedor no encontraba los pesos: el caché de Hugging Face guarda enlaces simbólicos a ../../blobs, y hay que montar el repositorio completo. Chandra 2 se instaló y funcionó sin tropiezos.
El SGLang modificado que Baidu publica en su repositorio costó más, y lo cuento abajo.
Calidad: en texto limpio empatan todos, en el informe real Unlimited-OCR 2 y Chandra 2 quedan arriba
| CER mediano por página | Tesseract | DeepSeek | Marker 2.0 | Chandra 2 | Unlimited 1 | Unlimited 2 |
|---|---|---|---|---|---|---|
| Quijote limpio (20 páginas) | 0,01 % | 0,01 % | 0,00 % | 0,16 % | 0,02 % | 0,00 % |
| Quijote degradado (20) | 0,10 % | 0,05 % | 0,00 % | 0,16 % | 0,00 % | 0,03 % |
| IPoM real (20) | 0,51 % | 3,12 % | 2,51 % | 0,31 % | 0,60 % | 0,12 % |
| Peor página del IPoM | 3,85 % | 19,78 % | 11,26 % | 7,49 % | 8,77 % | 12,02 % |
En el Quijote limpio todos quedaron prácticamente en cero, Tesseract incluido. Ese instrumento no distingue nada, así que la enmienda 3 agregó las mismas 20 páginas degradadas como un escaneo: rotación de hasta 1,5°, menos resolución, desenfoque, ruido y JPEG de calidad 35, todo con semilla fija. Ahí Tesseract tuvo una página con 11,9 % de error y los modelos de visión no pasaron de 0,29 %, salvo Marker, con una página en 1,3 %. Pero las medianas quedan en 0,1 % o menos: es una degradación suave.
El informe real es donde se separan. Diferencias pareadas por página, con intervalo de confianza al 95 % por bootstrap:
- Unlimited-OCR 2 contra Chandra 2: -0,15 puntos (IC -0,36 a 0,00). Empate: el intervalo llega al cero.
- Unlimited-OCR 2 contra Marker: -0,68 puntos (IC -2,94 a -0,17). Mejor.
- Unlimited-OCR 2 contra DeepSeek-OCR: -1,5 puntos (IC -7,3 a -1,3). Mejor.
- Unlimited-OCR 2 contra Tesseract: -0,29 puntos (IC -0,44 a -0,18). Mejor, por poco.
- Unlimited-OCR 2 contra su versión 1: -0,32 puntos (IC -0,69 a -0,25).
- DeepSeek-OCR contra Tesseract: +2,8 puntos (IC +1,0 a +8,7). Peor.
DeepSeek-OCR no inventaba texto: lo omitía. En 6 de las 20 páginas del informe devolvió menos del 90 % de los caracteres (Unlimited-OCR 2, en 1); en la página 57 entregó 2.602 de 3.241 y se saltó los últimos párrafos. Así que H3 queda partida: Unlimited-OCR 2 no es peor que DeepSeek-OCR, es claramente mejor; pero “ambos le ganan a Tesseract” es falso, porque en el informe real DeepSeek-OCR perdió contra Tesseract.
Marker es otro caso. En una página típica (la 29, con 2,9 % de error), casi todo el error sale de dónde ubica el pie de página (“informe de política monetaria septiembre 2026 28”) y de cómo numera las notas: el texto está. El CER castiga ese orden, y lo castiga igual para todos. H6 se cumple: Unlimited-OCR 2 no es peor que Marker ni que Chandra 2.
En velocidad el orden se invierte. Ojo: Tesseract se ejecutó en la CPU de mi Mac, no en la tarjeta.
Animación diez veces más rápida que lo medido. Tesseract se ejecutó en la CPU de otra máquina; en Marker y Chandra la cifra incluye cargar el modelo.
Animación cien veces más rápida que lo medido. Mismo CER mediano en los dos motores.
RTX 4070 Ti SUPER, BF16, una petición a la vez. Tesseract: CPU de un Mac Intel.
Con transformers, Unlimited-OCR 2 es el más lento: unos 36 s por página del informe, el doble que DeepSeek-OCR. Sospecho de su atención dispersa en esa implementación, pero no lo aislé. Con SGLang baja a 4,2 ms por token: las mismas 20 páginas tardan 9,3 veces menos que con transformers, con el mismo CER mediano. Las salidas no son idénticas: 5 de las 20 páginas del informe y 6 del Quijote difieren en detalles, como un acento, una palabra o un subrayado; ninguna en una cifra.
Un archivo enorme: el informe completo, 74 páginas
Para lo que yo usaba Datalab, la pregunta es otra: pasar un PDF largo entero sin que nada se caiga. Le di a Marker y a Chandra 2 las 74 páginas del IPoM.
| IPoM completo | páginas devueltas | minutos | s por página | memoria en la tarjeta |
|---|---|---|---|---|
| Marker 2.0 | 74 de 74 | 12,6 | 10,2 | 4,2 GB |
| Chandra 2 | 74 de 74 | 46,4 | 37,6 | 11,0 GB |
Los dos terminaron, y H7 se cumple. La memoria es la que ocupó cada uno en la tarjeta, descontando los 2,4 GB de los otros procesos. Marker fue casi cuatro veces más rápido. Hay dos diferencias de montaje que pueden explicarlo y que no aislé: Chandra, con el método hf, lee una página a la vez (su tamaño de lote por defecto), y Marker le manda sus pedidos a un servidor con 8 ranuras en paralelo. Unlimited-OCR 2 página por página ocuparía lo mismo que con una página, unos 8,2 GB, pero no pasé las 74 así: lo que probé fue su promesa de leerlas en una sola petición, y eso viene abajo.
Leer el documento entero de una vez no cambió el error
El modelo puede leer todas las páginas en una sola petición, para usar el contexto entre páginas. En el informe real no ayudó ni perjudicó: el CER mediano fue 0,25 % en una sola petición contra 0,12 % página por página, una diferencia pareada de +0,03 puntos (IC -0,04 a +0,13). En el Quijote limpio, +0,05 puntos. H4 se cumple: no empeora.
Esta conclusión cambió durante la auditoría. Mi primera versión decía que empeoraba un punto entero, y era un error mío: la normalización dejaba dentro del texto las coordenadas de los títulos que emite Unlimited-OCR 2, y la lectura de varias páginas marca más títulos. Lo corregí, quedó registrado en la ficha y recalculé todo.
La promesa de memoria constante, medida con transformers
La ficha pedía que, de 10 a 80 páginas, el pico de memoria y los milisegundos por token crecieran menos de 25 %. La versión 1, sin SR-SWA, era el control: tenía que crecer más. Las dos meten cada página como 256 tokens de imagen (una vista global de 1024 píxeles), así que la comparación es pareja.
- Unlimited-OCR 2:7,5 → 11,1 · 80 sin memoria
- Unlimited-OCR 1:8,4 → 10,6 · 40 sin memoria
Unlimited-OCR 2 llegó a 40 páginas y cayó en las 80 tras 74 minutos generando. La versión 1 cayó al empezar las 40. Con 20 páginas, la versión 2 usó 8,7 GB y la 1, 10,6 GB.
H1 no se cumple. De 10 a 40 páginas, Unlimited-OCR 2 subió de 7,5 a 11,1 GB (1,49 veces) y de 35,5 a 52,7 ms por token (1,49 veces). Con 80 páginas se quedó sin memoria después de 74 minutos generando, con 12,7 GB asignados.
H2 sí se cumple: la versión 1 crece más. De 10 a 20 páginas pasó de 8,4 a 10,6 GB y de 34 a 61 ms por token, y con 40 se quedó sin memoria a los 5 segundos, al leer las páginas. La versión 2 hace lo mismo con menos: a 20 páginas, 8,7 GB y 40 ms por token.
Eso es coherente con lo que dice el código. En transformers, la generación usa una caché dinámica y desactiva la ventana deslizante: guarda cada token. La atención solo lee 16 bloques más una ventana local de 128 tokens por paso, pero todo lo demás sigue en memoria. La cruz de las 80 páginas queda bajo la línea de lo libre porque la memoria asignada por PyTorch no cuenta el contexto de CUDA ni lo que reserva su asignador.
En transformers la caché sigue creciendo aunque la atención solo lea 16 bloques por paso. SGLang mantiene plano el tiempo por token, pero aquí sus ventanas se colgaron y una sola ventana cortó el documento donde se llenó la caché.
Hay que decirlo: en esa tarjeta estaban abiertos otros procesos (un juego abierto y ComfyUI) que ocupaban 2,4 GB. El modelo tenía 13,6 GB, no 16. Con la tarjeta vacía, las 80 páginas quizá habrían terminado; eso no lo medí.
Y con SGLang, el motor que recomienda Baidu
El README recomienda SGLang para servir, con un truco: leer el documento en ventanas de 10 páginas y pasar a la ventana siguiente solo la cola de la anterior. Ahí estaría la memoria acotada. La enmienda 4 lo midió, con un tope de 90 minutos para dejarlo funcionando.
Instalar su SGLang modificado pidió cuatro arreglos en NixOS: el índice de paquetes de NVIDIA para cuda-tile, un CUDA_HOME apuntando al nvcc que trae pip, ninja y un compilador de C++ para los kernels que compila al vuelo, y un enlace a libcudart.so. A los 15 minutos el servidor respondía.
| SGLang, Quijote | páginas pedidas | páginas devueltas | segundos | ms por token |
|---|---|---|---|---|
| ventanas de 10 | 10 | 10 | 56,3 | 4,17 |
| ventanas de 10 | 20 | — (colgado) | — | — |
| una ventana | 10 | 10 | 57,7 | 4,19 |
| una ventana | 20 | 20 | 112,5 | 4,17 |
| una ventana | 40 | 24 | 136,5 | 4,18 |
El tiempo por token quedó plano: 4,17 a 4,19 ms entre 10 y 40 páginas. Esa parte de la promesa sí se ve.
El modo por ventanas, no. Con 20 páginas, el servidor terminó la primera ventana, escribió retained 640 of 16177 window tokens y en los 9 minutos siguientes no volvió a producir nada, con la petición del cliente abierta. También lo intenté con max-running-requests en 64 y pidiendo ventanas sin solapamiento (overlap_pages en 0), y se detuvo en el mismo punto; de esos dos intentos no guardé el registro, porque cada arranque del servidor sobrescribió el anterior. La fila de 10 páginas de la tabla no prueba el ventaneo: cabe en una sola ventana.
Después probé el documento en una sola ventana, la otra configuración que documenta el README. Ese cambio no está en la ficha: es exploratorio. El servidor reserva una caché fija: 42.304 tokens, 2,4 GB. Las 40 páginas del Quijote necesitan más. El servidor no dio ningún error, y eso es por diseño: el comando del README lo lanza con --allow-auto-truncate, que recorta en vez de rechazar. Devolvió las primeras 24 páginas, con 10.965 tokens de entrada y 31.295 de salida, justo lo que cabe en la caché. Mi cliente no registró el motivo de término de la respuesta, así que no sé si lo marcó; en el texto devuelto no hay nada que lo diga. Si no hubiera contado las marcas de página, no me habría enterado.
H5, que pedía 80 páginas completas, no se cumple en esta configuración. Si las ventanas hubieran funcionado, quizá sí.
Lo que estos números no dicen
- Es una tarjeta de 16 GB compartida con otros procesos (2,4 GB ocupados), no la A800 de 80 GB del README.
- El modo por ventanas de SGLang se colgó en mi instalación, en NixOS y con arreglos propios. Puede ser mi entorno.
- La degradación tipo escaneo fue suave; no probé escaneos reales con su texto conocido.
- El IPoM tiene tablas: la referencia de
pdftotexty la salida de los modelos pueden ordenar distinto las celdas, y eso cuenta como error igual para todos. - Tesseract se ejecutó en otra máquina; su tiempo no se compara con los de la tarjeta.
- Medí Marker y Chandra en local. La API de pago de Datalab, que es la que usaba antes, dice usar modelos mejores que los pesos abiertos.
- Las ejecuciones largas son una por tamaño (dos en 10 y 20 páginas, con una diferencia menor a 1,5 %).
Mi opinión
Lo bueno:
- Empató arriba con Chandra 2 en el documento real, y página por página ocupa unos 8,2 GB de la tarjeta.
- Su licencia es MIT, sin condiciones. Los pesos de Chandra 2 son gratis solo bajo 2 millones de dólares de financiamiento o ingresos y no se pueden usar para competir con la API de Datalab; los de Marker, bajo 5 millones.
- Con SGLang, 4,2 ms por token y tiempo plano a medida que crece el documento.
- La versión 2 escala claramente mejor que la 1 con transformers.
Lo que no:
- Con transformers la memoria crece con el documento: “ilimitado” no se cumple en ese camino.
- En SGLang, el modo que haría ilimitado el documento se me colgó, y el modo sin ventanas corta el documento sin dar error.
- Leer el documento entero de una vez no mejoró el error en el informe real.
Conclusión: para OCR página por página en 16 GB, Unlimited-OCR 2 y Chandra 2 son lo mejor que medí, y Unlimited-OCR 2 trae la licencia más libre. Para archivos enormes, Marker sigue siendo lo práctico. Leer cientos de páginas en una sola petición, en mi tarjeta, todavía no.
Cuándo lo usaría y cuándo no
- Lo usaría para leer documentos página por página en una GPU de 12 GB o más (ocupa unos 8,2 GB), sobre todo con tablas y texto denso; Chandra 2 lee igual de bien, pero su licencia pone condiciones.
- Seguiría con Marker para pasar PDFs enormes enteros: 74 páginas en 12,6 minutos con 4,2 GB de la tarjeta, y el texto completo aunque mueva el pie de página.
- Lo serviría con SGLang y no con transformers, en el modo de una ventana y con documentos que quepan en su caché: el mismo CER, nueve veces más rápido.
- No le pediría un documento largo en una sola petición sin contar después las marcas
<PAGE>de la salida. - Seguiría con Tesseract para texto limpio en CPU: empató en calidad y fue 15 veces más rápido por página.
- No usaría DeepSeek-OCR con estos ajustes en documentos donde importa no perder párrafos.
Fuentes
- Unlimited-OCR 2 en Hugging Face, con el README, el informe técnico en PDF y la configuración de SGLang.
- Repositorio de Unlimited-OCR, con el SGLang modificado y el código de inferencia.
- Unlimited-OCR 1 y DeepSeek-OCR, los otros dos modelos.
- Chandra y Marker de Datalab, con la licencia de los pesos de Chandra.
- Tesseract, la línea base.
- Banco Central de Chile, Informe de Política Monetaria de septiembre de 2026.
- Don Quijote en el Proyecto Gutenberg, dominio público.
- La ficha con sus enmiendas, los scripts, las salidas crudas y el análisis:
lab/unlimited-ocr/en el repositorio y en este gist.
Medido el 27 y 28 de octubre de 2026 en fedora: RTX 4070 Ti SUPER de 16 GB (driver 595.71.05), NixOS con kernel 6.18.55, con 2,4 GB de la tarjeta ocupados por otros procesos. Unlimited-OCR 2 (revisión 9cf077c), Unlimited-OCR 1 (8249d20) y DeepSeek-OCR (9f30c71) en BF16; transformers 4.52.1, 4.57.1 y 4.46.3; SGLang 0.5.20.dev10 de Baidu. Marker 2.0.0 con Surya 2 en llama.cpp y Chandra OCR 0.2.0 (datalab-to/chandra-ocr-2, método hf). Tesseract 5.5.2 en la CPU de un Mac Intel.
BOLETÍN · MEDIDO
Una medición nueva, cuando la hay.
Cada post con sus números, sus fallos y los scripts para repetirlo. Entre uno y tres correos al mes. Te das de baja con un clic.
LO ÚLTIMO QUE LLEGÓ
- Unlimited-OCR 2 en 16 GBempate con Chandra 2, sin memoria a las 80 páginas
- Wild vs mold en btrfsel orden se invierte según el disco
- 7 arneses con MiniMax-M329 de 39 con el arnés más simple
Uso Listmonk en mi propio servidor; tu correo no sale de ahí. Confirmas por correo antes de recibir nada.
Comentarios
Todavía no hay comentarios. El primero es tuyo.