Image-Text-to-Text
Transformers
Safetensors
English
Chinese
internvl_chat
feature-extraction
visual-language
paddleocr
document-parse
HPD-Parsing
speculative-decoding
P-MTP
eval results
conversational
custom_code
Instructions to use PaddlePaddle/HPD-Parsing with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use PaddlePaddle/HPD-Parsing with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="PaddlePaddle/HPD-Parsing", trust_remote_code=True) messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("PaddlePaddle/HPD-Parsing", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use PaddlePaddle/HPD-Parsing with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "PaddlePaddle/HPD-Parsing" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "PaddlePaddle/HPD-Parsing", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/PaddlePaddle/HPD-Parsing
- SGLang
How to use PaddlePaddle/HPD-Parsing with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "PaddlePaddle/HPD-Parsing" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "PaddlePaddle/HPD-Parsing", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "PaddlePaddle/HPD-Parsing" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "PaddlePaddle/HPD-Parsing", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use PaddlePaddle/HPD-Parsing with Docker Model Runner:
docker model run hf.co/PaddlePaddle/HPD-Parsing
Upload folder using huggingface_hub
Browse files- .gitattributes +2 -0
- HPD_Parsing.pdf +3 -0
- P-MTP/config.json +6 -0
- README.md +229 -0
- added_tokens.json +40 -0
- chat_template.jinja +6 -0
- config.json +131 -0
- configuration_intern_vit.py +119 -0
- configuration_internvl_chat.py +115 -0
- conversation.py +391 -0
- eval/README.md +59 -0
- eval/benchmark_tps.py +143 -0
- eval/hpd_to_markdown.py +206 -0
- generation_config.json +5 -0
- image_preprocess.py +84 -0
- merges.txt +0 -0
- modeling_intern_vit.py +433 -0
- modeling_internvl_chat.py +775 -0
- preprocessor_config.json +34 -0
- special_tokens_map.json +40 -0
- tokenizer.json +3 -0
- tokenizer_config.json +346 -0
- vocab.json +0 -0
.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 |
+
[](https://github.com/PaddlePaddle/PaddleOCR)
|
| 35 |
+
[](https://github.com/vllm-project/vllm)
|
| 36 |
+
[](./HPD_Parsing.pdf)
|
| 37 |
+
[](https://x.com/PaddlePaddle)
|
| 38 |
+
[](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
|
|
|