Unlimited OCR 2 lit un PDF de 59 pages en une requête sur une carte de 16 GB, une fois ses tuiles de page désactivées

L'analyseur de documents sous MIT de Baidu sur un guide de 59 pages en une requête : 15,8 pages/min, 4,2 % d'écart au texte du PDF, même qualité sur 16 GB.

La page 46 du guide fiscal de l'IRS destiné aux employeurs ouvre un tableau qui court jusqu'à la page 52. Un pipeline qui lit une page à la fois le rend en sept morceaux sans lien entre eux. Unlimited OCR 2, un analyseur de documents de Baidu sous licence MIT, prend le PDF entier en une seule requête. Nous avons passé les 59 pages du guide sur une seule RTX 4090. Avec un seul réglage d'image changé, il a lu tout le guide en 3 min 44 s, à 4,2 % du texte propre au PDF. Il a lui aussi découpé le tableau, mais il a étiqueté la plupart des raccords. Une vraie carte de 16 GB a donné la même qualité. Le code utilisé est à la fin.

IRS Publication 15, page 46, le début d'un tableau de 7 pages, tel qu'Unlimited OCR 2 l'a rendu sur une RTX 4090 quand les pages 46 à 54 étaient envoyées seules. À gauche, le mode par défaut en tuiles de page : 156 s, 63 805 tokens, des cadres rouges sur huit lignes du tableau tassées dans une seule cellule et sur une phrase répétée 1 508 fois, pages 47, 49 et 52 vides. À droite, une seule vue par page (crop_mode=false) : 25 s, 9 861 tokens, un cadre vert sur des lignes à quatre colonnes comme à l'impression, 7 tableaux HTML, page 46 à 0,7 % du texte du PDF

Les pages 46 à 54 envoyées seules, même carte, deux façons de montrer chaque page au modèle. À gauche, le réglage par défaut des auteurs. À droite, un seul réglage changé : six fois plus rapide, et plus de phrase répétée.

La différence entre les deux côtés tient à la façon dont chaque page est montrée au modèle. Par défaut, les auteurs découpent chaque page en tuiles, des morceaux agrandis, en plus d'une image d'ensemble, ce qui donne au modèle plus de détail pour les petits caractères. L'autre mode, activé avec crop_mode=false, ne montre que l'image d'ensemble. Presque tous les résultats qui suivent dépendent de ce choix.

Trois chiffres de notre test d'Unlimited OCR 2 sur un PDF de 59 pages en une requête : 15,8 pages par minute sur une RTX 4090 avec une seule vue par page. 4,2 % de distance d'édition au texte propre au PDF sur les 59 pages, contre 38,1 % dans le mode en tuiles par défaut, qui a bouclé sur le tableau de 7 pages. 16 GB, la plus petite carte qui a fonctionné, une vraie RTX 2000 Ada, pic de 13,8 GB, 5,6 pages par minute

Ce que Baidu a construit : un lecteur qui ne garde que quelques pages en vue

Comme le disent les auteurs, Unlimited OCR 2 lit un long document comme une personne recopie un livre à la main. Il regarde la page qu'il est en train de recopier, pas toutes les pages qui l'ont précédée. C'est ce qui lui permet d'avaler un long document d'un coup sans que sa mémoire grossisse page après page.

C'est important à cause de la façon dont un pipeline page par page traite un PDF. Il découpe le fichier en pages, lit chacune seule et recolle le texte. Tout ce qui franchit un saut de page est coupé. Un tableau sur deux pages devient deux tableaux, et le second n'a pas d'en-tête. La chaîne AI Search, où nous avons repéré le modèle dans sa vidéo d'actualité du 1er novembre, l'a dit sans détour : les systèmes par page "lose context when the content continues onto the next page".

Lire plusieurs pages ensemble n'est pas nouveau. La première version de Baidu, Unlimited OCR, le faisait déjà, jusqu'à 40 pages. La version 2 vise les centaines. Montrer toutes les pages au modèle d'un coup devient vite coûteux. Chaque page est une image, et les images se transforment en beaucoup d'entrée. Sans astuce, chaque nouveau mot que le modèle écrit doit relire tout cela. La fiche du modèle donne l'astuce de Baidu. Le modèle ne garde en vue qu'un nombre fixe de morceaux d'image, quel que soit le nombre de pages (l'exemple de la fiche est 512). Ainsi, selon les auteurs, chaque nouveau mot coûte à peu près le même temps et la même mémoire à la page 3 qu'à la page 300.

Par-dessus, les pages passent par fenêtres. Une fenêtre de pages est le groupe de pages que le modèle garde en vue pendant qu'il écrit, et la mémoire des pages hors de la fenêtre est libérée. La recette de service en utilise 10. Des fenêtres plus petites consomment moins de mémoire, et les notes des auteurs disent qu'elles peuvent nuire à la fusion du contenu d'une page à l'autre. Le modèle lui-même est petit : 6,7 GB sur disque.

