feferai commited on
Commit
8ce9251
·
verified ·
1 Parent(s): fff09f5

FEFER AI 🦖 — VisionPsy-Nano-460M for pegd.fun / Stable 988

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/category_performance.png filter=lfs diff=lfs merge=lfs -text
37
+ assets/category_summary.png filter=lfs diff=lfs merge=lfs -text
38
+ fefer-512.png filter=lfs diff=lfs merge=lfs -text
ATTRIBUTIONS.md ADDED
The diff for this file is too large to render. See raw diff
 
LICENSE ADDED
@@ -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 [yyyy] [name of copyright owner]
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.
README.md ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: qvac/VisionPsy-Nano-460M
4
+ pipeline_tag: image-text-to-text
5
+ library_name: transformers
6
+ tags:
7
+ - vision-language-model
8
+ - nanovlm
9
+ - chart-understanding
10
+ - ocr
11
+ - crypto
12
+ - launchpad
13
+ - stable-mainnet
14
+ - fefer
15
+ - pegd-fun
16
+ language:
17
+ - en
18
+ ---
19
+
20
+ <p align="center">
21
+ <img src="fefer-512.png" width="180" alt="FEFER AI" />
22
+ </p>
23
+
24
+ # FEFER-AI-460M 🦖
25
+
26
+ **FEFER** is the resident chart analyst of [pegd.fun](https://pegd.fun) — the stablecoin
27
+ launchpad on **Stable mainnet (chain id 988)**. This model is a FEFER-tuned build of
28
+ [`qvac/VisionPsy-Nano-460M`](https://huggingface.co/qvac/VisionPsy-Nano-460M)
29
+ (Tether AI Research), a ~460M-parameter vision-language model (SigLIP2 vision encoder +
30
+ SmolLM2-360M LM, nanoVLM architecture) that punches far above its size on chart
31
+ understanding, OCR, and visual instruction following.
32
+
33
+ FEFER reads **one thing and one thing only**: terminal-style candlestick charts rendered by
34
+ the pegd.fun indexer for Stable-988 launchpad tokens. Each chart has verified indexer facts
35
+ (market cap, 24h volume, graduation progress) printed directly on the image, so the model
36
+ cross-checks pixels against ground truth instead of inventing numbers.
37
+
38
+ ## What it's for
39
+
40
+ - Reading pegd.fun 1024×512 candle charts: trend, momentum, volume behavior, drawdowns
41
+ - Answering holder questions in the FEFER persona (plain language, risk note always included)
42
+ - Explaining launchpad mechanics it was trained on: fixed 1B supply, permanently locked
43
+ Uniswap V3 1% USDT0 pools, 3,000 USDT0 opening FDV, 9,000 USDT0 graduation, 80/20
44
+ creator/platform fee split
45
+
46
+ ## What it's NOT for
47
+
48
+ - Price prediction or financial advice — FEFER always says so, by training and by prompt
49
+ - Multi-image reasoning (single-image by architecture)
50
+ - Anything outside Stable mainnet 988 — other chains are out of scope on purpose
51
+
52
+ ## Quickstart (transformers)
53
+
54
+ ```python
55
+ from transformers import AutoModelForImageTextToText, AutoProcessor
56
+ from PIL import Image
57
+
58
+ repo = "feferai/FEFER-AI-460M" # replace after publishing
59
+ model = AutoModelForImageTextToText.from_pretrained(repo, trust_remote_code=True)
60
+ processor = AutoProcessor.from_pretrained(repo, trust_remote_code=True)
61
+
62
+ chart = Image.open("fefer_chart.png") # a pegd.fun /api/fefer/chart/<token>.png render
63
+ messages = [{"role": "user", "content": [
64
+ {"type": "image", "image": chart},
65
+ {"type": "text", "text": "Read this chart. Trend, volume, and graduation status?"},
66
+ ]}]
67
+ inputs = processor.apply_chat_template(messages, add_generation_prompt=True,
68
+ tokenize=True, return_dict=True, return_tensors="pt")
69
+ out = model.generate(**inputs, max_new_tokens=320)
70
+ print(processor.batch_decode(out[:, inputs["input_ids"].shape[1]:], skip_special_tokens=True)[0])
71
+ ```
72
+
73
+ ## Serving for pegd.fun
74
+
75
+ Any OpenAI-compatible endpoint works. Then point the launchpad at it:
76
+
77
+ ```bash
78
+ # GPU
79
+ vllm serve feferai/FEFER-AI-460M --trust-remote-code --port 8000
80
+ # or CPU (no GPU needed at 460M)
81
+ python fefer-ai/serve_cpu.py --model feferai/FEFER-AI-460M --port 8008
82
+
83
+ # pegd.fun server env
84
+ FEFER_AI_URL=http://127.0.0.1:8000/v1
85
+ FEFER_AI_MODEL=feferai/FEFER-AI-460M
86
+ ```
87
+
88
+ ## Training
89
+
90
+ LoRA fine-tune on the language-model attention projections (vision tower frozen), on
91
+ chart/answer pairs harvested from live pegd.fun launches by `fefer-ai/build_dataset.mjs`
92
+ — every sample is a real indexer-rendered chart paired with facts computed from on-chain
93
+ trade history, so supervision is grounded, not synthetic guesswork. Merged with
94
+ `merge_and_unload` for standalone serving.
95
+
96
+ ## Limitations & bias
97
+
98
+ 460M parameters is small: FEFER is a sharp chart *reader*, not an oracle. It can misread
99
+ dense wicks, and it inherits any biases of the base model. Launchpad tokens are volatile;
100
+ nothing this model outputs is financial advice, and it is trained to say exactly that.
101
+
102
+ ## License & attribution
103
+
104
+ Apache 2.0, same as the base model. Built on **VisionPsy-Nano-460M by Tether AI Research
105
+ (qvac)** — all credit for the base architecture and pretraining to them. FEFER branding,
106
+ chart pipeline, dataset tooling, and fine-tune by the pegd.fun team.
assets/category_performance.png ADDED

Git LFS Details

  • SHA256: 08e875bd4a9c7ff23f832660053d7cf079b499892e33e2d5a8ff446ef97058de
  • Pointer size: 131 Bytes
  • Size of remote file: 462 kB
assets/category_summary.png ADDED

Git LFS Details

  • SHA256: 02d86a9d7543eaeb99bba876e65ef0e9f1ff90de52a5b02c066d364314e24ae3
  • Pointer size: 131 Bytes
  • Size of remote file: 234 kB
chat_template.jinja ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {% for message in messages %}{{'<|im_start|>' + message['role'] + '
2
+ ' + message['content'] + '<|im_end|>' + '
3
+ '}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant
4
+ ' }}{% endif %}
config.json ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "VisionPsyNanoForConditionalGeneration"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_visionpsynano.VisionPsyNanoConfig",
7
+ "AutoModel": "modeling_visionpsynano.VisionPsyNanoForConditionalGeneration",
8
+ "AutoModelForImageTextToText": "modeling_visionpsynano.VisionPsyNanoForConditionalGeneration",
9
+ "AutoProcessor": "processing_visionpsynano.VisionPsyNanoProcessor"
10
+ },
11
+ "compile_inference": true,
12
+ "compile_inference_mode": "default",
13
+ "cuda_graphs_cache_quantum": 128,
14
+ "eos_check_interval": 16,
15
+ "extra_token_amount": 66,
16
+ "hf_repo_name": "qvac/VisionPsy-Nano-460M",
17
+ "inference_max_img_size": null,
18
+ "is_flash": false,
19
+ "lm_attn_scaling": 1.0,
20
+ "lm_base_vocab_size": 49152,
21
+ "lm_chat_template": "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}",
22
+ "lm_dropout": 0.0,
23
+ "lm_hidden_dim": 960,
24
+ "lm_inter_dim": 2560,
25
+ "lm_max_length": 8192,
26
+ "lm_max_position_embeddings": 8192,
27
+ "lm_model_type": "HuggingFaceTB/SmolLM2-360M-Instruct",
28
+ "lm_n_blocks": 32,
29
+ "lm_n_heads": 15,
30
+ "lm_n_kv_heads": 5,
31
+ "lm_re_base": 100000,
32
+ "lm_rms_eps": 1e-05,
33
+ "lm_tie_weights": true,
34
+ "lm_tokenizer": "HuggingFaceTB/SmolLM2-360M-Instruct",
35
+ "lm_use_tokens": false,
36
+ "lm_vocab_size": 49218,
37
+ "max_img_size": 2048,
38
+ "model_type": "visionpsynano",
39
+ "mp_image_token_length": 64,
40
+ "mp_pixel_shuffle_factor": 4,
41
+ "resize_min_side_len": null,
42
+ "resize_to_max_side_len": true,
43
+ "transformers_version": "5.14.1",
44
+ "vit_cls_flag": false,
45
+ "vit_dropout": 0.0,
46
+ "vit_hidden_dim": 768,
47
+ "vit_img_size": 512,
48
+ "vit_inter_dim": 3072,
49
+ "vit_ln_eps": 1e-06,
50
+ "vit_model_type": "google/siglip2-base-patch16-512",
51
+ "vit_n_blocks": 12,
52
+ "vit_n_heads": 12,
53
+ "vit_patch_size": 16,
54
+ "vlm_checkpoint_path": null,
55
+ "vlm_extra_tokens": {
56
+ "global_image_token": "<|global_image|>",
57
+ "image_token": "<|image|>",
58
+ "r1c1": "<row_1_col_1>",
59
+ "r1c2": "<row_1_col_2>",
60
+ "r1c3": "<row_1_col_3>",
61
+ "r1c4": "<row_1_col_4>",
62
+ "r1c5": "<row_1_col_5>",
63
+ "r1c6": "<row_1_col_6>",
64
+ "r1c7": "<row_1_col_7>",
65
+ "r1c8": "<row_1_col_8>",
66
+ "r2c1": "<row_2_col_1>",
67
+ "r2c2": "<row_2_col_2>",
68
+ "r2c3": "<row_2_col_3>",
69
+ "r2c4": "<row_2_col_4>",
70
+ "r2c5": "<row_2_col_5>",
71
+ "r2c6": "<row_2_col_6>",
72
+ "r2c7": "<row_2_col_7>",
73
+ "r2c8": "<row_2_col_8>",
74
+ "r3c1": "<row_3_col_1>",
75
+ "r3c2": "<row_3_col_2>",
76
+ "r3c3": "<row_3_col_3>",
77
+ "r3c4": "<row_3_col_4>",
78
+ "r3c5": "<row_3_col_5>",
79
+ "r3c6": "<row_3_col_6>",
80
+ "r3c7": "<row_3_col_7>",
81
+ "r3c8": "<row_3_col_8>",
82
+ "r4c1": "<row_4_col_1>",
83
+ "r4c2": "<row_4_col_2>",
84
+ "r4c3": "<row_4_col_3>",
85
+ "r4c4": "<row_4_col_4>",
86
+ "r4c5": "<row_4_col_5>",
87
+ "r4c6": "<row_4_col_6>",
88
+ "r4c7": "<row_4_col_7>",
89
+ "r4c8": "<row_4_col_8>",
90
+ "r5c1": "<row_5_col_1>",
91
+ "r5c2": "<row_5_col_2>",
92
+ "r5c3": "<row_5_col_3>",
93
+ "r5c4": "<row_5_col_4>",
94
+ "r5c5": "<row_5_col_5>",
95
+ "r5c6": "<row_5_col_6>",
96
+ "r5c7": "<row_5_col_7>",
97
+ "r5c8": "<row_5_col_8>",
98
+ "r6c1": "<row_6_col_1>",
99
+ "r6c2": "<row_6_col_2>",
100
+ "r6c3": "<row_6_col_3>",
101
+ "r6c4": "<row_6_col_4>",
102
+ "r6c5": "<row_6_col_5>",
103
+ "r6c6": "<row_6_col_6>",
104
+ "r6c7": "<row_6_col_7>",
105
+ "r6c8": "<row_6_col_8>",
106
+ "r7c1": "<row_7_col_1>",
107
+ "r7c2": "<row_7_col_2>",
108
+ "r7c3": "<row_7_col_3>",
109
+ "r7c4": "<row_7_col_4>",
110
+ "r7c5": "<row_7_col_5>",
111
+ "r7c6": "<row_7_col_6>",
112
+ "r7c7": "<row_7_col_7>",
113
+ "r7c8": "<row_7_col_8>",
114
+ "r8c1": "<row_8_col_1>",
115
+ "r8c2": "<row_8_col_2>",
116
+ "r8c3": "<row_8_col_3>",
117
+ "r8c4": "<row_8_col_4>",
118
+ "r8c5": "<row_8_col_5>",
119
+ "r8c6": "<row_8_col_6>",
120
+ "r8c7": "<row_8_col_7>",
121
+ "r8c8": "<row_8_col_8>"
122
+ },
123
+ "vlm_load_backbone_weights": false,
124
+ "torch_dtype": "float32"
125
+ }
configuration_visionpsynano.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HuggingFace PretrainedConfig for VisionPsyNano."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from typing import Any, Optional
6
+
7
+ from transformers import PretrainedConfig
8
+
9
+
10
+ FLASH_MIN_SIDE_LEN = 512
11
+
12
+
13
+ def resolve_is_flash(
14
+ *,
15
+ is_flash: Optional[bool] = None,
16
+ variant: Optional[str] = None,
17
+ resize_to_max_side_len: Optional[bool] = None,
18
+ ) -> bool:
19
+ """Resolve Flash preprocess mode from explicit flag, legacy variant, or resize policy."""
20
+ if is_flash is not None:
21
+ return bool(is_flash)
22
+ if variant is not None:
23
+ v = str(variant).lower().strip()
24
+ if v in ("flash", "nano-flash", "visionpsy-nano-flash"):
25
+ return True
26
+ if v in ("nano", "plain", "nano-plain", "visionpsy-nano"):
27
+ return False
28
+ raise ValueError(
29
+ "legacy variant must be 'nano' or 'flash' "
30
+ f"(or aliases); got {variant!r}. Prefer is_flash=True/False."
31
+ )
32
+ if resize_to_max_side_len is not None:
33
+ return not bool(resize_to_max_side_len)
34
+ return False
35
+
36
+
37
+ def apply_flash_preprocess(
38
+ *,
39
+ is_flash: bool,
40
+ resize_to_max_side_len: Optional[bool] = None,
41
+ resize_min_side_len: Optional[int] = None,
42
+ ) -> tuple[bool, Optional[int]]:
43
+ """Return (resize_to_max_side_len, resize_min_side_len) for the given Flash mode."""
44
+ if resize_to_max_side_len is None:
45
+ resize_to_max_side_len = not is_flash
46
+ resize_to_max_side_len = bool(resize_to_max_side_len)
47
+ if is_flash:
48
+ resize_min_side_len = max(int(resize_min_side_len or 0), FLASH_MIN_SIDE_LEN)
49
+ elif resize_to_max_side_len:
50
+ resize_min_side_len = None
51
+ return resize_to_max_side_len, resize_min_side_len
52
+
53
+
54
+ _DEFAULT_CHAT_TEMPLATE = (
55
+ "{% for message in messages %}"
56
+ "{{'<|im_start|>' + message['role'] + '\\n' + message['content'] + '<|im_end|>' + '\\n'}}"
57
+ "{% endfor %}"
58
+ "{% if add_generation_prompt %}{{ '<|im_start|>assistant\\n' }}{% endif %}"
59
+ )
60
+
61
+ _DEFAULT_EXTRA_TOKENS = {
62
+ "image_token": "<|image|>",
63
+ "global_image_token": "<|global_image|>",
64
+ "r1c1": "<row_1_col_1>",
65
+ "r1c2": "<row_1_col_2>",
66
+ "r1c3": "<row_1_col_3>",
67
+ "r1c4": "<row_1_col_4>",
68
+ "r1c5": "<row_1_col_5>",
69
+ "r1c6": "<row_1_col_6>",
70
+ "r1c7": "<row_1_col_7>",
71
+ "r1c8": "<row_1_col_8>",
72
+ "r2c1": "<row_2_col_1>",
73
+ "r2c2": "<row_2_col_2>",
74
+ "r2c3": "<row_2_col_3>",
75
+ "r2c4": "<row_2_col_4>",
76
+ "r2c5": "<row_2_col_5>",
77
+ "r2c6": "<row_2_col_6>",
78
+ "r2c7": "<row_2_col_7>",
79
+ "r2c8": "<row_2_col_8>",
80
+ "r3c1": "<row_3_col_1>",
81
+ "r3c2": "<row_3_col_2>",
82
+ "r3c3": "<row_3_col_3>",
83
+ "r3c4": "<row_3_col_4>",
84
+ "r3c5": "<row_3_col_5>",
85
+ "r3c6": "<row_3_col_6>",
86
+ "r3c7": "<row_3_col_7>",
87
+ "r3c8": "<row_3_col_8>",
88
+ "r4c1": "<row_4_col_1>",
89
+ "r4c2": "<row_4_col_2>",
90
+ "r4c3": "<row_4_col_3>",
91
+ "r4c4": "<row_4_col_4>",
92
+ "r4c5": "<row_4_col_5>",
93
+ "r4c6": "<row_4_col_6>",
94
+ "r4c7": "<row_4_col_7>",
95
+ "r4c8": "<row_4_col_8>",
96
+ "r5c1": "<row_5_col_1>",
97
+ "r5c2": "<row_5_col_2>",
98
+ "r5c3": "<row_5_col_3>",
99
+ "r5c4": "<row_5_col_4>",
100
+ "r5c5": "<row_5_col_5>",
101
+ "r5c6": "<row_5_col_6>",
102
+ "r5c7": "<row_5_col_7>",
103
+ "r5c8": "<row_5_col_8>",
104
+ "r6c1": "<row_6_col_1>",
105
+ "r6c2": "<row_6_col_2>",
106
+ "r6c3": "<row_6_col_3>",
107
+ "r6c4": "<row_6_col_4>",
108
+ "r6c5": "<row_6_col_5>",
109
+ "r6c6": "<row_6_col_6>",
110
+ "r6c7": "<row_6_col_7>",
111
+ "r6c8": "<row_6_col_8>",
112
+ "r7c1": "<row_7_col_1>",
113
+ "r7c2": "<row_7_col_2>",
114
+ "r7c3": "<row_7_col_3>",
115
+ "r7c4": "<row_7_col_4>",
116
+ "r7c5": "<row_7_col_5>",
117
+ "r7c6": "<row_7_col_6>",
118
+ "r7c7": "<row_7_col_7>",
119
+ "r7c8": "<row_7_col_8>",
120
+ "r8c1": "<row_8_col_1>",
121
+ "r8c2": "<row_8_col_2>",
122
+ "r8c3": "<row_8_col_3>",
123
+ "r8c4": "<row_8_col_4>",
124
+ "r8c5": "<row_8_col_5>",
125
+ "r8c6": "<row_8_col_6>",
126
+ "r8c7": "<row_8_col_7>",
127
+ "r8c8": "<row_8_col_8>",
128
+ }
129
+
130
+
131
+ class VisionPsyNanoConfig(PretrainedConfig):
132
+ """Config for VisionPsyNano (``is_flash`` selects preprocess mode)."""
133
+
134
+ model_type = "visionpsynano"
135
+ keys_to_ignore_at_inference = ["past_key_values"]
136
+
137
+ def __init__(
138
+ self,
139
+ is_flash: Optional[bool] = None,
140
+ variant: Optional[str] = None,
141
+ vit_hidden_dim: int = 768,
142
+ vit_inter_dim: int = 3072,
143
+ vit_patch_size: int = 16,
144
+ vit_img_size: int = 512,
145
+ vit_n_heads: int = 12,
146
+ vit_dropout: float = 0.0,
147
+ vit_n_blocks: int = 12,
148
+ vit_ln_eps: float = 1e-6,
149
+ vit_cls_flag: bool = False,
150
+ vit_model_type: str = "google/siglip2-base-patch16-512",
151
+ lm_hidden_dim: int = 960,
152
+ lm_inter_dim: int = 2560,
153
+ lm_rms_eps: float = 1e-5,
154
+ lm_re_base: int = 100000,
155
+ lm_max_position_embeddings: int = 8192,
156
+ lm_base_vocab_size: int = 49152,
157
+ extra_token_amount: int = 66,
158
+ lm_vocab_size: Optional[int] = None,
159
+ lm_n_heads: int = 15,
160
+ lm_n_kv_heads: int = 5,
161
+ lm_dropout: float = 0.0,
162
+ lm_n_blocks: int = 32,
163
+ lm_attn_scaling: float = 1.0,
164
+ lm_max_length: int = 4096,
165
+ lm_use_tokens: bool = False,
166
+ lm_tie_weights: bool = True,
167
+ lm_model_type: str = "HuggingFaceTB/SmolLM2-360M-Instruct",
168
+ lm_tokenizer: str = "HuggingFaceTB/SmolLM2-360M-Instruct",
169
+ lm_chat_template: str = _DEFAULT_CHAT_TEMPLATE,
170
+ mp_pixel_shuffle_factor: int = 4,
171
+ mp_image_token_length: int = 64,
172
+ max_img_size: int = 2048,
173
+ resize_to_max_side_len: Optional[bool] = None,
174
+ resize_min_side_len: Optional[int] = None,
175
+ inference_max_img_size: Optional[int] = None,
176
+ vlm_extra_tokens: Optional[dict] = None,
177
+ vlm_load_backbone_weights: bool = True,
178
+ vlm_checkpoint_path: str = "checkpoints",
179
+ hf_repo_name: str = "qvac/VisionPsy-Nano-460M",
180
+ compile_inference: bool = True,
181
+ compile_inference_mode: str = "reduce-overhead",
182
+ cuda_graphs_cache_quantum: int = 128,
183
+ eos_check_interval: int = 16,
184
+ **kwargs: Any,
185
+ ):
186
+ is_flash = resolve_is_flash(
187
+ is_flash=is_flash,
188
+ variant=variant,
189
+ resize_to_max_side_len=resize_to_max_side_len,
190
+ )
191
+ resize_to_max_side_len, resize_min_side_len = apply_flash_preprocess(
192
+ is_flash=is_flash,
193
+ resize_to_max_side_len=resize_to_max_side_len,
194
+ resize_min_side_len=resize_min_side_len,
195
+ )
196
+
197
+ if lm_vocab_size is None:
198
+ lm_vocab_size = lm_base_vocab_size + extra_token_amount
199
+
200
+ self.is_flash = bool(is_flash)
201
+ self.vit_hidden_dim = vit_hidden_dim
202
+ self.vit_inter_dim = vit_inter_dim
203
+ self.vit_patch_size = vit_patch_size
204
+ self.vit_img_size = vit_img_size
205
+ self.vit_n_heads = vit_n_heads
206
+ self.vit_dropout = vit_dropout
207
+ self.vit_n_blocks = vit_n_blocks
208
+ self.vit_ln_eps = vit_ln_eps
209
+ self.vit_cls_flag = vit_cls_flag
210
+ self.vit_model_type = vit_model_type
211
+
212
+ self.lm_hidden_dim = lm_hidden_dim
213
+ self.lm_inter_dim = lm_inter_dim
214
+ self.lm_rms_eps = lm_rms_eps
215
+ self.lm_re_base = lm_re_base
216
+ self.lm_max_position_embeddings = lm_max_position_embeddings
217
+ self.lm_base_vocab_size = lm_base_vocab_size
218
+ self.extra_token_amount = extra_token_amount
219
+ self.lm_vocab_size = lm_vocab_size
220
+ self.lm_n_heads = lm_n_heads
221
+ self.lm_n_kv_heads = lm_n_kv_heads
222
+ self.lm_dropout = lm_dropout
223
+ self.lm_n_blocks = lm_n_blocks
224
+ self.lm_attn_scaling = lm_attn_scaling
225
+ self.lm_max_length = lm_max_length
226
+ self.lm_use_tokens = lm_use_tokens
227
+ self.lm_tie_weights = lm_tie_weights
228
+ self.lm_model_type = lm_model_type
229
+ self.lm_tokenizer = lm_tokenizer
230
+ self.lm_chat_template = lm_chat_template
231
+
232
+ self.mp_pixel_shuffle_factor = mp_pixel_shuffle_factor
233
+ self.mp_image_token_length = mp_image_token_length
234
+ self.max_img_size = max_img_size
235
+ self.resize_to_max_side_len = bool(resize_to_max_side_len)
236
+ self.resize_min_side_len = resize_min_side_len
237
+ self.inference_max_img_size = inference_max_img_size
238
+ self.vlm_extra_tokens = dict(vlm_extra_tokens or _DEFAULT_EXTRA_TOKENS)
239
+ self.vlm_load_backbone_weights = vlm_load_backbone_weights
240
+ self.vlm_checkpoint_path = vlm_checkpoint_path
241
+ if self.is_flash and hf_repo_name == "qvac/VisionPsy-Nano-460M":
242
+ self.hf_repo_name = "qvac/VisionPsy-Nano-460M-Flash"
243
+ else:
244
+ self.hf_repo_name = hf_repo_name
245
+
246
+ self.compile_inference = compile_inference
247
+ self.compile_inference_mode = compile_inference_mode
248
+ self.cuda_graphs_cache_quantum = cuda_graphs_cache_quantum
249
+ self.eos_check_interval = eos_check_interval
250
+
251
+ kwargs.pop("text_config", None)
252
+ kwargs.pop("vision_config", None)
253
+
254
+ super().__init__(**kwargs)
255
+
256
+ def get_text_config(self, decoder: bool = False, **kwargs):
257
+ """VisionPsyNano is a single flat config (not text+vision composite)."""
258
+ return self
259
+
260
+ @property
261
+ def variant(self) -> str:
262
+ return "flash" if self.is_flash else "nano"
263
+
264
+ def to_vlm_config(self):
265
+ """Convert to the internal VLMConfig dataclass used by the core modules."""
266
+ from dataclasses import fields as dc_fields
267
+
268
+ try:
269
+ from .vlm_config import VLMConfig
270
+ except ImportError:
271
+ from vlm_config import VLMConfig
272
+
273
+ valid = {f.name for f in dc_fields(VLMConfig)}
274
+ payload = {k: getattr(self, k) for k in valid if hasattr(self, k)}
275
+ return VLMConfig(**payload)
276
+
277
+ @classmethod
278
+ def from_vlm_config(
279
+ cls, cfg, *, is_flash: Optional[bool] = None, variant: Optional[str] = None
280
+ ) -> "VisionPsyNanoConfig":
281
+ from dataclasses import asdict
282
+
283
+ data = asdict(cfg)
284
+ data["is_flash"] = resolve_is_flash(
285
+ is_flash=is_flash,
286
+ variant=variant,
287
+ resize_to_max_side_len=data.get("resize_to_max_side_len"),
288
+ )
289
+ data.pop("variant", None)
290
+ return cls(**data)
291
+
292
+ @classmethod
293
+ def from_legacy_dict(
294
+ cls,
295
+ raw: dict,
296
+ *,
297
+ is_flash: Optional[bool] = None,
298
+ variant: Optional[str] = None,
299
+ ) -> "VisionPsyNanoConfig":
300
+ """Load an existing VisionPsyNano / nanoVLM config.json (without model_type)."""
301
+ raw = dict(raw)
302
+ for key in (
303
+ "model_type",
304
+ "architectures",
305
+ "auto_map",
306
+ "transformers_version",
307
+ "text_config",
308
+ "vision_config",
309
+ ):
310
+ raw.pop(key, None)
311
+ raw["is_flash"] = resolve_is_flash(
312
+ is_flash=is_flash if is_flash is not None else raw.get("is_flash"),
313
+ variant=variant if variant is not None else raw.get("variant"),
314
+ resize_to_max_side_len=raw.get("resize_to_max_side_len"),
315
+ )
316
+ raw.pop("variant", None)
317
+ return cls(**raw)
custom_transforms.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ from torchvision.transforms.functional import resize, InterpolationMode
4
+ from einops import rearrange
5
+ from typing import Tuple, Union
6
+ from PIL import Image
7
+
8
+
9
+ class DynamicResize(torch.nn.Module):
10
+ """Resize H/W to patch-aligned sizes within max_side_len."""
11
+ def __init__(
12
+ self,
13
+ patch_size: int,
14
+ max_side_len: int,
15
+ resize_to_max_side_len: bool = False,
16
+ min_side_len: int | None = None,
17
+ interpolation: InterpolationMode = InterpolationMode.BICUBIC,
18
+ ) -> None:
19
+ super().__init__()
20
+ self.p = int(patch_size)
21
+ self.m = int(max_side_len)
22
+ self.interpolation = interpolation
23
+ self.resize_to_max_side_len = resize_to_max_side_len
24
+ self.min_side_len = int(min_side_len) if min_side_len else None
25
+
26
+ def _get_new_hw(self, h: int, w: int) -> Tuple[int, int]:
27
+ """Compute target (h, w) divisible by patch_size."""
28
+ long, short = (w, h) if w >= h else (h, w)
29
+
30
+ if (
31
+ self.min_side_len
32
+ and not self.resize_to_max_side_len
33
+ and short < self.min_side_len
34
+ ):
35
+ den = short * self.p
36
+ target_long = min(self.m, -(-(long * self.min_side_len) // den) * self.p)
37
+ target_short = max(-(-(short * self.min_side_len) // den) * self.p, self.p)
38
+ return (target_short, target_long) if w >= h else (target_long, target_short)
39
+
40
+ target_long = self.m if self.resize_to_max_side_len else min(self.m, math.ceil(long / self.p) * self.p)
41
+ scale = target_long / long
42
+ target_short = math.ceil(short * scale / self.p) * self.p
43
+ target_short = max(target_short, self.p)
44
+
45
+ return (target_short, target_long) if w >= h else (target_long, target_short)
46
+
47
+ def forward(self, img: Union[Image.Image, torch.Tensor]):
48
+ if isinstance(img, Image.Image):
49
+ w, h = img.size
50
+ new_h, new_w = self._get_new_hw(h, w)
51
+ return resize(img, [new_h, new_w], interpolation=self.interpolation)
52
+
53
+ if not torch.is_tensor(img):
54
+ raise TypeError(
55
+ "DynamicResize expects a PIL Image or a torch.Tensor; "
56
+ f"got {type(img)}"
57
+ )
58
+
59
+ batched = img.ndim == 4
60
+ if img.ndim not in (3, 4):
61
+ raise ValueError(
62
+ "Tensor input must have shape (C,H,W) or (B,C,H,W); "
63
+ f"got {img.shape}"
64
+ )
65
+
66
+ imgs = img if batched else img.unsqueeze(0)
67
+ _, _, h, w = imgs.shape
68
+ new_h, new_w = self._get_new_hw(h, w)
69
+ out = resize(imgs, [new_h, new_w], interpolation=self.interpolation)
70
+
71
+ return out if batched else out.squeeze(0)
72
+
73
+
74
+ class SplitImage(torch.nn.Module):
75
+ """Split (B, C, H, W) image tensor into square patches.
76
+
77
+ Returns:
78
+ patches: (B·n_h·n_w, C, patch_size, patch_size)
79
+ grid: (n_h, n_w) - number of patches along H and W
80
+ """
81
+ def __init__(self, patch_size: int) -> None:
82
+ super().__init__()
83
+ self.p = patch_size
84
+
85
+ def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Tuple[int, int]]:
86
+ if x.ndim == 3:
87
+ x = x.unsqueeze(0)
88
+
89
+ b, c, h, w = x.shape
90
+ if h % self.p or w % self.p:
91
+ raise ValueError(f'Image size {(h,w)} not divisible by patch_size {self.p}')
92
+
93
+ n_h, n_w = h // self.p, w // self.p
94
+ patches = rearrange(x, 'b c (nh ph) (nw pw) -> (b nh nw) c ph pw',
95
+ ph=self.p, pw=self.p)
96
+ return patches, (n_h, n_w)
97
+
98
+
99
+ class GlobalAndSplitImages(torch.nn.Module):
100
+ def __init__(self, patch_size: int):
101
+ super().__init__()
102
+ self.p = patch_size
103
+ self.splitter = SplitImage(patch_size)
104
+
105
+ def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Tuple[int, int]]:
106
+ if x.ndim == 3:
107
+ x = x.unsqueeze(0)
108
+
109
+ patches, grid = self.splitter(x)
110
+
111
+ if grid == (1, 1):
112
+ return patches, grid
113
+
114
+ global_patch = resize(x, [self.p, self.p])
115
+ return torch.cat([global_patch, patches], dim=0), grid
fefer-512.png ADDED

Git LFS Details

  • SHA256: 825336f9e064426bde2e7ea54740eb4acd9f1166fc279eb59f19038c87c6138f
  • Pointer size: 131 Bytes
  • Size of remote file: 251 kB
language_model.py ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import inspect
2
+ import math
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+ def _sdpa_supports_native_gqa() -> bool:
8
+
9
+ try:
10
+ params = inspect.signature(F.scaled_dot_product_attention).parameters
11
+ return "enable_gqa" in params
12
+ except (TypeError, ValueError):
13
+ try:
14
+ q = torch.zeros(1, 2, 1, 4)
15
+ k = torch.zeros(1, 1, 1, 4)
16
+ v = torch.zeros(1, 1, 1, 4)
17
+ F.scaled_dot_product_attention(q, k, v, enable_gqa=True)
18
+ return True
19
+ except TypeError:
20
+ return False
21
+ except Exception:
22
+ return True
23
+
24
+ _SDPA_HAS_GQA = _sdpa_supports_native_gqa()
25
+
26
+ class RMSNorm(nn.Module):
27
+
28
+ def __init__(self, cfg):
29
+ super().__init__()
30
+ self.weight = nn.Parameter(torch.ones(cfg.lm_hidden_dim))
31
+ self.eps = cfg.lm_rms_eps
32
+ self._normalized_shape = (cfg.lm_hidden_dim,)
33
+
34
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
35
+
36
+ return F.rms_norm(x, self._normalized_shape, self.weight, self.eps)
37
+
38
+ class RotaryEmbedding(nn.Module):
39
+
40
+ def __init__(self, cfg):
41
+ super().__init__()
42
+ assert cfg.lm_hidden_dim % cfg.lm_n_heads == 0, "Hidden dimension must be divisible by number of heads"
43
+
44
+ self.dim = cfg.lm_hidden_dim // cfg.lm_n_heads
45
+ self.base = cfg.lm_re_base
46
+ self.max_seq_len = cfg.lm_max_position_embeddings
47
+ inv_freq = 1.0 / (self.base ** (torch.arange(0, self.dim, 2).float() / self.dim))
48
+ self.register_buffer("inv_freq", inv_freq)
49
+ self.original_max_seq_len = cfg.lm_max_position_embeddings
50
+ self.attention_scaling = cfg.lm_attn_scaling
51
+
52
+ positions = torch.arange(self.max_seq_len, dtype=torch.float)
53
+ freqs = positions.unsqueeze(-1) * inv_freq.unsqueeze(0)
54
+ emb = torch.cat([freqs, freqs], dim=-1)
55
+ cos_cached = emb.cos() * self.attention_scaling
56
+ sin_cached = emb.sin() * self.attention_scaling
57
+ self.register_buffer("cos_cached", cos_cached, persistent=False)
58
+ self.register_buffer("sin_cached", sin_cached, persistent=False)
59
+
60
+ @torch.no_grad()
61
+ def forward(self, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
62
+
63
+ if position_ids.dtype != torch.long:
64
+ position_ids = position_ids.long()
65
+ cos = self.cos_cached[position_ids]
66
+ sin = self.sin_cached[position_ids]
67
+ return cos, sin
68
+
69
+ def extend_cache(self, new_max_seq_len: int) -> None:
70
+
71
+ positions = torch.arange(
72
+ new_max_seq_len, dtype=torch.float, device=self.cos_cached.device,
73
+ )
74
+ freqs = positions.unsqueeze(-1) * self.inv_freq.unsqueeze(0)
75
+ emb = torch.cat([freqs, freqs], dim=-1)
76
+ self.cos_cached = emb.cos() * self.attention_scaling
77
+ self.sin_cached = emb.sin() * self.attention_scaling
78
+ self.original_max_seq_len = new_max_seq_len
79
+
80
+ def apply_rotary_pos_embd(
81
+ q: torch.Tensor,
82
+ k: torch.Tensor,
83
+ cos: torch.Tensor,
84
+ sin: torch.Tensor,
85
+ ) -> tuple[torch.Tensor, torch.Tensor]:
86
+
87
+ half = q.shape[-1] // 2
88
+ cos_h = cos[..., :half]
89
+ sin_h = sin[..., :half]
90
+
91
+ q1 = q[..., :half]
92
+ q2 = q[..., half:]
93
+ k1 = k[..., :half]
94
+ k2 = k[..., half:]
95
+
96
+ q_embed = torch.cat(
97
+ (q1 * cos_h - q2 * sin_h, q2 * cos_h + q1 * sin_h),
98
+ dim=-1,
99
+ )
100
+ k_embed = torch.cat(
101
+ (k1 * cos_h - k2 * sin_h, k2 * cos_h + k1 * sin_h),
102
+ dim=-1,
103
+ )
104
+ return q_embed, k_embed
105
+
106
+ class LanguageModelGroupedQueryAttention(nn.Module):
107
+
108
+ def __init__(self, cfg):
109
+ super().__init__()
110
+
111
+ self.n_heads = cfg.lm_n_heads
112
+ self.n_kv_heads = cfg.lm_n_kv_heads
113
+ self.embd_dim = cfg.lm_hidden_dim
114
+ self.dropout = cfg.lm_dropout
115
+
116
+ assert self.n_heads % self.n_kv_heads == 0, "n_heads must be divisible by n_kv_heads"
117
+ assert self.embd_dim % self.n_heads == 0, "embd_dim must be divisible by num_heads"
118
+
119
+ self.n_kv_groups = self.n_heads // self.n_kv_heads
120
+ self.head_dim = self.embd_dim // self.n_heads
121
+
122
+ self.q_proj = nn.Linear(self.embd_dim, self.embd_dim, bias=False)
123
+ self.k_proj = nn.Linear(self.embd_dim, self.head_dim * self.n_kv_heads, bias=False)
124
+ self.v_proj = nn.Linear(self.embd_dim, self.head_dim * self.n_kv_heads, bias=False)
125
+ self.out_proj = nn.Linear(self.embd_dim, self.embd_dim, bias=False)
126
+
127
+ self.attn_dropout = nn.Dropout(self.dropout)
128
+ self.resid_dropout = nn.Dropout(self.dropout)
129
+
130
+ self.sdpa = hasattr(torch.nn.functional, 'scaled_dot_product_attention')
131
+ if not self.sdpa:
132
+ print("Warning: scaled dot product attention not available, using standard attention in LM.")
133
+
134
+ def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, attention_mask=None, block_kv_cache=None, start_pos: int = 0) -> tuple[torch.Tensor, dict]:
135
+
136
+ B, T_curr, C = x.size()
137
+
138
+ q_curr = self.q_proj(x).view(B, T_curr, self.n_heads, self.head_dim).transpose(1, 2)
139
+ k_curr = self.k_proj(x).view(B, T_curr, self.n_kv_heads, self.head_dim).transpose(1, 2)
140
+ v_curr = self.v_proj(x).view(B, T_curr, self.n_kv_heads, self.head_dim).transpose(1, 2)
141
+
142
+ q, k_rotated = apply_rotary_pos_embd(q_curr, k_curr, cos, sin)
143
+
144
+ use_static_cache = (
145
+ block_kv_cache is not None
146
+ and block_kv_cache.get('cache_max_length') is not None
147
+ )
148
+
149
+ static_compile_decode = (
150
+ use_static_cache
151
+ and T_curr == 1
152
+ and isinstance(start_pos, torch.Tensor)
153
+ )
154
+ sdpa_attn_mask_static: torch.Tensor | None = None
155
+
156
+ if static_compile_decode:
157
+ cache_max_length = block_kv_cache['cache_max_length']
158
+ idx = start_pos.unsqueeze(0)
159
+ block_kv_cache['key_cache'].index_copy_(2, idx, k_rotated)
160
+ block_kv_cache['value_cache'].index_copy_(2, idx, v_curr)
161
+
162
+ k = block_kv_cache['key_cache']
163
+ v = block_kv_cache['value_cache']
164
+ T_kv = cache_max_length
165
+
166
+ arange = torch.arange(
167
+ cache_max_length, device=q.device, dtype=torch.long,
168
+ )
169
+ sdpa_attn_mask_static = (arange <= start_pos).view(1, 1, 1, -1)
170
+ elif use_static_cache:
171
+ cache_max_length = block_kv_cache['cache_max_length']
172
+ if block_kv_cache.get('key_cache') is None:
173
+ cache_shape = (B, self.n_kv_heads, cache_max_length, self.head_dim)
174
+ block_kv_cache['key_cache'] = torch.zeros(
175
+ cache_shape, dtype=k_rotated.dtype, device=k_rotated.device,
176
+ )
177
+ block_kv_cache['value_cache'] = torch.zeros(
178
+ cache_shape, dtype=v_curr.dtype, device=v_curr.device,
179
+ )
180
+ new_pos = start_pos + T_curr
181
+ block_kv_cache['key_cache'][:, :, start_pos:new_pos] = k_rotated
182
+ block_kv_cache['value_cache'][:, :, start_pos:new_pos] = v_curr
183
+ k = block_kv_cache['key_cache'][:, :, :new_pos]
184
+ v = block_kv_cache['value_cache'][:, :, :new_pos]
185
+ T_kv = new_pos
186
+ else:
187
+ is_prefill = block_kv_cache is None
188
+ if not is_prefill and block_kv_cache.get('key') is not None:
189
+ k = block_kv_cache['key']
190
+ v = block_kv_cache['value']
191
+ k = torch.cat([k, k_rotated], dim=2)
192
+ v = torch.cat([v, v_curr], dim=2)
193
+ block_kv_cache['key'] = k
194
+ block_kv_cache['value'] = v
195
+ else:
196
+ k = k_rotated
197
+ v = v_curr
198
+ block_kv_cache = {'key': k, 'value': v}
199
+
200
+ if _SDPA_HAS_GQA and self.sdpa and x.device.type != 'mps':
201
+ k_exp = k
202
+ v_exp = v
203
+ else:
204
+ k_exp = k.repeat_interleave(self.n_kv_groups, dim=1)
205
+ v_exp = v.repeat_interleave(self.n_kv_groups, dim=1)
206
+
207
+ T_kv = k_exp.size(2)
208
+
209
+ if self.sdpa and x.device.type != 'mps':
210
+ if sdpa_attn_mask_static is not None:
211
+ sdpa_attn_mask = sdpa_attn_mask_static
212
+ sdpa_is_causal = False
213
+ elif attention_mask is None:
214
+ sdpa_attn_mask = None
215
+ sdpa_is_causal = (T_curr == T_kv and T_curr > 1)
216
+ else:
217
+ if attention_mask.size(-1) < T_kv:
218
+ pad = attention_mask.new_ones(
219
+ attention_mask.size(0), T_kv - attention_mask.size(-1)
220
+ )
221
+ key_padding = torch.cat([attention_mask, pad], dim=-1).bool()
222
+ else:
223
+ key_padding = attention_mask[:, :T_kv].bool()
224
+ if T_curr == T_kv and T_curr > 1:
225
+ causal = torch.ones(
226
+ T_curr, T_kv, dtype=torch.bool, device=q.device,
227
+ ).tril()
228
+ else:
229
+ causal = torch.ones(
230
+ T_curr, T_kv, dtype=torch.bool, device=q.device,
231
+ )
232
+ sdpa_attn_mask = (
233
+ causal.unsqueeze(0).unsqueeze(0)
234
+ & key_padding.unsqueeze(1).unsqueeze(2)
235
+ )
236
+ sdpa_is_causal = False
237
+
238
+ sdpa_kwargs = {
239
+ "attn_mask": sdpa_attn_mask,
240
+ "dropout_p": self.dropout if self.training else 0.0,
241
+ "is_causal": sdpa_is_causal,
242
+ }
243
+ if _SDPA_HAS_GQA and k_exp.size(1) != q.size(1):
244
+ sdpa_kwargs["enable_gqa"] = True
245
+
246
+ y = torch.nn.functional.scaled_dot_product_attention(
247
+ q, k_exp, v_exp,
248
+ **sdpa_kwargs,
249
+ )
250
+ else:
251
+ attn = torch.matmul(q, k_exp.transpose(2, 3)) / math.sqrt(self.head_dim)
252
+ if T_curr == T_kv and T_curr > 1:
253
+ causal_mask_val = torch.tril(
254
+ torch.ones(T_curr, T_curr, device=x.device, dtype=torch.bool),
255
+ ).view(1, 1, T_curr, T_curr)
256
+ attn = attn.masked_fill(~causal_mask_val, float('-inf'))
257
+
258
+ if attention_mask is not None:
259
+ additive = (
260
+ 1.0 - attention_mask[:, :T_kv].unsqueeze(1).unsqueeze(2).float()
261
+ ) * torch.finfo(q.dtype).min
262
+ attn = attn + additive
263
+
264
+ attn = F.softmax(attn, dim=-1)
265
+ attn = self.attn_dropout(attn)
266
+ y = attn @ v_exp
267
+ y = y.transpose(1, 2).contiguous().view(B, T_curr, C)
268
+ y = self.out_proj(y)
269
+ y = self.resid_dropout(y)
270
+
271
+ return y, block_kv_cache
272
+
273
+ class LanguageModelMLP(nn.Module):
274
+
275
+ def __init__(self, cfg):
276
+ super().__init__()
277
+ self.embd_dim = cfg.lm_hidden_dim
278
+ self.inter_dim = cfg.lm_inter_dim
279
+
280
+ self.activation_fn = F.silu
281
+ self.gate_up_proj = nn.Linear(self.embd_dim, 2 * self.inter_dim, bias=False)
282
+ self.down_proj = nn.Linear(self.inter_dim, self.embd_dim, bias=False)
283
+
284
+ self._register_load_state_dict_pre_hook(self._fuse_legacy_gate_up_hook)
285
+
286
+ @staticmethod
287
+ def _fuse_legacy_gate_up_hook(
288
+ state_dict, prefix, local_metadata, strict,
289
+ missing_keys, unexpected_keys, error_msgs,
290
+ ):
291
+
292
+ gate_key = f"{prefix}gate_proj.weight"
293
+ up_key = f"{prefix}up_proj.weight"
294
+ fused_key = f"{prefix}gate_up_proj.weight"
295
+ if (
296
+ gate_key in state_dict
297
+ and up_key in state_dict
298
+ and fused_key not in state_dict
299
+ ):
300
+ gate_w = state_dict.pop(gate_key)
301
+ up_w = state_dict.pop(up_key)
302
+ state_dict[fused_key] = torch.cat([gate_w, up_w], dim=0)
303
+
304
+ def forward(self, x):
305
+
306
+ gate, up = self.gate_up_proj(x).chunk(2, dim=-1)
307
+ return self.down_proj(self.activation_fn(gate) * up)
308
+
309
+ class LanguageModelBlock(nn.Module):
310
+ def __init__(self, cfg):
311
+ super().__init__()
312
+ self.mlp = LanguageModelMLP(cfg)
313
+ self.attn = LanguageModelGroupedQueryAttention(cfg)
314
+ self.norm1 = RMSNorm(cfg)
315
+ self.norm2 = RMSNorm(cfg)
316
+ def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, attention_mask: torch.Tensor=None, block_kv_cache: dict=None, start_pos: int = 0):
317
+
318
+ res = x
319
+ x = self.norm1(x)
320
+ x, block_kv_cache = self.attn(x, cos, sin, attention_mask, block_kv_cache, start_pos=start_pos)
321
+ x = res + x
322
+
323
+ res = x
324
+ x = self.norm2(x)
325
+ x = self.mlp(x)
326
+ x = res + x
327
+
328
+ return x, block_kv_cache
329
+
330
+ class LanguageModel(nn.Module):
331
+ def __init__(self, cfg):
332
+ super().__init__()
333
+ self.cfg = cfg
334
+ self.lm_use_tokens = cfg.lm_use_tokens
335
+ self.lm_tie_weights = cfg.lm_tie_weights
336
+
337
+ self.token_embedding = nn.Embedding(cfg.lm_vocab_size, cfg.lm_hidden_dim)
338
+ self.rotary_embd = RotaryEmbedding(cfg)
339
+ self.blocks = nn.ModuleList([
340
+ LanguageModelBlock(cfg) for _ in range(cfg.lm_n_blocks)
341
+ ])
342
+ self.norm = RMSNorm(cfg)
343
+ self.head = nn.Linear(cfg.lm_hidden_dim, cfg.lm_vocab_size, bias=False)
344
+ if self.lm_tie_weights:
345
+ self.head.weight = self.token_embedding.weight
346
+
347
+ self.apply(self._init_weights)
348
+
349
+ def _init_weights(self, module):
350
+ if isinstance(module, nn.Linear):
351
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
352
+ if module.bias is not None:
353
+ torch.nn.init.zeros_(module.bias)
354
+ elif isinstance(module, nn.Embedding):
355
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
356
+ elif isinstance(module, RMSNorm):
357
+ module.weight.data.fill_(1.0)
358
+
359
+ def forward(self, x: torch.Tensor, attention_mask: torch.Tensor=None, kv_cache: list[dict]=None, start_pos: int=0):
360
+
361
+ if self.lm_use_tokens:
362
+ x = self.token_embedding(x)
363
+
364
+ B, T_curr, _ = x.size()
365
+
366
+ if isinstance(start_pos, torch.Tensor):
367
+ offsets = torch.arange(0, T_curr, device=x.device, dtype=torch.long)
368
+ current_position_ids = (offsets + start_pos).unsqueeze(0).expand(B, -1)
369
+ else:
370
+ current_position_ids = torch.arange(start_pos, start_pos + T_curr, device=x.device).unsqueeze(0).expand(B, -1)
371
+ cos, sin = self.rotary_embd(current_position_ids)
372
+ cos = cos.unsqueeze(1)
373
+ sin = sin.unsqueeze(1)
374
+
375
+ if kv_cache is None:
376
+ kv_cache = [None] * len(self.blocks)
377
+
378
+ for i, block in enumerate(self.blocks):
379
+ x, kv_cache[i] = block(x, cos, sin, attention_mask, kv_cache[i], start_pos=start_pos)
380
+
381
+ x = self.norm(x)
382
+
383
+ if self.lm_use_tokens:
384
+ x = self.head(x)
385
+
386
+ return x, kv_cache
387
+
388
+ @torch.inference_mode()
389
+ def generate(self, inputs: torch.Tensor, max_new_tokens: int=20):
390
+
391
+ if inputs.dim() == 1:
392
+ inputs = inputs.unsqueeze(0)
393
+ generated_outputs = inputs.clone()
394
+
395
+ prompt_output, kv_cache_list = self.forward(
396
+ generated_outputs,
397
+ attention_mask=None,
398
+ kv_cache=None,
399
+ start_pos=0
400
+ )
401
+ last_output = prompt_output[:, -1, :]
402
+
403
+ for i in range(max_new_tokens):
404
+ if self.lm_use_tokens:
405
+ next_output = torch.argmax(last_output, dim=-1, keepdim=True)
406
+ else:
407
+ next_output = last_output.unsqueeze(1)
408
+
409
+ generated_outputs = torch.cat((generated_outputs, next_output), dim=1)
410
+ current_token_start_pos = generated_outputs.size(1) - 1
411
+
412
+ if i == max_new_tokens - 1:
413
+ break
414
+
415
+ decode_step_output, kv_cache_list = self.forward(
416
+ next_output,
417
+ attention_mask=None,
418
+ kv_cache=kv_cache_list,
419
+ start_pos=current_token_start_pos
420
+ )
421
+ last_output = decode_step_output[:, -1, :]
422
+ return generated_outputs
423
+
424
+ @classmethod
425
+ def from_pretrained(cls, cfg):
426
+ from transformers import AutoConfig
427
+ from huggingface_hub import hf_hub_download
428
+ import safetensors
429
+ import torch.nn.init as init
430
+ import json
431
+ from huggingface_hub.utils import EntryNotFoundError
432
+ hf_config = AutoConfig.from_pretrained(cfg.lm_model_type)
433
+ original_vocab_size = hf_config.vocab_size
434
+ cfg.lm_hidden_dim = hf_config.hidden_size
435
+ cfg.lm_inter_dim = hf_config.intermediate_size
436
+ cfg.lm_rms_eps = hf_config.rms_norm_eps
437
+ cfg.lm_re_base = hf_config.rope_theta
438
+ cfg.lm_max_position_embeddings = hf_config.max_position_embeddings
439
+ if hasattr(cfg, 'lm_vocab_size'):
440
+ if cfg.lm_vocab_size < original_vocab_size:
441
+ raise ValueError(f"Config vocab size ({cfg.lm_vocab_size}) is smaller than pretrained model vocab size ({original_vocab_size})")
442
+ else:
443
+ cfg.lm_vocab_size = original_vocab_size
444
+ cfg.lm_n_heads = hf_config.num_attention_heads
445
+ cfg.lm_n_kv_heads = hf_config.num_key_value_heads
446
+ cfg.lm_dropout = hf_config.attention_dropout
447
+ cfg.lm_n_blocks = hf_config.num_hidden_layers
448
+ model = cls(cfg)
449
+ try:
450
+ index_path = hf_hub_download(repo_id=cfg.lm_model_type, filename="model.safetensors.index.json")
451
+ with open(index_path, 'r') as f:
452
+ index = json.load(f)
453
+ safetensors_filenames = sorted(list(set(index['weight_map'].values())))
454
+ safetensors_files = [hf_hub_download(repo_id=cfg.lm_model_type, filename=fn) for fn in safetensors_filenames]
455
+ except EntryNotFoundError:
456
+ safetensors_files = [hf_hub_download(repo_id=cfg.lm_model_type, filename="model.safetensors")]
457
+
458
+ sd = model.state_dict()
459
+ mapping = {
460
+ 'model.embed_tokens.weight': 'token_embedding.weight',
461
+ 'model.norm.weight': 'norm.weight'
462
+ }
463
+ for i in range(cfg.lm_n_blocks):
464
+ layer_prefix = f'model.layers.{i}.'
465
+ block_prefix = f'blocks.{i}.'
466
+ mapping.update({
467
+ f"{layer_prefix}self_attn.q_proj.weight": f"{block_prefix}attn.q_proj.weight",
468
+ f"{layer_prefix}self_attn.k_proj.weight": f"{block_prefix}attn.k_proj.weight",
469
+ f"{layer_prefix}self_attn.v_proj.weight": f"{block_prefix}attn.v_proj.weight",
470
+ f"{layer_prefix}self_attn.o_proj.weight": f"{block_prefix}attn.out_proj.weight",
471
+ f"{layer_prefix}mlp.down_proj.weight": f"{block_prefix}mlp.down_proj.weight",
472
+ f"{layer_prefix}input_layernorm.weight": f"{block_prefix}norm1.weight",
473
+ f"{layer_prefix}post_attention_layernorm.weight": f"{block_prefix}norm2.weight"
474
+ })
475
+ has_extended_embeddings = False
476
+ loaded_keys = set()
477
+ for safetensors_file in safetensors_files:
478
+ with safetensors.safe_open(filename=safetensors_file, framework="pt", device="cpu") as f:
479
+ for hf_key, our_key in mapping.items():
480
+ if our_key in loaded_keys:
481
+ continue
482
+ if hf_key in f.keys() and our_key in sd:
483
+ tensor = f.get_tensor(hf_key)
484
+ if hf_key == 'model.embed_tokens.weight' and tensor.shape[0] != sd[our_key].shape[0]:
485
+ has_extended_embeddings = True
486
+ print(f"Extending token embeddings from {tensor.shape} to {sd[our_key].shape}")
487
+ sd[our_key][:tensor.shape[0]].copy_(tensor)
488
+ std = 0.02
489
+ init.normal_(sd[our_key][tensor.shape[0]:], mean=0.0, std=std)
490
+ print(f"Initialized {sd[our_key].shape[0] - tensor.shape[0]} new token embeddings")
491
+ sd['head.weight'].copy_(sd[our_key])
492
+ elif tensor.shape == sd[our_key].shape:
493
+ sd[our_key].copy_(tensor)
494
+ else:
495
+ print(f"Shape mismatch for {hf_key} -> {our_key}: {tensor.shape} vs {sd[our_key].shape}")
496
+ loaded_keys.add(our_key)
497
+
498
+ for hf_key, our_key in mapping.items():
499
+ if our_key not in loaded_keys:
500
+ if our_key in sd:
501
+ print(f"Warning: Key {our_key} not found in any safetensors file (HF key: {hf_key})")
502
+
503
+ for i in range(cfg.lm_n_blocks):
504
+ layer_prefix = f"model.layers.{i}."
505
+ block_prefix = f"blocks.{i}."
506
+ fused_param_key = f"{block_prefix}mlp.gate_up_proj.weight"
507
+ if fused_param_key not in sd or fused_param_key in loaded_keys:
508
+ continue
509
+ gate_hf_key = f"{layer_prefix}mlp.gate_proj.weight"
510
+ up_hf_key = f"{layer_prefix}mlp.up_proj.weight"
511
+ gate_w = None
512
+ up_w = None
513
+ for safetensors_file in safetensors_files:
514
+ with safetensors.safe_open(filename=safetensors_file, framework="pt", device="cpu") as f:
515
+ keys = f.keys()
516
+ if gate_w is None and gate_hf_key in keys:
517
+ gate_w = f.get_tensor(gate_hf_key)
518
+ if up_w is None and up_hf_key in keys:
519
+ up_w = f.get_tensor(up_hf_key)
520
+ if gate_w is not None and up_w is not None:
521
+ break
522
+ if gate_w is None or up_w is None:
523
+ print(
524
+ f"Warning: gate_proj or up_proj missing for layer {i} "
525
+ f"(gate={gate_w is not None}, up={up_w is not None}); "
526
+ f"{fused_param_key} left at init values."
527
+ )
528
+ continue
529
+ fused = torch.cat([gate_w, up_w], dim=0)
530
+ if fused.shape != sd[fused_param_key].shape:
531
+ print(
532
+ f"Shape mismatch for fused gate_up at layer {i}: "
533
+ f"{fused.shape} vs {sd[fused_param_key].shape}"
534
+ )
535
+ continue
536
+ sd[fused_param_key].copy_(fused)
537
+ loaded_keys.add(fused_param_key)
538
+
539
+ model.load_state_dict(sd)
540
+ if has_extended_embeddings and hasattr(model, 'head') and 'head.weight' in sd:
541
+ lm_head_loaded = False
542
+ for safetensors_file in safetensors_files:
543
+ with safetensors.safe_open(filename=safetensors_file, framework="pt", device="cpu") as f:
544
+ if 'lm_head.weight' in f.keys():
545
+ lm_head = f.get_tensor('lm_head.weight')
546
+ if lm_head.shape[0] != sd['head.weight'].shape[0]:
547
+ print(f"Extending LM head from {lm_head.shape} to {sd['head.weight'].shape}")
548
+ sd['head.weight'][:lm_head.shape[0]].copy_(lm_head)
549
+ std = 0.02
550
+ init.normal_(sd['head.weight'][lm_head.shape[0]:], mean=0.0, std=std)
551
+ model.load_state_dict(sd)
552
+ lm_head_loaded = True
553
+ break
554
+ if cfg.lm_tie_weights and hasattr(model, 'head') and hasattr(model, 'token_embedding'):
555
+ model.head.weight = model.token_embedding.weight
556
+ print(f"Successfully loaded {cfg.lm_model_type} weights from safetensors. Model has {sum(p.numel() for p in model.parameters()):,} parameters.")
557
+ return model
modality_projector.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+
3
+ class ModalityProjector(nn.Module):
4
+ def __init__(self, cfg):
5
+ super().__init__()
6
+ self.cfg = cfg
7
+ self.input_dim = cfg.vit_hidden_dim * (cfg.mp_pixel_shuffle_factor**2)
8
+ self.output_dim = cfg.lm_hidden_dim
9
+ self.scale_factor = cfg.mp_pixel_shuffle_factor
10
+
11
+ self.proj = nn.Linear(self.input_dim, self.output_dim, bias=False)
12
+ self.apply(self._init_weights)
13
+
14
+ def _init_weights(self, module):
15
+ if isinstance(module, nn.Linear):
16
+ nn.init.normal_(self.proj.weight, mean=0.0, std=0.02)
17
+ if module.bias is not None:
18
+ nn.init.zeros_(module.bias)
19
+
20
+ def pixel_shuffle(self, x):
21
+ bsz, seq, embed_dim = x.size()
22
+ seq_root = int(seq**0.5)
23
+ assert seq_root**2 == seq
24
+ assert seq_root % self.scale_factor == 0
25
+
26
+ height = width = seq_root
27
+ x = x.view(bsz, height, width, embed_dim)
28
+ h_out = height // self.scale_factor
29
+ w_out = width // self.scale_factor
30
+ x = x.reshape(bsz, h_out, self.scale_factor, w_out, self.scale_factor, embed_dim)
31
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous()
32
+ x = x.reshape(bsz, h_out * w_out, embed_dim * self.scale_factor**2)
33
+ return x
34
+
35
+ def forward(self, x):
36
+ x = self.pixel_shuffle(x)
37
+ x = self.proj(x)
38
+
39
+ return x
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5b5ddf088789717db7d7bc25d8ddd88ee4bf678ff36dfe28ca5d760f79d1e3fa
3
+ size 2029498200
model_utils.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+
4
+ def top_k_top_p_filtering(logits, top_k=0, top_p=1.0, filter_value=-float('Inf')):
5
+ top_k = min(top_k, logits.size(-1))
6
+
7
+ if top_k > 0:
8
+ indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
9
+ logits = logits.masked_fill(indices_to_remove, filter_value)
10
+
11
+ if top_p < 1.0:
12
+ sorted_logits, sorted_indices = torch.sort(logits, descending=True)
13
+ cumulative_probs = torch.softmax(sorted_logits, dim=-1).cumsum(dim=-1)
14
+
15
+ sorted_indices_to_remove = cumulative_probs > top_p
16
+
17
+ sorted_indices_to_remove[..., 0] = False
18
+ indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
19
+ logits = logits.masked_fill(indices_to_remove, filter_value)
20
+
21
+ return logits
modeling_visionpsynano.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HuggingFace PreTrainedModel wrapper for VisionPsyNano."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from typing import Optional
6
+
7
+ import torch
8
+ from transformers import PreTrainedModel
9
+
10
+
11
+ try:
12
+ from .configuration_visionpsynano import VisionPsyNanoConfig
13
+ except ImportError:
14
+ from configuration_visionpsynano import VisionPsyNanoConfig
15
+
16
+ try:
17
+ from .processors import get_tokenizer
18
+ from .language_model import LanguageModel
19
+ from .modality_projector import ModalityProjector
20
+ from .vision_language_model import VisionLanguageModel
21
+ from .vision_transformer import ViT
22
+ except ImportError as exc:
23
+ raise ImportError(
24
+ "Failed to import VisionPsyNano core modules (models/, data/). "
25
+ "For Hub packages use scripts/package_hub_repo.py so imports are flattened."
26
+ ) from exc
27
+
28
+
29
+ class VisionPsyNanoForConditionalGeneration(PreTrainedModel):
30
+ config_class = VisionPsyNanoConfig
31
+ base_model_prefix = ""
32
+ _no_split_modules = ["ViTBlock", "LanguageModelBlock", "ModalityProjector"]
33
+ main_input_name = "input_ids"
34
+ supports_gradient_checkpointing = False
35
+
36
+ def __init__(self, config: VisionPsyNanoConfig):
37
+ super().__init__(config)
38
+ self.cfg = config.to_vlm_config()
39
+ self.vision_encoder = ViT(self.cfg)
40
+ self.decoder = LanguageModel(self.cfg)
41
+ self.MP = ModalityProjector(self.cfg)
42
+ self.tokenizer = get_tokenizer(
43
+ self.cfg.lm_tokenizer,
44
+ self.cfg.vlm_extra_tokens,
45
+ self.cfg.lm_chat_template,
46
+ )
47
+ self._sync_image_token_id()
48
+ self._compiled_decoder = None
49
+ self._compiled_vision_encoder = None
50
+ self.load_backbone = False
51
+ self.post_init()
52
+ self.tie_weights()
53
+
54
+ def _sync_image_token_id(self) -> None:
55
+ tok = self.tokenizer
56
+ if hasattr(tok, "image_token"):
57
+ tok.image_token_id = tok.convert_tokens_to_ids(tok.image_token)
58
+ elif "<|image|>" in getattr(tok, "get_vocab", lambda: {})():
59
+ tok.image_token = "<|image|>"
60
+ tok.image_token_id = tok.convert_tokens_to_ids("<|image|>")
61
+
62
+ def set_tokenizer(self, tokenizer) -> None:
63
+ self.tokenizer = tokenizer
64
+ self._sync_image_token_id()
65
+
66
+ def _reinit_rotary_cache(self) -> None:
67
+ re = getattr(self.decoder, "rotary_embd", None)
68
+ if re is None or not hasattr(re, "extend_cache"):
69
+ return
70
+ cos = getattr(re, "cos_cached", None)
71
+ if cos is None or getattr(cos, "is_meta", False) or cos.device.type == "meta":
72
+ return
73
+ length = int(getattr(re, "original_max_seq_len", None) or re.max_seq_len)
74
+ re.extend_cache(max(length, 1))
75
+ if bool(torch.isnan(re.cos_cached).any()) or bool(torch.isnan(re.sin_cached).any()):
76
+ device = re.inv_freq.device
77
+ positions = torch.arange(length, dtype=torch.float, device=device)
78
+ freqs = positions.unsqueeze(-1) * re.inv_freq.unsqueeze(0)
79
+ emb = torch.cat([freqs, freqs], dim=-1)
80
+ re.cos_cached = emb.cos() * re.attention_scaling
81
+ re.sin_cached = emb.sin() * re.attention_scaling
82
+ re.original_max_seq_len = length
83
+
84
+ def tie_weights(self, recompute_mapping: bool = True, **kwargs):
85
+ _ = recompute_mapping, kwargs
86
+ if not getattr(self.cfg, "lm_tie_weights", True):
87
+ return
88
+ head_w = self.decoder.head.weight
89
+ emb_w = self.decoder.token_embedding.weight
90
+ if head_w.data_ptr() != emb_w.data_ptr():
91
+ with torch.no_grad():
92
+ emb_w.copy_(head_w)
93
+ self.decoder.head.weight = self.decoder.token_embedding.weight
94
+
95
+ def _tie_weights(self):
96
+ self.tie_weights()
97
+
98
+ def _repair_legacy_checkpoint_weights(
99
+ self, pretrained_model_name_or_path, *, revision: Optional[str] = None
100
+ ) -> None:
101
+ weights_path = None
102
+ if os.path.isdir(pretrained_model_name_or_path):
103
+ candidate = os.path.join(pretrained_model_name_or_path, "model.safetensors")
104
+ if os.path.isfile(candidate):
105
+ weights_path = candidate
106
+ else:
107
+ try:
108
+ from huggingface_hub import hf_hub_download
109
+
110
+ weights_path = hf_hub_download(
111
+ pretrained_model_name_or_path,
112
+ "model.safetensors",
113
+ revision=revision,
114
+ )
115
+ except Exception:
116
+ return
117
+ if not weights_path:
118
+ return
119
+
120
+ from safetensors import safe_open
121
+
122
+ with safe_open(weights_path, framework="pt", device="cpu") as f:
123
+ keys = set(f.keys())
124
+ for i, block in enumerate(self.decoder.blocks):
125
+ gate_key = f"decoder.blocks.{i}.mlp.gate_proj.weight"
126
+ up_key = f"decoder.blocks.{i}.mlp.up_proj.weight"
127
+ fused_key = f"decoder.blocks.{i}.mlp.gate_up_proj.weight"
128
+ if gate_key in keys and up_key in keys and fused_key not in keys:
129
+ fused = torch.cat([f.get_tensor(gate_key), f.get_tensor(up_key)], dim=0)
130
+ param = block.mlp.gate_up_proj.weight
131
+ param.data.copy_(fused.to(device=param.device, dtype=param.dtype))
132
+ if (
133
+ "decoder.token_embedding.weight" not in keys
134
+ and "decoder.head.weight" in keys
135
+ ):
136
+ head = f.get_tensor("decoder.head.weight")
137
+ emb = self.decoder.token_embedding.weight
138
+ emb.data.copy_(head.to(device=emb.device, dtype=emb.dtype))
139
+
140
+ def _vision_encoder_for_prefill(self):
141
+ return VisionLanguageModel._vision_encoder_for_prefill(self)
142
+
143
+ def _decoder_for_decode(self):
144
+ return VisionLanguageModel._decoder_for_decode(self)
145
+
146
+ def _replace_img_tokens_with_embd(self, input_ids, token_embd, image_embd):
147
+ return VisionLanguageModel._replace_img_tokens_with_embd(
148
+ self, input_ids, token_embd, image_embd
149
+ )
150
+
151
+ def _process_images(self, images, device):
152
+ return VisionLanguageModel._process_images(self, images, device)
153
+
154
+ def apply_deploy_profile(self, device: Optional[torch.device] = None) -> None:
155
+ try:
156
+ from .runtime_profile import apply_deploy_profile
157
+ except ImportError:
158
+ from runtime_profile import apply_deploy_profile
159
+
160
+ apply_deploy_profile(self, device or next(self.parameters()).device)
161
+
162
+ def apply_eager_profile(self) -> None:
163
+ try:
164
+ from .runtime_profile import apply_eager_profile
165
+ except ImportError:
166
+ from runtime_profile import apply_eager_profile
167
+
168
+ apply_eager_profile(self)
169
+
170
+ def forward(
171
+ self,
172
+ input_ids: Optional[torch.LongTensor] = None,
173
+ images=None,
174
+ pixel_values=None,
175
+ attention_mask: Optional[torch.Tensor] = None,
176
+ labels: Optional[torch.LongTensor] = None,
177
+ targets: Optional[torch.LongTensor] = None,
178
+ **kwargs,
179
+ ):
180
+ images = images if images is not None else pixel_values
181
+ targets = targets if targets is not None else labels
182
+ return VisionLanguageModel.forward(
183
+ self, input_ids, images, attention_mask=attention_mask, targets=targets
184
+ )
185
+
186
+ @torch.inference_mode()
187
+ def generate(
188
+ self,
189
+ input_ids: Optional[torch.LongTensor] = None,
190
+ images=None,
191
+ pixel_values=None,
192
+ attention_mask: Optional[torch.Tensor] = None,
193
+ max_new_tokens: int = 5,
194
+ top_k: int = 50,
195
+ top_p: float = 0.9,
196
+ temperature: float = 0.5,
197
+ greedy: bool = False,
198
+ **kwargs,
199
+ ):
200
+ images = images if images is not None else pixel_values
201
+ _ = kwargs
202
+ return VisionLanguageModel.generate(
203
+ self,
204
+ input_ids,
205
+ images,
206
+ attention_mask=attention_mask,
207
+ max_new_tokens=max_new_tokens,
208
+ top_k=top_k,
209
+ top_p=top_p,
210
+ temperature=temperature,
211
+ greedy=greedy,
212
+ )
213
+
214
+ @classmethod
215
+ def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
216
+ model = super().from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
217
+ model._repair_legacy_checkpoint_weights(
218
+ pretrained_model_name_or_path, revision=kwargs.get("revision")
219
+ )
220
+ model.tie_weights()
221
+ model._reinit_rotary_cache()
222
+ try:
223
+ from transformers import AutoTokenizer
224
+
225
+ try:
226
+ from .processing_visionpsynano import VisionPsyNanoProcessor
227
+ except ImportError:
228
+ from processing_visionpsynano import VisionPsyNanoProcessor
229
+
230
+ tok = AutoTokenizer.from_pretrained(
231
+ pretrained_model_name_or_path,
232
+ trust_remote_code=kwargs.get("trust_remote_code", False),
233
+ )
234
+ VisionPsyNanoProcessor._attach_extra_token_attrs(tok, model.config.vlm_extra_tokens)
235
+ model.set_tokenizer(tok)
236
+ except Exception:
237
+ model._sync_image_token_id()
238
+ return model
239
+
240
+ @classmethod
241
+ def from_legacy_pretrained(
242
+ cls,
243
+ repo_id_or_path: str,
244
+ *,
245
+ is_flash: Optional[bool] = None,
246
+ variant: Optional[str] = None,
247
+ revision: Optional[str] = None,
248
+ **kwargs,
249
+ ) -> "VisionPsyNanoForConditionalGeneration":
250
+ import json
251
+
252
+ from huggingface_hub import hf_hub_download
253
+ from safetensors.torch import load_model
254
+
255
+ if os.path.isdir(repo_id_or_path):
256
+ config_path = os.path.join(repo_id_or_path, "config.json")
257
+ weights_path = os.path.join(repo_id_or_path, "model.safetensors")
258
+ else:
259
+ config_path = hf_hub_download(
260
+ repo_id=repo_id_or_path, filename="config.json", revision=revision
261
+ )
262
+ weights_path = hf_hub_download(
263
+ repo_id=repo_id_or_path, filename="model.safetensors", revision=revision
264
+ )
265
+
266
+ with open(config_path, "r") as f:
267
+ raw = json.load(f)
268
+
269
+ config = VisionPsyNanoConfig.from_legacy_dict(
270
+ raw, is_flash=is_flash, variant=variant
271
+ )
272
+ model = cls(config)
273
+ load_model(model, weights_path)
274
+ model.tie_weights()
275
+ model._reinit_rotary_cache()
276
+ tok_dir = repo_id_or_path if os.path.isdir(repo_id_or_path) else os.path.dirname(config_path)
277
+ if any(
278
+ os.path.exists(os.path.join(tok_dir, n))
279
+ for n in ("tokenizer.json", "tokenizer_config.json")
280
+ ):
281
+ from transformers import AutoTokenizer
282
+
283
+ try:
284
+ from .processing_visionpsynano import VisionPsyNanoProcessor
285
+ except ImportError:
286
+ from processing_visionpsynano import VisionPsyNanoProcessor
287
+
288
+ tok = AutoTokenizer.from_pretrained(tok_dir)
289
+ VisionPsyNanoProcessor._attach_extra_token_attrs(tok, config.vlm_extra_tokens)
290
+ model.set_tokenizer(tok)
291
+ return model
292
+
293
+
294
+ try:
295
+ from transformers import AutoConfig, AutoModel, AutoModelForImageTextToText
296
+
297
+ AutoConfig.register("visionpsynano", VisionPsyNanoConfig)
298
+ AutoModel.register(VisionPsyNanoConfig, VisionPsyNanoForConditionalGeneration)
299
+ AutoModelForImageTextToText.register(VisionPsyNanoConfig, VisionPsyNanoForConditionalGeneration)
300
+ except Exception:
301
+ pass
preprocessor_config.json ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "processor_class": "VisionPsyNanoProcessor",
3
+ "auto_map": {
4
+ "AutoProcessor": "processing_visionpsynano.VisionPsyNanoProcessor"
5
+ },
6
+ "is_flash": false,
7
+ "vit_img_size": 512,
8
+ "max_img_size": 2048,
9
+ "inference_max_img_size": null,
10
+ "resize_to_max_side_len": true,
11
+ "resize_min_side_len": null,
12
+ "mp_image_token_length": 64,
13
+ "lm_max_position_embeddings": 8192,
14
+ "lm_tokenizer": "HuggingFaceTB/SmolLM2-360M-Instruct",
15
+ "lm_chat_template": "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}",
16
+ "vlm_extra_tokens": {
17
+ "image_token": "<|image|>",
18
+ "global_image_token": "<|global_image|>",
19
+ "r1c1": "<row_1_col_1>",
20
+ "r1c2": "<row_1_col_2>",
21
+ "r1c3": "<row_1_col_3>",
22
+ "r1c4": "<row_1_col_4>",
23
+ "r1c5": "<row_1_col_5>",
24
+ "r1c6": "<row_1_col_6>",
25
+ "r1c7": "<row_1_col_7>",
26
+ "r1c8": "<row_1_col_8>",
27
+ "r2c1": "<row_2_col_1>",
28
+ "r2c2": "<row_2_col_2>",
29
+ "r2c3": "<row_2_col_3>",
30
+ "r2c4": "<row_2_col_4>",
31
+ "r2c5": "<row_2_col_5>",
32
+ "r2c6": "<row_2_col_6>",
33
+ "r2c7": "<row_2_col_7>",
34
+ "r2c8": "<row_2_col_8>",
35
+ "r3c1": "<row_3_col_1>",
36
+ "r3c2": "<row_3_col_2>",
37
+ "r3c3": "<row_3_col_3>",
38
+ "r3c4": "<row_3_col_4>",
39
+ "r3c5": "<row_3_col_5>",
40
+ "r3c6": "<row_3_col_6>",
41
+ "r3c7": "<row_3_col_7>",
42
+ "r3c8": "<row_3_col_8>",
43
+ "r4c1": "<row_4_col_1>",
44
+ "r4c2": "<row_4_col_2>",
45
+ "r4c3": "<row_4_col_3>",
46
+ "r4c4": "<row_4_col_4>",
47
+ "r4c5": "<row_4_col_5>",
48
+ "r4c6": "<row_4_col_6>",
49
+ "r4c7": "<row_4_col_7>",
50
+ "r4c8": "<row_4_col_8>",
51
+ "r5c1": "<row_5_col_1>",
52
+ "r5c2": "<row_5_col_2>",
53
+ "r5c3": "<row_5_col_3>",
54
+ "r5c4": "<row_5_col_4>",
55
+ "r5c5": "<row_5_col_5>",
56
+ "r5c6": "<row_5_col_6>",
57
+ "r5c7": "<row_5_col_7>",
58
+ "r5c8": "<row_5_col_8>",
59
+ "r6c1": "<row_6_col_1>",
60
+ "r6c2": "<row_6_col_2>",
61
+ "r6c3": "<row_6_col_3>",
62
+ "r6c4": "<row_6_col_4>",
63
+ "r6c5": "<row_6_col_5>",
64
+ "r6c6": "<row_6_col_6>",
65
+ "r6c7": "<row_6_col_7>",
66
+ "r6c8": "<row_6_col_8>",
67
+ "r7c1": "<row_7_col_1>",
68
+ "r7c2": "<row_7_col_2>",
69
+ "r7c3": "<row_7_col_3>",
70
+ "r7c4": "<row_7_col_4>",
71
+ "r7c5": "<row_7_col_5>",
72
+ "r7c6": "<row_7_col_6>",
73
+ "r7c7": "<row_7_col_7>",
74
+ "r7c8": "<row_7_col_8>",
75
+ "r8c1": "<row_8_col_1>",
76
+ "r8c2": "<row_8_col_2>",
77
+ "r8c3": "<row_8_col_3>",
78
+ "r8c4": "<row_8_col_4>",
79
+ "r8c5": "<row_8_col_5>",
80
+ "r8c6": "<row_8_col_6>",
81
+ "r8c7": "<row_8_col_7>",
82
+ "r8c8": "<row_8_col_8>"
83
+ }
84
+ }
processing_visionpsynano.py ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HuggingFace processor for VisionPsyNano."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ from typing import Any, List, Optional, Union
7
+
8
+ import torch
9
+ from PIL import Image
10
+ from transformers import AutoTokenizer, BatchFeature, ProcessorMixin
11
+ from transformers.utils import logging
12
+
13
+
14
+ try:
15
+ from .configuration_visionpsynano import (
16
+ VisionPsyNanoConfig,
17
+ apply_flash_preprocess,
18
+ resolve_is_flash,
19
+ )
20
+ except ImportError:
21
+ from configuration_visionpsynano import (
22
+ VisionPsyNanoConfig,
23
+ apply_flash_preprocess,
24
+ resolve_is_flash,
25
+ )
26
+
27
+ try:
28
+ from .processors import get_image_processor, get_image_string, get_tokenizer
29
+ except ImportError as exc:
30
+ raise ImportError(
31
+ "Failed to import VisionPsyNano data processors. "
32
+ "For Hub packages use scripts/package_hub_repo.py so imports are flattened."
33
+ ) from exc
34
+
35
+ logger = logging.get_logger(__name__)
36
+
37
+
38
+ def _as_pil_list(images) -> List[Image.Image]:
39
+ if images is None:
40
+ return []
41
+ if isinstance(images, Image.Image):
42
+ return [images.convert("RGB")]
43
+ if isinstance(images, (list, tuple)):
44
+ out = []
45
+ for im in images:
46
+ if isinstance(im, Image.Image):
47
+ out.append(im.convert("RGB"))
48
+ else:
49
+ raise TypeError(f"Expected PIL.Image, got {type(im)}")
50
+ return out
51
+ raise TypeError(f"Unsupported images type: {type(images)}")
52
+
53
+
54
+ def _extract_user_text(text: Any) -> str:
55
+ """Normalize text / chat messages into a single user prompt string."""
56
+ if text is None:
57
+ return ""
58
+ if isinstance(text, str):
59
+ return text
60
+ if isinstance(text, list) and text and isinstance(text[0], dict) and "role" in text[0]:
61
+ parts = []
62
+ for msg in text:
63
+ content = msg.get("content", "")
64
+ if isinstance(content, str):
65
+ parts.append(content)
66
+ elif isinstance(content, list):
67
+ for block in content:
68
+ if isinstance(block, str):
69
+ parts.append(block)
70
+ elif isinstance(block, dict) and block.get("type") == "text":
71
+ parts.append(block.get("text", ""))
72
+ return " ".join(p for p in parts if p).strip()
73
+ if isinstance(text, list) and all(isinstance(t, str) for t in text):
74
+ if len(text) == 1:
75
+ return text[0]
76
+ raise ValueError("Batch text list with >1 items is not supported; call per sample.")
77
+ raise TypeError(f"Unsupported text type: {type(text)}")
78
+
79
+
80
+ class VisionPsyNanoProcessor(ProcessorMixin):
81
+ """Tokenizer + VisionPsyNano image preprocessing (default or Flash)."""
82
+
83
+ attributes = ["tokenizer"]
84
+ tokenizer_class = "AutoTokenizer"
85
+
86
+ def __init__(self, tokenizer, chat_template=None, **kwargs):
87
+ image_keys = {
88
+ "is_flash",
89
+ "variant",
90
+ "vit_img_size",
91
+ "max_img_size",
92
+ "inference_max_img_size",
93
+ "resize_to_max_side_len",
94
+ "resize_min_side_len",
95
+ "mp_image_token_length",
96
+ "lm_max_position_embeddings",
97
+ "vlm_extra_tokens",
98
+ "lm_tokenizer",
99
+ "lm_chat_template",
100
+ }
101
+ image_cfg = {k: kwargs.pop(k) for k in list(kwargs) if k in image_keys}
102
+ super().__init__(tokenizer, chat_template=chat_template, **kwargs)
103
+
104
+ flash = resolve_is_flash(
105
+ is_flash=image_cfg.get("is_flash"),
106
+ variant=image_cfg.get("variant"),
107
+ resize_to_max_side_len=image_cfg.get("resize_to_max_side_len"),
108
+ )
109
+ resize_to_max, resize_min = apply_flash_preprocess(
110
+ is_flash=flash,
111
+ resize_to_max_side_len=image_cfg.get("resize_to_max_side_len"),
112
+ resize_min_side_len=image_cfg.get("resize_min_side_len"),
113
+ )
114
+ self.is_flash = flash
115
+ self.vit_img_size = int(image_cfg.get("vit_img_size", 512))
116
+ self.max_img_size = int(image_cfg.get("max_img_size", 2048))
117
+ self.inference_max_img_size = image_cfg.get("inference_max_img_size")
118
+ self.resize_to_max_side_len = resize_to_max
119
+ self.resize_min_side_len = resize_min
120
+ self.mp_image_token_length = int(image_cfg.get("mp_image_token_length", 64))
121
+ self.lm_max_position_embeddings = int(
122
+ image_cfg.get("lm_max_position_embeddings", 8192)
123
+ )
124
+ self.vlm_extra_tokens = image_cfg.get("vlm_extra_tokens")
125
+ self.lm_tokenizer = image_cfg.get("lm_tokenizer")
126
+ self.lm_chat_template = image_cfg.get("lm_chat_template")
127
+
128
+ def _effective_max_img_size(self) -> int:
129
+ return int(self.inference_max_img_size or self.max_img_size)
130
+
131
+ def _build_image_processor(self):
132
+ return get_image_processor(
133
+ self._effective_max_img_size(),
134
+ self.vit_img_size,
135
+ self.resize_to_max_side_len,
136
+ self.resize_min_side_len,
137
+ )
138
+
139
+ def set_flash(self, enabled: bool = True) -> None:
140
+ """Enable/disable Flash optimized image preprocessing in-place."""
141
+ self.is_flash = bool(enabled)
142
+ self.resize_to_max_side_len, self.resize_min_side_len = apply_flash_preprocess(
143
+ is_flash=self.is_flash,
144
+ resize_to_max_side_len=None,
145
+ resize_min_side_len=self.resize_min_side_len if self.is_flash else None,
146
+ )
147
+
148
+ def __call__(
149
+ self,
150
+ images: Optional[Union[Image.Image, List[Image.Image]]] = None,
151
+ text: Optional[Any] = None,
152
+ return_tensors: Optional[str] = "pt",
153
+ is_flash: Optional[bool] = None,
154
+ **kwargs,
155
+ ) -> BatchFeature:
156
+ if is_flash is not None:
157
+ self.set_flash(bool(is_flash))
158
+
159
+ prompt = _extract_user_text(text)
160
+ pil_images = _as_pil_list(images)
161
+
162
+ image_processor = self._build_image_processor()
163
+ processed_tensors = []
164
+ ratios = []
165
+ for img in pil_images:
166
+ processed_image, splitted_image_ratio = image_processor(img)
167
+ if (
168
+ not hasattr(self.tokenizer, "global_image_token")
169
+ and splitted_image_ratio[0] * splitted_image_ratio[1]
170
+ == len(processed_image) - 1
171
+ ):
172
+ processed_image = processed_image[1:]
173
+ processed_tensors.append(processed_image)
174
+ ratios.append(splitted_image_ratio)
175
+
176
+ if processed_tensors:
177
+ image_string = get_image_string(
178
+ self.tokenizer, ratios, self.mp_image_token_length
179
+ )
180
+ images_tensor = torch.cat(processed_tensors, dim=0)
181
+ else:
182
+ image_string = ""
183
+ images_tensor = None
184
+
185
+ messages = [{"role": "user", "content": image_string + prompt}]
186
+ full_prompt = self.tokenizer.apply_chat_template(
187
+ [messages], tokenize=False, add_generation_prompt=True
188
+ )
189
+ if isinstance(full_prompt, list):
190
+ full_prompt = full_prompt[0]
191
+
192
+ encoded = self.tokenizer(
193
+ [full_prompt],
194
+ return_tensors=return_tensors,
195
+ padding=False,
196
+ truncation=True,
197
+ max_length=self.lm_max_position_embeddings,
198
+ **{k: v for k, v in kwargs.items() if k in ("padding", "truncation", "max_length")},
199
+ )
200
+
201
+ data = {
202
+ "input_ids": encoded["input_ids"],
203
+ "attention_mask": encoded.get("attention_mask"),
204
+ }
205
+ if images_tensor is not None:
206
+ data["images"] = images_tensor
207
+ data["pixel_values"] = images_tensor # HF-friendly alias
208
+ return BatchFeature(data=data, tensor_type=return_tensors)
209
+
210
+ def batch_decode(self, *args, **kwargs):
211
+ return self.tokenizer.batch_decode(*args, **kwargs)
212
+
213
+ def decode(self, *args, **kwargs):
214
+ return self.tokenizer.decode(*args, **kwargs)
215
+
216
+ def image_config_dict(self) -> dict:
217
+ return {
218
+ "processor_class": "VisionPsyNanoProcessor",
219
+ "auto_map": {
220
+ "AutoProcessor": "processing_visionpsynano.VisionPsyNanoProcessor",
221
+ },
222
+ "is_flash": self.is_flash,
223
+ "vit_img_size": self.vit_img_size,
224
+ "max_img_size": self.max_img_size,
225
+ "inference_max_img_size": self.inference_max_img_size,
226
+ "resize_to_max_side_len": self.resize_to_max_side_len,
227
+ "resize_min_side_len": self.resize_min_side_len,
228
+ "mp_image_token_length": self.mp_image_token_length,
229
+ "lm_max_position_embeddings": self.lm_max_position_embeddings,
230
+ "lm_tokenizer": self.lm_tokenizer,
231
+ "lm_chat_template": self.lm_chat_template,
232
+ "vlm_extra_tokens": self.vlm_extra_tokens,
233
+ }
234
+
235
+ def save_pretrained(self, save_directory: str, **kwargs):
236
+ os.makedirs(save_directory, exist_ok=True)
237
+ super().save_pretrained(save_directory, **kwargs)
238
+
239
+ proc_cfg_path = os.path.join(save_directory, "processor_config.json")
240
+ proc_cfg = {}
241
+ if os.path.exists(proc_cfg_path):
242
+ with open(proc_cfg_path) as f:
243
+ proc_cfg = json.load(f)
244
+ proc_cfg["processor_class"] = "VisionPsyNanoProcessor"
245
+ proc_cfg["auto_map"] = {
246
+ "AutoProcessor": "processing_visionpsynano.VisionPsyNanoProcessor",
247
+ }
248
+ with open(proc_cfg_path, "w") as f:
249
+ json.dump(proc_cfg, f, indent=2)
250
+ f.write("\n")
251
+
252
+ with open(os.path.join(save_directory, "preprocessor_config.json"), "w") as f:
253
+ json.dump(self.image_config_dict(), f, indent=2)
254
+ f.write("\n")
255
+
256
+ @staticmethod
257
+ def _attach_extra_token_attrs(tokenizer, vlm_extra_tokens: Optional[dict]):
258
+ """Restore named attrs (``image_token``, ``r1c1``, ...) used by image strings."""
259
+ if not vlm_extra_tokens:
260
+ return tokenizer
261
+ for name, token in vlm_extra_tokens.items():
262
+ if not hasattr(tokenizer, name):
263
+ setattr(tokenizer, name, token)
264
+ if hasattr(tokenizer, "image_token"):
265
+ try:
266
+ tokenizer.image_token_id = tokenizer.convert_tokens_to_ids(tokenizer.image_token)
267
+ except Exception:
268
+ pass
269
+ return tokenizer
270
+
271
+ @classmethod
272
+ def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
273
+ trust_remote_code = kwargs.pop("trust_remote_code", False)
274
+ flash_override = kwargs.pop("is_flash", None)
275
+
276
+ tokenizer = None
277
+ prep = {}
278
+
279
+ def _prep_from_cfg(cfg: VisionPsyNanoConfig) -> dict:
280
+ return {
281
+ "is_flash": cfg.is_flash,
282
+ "vit_img_size": cfg.vit_img_size,
283
+ "max_img_size": cfg.max_img_size,
284
+ "inference_max_img_size": cfg.inference_max_img_size,
285
+ "resize_to_max_side_len": cfg.resize_to_max_side_len,
286
+ "resize_min_side_len": cfg.resize_min_side_len,
287
+ "mp_image_token_length": cfg.mp_image_token_length,
288
+ "lm_max_position_embeddings": cfg.lm_max_position_embeddings,
289
+ "lm_tokenizer": cfg.lm_tokenizer,
290
+ "lm_chat_template": cfg.lm_chat_template,
291
+ "vlm_extra_tokens": cfg.vlm_extra_tokens,
292
+ }
293
+
294
+ resolved = pretrained_model_name_or_path
295
+ prep_path = None
296
+ if os.path.isdir(resolved):
297
+ prep_path = os.path.join(resolved, "preprocessor_config.json")
298
+ tok_files_present = any(
299
+ os.path.exists(os.path.join(resolved, name))
300
+ for name in ("tokenizer.json", "tokenizer_config.json", "vocab.json")
301
+ )
302
+ if tok_files_present:
303
+ tokenizer = AutoTokenizer.from_pretrained(
304
+ resolved, trust_remote_code=trust_remote_code, **kwargs
305
+ )
306
+ if prep_path and os.path.exists(prep_path):
307
+ with open(prep_path) as f:
308
+ prep = json.load(f)
309
+ else:
310
+ from huggingface_hub import hf_hub_download, list_repo_files
311
+
312
+ try:
313
+ files = set(list_repo_files(resolved))
314
+ except Exception:
315
+ files = set()
316
+ if "preprocessor_config.json" in files:
317
+ prep_path = hf_hub_download(resolved, "preprocessor_config.json")
318
+ with open(prep_path) as f:
319
+ prep = json.load(f)
320
+ if any(n in files for n in ("tokenizer.json", "tokenizer_config.json", "vocab.json")):
321
+ tokenizer = AutoTokenizer.from_pretrained(
322
+ resolved, trust_remote_code=trust_remote_code, **kwargs
323
+ )
324
+
325
+ if tokenizer is None:
326
+ lm_tok = prep.get("lm_tokenizer")
327
+ extra = prep.get("vlm_extra_tokens")
328
+ chat = prep.get("lm_chat_template")
329
+ if lm_tok is None:
330
+ cfg = VisionPsyNanoConfig.from_pretrained(
331
+ pretrained_model_name_or_path, trust_remote_code=trust_remote_code
332
+ )
333
+ lm_tok = cfg.lm_tokenizer
334
+ extra = cfg.vlm_extra_tokens
335
+ chat = cfg.lm_chat_template
336
+ if not prep:
337
+ prep = _prep_from_cfg(cfg)
338
+ tokenizer = get_tokenizer(lm_tok, extra, chat)
339
+
340
+ if not prep:
341
+ try:
342
+ cfg = VisionPsyNanoConfig.from_pretrained(
343
+ pretrained_model_name_or_path, trust_remote_code=trust_remote_code
344
+ )
345
+ prep = _prep_from_cfg(cfg)
346
+ except Exception as e:
347
+ logger.warning("Could not load VisionPsyNanoConfig for processor defaults: %s", e)
348
+
349
+ if flash_override is not None:
350
+ prep["is_flash"] = bool(flash_override)
351
+
352
+ extra = prep.get("vlm_extra_tokens")
353
+ if extra is None:
354
+ try:
355
+ cfg = VisionPsyNanoConfig.from_pretrained(
356
+ pretrained_model_name_or_path, trust_remote_code=trust_remote_code
357
+ )
358
+ extra = cfg.vlm_extra_tokens
359
+ prep.setdefault("vlm_extra_tokens", extra)
360
+ except Exception:
361
+ pass
362
+ cls._attach_extra_token_attrs(tokenizer, extra)
363
+
364
+ chat_template = getattr(tokenizer, "chat_template", None) or prep.get("lm_chat_template")
365
+ return cls(tokenizer, chat_template=chat_template, **{
366
+ k: prep[k]
367
+ for k in (
368
+ "is_flash",
369
+ "vit_img_size",
370
+ "max_img_size",
371
+ "inference_max_img_size",
372
+ "resize_to_max_side_len",
373
+ "resize_min_side_len",
374
+ "mp_image_token_length",
375
+ "lm_max_position_embeddings",
376
+ "vlm_extra_tokens",
377
+ "lm_tokenizer",
378
+ "lm_chat_template",
379
+ )
380
+ if k in prep
381
+ })
382
+
383
+ @classmethod
384
+ def from_config(cls, config: VisionPsyNanoConfig, tokenizer=None) -> "VisionPsyNanoProcessor":
385
+ if tokenizer is None:
386
+ tokenizer = get_tokenizer(
387
+ config.lm_tokenizer, config.vlm_extra_tokens, config.lm_chat_template
388
+ )
389
+ return cls(
390
+ tokenizer,
391
+ chat_template=config.lm_chat_template,
392
+ is_flash=config.is_flash,
393
+ vit_img_size=config.vit_img_size,
394
+ max_img_size=config.max_img_size,
395
+ inference_max_img_size=config.inference_max_img_size,
396
+ resize_to_max_side_len=config.resize_to_max_side_len,
397
+ resize_min_side_len=config.resize_min_side_len,
398
+ mp_image_token_length=config.mp_image_token_length,
399
+ lm_max_position_embeddings=config.lm_max_position_embeddings,
400
+ vlm_extra_tokens=config.vlm_extra_tokens,
401
+ lm_tokenizer=config.lm_tokenizer,
402
+ lm_chat_template=config.lm_chat_template,
403
+ )
404
+
405
+
406
+ try:
407
+ from transformers import AutoProcessor
408
+
409
+ AutoProcessor.register(VisionPsyNanoConfig, VisionPsyNanoProcessor)
410
+ except Exception:
411
+ pass
processor_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "processor_class": "VisionPsyNanoProcessor",
3
+ "auto_map": {
4
+ "AutoProcessor": "processing_visionpsynano.VisionPsyNanoProcessor"
5
+ }
6
+ }
processors.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from transformers import AutoTokenizer
4
+ import torchvision.transforms as transforms
5
+
6
+ from .custom_transforms import DynamicResize, GlobalAndSplitImages
7
+
8
+ TOKENIZERS_CACHE = {}
9
+ FLASH_MIN_SIDE_LEN = 512
10
+
11
+
12
+ # Local copies of configuration_visionpsynano.{resolve_is_flash,apply_flash_preprocess}:
13
+ # the data/ tree is vendored standalone (vLLM plugin), so it must not import from
14
+ # the HF package layout. Keep in sync with configuration_visionpsynano.py.
15
+ def resolve_is_flash(
16
+ *,
17
+ is_flash: Optional[bool] = None,
18
+ variant: Optional[str] = None,
19
+ resize_to_max_side_len: Optional[bool] = None,
20
+ ) -> bool:
21
+ """Resolve Flash preprocess mode from explicit flag, legacy variant, or resize policy."""
22
+ if is_flash is not None:
23
+ return bool(is_flash)
24
+ if variant is not None:
25
+ v = str(variant).lower().strip()
26
+ if v in ("flash", "nano-flash", "visionpsy-nano-flash"):
27
+ return True
28
+ if v in ("nano", "plain", "nano-plain", "visionpsy-nano"):
29
+ return False
30
+ raise ValueError(
31
+ "legacy variant must be 'nano' or 'flash' "
32
+ f"(or aliases); got {variant!r}. Prefer is_flash=True/False."
33
+ )
34
+ if resize_to_max_side_len is not None:
35
+ return not bool(resize_to_max_side_len)
36
+ return False
37
+
38
+
39
+ def apply_flash_preprocess(
40
+ *,
41
+ is_flash: bool,
42
+ resize_to_max_side_len: Optional[bool] = None,
43
+ resize_min_side_len: Optional[int] = None,
44
+ ) -> tuple[bool, Optional[int]]:
45
+ """Return (resize_to_max_side_len, resize_min_side_len) for the given Flash mode."""
46
+ if resize_to_max_side_len is None:
47
+ resize_to_max_side_len = not is_flash
48
+ resize_to_max_side_len = bool(resize_to_max_side_len)
49
+ if is_flash:
50
+ resize_min_side_len = max(int(resize_min_side_len or 0), FLASH_MIN_SIDE_LEN)
51
+ elif resize_to_max_side_len:
52
+ resize_min_side_len = None
53
+ return resize_to_max_side_len, resize_min_side_len
54
+
55
+
56
+ def apply_model_preprocess(cfg, *, flash: bool = False) -> bool:
57
+ is_flash = resolve_is_flash(
58
+ is_flash=True if flash else getattr(cfg, "is_flash", None),
59
+ variant=getattr(cfg, "variant", None),
60
+ resize_to_max_side_len=(
61
+ False if flash else getattr(cfg, "resize_to_max_side_len", None)
62
+ ),
63
+ )
64
+ resize_to_max, resize_min = apply_flash_preprocess(
65
+ is_flash=is_flash,
66
+ resize_to_max_side_len=None if flash else getattr(cfg, "resize_to_max_side_len", None),
67
+ resize_min_side_len=getattr(cfg, "resize_min_side_len", None),
68
+ )
69
+ cfg.resize_to_max_side_len = resize_to_max
70
+ cfg.resize_min_side_len = resize_min
71
+ if hasattr(cfg, "is_flash"):
72
+ try:
73
+ cfg.is_flash = is_flash
74
+ except AttributeError:
75
+ pass
76
+ return is_flash
77
+
78
+
79
+ def get_tokenizer(name, extra_special_tokens=None, chat_template=None):
80
+ cache_key = (
81
+ name,
82
+ tuple(sorted((extra_special_tokens or {}).items())),
83
+ chat_template,
84
+ )
85
+ if cache_key not in TOKENIZERS_CACHE:
86
+ tokenizer_init_kwargs = {"use_fast": True}
87
+ if extra_special_tokens is not None:
88
+ tokenizer_init_kwargs["extra_special_tokens"] = extra_special_tokens
89
+ if chat_template is not None:
90
+ tokenizer_init_kwargs["chat_template"] = chat_template
91
+ tokenizer = AutoTokenizer.from_pretrained(name, **tokenizer_init_kwargs,)
92
+ tokenizer.pad_token = tokenizer.eos_token
93
+ TOKENIZERS_CACHE[cache_key] = tokenizer
94
+ return TOKENIZERS_CACHE[cache_key]
95
+
96
+ def get_image_processor(max_img_size, splitted_image_size, resize_to_max_side_len=False, min_side_len=None):
97
+ return transforms.Compose([
98
+ DynamicResize(splitted_image_size, max_img_size, resize_to_max_side_len, min_side_len),
99
+ transforms.ToTensor(),
100
+ GlobalAndSplitImages(splitted_image_size),
101
+ ])
102
+
103
+ def get_image_string(tokenizer, splitted_image_counts, mp_image_token_length):
104
+ image_string = ""
105
+
106
+ for idx, (n_h, n_w) in enumerate(splitted_image_counts):
107
+ if len(splitted_image_counts) > 1:
108
+ image_string += f"<image: {idx}>"
109
+ if hasattr(tokenizer, "global_image_token"):
110
+ image_string += tokenizer.global_image_token
111
+ image_string += tokenizer.image_token * mp_image_token_length
112
+ if n_h == 1 and n_w == 1:
113
+ continue
114
+ for i in range(n_h):
115
+ for j in range(n_w):
116
+ image_string += getattr(tokenizer, f'r{i+1}c{j+1}')
117
+ image_string += tokenizer.image_token * mp_image_token_length
118
+ return image_string
runtime_profile.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inference runtime profiles (eager vs torch.compile + CUDA graphs)."""
2
+ from __future__ import annotations
3
+
4
+ import torch
5
+
6
+
7
+ def apply_eager_profile(model) -> None:
8
+ for cfg in (getattr(model, "cfg", None), getattr(model, "config", None)):
9
+ if cfg is not None and hasattr(cfg, "compile_inference"):
10
+ cfg.compile_inference = False
11
+
12
+
13
+ def apply_deploy_profile(model, device: torch.device) -> None:
14
+ if device.type != "cuda":
15
+ apply_eager_profile(model)
16
+ return
17
+ for cfg in (getattr(model, "cfg", None), getattr(model, "config", None)):
18
+ if cfg is None:
19
+ continue
20
+ if hasattr(cfg, "compile_inference"):
21
+ cfg.compile_inference = True
22
+ if hasattr(cfg, "compile_inference_mode"):
23
+ cfg.compile_inference_mode = "reduce-overhead"
24
+ if hasattr(cfg, "cuda_graphs_cache_quantum"):
25
+ cfg.cuda_graphs_cache_quantum = 128
26
+ if hasattr(cfg, "eos_check_interval"):
27
+ cfg.eos_check_interval = 16
28
+
29
+
30
+ def profile_label(model, device: torch.device) -> str:
31
+ cfg = getattr(model, "cfg", None) or getattr(model, "config", None)
32
+ if device.type != "cuda":
33
+ return "cpu-eager"
34
+ if cfg is not None and getattr(cfg, "compile_inference", False):
35
+ mode = getattr(cfg, "compile_inference_mode", "default")
36
+ return f"cuda-compile-{mode}"
37
+ return "cuda-eager"
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": "<|im_start|>",
5
+ "clean_up_tokenization_spaces": false,
6
+ "eos_token": "<|im_end|>",
7
+ "errors": "replace",
8
+ "global_image_token": "<|global_image|>",
9
+ "image_token": "<|image|>",
10
+ "is_local": false,
11
+ "local_files_only": false,
12
+ "model_max_length": 8192,
13
+ "model_specific_special_tokens": {
14
+ "global_image_token": "<|global_image|>",
15
+ "image_token": "<|image|>",
16
+ "r1c1": "<row_1_col_1>",
17
+ "r1c2": "<row_1_col_2>",
18
+ "r1c3": "<row_1_col_3>",
19
+ "r1c4": "<row_1_col_4>",
20
+ "r1c5": "<row_1_col_5>",
21
+ "r1c6": "<row_1_col_6>",
22
+ "r1c7": "<row_1_col_7>",
23
+ "r1c8": "<row_1_col_8>",
24
+ "r2c1": "<row_2_col_1>",
25
+ "r2c2": "<row_2_col_2>",
26
+ "r2c3": "<row_2_col_3>",
27
+ "r2c4": "<row_2_col_4>",
28
+ "r2c5": "<row_2_col_5>",
29
+ "r2c6": "<row_2_col_6>",
30
+ "r2c7": "<row_2_col_7>",
31
+ "r2c8": "<row_2_col_8>",
32
+ "r3c1": "<row_3_col_1>",
33
+ "r3c2": "<row_3_col_2>",
34
+ "r3c3": "<row_3_col_3>",
35
+ "r3c4": "<row_3_col_4>",
36
+ "r3c5": "<row_3_col_5>",
37
+ "r3c6": "<row_3_col_6>",
38
+ "r3c7": "<row_3_col_7>",
39
+ "r3c8": "<row_3_col_8>",
40
+ "r4c1": "<row_4_col_1>",
41
+ "r4c2": "<row_4_col_2>",
42
+ "r4c3": "<row_4_col_3>",
43
+ "r4c4": "<row_4_col_4>",
44
+ "r4c5": "<row_4_col_5>",
45
+ "r4c6": "<row_4_col_6>",
46
+ "r4c7": "<row_4_col_7>",
47
+ "r4c8": "<row_4_col_8>",
48
+ "r5c1": "<row_5_col_1>",
49
+ "r5c2": "<row_5_col_2>",
50
+ "r5c3": "<row_5_col_3>",
51
+ "r5c4": "<row_5_col_4>",
52
+ "r5c5": "<row_5_col_5>",
53
+ "r5c6": "<row_5_col_6>",
54
+ "r5c7": "<row_5_col_7>",
55
+ "r5c8": "<row_5_col_8>",
56
+ "r6c1": "<row_6_col_1>",
57
+ "r6c2": "<row_6_col_2>",
58
+ "r6c3": "<row_6_col_3>",
59
+ "r6c4": "<row_6_col_4>",
60
+ "r6c5": "<row_6_col_5>",
61
+ "r6c6": "<row_6_col_6>",
62
+ "r6c7": "<row_6_col_7>",
63
+ "r6c8": "<row_6_col_8>",
64
+ "r7c1": "<row_7_col_1>",
65
+ "r7c2": "<row_7_col_2>",
66
+ "r7c3": "<row_7_col_3>",
67
+ "r7c4": "<row_7_col_4>",
68
+ "r7c5": "<row_7_col_5>",
69
+ "r7c6": "<row_7_col_6>",
70
+ "r7c7": "<row_7_col_7>",
71
+ "r7c8": "<row_7_col_8>",
72
+ "r8c1": "<row_8_col_1>",
73
+ "r8c2": "<row_8_col_2>",
74
+ "r8c3": "<row_8_col_3>",
75
+ "r8c4": "<row_8_col_4>",
76
+ "r8c5": "<row_8_col_5>",
77
+ "r8c6": "<row_8_col_6>",
78
+ "r8c7": "<row_8_col_7>",
79
+ "r8c8": "<row_8_col_8>"
80
+ },
81
+ "pad_token": "<|im_end|>",
82
+ "processor_class": "VisionPsyNanoProcessor",
83
+ "r1c1": "<row_1_col_1>",
84
+ "r1c2": "<row_1_col_2>",
85
+ "r1c3": "<row_1_col_3>",
86
+ "r1c4": "<row_1_col_4>",
87
+ "r1c5": "<row_1_col_5>",
88
+ "r1c6": "<row_1_col_6>",
89
+ "r1c7": "<row_1_col_7>",
90
+ "r1c8": "<row_1_col_8>",
91
+ "r2c1": "<row_2_col_1>",
92
+ "r2c2": "<row_2_col_2>",
93
+ "r2c3": "<row_2_col_3>",
94
+ "r2c4": "<row_2_col_4>",
95
+ "r2c5": "<row_2_col_5>",
96
+ "r2c6": "<row_2_col_6>",
97
+ "r2c7": "<row_2_col_7>",
98
+ "r2c8": "<row_2_col_8>",
99
+ "r3c1": "<row_3_col_1>",
100
+ "r3c2": "<row_3_col_2>",
101
+ "r3c3": "<row_3_col_3>",
102
+ "r3c4": "<row_3_col_4>",
103
+ "r3c5": "<row_3_col_5>",
104
+ "r3c6": "<row_3_col_6>",
105
+ "r3c7": "<row_3_col_7>",
106
+ "r3c8": "<row_3_col_8>",
107
+ "r4c1": "<row_4_col_1>",
108
+ "r4c2": "<row_4_col_2>",
109
+ "r4c3": "<row_4_col_3>",
110
+ "r4c4": "<row_4_col_4>",
111
+ "r4c5": "<row_4_col_5>",
112
+ "r4c6": "<row_4_col_6>",
113
+ "r4c7": "<row_4_col_7>",
114
+ "r4c8": "<row_4_col_8>",
115
+ "r5c1": "<row_5_col_1>",
116
+ "r5c2": "<row_5_col_2>",
117
+ "r5c3": "<row_5_col_3>",
118
+ "r5c4": "<row_5_col_4>",
119
+ "r5c5": "<row_5_col_5>",
120
+ "r5c6": "<row_5_col_6>",
121
+ "r5c7": "<row_5_col_7>",
122
+ "r5c8": "<row_5_col_8>",
123
+ "r6c1": "<row_6_col_1>",
124
+ "r6c2": "<row_6_col_2>",
125
+ "r6c3": "<row_6_col_3>",
126
+ "r6c4": "<row_6_col_4>",
127
+ "r6c5": "<row_6_col_5>",
128
+ "r6c6": "<row_6_col_6>",
129
+ "r6c7": "<row_6_col_7>",
130
+ "r6c8": "<row_6_col_8>",
131
+ "r7c1": "<row_7_col_1>",
132
+ "r7c2": "<row_7_col_2>",
133
+ "r7c3": "<row_7_col_3>",
134
+ "r7c4": "<row_7_col_4>",
135
+ "r7c5": "<row_7_col_5>",
136
+ "r7c6": "<row_7_col_6>",
137
+ "r7c7": "<row_7_col_7>",
138
+ "r7c8": "<row_7_col_8>",
139
+ "r8c1": "<row_8_col_1>",
140
+ "r8c2": "<row_8_col_2>",
141
+ "r8c3": "<row_8_col_3>",
142
+ "r8c4": "<row_8_col_4>",
143
+ "r8c5": "<row_8_col_5>",
144
+ "r8c6": "<row_8_col_6>",
145
+ "r8c7": "<row_8_col_7>",
146
+ "r8c8": "<row_8_col_8>",
147
+ "tokenizer_class": "GPT2Tokenizer",
148
+ "unk_token": "<|endoftext|>",
149
+ "vocab_size": 49152
150
+ }
vision_language_model.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import tempfile
4
+ from dataclasses import asdict
5
+ from typing import Optional
6
+
7
+ from .model_utils import top_k_top_p_filtering
8
+ from .vision_transformer import ViT
9
+ from .language_model import LanguageModel
10
+ from .modality_projector import ModalityProjector
11
+ from .vlm_config import VLMConfig
12
+
13
+ from .processors import get_tokenizer
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+ from safetensors.torch import load_model, save_model
19
+
20
+ def _drop_if_all_attend(attention_mask: Optional[torch.Tensor]) -> Optional[torch.Tensor]:
21
+ """Drop all-ones masks (avoid decode length mismatch with a fixed prefill mask)."""
22
+ if attention_mask is None:
23
+ return None
24
+ if bool(attention_mask.all()):
25
+ return None
26
+ return attention_mask
27
+
28
+ class VisionLanguageModel(nn.Module):
29
+ def __init__(self, cfg: VLMConfig, load_backbone=True):
30
+ super().__init__()
31
+ self.cfg = cfg
32
+ if load_backbone:
33
+ print("Loading from backbone weights")
34
+ self.vision_encoder = ViT.from_pretrained(cfg)
35
+ self.decoder = LanguageModel.from_pretrained(cfg)
36
+ else:
37
+ self.vision_encoder = ViT(cfg)
38
+ self.decoder = LanguageModel(cfg)
39
+ self.MP = ModalityProjector(cfg)
40
+ self.load_backbone = load_backbone
41
+ self.tokenizer = get_tokenizer(cfg.lm_tokenizer, cfg.vlm_extra_tokens, cfg.lm_chat_template)
42
+
43
+ self._compiled_decoder = None
44
+ self._compiled_vision_encoder = None
45
+
46
+ def _vision_encoder_for_prefill(self):
47
+ if not getattr(self.cfg, "compile_inference", False):
48
+ return self.vision_encoder
49
+ if self._compiled_vision_encoder is None:
50
+ self._compiled_vision_encoder = torch.compile(
51
+ self.vision_encoder,
52
+ mode=getattr(self.cfg, "compile_inference_mode", "default"),
53
+ dynamic=True,
54
+ fullgraph=False,
55
+ )
56
+ return self._compiled_vision_encoder
57
+
58
+ def _decoder_for_decode(self):
59
+
60
+ if not getattr(self.cfg, "compile_inference", False):
61
+ return self.decoder
62
+ if self._compiled_decoder is None:
63
+ mode = getattr(self.cfg, "compile_inference_mode", "default")
64
+ self._compiled_decoder = torch.compile(
65
+ self.decoder,
66
+ mode=mode,
67
+ dynamic=(False if mode == "reduce-overhead" else True),
68
+ fullgraph=False,
69
+ )
70
+ return self._compiled_decoder
71
+
72
+ def _replace_img_tokens_with_embd(self, input_ids, token_embd, image_embd):
73
+ image_token_id = getattr(self.tokenizer, "image_token_id", None)
74
+ if image_token_id is None and hasattr(self.tokenizer, "image_token"):
75
+ image_token_id = self.tokenizer.convert_tokens_to_ids(self.tokenizer.image_token)
76
+ if image_token_id is None:
77
+ image_token_id = self.tokenizer.convert_tokens_to_ids("<|image|>")
78
+ mask = (input_ids == image_token_id).unsqueeze(-1)
79
+ flat = image_embd.view(-1, image_embd.size(-1)).to(token_embd.dtype)
80
+ token_embd.masked_scatter_(mask, flat)
81
+ return token_embd
82
+
83
+ def _process_images(self, images, device):
84
+ if isinstance(images, list):
85
+ if images and isinstance(images[0], list):
86
+ images = [img for sublist in images for img in sublist]
87
+
88
+ if not images:
89
+ return None
90
+ else:
91
+ return torch.cat(images, dim=0).to(device)
92
+ return images
93
+
94
+ def forward(self, input_ids, images, attention_mask=None, targets=None):
95
+ images_tensor = self._process_images(images, input_ids.device)
96
+ token_embd = self.decoder.token_embedding(input_ids)
97
+
98
+ if images_tensor is not None:
99
+ image_embd = self.vision_encoder(images_tensor)
100
+ image_embd = self.MP(image_embd)
101
+ token_embd = self._replace_img_tokens_with_embd(input_ids, token_embd, image_embd)
102
+
103
+ attention_mask = _drop_if_all_attend(attention_mask)
104
+
105
+ logits, _ = self.decoder(token_embd, attention_mask=attention_mask)
106
+
107
+ loss = None
108
+ if targets is not None:
109
+ logits = self.decoder.head(logits)
110
+ loss = F.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.reshape(-1), ignore_index=-100)
111
+
112
+ return logits, loss
113
+
114
+ @torch.inference_mode()
115
+ def generate(self, input_ids, images, attention_mask=None, max_new_tokens=5, top_k=50, top_p=0.9, temperature=0.5, greedy=False):
116
+ images_tensor = self._process_images(images, input_ids.device)
117
+ token_embd = self.decoder.token_embedding(input_ids)
118
+
119
+ if images_tensor is not None:
120
+ vision_encoder_fn = self._vision_encoder_for_prefill()
121
+ image_embd = vision_encoder_fn(images_tensor)
122
+ image_embd = self.MP(image_embd)
123
+ token_embd = self._replace_img_tokens_with_embd(input_ids, token_embd, image_embd)
124
+
125
+ current_total_seq_len = token_embd.size(1)
126
+ batch_size = input_ids.size(0)
127
+ device = input_ids.device
128
+
129
+ cache_max_length = current_total_seq_len + max(int(max_new_tokens), 1)
130
+ head_dim = self.cfg.lm_hidden_dim // self.cfg.lm_n_heads
131
+ use_cuda_graphs_pre = (
132
+ getattr(self.cfg, "compile_inference", False)
133
+ and getattr(self.cfg, "compile_inference_mode", "default") == "reduce-overhead"
134
+ and device.type == "cuda"
135
+ )
136
+ if use_cuda_graphs_pre:
137
+ quantum = int(getattr(self.cfg, "cuda_graphs_cache_quantum", 128))
138
+ if quantum > 1:
139
+ cache_max_length = ((cache_max_length + quantum - 1) // quantum) * quantum
140
+ cache_shape = (batch_size, self.cfg.lm_n_kv_heads, cache_max_length, head_dim)
141
+ cache_dtype = next(self.decoder.parameters()).dtype
142
+ use_cuda_graphs = use_cuda_graphs_pre
143
+ kv_cache_list = []
144
+ for _ in range(self.cfg.lm_n_blocks):
145
+ key_cache = torch.zeros(cache_shape, dtype=cache_dtype, device=device)
146
+ value_cache = torch.zeros(cache_shape, dtype=cache_dtype, device=device)
147
+ if use_cuda_graphs:
148
+ torch._dynamo.mark_static_address(key_cache)
149
+ torch._dynamo.mark_static_address(value_cache)
150
+ kv_cache_list.append({
151
+ "cache_max_length": cache_max_length,
152
+ "key_cache": key_cache,
153
+ "value_cache": value_cache,
154
+ })
155
+
156
+ attention_mask = _drop_if_all_attend(attention_mask)
157
+
158
+ prefill_output, kv_cache_list = self.decoder(
159
+ token_embd,
160
+ attention_mask=attention_mask,
161
+ kv_cache=kv_cache_list,
162
+ start_pos=0,
163
+ )
164
+
165
+ last_token_output_from_prefill = prefill_output[:, -1, :]
166
+
167
+ if not self.decoder.lm_use_tokens:
168
+ current_logits = self.decoder.head(last_token_output_from_prefill)
169
+ else:
170
+ current_logits = last_token_output_from_prefill
171
+
172
+ newly_generated_ids_list = []
173
+
174
+ decode_decoder = self._decoder_for_decode()
175
+
176
+ if use_cuda_graphs:
177
+ start_pos_buf = torch.zeros((), dtype=torch.long, device=device)
178
+ torch._dynamo.mark_static_address(start_pos_buf)
179
+ else:
180
+ start_pos_buf = None
181
+
182
+ eos_id = self.tokenizer.eos_token_id
183
+ eos_check_interval = int(getattr(self.cfg, "eos_check_interval", 16))
184
+ if eos_id is None or eos_check_interval <= 0:
185
+ eos_check_interval = 0
186
+
187
+ for step_idx in range(max_new_tokens):
188
+ if greedy:
189
+ next_token_id = torch.argmax(current_logits, dim=-1, keepdim=True)
190
+ else:
191
+ filtered_logits = top_k_top_p_filtering(current_logits, top_k=top_k, top_p=top_p)
192
+ probs = torch.softmax(filtered_logits / temperature, dim=-1)
193
+ next_token_id = torch.multinomial(probs, num_samples=1)
194
+
195
+ newly_generated_ids_list.append(next_token_id)
196
+
197
+ if (
198
+ eos_check_interval > 0
199
+ and (step_idx + 1) % eos_check_interval == 0
200
+ and bool((next_token_id == eos_id).all().item())
201
+ ):
202
+ break
203
+
204
+ next_token_embed = self.decoder.token_embedding(next_token_id)
205
+
206
+ current_token_start_pos = current_total_seq_len
207
+ current_total_seq_len += 1
208
+
209
+ if start_pos_buf is not None:
210
+ start_pos_buf.fill_(current_token_start_pos)
211
+ start_pos_to_pass = start_pos_buf
212
+ else:
213
+ start_pos_to_pass = current_token_start_pos
214
+
215
+ decode_step_output, kv_cache_list = decode_decoder(
216
+ next_token_embed,
217
+ attention_mask=attention_mask,
218
+ kv_cache=kv_cache_list,
219
+ start_pos=start_pos_to_pass,
220
+ )
221
+
222
+ last_token_output = decode_step_output[:, -1, :]
223
+
224
+ if not self.decoder.lm_use_tokens:
225
+ current_logits = self.decoder.head(last_token_output)
226
+ else:
227
+ current_logits = last_token_output
228
+ if not newly_generated_ids_list:
229
+ return torch.empty((batch_size,0), dtype=torch.long, device=input_ids.device)
230
+
231
+ generated_ids = torch.cat(newly_generated_ids_list, dim=1)
232
+
233
+ if self.tokenizer.eos_token_id is not None and generated_ids.numel() > 0:
234
+ seq_len = generated_ids.size(1)
235
+ device = generated_ids.device
236
+
237
+ eos_mask = (generated_ids == self.tokenizer.eos_token_id)
238
+
239
+ col_indices_for_min = torch.arange(seq_len, device=device)
240
+ masked_col_indices = torch.where(eos_mask, col_indices_for_min.unsqueeze(0).expand_as(generated_ids), seq_len + 1)
241
+
242
+ first_eos_indices_values = torch.min(masked_col_indices, dim=1).values
243
+ actual_first_eos_indices = torch.clamp(first_eos_indices_values, max=seq_len)
244
+
245
+ col_indices_for_comparison = torch.arange(seq_len, device=device).unsqueeze(0).expand_as(generated_ids)
246
+ replace_mask = col_indices_for_comparison > actual_first_eos_indices.unsqueeze(1)
247
+ generated_ids[replace_mask] = self.tokenizer.eos_token_id
248
+ return generated_ids
249
+
250
+ @classmethod
251
+ def from_pretrained(
252
+ cls, repo_id_or_path: str, *, revision: Optional[str] = None
253
+ ) -> "VisionLanguageModel":
254
+
255
+ if os.path.exists(repo_id_or_path):
256
+ config_path = os.path.join(repo_id_or_path, "config.json")
257
+ weights_path = os.path.join(repo_id_or_path, "model.safetensors")
258
+
259
+ if not os.path.exists(config_path):
260
+ raise ValueError(
261
+ f"Config file not found at {config_path}. Please provide a valid path."
262
+ )
263
+ if not os.path.exists(weights_path):
264
+ raise ValueError(
265
+ f"Weights file not found at {weights_path}. Please provide a valid path."
266
+ )
267
+ else:
268
+ from huggingface_hub import hf_hub_download
269
+
270
+ config_path = hf_hub_download(
271
+ repo_id=repo_id_or_path, filename="config.json", revision=revision
272
+ )
273
+ weights_path = hf_hub_download(
274
+ repo_id=repo_id_or_path, filename="model.safetensors", revision=revision
275
+ )
276
+
277
+ with open(config_path, "r") as f:
278
+ raw_cfg = json.load(f)
279
+ from dataclasses import fields as dc_fields
280
+ valid_keys = {fld.name for fld in dc_fields(VLMConfig)}
281
+ cfg = VLMConfig(**{k: v for k, v in raw_cfg.items() if k in valid_keys})
282
+
283
+ model = cls(cfg, load_backbone=False)
284
+ model._saved_config_keys = set(raw_cfg.keys())
285
+
286
+ load_model(model, weights_path)
287
+
288
+ return model
289
+
290
+ def save_pretrained(self, save_directory: str) -> None:
291
+
292
+ os.makedirs(save_directory, exist_ok=True)
293
+
294
+ with open(os.path.join(save_directory, "config.json"), "w") as f:
295
+ f.write(json.dumps(asdict(self.cfg), indent=4))
296
+
297
+ save_model(self, os.path.join(save_directory, "model.safetensors"))
298
+
299
+ def push_to_hub(self, repo_id: str, private: bool = False) -> None:
300
+
301
+ from huggingface_hub import create_repo, upload_folder
302
+
303
+ repo_url = create_repo(repo_id=repo_id, private=private, exist_ok=True)
304
+ repo_id = repo_url.repo_id
305
+ print("Created repo: ", repo_url)
306
+
307
+ with tempfile.TemporaryDirectory() as save_path:
308
+ self.save_pretrained(save_path)
309
+
310
+ with open(os.path.join(save_path, "README.md"), "w") as f:
311
+ f.write(MODEL_CARD_TEMPLATE.format(repo_id=repo_id))
312
+
313
+ return upload_folder(
314
+ repo_id=repo_id,
315
+ repo_type="model",
316
+ folder_path=save_path,
317
+ commit_message="Upload VisionPsyNano using push_to_hub",
318
+ )
319
+
320
+ MODEL_CARD_TEMPLATE = """
321
+ ---
322
+ library_name: transformers
323
+ pipeline_tag: image-text-to-text
324
+ tags:
325
+ - vision-language
326
+ - multimodal
327
+ ---
328
+
329
+ ```python
330
+ from transformers import AutoModelForImageTextToText, AutoProcessor
331
+ from PIL import Image
332
+
333
+ repo = "{repo_id}"
334
+ model = AutoModelForImageTextToText.from_pretrained(
335
+ repo, trust_remote_code=True, dtype="auto"
336
+ ).cuda().eval()
337
+ model.apply_deploy_profile(model.device)
338
+ processor = AutoProcessor.from_pretrained(repo, trust_remote_code=True)
339
+
340
+ inputs = processor(images=Image.open("image.jpg"), text="Describe the image.", return_tensors="pt")
341
+ inputs = {{k: v.cuda() if hasattr(v, "cuda") else v for k, v in inputs.items() if v is not None}}
342
+ inputs.pop("pixel_values", None)
343
+ out = model.generate(**inputs, max_new_tokens=64, greedy=True)
344
+ print(processor.batch_decode(out, skip_special_tokens=True)[0])
345
+ ```
346
+ """
vision_transformer.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+
6
+ class ViTPatchEmbeddings(nn.Module):
7
+ def __init__(self, cfg):
8
+ super().__init__()
9
+
10
+ self.img_size = cfg.vit_img_size
11
+ self.patch_size = cfg.vit_patch_size
12
+ self.num_patches = (self.img_size // self.patch_size) ** 2
13
+ self.cls_flag = cfg.vit_cls_flag
14
+ self.embd_dim = cfg.vit_hidden_dim
15
+
16
+ self.conv = nn.Conv2d(
17
+ in_channels=3,
18
+ out_channels=self.embd_dim,
19
+ kernel_size=self.patch_size,
20
+ stride=self.patch_size,
21
+ padding="valid",
22
+ )
23
+
24
+ if self.cls_flag:
25
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, self.embd_dim))
26
+ self.position_embedding = nn.Parameter(torch.rand(1, self.num_patches + 1, self.embd_dim))
27
+ else:
28
+ self.position_embedding = nn.Parameter(torch.rand(1, self.num_patches, self.embd_dim))
29
+
30
+ def forward(self, x):
31
+ x = self.conv(x)
32
+ x = x.flatten(2)
33
+ x = x.transpose(1, 2)
34
+
35
+ if self.cls_flag:
36
+ cls_token = self.cls_token.expand(x.shape[0], -1, -1)
37
+ x = torch.cat((cls_token, x), dim=1)
38
+ x = x + self.position_embedding
39
+ return x
40
+
41
+ class ViTMultiHeadAttention(nn.Module):
42
+ def __init__(self, cfg):
43
+ super().__init__()
44
+
45
+ self.n_heads = cfg.vit_n_heads
46
+ self.embd_dim = cfg.vit_hidden_dim
47
+ assert self.embd_dim % self.n_heads == 0, "embd_dim must be divisible by num_heads"
48
+ self.head_dim = self.embd_dim // self.n_heads
49
+ self.dropout = cfg.vit_dropout
50
+
51
+ self.qkv_proj = nn.Linear(self.embd_dim, 3 * self.embd_dim, bias=True)
52
+ self.out_proj = nn.Linear(self.embd_dim, self.embd_dim, bias=True)
53
+
54
+ self.attn_dropout = nn.Dropout(self.dropout)
55
+ self.resid_dropout = nn.Dropout(self.dropout)
56
+
57
+ self.sdpa = hasattr(torch.nn.functional, 'scaled_dot_product_attention')
58
+ if not self.sdpa:
59
+ print("Warning: scaled dot product attention not available. Using standard attention in ViT.")
60
+
61
+ def forward(self, x):
62
+ B, T, C = x.size()
63
+
64
+ qkv = self.qkv_proj(x)
65
+ q, k, v = qkv.split(C, dim=2)
66
+ q = q.view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
67
+ k = k.view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
68
+ v = v.view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
69
+
70
+ if self.sdpa:
71
+ y = torch.nn.functional.scaled_dot_product_attention(
72
+ q, k, v,
73
+ attn_mask=None,
74
+ dropout_p=self.dropout if self.training else 0.0,
75
+ is_causal=False
76
+ )
77
+ else:
78
+ attn = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
79
+ attn = F.softmax(attn, dim=-1)
80
+ attn = self.attn_dropout(attn)
81
+ y = attn @ v
82
+ y = y.transpose(1, 2).contiguous().view(B, T, C)
83
+ y = self.out_proj(y)
84
+ y = self.resid_dropout(y)
85
+
86
+ return y
87
+
88
+ class ViTMLP(nn.Module):
89
+ def __init__(self, cfg):
90
+ super().__init__()
91
+ self.activation_fn = nn.GELU(approximate='tanh')
92
+ self.fc1 = nn.Linear(cfg.vit_hidden_dim, cfg.vit_inter_dim)
93
+ self.fc2 = nn.Linear(cfg.vit_inter_dim, cfg.vit_hidden_dim)
94
+ self.dropout = nn.Dropout(cfg.vit_dropout)
95
+
96
+ def forward(self, x):
97
+ x = self.fc1(x)
98
+ x = self.activation_fn(x)
99
+ x = self.fc2(x)
100
+ x = self.dropout(x)
101
+ return x
102
+
103
+ class ViTBlock(nn.Module):
104
+ def __init__(self, cfg):
105
+ super().__init__()
106
+ self.ln1 = nn.LayerNorm(cfg.vit_hidden_dim, eps=cfg.vit_ln_eps)
107
+ self.attn = ViTMultiHeadAttention(cfg)
108
+ self.ln2 = nn.LayerNorm(cfg.vit_hidden_dim, eps=cfg.vit_ln_eps)
109
+ self.mlp = ViTMLP(cfg)
110
+ def forward(self, x):
111
+ x = x + self.attn(self.ln1(x))
112
+ x = x + self.mlp(self.ln2(x))
113
+ return x
114
+
115
+ class ViT(nn.Module):
116
+ def __init__(self, cfg):
117
+ super().__init__()
118
+ self.cfg = cfg
119
+ self.patch_embedding = ViTPatchEmbeddings(cfg)
120
+ self.cls_flag = cfg.vit_cls_flag
121
+ self.dropout = nn.Dropout(cfg.vit_dropout)
122
+ self.blocks = nn.ModuleList([ViTBlock(cfg) for _ in range(cfg.vit_n_blocks)])
123
+ self.layer_norm = nn.LayerNorm(cfg.vit_hidden_dim, eps=cfg.vit_ln_eps)
124
+
125
+ self.apply(self._init_weights)
126
+
127
+ def _init_weights(self, module):
128
+ if isinstance(module, nn.Linear):
129
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
130
+ if module.bias is not None:
131
+ torch.nn.init.zeros_(module.bias)
132
+ elif isinstance(module, nn.LayerNorm):
133
+ module.bias.data.zero_()
134
+ module.weight.data.fill_(1.0)
135
+ elif isinstance(module, nn.Conv2d):
136
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
137
+ if module.bias is not None:
138
+ torch.nn.init.zeros_(module.bias)
139
+
140
+ def forward(self, x):
141
+ x = self.patch_embedding(x)
142
+ x = self.dropout(x)
143
+ for block in self.blocks:
144
+ x = block(x)
145
+
146
+ if self.cls_flag:
147
+ x = self.layer_norm(x[:, 0])
148
+ else:
149
+ x = self.layer_norm(x)
150
+ return x
151
+ @classmethod
152
+ def from_pretrained(cls, cfg):
153
+ from transformers import SiglipVisionConfig
154
+ from huggingface_hub import hf_hub_download
155
+ import safetensors
156
+
157
+ hf_config = SiglipVisionConfig.from_pretrained(cfg.vit_model_type)
158
+ cfg.vit_dropout=hf_config.attention_dropout
159
+ cfg.vit_hidden_dim=hf_config.hidden_size
160
+ cfg.vit_img_size=hf_config.image_size
161
+ cfg.vit_inter_dim=hf_config.intermediate_size
162
+ cfg.vit_ln_eps=hf_config.layer_norm_eps
163
+ cfg.vit_n_heads=hf_config.num_attention_heads
164
+ cfg.vit_n_blocks=hf_config.num_hidden_layers
165
+ cfg.vit_patch_size=hf_config.patch_size
166
+ model = cls(cfg)
167
+ safetensors_file = hf_hub_download(repo_id=cfg.vit_model_type, filename="model.safetensors")
168
+
169
+ sd = model.state_dict()
170
+ mapping = {
171
+ 'vision_model.embeddings.patch_embedding.weight': 'patch_embedding.conv.weight',
172
+ 'vision_model.embeddings.patch_embedding.bias': 'patch_embedding.conv.bias',
173
+ 'vision_model.embeddings.position_embedding.weight': 'patch_embedding.position_embedding',
174
+ 'vision_model.post_layernorm.weight': 'layer_norm.weight',
175
+ 'vision_model.post_layernorm.bias': 'layer_norm.bias',
176
+ }
177
+ for i in range(cfg.vit_n_blocks):
178
+ mapping[f'vision_model.encoder.layers.{i}.layer_norm1.weight'] = f'blocks.{i}.ln1.weight'
179
+ mapping[f'vision_model.encoder.layers.{i}.layer_norm1.bias'] = f'blocks.{i}.ln1.bias'
180
+ mapping[f'vision_model.encoder.layers.{i}.layer_norm2.weight'] = f'blocks.{i}.ln2.weight'
181
+ mapping[f'vision_model.encoder.layers.{i}.layer_norm2.bias'] = f'blocks.{i}.ln2.bias'
182
+ mapping[f'vision_model.encoder.layers.{i}.mlp.fc1.weight'] = f'blocks.{i}.mlp.fc1.weight'
183
+ mapping[f'vision_model.encoder.layers.{i}.mlp.fc1.bias'] = f'blocks.{i}.mlp.fc1.bias'
184
+ mapping[f'vision_model.encoder.layers.{i}.mlp.fc2.weight'] = f'blocks.{i}.mlp.fc2.weight'
185
+ mapping[f'vision_model.encoder.layers.{i}.mlp.fc2.bias'] = f'blocks.{i}.mlp.fc2.bias'
186
+ mapping[f'vision_model.encoder.layers.{i}.self_attn.out_proj.weight'] = f'blocks.{i}.attn.out_proj.weight'
187
+ mapping[f'vision_model.encoder.layers.{i}.self_attn.out_proj.bias'] = f'blocks.{i}.attn.out_proj.bias'
188
+ with safetensors.safe_open(filename=safetensors_file, framework="pt", device="cpu") as f:
189
+ for hf_key, our_key in mapping.items():
190
+ if hf_key in f.keys() and our_key in sd:
191
+ tensor = f.get_tensor(hf_key)
192
+ if tensor.shape == sd[our_key].shape:
193
+ sd[our_key].copy_(tensor)
194
+ else:
195
+ if 'position_embedding' in hf_key:
196
+ sd[our_key].copy_(tensor.unsqueeze(0))
197
+ else:
198
+ print(f"Shape mismatch for {hf_key} -> {our_key}: {tensor.shape} vs {sd[our_key].shape}")
199
+ else:
200
+ if hf_key not in f.keys():
201
+ print(f"Warning: Key {hf_key} not found in safetensors file")
202
+ if our_key not in sd:
203
+ print(f"Warning: Key {our_key} not found in model state dict")
204
+ for i in range(model.cfg.vit_n_blocks):
205
+ q_weight = f.get_tensor(f'vision_model.encoder.layers.{i}.self_attn.q_proj.weight')
206
+ k_weight = f.get_tensor(f'vision_model.encoder.layers.{i}.self_attn.k_proj.weight')
207
+ v_weight = f.get_tensor(f'vision_model.encoder.layers.{i}.self_attn.v_proj.weight')
208
+ qkv_weight = torch.cat((q_weight, k_weight, v_weight), dim=0)
209
+ sd[f'blocks.{i}.attn.qkv_proj.weight'].copy_(qkv_weight)
210
+ q_bias = f.get_tensor(f'vision_model.encoder.layers.{i}.self_attn.q_proj.bias')
211
+ k_bias = f.get_tensor(f'vision_model.encoder.layers.{i}.self_attn.k_proj.bias')
212
+ v_bias = f.get_tensor(f'vision_model.encoder.layers.{i}.self_attn.v_proj.bias')
213
+ qkv_bias = torch.cat((q_bias, k_bias, v_bias), dim=0)
214
+ sd[f'blocks.{i}.attn.qkv_proj.bias'].copy_(qkv_bias)
215
+ model.load_state_dict(sd)
216
+ print(f"Successfully loaded {cfg.vit_model_type} weights from safetensors. Model has {sum(p.numel() for p in model.parameters()):,} parameters.")
217
+ return model
vlm_config.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass, field
2
+
3
+ @dataclass
4
+ class VLMConfig:
5
+ vit_hidden_dim: int = 768
6
+ vit_inter_dim: int = 4 * vit_hidden_dim
7
+ vit_patch_size: int = 16
8
+ vit_img_size: int = 512
9
+ vit_n_heads: int = 12
10
+ vit_dropout: float = 0.0
11
+ vit_n_blocks: int = 12
12
+ vit_ln_eps: float = 1e-6
13
+ vit_cls_flag: bool = False
14
+ vit_model_type: str = 'google/siglip2-base-patch16-512'
15
+
16
+ lm_hidden_dim: int = 960
17
+ lm_inter_dim: int = 2560
18
+ lm_rms_eps: float = 1e-5
19
+ lm_re_base: int = 100000
20
+ lm_max_position_embeddings: int = 8192
21
+ lm_base_vocab_size: int = 49152
22
+ extra_token_amount: int = 66
23
+ lm_vocab_size: int = lm_base_vocab_size + extra_token_amount
24
+ lm_n_heads: int = 15
25
+ lm_n_kv_heads: int = 5
26
+ lm_dropout: float = 0.0
27
+ lm_n_blocks: int = 32
28
+ lm_attn_scaling: float = 1.0
29
+ lm_max_length: int = 4096
30
+ lm_use_tokens: bool = False
31
+ lm_tie_weights: bool = True
32
+ lm_model_type: str = 'HuggingFaceTB/SmolLM2-360M-Instruct'
33
+ lm_tokenizer: str = 'HuggingFaceTB/SmolLM2-360M-Instruct'
34
+ lm_chat_template: str = "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}"
35
+
36
+ mp_pixel_shuffle_factor: int = 4
37
+ mp_image_token_length: int = 64
38
+
39
+ max_img_size: int = 2048
40
+ resize_to_max_side_len: bool = True
41
+ resize_min_side_len: int | None = None
42
+
43
+ inference_max_img_size: int | None = None
44
+
45
+ vlm_extra_tokens: dict[str, str] = field(default_factory=lambda: {"image_token": "<|image|>", "global_image_token": "<|global_image|>",
46
+ "r1c1": "<row_1_col_1>", "r1c2": "<row_1_col_2>", "r1c3": "<row_1_col_3>", "r1c4": "<row_1_col_4>", "r1c5": "<row_1_col_5>", "r1c6": "<row_1_col_6>", "r1c7": "<row_1_col_7>", "r1c8": "<row_1_col_8>",
47
+ "r2c1": "<row_2_col_1>", "r2c2": "<row_2_col_2>", "r2c3": "<row_2_col_3>", "r2c4": "<row_2_col_4>", "r2c5": "<row_2_col_5>", "r2c6": "<row_2_col_6>", "r2c7": "<row_2_col_7>", "r2c8": "<row_2_col_8>",
48
+ "r3c1": "<row_3_col_1>", "r3c2": "<row_3_col_2>", "r3c3": "<row_3_col_3>", "r3c4": "<row_3_col_4>", "r3c5": "<row_3_col_5>", "r3c6": "<row_3_col_6>", "r3c7": "<row_3_col_7>", "r3c8": "<row_3_col_8>",
49
+ "r4c1": "<row_4_col_1>", "r4c2": "<row_4_col_2>", "r4c3": "<row_4_col_3>", "r4c4": "<row_4_col_4>", "r4c5": "<row_4_col_5>", "r4c6": "<row_4_col_6>", "r4c7": "<row_4_col_7>", "r4c8": "<row_4_col_8>",
50
+ "r5c1": "<row_5_col_1>", "r5c2": "<row_5_col_2>", "r5c3": "<row_5_col_3>", "r5c4": "<row_5_col_4>", "r5c5": "<row_5_col_5>", "r5c6": "<row_5_col_6>", "r5c7": "<row_5_col_7>", "r5c8": "<row_5_col_8>",
51
+ "r6c1": "<row_6_col_1>", "r6c2": "<row_6_col_2>", "r6c3": "<row_6_col_3>", "r6c4": "<row_6_col_4>", "r6c5": "<row_6_col_5>", "r6c6": "<row_6_col_6>", "r6c7": "<row_6_col_7>", "r6c8": "<row_6_col_8>",
52
+ "r7c1": "<row_7_col_1>", "r7c2": "<row_7_col_2>", "r7c3": "<row_7_col_3>", "r7c4": "<row_7_col_4>", "r7c5": "<row_7_col_5>", "r7c6": "<row_7_col_6>", "r7c7": "<row_7_col_7>", "r7c8": "<row_7_col_8>",
53
+ "r8c1": "<row_8_col_1>", "r8c2": "<row_8_col_2>", "r8c3": "<row_8_col_3>", "r8c4": "<row_8_col_4>", "r8c5": "<row_8_col_5>", "r8c6": "<row_8_col_6>", "r8c7": "<row_8_col_7>", "r8c8": "<row_8_col_8>"})
54
+ vlm_load_backbone_weights: bool = True
55
+ vlm_checkpoint_path: str = 'checkpoints'
56
+ hf_repo_name: str = 'qvac/VisionPsy-Nano-460M'
57
+
58
+ compile_inference: bool = True
59
+ compile_inference_mode: str = 'reduce-overhead'
60
+ cuda_graphs_cache_quantum: int = 128
61
+
62
+ eos_check_interval: int = 16