WEISHU commited on
Commit
7325252
·
verified ·
1 Parent(s): b7cf821

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ 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
+ HPD_Parsing.pdf filter=lfs diff=lfs merge=lfs -text
37
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
HPD_Parsing.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ab70bc3e7a8a494dbf18e1b6e43e8c5efc2716d968c939351aa0538b914e2ea
3
+ size 10594788
P-MTP/config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MedusaModel"
4
+ ],
5
+ "model_type": "medusa"
6
+ }
README.md CHANGED
@@ -1,3 +1,232 @@
1
  ---
2
  license: apache-2.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: apache-2.0
3
+ pipeline_tag: image-text-to-text
4
+ tags:
5
+ - image-text-to-text
6
+ - transformers
7
+ - safetensors
8
+ - english
9
+ - chinese
10
+ - visual-language
11
+ - paddleocr
12
+ - document-parse
13
+ - HPD-Parsing
14
+ - speculative-decoding
15
+ - P-MTP
16
+ - custom-code
17
+ - eval results
18
+ - arxiv
19
+ - license
20
+ base_model: OpenGVLab/InternVL3_5-1B
21
+ language:
22
+ - en
23
+ - zh
24
+ library_name: transformers
25
  ---
26
+ <div align="center">
27
+
28
+ <h1 align="center">
29
+
30
+ HPD-Parsing: Hierarchical Parallel Document Parsing
31
+
32
+ </h1>
33
+
34
+ [![PaddleOCR](https://img.shields.io/github/stars/PaddlePaddle/PaddleOCR?color=ccf)](https://github.com/PaddlePaddle/PaddleOCR)
35
+ [![vLLM](https://img.shields.io/badge/Serving-vLLM-6080F0)](https://github.com/vllm-project/vllm)
36
+ [![arxiv](https://img.shields.io/badge/arXiv-b31b1b?logo=arxiv&logoColor=white)](./HPD_Parsing.pdf)
37
+ [![X](https://img.shields.io/badge/X-PaddlePaddle-6080F0)](https://x.com/PaddlePaddle)
38
+ [![License](https://img.shields.io/badge/license-Apache_2.0-green)](https://www.apache.org/licenses/LICENSE-2.0)
39
+
40
+ </div>
41
+
42
+ <div align="center">
43
+
44
+ <img src="https://raw.githubusercontent.com/weishu20/HPD-Parsing-images/master/images/teaser.svg" width="800"/>
45
+
46
+ </div>
47
+
48
+
49
+
50
+ ## News
51
+
52
+ * ```2026.07``` 🚀 We release **HPD-Parsing**, a 1B hierarchical parallel document parser. It achieves 94.91% overall on OmniDocBench v1.6 and 4,752 TPS peak throughput.
53
+
54
+
55
+ ## Introduction
56
+
57
+ <div align="center">
58
+ <img src="https://raw.githubusercontent.com/weishu20/HPD-Parsing-images/master/images/hierarchical_decoding.gif " width="800"/>
59
+ </div>
60
+
61
+ We introduce **HPD-Parsing**, a lightweight (1B) and high-throughput document parsing model built on a Hierarchical Parallel Decoding paradigm. Unified VLM-based parsers process an entire page jointly but generate the output through a single token-by-token autoregressive trajectory, creating a sequential bottleneck that grows with document length. HPD-Parsing is motivated by a key property of document parsing: **page structure requires global coordination, whereas content generation is largely localized within individual regions.** Based on this observation, a main layout branch coordinates the global document structure and dynamically dispatches localized content generation to concurrent branches, while Progressive Multi-Token Prediction (P-MTP) further reduces the decoding steps within each branch. **HPD-Parsing achieves an overall score of 94.91% on OmniDocBench v1.6 — a new state of the art among end-to-end unified parsers — while reaching a peak throughput of 4,752 TPS, 2.62× the fastest existing document parser and 3.06× its own autoregressive baseline.**
62
+
63
+
64
+
65
+
66
+
67
+ ### **Key Capabilities of HPD-Parsing**
68
+
69
+ **🚀 Hierarchical Parallel Decoding for High-Throughput Document Parsing**: We introduce Hierarchical Parallel Decoding (HPD), a new decoding paradigm that restructures full-page autoregressive generation into globally coordinated, localized parallel decoding. A main layout branch performs global coordination and dynamically decomposes the conventional single decoding trajectory into concurrent content branches, each responsible for a localized document region. Within each branch, P-MTP further reduces the number of decoding steps by predicting multiple future tokens at each iteration. Together with shared-prefix KV cache reuse, HPD substantially shortens the effective sequential decoding path along both branch and token dimensions.
70
+
71
+ **🔄 Staged Adaptation with Automated Difficulty-Aware Data Curation**: We develop a staged adaptation strategy that transfers conventional autoregressive document parsing capabilities to the proposed hierarchical parallel decoding paradigm while preserving parsing accuracy. The strategy is supported by an automated difficulty-aware data curation pipeline that integrates large-scale data collection, model-assisted annotation, difficulty estimation, and balanced sampling. By progressively adapting the model and emphasizing challenging samples, the training framework mitigates the accuracy degradation caused by the transition to parallel decoding with minimal manual annotation effort.
72
+
73
+
74
+ **⚡ State-of-the-Art Throughput with Competitive Parsing Accuracy**: HPD-Parsing achieves state-of-the-art inference efficiency on OmniDocBench v1.6, reaching a peak throughput of 4,752 Tokens Per Second (TPS). It delivers $1.62\times$ the throughput of the fastest existing document parsing model and more than $3.06\times$ that of its autoregressive baseline, while maintaining competitive parsing accuracy. These results demonstrate that document parsing can be effectively executed through global layout coordination and localized parallel decoding rather than a single sequential generation trajectory.
75
+
76
+
77
+ ### **HPD-Parsing Architecture**
78
+
79
+ HPD-Parsing adopts **InternVL3.5-1B** as its backbone, applies dynamic tile-based cropping (up to 24 tiles of 448×448) to preserve high-resolution details. Its primary departure from conventional unified parsers is the decoding paradigm: instead of generating the entire page along a single autoregressive trajectory, HPD-Parsing employs a main layout branch to coordinate the global structure and spawns localized content branches for concurrent region-level decoding, with P-MTP integrated into each branch.
80
+
81
+ <div align="center">
82
+ <img src="https://raw.githubusercontent.com/weishu20/HPD-Parsing-images/master/images/method.svg" width="800"/>
83
+ </div>
84
+
85
+
86
+
87
+
88
+ ## Usage
89
+
90
+ HPD-Parsing runs on a **customized build of vLLM** (based on vLLM v0.17.1) that implements the dynamic request forking required by hierarchical parallel decoding and adapts P-MTP speculative decoding.
91
+
92
+
93
+ ### 1. Prepare the Runtime Environment
94
+
95
+ Either option works; **the Docker image is strongly recommended** to minimize environment issues.
96
+
97
+ **Option A — Docker image** (ships the customized vLLM build and all dependencies):
98
+
99
+ ```text
100
+ ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/hpd-parsing-vllm:latest-nvidia-gpu
101
+ ```
102
+
103
+ The default entrypoint starts the inference server automatically (see Serving below). It is pulled automatically on the first `docker run`. For machines without internet access, use the offline image `...:latest-nvidia-gpu-offline` (~24.5 GB; it has the model weights built in). Pull it on a networked machine and transfer it:
104
+
105
+ ```shell
106
+ # On a machine with internet access
107
+ docker pull ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/hpd-parsing-vllm:latest-nvidia-gpu-offline
108
+ docker save ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/hpd-parsing-vllm:latest-nvidia-gpu-offline -o hpd-parsing-vllm-offline.tar
109
+ # Transfer the tar to the offline machine, then:
110
+ docker load -i hpd-parsing-vllm-offline.tar
111
+ ```
112
+
113
+ **Option B — Prebuilt package** (Python 3.10–3.13, NVIDIA driver with CUDA 12.8+). Install in a virtual environment to avoid dependency conflicts:
114
+
115
+ ```shell
116
+ python -m venv .venv_hpd_parsing
117
+ source .venv_hpd_parsing/bin/activate
118
+ python -m pip install https://paddle-model-ecology.bj.bcebos.com/paddlex/PaddleX3.0/deploy/hpd_parsing/vllm-0.17.1+hpdparsing-cp38-abi3-manylinux_2_31_x86_64.whl
119
+ ```
120
+
121
+ ### 2. vLLM Python API
122
+
123
+ ```python
124
+ # Set the environment variable before running: export MAX_PATCHES_WITH_RESIZE=true
125
+ import base64
126
+ from vllm import LLM, SamplingParams
127
+
128
+ llm = LLM(
129
+ model="PaddlePaddle/HPD-Parsing",
130
+ trust_remote_code=True,
131
+ max_model_len=16384,
132
+ limit_mm_per_prompt={"image": 1},
133
+ gpu_memory_utilization=0.9,
134
+ attention_backend="FLASHINFER",
135
+ enable_prefix_caching=True,
136
+ speculative_config={
137
+ "method": "medusa",
138
+ "model": "PaddlePaddle/HPD-Parsing/P-MTP",
139
+ "num_speculative_tokens": 6,
140
+ },
141
+ )
142
+ sampling_params = SamplingParams(temperature=0, max_tokens=8000)
143
+
144
+ with open("demo.png", "rb") as f:
145
+ image_base64 = base64.b64encode(f.read()).decode("utf-8")
146
+
147
+ messages = [
148
+ {
149
+ "role": "user",
150
+ "content": [
151
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}},
152
+ {"type": "text", "text": "document parsing with fork."},
153
+ ],
154
+ }
155
+ ]
156
+
157
+ outputs = llm.chat(messages=messages, sampling_params=sampling_params)
158
+ print(outputs[0].outputs[0].text)
159
+ ```
160
+
161
+
162
+
163
+ ## Inference with transformers
164
+
165
+ `model.generate_hpd(...)` reproduces the vLLM decoding paradigm in plain `transformers` (single image, batch size 1): the parent **layout** branch is decoded greedily and every `<FORK>` token spawns a **content** child branch that inherits the parent KV cache (shared-prefix reuse); the branches are then spliced back into one sequence. The `P-MTP/` head is used for speculative decoding when `use_mtp=True`.
166
+
167
+ The `P-MTP` weights ship inside the main checkpoint (keys `language_model.mtp.*`) and are loaded automatically by `from_pretrained`; `load_mtp_weights()` simply enables them. To load them from the standalone `P-MTP/` directory instead, pass its path.
168
+
169
+ Image preprocessing (dynamic tiling that mirrors vLLM's InternVL path with `MAX_PATCHES_WITH_RESIZE=true`) lives in [`image_preprocess.py`](./image_preprocess.py); `load_image` returns the stacked `pixel_values` tensor consumed by `generate_hpd`.
170
+
171
+ ```python
172
+ import torch
173
+ from transformers import AutoModel, AutoTokenizer
174
+
175
+ from image_preprocess import load_image
176
+
177
+ MODEL = "PaddlePaddle/HPD-Parsing"
178
+
179
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
180
+ model = AutoModel.from_pretrained(
181
+ MODEL, torch_dtype=torch.bfloat16, trust_remote_code=True,
182
+ ).eval().to(DEVICE)
183
+ tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True, use_fast=False)
184
+
185
+ model.load_mtp_weights() # or model.load_mtp_weights(f"{MODEL}/P-MTP")
186
+
187
+ pixel_values = load_image("test.png").to(torch.bfloat16).to(DEVICE)
188
+
189
+ response = model.generate_hpd(
190
+ tokenizer,
191
+ pixel_values,
192
+ "document parsing with fork.", # `document parsing.` for non-fork layout
193
+ dict(max_new_tokens=8000),
194
+ use_mtp=True, # P-MTP speculative decoding (parent branch)
195
+ num_speculative_tokens=6,
196
+ batch_children=False, # see note below
197
+ )
198
+ print(response)
199
+ ```
200
+
201
+ Key arguments:
202
+
203
+ - `use_mtp` (default `False`) / `num_speculative_tokens` (default `6`): enable P-MTP speculative decoding with the given draft length; requires `load_mtp_weights(...)`. Greedy verification keeps output identical to plain autoregressive decoding.
204
+ - `batch_children` (default `False`): serial per-child decoding that reuses the parent KV cache in place — lowest memory and fastest in practice. Set `True` to decode all children concurrently in one left-padded batch (same output, but heavier since `transformers` duplicates the shared prefix KV per child).
205
+ - `return_token_ids` (default `False`): also return the final token id list.
206
+
207
+ > Reference implementation only. Use the vLLM path for production throughput — its paged KV cache enables true concurrent branches with zero-copy prefix sharing.
208
+
209
+ ## Evaluation & Benchmark
210
+
211
+ Scripts under [`eval/`](./eval) reproduce both the throughput (TPS) and the OmniDocBench v1.6 accuracy numbers via a decoupled **infer -> convert -> evaluate** pipeline:
212
+
213
+ - [`eval/benchmark_tps.py`](./eval/benchmark_tps.py) — batched vLLM inference that reports TPS metrics and dumps the raw predictions in one run.
214
+ - [`eval/hpd_to_markdown.py`](./eval/hpd_to_markdown.py) — converts the `<BLOCK>...<CHILD>...` predictions into per-page markdown for OmniDocBench's end2end evaluation.
215
+
216
+ See [`eval/README.md`](./eval/README.md) for the full workflow, including how to run the official [OmniDocBench](https://github.com/opendatalab/OmniDocBench) evaluation on the generated markdown.
217
+
218
+ ## Acknowledgments
219
+
220
+ We would like to thank [InternVL](https://github.com/OpenGVLab/InternVL) and [Qwen3](https://github.com/QwenLM/Qwen3) for the backbone, [vLLM](https://github.com/vllm-project/vllm) for the serving framework, and [PaddleOCR-VL](https://github.com/PaddlePaddle/PaddleOCR), [MinerU](https://github.com/opendatalab/MinerU) and [OmniDocBench](https://github.com/opendatalab/OmniDocBench) for providing valuable data, model weights and benchmarks. We also appreciate everyone's contribution to this open-source project!
221
+
222
+ ## Citation
223
+
224
+ If you find HPD-Parsing helpful, feel free to give us a star and citation.
225
+
226
+ ```bibtex
227
+ @misc{wei2026hpdparsing,
228
+ title={HPD-Parsing: Hierarchical Parallel Document Parsing},
229
+ author={Shu Wei and Jingjing Wu and Lingshu Zhang and Qunyi Xie and Hao Zou and Le Xiang and Xu Fan and Yangliu Xu and Manhui Lin and Xiaolong Ma and Cheng Cui and Tengyu Du and YY},
230
+ year={2026},
231
+ }
232
+ ```
added_tokens.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</box>": 151677,
3
+ "</img>": 151670,
4
+ "</quad>": 151673,
5
+ "</ref>": 151675,
6
+ "</think>": 151668,
7
+ "</tool_call>": 151658,
8
+ "</tool_response>": 151666,
9
+ "<BLOCK>": 151678,
10
+ "<CHILD>": 151680,
11
+ "<FORK>": 151679,
12
+ "<IMG_CONTEXT>": 151671,
13
+ "<box>": 151676,
14
+ "<img>": 151669,
15
+ "<quad>": 151672,
16
+ "<ref>": 151674,
17
+ "<think>": 151667,
18
+ "<tool_call>": 151657,
19
+ "<tool_response>": 151665,
20
+ "<|box_end|>": 151649,
21
+ "<|box_start|>": 151648,
22
+ "<|endoftext|>": 151643,
23
+ "<|file_sep|>": 151664,
24
+ "<|fim_middle|>": 151660,
25
+ "<|fim_pad|>": 151662,
26
+ "<|fim_prefix|>": 151659,
27
+ "<|fim_suffix|>": 151661,
28
+ "<|im_end|>": 151645,
29
+ "<|im_start|>": 151644,
30
+ "<|image_pad|>": 151655,
31
+ "<|object_ref_end|>": 151647,
32
+ "<|object_ref_start|>": 151646,
33
+ "<|quad_end|>": 151651,
34
+ "<|quad_start|>": 151650,
35
+ "<|repo_name|>": 151663,
36
+ "<|video_pad|>": 151656,
37
+ "<|vision_end|>": 151653,
38
+ "<|vision_pad|>": 151654,
39
+ "<|vision_start|>": 151652
40
+ }
chat_template.jinja ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {% for message in messages %}{{'<|im_start|>' + message['role'] + '
2
+ '}}{% if message['content'] is string %}{{ message['content'] }}{% else %}{% for content in message['content'] %}{% if content['type'] == 'image' %}{{ '<image>
3
+ ' }}{% elif content['type'] == 'video' %}{{ '<video>
4
+ ' }}{% elif content['type'] == 'text' %}{{ content['text'] }}{% endif %}{% endfor %}{% endif %}{{'<|im_end|>
5
+ '}}{% endfor %}{% if add_generation_prompt %}{{'<|im_start|>assistant
6
+ ' }}{% endif %}
config.json ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "InternVLChatModel"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_internvl_chat.InternVLChatConfig",
7
+ "AutoModel": "modeling_internvl_chat.InternVLChatModel",
8
+ "AutoModelForCausalLM": "modeling_internvl_chat.InternVLChatModel"
9
+ },
10
+ "downsample_ratio": 0.5,
11
+ "dtype": "bfloat16",
12
+ "dynamic_image_size": true,
13
+ "eos_token_id": 151645,
14
+ "force_image_size": 448,
15
+ "hidden_size": 1024,
16
+ "keys_to_ignore_at_inference": [
17
+ "past_key_values"
18
+ ],
19
+ "fork_token_id": 151679, "child_token_id": 151680,
20
+ "llm_config": {
21
+ "_name_or_path": "/root/codespace/checkpoints/Qwen3-0.6B",
22
+ "architectures": [
23
+ "Qwen3ForCausalLM"
24
+ ],
25
+ "attention_bias": false,
26
+ "attention_dropout": 0.0,
27
+ "bos_token_id": 151643,
28
+ "debug": false,
29
+ "dtype": "bfloat16",
30
+ "eos_token_id": 151645,
31
+ "ep_size": 1,
32
+ "head_dim": 128,
33
+ "hidden_act": "silu",
34
+ "hidden_size": 1024,
35
+ "initializer_range": 0.02,
36
+ "intermediate_size": 3072,
37
+ "layer_types": [
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention",
42
+ "full_attention",
43
+ "full_attention",
44
+ "full_attention",
45
+ "full_attention",
46
+ "full_attention",
47
+ "full_attention",
48
+ "full_attention",
49
+ "full_attention",
50
+ "full_attention",
51
+ "full_attention",
52
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention",
57
+ "full_attention",
58
+ "full_attention",
59
+ "full_attention",
60
+ "full_attention",
61
+ "full_attention",
62
+ "full_attention",
63
+ "full_attention",
64
+ "full_attention",
65
+ "full_attention"
66
+ ],
67
+ "max_position_embeddings": 40960,
68
+ "max_window_layers": 28,
69
+ "micro_forward": false,
70
+ "model_type": "qwen3",
71
+ "num_attention_heads": 16,
72
+ "num_hidden_layers": 28,
73
+ "num_key_value_heads": 8,
74
+ "pad_token_id": 151643,
75
+ "rms_norm_eps": 1e-06,
76
+ "rope_scaling": null,
77
+ "rope_theta": 1000000,
78
+ "skip_checkpoint": false,
79
+ "sliding_window": null,
80
+ "use_cache": false,
81
+ "use_deepep": false,
82
+ "use_sliding_window": false,
83
+ "vocab_size": 151681
84
+ },
85
+ "max_dynamic_patch": 24,
86
+ "min_dynamic_patch": 1,
87
+ "model_type": "internvl_chat",
88
+ "output_attentions": false,
89
+ "pad2square": false,
90
+ "pad_token_id": 151643,
91
+ "ps_version": "v2",
92
+ "select_layer": -1,
93
+ "template": "internvl2_5",
94
+ "tie_word_embeddings": false,
95
+ "transformers_version": null,
96
+ "use_backbone_lora": 0,
97
+ "use_llm_lora": 0,
98
+ "use_thumbnail": true,
99
+ "vision_config": {
100
+ "architectures": [
101
+ "InternVisionModel"
102
+ ],
103
+ "attention_dropout": 0.0,
104
+ "auto_map": {
105
+ "AutoConfig": "configuration_intern_vit.InternVisionConfig",
106
+ "AutoModel": "modeling_intern_vit.InternVisionModel"
107
+ },
108
+ "drop_path_rate": 0.1,
109
+ "dropout": 0.0,
110
+ "dtype": "bfloat16",
111
+ "hidden_act": "gelu",
112
+ "hidden_size": 1024,
113
+ "image_size": 448,
114
+ "initializer_factor": 1.0,
115
+ "initializer_range": 0.02,
116
+ "intermediate_size": 4096,
117
+ "layer_norm_eps": 1e-06,
118
+ "model_type": "intern_vit_6b",
119
+ "norm_type": "layer_norm",
120
+ "num_attention_heads": 16,
121
+ "num_channels": 3,
122
+ "num_hidden_layers": 24,
123
+ "pad_token_id": 151643,
124
+ "patch_size": 14,
125
+ "qk_normalization": false,
126
+ "qkv_bias": true,
127
+ "use_fa3": false,
128
+ "use_flash_attn": true
129
+ },
130
+ "vocab_size": 151681
131
+ }
configuration_intern_vit.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --------------------------------------------------------
2
+ # InternVL
3
+ # Copyright (c) 2024 OpenGVLab
4
+ # Licensed under The MIT License [see LICENSE for details]
5
+ # --------------------------------------------------------
6
+ import os
7
+ from typing import Union
8
+
9
+ from transformers.configuration_utils import PretrainedConfig
10
+ from transformers.utils import logging
11
+
12
+ logger = logging.get_logger(__name__)
13
+
14
+
15
+ class InternVisionConfig(PretrainedConfig):
16
+ r"""
17
+ This is the configuration class to store the configuration of a [`InternVisionModel`]. It is used to
18
+ instantiate a vision encoder according to the specified arguments, defining the model architecture.
19
+
20
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
21
+ documentation from [`PretrainedConfig`] for more information.
22
+
23
+ Args:
24
+ num_channels (`int`, *optional*, defaults to 3):
25
+ Number of color channels in the input images (e.g., 3 for RGB).
26
+ patch_size (`int`, *optional*, defaults to 14):
27
+ The size (resolution) of each patch.
28
+ image_size (`int`, *optional*, defaults to 224):
29
+ The size (resolution) of each image.
30
+ qkv_bias (`bool`, *optional*, defaults to `False`):
31
+ Whether to add a bias to the queries and values in the self-attention layers.
32
+ hidden_size (`int`, *optional*, defaults to 3200):
33
+ Dimensionality of the encoder layers and the pooler layer.
34
+ num_attention_heads (`int`, *optional*, defaults to 25):
35
+ Number of attention heads for each attention layer in the Transformer encoder.
36
+ intermediate_size (`int`, *optional*, defaults to 12800):
37
+ Dimensionality of the "intermediate" (i.e., feed-forward) layer in the Transformer encoder.
38
+ qk_normalization (`bool`, *optional*, defaults to `True`):
39
+ Whether to normalize the queries and keys in the self-attention layers.
40
+ num_hidden_layers (`int`, *optional*, defaults to 48):
41
+ Number of hidden layers in the Transformer encoder.
42
+ use_flash_attn (`bool`, *optional*, defaults to `True`):
43
+ Whether to use flash attention mechanism.
44
+ hidden_act (`str` or `function`, *optional*, defaults to `"gelu"`):
45
+ The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
46
+ `"relu"`, `"selu"` and `"gelu_new"` ``"gelu"` are supported.
47
+ layer_norm_eps (`float`, *optional*, defaults to 1e-6):
48
+ The epsilon used by the layer normalization layers.
49
+ dropout (`float`, *optional*, defaults to 0.0):
50
+ The dropout probability for all fully connected layers in the embeddings, encoder, and pooler.
51
+ drop_path_rate (`float`, *optional*, defaults to 0.0):
52
+ Dropout rate for stochastic depth.
53
+ attention_dropout (`float`, *optional*, defaults to 0.0):
54
+ The dropout ratio for the attention probabilities.
55
+ initializer_range (`float`, *optional*, defaults to 0.02):
56
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
57
+ initializer_factor (`float`, *optional*, defaults to 0.1):
58
+ A factor for layer scale.
59
+ """
60
+
61
+ model_type = 'intern_vit_6b'
62
+
63
+ def __init__(
64
+ self,
65
+ num_channels=3,
66
+ patch_size=14,
67
+ image_size=224,
68
+ qkv_bias=False,
69
+ hidden_size=3200,
70
+ num_attention_heads=25,
71
+ intermediate_size=12800,
72
+ qk_normalization=True,
73
+ num_hidden_layers=48,
74
+ use_flash_attn=True,
75
+ hidden_act='gelu',
76
+ norm_type='rms_norm',
77
+ layer_norm_eps=1e-6,
78
+ dropout=0.0,
79
+ drop_path_rate=0.0,
80
+ attention_dropout=0.0,
81
+ initializer_range=0.02,
82
+ initializer_factor=0.1,
83
+ **kwargs,
84
+ ):
85
+ super().__init__(**kwargs)
86
+
87
+ self.hidden_size = hidden_size
88
+ self.intermediate_size = intermediate_size
89
+ self.dropout = dropout
90
+ self.drop_path_rate = drop_path_rate
91
+ self.num_hidden_layers = num_hidden_layers
92
+ self.num_attention_heads = num_attention_heads
93
+ self.num_channels = num_channels
94
+ self.patch_size = patch_size
95
+ self.image_size = image_size
96
+ self.initializer_range = initializer_range
97
+ self.initializer_factor = initializer_factor
98
+ self.attention_dropout = attention_dropout
99
+ self.layer_norm_eps = layer_norm_eps
100
+ self.hidden_act = hidden_act
101
+ self.norm_type = norm_type
102
+ self.qkv_bias = qkv_bias
103
+ self.qk_normalization = qk_normalization
104
+ self.use_flash_attn = use_flash_attn
105
+
106
+ @classmethod
107
+ def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> 'PretrainedConfig':
108
+ config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
109
+
110
+ if 'vision_config' in config_dict:
111
+ config_dict = config_dict['vision_config']
112
+
113
+ if 'model_type' in config_dict and hasattr(cls, 'model_type') and config_dict['model_type'] != cls.model_type:
114
+ logger.warning(
115
+ f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
116
+ f'{cls.model_type}. This is not supported for all configurations of models and can yield errors.'
117
+ )
118
+
119
+ return cls.from_dict(config_dict, **kwargs)
configuration_internvl_chat.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --------------------------------------------------------
2
+ # InternVL
3
+ # Copyright (c) 2024 OpenGVLab
4
+ # Licensed under The MIT License [see LICENSE for details]
5
+ # --------------------------------------------------------
6
+
7
+ import copy
8
+ from typing import Dict, Any, Optional
9
+
10
+ from transformers.configuration_utils import PretrainedConfig
11
+ from transformers.utils import logging
12
+
13
+ from .configuration_intern_vit import InternVisionConfig
14
+
15
+ logger = logging.get_logger(__name__)
16
+
17
+
18
+ class InternVLChatConfig(PretrainedConfig):
19
+ model_type = 'internvl_chat'
20
+ is_composition = True
21
+
22
+ def __init__(
23
+ self,
24
+ vision_config: Optional[Dict[str, Any]] = None,
25
+ llm_config: Optional[Dict[str, Any]] = None,
26
+ use_backbone_lora=0,
27
+ use_llm_lora=0,
28
+ select_layer=-1,
29
+ force_image_size=None,
30
+ downsample_ratio=0.5,
31
+ template=None,
32
+ dynamic_image_size=False,
33
+ use_thumbnail=False,
34
+ ps_version="v1",
35
+ min_dynamic_patch=1,
36
+ max_dynamic_patch=6,
37
+ **kwargs,
38
+ ):
39
+ super().__init__(**kwargs)
40
+
41
+ if vision_config is None:
42
+ vision_config = {'architectures': ['InternVisionModel']}
43
+ logger.info('vision_config is None. Initializing the InternVisionConfig with default values.')
44
+
45
+ if llm_config is None:
46
+ llm_config = {'architectures': ['Qwen2ForCausalLM']}
47
+ logger.info('llm_config is None. Initializing the LlamaConfig config with default values (`LlamaConfig`).')
48
+ assert 'architectures' in llm_config, "Should specify architecture in llm_config"
49
+
50
+ if isinstance(vision_config, dict):
51
+ self.vision_config = InternVisionConfig(**vision_config)
52
+ else:
53
+ self.vision_config = vision_config
54
+
55
+ if isinstance(llm_config, dict):
56
+ architecture: str = llm_config['architectures'][0]
57
+ if architecture == 'LlamaForCausalLM':
58
+ from transformers import LlamaConfig
59
+ self.llm_config = LlamaConfig(**llm_config)
60
+ elif architecture == 'Qwen2ForCausalLM':
61
+ from transformers import Qwen2Config
62
+ self.llm_config = Qwen2Config(**llm_config)
63
+ elif architecture == 'Qwen3MoeForCausalLM':
64
+ from transformers import Qwen3MoeConfig
65
+ self.llm_config = Qwen3MoeConfig(**llm_config)
66
+ elif architecture == 'Qwen3ForCausalLM':
67
+ from transformers import Qwen3Config
68
+ self.llm_config = Qwen3Config(**llm_config)
69
+ else:
70
+ raise ValueError('Unsupported architecture: {}'.format(architecture))
71
+ else:
72
+ self.llm_config = llm_config
73
+
74
+ self.use_backbone_lora = use_backbone_lora
75
+ self.use_llm_lora = use_llm_lora
76
+ self.select_layer = select_layer
77
+ self.force_image_size = force_image_size
78
+ self.downsample_ratio = downsample_ratio
79
+ self.template = template
80
+ self.dynamic_image_size = dynamic_image_size
81
+ self.use_thumbnail = use_thumbnail
82
+ self.ps_version = ps_version # pixel shuffle version
83
+ self.min_dynamic_patch = min_dynamic_patch
84
+ self.max_dynamic_patch = max_dynamic_patch
85
+ self.tie_word_embeddings = self.llm_config.tie_word_embeddings
86
+
87
+ logger.info(f'vision_select_layer: {self.select_layer}')
88
+ logger.info(f'ps_version: {self.ps_version}')
89
+ logger.info(f'min_dynamic_patch: {self.min_dynamic_patch}')
90
+ logger.info(f'max_dynamic_patch: {self.max_dynamic_patch}')
91
+
92
+ def to_dict(self):
93
+ """
94
+ Serializes this instance to a Python dictionary. Override the default [`~PretrainedConfig.to_dict`].
95
+
96
+ Returns:
97
+ `Dict[str, any]`: Dictionary of all the attributes that make up this configuration instance,
98
+ """
99
+ output = copy.deepcopy(self.__dict__)
100
+ output['vision_config'] = self.vision_config.to_dict()
101
+ output['llm_config'] = self.llm_config.to_dict()
102
+ output['model_type'] = self.__class__.model_type
103
+ output['use_backbone_lora'] = self.use_backbone_lora
104
+ output['use_llm_lora'] = self.use_llm_lora
105
+ output['select_layer'] = self.select_layer
106
+ output['force_image_size'] = self.force_image_size
107
+ output['downsample_ratio'] = self.downsample_ratio
108
+ output['template'] = self.template
109
+ output['dynamic_image_size'] = self.dynamic_image_size
110
+ output['use_thumbnail'] = self.use_thumbnail
111
+ output['ps_version'] = self.ps_version
112
+ output['min_dynamic_patch'] = self.min_dynamic_patch
113
+ output['max_dynamic_patch'] = self.max_dynamic_patch
114
+
115
+ return output
conversation.py ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Conversation prompt templates.
3
+
4
+ We kindly request that you import fastchat instead of copying this file if you wish to use it.
5
+ If you have changes in mind, please contribute back so the community can benefit collectively and continue to maintain these valuable templates.
6
+
7
+ Modified from https://github.com/lm-sys/FastChat/blob/main/fastchat/conversation.py
8
+ """
9
+
10
+ import dataclasses
11
+ from enum import IntEnum, auto
12
+ from typing import Dict, List, Tuple, Union
13
+
14
+
15
+ class SeparatorStyle(IntEnum):
16
+ """Separator styles."""
17
+
18
+ ADD_COLON_SINGLE = auto()
19
+ ADD_COLON_TWO = auto()
20
+ ADD_COLON_SPACE_SINGLE = auto()
21
+ NO_COLON_SINGLE = auto()
22
+ NO_COLON_TWO = auto()
23
+ ADD_NEW_LINE_SINGLE = auto()
24
+ LLAMA2 = auto()
25
+ CHATGLM = auto()
26
+ CHATML = auto()
27
+ CHATINTERN = auto()
28
+ DOLLY = auto()
29
+ RWKV = auto()
30
+ PHOENIX = auto()
31
+ ROBIN = auto()
32
+ FALCON_CHAT = auto()
33
+ CHATGLM3 = auto()
34
+ INTERNVL_ZH = auto()
35
+ MPT = auto()
36
+
37
+
38
+ @dataclasses.dataclass
39
+ class Conversation:
40
+ """A class that manages prompt templates and keeps all conversation history."""
41
+
42
+ # The name of this template
43
+ name: str
44
+ # The template of the system prompt
45
+ system_template: str = '{system_message}'
46
+ # The system message
47
+ system_message: str = ''
48
+ # The names of two roles
49
+ roles: Tuple[str] = ('USER', 'ASSISTANT')
50
+ # All messages. Each item is (role, message).
51
+ messages: List[List[str]] = ()
52
+ # The number of few shot examples
53
+ offset: int = 0
54
+ # The separator style and configurations
55
+ sep_style: SeparatorStyle = SeparatorStyle.ADD_COLON_SINGLE
56
+ sep: str = '\n'
57
+ sep2: str = None
58
+ # Stop criteria (the default one is EOS token)
59
+ stop_str: Union[str, List[str]] = None
60
+ # Stops generation if meeting any token in this list
61
+ stop_token_ids: List[int] = None
62
+
63
+ def get_prompt(self) -> str:
64
+ """Get the prompt for generation."""
65
+ system_prompt = self.system_template.format(system_message=self.system_message)
66
+ if self.sep_style == SeparatorStyle.ADD_COLON_SINGLE:
67
+ ret = system_prompt + self.sep
68
+ for role, message in self.messages:
69
+ if message:
70
+ ret += role + ': ' + message + self.sep
71
+ else:
72
+ ret += role + ':'
73
+ return ret
74
+ elif self.sep_style == SeparatorStyle.ADD_COLON_TWO:
75
+ seps = [self.sep, self.sep2]
76
+ ret = system_prompt + seps[0]
77
+ for i, (role, message) in enumerate(self.messages):
78
+ if message:
79
+ ret += role + ': ' + message + seps[i % 2]
80
+ else:
81
+ ret += role + ':'
82
+ return ret
83
+ elif self.sep_style == SeparatorStyle.ADD_COLON_SPACE_SINGLE:
84
+ ret = system_prompt + self.sep
85
+ for role, message in self.messages:
86
+ if message:
87
+ ret += role + ': ' + message + self.sep
88
+ else:
89
+ ret += role + ': ' # must be end with a space
90
+ return ret
91
+ elif self.sep_style == SeparatorStyle.ADD_NEW_LINE_SINGLE:
92
+ ret = '' if system_prompt == '' else system_prompt + self.sep
93
+ for role, message in self.messages:
94
+ if message:
95
+ ret += role + '\n' + message + self.sep
96
+ else:
97
+ ret += role + '\n'
98
+ return ret
99
+ elif self.sep_style == SeparatorStyle.NO_COLON_SINGLE:
100
+ ret = system_prompt
101
+ for role, message in self.messages:
102
+ if message:
103
+ ret += role + message + self.sep
104
+ else:
105
+ ret += role
106
+ return ret
107
+ elif self.sep_style == SeparatorStyle.NO_COLON_TWO:
108
+ seps = [self.sep, self.sep2]
109
+ ret = system_prompt
110
+ for i, (role, message) in enumerate(self.messages):
111
+ if message:
112
+ ret += role + message + seps[i % 2]
113
+ else:
114
+ ret += role
115
+ return ret
116
+ elif self.sep_style == SeparatorStyle.RWKV:
117
+ ret = system_prompt
118
+ for i, (role, message) in enumerate(self.messages):
119
+ if message:
120
+ ret += (
121
+ role
122
+ + ': '
123
+ + message.replace('\r\n', '\n').replace('\n\n', '\n')
124
+ )
125
+ ret += '\n\n'
126
+ else:
127
+ ret += role + ':'
128
+ return ret
129
+ elif self.sep_style == SeparatorStyle.LLAMA2:
130
+ seps = [self.sep, self.sep2]
131
+ if self.system_message:
132
+ ret = system_prompt
133
+ else:
134
+ ret = '[INST] '
135
+ for i, (role, message) in enumerate(self.messages):
136
+ tag = self.roles[i % 2]
137
+ if message:
138
+ if i == 0:
139
+ ret += message + ' '
140
+ else:
141
+ ret += tag + ' ' + message + seps[i % 2]
142
+ else:
143
+ ret += tag
144
+ return ret
145
+ elif self.sep_style == SeparatorStyle.CHATGLM:
146
+ # source: https://huggingface.co/THUDM/chatglm-6b/blob/1d240ba371910e9282298d4592532d7f0f3e9f3e/modeling_chatglm.py#L1302-L1308
147
+ # source2: https://huggingface.co/THUDM/chatglm2-6b/blob/e186c891cf64310ac66ef10a87e6635fa6c2a579/modeling_chatglm.py#L926
148
+ round_add_n = 1 if self.name == 'chatglm2' else 0
149
+ if system_prompt:
150
+ ret = system_prompt + self.sep
151
+ else:
152
+ ret = ''
153
+
154
+ for i, (role, message) in enumerate(self.messages):
155
+ if i % 2 == 0:
156
+ ret += f'[Round {i//2 + round_add_n}]{self.sep}'
157
+
158
+ if message:
159
+ ret += f'{role}:{message}{self.sep}'
160
+ else:
161
+ ret += f'{role}:'
162
+ return ret
163
+ elif self.sep_style == SeparatorStyle.CHATML:
164
+ ret = '' if system_prompt == '' else system_prompt + self.sep + '\n'
165
+ for role, message in self.messages:
166
+ if message:
167
+ ret += role + '\n' + message + self.sep + '\n'
168
+ else:
169
+ ret += role + '\n'
170
+ return ret
171
+ elif self.sep_style == SeparatorStyle.CHATGLM3:
172
+ ret = ''
173
+ if self.system_message:
174
+ ret += system_prompt
175
+ for role, message in self.messages:
176
+ if message:
177
+ ret += role + '\n' + ' ' + message
178
+ else:
179
+ ret += role
180
+ return ret
181
+ elif self.sep_style == SeparatorStyle.CHATINTERN:
182
+ # source: https://huggingface.co/internlm/internlm-chat-7b-8k/blob/bd546fa984b4b0b86958f56bf37f94aa75ab8831/modeling_internlm.py#L771
183
+ seps = [self.sep, self.sep2]
184
+ ret = system_prompt
185
+ for i, (role, message) in enumerate(self.messages):
186
+ # if i % 2 == 0:
187
+ # ret += "<s>"
188
+ if message:
189
+ ret += role + ':' + message + seps[i % 2] + '\n'
190
+ else:
191
+ ret += role + ':'
192
+ return ret
193
+ elif self.sep_style == SeparatorStyle.DOLLY:
194
+ seps = [self.sep, self.sep2]
195
+ ret = system_prompt
196
+ for i, (role, message) in enumerate(self.messages):
197
+ if message:
198
+ ret += role + ':\n' + message + seps[i % 2]
199
+ if i % 2 == 1:
200
+ ret += '\n\n'
201
+ else:
202
+ ret += role + ':\n'
203
+ return ret
204
+ elif self.sep_style == SeparatorStyle.PHOENIX:
205
+ ret = system_prompt
206
+ for role, message in self.messages:
207
+ if message:
208
+ ret += role + ': ' + '<s>' + message + '</s>'
209
+ else:
210
+ ret += role + ': ' + '<s>'
211
+ return ret
212
+ elif self.sep_style == SeparatorStyle.ROBIN:
213
+ ret = system_prompt + self.sep
214
+ for role, message in self.messages:
215
+ if message:
216
+ ret += role + ':\n' + message + self.sep
217
+ else:
218
+ ret += role + ':\n'
219
+ return ret
220
+ elif self.sep_style == SeparatorStyle.FALCON_CHAT:
221
+ ret = ''
222
+ if self.system_message:
223
+ ret += system_prompt + self.sep
224
+ for role, message in self.messages:
225
+ if message:
226
+ ret += role + ': ' + message + self.sep
227
+ else:
228
+ ret += role + ':'
229
+
230
+ return ret
231
+ elif self.sep_style == SeparatorStyle.INTERNVL_ZH:
232
+ seps = [self.sep, self.sep2]
233
+ ret = self.system_message + seps[0]
234
+ for i, (role, message) in enumerate(self.messages):
235
+ if message:
236
+ ret += role + ': ' + message + seps[i % 2]
237
+ else:
238
+ ret += role + ':'
239
+ return ret
240
+ elif self.sep_style == SeparatorStyle.MPT:
241
+ ret = system_prompt + self.sep
242
+ for role, message in self.messages:
243
+ if message:
244
+ if type(message) is tuple:
245
+ message, _, _ = message
246
+ ret += role + message + self.sep
247
+ else:
248
+ ret += role
249
+ return ret
250
+ else:
251
+ raise ValueError(f'Invalid style: {self.sep_style}')
252
+
253
+ def set_system_message(self, system_message: str):
254
+ """Set the system message."""
255
+ self.system_message = system_message
256
+
257
+ def append_message(self, role: str, message: str):
258
+ """Append a new message."""
259
+ self.messages.append([role, message])
260
+
261
+ def update_last_message(self, message: str):
262
+ """Update the last output.
263
+
264
+ The last message is typically set to be None when constructing the prompt,
265
+ so we need to update it in-place after getting the response from a model.
266
+ """
267
+ self.messages[-1][1] = message
268
+
269
+ def to_gradio_chatbot(self):
270
+ """Convert the conversation to gradio chatbot format."""
271
+ ret = []
272
+ for i, (role, msg) in enumerate(self.messages[self.offset :]):
273
+ if i % 2 == 0:
274
+ ret.append([msg, None])
275
+ else:
276
+ ret[-1][-1] = msg
277
+ return ret
278
+
279
+ def to_openai_api_messages(self):
280
+ """Convert the conversation to OpenAI chat completion format."""
281
+ ret = [{'role': 'system', 'content': self.system_message}]
282
+
283
+ for i, (_, msg) in enumerate(self.messages[self.offset :]):
284
+ if i % 2 == 0:
285
+ ret.append({'role': 'user', 'content': msg})
286
+ else:
287
+ if msg is not None:
288
+ ret.append({'role': 'assistant', 'content': msg})
289
+ return ret
290
+
291
+ def copy(self):
292
+ return Conversation(
293
+ name=self.name,
294
+ system_template=self.system_template,
295
+ system_message=self.system_message,
296
+ roles=self.roles,
297
+ messages=[[x, y] for x, y in self.messages],
298
+ offset=self.offset,
299
+ sep_style=self.sep_style,
300
+ sep=self.sep,
301
+ sep2=self.sep2,
302
+ stop_str=self.stop_str,
303
+ stop_token_ids=self.stop_token_ids,
304
+ )
305
+
306
+ def dict(self):
307
+ return {
308
+ 'template_name': self.name,
309
+ 'system_message': self.system_message,
310
+ 'roles': self.roles,
311
+ 'messages': self.messages,
312
+ 'offset': self.offset,
313
+ }
314
+
315
+
316
+ # A global registry for all conversation templates
317
+ conv_templates: Dict[str, Conversation] = {}
318
+
319
+
320
+ def register_conv_template(template: Conversation, override: bool = False):
321
+ """Register a new conversation template."""
322
+ if not override:
323
+ assert (
324
+ template.name not in conv_templates
325
+ ), f'{template.name} has been registered.'
326
+
327
+ conv_templates[template.name] = template
328
+
329
+
330
+ def get_conv_template(name: str) -> Conversation:
331
+ """Get a conversation template."""
332
+ return conv_templates[name].copy()
333
+
334
+
335
+ # Both Hermes-2 and internlm2-chat are chatml-format conversation templates. The difference
336
+ # is that during training, the preprocessing function for the Hermes-2 template doesn't add
337
+ # <s> at the beginning of the tokenized sequence, while the internlm2-chat template does.
338
+ # Therefore, they are completely equivalent during inference.
339
+ register_conv_template(
340
+ Conversation(
341
+ name='Hermes-2',
342
+ system_template='<|im_start|>system\n{system_message}',
343
+ # note: The new system prompt was not used here to avoid changes in benchmark performance.
344
+ # system_message='我是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。',
345
+ system_message='你是由上海人工智能实验室联合商汤科技开发的书生多模态大模型,英文名叫InternVL, 是一个有用无害的人工智能助手。',
346
+ roles=('<|im_start|>user\n', '<|im_start|>assistant\n'),
347
+ sep_style=SeparatorStyle.MPT,
348
+ sep='<|im_end|>',
349
+ stop_str='<|endoftext|>',
350
+ )
351
+ )
352
+
353
+
354
+ register_conv_template(
355
+ Conversation(
356
+ name='internlm2-chat',
357
+ system_template='<|im_start|>system\n{system_message}',
358
+ # note: The new system prompt was not used here to avoid changes in benchmark performance.
359
+ # system_message='我是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。',
360
+ system_message='你是由上海人工智能实验室联合商汤科技开发的书生多模态大模型,英文名叫InternVL, 是一个有用无害的人工智能助手。',
361
+ roles=('<|im_start|>user\n', '<|im_start|>assistant\n'),
362
+ sep_style=SeparatorStyle.MPT,
363
+ sep='<|im_end|>',
364
+ )
365
+ )
366
+
367
+
368
+ register_conv_template(
369
+ Conversation(
370
+ name='phi3-chat',
371
+ system_template='<|system|>\n{system_message}',
372
+ # note: The new system prompt was not used here to avoid changes in benchmark performance.
373
+ # system_message='我是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。',
374
+ system_message='你是由上海人工智能实验室联合商汤科技开发的书生多模态大模型,英文名叫InternVL, 是一个有用无害的人工智能助手。',
375
+ roles=('<|user|>\n', '<|assistant|>\n'),
376
+ sep_style=SeparatorStyle.MPT,
377
+ sep='<|end|>',
378
+ )
379
+ )
380
+
381
+
382
+ register_conv_template(
383
+ Conversation(
384
+ name='internvl2_5',
385
+ system_template='<|im_start|>system\n{system_message}',
386
+ system_message='你是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。',
387
+ roles=('<|im_start|>user\n', '<|im_start|>assistant\n'),
388
+ sep_style=SeparatorStyle.MPT,
389
+ sep='<|im_end|>\n',
390
+ )
391
+ )
eval/README.md ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HPD-Parsing Evaluation & Benchmark
2
+
3
+ Scripts to reproduce the HPD-Parsing **throughput (TPS)** numbers and the **OmniDocBench v1.6** accuracy, using the customized vLLM build (`<FORK>`/`<CHILD>` hierarchical parallel decoding + P-MTP speculative decoding).
4
+
5
+ ```
6
+ eval/
7
+ ├── benchmark_tps.py # vLLM batch inference: measures TPS and dumps raw predictions
8
+ └── hpd_to_markdown.py # converts <BLOCK>...<CHILD>... predictions to per-page markdown
9
+ ```
10
+
11
+ The pipeline is decoupled into three steps: **infer → convert → evaluate**. A single inference run produces both the speed metrics and the prediction file that feeds the accuracy evaluation.
12
+
13
+ ## Prerequisites
14
+
15
+ - The customized vLLM build that supports `<FORK>`/`<CHILD>` decoding and `RepetitionDetectionParams` (see the main [README](../README.md) install section).
16
+ - The model weights `PaddlePaddle/HPD-Parsing` (including the `P-MTP/` speculative head).
17
+ - The [OmniDocBench](https://github.com/opendatalab/OmniDocBench) evaluation dataset (`OmniDocBench.json` + `images/`) and its evaluation suite (for step 3).
18
+
19
+ ## Step 1 — Throughput benchmark + dump predictions
20
+
21
+ `benchmark_tps.py` runs batched inference over an image folder, times the whole batch loop with `time.perf_counter()`, and reports TPS metrics. It also writes the raw predictions used by the accuracy evaluation, so you only run the model once.
22
+
23
+ ```bash
24
+ MAX_PATCHES_WITH_RESIZE=true python eval/benchmark_tps.py
25
+ ```
26
+
27
+ Edit the variables at the top of `__main__` to match your setup:
28
+
29
+ - `model_path` / `model_path_medusa` — model dir and its `P-MTP/` head (default `PaddlePaddle/HPD-Parsing/`).
30
+ - `root` — image folder (default `OmniDocBench_1_6/images/`).
31
+ - `prompt` — `document parsing with fork.` enables hierarchical parallel decoding; use `document parsing.` for standard full-page parsing.
32
+ - `batch_size` (default `512`), `max_model_len`, `max_num_seqs`, and the `speculative_config` (`num_speculative_tokens=6` for P-MTP). To measure the autoregressive baseline, remove `speculative_config`.
33
+
34
+ Outputs:
35
+
36
+ - Predictions -> `batch_512_pred_HPD-Parsing.json` (a list of `{index, img_path, pred}`), consumed by step 2.
37
+ - Metrics -> `records/<ckpt>.txt`: Total Time, Throughput (Requests/s), Input/Output/Total Tokens/s, and average tokens per request.
38
+
39
+ ## Step 2 — Convert predictions to markdown
40
+
41
+ `hpd_to_markdown.py` parses each `<BLOCK> <type> [bbox] <CHILD> <content>` prediction into a reading-order markdown file named after the source image (`<image_stem>.md`), which is the input format OmniDocBench's end2end evaluation expects.
42
+
43
+ ```bash
44
+ python eval/hpd_to_markdown.py \
45
+ --input batch_512_pred_HPD-Parsing.json \
46
+ --out-md pred_md/HPD-Parsing/
47
+ ```
48
+
49
+ ## Step 3 — Run OmniDocBench end2end evaluation
50
+
51
+ Use the official [OmniDocBench](https://github.com/opendatalab/OmniDocBench) suite. Point its end2end config at the markdown folder from step 2 and the ground-truth `OmniDocBench.json`, then run its evaluator to get the overall score and per-metric breakdown (text / formula / table / reading order).
52
+
53
+ ```bash
54
+ git clone https://github.com/opendatalab/OmniDocBench.git
55
+ # set the prediction folder to pred_md/HPD-Parsing/ and gt to OmniDocBench.json
56
+ # in the end2end config, then run the OmniDocBench evaluation entry point
57
+ ```
58
+
59
+ > The OmniDocBench evaluation code is a separate project with its own license and is intentionally not vendored here.
eval/benchmark_tps.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HPD-Parsing throughput (TPS) benchmark on vLLM.
2
+
3
+ Batched offline inference over an image folder; reports TPS metrics and dumps
4
+ the raw predictions consumed by hpd_to_markdown.py (model is run once).
5
+
6
+ MAX_PATCHES_WITH_RESIZE=true python eval/benchmark_tps.py
7
+ """
8
+ import os
9
+
10
+ os.environ.setdefault("MAX_PATCHES_WITH_RESIZE", "true") # before importing vLLM
11
+
12
+ import base64
13
+ import json
14
+ import time
15
+ from typing import Any, Dict
16
+
17
+ from vllm import LLM, SamplingParams
18
+ from vllm.sampling_params import RepetitionDetectionParams
19
+
20
+ PRED_OUT = "batch_512_pred_HPD-Parsing.json"
21
+
22
+ llm = None
23
+ sampling_params = None
24
+
25
+
26
+ def encode_image_to_base64(image_path: str) -> str:
27
+ with open(image_path, "rb") as f:
28
+ return base64.b64encode(f.read()).decode("utf-8")
29
+
30
+
31
+ def batch_predict_vllm(tasks, batch_size=512):
32
+ all_outputs = []
33
+ records = []
34
+ for start in range(0, len(tasks), batch_size):
35
+ batch_tasks = tasks[start:start + batch_size]
36
+ messages_list = [
37
+ [{
38
+ "role": "user",
39
+ "content": [
40
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}},
41
+ {"type": "text", "text": question},
42
+ ],
43
+ }]
44
+ for question, img_b64, _ in batch_tasks
45
+ ]
46
+ outputs = llm.chat(messages=messages_list, sampling_params=sampling_params)
47
+ all_outputs.extend(outputs)
48
+ for j, out in enumerate(outputs):
49
+ records.append({
50
+ "index": start + j,
51
+ "img_path": batch_tasks[j][-1],
52
+ "pred": out.outputs[0].text,
53
+ })
54
+ return all_outputs, records
55
+
56
+
57
+ def parse_ckpt_name(model_path: str) -> str:
58
+ parts = model_path.rstrip('/').split('/')
59
+ return '_'.join(p for p in parts if p and p not in ['checkpoints', 'output', 'rank_0'])
60
+
61
+
62
+ def record_metrics(model_path: str, metrics: Dict[str, Any], output_dir: str = "./records"):
63
+ os.makedirs(output_dir, exist_ok=True)
64
+ output_file = os.path.join(output_dir, f"{parse_ckpt_name(model_path)}.txt")
65
+ with open(output_file, 'w', encoding='utf-8') as f:
66
+ f.write(f"Model Path: {model_path}\n")
67
+ f.write(f"Checkpoint: {parse_ckpt_name(model_path)}\n")
68
+ f.write("=" * 50 + "\n")
69
+ for key, value in metrics.items():
70
+ f.write(f"{key}: {value}\n")
71
+ print(f"Metrics saved to: {output_file}")
72
+
73
+
74
+ if __name__ == '__main__':
75
+ model_path = "PaddlePaddle/HPD-Parsing/"
76
+ model_path_medusa = "PaddlePaddle/HPD-Parsing/P-MTP/"
77
+ root = "OmniDocBench_1_6/images/"
78
+ prompt = "document parsing with fork."
79
+ batch_size = 512
80
+
81
+ llm = LLM(
82
+ model=model_path,
83
+ tensor_parallel_size=1,
84
+ gpu_memory_utilization=0.9,
85
+ enable_prefix_caching=True,
86
+ attention_backend="FLASHINFER",
87
+ trust_remote_code=True,
88
+ max_model_len=24000,
89
+ max_num_seqs=512,
90
+ speculative_config={"method": "medusa", "model": model_path_medusa, "num_speculative_tokens": 6},
91
+ )
92
+ sampling_params = SamplingParams(
93
+ temperature=0,
94
+ max_tokens=8000,
95
+ stop=None,
96
+ repetition_detection=RepetitionDetectionParams(
97
+ min_pattern_size=64,
98
+ max_pattern_size=128,
99
+ min_count=10,
100
+ ),
101
+ )
102
+
103
+ valid_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp'}
104
+ image_paths = [
105
+ os.path.join(root, p) for p in os.listdir(root)
106
+ if os.path.splitext(p)[1].lower() in valid_extensions
107
+ ]
108
+ tasks = [(prompt, encode_image_to_base64(p), p) for p in image_paths]
109
+ print(f"Total images: {len(tasks)}")
110
+
111
+ start_time = time.perf_counter()
112
+ outputs, records = batch_predict_vllm(tasks, batch_size=batch_size)
113
+ end_time = time.perf_counter()
114
+
115
+ with open(PRED_OUT, "w", encoding="utf-8") as fp:
116
+ json.dump(records, fp, indent=4, ensure_ascii=False)
117
+ print(f"Predictions saved to: {PRED_OUT}")
118
+
119
+ total_time = end_time - start_time
120
+ total_input_tokens = sum(len(out.prompt_token_ids) for out in outputs)
121
+ total_output_tokens = sum(len(out.outputs[0].token_ids) for out in outputs)
122
+
123
+ print(f"Total Time: {total_time:.2f} s")
124
+ print(f"Throughput (Requests/s): {len(tasks) / total_time:.2f}")
125
+ print(f"Input Tokens/s: {total_input_tokens / total_time:.2f}")
126
+ print(f"Output Tokens/s: {total_output_tokens / total_time:.2f}")
127
+ print(f"Total Tokens/s: {(total_input_tokens + total_output_tokens) / total_time:.2f}")
128
+ print(f"Avg Input Tokens per Request: {total_input_tokens / len(tasks):.2f}")
129
+ print(f"Avg Output Tokens per Request: {total_output_tokens / len(tasks):.2f}")
130
+
131
+ metrics = {
132
+ "Total Time (s)": f"{total_time:.2f}",
133
+ "Throughput (Requests/s)": f"{len(tasks) / total_time:.2f}",
134
+ "Input Tokens/s": f"{total_input_tokens / total_time:.2f}",
135
+ "Output Tokens/s": f"{total_output_tokens / total_time:.2f}",
136
+ "Total Tokens/s": f"{(total_input_tokens + total_output_tokens) / total_time:.2f}",
137
+ "Avg Input Tokens per Request": f"{total_input_tokens / len(tasks):.2f}",
138
+ "Avg Output Tokens per Request": f"{total_output_tokens / len(tasks):.2f}",
139
+ "Total Requests": len(tasks),
140
+ "Total Input Tokens": total_input_tokens,
141
+ "Total Output Tokens": total_output_tokens,
142
+ }
143
+ record_metrics(model_path, metrics)
eval/hpd_to_markdown.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Convert HPD-Parsing predictions (JSON) into per-page markdown for OmniDocBench.
2
+
3
+ Input : JSON, a list of ``{img_path, pred}`` (pred is the ``<BLOCK> <type> [bbox]
4
+ <CHILD> <content>`` stream from ``document parsing with fork.``).
5
+ Output: a folder of ``<image_stem>.md`` files matching the OmniDocBench GT paths.
6
+
7
+ python hpd_to_markdown.py --input preds.json --out-md pred_md/ \
8
+ --simplify-left-paren --clean-formula-tail --norm-formula-flag --wrap-cjk-arith
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import re
15
+ from pathlib import Path
16
+
17
+
18
+ _TALL = re.compile(
19
+ r'\\d?frac|\\tfrac|\\cfrac|\\binom|\\sqrt'
20
+ r'|\\sum|\\prod|\\coprod|\\int|\\iint|\\iiint|\\oint'
21
+ r'|\\bigcup|\\bigcap|\\bigoplus|\\bigotimes|\\bigsqcup'
22
+ r'|\\begin\{'
23
+ r'|\\overbrace|\\underbrace|\\overset|\\underset|\\stackrel'
24
+ r'|\\substack|\\atop|\\\\'
25
+ )
26
+
27
+
28
+ def _scan_delims(s):
29
+ out = []
30
+ for m in re.finditer(r'\\(left|right)\s*', s):
31
+ dm = re.match(r'\\[a-zA-Z]+|\\.|.', s[m.end():])
32
+ if not dm:
33
+ continue
34
+ out.append({'kind': m.group(1), 'delim': dm.group(0),
35
+ 'start': m.start(), 'end': m.end() + dm.end()})
36
+ return out
37
+
38
+
39
+ def simplify_left_right(s: str) -> str:
40
+ """Downgrade `\\left( ... \\right)` with no tall inner structure to plain `( )`."""
41
+ if '\\left' not in s:
42
+ return s
43
+ stack, pairs = [], []
44
+ for d in _scan_delims(s):
45
+ if d['kind'] == 'left':
46
+ stack.append(d)
47
+ elif stack:
48
+ pairs.append((stack.pop(), d))
49
+ edits = []
50
+ for L, R in pairs:
51
+ if L['delim'] == '(' and R['delim'] == ')' and not _TALL.search(s[L['end']:R['start']]):
52
+ edits.append((L['start'], L['end'], '('))
53
+ edits.append((R['start'], R['end'], ')'))
54
+ for st, en, rep in sorted(edits, key=lambda x: x[0], reverse=True):
55
+ s = s[:st] + rep + s[en:]
56
+ return s
57
+
58
+
59
+ _ELLIPSIS = r'(?:\\dots|\\cdots|\\ldots|\\dotsb|\\dotsc)'
60
+ _CLOSER = r'(?:\\right\s*[.\}\]\)]|\\end\s*\{(?:array|matrix|cases|bmatrix|pmatrix|vmatrix|smallmatrix)\})'
61
+ _TAIL_WRAP = re.compile(r'^(?P<core>.*?)(?P<wrap>\s*(?:\\\]|\\\)|\$\$))?\s*$', re.DOTALL)
62
+
63
+
64
+ def clean_formula_tail(s: str) -> str:
65
+ """Strip degenerate formula tails (repeated/dangling ellipses, stray `\\quad`)."""
66
+ if not s:
67
+ return s
68
+ m = _TAIL_WRAP.match(s)
69
+ core, wrap = m.group('core'), m.group('wrap') or ''
70
+ prev = None
71
+ while prev != core:
72
+ prev = core
73
+ core = re.sub(r'(' + _ELLIPSIS + r')(?:\s*' + _ELLIPSIS + r')+', r'\1', core)
74
+ core = re.sub(r'(?P<keep>' + _CLOSER + r')\s*(?:\\q?quad\s*)*' + _ELLIPSIS + r'\s*$',
75
+ lambda mm: mm.group('keep'), core)
76
+ core = re.sub(r'(?:\s*\\q?quad)+\s*' + _ELLIPSIS + r'\s*$', '', core)
77
+ core = re.sub(r'(?:\s*\\q?quad)+\s*$', '', core)
78
+ core = core.rstrip()
79
+ return core + wrap
80
+
81
+
82
+ _OP_MAP = {
83
+ '≈': r'\approx', '≠': r'\neq', '≤': r'\leq', '≥': r'\geq', '×': r'\times',
84
+ '÷': r'\div', '±': r'\pm', '∓': r'\mp', '·': r'\cdot', '∙': r'\cdot',
85
+ '⋅': r'\cdot', '∗': '*', '−': '-', '≡': r'\equiv', '∝': r'\propto',
86
+ '∞': r'\infty', '√': r'\sqrt', '→': r'\to', '≪': r'\ll', '≫': r'\gg',
87
+ }
88
+ _ARITH_ALLOWED = re.compile(r'^[0-9A-Za-z\s=+\-*/^_().,:;<>|%!\u4e00-\u9fff' + ''.join(_OP_MAP.keys()) + r']+$')
89
+ _ARITH_HASOP = re.compile(r'[=+\-*/' + ''.join(_OP_MAP.keys()) + r']')
90
+ _KNOWN_FUNCS = {'sin', 'cos', 'tan', 'cot', 'sec', 'csc', 'log', 'ln', 'exp',
91
+ 'lim', 'max', 'min', 'det', 'mod', 'arcsin', 'arccos', 'arctan', 'sqrt'}
92
+ _CJK_RUN = re.compile(r'[\u4e00-\u9fff]+')
93
+ _MATH_SPAN = re.compile(r'(\\\[.*?\\\]|\$\$.*?\$\$|\\\(.*?\\\)|\$.*?\$)', re.DOTALL)
94
+
95
+ WRAP_CJK_IN_ARITH = True
96
+
97
+
98
+ def _convert_unicode_ops(s: str) -> str:
99
+ for k, v in _OP_MAP.items():
100
+ s = s.replace(k, (v + ' ') if v.startswith('\\') else v)
101
+ if WRAP_CJK_IN_ARITH:
102
+ s = _CJK_RUN.sub(lambda m: r'\text{' + m.group(0) + '}', s)
103
+ return re.sub(r'[ \t]{2,}', ' ', s)
104
+
105
+
106
+ def _is_pure_arith_line(line: str) -> bool:
107
+ t = line.strip()
108
+ if not t or '\\(' in t or '\\[' in t or '$' in t or '<' in t:
109
+ return False
110
+ if not WRAP_CJK_IN_ARITH and re.search(r'[\u4e00-\u9fff]', t):
111
+ return False
112
+ if not _ARITH_ALLOWED.match(t) or not _ARITH_HASOP.search(t):
113
+ return False
114
+ return all(w.lower() in _KNOWN_FUNCS for w in re.findall(r'[A-Za-z]{2,}', t))
115
+
116
+
117
+ def normalize_arith(text: str) -> str:
118
+ """Normalize Unicode operators to LaTeX and wrap pure-arithmetic lines as `\\( .. \\)`."""
119
+ if not text:
120
+ return text
121
+ text = _MATH_SPAN.sub(lambda m: _convert_unicode_ops(m.group(0)), text)
122
+ out = []
123
+ for line in text.split('\n'):
124
+ if _is_pure_arith_line(line):
125
+ out.append('\\( ' + _convert_unicode_ops(line.strip()) + ' \\)')
126
+ else:
127
+ out.append(line)
128
+ return '\n'.join(out)
129
+
130
+
131
+ def remove_block_fork_tags(result, simplify_left_paren=True, clean_formula_tail_flag=True,
132
+ norm_formula_flag=True):
133
+ """Split on `<BLOCK>`, keep the text after each `<CHILD>`, and join in reading order."""
134
+ seg_pattern = re.compile(r'[^<]*<CHILD>(.*)', re.DOTALL)
135
+ lines = []
136
+ for seg in result.split('<BLOCK>')[1:]:
137
+ cat_m = re.match(r'\s*([a-zA-Z_]+)', seg)
138
+ if cat_m and cat_m.group(1).lower() in ['chart', 'seal']:
139
+ continue
140
+ m = seg_pattern.match(seg)
141
+ if not m:
142
+ continue
143
+ text = m.group(1).strip()
144
+ text = re.sub(r'\b\w+\s*\[\s*[-\d.,\s]+\]\s*<(?:FORK|CHILD|BLOCK)>', '', text)
145
+ text = re.sub(r'<(?:FORK|CHILD|BLOCK)>', '', text).strip()
146
+ text = text.replace('The image is too blurry to recognize any text content.', '').strip()
147
+ text = text.replace("The image contains no text or characters. It is a graphical element (a horizontal line with a vertical line) and does not contain any chart, graph, or data points that can be extracted. Therefore, the correct OCR output is an empty string.", "").strip()
148
+ if not text or text == '[Non-Text]':
149
+ continue
150
+ if text.startswith('\\[') and not text.endswith('\n\\]'):
151
+ text += '\n\\]'
152
+ if text.startswith('<table>') and not text.endswith('</table>'):
153
+ text += '</table>'
154
+ if '\\[\n' in text and '\\\\' not in text:
155
+ text = text.replace('\\[\n', '\\(').replace('\n\\]', '\\)')
156
+ text = text.replace('\\) \\(', '\\)\n\n\\(')
157
+ if '÷' in text and '\\(' not in text:
158
+ text = '\\( ' + text + ' \\)'
159
+ text = re.sub(r'\\tag\s*\{[^{}]*\}', '', text)
160
+ text = text.replace('\\supset', '\\sqsupset')
161
+ if simplify_left_paren:
162
+ text = simplify_left_right(text)
163
+ if clean_formula_tail_flag:
164
+ text = clean_formula_tail(text)
165
+ if norm_formula_flag:
166
+ text = normalize_arith(text)
167
+ lines.append(text)
168
+ return '\n\n'.join(lines).strip()
169
+
170
+
171
+ def basename_to_md_name(img_path: str) -> str:
172
+ return os.path.splitext(os.path.basename(img_path))[0] + ".md"
173
+
174
+
175
+ def convert_json(in_path, out_md_dir, simplify_left_paren=True,
176
+ clean_formula_tail_flag=True, norm_formula_flag=True) -> int:
177
+ with open(in_path, "r", encoding="utf-8") as f:
178
+ rows = json.load(f)
179
+ os.makedirs(out_md_dir, exist_ok=True)
180
+ n = 0
181
+ for row in rows:
182
+ img_path = row.get("img_path") or row.get("image_path")
183
+ pred = row.get("pred") or row.get("prediction") or ""
184
+ if not img_path:
185
+ continue
186
+ md = remove_block_fork_tags(pred, simplify_left_paren, clean_formula_tail_flag, norm_formula_flag)
187
+ with open(os.path.join(out_md_dir, basename_to_md_name(img_path)), "w", encoding="utf-8") as f:
188
+ f.write(md)
189
+ n += 1
190
+ return n
191
+
192
+
193
+ def main():
194
+ ap = argparse.ArgumentParser()
195
+ ap.add_argument("--input", required=True, help="json path (list of {img_path, pred})")
196
+ ap.add_argument("--out-md", required=True, help="output markdown folder")
197
+ args = ap.parse_args()
198
+
199
+ if Path(args.input).suffix.lower() != ".json":
200
+ raise SystemExit(f"unsupported extension: {Path(args.input).suffix} (expects .json)")
201
+ n = convert_json(args.input, args.out_md)
202
+ print(f"[ok] wrote {n} markdown files -> {args.out_md}")
203
+
204
+
205
+ if __name__ == "__main__":
206
+ main()
generation_config.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "eos_token_id": 151645,
4
+ "transformers_version": "4.56.2"
5
+ }
image_preprocess.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Image preprocessing utilities for HPD-Parsing (transformers path).
2
+
3
+ Mirrors vLLM's InternVL dynamic tiling path with ``MAX_PATCHES_WITH_RESIZE=true``:
4
+ resize to the closest aspect ratio in a ``(min_num, max_num)`` grid, split into
5
+ ``448x448`` tiles, and optionally append a thumbnail.
6
+ """
7
+
8
+ import torch
9
+ import torchvision.transforms as T
10
+ from torchvision.transforms.functional import InterpolationMode
11
+ from PIL import Image
12
+
13
+ IMAGENET_MEAN, IMAGENET_STD = (0.485, 0.456, 0.406), (0.229, 0.224, 0.225)
14
+
15
+ IMAGE_SIZE = 448
16
+ MIN_DYNAMIC_PATCH = 1
17
+ MAX_DYNAMIC_PATCH = 24
18
+ USE_THUMBNAIL = True
19
+
20
+
21
+ def build_transform(input_size=IMAGE_SIZE):
22
+ return T.Compose([
23
+ T.Lambda(lambda img: img.convert("RGB")),
24
+ T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC),
25
+ T.ToTensor(),
26
+ T.Normalize(IMAGENET_MEAN, IMAGENET_STD),
27
+ ])
28
+
29
+
30
+ def get_target_ratios(min_num, max_num):
31
+ ratios = {(i, j)
32
+ for n in range(min_num, max_num + 1)
33
+ for i in range(1, n + 1) for j in range(1, n + 1)
34
+ if min_num <= i * j <= max_num}
35
+ return sorted(ratios, key=lambda x: x[0] * x[1])
36
+
37
+
38
+ def find_closest_aspect_ratio_optim(aspect_ratio, target_ratios, width, height,
39
+ image_size, top_k=3, ar_threshold=0.2):
40
+ area = width * height
41
+ candidates = []
42
+ for ratio in target_ratios:
43
+ ar_diff = abs(aspect_ratio - ratio[0] / ratio[1])
44
+ if ar_threshold is not None and ar_diff > ar_threshold:
45
+ continue
46
+ area_diff = abs(area - image_size * image_size * ratio[0] * ratio[1])
47
+ candidates.append((ratio, area_diff, ar_diff))
48
+ if not candidates: # fall back to no aspect-ratio filtering
49
+ for ratio in target_ratios:
50
+ ar_diff = abs(aspect_ratio - ratio[0] / ratio[1])
51
+ area_diff = abs(area - image_size * image_size * ratio[0] * ratio[1])
52
+ candidates.append((ratio, area_diff, ar_diff))
53
+ candidates.sort(key=lambda x: x[1])
54
+ top = candidates[:top_k]
55
+ top.sort(key=lambda x: x[2])
56
+ return top[0][0]
57
+
58
+
59
+ def dynamic_preprocess(image, target_ratios, image_size=IMAGE_SIZE, use_thumbnail=USE_THUMBNAIL):
60
+ w, h = image.size
61
+ ratio = find_closest_aspect_ratio_optim(w / h, target_ratios, w, h, image_size)
62
+ tw, th = image_size * ratio[0], image_size * ratio[1]
63
+ blocks = ratio[0] * ratio[1]
64
+ resized = image.resize((tw, th))
65
+ cols = tw // image_size
66
+ tiles = []
67
+ for i in range(blocks):
68
+ box = ((i % cols) * image_size, (i // cols) * image_size,
69
+ ((i % cols) + 1) * image_size, ((i // cols) + 1) * image_size)
70
+ tiles.append(resized.crop(box))
71
+ if use_thumbnail and blocks != 1:
72
+ tiles.append(image.resize((image_size, image_size)))
73
+ return tiles
74
+
75
+
76
+ def load_image(path):
77
+ image = Image.open(path).convert("RGB")
78
+ min_num, max_num = MIN_DYNAMIC_PATCH, MAX_DYNAMIC_PATCH
79
+ if USE_THUMBNAIL and max_num != 1:
80
+ max_num += 1
81
+ target_ratios = get_target_ratios(min_num, max_num)
82
+ transform = build_transform(IMAGE_SIZE)
83
+ tiles = dynamic_preprocess(image, target_ratios, IMAGE_SIZE, USE_THUMBNAIL)
84
+ return torch.stack([transform(t) for t in tiles])
merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
modeling_intern_vit.py ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --------------------------------------------------------
2
+ # InternVL
3
+ # Copyright (c) 2024 OpenGVLab
4
+ # Licensed under The MIT License [see LICENSE for details]
5
+ # --------------------------------------------------------
6
+
7
+ from typing import Optional, Tuple, Union
8
+
9
+ import torch
10
+ import torch.nn.functional as F
11
+ import torch.utils.checkpoint
12
+ from einops import rearrange
13
+ from timm.layers import DropPath
14
+ from torch import nn
15
+ from transformers.activations import ACT2FN
16
+ from transformers.modeling_outputs import (BaseModelOutput,
17
+ BaseModelOutputWithPooling)
18
+ from transformers.modeling_utils import PreTrainedModel
19
+ from transformers.utils import logging
20
+
21
+ from .configuration_intern_vit import InternVisionConfig
22
+
23
+ try:
24
+ from flash_attn.bert_padding import pad_input, unpad_input
25
+ from flash_attn.flash_attn_interface import \
26
+ flash_attn_varlen_qkvpacked_func
27
+ has_flash_attn = True
28
+ except:
29
+ print('FlashAttention2 is not installed.')
30
+ has_flash_attn = False
31
+
32
+ logger = logging.get_logger(__name__)
33
+
34
+
35
+ class FlashAttention(nn.Module):
36
+ """Implement the scaled dot product attention with softmax.
37
+ Arguments
38
+ ---------
39
+ softmax_scale: The temperature to use for the softmax attention.
40
+ (default: 1/sqrt(d_keys) where d_keys is computed at
41
+ runtime)
42
+ attention_dropout: The dropout rate to apply to the attention
43
+ (default: 0.0)
44
+ """
45
+
46
+ def __init__(self, softmax_scale=None, attention_dropout=0.0, device=None, dtype=None):
47
+ super().__init__()
48
+ self.softmax_scale = softmax_scale
49
+ self.dropout_p = attention_dropout
50
+
51
+ def forward(self, qkv, key_padding_mask=None, causal=False, cu_seqlens=None,
52
+ max_s=None, need_weights=False):
53
+ """Implements the multihead softmax attention.
54
+ Arguments
55
+ ---------
56
+ qkv: The tensor containing the query, key, and value. (B, S, 3, H, D) if key_padding_mask is None
57
+ if unpadded: (nnz, 3, h, d)
58
+ key_padding_mask: a bool tensor of shape (B, S)
59
+ """
60
+ assert not need_weights
61
+ assert qkv.dtype in [torch.float16, torch.bfloat16]
62
+ assert qkv.is_cuda
63
+
64
+ if cu_seqlens is None:
65
+ batch_size = qkv.shape[0]
66
+ seqlen = qkv.shape[1]
67
+ if key_padding_mask is None:
68
+ qkv = rearrange(qkv, 'b s ... -> (b s) ...')
69
+ max_s = seqlen
70
+ cu_seqlens = torch.arange(0, (batch_size + 1) * seqlen, step=seqlen, dtype=torch.int32,
71
+ device=qkv.device)
72
+ output = flash_attn_varlen_qkvpacked_func(
73
+ qkv, cu_seqlens, max_s, self.dropout_p if self.training else 0.0,
74
+ softmax_scale=self.softmax_scale, causal=causal
75
+ )
76
+ output = rearrange(output, '(b s) ... -> b s ...', b=batch_size)
77
+ else:
78
+ nheads = qkv.shape[-2]
79
+ x = rearrange(qkv, 'b s three h d -> b s (three h d)')
80
+ x_unpad, indices, cu_seqlens, max_s = unpad_input(x, key_padding_mask)
81
+ x_unpad = rearrange(x_unpad, 'nnz (three h d) -> nnz three h d', three=3, h=nheads)
82
+ output_unpad = flash_attn_varlen_qkvpacked_func(
83
+ x_unpad, cu_seqlens, max_s, self.dropout_p if self.training else 0.0,
84
+ softmax_scale=self.softmax_scale, causal=causal
85
+ )
86
+ output = rearrange(pad_input(rearrange(output_unpad, 'nnz h d -> nnz (h d)'),
87
+ indices, batch_size, seqlen),
88
+ 'b s (h d) -> b s h d', h=nheads)
89
+ else:
90
+ assert max_s is not None
91
+ output = flash_attn_varlen_qkvpacked_func(
92
+ qkv, cu_seqlens, max_s, self.dropout_p if self.training else 0.0,
93
+ softmax_scale=self.softmax_scale, causal=causal
94
+ )
95
+
96
+ return output, None
97
+
98
+
99
+ class InternRMSNorm(nn.Module):
100
+ def __init__(self, hidden_size, eps=1e-6):
101
+ super().__init__()
102
+ self.weight = nn.Parameter(torch.ones(hidden_size))
103
+ self.variance_epsilon = eps
104
+
105
+ def forward(self, hidden_states):
106
+ input_dtype = hidden_states.dtype
107
+ hidden_states = hidden_states.to(torch.float32)
108
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
109
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
110
+ return self.weight * hidden_states.to(input_dtype)
111
+
112
+
113
+ try:
114
+ from apex.normalization import FusedRMSNorm
115
+
116
+ InternRMSNorm = FusedRMSNorm # noqa
117
+
118
+ logger.info('Discovered apex.normalization.FusedRMSNorm - will use it instead of InternRMSNorm')
119
+ except ImportError:
120
+ # using the normal InternRMSNorm
121
+ pass
122
+ except Exception:
123
+ logger.warning('discovered apex but it failed to load, falling back to InternRMSNorm')
124
+ pass
125
+
126
+
127
+ NORM2FN = {
128
+ 'rms_norm': InternRMSNorm,
129
+ 'layer_norm': nn.LayerNorm,
130
+ }
131
+
132
+
133
+ class InternVisionEmbeddings(nn.Module):
134
+ def __init__(self, config: InternVisionConfig):
135
+ super().__init__()
136
+ self.config = config
137
+ self.embed_dim = config.hidden_size
138
+ self.image_size = config.image_size
139
+ self.patch_size = config.patch_size
140
+
141
+ self.class_embedding = nn.Parameter(
142
+ torch.randn(1, 1, self.embed_dim),
143
+ )
144
+
145
+ self.patch_embedding = nn.Conv2d(
146
+ in_channels=3, out_channels=self.embed_dim, kernel_size=self.patch_size, stride=self.patch_size
147
+ )
148
+
149
+ self.num_patches = (self.image_size // self.patch_size) ** 2
150
+ self.num_positions = self.num_patches + 1
151
+
152
+ self.position_embedding = nn.Parameter(torch.randn(1, self.num_positions, self.embed_dim))
153
+
154
+ def _get_pos_embed(self, pos_embed, H, W):
155
+ target_dtype = pos_embed.dtype
156
+ pos_embed = pos_embed.float().reshape(
157
+ 1, self.image_size // self.patch_size, self.image_size // self.patch_size, -1).permute(0, 3, 1, 2)
158
+ pos_embed = F.interpolate(pos_embed, size=(H, W), mode='bicubic', align_corners=False). \
159
+ reshape(1, -1, H * W).permute(0, 2, 1).to(target_dtype)
160
+ return pos_embed
161
+
162
+ def forward(self, pixel_values: torch.FloatTensor) -> torch.Tensor:
163
+ target_dtype = self.patch_embedding.weight.dtype
164
+ patch_embeds = self.patch_embedding(pixel_values) # shape = [*, channel, width, height]
165
+ batch_size, _, height, width = patch_embeds.shape
166
+ patch_embeds = patch_embeds.flatten(2).transpose(1, 2)
167
+ class_embeds = self.class_embedding.expand(batch_size, 1, -1).to(target_dtype)
168
+ embeddings = torch.cat([class_embeds, patch_embeds], dim=1)
169
+ position_embedding = torch.cat([
170
+ self.position_embedding[:, :1, :],
171
+ self._get_pos_embed(self.position_embedding[:, 1:, :], height, width)
172
+ ], dim=1)
173
+ embeddings = embeddings + position_embedding.to(target_dtype)
174
+ return embeddings
175
+
176
+
177
+ class InternAttention(nn.Module):
178
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
179
+
180
+ def __init__(self, config: InternVisionConfig):
181
+ super().__init__()
182
+ self.config = config
183
+ self.embed_dim = config.hidden_size
184
+ self.num_heads = config.num_attention_heads
185
+ self.use_flash_attn = config.use_flash_attn and has_flash_attn
186
+ if config.use_flash_attn and not has_flash_attn:
187
+ print('Warning: Flash Attention is not available, use_flash_attn is set to False.')
188
+ self.head_dim = self.embed_dim // self.num_heads
189
+ if self.head_dim * self.num_heads != self.embed_dim:
190
+ raise ValueError(
191
+ f'embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:'
192
+ f' {self.num_heads}).'
193
+ )
194
+
195
+ self.scale = self.head_dim ** -0.5
196
+ self.qkv = nn.Linear(self.embed_dim, 3 * self.embed_dim, bias=config.qkv_bias)
197
+ self.attn_drop = nn.Dropout(config.attention_dropout)
198
+ self.proj_drop = nn.Dropout(config.dropout)
199
+
200
+ self.qk_normalization = config.qk_normalization
201
+
202
+ if self.qk_normalization:
203
+ self.q_norm = InternRMSNorm(self.embed_dim, eps=config.layer_norm_eps)
204
+ self.k_norm = InternRMSNorm(self.embed_dim, eps=config.layer_norm_eps)
205
+
206
+ if self.use_flash_attn:
207
+ self.inner_attn = FlashAttention(attention_dropout=config.attention_dropout)
208
+ self.proj = nn.Linear(self.embed_dim, self.embed_dim)
209
+
210
+ def _naive_attn(self, x):
211
+ B, N, C = x.shape
212
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
213
+ q, k, v = qkv.unbind(0) # make torchscript happy (cannot use tensor as tuple)
214
+
215
+ if self.qk_normalization:
216
+ B_, H_, N_, D_ = q.shape
217
+ q = self.q_norm(q.transpose(1, 2).flatten(-2, -1)).view(B_, N_, H_, D_).transpose(1, 2)
218
+ k = self.k_norm(k.transpose(1, 2).flatten(-2, -1)).view(B_, N_, H_, D_).transpose(1, 2)
219
+
220
+ attn = ((q * self.scale) @ k.transpose(-2, -1))
221
+ attn = attn.softmax(dim=-1)
222
+ attn = self.attn_drop(attn)
223
+
224
+ x = (attn @ v).transpose(1, 2).reshape(B, N, C)
225
+ x = self.proj(x)
226
+ x = self.proj_drop(x)
227
+ return x
228
+
229
+ def _flash_attn(self, x, key_padding_mask=None, need_weights=False):
230
+ qkv = self.qkv(x)
231
+ qkv = rearrange(qkv, 'b s (three h d) -> b s three h d', three=3, h=self.num_heads)
232
+
233
+ if self.qk_normalization:
234
+ q, k, v = qkv.unbind(2)
235
+ q = self.q_norm(q.flatten(-2, -1)).view(q.shape)
236
+ k = self.k_norm(k.flatten(-2, -1)).view(k.shape)
237
+ qkv = torch.stack([q, k, v], dim=2)
238
+
239
+ context, _ = self.inner_attn(
240
+ qkv, key_padding_mask=key_padding_mask, need_weights=need_weights, causal=False
241
+ )
242
+ outs = self.proj(rearrange(context, 'b s h d -> b s (h d)'))
243
+ outs = self.proj_drop(outs)
244
+ return outs
245
+
246
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
247
+ x = self._naive_attn(hidden_states) if not self.use_flash_attn else self._flash_attn(hidden_states)
248
+ return x
249
+
250
+
251
+ class InternMLP(nn.Module):
252
+ def __init__(self, config: InternVisionConfig):
253
+ super().__init__()
254
+ self.config = config
255
+ self.act = ACT2FN[config.hidden_act]
256
+ self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
257
+ self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
258
+
259
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
260
+ hidden_states = self.fc1(hidden_states)
261
+ hidden_states = self.act(hidden_states)
262
+ hidden_states = self.fc2(hidden_states)
263
+ return hidden_states
264
+
265
+
266
+ class InternVisionEncoderLayer(nn.Module):
267
+ def __init__(self, config: InternVisionConfig, drop_path_rate: float):
268
+ super().__init__()
269
+ self.embed_dim = config.hidden_size
270
+ self.intermediate_size = config.intermediate_size
271
+ self.norm_type = config.norm_type
272
+
273
+ self.attn = InternAttention(config)
274
+ self.mlp = InternMLP(config)
275
+ self.norm1 = NORM2FN[self.norm_type](self.embed_dim, eps=config.layer_norm_eps)
276
+ self.norm2 = NORM2FN[self.norm_type](self.embed_dim, eps=config.layer_norm_eps)
277
+
278
+ self.ls1 = nn.Parameter(config.initializer_factor * torch.ones(self.embed_dim))
279
+ self.ls2 = nn.Parameter(config.initializer_factor * torch.ones(self.embed_dim))
280
+ self.drop_path1 = DropPath(drop_path_rate) if drop_path_rate > 0. else nn.Identity()
281
+ self.drop_path2 = DropPath(drop_path_rate) if drop_path_rate > 0. else nn.Identity()
282
+
283
+ def forward(
284
+ self,
285
+ hidden_states: torch.Tensor,
286
+ ) -> Tuple[torch.FloatTensor, Optional[torch.FloatTensor], Optional[Tuple[torch.FloatTensor]]]:
287
+ """
288
+ Args:
289
+ hidden_states (`Tuple[torch.FloatTensor, Optional[torch.FloatTensor]]`): input to the layer of shape `(batch, seq_len, embed_dim)`
290
+ """
291
+ hidden_states = hidden_states + self.drop_path1(self.attn(self.norm1(hidden_states).to(hidden_states.dtype)) * self.ls1)
292
+
293
+ hidden_states = hidden_states + self.drop_path2(self.mlp(self.norm2(hidden_states).to(hidden_states.dtype)) * self.ls2)
294
+
295
+ return hidden_states
296
+
297
+
298
+ class InternVisionEncoder(nn.Module):
299
+ """
300
+ Transformer encoder consisting of `config.num_hidden_layers` self attention layers. Each layer is a
301
+ [`InternEncoderLayer`].
302
+
303
+ Args:
304
+ config (`InternConfig`):
305
+ The corresponding vision configuration for the `InternEncoder`.
306
+ """
307
+
308
+ def __init__(self, config: InternVisionConfig):
309
+ super().__init__()
310
+ self.config = config
311
+ # stochastic depth decay rule
312
+ dpr = [x.item() for x in torch.linspace(0, config.drop_path_rate, config.num_hidden_layers)]
313
+ self.layers = nn.ModuleList([
314
+ InternVisionEncoderLayer(config, dpr[idx]) for idx in range(config.num_hidden_layers)])
315
+ self.gradient_checkpointing = True
316
+
317
+ def forward(
318
+ self,
319
+ inputs_embeds,
320
+ output_hidden_states: Optional[bool] = None,
321
+ return_dict: Optional[bool] = None,
322
+ ) -> Union[Tuple, BaseModelOutput]:
323
+ r"""
324
+ Args:
325
+ inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`):
326
+ Embedded representation of the inputs. Should be float, not int tokens.
327
+ output_hidden_states (`bool`, *optional*):
328
+ Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors
329
+ for more detail.
330
+ return_dict (`bool`, *optional*):
331
+ Whether or not to return a [`~utils.ModelOutput`] instead of a plain tuple.
332
+ """
333
+ output_hidden_states = (
334
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
335
+ )
336
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
337
+
338
+ encoder_states = () if output_hidden_states else None
339
+ hidden_states = inputs_embeds
340
+
341
+ for idx, encoder_layer in enumerate(self.layers):
342
+ if output_hidden_states:
343
+ encoder_states = encoder_states + (hidden_states,)
344
+ if self.gradient_checkpointing and self.training:
345
+ layer_outputs = torch.utils.checkpoint.checkpoint(
346
+ encoder_layer,
347
+ hidden_states)
348
+ else:
349
+ layer_outputs = encoder_layer(
350
+ hidden_states,
351
+ )
352
+ hidden_states = layer_outputs
353
+
354
+ if output_hidden_states:
355
+ encoder_states = encoder_states + (hidden_states,)
356
+
357
+ if not return_dict:
358
+ return tuple(v for v in [hidden_states, encoder_states] if v is not None)
359
+ return BaseModelOutput(
360
+ last_hidden_state=hidden_states, hidden_states=encoder_states
361
+ )
362
+
363
+
364
+ class InternVisionModel(PreTrainedModel):
365
+ main_input_name = 'pixel_values'
366
+ _supports_flash_attn_2 = True
367
+ supports_gradient_checkpointing = True
368
+ config_class = InternVisionConfig
369
+ _no_split_modules = ['InternVisionEncoderLayer']
370
+ # support transformers 4.51.+
371
+ _tp_plan = ''
372
+
373
+ def __init__(self, config: InternVisionConfig):
374
+ super().__init__(config)
375
+ self.config = config
376
+
377
+ self.embeddings = InternVisionEmbeddings(config)
378
+ self.encoder = InternVisionEncoder(config)
379
+
380
+ def resize_pos_embeddings(self, old_size, new_size, patch_size):
381
+ pos_emb = self.embeddings.position_embedding
382
+ _, num_positions, embed_dim = pos_emb.shape
383
+ cls_emb = pos_emb[:, :1, :]
384
+ pos_emb = pos_emb[:, 1:, :].reshape(1, old_size // patch_size, old_size // patch_size, -1).permute(0, 3, 1, 2)
385
+ pos_emb = F.interpolate(pos_emb.float(), size=new_size // patch_size, mode='bicubic', align_corners=False)
386
+ pos_emb = pos_emb.to(cls_emb.dtype).reshape(1, embed_dim, -1).permute(0, 2, 1)
387
+ pos_emb = torch.cat([cls_emb, pos_emb], dim=1)
388
+ self.embeddings.position_embedding = nn.Parameter(pos_emb)
389
+ self.embeddings.image_size = new_size
390
+ logger.info('Resized position embeddings from {} to {}'.format(old_size, new_size))
391
+
392
+ def get_input_embeddings(self):
393
+ return self.embeddings
394
+
395
+ def forward(
396
+ self,
397
+ pixel_values: Optional[torch.FloatTensor] = None,
398
+ output_hidden_states: Optional[bool] = None,
399
+ return_dict: Optional[bool] = None,
400
+ pixel_embeds: Optional[torch.FloatTensor] = None,
401
+ ) -> Union[Tuple, BaseModelOutputWithPooling]:
402
+ output_hidden_states = (
403
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
404
+ )
405
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
406
+
407
+ if pixel_values is None and pixel_embeds is None:
408
+ raise ValueError('You have to specify pixel_values or pixel_embeds')
409
+
410
+ if pixel_embeds is not None:
411
+ hidden_states = pixel_embeds
412
+ else:
413
+ if len(pixel_values.shape) == 4:
414
+ hidden_states = self.embeddings(pixel_values)
415
+ else:
416
+ raise ValueError(f'wrong pixel_values size: {pixel_values.shape}')
417
+ encoder_outputs = self.encoder(
418
+ inputs_embeds=hidden_states,
419
+ output_hidden_states=output_hidden_states,
420
+ return_dict=return_dict,
421
+ )
422
+ last_hidden_state = encoder_outputs.last_hidden_state
423
+ pooled_output = last_hidden_state[:, 0, :]
424
+
425
+ if not return_dict:
426
+ return (last_hidden_state, pooled_output) + encoder_outputs[1:]
427
+
428
+ return BaseModelOutputWithPooling(
429
+ last_hidden_state=last_hidden_state,
430
+ pooler_output=pooled_output,
431
+ hidden_states=encoder_outputs.hidden_states,
432
+ attentions=encoder_outputs.attentions,
433
+ )
modeling_internvl_chat.py ADDED
@@ -0,0 +1,775 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --------------------------------------------------------
2
+ # InternVL
3
+ # Copyright (c) 2024 OpenGVLab
4
+ # Licensed under The MIT License [see LICENSE for details]
5
+ # --------------------------------------------------------
6
+
7
+ import os
8
+ import warnings
9
+ from typing import List, Optional, Tuple, Union
10
+
11
+ import torch.utils.checkpoint
12
+ import torch.nn.functional as F
13
+ import transformers
14
+ from torch import nn
15
+ from torch.nn import CrossEntropyLoss
16
+ from transformers import GenerationConfig
17
+ from transformers.cache_utils import DynamicCache
18
+ from transformers.modeling_outputs import CausalLMOutputWithPast
19
+ from transformers.modeling_utils import PreTrainedModel
20
+ from transformers.utils import logging
21
+ from transformers import LlamaForCausalLM, Qwen2ForCausalLM, Qwen3ForCausalLM, Qwen3MoeForCausalLM
22
+
23
+ from .configuration_internvl_chat import InternVLChatConfig
24
+ from .conversation import get_conv_template
25
+ from .modeling_intern_vit import InternVisionModel, has_flash_attn
26
+
27
+ logger = logging.get_logger(__name__)
28
+
29
+
30
+ def version_cmp(v1, v2, op='eq'):
31
+ import operator
32
+
33
+ from packaging import version
34
+ op_func = getattr(operator, op)
35
+ return op_func(version.parse(v1), version.parse(v2))
36
+
37
+
38
+ class HPDRMSNorm(nn.Module):
39
+ def __init__(self, hidden_size, eps=1e-6):
40
+ super().__init__()
41
+ self.weight = nn.Parameter(torch.ones(hidden_size))
42
+ self.variance_epsilon = eps
43
+
44
+ def forward(self, hidden_states):
45
+ input_dtype = hidden_states.dtype
46
+ hidden_states = hidden_states.to(torch.float32)
47
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
48
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
49
+ return self.weight * hidden_states.to(input_dtype)
50
+
51
+
52
+ class _HPDGatedMLP(nn.Module):
53
+ def __init__(self, hidden_size, intermediate_size):
54
+ super().__init__()
55
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
56
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
57
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
58
+
59
+ def forward(self, x):
60
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
61
+
62
+
63
+ class _HPDMTPLayer(nn.Module):
64
+ def __init__(self, hidden_size, intermediate_size):
65
+ super().__init__()
66
+ self.mlp = _HPDGatedMLP(hidden_size, intermediate_size)
67
+
68
+
69
+ class HPDMultiTokenPredictor(nn.Module):
70
+ """Single-block Progressive Multi-Token Prediction head (P-MTP).
71
+
72
+ Mirrors the vLLM ``Qwen3_5MultiTokenPredictor`` module (loaded there as a
73
+ ``medusa`` speculative model) so its weights, stored under
74
+ ``language_model.mtp.*`` in the checkpoint, load without remapping. One MTP
75
+ block is applied autoregressively to draft several future tokens; each step
76
+ is conditioned on the previous step's predicted token. The token embedding
77
+ and the output head are shared with the backbone (the checkpoint contains no
78
+ ``mtp.embed_tokens``/``mtp.lm_head``), so ``step`` takes an already-embedded
79
+ ``prev_embeds`` tensor.
80
+ """
81
+
82
+ def __init__(self, hidden_size, intermediate_size, rms_norm_eps=1e-6):
83
+ super().__init__()
84
+ self.fc = nn.Linear(hidden_size * 2, hidden_size, bias=False)
85
+ self.pre_fc_norm_hidden = HPDRMSNorm(hidden_size, rms_norm_eps)
86
+ self.pre_fc_norm_embedding = HPDRMSNorm(hidden_size, rms_norm_eps)
87
+ self.layers = nn.ModuleList([_HPDMTPLayer(hidden_size, intermediate_size)])
88
+ self.norm = HPDRMSNorm(hidden_size, rms_norm_eps)
89
+
90
+ def step(self, hidden_states, prev_embeds):
91
+ # hidden_states: [B, H] backbone hidden that produced prev_token;
92
+ # prev_embeds: [B, H] backbone embedding of that token.
93
+ embeds = self.pre_fc_norm_embedding(prev_embeds)
94
+ hidden = self.pre_fc_norm_hidden(hidden_states)
95
+ x = self.fc(torch.cat([hidden, embeds], dim=-1))
96
+ mlp_out = self.layers[0].mlp(x)
97
+ return self.norm(mlp_out + x)
98
+
99
+
100
+ class InternVLChatModel(PreTrainedModel):
101
+ config_class = InternVLChatConfig
102
+ main_input_name = 'pixel_values'
103
+ base_model_prefix = 'language_model'
104
+ _supports_flash_attn_2 = True
105
+ supports_gradient_checkpointing = True
106
+ _no_split_modules = [
107
+ "InternVisionModel",
108
+ "Qwen3DecoderLayer",
109
+ ]
110
+
111
+ # support transformers 4.51.+
112
+ _tp_plan = ''
113
+
114
+ def __init__(self, config: InternVLChatConfig, vision_model=None, language_model=None, use_flash_attn=True):
115
+ super().__init__(config)
116
+
117
+ assert version_cmp(transformers.__version__, '4.37.0', 'ge')
118
+ image_size = config.force_image_size or config.vision_config.image_size
119
+ patch_size = config.vision_config.patch_size
120
+ self.patch_size = patch_size
121
+ self.select_layer = config.select_layer
122
+ self.template = config.template
123
+ self.num_image_token = int((image_size // patch_size) ** 2 * (config.downsample_ratio ** 2))
124
+ self.downsample_ratio = config.downsample_ratio
125
+ self.ps_version = config.ps_version
126
+ use_flash_attn = use_flash_attn if has_flash_attn else False
127
+ config.vision_config.use_flash_attn = True if use_flash_attn else False
128
+ config.llm_config._attn_implementation = 'flash_attention_2' if use_flash_attn else 'eager'
129
+
130
+ logger.info(f'num_image_token: {self.num_image_token}')
131
+ logger.info(f'ps_version: {self.ps_version}')
132
+ if vision_model is not None:
133
+ self.vision_model = vision_model
134
+ else:
135
+ self.vision_model = InternVisionModel(config.vision_config)
136
+ if language_model is not None:
137
+ self.language_model = language_model
138
+ else:
139
+ architecture: str = config.llm_config.architectures[0]
140
+ if architecture == 'LlamaForCausalLM':
141
+ self.language_model = LlamaForCausalLM(config.llm_config)
142
+ elif architecture == 'Qwen2ForCausalLM':
143
+ self.language_model = Qwen2ForCausalLM(config.llm_config)
144
+ elif architecture == 'Qwen3MoeForCausalLM':
145
+ self.language_model = Qwen3MoeForCausalLM(config.llm_config)
146
+ elif architecture == 'Qwen3ForCausalLM':
147
+ self.language_model = Qwen3ForCausalLM(config.llm_config)
148
+ else:
149
+ raise NotImplementedError(f'{architecture} is not implemented.')
150
+
151
+ vit_hidden_size = config.vision_config.hidden_size
152
+ llm_hidden_size = config.llm_config.hidden_size
153
+
154
+ self.mlp1 = nn.Sequential(
155
+ nn.LayerNorm(vit_hidden_size * int(1 / self.downsample_ratio) ** 2),
156
+ nn.Linear(vit_hidden_size * int(1 / self.downsample_ratio) ** 2, llm_hidden_size),
157
+ nn.GELU(),
158
+ nn.Linear(llm_hidden_size, llm_hidden_size)
159
+ )
160
+
161
+ self.img_context_token_id = None
162
+ self.conv_template = get_conv_template(self.template)
163
+ self.system_message = self.conv_template.system_message
164
+
165
+ # P-MTP head + hierarchical fork decoding metadata (see generate_hpd).
166
+ self.fork_token_id = getattr(config, 'fork_token_id', None)
167
+ self.child_token_id = getattr(config, 'child_token_id', None)
168
+ self._mtp_weights_loaded = False
169
+ llm_cfg = config.llm_config
170
+ self.language_model.mtp = HPDMultiTokenPredictor(
171
+ hidden_size=llm_cfg.hidden_size,
172
+ intermediate_size=llm_cfg.intermediate_size,
173
+ rms_norm_eps=getattr(llm_cfg, 'rms_norm_eps', 1e-6),
174
+ )
175
+
176
+ def forward(
177
+ self,
178
+ pixel_values: torch.FloatTensor,
179
+ input_ids: torch.LongTensor = None,
180
+ attention_mask: Optional[torch.Tensor] = None,
181
+ position_ids: Optional[torch.LongTensor] = None,
182
+ image_flags: Optional[torch.LongTensor] = None,
183
+ past_key_values: Optional[List[torch.FloatTensor]] = None,
184
+ labels: Optional[torch.LongTensor] = None,
185
+ use_cache: Optional[bool] = None,
186
+ output_attentions: Optional[bool] = None,
187
+ output_hidden_states: Optional[bool] = None,
188
+ return_dict: Optional[bool] = None,
189
+ ) -> Union[Tuple, CausalLMOutputWithPast]:
190
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
191
+
192
+ image_flags = image_flags.squeeze(-1)
193
+ input_embeds = self.language_model.get_input_embeddings()(input_ids).clone()
194
+
195
+ vit_embeds = self.extract_feature(pixel_values)
196
+ vit_embeds = vit_embeds[image_flags == 1]
197
+ vit_batch_size = pixel_values.shape[0]
198
+
199
+ B, N, C = input_embeds.shape
200
+ input_embeds = input_embeds.reshape(B * N, C)
201
+
202
+ # if torch.distributed.is_initialized() and torch.distributed.get_rank() == 0:
203
+ # print(f'dynamic ViT batch size: {vit_batch_size}, images per sample: {vit_batch_size / B}, dynamic token length: {N}')
204
+
205
+ input_ids = input_ids.reshape(B * N)
206
+ selected = (input_ids == self.img_context_token_id)
207
+ try:
208
+ input_embeds[selected] = input_embeds[selected] * 0.0 + vit_embeds.reshape(-1, C)
209
+ except Exception as e:
210
+ vit_embeds = vit_embeds.reshape(-1, C)
211
+ print(f'warning: {e}, input_embeds[selected].shape={input_embeds[selected].shape}, '
212
+ f'vit_embeds.shape={vit_embeds.shape}')
213
+ n_token = min(selected.sum(), vit_embeds.size(0))
214
+ input_embeds[selected][:n_token] = input_embeds[selected][:n_token] * 0.0 + vit_embeds[:n_token]
215
+
216
+ input_embeds = input_embeds.reshape(B, N, C)
217
+
218
+ outputs = self.language_model(
219
+ inputs_embeds=input_embeds,
220
+ attention_mask=attention_mask,
221
+ position_ids=position_ids,
222
+ past_key_values=past_key_values,
223
+ use_cache=use_cache,
224
+ output_attentions=output_attentions,
225
+ output_hidden_states=output_hidden_states,
226
+ return_dict=return_dict,
227
+ )
228
+ logits = outputs.logits
229
+
230
+ loss = None
231
+ if labels is not None:
232
+ # Shift so that tokens < n predict n
233
+ shift_logits = logits[..., :-1, :].contiguous()
234
+ shift_labels = labels[..., 1:].contiguous()
235
+ # Flatten the tokens
236
+ loss_fct = CrossEntropyLoss()
237
+ shift_logits = shift_logits.view(-1, self.language_model.config.vocab_size)
238
+ shift_labels = shift_labels.view(-1)
239
+ # Enable model parallelism
240
+ shift_labels = shift_labels.to(shift_logits.device)
241
+ loss = loss_fct(shift_logits, shift_labels)
242
+
243
+ if not return_dict:
244
+ output = (logits,) + outputs[1:]
245
+ return (loss,) + output if loss is not None else output
246
+
247
+ return CausalLMOutputWithPast(
248
+ loss=loss,
249
+ logits=logits,
250
+ past_key_values=outputs.past_key_values,
251
+ hidden_states=outputs.hidden_states,
252
+ attentions=outputs.attentions,
253
+ )
254
+
255
+ def pixel_shuffle(self, x, scale_factor=0.5):
256
+ n, w, h, c = x.size()
257
+ # N, W, H, C --> N, W, H * scale, C // scale
258
+ x = x.view(n, w, int(h * scale_factor), int(c / scale_factor))
259
+ # N, W, H * scale, C // scale --> N, H * scale, W, C // scale
260
+ x = x.permute(0, 2, 1, 3).contiguous()
261
+ # N, H * scale, W, C // scale --> N, H * scale, W * scale, C // (scale ** 2)
262
+ x = x.view(n, int(h * scale_factor), int(w * scale_factor),
263
+ int(c / (scale_factor * scale_factor)))
264
+ if self.ps_version == 'v1':
265
+ warnings.warn("In ps_version 'v1', the height and width have not been swapped back, "
266
+ 'which results in a transposed image.')
267
+ else:
268
+ x = x.permute(0, 2, 1, 3).contiguous()
269
+ return x
270
+
271
+ def extract_feature(self, pixel_values):
272
+ if self.select_layer == -1:
273
+ vit_embeds = self.vision_model(
274
+ pixel_values=pixel_values,
275
+ output_hidden_states=False,
276
+ return_dict=True).last_hidden_state
277
+ else:
278
+ vit_embeds = self.vision_model(
279
+ pixel_values=pixel_values,
280
+ output_hidden_states=True,
281
+ return_dict=True).hidden_states[self.select_layer]
282
+ vit_embeds = vit_embeds[:, 1:, :]
283
+
284
+ h = w = int(vit_embeds.shape[1] ** 0.5)
285
+ vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], h, w, -1)
286
+ vit_embeds = self.pixel_shuffle(vit_embeds, scale_factor=self.downsample_ratio)
287
+ vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], -1, vit_embeds.shape[-1])
288
+ vit_embeds = self.mlp1(vit_embeds)
289
+ return vit_embeds
290
+
291
+ def batch_chat(self, tokenizer, pixel_values, questions, generation_config, num_patches_list=None,
292
+ history=None, return_history=False, IMG_START_TOKEN='<img>', IMG_END_TOKEN='</img>',
293
+ IMG_CONTEXT_TOKEN='<IMG_CONTEXT>', verbose=False, image_counts=None):
294
+ if history is not None or return_history:
295
+ print('Now multi-turn chat is not supported in batch_chat.')
296
+ raise NotImplementedError
297
+
298
+ if image_counts is not None:
299
+ num_patches_list = image_counts
300
+ print('Warning: `image_counts` is deprecated. Please use `num_patches_list` instead.')
301
+
302
+ img_context_token_id = tokenizer.convert_tokens_to_ids(IMG_CONTEXT_TOKEN)
303
+ self.img_context_token_id = img_context_token_id
304
+
305
+ if verbose and pixel_values is not None:
306
+ image_bs = pixel_values.shape[0]
307
+ print(f'dynamic ViT batch size: {image_bs}')
308
+
309
+ queries = []
310
+ for idx, num_patches in enumerate(num_patches_list):
311
+ question = questions[idx]
312
+ if pixel_values is not None and '<image>' not in question:
313
+ question = '<image>\n' + question
314
+ template = get_conv_template(self.template)
315
+ template.system_message = self.system_message
316
+ template.append_message(template.roles[0], question)
317
+ template.append_message(template.roles[1], None)
318
+ query = template.get_prompt()
319
+
320
+ image_tokens = IMG_START_TOKEN + IMG_CONTEXT_TOKEN * self.num_image_token * num_patches + IMG_END_TOKEN
321
+ query = query.replace('<image>', image_tokens, 1)
322
+ queries.append(query)
323
+
324
+ tokenizer.padding_side = 'left'
325
+ model_inputs = tokenizer(queries, return_tensors='pt', padding=True)
326
+ input_ids = model_inputs['input_ids'].to(self.device)
327
+ attention_mask = model_inputs['attention_mask'].to(self.device)
328
+ eos_token_id = tokenizer.convert_tokens_to_ids(template.sep.strip())
329
+ generation_config['eos_token_id'] = eos_token_id
330
+ generation_output = self.generate(
331
+ pixel_values=pixel_values,
332
+ input_ids=input_ids,
333
+ attention_mask=attention_mask,
334
+ **generation_config
335
+ )
336
+ responses = tokenizer.batch_decode(generation_output, skip_special_tokens=True)
337
+ responses = [response.split(template.sep.strip())[0].strip() for response in responses]
338
+ return responses
339
+
340
+ def chat(self, tokenizer, pixel_values, question, generation_config, history=None, return_history=False,
341
+ num_patches_list=None, IMG_START_TOKEN='<img>', IMG_END_TOKEN='</img>', IMG_CONTEXT_TOKEN='<IMG_CONTEXT>',
342
+ verbose=False):
343
+
344
+ if history is None and pixel_values is not None and '<image>' not in question:
345
+ question = '<image>\n' + question
346
+
347
+ if num_patches_list is None:
348
+ num_patches_list = [pixel_values.shape[0]] if pixel_values is not None else []
349
+ assert pixel_values is None or len(pixel_values) == sum(num_patches_list)
350
+
351
+ img_context_token_id = tokenizer.convert_tokens_to_ids(IMG_CONTEXT_TOKEN)
352
+ self.img_context_token_id = img_context_token_id
353
+
354
+ template = get_conv_template(self.template)
355
+ template.system_message = self.system_message
356
+ eos_token_id = tokenizer.convert_tokens_to_ids(template.sep.strip())
357
+
358
+ history = [] if history is None else history
359
+ for (old_question, old_answer) in history:
360
+ template.append_message(template.roles[0], old_question)
361
+ template.append_message(template.roles[1], old_answer)
362
+ template.append_message(template.roles[0], question)
363
+ template.append_message(template.roles[1], None)
364
+ query = template.get_prompt()
365
+
366
+ if verbose and pixel_values is not None:
367
+ image_bs = pixel_values.shape[0]
368
+ print(f'dynamic ViT batch size: {image_bs}')
369
+
370
+ for num_patches in num_patches_list:
371
+ image_tokens = IMG_START_TOKEN + IMG_CONTEXT_TOKEN * self.num_image_token * num_patches + IMG_END_TOKEN
372
+ query = query.replace('<image>', image_tokens, 1)
373
+
374
+ model_inputs = tokenizer(query, return_tensors='pt')
375
+ input_ids = model_inputs['input_ids'].to(self.device)
376
+ attention_mask = model_inputs['attention_mask'].to(self.device)
377
+ generation_config['eos_token_id'] = eos_token_id
378
+ generation_output = self.generate(
379
+ pixel_values=pixel_values,
380
+ input_ids=input_ids,
381
+ attention_mask=attention_mask,
382
+ **generation_config
383
+ )
384
+ response = tokenizer.batch_decode(generation_output, skip_special_tokens=True)[0]
385
+ response = response.split(template.sep.strip())[0].strip()
386
+ history.append((question, response))
387
+ if return_history:
388
+ return response, history
389
+ else:
390
+ query_to_print = query.replace(IMG_CONTEXT_TOKEN, '')
391
+ query_to_print = query_to_print.replace(f'{IMG_START_TOKEN}{IMG_END_TOKEN}', '<image>')
392
+ if verbose:
393
+ print(query_to_print, response)
394
+ return response
395
+
396
+ @torch.no_grad()
397
+ def generate(
398
+ self,
399
+ pixel_values: Optional[torch.FloatTensor] = None,
400
+ input_ids: Optional[torch.FloatTensor] = None,
401
+ attention_mask: Optional[torch.LongTensor] = None,
402
+ visual_features: Optional[torch.FloatTensor] = None,
403
+ generation_config: Optional[GenerationConfig] = None,
404
+ output_hidden_states: Optional[bool] = None,
405
+ **generate_kwargs,
406
+ ) -> torch.LongTensor:
407
+
408
+ assert self.img_context_token_id is not None
409
+ if pixel_values is not None:
410
+ if visual_features is not None:
411
+ vit_embeds = visual_features
412
+ else:
413
+ vit_embeds = self.extract_feature(pixel_values)
414
+ input_embeds = self.language_model.get_input_embeddings()(input_ids)
415
+ B, N, C = input_embeds.shape
416
+ input_embeds = input_embeds.reshape(B * N, C)
417
+
418
+ input_ids = input_ids.reshape(B * N)
419
+ selected = (input_ids == self.img_context_token_id)
420
+ assert selected.sum() != 0
421
+ input_embeds[selected] = vit_embeds.reshape(-1, C).to(input_embeds.device)
422
+
423
+ input_embeds = input_embeds.reshape(B, N, C)
424
+ else:
425
+ input_embeds = self.language_model.get_input_embeddings()(input_ids)
426
+
427
+ outputs = self.language_model.generate(
428
+ inputs_embeds=input_embeds,
429
+ attention_mask=attention_mask,
430
+ generation_config=generation_config,
431
+ output_hidden_states=output_hidden_states,
432
+ use_cache=True,
433
+ **generate_kwargs,
434
+ )
435
+
436
+ return outputs
437
+
438
+ def load_mtp_weights(self, path=None, prefix='language_model.mtp.'):
439
+ """Load P-MTP head weights.
440
+
441
+ If ``path`` is None the weights are assumed to already be present (e.g.
442
+ loaded from the main checkpoint via ``from_pretrained`` because the keys
443
+ ``language_model.mtp.*`` match this module tree) and this call only marks
444
+ MTP as usable. Otherwise ``path`` may be a ``.safetensors`` file or a
445
+ directory containing one; keys are stripped of ``prefix`` (or a leading
446
+ ``mtp.``) before loading.
447
+ """
448
+ if path is None:
449
+ self._mtp_weights_loaded = True
450
+ return
451
+ from safetensors.torch import load_file
452
+ if os.path.isdir(path):
453
+ files = [os.path.join(path, f) for f in sorted(os.listdir(path))
454
+ if f.endswith('.safetensors')]
455
+ else:
456
+ files = [path]
457
+ remapped = {}
458
+ for fp in files:
459
+ for k, v in load_file(fp).items():
460
+ name = k
461
+ if name.startswith(prefix):
462
+ name = name[len(prefix):]
463
+ elif name.startswith('mtp.'):
464
+ name = name[len('mtp.'):]
465
+ else:
466
+ continue
467
+ remapped[name] = v
468
+ if not remapped:
469
+ raise RuntimeError(f'No MTP tensors (prefix {prefix!r}) found under {path}')
470
+ missing, unexpected = self.language_model.mtp.load_state_dict(remapped, strict=False)
471
+ self.language_model.mtp.to(device=self.device, dtype=self.dtype)
472
+ self._mtp_weights_loaded = True
473
+ return missing, unexpected
474
+
475
+ def _embed_ids(self, input_ids, vit_embeds=None):
476
+ embeds = self.language_model.get_input_embeddings()(input_ids)
477
+ if vit_embeds is not None:
478
+ B, N, C = embeds.shape
479
+ flat = embeds.reshape(B * N, C)
480
+ selected = (input_ids.reshape(B * N) == self.img_context_token_id)
481
+ flat[selected] = vit_embeds.reshape(-1, C).to(flat.dtype)
482
+ embeds = flat.reshape(B, N, C)
483
+ return embeds
484
+
485
+ def _lm_forward(self, inputs_embeds, cache, need_hidden):
486
+ past_len = cache.get_seq_length()
487
+ seq_len = inputs_embeds.shape[1]
488
+ cache_position = torch.arange(past_len, past_len + seq_len, device=inputs_embeds.device)
489
+ out = self.language_model(
490
+ inputs_embeds=inputs_embeds,
491
+ past_key_values=cache,
492
+ use_cache=True,
493
+ cache_position=cache_position,
494
+ position_ids=cache_position.unsqueeze(0),
495
+ output_hidden_states=need_hidden,
496
+ return_dict=True,
497
+ )
498
+ hidden = out.hidden_states[-1] if need_hidden else None
499
+ return out.logits, hidden, out.past_key_values
500
+
501
+ @torch.no_grad()
502
+ def _decode_children_batched(self, parent_cache, prompt_len, fork_indices,
503
+ eos_ids, max_new_tokens):
504
+ """Decode all fork children concurrently as one batch (greedy AR).
505
+
506
+ Each child ``i`` inherits the parent prefix up to its ``<FORK>`` (real
507
+ length ``Li = prompt_len + fork_indices[i]``). The parent KV is copied
508
+ into a left-padded batched cache (row ``i`` right-aligned to hold its
509
+ ``Li`` prefix positions), then all children step in lockstep until every
510
+ row hits EOS. Per-row ``position_ids`` carry each child's true positions
511
+ so RoPE matches the parent context. Returns a list of token-id lists.
512
+
513
+ Children run without the P-MTP head (batched ragged acceptance would
514
+ desync row lengths); the greedy output is identical to the serial path.
515
+ """
516
+ device = self.device
517
+ B = len(fork_indices)
518
+ Ls = [prompt_len + f for f in fork_indices]
519
+ Lmax = max(Ls)
520
+
521
+ cache = DynamicCache()
522
+ for idx, layer in enumerate(parent_cache.layers):
523
+ K, V = layer.keys, layer.values # [1, h, Pseq, d]
524
+ h, d = K.shape[1], K.shape[3]
525
+ Kb = K.new_zeros((B, h, Lmax, d))
526
+ Vb = V.new_zeros((B, h, Lmax, d))
527
+ for i, Li in enumerate(Ls):
528
+ Kb[i, :, Lmax - Li:, :] = K[0, :, :Li, :]
529
+ Vb[i, :, Lmax - Li:, :] = V[0, :, :Li, :]
530
+ cache.update(Kb, Vb, idx)
531
+
532
+ mask = torch.zeros((B, Lmax), dtype=torch.long, device=device)
533
+ for i, Li in enumerate(Ls):
534
+ mask[i, Lmax - Li:] = 1
535
+
536
+ real_pos = torch.tensor(Ls, device=device) # <CHILD> sits at position Li
537
+ cur_embeds = self.language_model.get_input_embeddings()(
538
+ torch.tensor([[self.child_token_id]], device=device)).expand(B, 1, -1).contiguous()
539
+
540
+ outputs = [[] for _ in range(B)]
541
+ done = torch.zeros(B, dtype=torch.bool, device=device)
542
+ steps = 0
543
+ while steps < max_new_tokens and not bool(done.all()):
544
+ cache_len = cache.get_seq_length()
545
+ full_mask = torch.cat([mask, torch.ones((B, 1), dtype=torch.long, device=device)], dim=1)
546
+ out = self.language_model(
547
+ inputs_embeds=cur_embeds,
548
+ past_key_values=cache,
549
+ use_cache=True,
550
+ attention_mask=full_mask,
551
+ position_ids=real_pos.unsqueeze(1),
552
+ cache_position=torch.tensor([cache_len], device=device),
553
+ return_dict=True,
554
+ )
555
+ cache = out.past_key_values
556
+ nxt = out.logits[:, -1, :].argmax(-1) # [B]
557
+ mask = full_mask
558
+ real_pos = real_pos + 1
559
+ for i in range(B):
560
+ if not done[i]:
561
+ tid = int(nxt[i])
562
+ outputs[i].append(tid)
563
+ if tid in eos_ids or len(outputs[i]) >= max_new_tokens:
564
+ done[i] = True
565
+ cur_embeds = self.language_model.get_input_embeddings()(nxt.unsqueeze(1))
566
+ steps += 1
567
+ return outputs
568
+
569
+ @torch.no_grad()
570
+ def _decode_from(self, cache, first_embeds, eos_ids, max_new_tokens, use_mtp, k, allow_fork):
571
+ """Greedily decode one branch (batch size 1) starting from ``cache``.
572
+
573
+ ``first_embeds`` primes the branch (the prompt for the parent, or the
574
+ single ``<CHILD>`` token embedding for a child that inherits the parent
575
+ KV cache). With ``use_mtp`` the P-MTP head drafts ``k`` tokens per step
576
+ and the backbone verifies them in a single forward (greedy accept: keep
577
+ the longest matching prefix, then one correction/bonus token), rolling
578
+ the cache back with ``crop``. Returns ``(real_tokens, fork_indices,
579
+ cache)``; ``fork_indices`` marks positions of ``<FORK>`` tokens (only
580
+ when ``allow_fork``).
581
+ """
582
+ device = first_embeds.device
583
+ logits, hidden, cache = self._lm_forward(first_embeds, cache, use_mtp)
584
+ q = cache.get_seq_length() - 1 # cache holds positions [0..q]
585
+ prod_hidden = hidden[:, -1, :] if use_mtp else None
586
+ t = int(logits[0, -1].argmax(-1)) # confirmed token at position q+1
587
+
588
+ real = []
589
+ fork_indices = []
590
+
591
+ def emit(tok):
592
+ real.append(tok)
593
+ if allow_fork and self.fork_token_id is not None and tok == self.fork_token_id:
594
+ fork_indices.append(len(real) - 1)
595
+ return tok in eos_ids
596
+
597
+ stopped = False
598
+ while len(real) < max_new_tokens and not stopped:
599
+ if use_mtp:
600
+ drafts = []
601
+ h = prod_hidden
602
+ prev = t
603
+ for _ in range(k):
604
+ prev_embeds = self.language_model.get_input_embeddings()(
605
+ torch.tensor([prev], device=device))
606
+ h = self.language_model.mtp.step(h, prev_embeds)
607
+ prev = int(self.language_model.lm_head(h).argmax(-1))
608
+ drafts.append(prev)
609
+
610
+ verify_ids = torch.tensor([[t] + drafts], device=device)
611
+ vlogits, vhidden, cache = self._lm_forward(self._embed_ids(verify_ids), cache, True)
612
+ target = vlogits[0].argmax(-1).tolist() # target[i] = argmax after verify_ids[i]
613
+
614
+ emitted = []
615
+ j = 0
616
+ all_match = True
617
+ for i in range(k):
618
+ emitted.append(target[i])
619
+ if drafts[i] != target[i]:
620
+ all_match = False
621
+ break
622
+ j += 1
623
+ if all_match:
624
+ emitted.append(target[k]) # bonus token
625
+
626
+ confirmed = [t] + emitted[:-1]
627
+ next_t = emitted[-1]
628
+ cache.crop((q + 1) + 1 + j) # keep [0..q+1+j]
629
+ prod_hidden = vhidden[0, j:j + 1, :]
630
+ q = q + 1 + j
631
+ t = next_t
632
+ for tok in confirmed:
633
+ if emit(tok):
634
+ stopped = True
635
+ break
636
+ else:
637
+ vlogits, _, cache = self._lm_forward(
638
+ self._embed_ids(torch.tensor([[t]], device=device)), cache, False)
639
+ if emit(t):
640
+ stopped = True
641
+ q = q + 1
642
+ t = int(vlogits[0, -1].argmax(-1))
643
+
644
+ if not stopped and len(real) < max_new_tokens:
645
+ emit(t)
646
+ return real, fork_indices, cache
647
+
648
+ @torch.no_grad()
649
+ def generate_hpd(self, tokenizer, pixel_values, question, generation_config=None,
650
+ use_mtp=False, num_speculative_tokens=6, num_patches_list=None,
651
+ IMG_START_TOKEN='<img>', IMG_END_TOKEN='</img>',
652
+ IMG_CONTEXT_TOKEN='<IMG_CONTEXT>', return_token_ids=False,
653
+ batch_children=False, verbose=False):
654
+ """Hierarchical parallel decoding (``<FORK>``/``<CHILD>``) with optional P-MTP.
655
+
656
+ Reproduces the vLLM decoding paradigm in plain transformers for a single
657
+ image / single prompt (batch size 1):
658
+
659
+ - The parent "layout" branch is decoded greedily; each ``<FORK>`` token
660
+ spawns a content branch. The parent keeps the real ``<FORK>`` in its
661
+ context and continues.
662
+ - Each child branch inherits the parent KV cache (shared-prefix reuse,
663
+ like vLLM). By default children are decoded serially, reusing the
664
+ parent cache in place (crop to the tokens before each ``<FORK>``);
665
+ this is the fastest, lowest-memory option in transformers. Set
666
+ ``batch_children=True`` to instead decode all children concurrently in
667
+ one left-padded batch (``_decode_children_batched``) -- identical
668
+ output, but for many short children over a long prefix it is slower
669
+ and much more memory-hungry (no paged prefix sharing like vLLM).
670
+ - Outputs are spliced: each ``<FORK>`` in the parent stream is rewritten
671
+ to ``<CHILD>`` and the matching child's tokens are inserted after it,
672
+ in fork order.
673
+
674
+ Use ``use_mtp=True`` only after ``load_mtp_weights(...)`` has been called.
675
+ """
676
+ if self.fork_token_id is None or self.child_token_id is None:
677
+ raise RuntimeError('fork_token_id/child_token_id missing from config; cannot fork-decode.')
678
+ if use_mtp and not self._mtp_weights_loaded:
679
+ raise RuntimeError('P-MTP weights not loaded. Call model.load_mtp_weights(path) '
680
+ 'or set use_mtp=False.')
681
+
682
+ generation_config = generation_config or {}
683
+ max_new_tokens = generation_config.get('max_new_tokens', 8000)
684
+ k = num_speculative_tokens
685
+
686
+ if pixel_values is not None and '<image>' not in question:
687
+ question = '<image>\n' + question
688
+ if num_patches_list is None:
689
+ num_patches_list = [pixel_values.shape[0]] if pixel_values is not None else []
690
+ self.img_context_token_id = tokenizer.convert_tokens_to_ids(IMG_CONTEXT_TOKEN)
691
+
692
+ template = get_conv_template(self.template)
693
+ template.system_message = self.system_message
694
+ template.append_message(template.roles[0], question)
695
+ template.append_message(template.roles[1], None)
696
+ query = template.get_prompt()
697
+ for num_patches in num_patches_list:
698
+ image_tokens = IMG_START_TOKEN + IMG_CONTEXT_TOKEN * self.num_image_token * num_patches + IMG_END_TOKEN
699
+ query = query.replace('<image>', image_tokens, 1)
700
+
701
+ prompt_ids = tokenizer(query, return_tensors='pt')['input_ids'].to(self.device)
702
+ prompt_len = prompt_ids.shape[1]
703
+
704
+ eos_ids = set()
705
+ sep_id = tokenizer.convert_tokens_to_ids(template.sep.strip())
706
+ if isinstance(sep_id, int) and sep_id >= 0:
707
+ eos_ids.add(sep_id)
708
+ if self.config.eos_token_id is not None:
709
+ eos_ids.add(self.config.eos_token_id)
710
+
711
+ vit_embeds = self.extract_feature(pixel_values) if pixel_values is not None else None
712
+ prompt_embeds = self._embed_ids(prompt_ids, vit_embeds)
713
+
714
+ parent_real, fork_indices, parent_cache = self._decode_from(
715
+ DynamicCache(), prompt_embeds, eos_ids, max_new_tokens, use_mtp, k, allow_fork=True)
716
+
717
+ # Once the parent finishes, every fork point and its shared prefix is
718
+ # fixed. Children can then be decoded either serially (reusing the parent
719
+ # cache in place, low memory) or concurrently in one padded batch
720
+ # (``batch_children=True``). NOTE: for many short children over a long
721
+ # shared prefix, batching is slower and far more memory-hungry in stock
722
+ # transformers (the prefix KV is duplicated per row and short children
723
+ # still step until the longest one finishes), so serial is the default.
724
+ children = []
725
+ if fork_indices and batch_children:
726
+ children = self._decode_children_batched(
727
+ parent_cache, prompt_len, fork_indices, eos_ids, max_new_tokens)
728
+ elif fork_indices:
729
+ child_embeds = self.language_model.get_input_embeddings()(
730
+ torch.tensor([[self.child_token_id]], device=self.device))
731
+ children = [None] * len(fork_indices)
732
+ # Reverse fork order: crop the shared parent cache to the tokens
733
+ # before each <FORK> and reuse it in place. A child only appends
734
+ # beyond its crop point, which the next (smaller) crop discards, so
735
+ # the parent prefix KV stays intact and no clone is needed.
736
+ for i in range(len(fork_indices) - 1, -1, -1):
737
+ parent_cache.crop(prompt_len + fork_indices[i])
738
+ child_real, _, _ = self._decode_from(
739
+ parent_cache, child_embeds, eos_ids, max_new_tokens, use_mtp, k, allow_fork=False)
740
+ children[i] = child_real
741
+
742
+ final_ids = []
743
+ child_ptr = 0
744
+ for tok in parent_real:
745
+ if tok == self.fork_token_id:
746
+ final_ids.append(self.child_token_id)
747
+ if child_ptr < len(children):
748
+ final_ids.extend(children[child_ptr])
749
+ child_ptr += 1
750
+ else:
751
+ final_ids.append(tok)
752
+
753
+ text = tokenizer.decode(final_ids, skip_special_tokens=True)
754
+ if verbose:
755
+ print(f'[HPD] parent tokens={len(parent_real)}, forks={len(fork_indices)}, '
756
+ f'children_tokens={[len(c) for c in children]}')
757
+ if return_token_ids:
758
+ return text, final_ids
759
+ return text
760
+
761
+ @property
762
+ def lm_head(self):
763
+ return self.language_model.get_output_embeddings()
764
+
765
+ def get_output_embeddings(self):
766
+ return self.language_model.get_output_embeddings()
767
+
768
+ def get_input_embeddings(self):
769
+ return self.language_model.get_input_embeddings()
770
+
771
+ def set_input_embeddings(self, value):
772
+ return self.language_model.set_input_embeddings(value)
773
+
774
+ def set_output_embeddings(self, value):
775
+ return self.language_model.set_output_embeddings(value)
preprocessor_config.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "crop_size": null,
3
+ "crop_to_patches": false,
4
+ "data_format": "channels_first",
5
+ "default_to_square": true,
6
+ "device": null,
7
+ "do_center_crop": null,
8
+ "do_convert_rgb": true,
9
+ "do_normalize": true,
10
+ "do_rescale": true,
11
+ "do_resize": true,
12
+ "image_mean": [
13
+ 0.485,
14
+ 0.456,
15
+ 0.406
16
+ ],
17
+ "image_processor_type": "GotOcr2ImageProcessorFast",
18
+ "image_std": [
19
+ 0.229,
20
+ 0.224,
21
+ 0.225
22
+ ],
23
+ "input_data_format": null,
24
+ "max_patches": 12,
25
+ "min_patches": 1,
26
+ "processor_class": "InternVLProcessor",
27
+ "resample": 3,
28
+ "rescale_factor": 0.00392156862745098,
29
+ "return_tensors": null,
30
+ "size": {
31
+ "height": 448,
32
+ "width": 448
33
+ }
34
+ }
special_tokens_map.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|im_start|>",
4
+ "<|im_end|>",
5
+ "<|object_ref_start|>",
6
+ "<|object_ref_end|>",
7
+ "<|box_start|>",
8
+ "<|box_end|>",
9
+ "<|quad_start|>",
10
+ "<|quad_end|>",
11
+ "<|vision_start|>",
12
+ "<|vision_end|>",
13
+ "<|vision_pad|>",
14
+ "<|image_pad|>",
15
+ "<|video_pad|>",
16
+ "<img>",
17
+ "</img>",
18
+ "<IMG_CONTEXT>",
19
+ "<quad>",
20
+ "</quad>",
21
+ "<ref>",
22
+ "</ref>",
23
+ "<box>",
24
+ "</box>"
25
+ ],
26
+ "eos_token": {
27
+ "content": "<|im_end|>",
28
+ "lstrip": false,
29
+ "normalized": false,
30
+ "rstrip": false,
31
+ "single_word": false
32
+ },
33
+ "pad_token": {
34
+ "content": "<|endoftext|>",
35
+ "lstrip": false,
36
+ "normalized": false,
37
+ "rstrip": false,
38
+ "single_word": false
39
+ }
40
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e9a5bd06115c607d2bda92cdefb7b21e915832ea58afd1688796a4db56e6d67
3
+ size 11424851
tokenizer_config.json ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_eos_token": false,
4
+ "add_prefix_space": false,
5
+ "added_tokens_decoder": {
6
+ "151643": {
7
+ "content": "<|endoftext|>",
8
+ "lstrip": false,
9
+ "normalized": false,
10
+ "rstrip": false,
11
+ "single_word": false,
12
+ "special": true
13
+ },
14
+ "151644": {
15
+ "content": "<|im_start|>",
16
+ "lstrip": false,
17
+ "normalized": false,
18
+ "rstrip": false,
19
+ "single_word": false,
20
+ "special": true
21
+ },
22
+ "151645": {
23
+ "content": "<|im_end|>",
24
+ "lstrip": false,
25
+ "normalized": false,
26
+ "rstrip": false,
27
+ "single_word": false,
28
+ "special": true
29
+ },
30
+ "151646": {
31
+ "content": "<|object_ref_start|>",
32
+ "lstrip": false,
33
+ "normalized": false,
34
+ "rstrip": false,
35
+ "single_word": false,
36
+ "special": true
37
+ },
38
+ "151647": {
39
+ "content": "<|object_ref_end|>",
40
+ "lstrip": false,
41
+ "normalized": false,
42
+ "rstrip": false,
43
+ "single_word": false,
44
+ "special": true
45
+ },
46
+ "151648": {
47
+ "content": "<|box_start|>",
48
+ "lstrip": false,
49
+ "normalized": false,
50
+ "rstrip": false,
51
+ "single_word": false,
52
+ "special": true
53
+ },
54
+ "151649": {
55
+ "content": "<|box_end|>",
56
+ "lstrip": false,
57
+ "normalized": false,
58
+ "rstrip": false,
59
+ "single_word": false,
60
+ "special": true
61
+ },
62
+ "151650": {
63
+ "content": "<|quad_start|>",
64
+ "lstrip": false,
65
+ "normalized": false,
66
+ "rstrip": false,
67
+ "single_word": false,
68
+ "special": true
69
+ },
70
+ "151651": {
71
+ "content": "<|quad_end|>",
72
+ "lstrip": false,
73
+ "normalized": false,
74
+ "rstrip": false,
75
+ "single_word": false,
76
+ "special": true
77
+ },
78
+ "151652": {
79
+ "content": "<|vision_start|>",
80
+ "lstrip": false,
81
+ "normalized": false,
82
+ "rstrip": false,
83
+ "single_word": false,
84
+ "special": true
85
+ },
86
+ "151653": {
87
+ "content": "<|vision_end|>",
88
+ "lstrip": false,
89
+ "normalized": false,
90
+ "rstrip": false,
91
+ "single_word": false,
92
+ "special": true
93
+ },
94
+ "151654": {
95
+ "content": "<|vision_pad|>",
96
+ "lstrip": false,
97
+ "normalized": false,
98
+ "rstrip": false,
99
+ "single_word": false,
100
+ "special": true
101
+ },
102
+ "151655": {
103
+ "content": "<|image_pad|>",
104
+ "lstrip": false,
105
+ "normalized": false,
106
+ "rstrip": false,
107
+ "single_word": false,
108
+ "special": true
109
+ },
110
+ "151656": {
111
+ "content": "<|video_pad|>",
112
+ "lstrip": false,
113
+ "normalized": false,
114
+ "rstrip": false,
115
+ "single_word": false,
116
+ "special": true
117
+ },
118
+ "151657": {
119
+ "content": "<tool_call>",
120
+ "lstrip": false,
121
+ "normalized": false,
122
+ "rstrip": false,
123
+ "single_word": false,
124
+ "special": false
125
+ },
126
+ "151658": {
127
+ "content": "</tool_call>",
128
+ "lstrip": false,
129
+ "normalized": false,
130
+ "rstrip": false,
131
+ "single_word": false,
132
+ "special": false
133
+ },
134
+ "151659": {
135
+ "content": "<|fim_prefix|>",
136
+ "lstrip": false,
137
+ "normalized": false,
138
+ "rstrip": false,
139
+ "single_word": false,
140
+ "special": false
141
+ },
142
+ "151660": {
143
+ "content": "<|fim_middle|>",
144
+ "lstrip": false,
145
+ "normalized": false,
146
+ "rstrip": false,
147
+ "single_word": false,
148
+ "special": false
149
+ },
150
+ "151661": {
151
+ "content": "<|fim_suffix|>",
152
+ "lstrip": false,
153
+ "normalized": false,
154
+ "rstrip": false,
155
+ "single_word": false,
156
+ "special": false
157
+ },
158
+ "151662": {
159
+ "content": "<|fim_pad|>",
160
+ "lstrip": false,
161
+ "normalized": false,
162
+ "rstrip": false,
163
+ "single_word": false,
164
+ "special": false
165
+ },
166
+ "151663": {
167
+ "content": "<|repo_name|>",
168
+ "lstrip": false,
169
+ "normalized": false,
170
+ "rstrip": false,
171
+ "single_word": false,
172
+ "special": false
173
+ },
174
+ "151664": {
175
+ "content": "<|file_sep|>",
176
+ "lstrip": false,
177
+ "normalized": false,
178
+ "rstrip": false,
179
+ "single_word": false,
180
+ "special": false
181
+ },
182
+ "151665": {
183
+ "content": "<tool_response>",
184
+ "lstrip": false,
185
+ "normalized": false,
186
+ "rstrip": false,
187
+ "single_word": false,
188
+ "special": false
189
+ },
190
+ "151666": {
191
+ "content": "</tool_response>",
192
+ "lstrip": false,
193
+ "normalized": false,
194
+ "rstrip": false,
195
+ "single_word": false,
196
+ "special": false
197
+ },
198
+ "151667": {
199
+ "content": "<think>",
200
+ "lstrip": false,
201
+ "normalized": false,
202
+ "rstrip": false,
203
+ "single_word": false,
204
+ "special": false
205
+ },
206
+ "151668": {
207
+ "content": "</think>",
208
+ "lstrip": false,
209
+ "normalized": false,
210
+ "rstrip": false,
211
+ "single_word": false,
212
+ "special": false
213
+ },
214
+ "151669": {
215
+ "content": "<img>",
216
+ "lstrip": false,
217
+ "normalized": false,
218
+ "rstrip": false,
219
+ "single_word": false,
220
+ "special": true
221
+ },
222
+ "151670": {
223
+ "content": "</img>",
224
+ "lstrip": false,
225
+ "normalized": false,
226
+ "rstrip": false,
227
+ "single_word": false,
228
+ "special": true
229
+ },
230
+ "151671": {
231
+ "content": "<IMG_CONTEXT>",
232
+ "lstrip": false,
233
+ "normalized": false,
234
+ "rstrip": false,
235
+ "single_word": false,
236
+ "special": true
237
+ },
238
+ "151672": {
239
+ "content": "<quad>",
240
+ "lstrip": false,
241
+ "normalized": false,
242
+ "rstrip": false,
243
+ "single_word": false,
244
+ "special": true
245
+ },
246
+ "151673": {
247
+ "content": "</quad>",
248
+ "lstrip": false,
249
+ "normalized": false,
250
+ "rstrip": false,
251
+ "single_word": false,
252
+ "special": true
253
+ },
254
+ "151674": {
255
+ "content": "<ref>",
256
+ "lstrip": false,
257
+ "normalized": false,
258
+ "rstrip": false,
259
+ "single_word": false,
260
+ "special": true
261
+ },
262
+ "151675": {
263
+ "content": "</ref>",
264
+ "lstrip": false,
265
+ "normalized": false,
266
+ "rstrip": false,
267
+ "single_word": false,
268
+ "special": true
269
+ },
270
+ "151676": {
271
+ "content": "<box>",
272
+ "lstrip": false,
273
+ "normalized": false,
274
+ "rstrip": false,
275
+ "single_word": false,
276
+ "special": true
277
+ },
278
+ "151677": {
279
+ "content": "</box>",
280
+ "lstrip": false,
281
+ "normalized": false,
282
+ "rstrip": false,
283
+ "single_word": false,
284
+ "special": true
285
+ },
286
+ "151678": {
287
+ "content": "<BLOCK>",
288
+ "lstrip": false,
289
+ "normalized": true,
290
+ "rstrip": false,
291
+ "single_word": false,
292
+ "special": false
293
+ },
294
+ "151679": {
295
+ "content": "<FORK>",
296
+ "lstrip": false,
297
+ "normalized": true,
298
+ "rstrip": false,
299
+ "single_word": false,
300
+ "special": false
301
+ },
302
+ "151680": {
303
+ "content": "<CHILD>",
304
+ "lstrip": false,
305
+ "normalized": true,
306
+ "rstrip": false,
307
+ "single_word": false,
308
+ "special": false
309
+ }
310
+ },
311
+ "additional_special_tokens": [
312
+ "<|im_start|>",
313
+ "<|im_end|>",
314
+ "<|object_ref_start|>",
315
+ "<|object_ref_end|>",
316
+ "<|box_start|>",
317
+ "<|box_end|>",
318
+ "<|quad_start|>",
319
+ "<|quad_end|>",
320
+ "<|vision_start|>",
321
+ "<|vision_end|>",
322
+ "<|vision_pad|>",
323
+ "<|image_pad|>",
324
+ "<|video_pad|>",
325
+ "<img>",
326
+ "</img>",
327
+ "<IMG_CONTEXT>",
328
+ "<quad>",
329
+ "</quad>",
330
+ "<ref>",
331
+ "</ref>",
332
+ "<box>",
333
+ "</box>"
334
+ ],
335
+ "bos_token": null,
336
+ "clean_up_tokenization_spaces": false,
337
+ "eos_token": "<|im_end|>",
338
+ "errors": "replace",
339
+ "extra_special_tokens": {},
340
+ "model_max_length": 14588,
341
+ "pad_token": "<|endoftext|>",
342
+ "processor_class": "InternVLProcessor",
343
+ "split_special_tokens": false,
344
+ "tokenizer_class": "Qwen2Tokenizer",
345
+ "unk_token": null
346
+ }
vocab.json ADDED
The diff for this file is too large to render. See raw diff