pixr 0.1.0.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. pixr-0.1.0.dev0/LICENSE +201 -0
  2. pixr-0.1.0.dev0/PKG-INFO +349 -0
  3. pixr-0.1.0.dev0/README.md +311 -0
  4. pixr-0.1.0.dev0/pyproject.toml +73 -0
  5. pixr-0.1.0.dev0/src/pixr/__init__.py +3 -0
  6. pixr-0.1.0.dev0/src/pixr/app/__init__.py +0 -0
  7. pixr-0.1.0.dev0/src/pixr/app/eval.py +95 -0
  8. pixr-0.1.0.dev0/src/pixr/app/train.py +192 -0
  9. pixr-0.1.0.dev0/src/pixr/data/__init__.py +2 -0
  10. pixr-0.1.0.dev0/src/pixr/data/loader.py +26 -0
  11. pixr-0.1.0.dev0/src/pixr/data/transforms.py +52 -0
  12. pixr-0.1.0.dev0/src/pixr/framework/__init__.py +7 -0
  13. pixr-0.1.0.dev0/src/pixr/framework/config.py +79 -0
  14. pixr-0.1.0.dev0/src/pixr/framework/device.py +176 -0
  15. pixr-0.1.0.dev0/src/pixr/framework/ema.py +85 -0
  16. pixr-0.1.0.dev0/src/pixr/framework/logger.py +32 -0
  17. pixr-0.1.0.dev0/src/pixr/framework/monitor.py +276 -0
  18. pixr-0.1.0.dev0/src/pixr/framework/random.py +15 -0
  19. pixr-0.1.0.dev0/src/pixr/framework/task.py +369 -0
  20. pixr-0.1.0.dev0/src/pixr/framework/trainer.py +397 -0
  21. pixr-0.1.0.dev0/src/pixr/generation.py +242 -0
  22. pixr-0.1.0.dev0/src/pixr/loss.py +128 -0
  23. pixr-0.1.0.dev0/src/pixr/models/__init__.py +11 -0
  24. pixr-0.1.0.dev0/src/pixr/models/attention.py +178 -0
  25. pixr-0.1.0.dev0/src/pixr/models/config.py +239 -0
  26. pixr-0.1.0.dev0/src/pixr/models/configs/cronut_b16_tiktoken.json +34 -0
  27. pixr-0.1.0.dev0/src/pixr/models/configs/cronut_s16_tiktoken.json +34 -0
  28. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_hybrid_r50_base_1280.json +17 -0
  29. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_clip_base.json +30 -0
  30. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_clip_base_8k.json +30 -0
  31. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_clip_base_ettin150m.json +31 -0
  32. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_clip_large.json +30 -0
  33. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_clip_large_ettin400m.json +31 -0
  34. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_siglip2_base.json +14 -0
  35. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_siglip2_base_ettin150m.json +15 -0
  36. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_siglip2_base_qwen3_0.6b.json +16 -0
  37. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_naflex_siglip2_so400m.json +14 -0
  38. pixr-0.1.0.dev0/src/pixr/models/configs/cruller_vit_clip_base_1280.json +20 -0
  39. pixr-0.1.0.dev0/src/pixr/models/configs/fritter_naflex_siglip2_base_ettin150m.json +18 -0
  40. pixr-0.1.0.dev0/src/pixr/models/configs/fritter_naflex_siglip2_base_ettin400m.json +18 -0
  41. pixr-0.1.0.dev0/src/pixr/models/configs/fritter_naflex_siglip2_base_qwen3_0.6b.json +18 -0
  42. pixr-0.1.0.dev0/src/pixr/models/configs/fritter_naflex_siglip2_so400m_ettin1b.json +18 -0
  43. pixr-0.1.0.dev0/src/pixr/models/cronut.py +389 -0
  44. pixr-0.1.0.dev0/src/pixr/models/cruller.py +124 -0
  45. pixr-0.1.0.dev0/src/pixr/models/factory.py +79 -0
  46. pixr-0.1.0.dev0/src/pixr/models/fritter.py +198 -0
  47. pixr-0.1.0.dev0/src/pixr/models/image_encoder_timm.py +181 -0
  48. pixr-0.1.0.dev0/src/pixr/models/kv_cache.py +112 -0
  49. pixr-0.1.0.dev0/src/pixr/models/param_groups.py +76 -0
  50. pixr-0.1.0.dev0/src/pixr/models/text_decoder.py +433 -0
  51. pixr-0.1.0.dev0/src/pixr/models/text_decoder_ettin.py +436 -0
  52. pixr-0.1.0.dev0/src/pixr/models/text_decoder_qwen3.py +352 -0
  53. pixr-0.1.0.dev0/src/pixr/task/__init__.py +4 -0
  54. pixr-0.1.0.dev0/src/pixr/task/common.py +212 -0
  55. pixr-0.1.0.dev0/src/pixr/task/docvqa.py +94 -0
  56. pixr-0.1.0.dev0/src/pixr/task/read.py +140 -0
  57. pixr-0.1.0.dev0/src/pixr/task/task_registry.py +19 -0
  58. pixr-0.1.0.dev0/src/pixr/tokenizers/__init__.py +3 -0
  59. pixr-0.1.0.dev0/src/pixr/tokenizers/config.py +8 -0
  60. pixr-0.1.0.dev0/src/pixr/tokenizers/tiktoken_tokenizer.py +152 -0
  61. pixr-0.1.0.dev0/src/pixr/tokenizers/tokenizer.py +45 -0
  62. pixr-0.1.0.dev0/src/pixr/utils/__init__.py +3 -0
  63. pixr-0.1.0.dev0/src/pixr/utils/cli.py +110 -0
  64. pixr-0.1.0.dev0/src/pixr/utils/file_utils.py +36 -0
  65. pixr-0.1.0.dev0/src/pixr/utils/json_utils.py +317 -0
  66. pixr-0.1.0.dev0/src/pixr/utils/metrics.py +65 -0
  67. pixr-0.1.0.dev0/src/pixr/utils/name_utils.py +11 -0
  68. pixr-0.1.0.dev0/src/pixr/version.py +1 -0
  69. pixr-0.1.0.dev0/tests/test_app_cfg.py +44 -0
  70. pixr-0.1.0.dev0/tests/test_distributed.py +166 -0
  71. pixr-0.1.0.dev0/tests/test_loss.py +74 -0
  72. pixr-0.1.0.dev0/tests/test_metrics.py +13 -0
  73. pixr-0.1.0.dev0/tests/test_models.py +725 -0
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2024 Hugging Face Inc.
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,349 @@
1
+ Metadata-Version: 2.4
2
+ Name: pixr
3
+ Version: 0.1.0.dev0
4
+ Summary: Pixel Reading: OCR-free document understanding models (image encoder + text decoder), training and evaluation
5
+ Keywords: document-ai,ocr-free,document-understanding,vision-language,docvqa,pytorch
6
+ Author-Email: Ross Wightman <rwightman@gmail.com>
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Education
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Project-URL: homepage, https://github.com/rwightman/pixr
19
+ Project-URL: repository, https://github.com/rwightman/pixr
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: torch>=2.8
22
+ Requires-Dist: timm>=1.0.31
23
+ Requires-Dist: transformers
24
+ Requires-Dist: huggingface_hub
25
+ Requires-Dist: safetensors
26
+ Requires-Dist: fsspec
27
+ Requires-Dist: numpy
28
+ Requires-Dist: chugr>=0.2.0.dev0
29
+ Requires-Dist: Levenshtein
30
+ Requires-Dist: pyyaml
31
+ Requires-Dist: jsonargparse
32
+ Requires-Dist: tiktoken
33
+ Provides-Extra: wandb
34
+ Requires-Dist: wandb; extra == "wandb"
35
+ Provides-Extra: tensorboard
36
+ Requires-Dist: tensorboard; extra == "tensorboard"
37
+ Description-Content-Type: text/markdown
38
+
39
+ # Pixel Reading (`pixr`)
40
+
41
+ OCR-free, end-to-end document understanding models trained on open data: pixels in, text out. `pixr` covers the
42
+ models (image encoder + text decoder families), the training tasks (page reading pretraining, DocVQA fine-tuning),
43
+ generation (KV cached greedy / beam search) and a lightweight PyTorch training framework (DDP / FSDP2, compile, EMA).
44
+ Data pipelines are provided by [`chugr`](https://github.com/rwightman/chugr).
45
+
46
+ `pixr` is the continuation of the original `pixparse` project (huggingface/pixparse, 2023), renamed and revived.
47
+
48
+ ## Status
49
+
50
+ Alpha, expect breaking changes. The current focus is reproducing decent OCR / DocVQA results w/ open data and modern
51
+ (NaFlex, native aspect ratio) image encoders.
52
+
53
+ ### Changes since `pixparse`
54
+
55
+ * Ported to `chugr` 0.2, NaFlexVit (native aspect / variable resolution) image encoders, and self-contained text
56
+ decoders (BART, Ettin, Qwen3) w/ three model families (see below)
57
+ * FSDP2, torch.compile, EMA, NaFlex batching, chunked LM loss, KV cached greedy / beam search generation
58
+ * Fixes for issues that hampered the original DocVQA results:
59
+ * DocVQA fine-tune loss was applied to question + answer tokens (prompt masking used the task start token)
60
+ * train / eval image preprocessing mismatched (aspect preserving resize + pad vs stretch, long axis rotation)
61
+ * DocVQA eval decoded by re-tokenizing the generated string each step (BPE round trip is not idempotent)
62
+ * `scale_embedding` was forced on for BART decoders (copied from Donut's mBART, BART is trained w/o)
63
+ * `ModelArgs` command line overrides (image size, etc) were silently dropped
64
+ * pretrained BART cross-attention weights significantly slow learning w/ an image encoder, cross-attention is now
65
+ re-initialized w/ zero init output projections by default (decoder starts as the pretrained LM)
66
+
67
+ ## Installation
68
+
69
+ ```bash
70
+ pip install --pre pixr # pulls in chugr
71
+ pip install --pre "pixr[wandb]" # optional logging extras: wandb, tensorboard
72
+ ```
73
+
74
+ NOTE the NaFlexVit attention backends (`attn_backend`) and pretrained patch embedding channel adaptation used by the
75
+ NaFlex models need `timm` changes that are not in a release yet, until they are, install `timm` from source w/ them.
76
+
77
+ From a source checkout (the training / eval scripts and the test launcher live in the repository):
78
+
79
+ ```bash
80
+ pip install -e ./chugr -e ./pixr
81
+ ```
82
+
83
+ ## Models
84
+
85
+ Three model families share the same tasks, data pipelines, loss masking and (greedy / beam search, KV cached) generation:
86
+
87
+ * **Cruller** -- image encoder + autoregressive text decoder w/ cross-attention (Donut style)
88
+ * **Fritter** -- image encoder -> (optional m x m token merge) -> projector -> image token prefix for a decoder-only
89
+ LM w/ prefix-LM attention (PaLI / LLaVA style)
90
+ * **Cronut** -- native decoder-only transformer over image patches and text (GenLIP style single trunk w/ 2D/1D
91
+ interleaved MRoPE, qk-norm, gated attention, SwiGLU), trained from scratch w/ a tiktoken tokenizer
92
+
93
+ Model configs live in `src/pixr/models/configs`:
94
+
95
+ | config | image encoder | text decoder |
96
+ |---|---|---|
97
+ | `cruller_naflex_clip_base` | NaFlexVit B/16, CLIP style (pre-norm, cls token), OpenAI CLIP init, 4096 patches | BART base (6L) |
98
+ | `cruller_naflex_clip_base_8k` | as above, 8192 patches | BART base (6L) |
99
+ | `cruller_naflex_clip_base_ettin150m` | as above, 4096 patches | Ettin (ModernBERT) decoder 150M + cross-attn |
100
+ | `cruller_naflex_siglip2_base` | NaFlexVit B/16 SigLIP2 | BART base (6L) |
101
+ | `cruller_naflex_clip_large` | NaFlexVit L/14 CLIP style, OpenAI CLIP init, 6144 patches | BART large (8L) |
102
+ | `cruller_naflex_clip_large_ettin400m` | as above | Ettin decoder 400M + cross-attn |
103
+ | `cruller_naflex_siglip2_base_ettin150m` | NaFlexVit B/16 SigLIP2 | Ettin decoder 150M + cross-attn |
104
+ | `cruller_naflex_siglip2_so400m` | NaFlexVit SO400M/16 SigLIP2 | BART large (8L) |
105
+ | `fritter_naflex_siglip2_base_ettin150m` | NaFlexVit B/16 SigLIP2, 2x2 token merge | Ettin decoder 150M (prefix-LM) |
106
+ | `fritter_naflex_siglip2_base_ettin400m` | as above | Ettin decoder 400M (prefix-LM) |
107
+ | `fritter_naflex_siglip2_so400m_ettin1b` | NaFlexVit SO400M/16 SigLIP2, 2x2 token merge | Ettin decoder 1B (prefix-LM) |
108
+ | `cruller_naflex_siglip2_base_qwen3_0.6b` | NaFlexVit B/16 SigLIP2 | Qwen3 0.6B + zero init cross-attn every N layers |
109
+ | `fritter_naflex_siglip2_base_qwen3_0.6b` | NaFlexVit B/16 SigLIP2, 2x2 token merge | Qwen3 0.6B (prefix-LM) |
110
+ | `cronut_s16_tiktoken` / `cronut_b16_tiktoken` | -- (patch embed in trunk) | -- (single trunk), `cl100k_base` |
111
+ | `cruller_vit_clip_base_1280` | fixed size ViT B/16 CLIP @ 1280x960 | BART base (6L) |
112
+ | `cruller_hybrid_r50_base_1280` | fixed size hybrid R50 + ViT B/16 @ 1280x960 | BART base (6L) |
113
+
114
+ NaFlex models consume pages at their native aspect ratio, resized to fit a patch budget (`--task.model.max-seq-len`),
115
+ the budget can be changed between pretraining and fine-tuning.
116
+
117
+ Ettin (ModernBERT) decoders use 64 token sliding window attention in 2 of every 3 layers, a poor fit for a long
118
+ image prefix (Fritter), Qwen3 (full attention) is the recommended prefix-LM decoder.
119
+
120
+ ### Attention backends
121
+
122
+ Padded NaFlex sequences can use a dense mask or a variable length kernel, selected per model config field:
123
+
124
+ | field | options |
125
+ |---|---|
126
+ | `image_encoder.attn_backend` | `sdpa` (dense mask), `varlen` (packed, CUDA), `flex` (FlexAttention block mask), batches w/o padding skip masking for all -- requires the timm `naflex-attn-backends` branch |
127
+ | `text_decoder.cross_attn_backend` | `sdpa`, `varlen`, `flex` |
128
+ | `text_decoder.self_attn_backend` | `sdpa` (dense prefix-LM mask), `flex` (prefix-LM block mask), Ettin / Qwen3 |
129
+ | `cronut.attn_backend` | `sdpa`, `flex` |
130
+
131
+ Override from the command line w/ `--task.model.image-attn-backend`, `--task.model.cross-attn-backend`, and
132
+ `--task.model.self-attn-backend` (decoder self-attention, or the Cronut trunk), e.g. `--task.model.image-attn-backend flex`.
133
+
134
+ ### Tokenizers
135
+
136
+ HF tokenizers by name (`facebook/bart-base`, `Qwen/Qwen3-0.6B`, ...) or tiktoken encodings via `tiktoken:<encoding>`
137
+ (`tiktoken:cl100k_base`, `tiktoken:o200k_base`, ...), w/ eos / pad / bos and task tokens appended above the base vocab.
138
+
139
+ ## Usage
140
+
141
+ ### Training / evaluation apps
142
+
143
+ The apps are installed as `pixr-train` / `pixr-eval` (or `python -m pixr.app.train` / `python -m pixr.app.eval`).
144
+ Distributed runs use `torchrun` (or `python -m torch.distributed.run`):
145
+
146
+ ```bash
147
+ torchrun --nproc_per_node 2 -m pixr.app.train --task read --task.model.name cruller_naflex_siglip2_base \
148
+ --train-data.source "<idl-wds shards>" --train-data.num-samples 200000 --trainer.num-intervals 40 ...
149
+ pixr-eval --task docvqa --checkpoint-path <checkpoint.pt> \
150
+ --eval-data.source pixparse/docvqa-single-page-questions --eval-data.split validation --eval-data.format hfds
151
+ ```
152
+
153
+ Scripts in `scripts/` (source checkout) cover the main workflow w/ tuned defaults, see each script for env var options:
154
+
155
+ ```bash
156
+ # 1. pretrain (read all text on a page) on IDL, streamed from the HF hub
157
+ NUM_GPUS=2 MODEL=cruller_naflex_siglip2_base ./scripts/pretrain_idl.sh
158
+
159
+ # held out reading CER / WER
160
+ CHECKPOINT=output/pretrain_cruller_naflex_siglip2_base_idl/checkpoints/checkpoint-latest.pt ./scripts/eval_ocr.sh
161
+
162
+ # 2. fine-tune on DocVQA (ANLS on validation each eval interval)
163
+ CHECKPOINT=output/pretrain_cruller_naflex_siglip2_base_idl/checkpoints/checkpoint-latest.pt ./scripts/finetune_docvqa.sh
164
+
165
+ # 3. evaluate (validation ANLS, or SPLIT=test for an eval server submission file)
166
+ CHECKPOINT=output/finetune_docvqa/checkpoints/checkpoint-latest.pt ./scripts/eval_docvqa.sh
167
+ ```
168
+
169
+ ### Command line / config
170
+
171
+ Apps are configured by dataclasses parsed w/ [jsonargparse](https://github.com/omni-us/jsonargparse):
172
+
173
+ * `--task read|docvqa` selects the task (and the type of the `task` config section), default `read` for train
174
+ * nested fields map to dotted args, written w/ dashes: `--trainer.opt.learning-rate 1e-4` (underscores also accepted)
175
+ * `--task.*` -- task: model, tokenizer, image transforms, dtype / amp, max text length, eval generation
176
+ * `--trainer.*` -- optimization: intervals, warmup, optimizer / schedule (`--trainer.opt.*`), EMA, compile, grad ckpt,
177
+ FSDP
178
+ * `--train-data.*` / `--eval-data.*` -- chugr data args (source, format, split, batch size, workers, NaFlex batching)
179
+ * `--config file.yaml` loads a config (CLI args override), `--print-config` dumps the resolved config, `--help` lists all
180
+ * lists are passed as `--train-data.naflex-seq-lens [1024,2048,4096]`
181
+
182
+ The full resolved config is written to `<output_dir>/<experiment>/config.yaml` and can be passed back via `--config`.
183
+
184
+ ### Distributed / compile
185
+
186
+ Distributed runs (`torch.distributed.run`) use DDP by default, `--trainer.fsdp true` shards params, grads and
187
+ optimizer state w/ FSDP2 (`fully_shard` per encoder block / decoder layer + root, models list their blocks in
188
+ `fsdp_shard_modules()`). The handling follows the OpenCLIP `TrainingTask` (`prepare_distributed` / `prepare_fsdp` /
189
+ `compile` on the task):
190
+
191
+ * FSDP mixed precision is a `MixedPrecisionPolicy` (bf16 params, fp32 gradient reduction), autocast is suppressed
192
+ * `--trainer.grad-checkpointing` w/ FSDP applies composable activation checkpointing per block (inline otherwise)
193
+ * `--trainer.compile true --trainer.compile-strategy task|model|blocks`: `task` compiles the task training forward
194
+ (model + loss), `model` the model before DDP, `blocks` the blocks in place before DDP (checkpoint recompute stays
195
+ in eager autograd, bounding compiled backward memory). W/ FSDP, blocks are always compiled inside `prepare_fsdp()`,
196
+ ordered compile -> composable checkpointing -> `fully_shard` (checkpoint hooks on the compiled wrapper so recompute
197
+ re-enters the FSDP all-gather hooks), `--compile-mode` / `--compile-dynamic` / `--compile-backend` pass through
198
+ * eval and train metrics run w/ the params gathered (`task.full_params()`), so ranks decode independently
199
+ * checkpoints are gathered full state dicts (model, EMA, optimizer), loadable w/o FSDP, model / EMA weights are
200
+ interchangeable between FSDP and DDP runs (optimizer state is not)
201
+
202
+ `scripts/check_distributed.py` checks single / DDP / FSDP parity (loss trajectories, final params), grad
203
+ checkpointing, accumulation, compile, EMA, resume and generation through the Trainer on synthetic batches.
204
+
205
+ ### Generation
206
+
207
+ `pixr.generation` provides batched, KV cached greedy (`generate_greedy`) and beam search (`generate_beam`) decoding,
208
+ `--task.eval-num-beams` selects beam search for eval. Cruller, Fritter and Cronut share one incremental decoding
209
+ interface:
210
+
211
+ ```python
212
+ memory = model.encode_image(image_input) # per image, computed once
213
+ logits, state = model.prefill(memory, prompt_ids, prompt_mask) # left padded prompts
214
+ logits, state = model.step(token_ids, state) # one token per hypothesis
215
+ state.reorder(parent_idx) # select / duplicate hypotheses (beam search)
216
+ ```
217
+
218
+ The per image memory and the per hypothesis sequence state are separate (`pixr/models/kv_cache.py`). Cruller's
219
+ cross-attention K / V are projected once inside each decoder layer and shared by all hypotheses of an image (several
220
+ prompts or beams per image, image-major order), only the self-attention caches are reordered. Under FSDP, generation
221
+ must run inside `task.full_params()` (ranks then decode independently).
222
+
223
+ ### LR scaling
224
+
225
+ Params are grouped by component via the model's `param_layer_map()`: `encoder` (image encoder), `bridge`
226
+ (cross-attention / prefix projector), `decoder` (text decoder / LM trunk). Each has an LR multiplier
227
+ (`--trainer.opt.encoder-lr-scale`, `--trainer.opt.bridge-lr-scale`, `--trainer.opt.decoder-lr-scale`) and the
228
+ encoder / decoder support layer-wise LR decay (`--trainer.opt.encoder-layer-decay 0.75`,
229
+ `--trainer.opt.decoder-layer-decay`). `--trainer.opt.lr-scale-overrides '{"embed_tokens": 2.0}'` adds multipliers
230
+ by param name regex. Training pretrained encoders at full LR from the start wrecks their features before the decoder
231
+ learns to use them (reading never takes off), `scripts/pretrain_idl.sh` uses `ENC_LR_SCALE=0.1`.
232
+
233
+ ### LM loss
234
+
235
+ The LM head + cross-entropy is fused and chunked over tokens by default (`--task.loss-chunk-size 4096`, 0 to
236
+ materialize full logits), padding is dropped before the head GEMM and each chunk's logits are recomputed in backward,
237
+ so the [tokens, vocab] logits (~150k vocab for Qwen3) are never materialized. `--task.z-loss-weight` (e.g. `1e-4`) adds
238
+ a z-loss (mean squared logsumexp), `--task.label-smoothing` is supported by both paths.
239
+
240
+ ### NaFlex batching
241
+
242
+ For NaFlex models the train loader can draw a sequence length per batch and size the batch to a token budget, w/
243
+ samples resized to the batch's patch budget (rank-identical schedule, from OpenCLIP's streaming NaFlex loader):
244
+
245
+ ```bash
246
+ --train-data.naflex-seq-lens [1024,2048,4096] --train-data.naflex-seq-len-probs [0.25,0.25,0.5] \
247
+ --train-data.naflex-max-tokens 65536 --train-data.naflex-text-tokens 512 --trainer.opt.loss-batch-scale sqrt
248
+ ```
249
+
250
+ `--train-data.length-bucketing true` groups samples of similar target text length. Neither requires resampled shards.
251
+
252
+ Checkpoints include the resolved model config and the tokens added to the tokenizer, so fine-tuning and evaluation
253
+ only need `--checkpoint-path`, task tokens are appended and their embeddings initialized from the mean embedding.
254
+
255
+ ## Code Organization
256
+
257
+ Within `src/pixr`:
258
+ * `app/` - CLI applications for training and evaluation
259
+ * `app/train.py` - main training CLI entrypoint, will attempt to keep useable across tasks
260
+ * `app/eval.py` - main evaluation CLI entrypoint
261
+ * `data/` - data loaders, image and text preprocessing
262
+ * `framework/` - lightweight train & evaluation scaffolding on top of canonical PyTorch
263
+ * `framework/task.py` - `TrainingTask` base (model + objective + eval)
264
+ * `framework/trainer.py` - `Trainer` (optimizer, LR schedule, AMP, grad accumulation, DDP / FSDP2 / compile setup,
265
+ EMA, train state)
266
+ * `models/` - modelling code with associated factory methods and helpers
267
+ * `task/` - tasks (`read` pretraining, `docvqa` fine-tune), registered in `task/task_registry.py`
268
+ * `tokenizers/` - tokenizer creation and task token handling
269
+ * `generation.py` - batched, KV cached greedy and beam search decoding (`--task.eval-num-beams`)
270
+ * `utils/` - misc utils that don't have a home (`utils/cli.py` -- jsonargparse config / CLI parsing)
271
+
272
+ ## Concepts & Terminology
273
+
274
+ Some terms and concepts used in this project that may be a bit unfamiliar.
275
+
276
+ ### Task
277
+ A key organization concept in this project. Package the model with its loss, pre/post-processing, and optimization setup together for a given objective.
278
+
279
+ Examples of tasks conceptually:
280
+ * Pretraining a Donut style (image enc + text dec) model on supervised (OCR annotation) doc-text dataset
281
+ * Pretraining a Pix2Struct style (image enc + text dec) model w/ a dataset of webpage/screenshots and structured, simplified HTML
282
+ * Pretraining a PaLI style (image enc + text enc-dec) model w/ prefix & masked-token completion on datasets as above
283
+ * Fine-tuning any of the above pretrained models on a possibly large variety of downstream tasks
284
+ * Semi-structured doc parsing - receipt, invoice, business cards, etc.
285
+ * VQA
286
+ * Captioning
287
+ * ... and more
288
+
289
+ With the Task concept, the data pipeline exists outside the task. The data pipeline is coupled to the task by passing the pre-processing functions created within the task to the data pipeline on creation (`task.train_data_cfg()` / `task.eval_data_cfg()`).
290
+
291
+ A task is a `TrainingTask` (an `nn.Module` wrapping the model) implementing `training_forward(batch) -> (outputs, losses)`,
292
+ optional `train_metrics()`, and `evaluate(loader)`. The generic `Trainer` owns everything else (optimizer, schedule,
293
+ AMP, accumulation, setup ordering for DDP / FSDP2 / compile, EMA, checkpointing state). Tasks are agnostic to the model family.
294
+
295
+ ### Interval
296
+
297
+ You'll see the term 'interval' in the code, sometimes next to epoch. It's related, but an epoch means 'one complete pass of the dataset', an interval may be an epoch, but it may not. Interval is a span of training between checkpoints, ideally meaningful enough in duration to warrant evaluating and archiving each interval checkpoint.
298
+
299
+ In OpenCLIP development the term arose when using shard sampling with replacement, where the intervals between checkpoints were determined by limitations on job durations or likelihood of crashes.
300
+
301
+ ## Development
302
+
303
+ Tests: `pytest tests` (`tests/test_distributed.py` runs FSDP2 / DDP on a single rank NCCL process group, skipped w/o CUDA).
304
+
305
+ For short test / train / eval runs on two GPUs (source checkout), use the scenario launcher from an environment with editable
306
+ `pixr`, `chugr`, and the local `timm` installed:
307
+
308
+ ```bash
309
+ python scripts/run_review.py --list
310
+ python scripts/run_review.py all --validate # parse app args, no GPU jobs
311
+ python scripts/run_review.py smoke --dry-run # inspect commands
312
+ python scripts/run_review.py smoke --devices 0,1
313
+ python scripts/run_review.py fsdp_compile naflex_repeat prefix_flex
314
+ python scripts/run_review.py data_ranks # check rank streams are distinct
315
+ python scripts/run_review.py tests parity
316
+ python scripts/run_review.py all
317
+ ```
318
+
319
+ The launcher runs scenarios sequentially through `pixr.app.train`, `pixr.app.eval`, pytest and
320
+ `scripts/check_distributed.py`. Scenarios cover DDP / FSDP2, gradient accumulation, compile, checkpointing, EMA,
321
+ NaFlex batching, all three model families, attention backends, checkpoint resume and DocVQA fine-tuning.
322
+ `data_ranks` checks actual resampled WDS sample identities across ranks, without loading a model or decoding PDFs.
323
+ It fails if any two ranks receive an identical stream. App runs also fail if no updates or non-finite losses are logged.
324
+ `parity` compares FP32 DDP / FSDP2 model and EMA parameters and fails above a relative L2 difference of `1e-5`;
325
+ its resumed losses and generated outputs are also recorded for inspection.
326
+
327
+ Defaults are short execution checks: random initialization, 64 global samples per interval, batch 2 per GPU,
328
+ accumulation 2, 256 image patches and 128 text tokens. `--pretrained` enables hub weight initialization for the
329
+ app runs; tokenizers and Qwen3 / Ettin configs can still require downloads without it. `pretrained_tests` downloads
330
+ HF reference weights; `docvqa` uses the HF DocVQA dataset. `--samples`, `--batch-size`, `--accum`, `--patches`,
331
+ `--text-length`, and `--workers` adjust the workload. These short random-weight runs do not measure convergence.
332
+
333
+ The default data paths are sibling `../data/idl/idl-train-000{01..07}.tar` for training and shard `00000` for eval;
334
+ override with `--train-source` / `--eval-source`. `naflex_repeat` needs at least `GPUs * workers` training shards.
335
+ Logs, exact commands, resolved configs, checkpoints and `summary.json` go into a new `../runs/review-<timestamp>`
336
+ directory (`--output` overrides it). Each command has a 20-minute timeout (`--timeout` in seconds). Failed scenarios
337
+ are logged and the remaining scenarios continue; `--fail-fast` stops at the first failure. The launcher exits
338
+ nonzero on failure and prints a `tail -f` command for the active log.
339
+
340
+ ## Acknowledgements
341
+
342
+ Model and training design draws on [Donut](https://github.com/clovaai/donut), Pix2Struct, PaLI, LLaVA and GenLIP.
343
+ Image encoders come from [`timm`](https://github.com/huggingface/pytorch-image-models), the training task / FSDP2
344
+ handling and NaFlex batching follow [OpenCLIP](https://github.com/mlfoundations/open_clip), datasets are on the
345
+ Hugging Face Hub under the [`pixparse`](https://huggingface.co/pixparse) organization.
346
+
347
+ ## License
348
+
349
+ Apache 2.0, see [LICENSE](LICENSE).