Le mérite revient à l'équipe de Baidu. Shu Wei, Jingjing Wu et Qunyi Xie signent en tête l'article comme contributeurs principaux, avec onze autres contributeurs. Ils ont construit sur leur premier Unlimited OCR et sur DeepSeek-OCR, dont le modèle réutilise des pièces. Une part du mérite va donc à l'équipe de DeepSeek-OCR. Baidu a publié les poids et le code sous MIT, qui autorise l'usage commercial. Ils ont aussi livré une version modifiée de SGLang, le framework de service ouvert, avec une recette complète, dans le dépôt du modèle. Le mérite du serveur lui-même revient au projet SGLang. Notre part, c'est le reste : emballer ce serveur derrière une API, le faire tourner sur un vrai document, vérifier la sortie et vérifier qu'une carte de 16 GB fait le travail.

Le test : un guide fiscal de 59 pages avec un tableau qui court sur sept pages

Nous voulions un document qui punit la lecture page par page. L'IRS Publication 15 (2026), le "Employer's Tax Guide" américain, fait l'affaire. Il fait 59 pages, est dans le domaine public, et a des paragraphes qui franchissent un saut de page sur la plupart des pages. La section 15 contient un tableau qui va de la page 46 à la page 52.

Nous avons fait tourner la version de SGLang des auteurs, sans modification, sur une seule RTX 4090, une carte de jeu de 24 GB. Chaque exécution envoyait le PDF entier en une requête et recevait la réponse en streaming.

Pour noter la sortie, il nous fallait une référence que personne n'avait tapée à la main. Le PDF porte sa propre couche de texte, nous avons donc comparé le texte du modèle avec elle, caractère par caractère. Le score est la part des caractères qu'il faudrait changer pour passer de l'un à l'autre : 0 % veut dire identique. Il est strict sur l'ordre de lecture, donc un encadré lu avant son paragraphe coûte des points même quand tous les mots sont justes. Pourquoi ne pas simplement extraire cette couche de texte ? Elle donne une référence gratuite et exacte pour noter, mais ni ordre de lecture, ni position des blocs, ni tableaux HTML, ni étiquettes entre pages. C'est ce que le modèle ajoute.

C'était un seul document, en anglais, lu une fois par réglage, une requête à la fois. Il n'avait que des pages numériques propres, donc les scans, où la couche de texte manque, ne sont pas testés. Notre plus gros document faisait 59 pages, pas les centaines que visent les auteurs, donc tout ce que nous disons d'un livre de 300 pages est leur affirmation, pas notre mesure. Les autres cas non testés sont listés en annexe.

Résultats : 59 pages en moins de 4 minutes, à 4,2 % du texte du PDF, tuiles désactivées

Une seule vue par page a gagné, en vitesse comme en précision.

PDF de 59 pages, une requête, RTX 4090Tuiles de page (défaut des auteurs, part de mémoire 0,7)Une vue par page (part de mémoire 0,5)
Document entier6 min 45 s, 8,7 pages/min3 min 44 s, 15,8 pages/min
Distance au texte propre au PDF38,1 % (2,4 % sans les pages 46 à 54)4,2 % (2,45 % sans les pages 46 à 54)
Pages du tableau, 46 à 52boucles sur les pages 46 et 51, pages 47, 50 et 52 videstableaux HTML, cinq pages entre 20 % et 46 % d'écart

L'exécution en vue unique a aussi utilisé une part plus petite de la mémoire de la carte. La part n'a rien changé à la vitesse en mode tuiles, mais nous ne l'avons pas vérifié dans ce mode-ci. La vitesse est restée stable de la première à la dernière page.

Sur le texte courant, les deux modes étaient bons. Sans les pages du tableau, tous deux étaient à environ 2,4 % du texte du PDF, deux ou trois caractères sur cent. Les 4,2 % du mode le plus rapide viennent surtout des pages du tableau : cinq de ses sept pages à plus de 20 % d'écart sont des pages du tableau. Les deux autres sont la couverture, pleine de liens, et une page lue dans le désordre.

Le modèle étiquette chaque paragraphe coupé par un saut de page comme continuant à la page suivante, 31 à 34 fois par exécution ici. Le tableau est revenu en sept morceaux, un par page, comme le donnerait un analyseur page par page. La différence, ce sont les étiquettes : cinq des six raccords en portent une. Le raccord de la page 49 n'en a pas. Ces étiquettes sont ce dont un programme a besoin pour recoudre le tableau en un seul.

Là où il casse : les tuiles sur un long tableau, et une seule fenêtre pour tout le document

Le mode par défaut a échoué sur le seul tableau qui comptait. Page 46, il a tassé huit lignes du tableau dans une seule cellule. Dans la cellule suivante, il a écrit une phrase, "Taxable if wages subject to social security tax and Medicare tax", 1 508 fois. Une pédale de boucle que personne n'a coupée, arrêtée seulement par le plafond de 20 000 tokens par page (les tokens sont les morceaux de mots qu'un modèle lit et écrit). Les pages 47, 50 et 52 sont revenues vides. Page 51, il a de nouveau bouclé, en écrivant "Exempt." 3 088 fois. Page 53, juste après le tableau, il a écrit 6 658 cellules de tableau vides. Il s'est rétabli à la page 55. La protection des auteurs contre la répétition était active. Nous avons renvoyé les pages 46 à 54 seules et obtenu la même boucle page 46. C'est donc le comportement du modèle sur ce tableau dans ce mode, pas un accident de mémoire.

En mode tuiles, une seule fenêtre pour tout le document ne rentrait pas. La recette de service a un interrupteur qui supprime les fenêtres et donne au modèle les 59 pages d'un coup. Avec les tuiles, les images des pages prenaient à elles seules environ 89 000 des 131 072 tokens que le modèle peut traiter à la fois. Il restait trop peu de place pour la réponse, et elle s'est arrêtée au milieu de la page 27. Le serveur ne lève pas d'erreur ici. Avec les réglages de la recette, il raccourcit la réponse sans rien dire, donc on ne s'en aperçoit qu'en comptant les pages. Sur les 26 pages qu'il a écrites, la qualité égalait celle de l'exécution par fenêtres, 3,67 % d'écart dans les deux cas.

Les auteurs citent une autre limite dans leur article. Comme le modèle ne regarde que quelques pages à la fois, il ne garde pas tout le document en tête. Cela limite sa compréhension de la structure à l'échelle du document, par exemple quel titre se trouve sous quel autre.

Quel GPU : une vraie carte de 16 GB a lu tout le guide en environ 10 minutes et demie

Une vraie carte de 16 GB a fonctionné : une RTX 2000 Ada, une petite carte de station de travail de 70 watts, avec une seule vue par page. Sa qualité a égalé celle de la 4090.

Le serveur réserve une part fixe de la carte qu'il trouve, 70 % par défaut, et la remplit de mémoire de travail. Les 19,4 GB sur la 4090 ne sont donc pas ce dont le modèle a besoin. Sur une carte plus grosse, il réserve simplement davantage. Pour émuler une carte de 16 GB, nous avons dit au serveur de réserver la moitié de la 4090. La part n'a pas changé la vitesse : les tuiles tournaient à 8,7 pages par minute à 70 % comme à 50 %.

CarteComment il a tournéPic de mémoireVitesseSortie
RTX 4090 24 GB, réelletuiles, 70 % de la carte19,4 GB8,7 pages/minboucles sur le tableau, comme chaque exécution en tuiles qui le couvrait
Limite de 16 GB sur la 4090, émuléetuiles, 50 % de la carte14,7 GB8,7 pages/minmême boucle, texte quasi identique
Limite de 16 GB sur la 4090, émuléeune vue par page, 50 % de la carte15,2 GB15,8 pages/min4,18 % d'écart
RTX 2000 Ada 16 GB, réelleune vue par page, réglages par défaut (70 %)13,8 GB5,6 pages/min4,23 % d'écart
RTX 2000 Ada 16 GB, réelletuiles, pages 1 à 10 seulement13,8 GB5,0 pages/min en aller-retouridentique à la 4090 sur ces pages

Les lignes émulées montrent que la mémoire suffit, pas la vitesse d'une petite carte.

Sur InferenceKey : la version de SGLang des auteurs derrière une API, prête en 61 à 143 secondes avec les fichiers en cache

Sur votre propre GPU, vous pouvez faire tourner exactement le serveur qu'utilisent les auteurs, sans modification, derrière une API que votre application sait déjà appeler. InferenceKey n'a pas de support prêt à l'emploi pour ce modèle, parce qu'il a besoin de la version modifiée de SGLang de Baidu plutôt que de la version standard. Nous avons donc écrit un backend sur mesure : un court fichier Python qu'InferenceKey installe et lance sur votre machine GPU. Il télécharge les poids à une version fixée et installe le serveur des auteurs depuis le dépôt du modèle. Votre application l'appelle comme elle appelle l'API de chat d'OpenAI, avec un lien vers le PDF dans le message, et reçoit le Markdown au fil de l'écriture.

Une étape de préparation avait déjà téléchargé les poids et installé le serveur. À partir de là, le premier démarrage a pris 143 secondes, surtout le temps que le serveur se prépare pour la carte. Les redémarrages ont pris 61 à 63 secondes, et la carte de 16 GB était prête en 96. Sur une machine sans rien en cache, ajoutez environ 70 secondes pour le téléchargement et l'installation. Les réglages qui jouent sur la mémoire, la fenêtre de pages et la part de la carte, sont de simples variables dans l'exemple. Les options pour un seul document, comme une plage de pages ou crop_mode=false, vont dans le texte du message.

Deux aspérités sur lesquelles vous tomberez aussi :

  • Le paquet Python d'InferenceKey publié est antérieur à ce type de backend. Installé de la façon habituelle, le backend ne démarre pas. Le contournement consiste à installer notre bibliothèque directement depuis GitHub sur la machine GPU, comme l'explique l'exemple. Statut : ouvert, en attente de la prochaine version du paquet.
  • Un PDF envoyé dans la requête n'atteint jamais la machine GPU. Le job est mis en file mais n'arrive jamais, et le client abandonne au bout de 180 secondes. Envoyez plutôt un lien vers le PDF, et le backend le télécharge. Statut : ouvert, cause pas encore trouvée.

Faut-il l'utiliser ? Oui pour des PDF comme celui-ci, sur une carte de 16 GB avec les tuiles désactivées

  • Si vous analysez de longs rapports, manuels ou contrats avec des tableaux et des paragraphes qui franchissent les pages, il vaut la peine d'être lancé. Une requête par document vous donne le texte, les positions et des étiquettes à travers les sauts de page, sous une licence qui autorise l'usage commercial.
  • Si vos documents ont de longs tableaux, mettez crop_mode=false et vérifiez les chiffres sur les pages des tableaux.
  • Si vous gardez les tuiles, gardez les fenêtres de 10 pages par défaut et comptez les pages qui reviennent.
  • Si votre carte est petite, une carte de 16 GB suffit pour un document comme le nôtre.
  • S'il vous faut des centaines de pages, le benchmark des auteurs sur 100 à 300 pages lui donne 77, contre 34 pour le modèle suivant (leur test, pas le nôtre). Nous avons testé 59 pages, alors vérifiez vous-même sur un long document.

Pour analyser le guide de l'IRS sur votre propre GPU NVIDIA, publiez le backend avec python publish.py et lancez python main.py depuis l'exemple du SDK. Vous obtenez tout le document en Markdown avec des marqueurs de page, des tableaux HTML et les temps. Il vous faut un projet InferenceKey et une carte NVIDIA de 16 GB ou plus. Le README de l'exemple liste les clés.

Pour les ingénieurs

Modèle et fichiers

Fiche du modèle, GitHub (baidu/Unlimited-OCR), article (PDF dans le dépôt du modèle), MarathonDocBench.

  • Dépôt baidu/Unlimited-OCR-2, licence MIT, sans accès restreint, sha c14071b549f4fcab10909f3c4acc6a2edb366947, lastModified 2026-10-29T09:18:46Z. createdAt HF 2026-10-26T03:57:46Z ; la note de version de la fiche indique 2026/10/27.
  • Métadonnées safetensors : 3 343 978 240 paramètres BF16. Article : décodeur MoE, 3B au total et 500M activés par token (une figure l'étiquette "3B-MOE-A570M", le tableau 3 "3B-A0.5B").
  • Deux shards, 4 998 430 312 + 1 689 875 264 octets (environ 6,7 GB). Le dépôt fournit aussi la wheel SGLang des auteurs wheel/sglang-0.5.20.dev13+g75b9b06b02-py3-none-any.whl (15,3 MB) et un HarryPotter.gif de 156 MB qui rejoue un livre complet analysé en une requête. Stockage total du dépôt 7,12 GB. Nous n'avons vérifié aucune version FP8 ou GGUF.
  • Dépendances de la wheel : torch 2.13.0 (cu130), flashinfer 0.6.18 cu13, sglang-kernel 0.4.7, transformers 5.12.1, donc un driver CUDA 13. Backend d'attention unlimited_ocr_v2 (Triton, PDL seulement sur sm90+, donc Ada fonctionne).
  • Chemins d'inférence de la fiche : Transformers (infer_multi, testé avec Python 3.10, torch 2.13.0, transformers 4.52.1 sur une A800-80GB) et SGLang (recommandé pour le service) dans un venv Python 3.10 construit avec uv, avec pymupdf==1.27.2.2 et fastokens==0.3.2.
  • Le infer.py de GitHub utilise par défaut le mode gundam (tuiles) et repasse en mode base pour les documents de plusieurs pages dont le budget de tokens est trop court. Il utilise max_model_len 131072 pour MPDocBench et 786432 pour MarathonDocBench.

Mécanisme (article, sections 3.3 et 3.4)

Fiche du modèle : SR-SWA "compresses text-visual attention interactions from ultra-long sequences down to a fixed-size set of working references (e.g., 512 tokens)", ce qui garde la latence et la mémoire de décodage par pas presque constantes quelle que soit la longueur du document.

SR-SWA (Sparse Reference Sliding Window Attention) = Page Gate (restreint l'attention visuelle aux blocs des pages cibles, avec une coordonnée de page bornée alternant 0-1 plus une coordonnée intra-page) + Token Sieve (routeur alpha-entmax qui garde les top-k blocs, entraîné de bout en bout, sans perte de distillation), plus une fenêtre locale fixe. L'Unlimited-Horizon Decoding Paradigm découpe le document en blocs de pages qui se chevauchent et partagent une page frontière dont le KV est réutilisé, et libère le KV visuel hors du bloc actif. Entraînement : 25 x 8 nœuds H800, batch global de 1 600, séquence max de 64K, initialisé depuis Unlimited OCR v1, DeepEncoder gelé, MS-Swift. Section 3.2 : le modèle hérite de l'ossature d'Unlimited OCR (v1, qui a poussé l'analyse multipage à quelques dizaines de pages avec une limite d'entrée de 40 pages) et repose sur DeepSeek-OCR (DeepEncoder plus un décodeur MoE).

Liste des auteurs (article, section 8) : contributeurs principaux Shu Wei, Jingjing Wu, Qunyi Xie ; contributeurs Lingshu Zhang, Xiangcheng Du, Yannian Fu, Mengjie Wang, Zuyi Wang, Zhichao Liu, Yangliu Xu, Youyang Yin, Huanhuan Liu, YY, Yi Yang (Baidu ERNIE Team).

Affirmations des auteurs (non reproduites par nous)

  • Accélérations par pas de 7,0x et 5,6x face à Full Attention et R-SWA à l'échelle de la centaine de pages (figure de latence en mode Gundam, batch 3, 1 024 tokens par page).
  • Tableau 4 (MarathonDocBench) : pic de mémoire KV à B=1 de 47,00 GB (standard) contre 1,77 GB (unlimited-horizon) ; débit à B=16 de 360 contre 1 315 TPS.
  • MarathonDocBench, le benchmark propre aux auteurs publié avec le modèle, score global (100 documents de 100 à 300 pages, environ 18k pages, chinois et anglais mêlés) : Unlimited OCR 2 77,06, Kimi-K3 33,74, Gemini-3.8-Flash 28,61, Qwen-3.8-Flash-Next 27,8, Gemini-3.1-Pro Preview 26,97, Qwen-3.8-Max 22,93, Minimax-M3 17,97, DeepSeek-4.1-Flash 12,34, GLM-5.3-Flash 7,51. Annotations sous Apache-2.0, images réservées à la recherche.
  • MPDocBench (jusqu'à 30 pages), score global : Unlimited-OCR 2 84,59, Qwen3.8-Max 78,30, Qwen-3.8-Flash-Next 77,36, Gemini-3.8-Flash 76,25, Gemini-3.1-Pro Preview 71,79, Unlimited-OCR v1 61,89. La v1 n'est pas sur MarathonDocBench à cause de sa limite d'entrée de 40 pages.
  • Limite (article, section 6) : l'attention clairsemée ne préserve pas l'information globale du document, ce qui limite la structure au niveau du document, comme la hiérarchie des titres. La diversité des données d'entraînement est limitée.

Installation

  • Une RTX 4090 24 GB sur notre cloud (driver 595.91.07, CUDA 13.2, 450 W, disque de conteneur de 100 GB) pour les exécutions principales, et une RTX 2000 Ada Generation 16 GB (70 W, sm 8.9, même driver) pour l'échelle. Les deux enregistrées comme workers auprès d'un manager InferenceKey local. La production n'a jamais été touchée.
  • Backend sur mesure unlimited-ocr-2 (version finale 0.1.2) : setup() télécharge la révision fixée, construit un environnement Python 3.10 avec uv et la wheel des auteurs (environ 60 s ; le repli sur Transformers n'a pas été nécessaire), extrait le DeepseekOCRNoRepeatNGramLogitProcessor des auteurs et lance sglang.launch_server avec les flags et l'env de la fiche. chat_stream() prend un lien vers un PDF dans le texte (ou une partie file OpenAI, ou des images), rend les pages avec PyMuPDF à 300 dpi, construit "<PAGE><image>" * n + "Multi page parsing." et envoie une seule requête en streaming.
  • Paramètres de requête (de la fiche) : température 0, max_tokens = 8000 x pages, garde n-gram ngram_size 35, window_size 1024 (multipage), page_token_id 128827, eos_token_id 1, max_page_tokens 20000.
  • Flags du serveur : --mem-fraction-static 0.7, --max-running-requests 8 (fiche : 64), --max-prefill-tokens 2048, --enable-custom-logit-processor, --allow-auto-truncate, --disable-radix-cache, buckets de CUDA graphs de prefill dimensionnés sur max_prefill_tokens. Env : SGLANG_UOCR_EANABLE_DOC_LIMIT=1 (orthographe des auteurs ; 0 = une seule fenêtre pour tout le document), SGLANG_UOCR_DOC_GUNDAM_PAGE_WINDOW=10, SGLANG_UOCR_DOC_BASE_PAGE_WINDOW=10, SGLANG_UOCR_MAX_PAGE_TOKENS=20000, SGLANG_OPT_UOCR_SAM_REL_POS_BIAS_MB=64 (la valeur par défaut de 512 "allows a single ~462 MiB allocation and can OOM"), SGLANG_UOCR_SHM_WRAP_MODEL_DATA=1, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True.
  • Notes de la fiche sur la mémoire : 0,75 laisse trop peu de marge à 32 requêtes simultanées ou plus, 0,7 est stable ; --max-prefill-tokens pilote un tenseur temporaire d'environ [tokens, context/32], donc passer de 4096 à 2048 divise ce pic par deux ; le plus grand bucket de CUDA graph de prefill doit être égal à max-prefill-tokens.
  • Vérité de référence : la couche de texte du PDF (PyMuPDF). NED = Levenshtein / longueur max, après passage en minuscules et suppression du balisage, des étiquettes de mise en page et des espaces.

Temps jusqu'à prêt

PhaseTemps
Premier démarrage sur la plateforme, RTX 4090 (poids et env en cache grâce à une étape de préparation)143 s, dont 105,1 s de démarrage de SGLang, capture des CUDA graphs comprise (graphs de prefill 23,5 s)
Téléchargement des poids / env uv avec la wheel10 s / 60 s (8 s / 51 s sur la RTX 2000 Ada)
Redémarrages après redéploiement, RTX 409061 à 63 s
RTX 2000 Ada96 s (scheduler 48,4 s, CUDA graphs prefill 18,8 s + decode 16,6 s)
Pool KV160 640 tokens à la part 0,7 et 83 840 à 0,5 sur la 4090 ; 72 096 sur la RTX 2000 Ada
Rendu de 59 pages à 300 dpi16,5 à 17,0 s sur le CPU de la machine GPU

Exécutions sur la RTX 4090

Backend = la ligne done: du backend lui-même. Client = aller-retour à travers la plateforme. Les temps jusqu'au premier token sont côté backend ; à travers la plateforme, ils étaient de 27,6 s (tuiles), 21,2 s (vue unique) et 71,2 s (fenêtre unique).

ExécutionModePagesBackend sPremier texte sPages/min backend / clientTokens prompt / sortiePic MiBRésultat
w10-fulltuiles, fenêtre 10, part 0,759387,8710,539,1 / 8,7 (404,91 s)97 621 / 160 25419 386boucle sur 46, de nouveau sur 51 ('Exempt.' x3 088) et 53 (6 658 cellules vides) ; 47, 50, 52, 54 vides, 48 un fragment de 412 caractères ; NED 38,06 %, 2,38 % sans 46-54
base-w10-fullvue unique, fenêtre 10, part 0,559206,013,3417,2 / 15,8 (223,83 s)18 277 / 89 47715 212NED 4,18 %, 2,45 % sans 46-54 ; pages au-dessus de 20 % : 1 (30,0 %), 8 (77,6 %), 46 (45,2 %), 47 (23,0 %), 48 (26,9 %), 49 (45,5 %), 51 (20,4 %) ; 16 tableaux, 34 cross_text, 5 append_table
whole-fulltuiles, fenêtre unique59 demandées, 27 écrites147,4650,28(168,37 s client)89 331 / 41 73918 058tronqué au milieu de la page 27
cap16-w10tuiles, fenêtre 10, part 0,559386,569,869,2 / 8,7 (407,62 s)97 621 / 160 24214 670identique à w10-full
table-gundamtuiles46-54156,148,273,5 / 3,413 631 / 63 80514 670même boucle sur 46, 47/49/52 vides
table-basevue unique46-5425,042,8421,6 / 18,72 471 / 9 86115 1507 tableaux, NED du document 11,0 %, page 46 0,68 %, pages 48/49/51 26-37 % ; 5 append_table
  • Décodage à environ 430 à 435 tok/s en mode tuiles et environ 445 tok/s en mode base à batch 1, stable sur tout le document. La libération des fenêtres se voit dans le log de SGLang (#token de 29 248 à 19 936 entre deux fenêtres).
  • Document entier contre fenêtres sur les pages 1-26 (mode tuiles) : NED de 3,67 % dans les deux cas. 4 pages sur 26 identiques à l'octet près, les autres diffèrent par les guillemets et de petites tournures.
  • Fenêtre unique, message exact du serveur : Requested token count (89331 input + 472000 new) exceeds the model's context length (131072 tokens). Truncating max_new_tokens. La réponse s'arrête sans erreur à cause de --allow-auto-truncate.
  • Les étiquettes append_table ont été comptées, pas suivies : le tableau revient en 7 morceaux sur les pages 46-52, et seuls 5 des 6 raccords sont étiquetés (pages 47, 48, 50, 51, 52 ; le tableau de la page 49 n'en a pas, dans les deux exécutions base). Nous n'avons pas recousu les morceaux en un seul tableau ni vérifié qu'ils se raccordent correctement.
  • Format de sortie : <PAGE> par page, <BLOCK>type[x1, y1, x2, y2]<FORK>text par bloc sur une grille de 1000 x 1000, tableaux en HTML. Liens entre pages : cross_text / append_text (31 à 34 sur 59 pages), append_table sur les suites de tableau.

Page 46, une vue par page (premières lignes) :

<BLOCK>title<LEVEL1>[66, 41, 895, 66]<FORK>15. Special Rules for Various Types of Services and Payments
<BLOCK>table[64, 104, 936, 884]<FORK><table><tr><td rowspan="2">Special classes of employment and special types of payments</td><td colspan="3">Treatment under employment taxes</td></tr>...<tr><td>1. Service on farm in connection with cultivating soil; ...</td><td>Taxable if wages subject to social security tax and Medicare tax.</td><td>Taxable if $150 test or $2,500 test in section 9 is met.</td><td>Taxable if either test in section 14 is met.</td></tr>

La même page en mode tuiles :

<td>Agricultural labor:1. Service on farm ... 8. Handling or processing commodities ...</td><td>Taxable if wages subject to social security tax and Medicare tax.Taxable if wages subject to social security tax and Medicare tax. ...  (x1,508)

Échelle de VRAM

GPU ou limiteConfigPic de VRAMTemps pour le PDF de 59 pagesSortie correcteRéel ou émulé
RTX 4090 24 GBtuiles, part 0,7, fenêtre 1019 386 MiB387,9 s backend, 8,7 pages/minoui (la boucle sur le tableau est un comportement du modèle, identique dans chaque exécution en tuiles qui couvrait les pages 46-54)réel
RTX 4090 24 GBtuiles, fenêtre unique18 058 MiB147,5 s, arrêt à la page 27non (limite de contexte)réel
Limite de 16 GB sur la 4090tuiles, part 0,5, fenêtre 1014 670 MiB386,6 s, 8,7 pages/minoui, même boucle ; 13 pages identiques à l'octet près, le reste avec de minuscules différencesémulé
Limite de 16 GB sur la 4090vue unique, part 0,5, fenêtre 1015 212 MiB206,0 s, 15,8 pages/minoui, 4,18 %émulé
RTX 2000 Ada 16 GB (70 W)vue unique, réglages par défaut (part 0,7, fenêtre 10)13 819 MiB614,7 s backend, 634,1 s aller-retour, 5,6 pages/minoui, 4,23 %réel
RTX 2000 Ada 16 GBtuiles, réglages par défaut, pages 1-1013 819 MiB116,7 s backend (5,1 pages/min), 120,5 s client (5,0)oui, identique à la 4090réel

Environ 147 tok/s sur la RTX 2000 Ada contre environ 440 sur la 4090. Pas essayé : 12 GB ou moins, les tuiles sur les 59 pages sur une vraie carte de 16 GB, une concurrence supérieure à 1, un analyseur page par page sur le même guide, le recousage des morceaux du tableau, les niveaux de titres, le chemin Transformers, les versions FP8 ou GGUF, un document de 100 pages, une seule fenêtre pour tout le document avec crop_mode=false (son prompt de 59 pages faisait 18 277 tokens contre 89 331 avec les tuiles, il pourrait donc tenir dans le contexte de 131 072).

Pas à pas avec l'exemple du SDK

Exemple sur GitHub, commit 944a21f sur main d'inferencekey-sdk. Fichiers : backend.py, publish.py (api="openai-chat"), main.py, requirements.txt, .env.example, README.md.

cp .env.example .env    # INFERENCEKEY_SDK_TOKEN, INFERENCEKEY_API_KEY, INFERENCEKEY_PROJECT, IK_WORKER_ID,
                        # plus INFERENCEKEY_TENANT_ID and INFERENCEKEY_USER_TOKEN (dashboard session token) for the upload
pip install -r requirements.txt
python publish.py       # once: builds dist/*.tar.gz and uploads it (only if TENANT_ID and USER_TOKEN are set)
IK_PDF=https://www.irs.gov/pub/irs-pdf/p15.pdf python main.py
  • Sans INFERENCEKEY_TENANT_ID et INFERENCEKEY_USER_TOKEN, publish.py se contente de construire dist/*.tar.gz. Téléversez-le à la main depuis Dashboard -> Custom backends.

  • Sur le worker, faites pointer INFERENCEKEY_SDK_PYTHON_DIR vers un checkout de bindings/python/python depuis main d'inferencekey-sdk avant de déployer. Le paquet PyPI (0.260725.739) fait échouer le backend à l'import (voir Problèmes, 1).

  • IK_WORKER_ID doit être un worker NVIDIA avec 16 GB ou plus et un driver CUDA 13. Le placement est privé avec une politique d'exécution FIXED. Nous avons utilisé un disque de conteneur de 100 GB.

  • main.py s'assure que le workload existe, attend qu'il soit prêt, analyse IK_PDF, enregistre outputs/<name>.md et outputs/<name>.json (pages, secondes, pages par minute, temps jusqu'au premier texte) et supprime le workload sauf si IK_KEEP_ALIVE=1.

  • main.py utilise IK_CROP_MODE=false par défaut depuis ce test. Le backend lui-même garde le réglage par défaut des auteurs (crop_mode=true). IK_PAGES=1-20 en option.

  • Réglages du backend : UOCR_PAGE_WINDOW (10 ; 0 = une seule fenêtre), UOCR_MEM_FRACTION_STATIC (0.7), UOCR_MAX_RUNNING_REQUESTS (8), max_prefill_tokens 2048, max_page_tokens 20000, dpi 300.

  • Endpoint : {base}/endpoint/{project}/{workload}/v1/chat/completions, "stream": true. Options dans le texte du message : "Parse this document: https://example.org/report.pdf pages 10-30 crop_mode=false dpi=200".

Problèmes

  1. SDK PyPI trop ancien pour les backends de chat sur mesure. Installé avec pip install inferencekey, le backend échoue à l'import. inferencekey 0.260725.739 sur PyPI n'a pas de chat() / chat_stream() pour les backends sur mesure : cannot import name 'chat_completion' from 'inferencekey.backend'. Contournement : le SDK depuis GitHub dans le chemin du worker (INFERENCEKEY_SDK_PYTHON_DIR). Ouvert, il faut une version PyPI.
  2. Le PDF en ligne n'a pas atteint le worker. Un PDF de 1,7 MB envoyé dans la requête (2,2 MB de JSON) a atteint la file des jobs mais jamais le worker, et le client s'est arrêté au délai de 180 s pour le premier token. La route accepte 8 MiB. Contournement : envoyer un lien, le backend le télécharge. Ouvert, cause non isolée.
  3. Champs de chat personnalisés de premier niveau ignorés. Le worker ne transmet au backend sur mesure que les champs de chat standard (build_vllm_chat_stream_body dans worker/src/jobs.rs). Contournement : les options dans le texte du message. Ouvert.
  4. Manifeste sans api="openai-chat". Le premier publish.py l'omettait et les requêtes en streaming étaient refusées. Corrigé dans l'exemple (0.1.1).
  5. SSE décodé en Latin-1 par requests (pas de charset sur text/event-stream). Corrigé dans main.py avec resp.encoding = "utf-8".
  6. Le mode tuiles boucle sur le tableau de 7 pages (pages 46-52) avec la garde n-gram des auteurs active. Comportement du modèle, rapporté tel quel.
  7. Une seule fenêtre pour tout le document tronque sans rien dire à cette longueur (voir plus haut).
  8. La pastille Access du panneau affichait "No access" pour le workload alors que les appels fonctionnaient. Cosmétique.

Vidéo de démo

Test de déploiement d'Unlimited OCR 2 : le terminal termine les pages 46 à 54, avec le bandeau '9 pages in 28 s'

3 minutes 10 secondes. Le testeur du panneau analyse les pages 7 et 8 (premier token à 3 268 ms, total 10,02 s), puis le terminal analyse les pages 46 à 54 avec une vue par page (9 pages en 28 s, 19,3 pages/min). La vidéo montre aussi l'animation des auteurs qui rejoue un livre entier analysé en une requête et leurs chiffres de benchmark, présentés comme les leurs.

Sources

  • Fiche du modèle : https://huggingface.co/baidu/Unlimited-OCR-2
  • Code : https://github.com/baidu/Unlimited-OCR
  • Article : https://huggingface.co/baidu/Unlimited-OCR-2/blob/main/Unlimited-OCR-2.pdf
  • MarathonDocBench : https://huggingface.co/datasets/baidu/MarathonDocBench
  • Document de test : https://www.irs.gov/pub/irs-pdf/p15.pdf
  • Où nous l'avons repéré : AI Search, 1er novembre 2026, chapitre "Unlimited OCR 2" à 05:48, https://www.youtube.com/watch?v=IGICYFyP_EI
  • Notre exemple du SDK : https://github.com/inferencekey/inferencekey-sdk/tree/main/examples/unlimited-ocr-2-pdf-parsing-custom-backend-private-nvidia