init
Browse files- .gitattributes +2 -34
- .gitignore +44 -0
- LICENSE +140 -0
- README.md +0 -3
- added_tokens.json +28 -0
- chat_template.jinja +18 -0
- config.json +109 -0
- configuration_oxr.py +100 -0
- generation_config.json +8 -0
- layout_analysis/README.md +33 -0
- layout_analysis/last.pth +3 -0
- layout_analysis/metadata.json +29 -0
- merges.txt +0 -0
- model.safetensors +3 -0
- modeling_oxr.py +1037 -0
- plugins/vllm/README.md +30 -0
- plugins/vllm/oxr_vllm/__init__.py +11 -0
- plugins/vllm/oxr_vllm/oxr_model.py +660 -0
- plugins/vllm/oxr_vllm/oxr_processor.py +384 -0
- plugins/vllm/pyproject.toml +16 -0
- preprocessor_config.json +24 -0
- special_tokens_map.json +31 -0
- tokenizer.json +3 -0
- tokenizer_config.json +240 -0
- vocab.json +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,3 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignore non-release weights; release checkpoints are tracked with Git LFS.
|
| 2 |
+
*.safetensors
|
| 3 |
+
*.bin
|
| 4 |
+
*.pt
|
| 5 |
+
*.pth
|
| 6 |
+
*.ckpt
|
| 7 |
+
*.gguf
|
| 8 |
+
*.onnx
|
| 9 |
+
|
| 10 |
+
# Weight index — references .safetensors, meaningless without them
|
| 11 |
+
model.safetensors.index.json
|
| 12 |
+
|
| 13 |
+
!model.safetensors
|
| 14 |
+
!layout_analysis/last.pth
|
| 15 |
+
|
| 16 |
+
# Training artifacts (not part of the modeling spec)
|
| 17 |
+
trainer_state.json
|
| 18 |
+
training_args.bin
|
| 19 |
+
zero_to_fp32.py
|
| 20 |
+
optimizer.pt
|
| 21 |
+
scheduler.pt
|
| 22 |
+
rng_state.pth
|
| 23 |
+
|
| 24 |
+
# Misc
|
| 25 |
+
*.bak
|
| 26 |
+
.DS_Store
|
| 27 |
+
__pycache__/
|
| 28 |
+
*.pyc
|
| 29 |
+
*.egg-info/
|
| 30 |
+
build/
|
| 31 |
+
dist/
|
| 32 |
+
.pytest_cache/
|
| 33 |
+
.venv/
|
| 34 |
+
|
| 35 |
+
# Local coding-assistant instructions are not model-release assets.
|
| 36 |
+
[Aa][Gg][Ee][Nn][Tt][Ss].md
|
| 37 |
+
[Aa][Gg][Ee][Nn][Tt][Ss].override.md
|
| 38 |
+
[Cc][Ll][Aa][Uu][Dd][Ee].md
|
| 39 |
+
[Gg][Ee][Mm][Ii][Nn][Ii].md
|
| 40 |
+
copilot-instructions.md
|
| 41 |
+
.claude/
|
| 42 |
+
.codex/
|
| 43 |
+
.agents/
|
| 44 |
+
.cursor/
|
LICENSE
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OXR License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 SoMark Team
|
| 4 |
+
|
| 5 |
+
OXR is licensed under the Apache License, Version 2.0, subject to the
|
| 6 |
+
additional terms below. These additional terms take precedence in the event
|
| 7 |
+
of a conflict; otherwise, Apache License 2.0 applies.
|
| 8 |
+
|
| 9 |
+
## 1. Commercial License and Thresholds
|
| 10 |
+
|
| 11 |
+
Commercial use of OXR and its Derivative Works is permitted without a
|
| 12 |
+
separate commercial license only while you and your Affiliates, on a
|
| 13 |
+
consolidated basis, do not exceed either of the following thresholds:
|
| 14 |
+
|
| 15 |
+
(a) USD 2,000,000 in Total Funding; or
|
| 16 |
+
(b) USD 2,000,000 in Annual Revenue.
|
| 17 |
+
|
| 18 |
+
If either threshold is exceeded, you must obtain a separate commercial
|
| 19 |
+
license from SoMark Team before commencing or continuing commercial use.
|
| 20 |
+
Being below these thresholds does not waive any other term of this License,
|
| 21 |
+
including the competing hosted API restriction in Section 3.
|
| 22 |
+
|
| 23 |
+
## 2. Online Service Attribution
|
| 24 |
+
|
| 25 |
+
When providing an online service to third parties using OXR or its Derivative
|
| 26 |
+
Works, you must prominently identify SoMark OXR in the service's user interface or
|
| 27 |
+
publicly accessible documentation.
|
| 28 |
+
|
| 29 |
+
## 3. Competing Hosted API Services
|
| 30 |
+
|
| 31 |
+
Unless expressly authorized in advance by a separate written license from
|
| 32 |
+
SoMark Team, you may not use OXR or its Derivative Works to offer, operate,
|
| 33 |
+
or make available to third parties a hosted API service that competes with
|
| 34 |
+
a hosted API service offered by SoMark Team or its Affiliates.
|
| 35 |
+
|
| 36 |
+
This restriction includes services offered through a reseller, a white-label
|
| 37 |
+
arrangement, or an intermediary interface, whether paid or free. A competing
|
| 38 |
+
hosted API service is one whose principal functionality provides document
|
| 39 |
+
parsing, optical character recognition (OCR), document layout analysis, or
|
| 40 |
+
document content extraction as a substitute for substantially similar
|
| 41 |
+
functionality of a hosted API service offered by SoMark Team or its Affiliates.
|
| 42 |
+
|
| 43 |
+
This restriction applies regardless of Total Funding or Annual Revenue.
|
| 44 |
+
Use solely within your organization and its Affiliates, without offering such
|
| 45 |
+
a service to third parties, is not prohibited by this Section; all other
|
| 46 |
+
terms of this License still apply.
|
| 47 |
+
|
| 48 |
+
## 4. Termination
|
| 49 |
+
|
| 50 |
+
Your rights under this License terminate automatically, without notice from
|
| 51 |
+
SoMark Team, if you fail to obtain a commercial license as required by
|
| 52 |
+
Section 1, fail to comply with Section 2, or violate Section 3.
|
| 53 |
+
|
| 54 |
+
## 5. Definitions and Scope
|
| 55 |
+
|
| 56 |
+
"Affiliates" are entities that directly or indirectly control you, are
|
| 57 |
+
controlled by you, or share common control with you. "Control" means the
|
| 58 |
+
power to direct an entity's management or operations through ownership,
|
| 59 |
+
voting rights, contractual rights, or other means.
|
| 60 |
+
|
| 61 |
+
"Total Funding" means cumulative equity and debt financing raised from all
|
| 62 |
+
sources by you and your Affiliates, on a consolidated basis, excluding
|
| 63 |
+
intra-group transactions to avoid double counting.
|
| 64 |
+
|
| 65 |
+
"Annual Revenue" means the gross revenue of you and your Affiliates for the
|
| 66 |
+
most recently completed fiscal year, on a consolidated basis, excluding
|
| 67 |
+
intra-group transactions to avoid double counting. Amounts in other
|
| 68 |
+
currencies are measured at their US dollar equivalent.
|
| 69 |
+
|
| 70 |
+
"Derivative Works" has the meaning given in Apache License 2.0. When
|
| 71 |
+
redistributing OXR or its Derivative Works, you must include these additional
|
| 72 |
+
terms together with Apache License 2.0. Third-party components separately
|
| 73 |
+
identified as being under other licenses remain governed by those licenses.
|
| 74 |
+
|
| 75 |
+
The full text of Apache License 2.0 is available at
|
| 76 |
+
https://www.apache.org/licenses/LICENSE-2.0 .
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
# OXR 许可协议
|
| 81 |
+
|
| 82 |
+
版权所有 (c) 2026 SoMark Team
|
| 83 |
+
|
| 84 |
+
OXR 依据 Apache License 2.0 进行许可,同时受下列附加条款约束。若存在冲突,
|
| 85 |
+
以本附加条款为准;其他事项适用 Apache License 2.0。
|
| 86 |
+
|
| 87 |
+
## 1. 商业许可与门槛
|
| 88 |
+
|
| 89 |
+
仅当您及您的关联方按合并口径计算,均未超过以下任一门槛时,方可无需另行
|
| 90 |
+
取得商业许可而将 OXR 及其衍生作品用于商业用途:
|
| 91 |
+
|
| 92 |
+
(a) 累计融资总额 2,000,000 美元;或
|
| 93 |
+
(b) 年度总收入 2,000,000 美元。
|
| 94 |
+
|
| 95 |
+
一旦超过任一门槛,您必须在开始或继续商业使用之前,从 SoMark Team 取得
|
| 96 |
+
单独的商业许可。未超过上述门槛并不免除本许可中的其他义务,包括第 3 条
|
| 97 |
+
关于竞争性托管 API 服务的限制。
|
| 98 |
+
|
| 99 |
+
## 2. 在线服务标识义务
|
| 100 |
+
|
| 101 |
+
如果您使用 OXR 或其衍生作品向第三方提供在线服务,必须在该服务的用户界面
|
| 102 |
+
或公开可访问的文档中,显著标明使用了 SoMark OXR。
|
| 103 |
+
|
| 104 |
+
## 3. 竞争性托管 API 服务限制
|
| 105 |
+
|
| 106 |
+
除非事先通过与 SoMark Team 签订单独的书面许可取得明确授权,否则您不得
|
| 107 |
+
使用 OXR 或其衍生作品,向第三方提供、运营或开放与 SoMark Team 或其关联方
|
| 108 |
+
所提供的托管 API 服务构成竞争的托管 API 服务。
|
| 109 |
+
|
| 110 |
+
本限制包括通过转售、白标或中间接口提供的服务,无论收费或免费。竞争性
|
| 111 |
+
托管 API 服务,是指以文档解析、光学字符识别(OCR)、文档版面分析或文档
|
| 112 |
+
内容提取为主要功能,并能够替代 SoMark Team 或其关联方提供的托管 API
|
| 113 |
+
服务中实质相似功能的服务。
|
| 114 |
+
|
| 115 |
+
本限制不受累计融资总额或年度总收入门槛影响。仅在您所属组织及其关联方内部
|
| 116 |
+
使用,且不向第三方提供上述服务的,不属于本条禁止的行为;本许可的其他
|
| 117 |
+
条款仍然适用。
|
| 118 |
+
|
| 119 |
+
## 4. 许可终止
|
| 120 |
+
|
| 121 |
+
如果您未按第 1 条要求取得商业许可、未遵守第 2 条标识义务,或违反第 3 条,
|
| 122 |
+
您在本许可项下的权利将自动终止,SoMark Team 无须另行通知。
|
| 123 |
+
|
| 124 |
+
## 5. 定义与适用范围
|
| 125 |
+
|
| 126 |
+
“关联方”是指直接或间接控制您、受您控制,或与您处于共同控制之下的实体。
|
| 127 |
+
“控制”是指通过所有权、表决权、合同权利或其他方式,决定某一实体管理或
|
| 128 |
+
经营事项的能力。
|
| 129 |
+
|
| 130 |
+
“累计融资总额”是指您及您的关联方按合并口径计算,从所有来源累计取得的
|
| 131 |
+
股权融资和债务融资总额,剔除集团内部交易以避免重复计算。
|
| 132 |
+
|
| 133 |
+
“年度总收入”是指您及您的关联方最近一个已结束财年的总收入,按合并口径
|
| 134 |
+
计算,并剔除集团内部交易以避免重复计算。其他币种金额按其等值美元计算。
|
| 135 |
+
|
| 136 |
+
“衍生作品”的含义与 Apache License 2.0 中的定义一致。再分发 OXR 或其
|
| 137 |
+
衍生作品时,必须同时附上本附加条款和 Apache License 2.0。单独标明适用
|
| 138 |
+
其他许可的第三方组件,仍受各自许可约束。
|
| 139 |
+
|
| 140 |
+
Apache License 2.0 全文见:https://www.apache.org/licenses/LICENSE-2.0 。
|
README.md
CHANGED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: apache-2.0
|
| 3 |
-
---
|
|
|
|
|
|
|
|
|
|
|
|
added_tokens.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"</think>": 151668,
|
| 3 |
+
"</tool_call>": 151658,
|
| 4 |
+
"</tool_response>": 151666,
|
| 5 |
+
"<think>": 151667,
|
| 6 |
+
"<tool_call>": 151657,
|
| 7 |
+
"<tool_response>": 151665,
|
| 8 |
+
"<|box_end|>": 151649,
|
| 9 |
+
"<|box_start|>": 151648,
|
| 10 |
+
"<|endoftext|>": 151643,
|
| 11 |
+
"<|file_sep|>": 151664,
|
| 12 |
+
"<|fim_middle|>": 151660,
|
| 13 |
+
"<|fim_pad|>": 151662,
|
| 14 |
+
"<|fim_prefix|>": 151659,
|
| 15 |
+
"<|fim_suffix|>": 151661,
|
| 16 |
+
"<|im_end|>": 151645,
|
| 17 |
+
"<|im_start|>": 151644,
|
| 18 |
+
"<|image_pad|>": 151655,
|
| 19 |
+
"<|object_ref_end|>": 151647,
|
| 20 |
+
"<|object_ref_start|>": 151646,
|
| 21 |
+
"<|quad_end|>": 151651,
|
| 22 |
+
"<|quad_start|>": 151650,
|
| 23 |
+
"<|repo_name|>": 151663,
|
| 24 |
+
"<|video_pad|>": 151656,
|
| 25 |
+
"<|vision_end|>": 151653,
|
| 26 |
+
"<|vision_pad|>": 151654,
|
| 27 |
+
"<|vision_start|>": 151652
|
| 28 |
+
}
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- for message in messages -%}
|
| 2 |
+
{{- '<|im_start|>' + message['role'] + '\n' -}}
|
| 3 |
+
{%- if message['content'] is string -%}
|
| 4 |
+
{{- message['content'] -}}
|
| 5 |
+
{%- else -%}
|
| 6 |
+
{%- for content in message['content'] -%}
|
| 7 |
+
{%- if content['type'] == 'image_url' or content['type'] == 'image' -%}
|
| 8 |
+
{{- '<image>' -}}
|
| 9 |
+
{%- elif content['type'] == 'text' -%}
|
| 10 |
+
{{- content['text'] -}}
|
| 11 |
+
{%- endif -%}
|
| 12 |
+
{%- endfor -%}
|
| 13 |
+
{%- endif -%}
|
| 14 |
+
{{- '<|im_end|>\n' -}}
|
| 15 |
+
{%- endfor -%}
|
| 16 |
+
{%- if add_generation_prompt -%}
|
| 17 |
+
{{- '<|im_start|>assistant\n' -}}
|
| 18 |
+
{%- endif -%}
|
config.json
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"OXR"
|
| 4 |
+
],
|
| 5 |
+
"auto_map": {
|
| 6 |
+
"AutoConfig": "configuration_oxr.OXRConfig",
|
| 7 |
+
"AutoModelForCausalLM": "modeling_oxr.OXR"
|
| 8 |
+
},
|
| 9 |
+
"conversation_formatter_class": "Qwen3ConversationFormatter",
|
| 10 |
+
"dtype": "bfloat16",
|
| 11 |
+
"eos_token_id": 151643,
|
| 12 |
+
"hidden_size": 1024,
|
| 13 |
+
"keys_to_ignore_at_inference": [
|
| 14 |
+
"past_key_values"
|
| 15 |
+
],
|
| 16 |
+
"llm_config": {
|
| 17 |
+
"_name_or_path": "Qwen/Qwen3-0.6B-Base",
|
| 18 |
+
"architectures": [
|
| 19 |
+
"Qwen3ForCausalLM"
|
| 20 |
+
],
|
| 21 |
+
"attention_bias": false,
|
| 22 |
+
"attention_dropout": 0.0,
|
| 23 |
+
"bos_token_id": 151643,
|
| 24 |
+
"dtype": "bfloat16",
|
| 25 |
+
"eos_token_id": 151643,
|
| 26 |
+
"head_dim": 128,
|
| 27 |
+
"hidden_act": "silu",
|
| 28 |
+
"hidden_size": 1024,
|
| 29 |
+
"initializer_range": 0.02,
|
| 30 |
+
"intermediate_size": 3072,
|
| 31 |
+
"layer_types": [
|
| 32 |
+
"full_attention",
|
| 33 |
+
"full_attention",
|
| 34 |
+
"full_attention",
|
| 35 |
+
"full_attention",
|
| 36 |
+
"full_attention",
|
| 37 |
+
"full_attention",
|
| 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 |
+
],
|
| 61 |
+
"max_position_embeddings": 32768,
|
| 62 |
+
"max_window_layers": 28,
|
| 63 |
+
"model_type": "qwen3",
|
| 64 |
+
"num_attention_heads": 16,
|
| 65 |
+
"num_hidden_layers": 28,
|
| 66 |
+
"num_key_value_heads": 8,
|
| 67 |
+
"pad_token_id": 151643,
|
| 68 |
+
"rms_norm_eps": 1e-06,
|
| 69 |
+
"rope_scaling": null,
|
| 70 |
+
"rope_theta": 1000000,
|
| 71 |
+
"sliding_window": null,
|
| 72 |
+
"tie_word_embeddings": true,
|
| 73 |
+
"use_cache": false,
|
| 74 |
+
"use_sliding_window": false,
|
| 75 |
+
"vocab_size": 151936
|
| 76 |
+
},
|
| 77 |
+
"max_position_embeddings": 40960,
|
| 78 |
+
"model_type": "oxr",
|
| 79 |
+
"num_attention_heads": 32,
|
| 80 |
+
"pad_token_id": 151643,
|
| 81 |
+
"transformers_version": "5.6.2",
|
| 82 |
+
"use_cache": false,
|
| 83 |
+
"visual_vocab_size": 65536,
|
| 84 |
+
"vit_config": {
|
| 85 |
+
"_attn_implementation_autoset": true,
|
| 86 |
+
"_name_or_path": "google/siglip2-so400m-patch16-512",
|
| 87 |
+
"attention_dropout": 0.0,
|
| 88 |
+
"dtype": "bfloat16",
|
| 89 |
+
"fullatt_block_indexes": null,
|
| 90 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 91 |
+
"hidden_size": 1152,
|
| 92 |
+
"hidden_stride": 2,
|
| 93 |
+
"image_size": 512,
|
| 94 |
+
"intermediate_size": 4304,
|
| 95 |
+
"layer_norm_eps": 1e-06,
|
| 96 |
+
"model_type": "siglip2_navit",
|
| 97 |
+
"num_attention_heads": 16,
|
| 98 |
+
"num_channels": 3,
|
| 99 |
+
"num_hidden_layers": 27,
|
| 100 |
+
"num_patches": -1,
|
| 101 |
+
"pad_token_id": 151643,
|
| 102 |
+
"patch_size": 16,
|
| 103 |
+
"preserve_original_pe": true,
|
| 104 |
+
"temporal_patch_size": 1,
|
| 105 |
+
"use_rope": true,
|
| 106 |
+
"window_size": 112
|
| 107 |
+
},
|
| 108 |
+
"vocab_size": 151936
|
| 109 |
+
}
|
configuration_oxr.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Optional, List, Union
|
| 2 |
+
|
| 3 |
+
from transformers import AutoConfig, Qwen3Config
|
| 4 |
+
from transformers.configuration_utils import PretrainedConfig
|
| 5 |
+
|
| 6 |
+
__all__ = ["Siglip2NavitConfig", "OXRConfig"]
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Siglip2NavitConfig(PretrainedConfig):
|
| 10 |
+
"""This is the configuration class to store the configuration of an [`AIMv2Model`].
|
| 11 |
+
|
| 12 |
+
Instantiating a configuration with the defaults will yield a similar configuration
|
| 13 |
+
to that of the [apple/aimv2-large-patch14-224](https://huggingface.co/apple/aimv2-large-patch14-224).
|
| 14 |
+
|
| 15 |
+
Args:
|
| 16 |
+
hidden_size: Dimension of the hidden representations.
|
| 17 |
+
intermediate_size: Dimension of the SwiGLU representations.
|
| 18 |
+
num_hidden_layers: Number of hidden layers in the Transformer.
|
| 19 |
+
num_attention_heads: Number of attention heads for each attention layer
|
| 20 |
+
in the Transformer.
|
| 21 |
+
num_channels: Number of input channels.
|
| 22 |
+
image_size: Image size.
|
| 23 |
+
patch_size: Patch size.
|
| 24 |
+
rms_norm_eps: Epsilon value used for the RMS normalization layer.
|
| 25 |
+
attention_dropout: Dropout ratio for attention probabilities.
|
| 26 |
+
projection_dropout: Dropout ratio for the projection layer after the attention.
|
| 27 |
+
qkv_bias: Whether to add a bias to the queries, keys and values.
|
| 28 |
+
use_bias: Whether to add a bias in the feed-forward and projection layers.
|
| 29 |
+
kwargs: Keyword arguments for the [`PretrainedConfig`].
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
model_type: str = "siglip2_navit"
|
| 33 |
+
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
hidden_size: int = 1024,
|
| 37 |
+
intermediate_size: int = 4096,
|
| 38 |
+
num_hidden_layers: int = 24,
|
| 39 |
+
num_attention_heads: int = 16,
|
| 40 |
+
num_channels: int = 3,
|
| 41 |
+
num_patches: int = -1,
|
| 42 |
+
image_size: int = 512,
|
| 43 |
+
patch_size: int = 16,
|
| 44 |
+
hidden_act: str="gelu_pytorch_tanh",
|
| 45 |
+
layer_norm_eps: float = 1e-6,
|
| 46 |
+
attention_dropout: float = 0.0,
|
| 47 |
+
hidden_stride: int = 2,
|
| 48 |
+
window_size: int = 112,
|
| 49 |
+
fullatt_block_indexes: Optional[list] = None,
|
| 50 |
+
temporal_patch_size: int = 1,
|
| 51 |
+
preserve_original_pe: bool = True,
|
| 52 |
+
use_rope: bool = True,
|
| 53 |
+
**kwargs: Any,
|
| 54 |
+
):
|
| 55 |
+
super().__init__(**kwargs)
|
| 56 |
+
self.hidden_size = hidden_size
|
| 57 |
+
self.intermediate_size = intermediate_size
|
| 58 |
+
self.num_hidden_layers = num_hidden_layers
|
| 59 |
+
self.num_attention_heads = num_attention_heads
|
| 60 |
+
self.num_channels = num_channels
|
| 61 |
+
self.num_patches = num_patches
|
| 62 |
+
self.patch_size = patch_size
|
| 63 |
+
self.image_size = image_size
|
| 64 |
+
self.hidden_act = hidden_act
|
| 65 |
+
self.attention_dropout = attention_dropout
|
| 66 |
+
self.layer_norm_eps = layer_norm_eps
|
| 67 |
+
self.hidden_stride = hidden_stride
|
| 68 |
+
self.window_size = window_size
|
| 69 |
+
self.fullatt_block_indexes = fullatt_block_indexes
|
| 70 |
+
self.temporal_patch_size = temporal_patch_size
|
| 71 |
+
self.preserve_original_pe = preserve_original_pe
|
| 72 |
+
self.use_rope = use_rope
|
| 73 |
+
|
| 74 |
+
class OXRConfig(PretrainedConfig):
|
| 75 |
+
model_type = "oxr"
|
| 76 |
+
sub_configs = dict(llm_config=Qwen3Config, vit_config=Siglip2NavitConfig)
|
| 77 |
+
|
| 78 |
+
def __init__(self,
|
| 79 |
+
llm_config: Optional[Union[Qwen3Config, dict]] = None,
|
| 80 |
+
vit_config: Optional[Union[Siglip2NavitConfig, dict]] = None,
|
| 81 |
+
visual_vocab_size=65536,
|
| 82 |
+
hidden_size=None,
|
| 83 |
+
**kwargs
|
| 84 |
+
):
|
| 85 |
+
super().__init__(**kwargs)
|
| 86 |
+
if isinstance(llm_config, dict):
|
| 87 |
+
if "model_type" in llm_config:
|
| 88 |
+
config_kwargs = {k: v for k, v in llm_config.items() if k != "model_type"}
|
| 89 |
+
llm_config = AutoConfig.for_model(llm_config["model_type"], **config_kwargs)
|
| 90 |
+
else:
|
| 91 |
+
llm_config = Qwen3Config(**llm_config)
|
| 92 |
+
self.llm_config = llm_config
|
| 93 |
+
if isinstance(vit_config, dict):
|
| 94 |
+
vit_config = Siglip2NavitConfig(**vit_config)
|
| 95 |
+
self.vit_config = vit_config
|
| 96 |
+
self.visual_vocab_size = visual_vocab_size
|
| 97 |
+
self.hidden_size = hidden_size
|
| 98 |
+
if kwargs.get('attn_implementation'):
|
| 99 |
+
self.llm_config._attn_implementation = kwargs['attn_implementation']
|
| 100 |
+
self.vit_config._attn_implementation = kwargs['attn_implementation']
|
generation_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"eos_token_id": [
|
| 4 |
+
151643,
|
| 5 |
+
151645
|
| 6 |
+
],
|
| 7 |
+
"transformers_version": "4.57.1"
|
| 8 |
+
}
|
layout_analysis/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Layout Analysis
|
| 2 |
+
|
| 3 |
+
D-FINE Stage-2 B2 layout model with 12 classes. Detects page regions;
|
| 4 |
+
text recognition and reading order are handled by OXR.
|
| 5 |
+
|
| 6 |
+
## Files
|
| 7 |
+
|
| 8 |
+
- `last.pth`: inference-only model weights (no optimizer or training state).
|
| 9 |
+
- `metadata.json`: model identity, architecture, preprocessing, class IDs,
|
| 10 |
+
checkpoint size and SHA-256 (schema version 1).
|
| 11 |
+
|
| 12 |
+
`model_version` is a public release version (`major.minor.patch`), not a
|
| 13 |
+
training-run identifier.
|
| 14 |
+
|
| 15 |
+
## Usage
|
| 16 |
+
|
| 17 |
+
Use the LA inference service in the OXR code repository at
|
| 18 |
+
`services/layout_analyze/`. Mount this directory into the service and set
|
| 19 |
+
`MODEL_PATH` to the mounted `last.pth` path.
|
| 20 |
+
|
| 21 |
+
The matching LA service reads `metadata.json` next to the checkpoint at startup
|
| 22 |
+
and validates its supported architecture, preprocessing, class IDs and weight
|
| 23 |
+
integrity. Both files must be mounted. `MODEL_METADATA_PATH` can override the
|
| 24 |
+
metadata path. Unsupported metadata or a mismatched checkpoint fails startup.
|
| 25 |
+
This is not a Transformers `AutoModel` configuration. Device, precision and
|
| 26 |
+
CUDA Graph settings are selected by the service, not by the model metadata.
|
| 27 |
+
|
| 28 |
+
Checkpoint verification:
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
shasum -a 256 layout_analysis/last.pth
|
| 32 |
+
# dff78ccf18439955362ca44a69eb28fb0b40222a7d278860589682de0176dc17
|
| 33 |
+
```
|
layout_analysis/last.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dff78ccf18439955362ca44a69eb28fb0b40222a7d278860589682de0176dc17
|
| 3 |
+
size 78791307
|
layout_analysis/metadata.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"model_type": "dfine",
|
| 4 |
+
"model_version": "1.0.0",
|
| 5 |
+
"architecture": "stage2_b2",
|
| 6 |
+
"checkpoint": "last.pth",
|
| 7 |
+
"checkpoint_size_bytes": 78791307,
|
| 8 |
+
"checkpoint_sha256": "dff78ccf18439955362ca44a69eb28fb0b40222a7d278860589682de0176dc17",
|
| 9 |
+
"num_classes": 12,
|
| 10 |
+
"image_size": 960,
|
| 11 |
+
"color_order": "rgb",
|
| 12 |
+
"resize_backend": "opencv_lanczos4",
|
| 13 |
+
"postprocess": "model_letterbox",
|
| 14 |
+
"provides_reading_order": false,
|
| 15 |
+
"id2label": {
|
| 16 |
+
"0": "Caption",
|
| 17 |
+
"1": "Footnote",
|
| 18 |
+
"2": "Formula",
|
| 19 |
+
"3": "Text",
|
| 20 |
+
"4": "Page-footer",
|
| 21 |
+
"5": "Page-header",
|
| 22 |
+
"6": "Picture",
|
| 23 |
+
"7": "Title",
|
| 24 |
+
"8": "Table",
|
| 25 |
+
"9": "Code Block",
|
| 26 |
+
"10": "Stamp",
|
| 27 |
+
"11": "Chemical Structure"
|
| 28 |
+
}
|
| 29 |
+
}
|
merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:795bb2f1faac579f58829913f2c606202531a96c1203e19ef7a70b103fb7ea51
|
| 3 |
+
size 3068857520
|
modeling_oxr.py
ADDED
|
@@ -0,0 +1,1037 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import Dict, List, Optional, Tuple, Union
|
| 3 |
+
|
| 4 |
+
import PIL.Image
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
from torch import Tensor, nn
|
| 8 |
+
from torch.nn import functional as F
|
| 9 |
+
from transformers import (
|
| 10 |
+
AutoConfig,
|
| 11 |
+
AutoImageProcessor,
|
| 12 |
+
AutoModel,
|
| 13 |
+
AutoModelForCausalLM,
|
| 14 |
+
AutoTokenizer,
|
| 15 |
+
__version__ as TRANSFORMERS_VERSION,
|
| 16 |
+
)
|
| 17 |
+
from transformers.activations import ACT2FN
|
| 18 |
+
from transformers.generation.utils import GenerateOutput
|
| 19 |
+
from transformers.modeling_outputs import BaseModelOutputWithNoAttention
|
| 20 |
+
from transformers.modeling_utils import PreTrainedModel
|
| 21 |
+
from transformers.utils import is_flash_attn_2_available
|
| 22 |
+
|
| 23 |
+
from .configuration_oxr import Siglip2NavitConfig, OXRConfig
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
if is_flash_attn_2_available() :
|
| 27 |
+
from flash_attn import flash_attn_varlen_func
|
| 28 |
+
from flash_attn.layers.rotary import apply_rotary_emb
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
IMAGE_PLACEHOLDER = "<image>"
|
| 32 |
+
IMAGE_PLACEHOLDER_ID = -200
|
| 33 |
+
VIDEO_PLACEHOLDER = "<video>"
|
| 34 |
+
VIDEO_PLACEHOLDER_ID = -201
|
| 35 |
+
|
| 36 |
+
VISUAL_ATOM_ID = -300
|
| 37 |
+
INDICATOR_IDS = [-301, -302, -303, -304]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# copied from qwen2.5-vl
|
| 41 |
+
class VisionRotaryEmbedding(nn.Module):
|
| 42 |
+
def __init__(self, dim: int, theta: float = 10000.0) -> None:
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.dim = dim
|
| 45 |
+
self.theta = theta
|
| 46 |
+
inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2, dtype=torch.float) / dim))
|
| 47 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 48 |
+
|
| 49 |
+
def forward(self, seqlen: int) -> torch.Tensor:
|
| 50 |
+
inv_freq = 1.0 / (
|
| 51 |
+
self.theta ** (torch.arange(0, self.dim, 2, device=self.inv_freq.device, dtype=torch.float) / self.dim)
|
| 52 |
+
)
|
| 53 |
+
seq = torch.arange(seqlen, device=inv_freq.device, dtype=inv_freq.dtype)
|
| 54 |
+
freqs = torch.outer(seq, inv_freq)
|
| 55 |
+
return freqs
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class Siglip2VisionEmbeddings(nn.Module):
|
| 59 |
+
def __init__(self, config: Siglip2NavitConfig):
|
| 60 |
+
super().__init__()
|
| 61 |
+
self.config = config
|
| 62 |
+
self.embed_dim = config.hidden_size
|
| 63 |
+
self.patch_size = config.patch_size
|
| 64 |
+
self.image_size = config.image_size
|
| 65 |
+
self.num_patches = config.num_patches
|
| 66 |
+
self.preserve_original_pe = config.preserve_original_pe
|
| 67 |
+
self.hidden_stride = config.hidden_stride
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# siglip2 naflex
|
| 71 |
+
if self.num_patches > 0:
|
| 72 |
+
self.patch_embedding = nn.Linear(
|
| 73 |
+
in_features=config.num_channels * self.patch_size * self.patch_size,
|
| 74 |
+
out_features=self.embed_dim,
|
| 75 |
+
)
|
| 76 |
+
if self.preserve_original_pe:
|
| 77 |
+
self.position_embedding_size = int(self.num_patches**0.5)
|
| 78 |
+
self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim)
|
| 79 |
+
|
| 80 |
+
else:
|
| 81 |
+
self.patch_embedding = nn.Conv2d(
|
| 82 |
+
in_channels=config.num_channels,
|
| 83 |
+
out_channels=self.embed_dim,
|
| 84 |
+
kernel_size=self.patch_size,
|
| 85 |
+
stride=self.patch_size,
|
| 86 |
+
padding="valid",
|
| 87 |
+
)
|
| 88 |
+
if self.preserve_original_pe:
|
| 89 |
+
self.num_patches = (self.image_size // self.patch_size) ** 2
|
| 90 |
+
self.position_embedding_size = self.image_size // self.patch_size
|
| 91 |
+
self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim)
|
| 92 |
+
|
| 93 |
+
@staticmethod
|
| 94 |
+
def resize_positional_embeddings(
|
| 95 |
+
positional_embeddings: torch.Tensor,
|
| 96 |
+
spatial_shapes: torch.LongTensor,
|
| 97 |
+
max_length: int,
|
| 98 |
+
) -> torch.Tensor:
|
| 99 |
+
"""
|
| 100 |
+
Resize positional embeddings to image-specific size and pad to a fixed size.
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
positional_embeddings (`torch.Tensor`):
|
| 104 |
+
Position embeddings of shape (height, width, embed_dim)
|
| 105 |
+
spatial_shapes (`torch.LongTensor`):
|
| 106 |
+
Spatial shapes of shape (batch_size, 2) to resize the positional embeddings to
|
| 107 |
+
max_length (`int`):
|
| 108 |
+
Maximum length of the positional embeddings to pad resized positional embeddings to
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
`torch.Tensor`: Embeddings of shape (batch_size, max_length, embed_dim)
|
| 112 |
+
"""
|
| 113 |
+
batch_size = spatial_shapes.shape[0]
|
| 114 |
+
embed_dim = positional_embeddings.shape[-1]
|
| 115 |
+
source_dtype = positional_embeddings.dtype
|
| 116 |
+
|
| 117 |
+
resulted_positional_embeddings = torch.empty(
|
| 118 |
+
(batch_size, max_length, embed_dim),
|
| 119 |
+
device=positional_embeddings.device,
|
| 120 |
+
dtype=source_dtype,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# (height, width, embed_dim) -> (1, embed_dim, height, width) for interpolation
|
| 124 |
+
positional_embeddings = positional_embeddings.permute(2, 0, 1).unsqueeze(0)
|
| 125 |
+
|
| 126 |
+
# Upcast to float32 on CPU because antialias is not supported for bfloat16/float16 on CPU
|
| 127 |
+
if positional_embeddings.device.type == "cpu":
|
| 128 |
+
positional_embeddings = positional_embeddings.to(torch.float32)
|
| 129 |
+
|
| 130 |
+
for i in range(batch_size):
|
| 131 |
+
# (1, dim, height, width) -> (1, dim, target_height, target_width)
|
| 132 |
+
height, width = spatial_shapes[i]
|
| 133 |
+
resized_embeddings = F.interpolate(
|
| 134 |
+
positional_embeddings,
|
| 135 |
+
size=(height, width),
|
| 136 |
+
mode="bilinear",
|
| 137 |
+
align_corners=False,
|
| 138 |
+
antialias=True,
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# (1, dim, target_height, target_width) -> (target_height * target_width, dim)
|
| 142 |
+
resized_embeddings = resized_embeddings.reshape(embed_dim, height * width).transpose(0, 1)
|
| 143 |
+
|
| 144 |
+
# Cast to original dtype
|
| 145 |
+
resized_embeddings = resized_embeddings.to(source_dtype)
|
| 146 |
+
|
| 147 |
+
resulted_positional_embeddings[i, : height * width] = resized_embeddings
|
| 148 |
+
resulted_positional_embeddings[i, height * width :] = resized_embeddings[0]
|
| 149 |
+
|
| 150 |
+
return resulted_positional_embeddings
|
| 151 |
+
|
| 152 |
+
def forward(self, pixel_values: torch.FloatTensor,
|
| 153 |
+
grid_thws: Optional[torch.LongTensor] = None) -> torch.Tensor:
|
| 154 |
+
"""
|
| 155 |
+
Args:
|
| 156 |
+
pixel_values (`torch.FloatTensor`):
|
| 157 |
+
Pixel values of shape (num_patches, num_channels * temporal_patch_size * patch_size * patch_size)
|
| 158 |
+
grid_thws: (`torch.LongTensor`):
|
| 159 |
+
grid shape (num_patches, 3)
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
# Apply patch embeddings to already patchified pixel values
|
| 163 |
+
target_dtype = self.patch_embedding.weight.dtype
|
| 164 |
+
if isinstance(self.patch_embedding, nn.Linear):
|
| 165 |
+
patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype))
|
| 166 |
+
elif isinstance(self.patch_embedding, nn.Conv2d):
|
| 167 |
+
pixel_values = pixel_values.view(-1, self.config.num_channels * self.config.temporal_patch_size, self.patch_size,
|
| 168 |
+
self.patch_size)
|
| 169 |
+
patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype))
|
| 170 |
+
patch_embeds = patch_embeds.reshape(-1, self.embed_dim)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
if self.preserve_original_pe:
|
| 174 |
+
assert grid_thws is not None
|
| 175 |
+
pos_embed_new = torch.zeros_like(patch_embeds)
|
| 176 |
+
ori_h = ori_w = self.position_embedding_size
|
| 177 |
+
positional_embeddings = self.position_embedding.weight.reshape(
|
| 178 |
+
self.position_embedding_size, self.position_embedding_size, -1
|
| 179 |
+
).unsqueeze(0).permute(0,3,1,2)
|
| 180 |
+
# pos_embed = self.pos_embed.reshape(1, ori_h, ori_w, -1).permute(0, 3, 1, 2)
|
| 181 |
+
cnt = 0
|
| 182 |
+
for t, h, w in grid_thws:
|
| 183 |
+
thw = t * h * w
|
| 184 |
+
pe = F.interpolate(positional_embeddings, size=(h, w), mode='bicubic', align_corners=False)
|
| 185 |
+
pe = pe.permute(0, 2, 3, 1).reshape(1, h * w, -1)
|
| 186 |
+
pe = pe[0].repeat(t, 1)
|
| 187 |
+
pe = pe.reshape(t, h // self.hidden_stride, self.hidden_stride, w // self.hidden_stride,
|
| 188 |
+
self.hidden_stride, -1)
|
| 189 |
+
pe = pe.permute(0, 1, 3, 2, 4, 5).reshape(thw, -1)
|
| 190 |
+
pos_embed_new[cnt:cnt + thw] = pe
|
| 191 |
+
cnt += thw
|
| 192 |
+
patch_embeds = patch_embeds + pos_embed_new
|
| 193 |
+
|
| 194 |
+
return patch_embeds
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# copied from qwen2.5-vl
|
| 198 |
+
def apply_rotary_pos_emb_flashatt(
|
| 199 |
+
q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 200 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 201 |
+
cos = cos.chunk(2, dim=-1)[0].contiguous()
|
| 202 |
+
sin = sin.chunk(2, dim=-1)[0].contiguous()
|
| 203 |
+
q_embed = apply_rotary_emb(q.float(), cos.float(), sin.float()).type_as(q)
|
| 204 |
+
k_embed = apply_rotary_emb(k.float(), cos.float(), sin.float()).type_as(k)
|
| 205 |
+
return q_embed, k_embed
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
# Copied from transformers.models.llama.modeling_llama.rotate_half
|
| 209 |
+
def rotate_half(x):
|
| 210 |
+
"""Rotates half the hidden dims of the input."""
|
| 211 |
+
x1 = x[..., : x.shape[-1] // 2]
|
| 212 |
+
x2 = x[..., x.shape[-1] // 2 :]
|
| 213 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def apply_rotary_pos_emb_vision(
|
| 217 |
+
q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 218 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 219 |
+
orig_q_dtype = q.dtype
|
| 220 |
+
orig_k_dtype = k.dtype
|
| 221 |
+
q, k = q.float(), k.float()
|
| 222 |
+
cos, sin = cos.unsqueeze(-2).float(), sin.unsqueeze(-2).float()
|
| 223 |
+
q_embed = (q * cos) + (rotate_half(q) * sin)
|
| 224 |
+
k_embed = (k * cos) + (rotate_half(k) * sin)
|
| 225 |
+
q_embed = q_embed.to(orig_q_dtype)
|
| 226 |
+
k_embed = k_embed.to(orig_k_dtype)
|
| 227 |
+
return q_embed, k_embed
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
class Siglip2Attention(nn.Module):
|
| 231 |
+
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
| 232 |
+
|
| 233 |
+
def __init__(self, config):
|
| 234 |
+
super().__init__()
|
| 235 |
+
self.config = config
|
| 236 |
+
self.embed_dim = config.hidden_size
|
| 237 |
+
self.num_heads = config.num_attention_heads
|
| 238 |
+
self.head_dim = self.embed_dim // self.num_heads
|
| 239 |
+
if self.head_dim * self.num_heads != self.embed_dim:
|
| 240 |
+
raise ValueError(
|
| 241 |
+
f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:"
|
| 242 |
+
f" {self.num_heads})."
|
| 243 |
+
)
|
| 244 |
+
self.scale = self.head_dim**-0.5
|
| 245 |
+
self.dropout = config.attention_dropout
|
| 246 |
+
self.is_causal = False
|
| 247 |
+
|
| 248 |
+
self.k_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 249 |
+
self.v_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 250 |
+
self.q_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 251 |
+
self.out_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 252 |
+
|
| 253 |
+
self.use_rope = config.use_rope
|
| 254 |
+
|
| 255 |
+
def forward(
|
| 256 |
+
self,
|
| 257 |
+
hidden_states: torch.Tensor,
|
| 258 |
+
cu_seqlens: torch.Tensor,
|
| 259 |
+
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 260 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 261 |
+
"""Input shape: Batch x Time x Channel"""
|
| 262 |
+
|
| 263 |
+
seq_length, embed_dim = hidden_states.shape
|
| 264 |
+
|
| 265 |
+
queries = self.q_proj(hidden_states)
|
| 266 |
+
keys = self.k_proj(hidden_states)
|
| 267 |
+
values = self.v_proj(hidden_states)
|
| 268 |
+
|
| 269 |
+
queries = queries.view(seq_length, self.num_heads, self.head_dim)
|
| 270 |
+
keys = keys.view(seq_length, self.num_heads, self.head_dim)
|
| 271 |
+
values = values.view(seq_length, self.num_heads, self.head_dim)
|
| 272 |
+
|
| 273 |
+
if self.use_rope:
|
| 274 |
+
cos, sin = position_embeddings
|
| 275 |
+
if is_flash_attn_2_available() :
|
| 276 |
+
queries, keys = apply_rotary_pos_emb_flashatt(queries.unsqueeze(0), keys.unsqueeze(0), cos, sin)
|
| 277 |
+
else:
|
| 278 |
+
queries, keys = apply_rotary_pos_emb_vision(queries.unsqueeze(0), keys.unsqueeze(0), cos, sin)
|
| 279 |
+
queries = queries.squeeze(0)
|
| 280 |
+
keys = keys.squeeze(0)
|
| 281 |
+
|
| 282 |
+
max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item()
|
| 283 |
+
if is_flash_attn_2_available() :
|
| 284 |
+
attn_output = flash_attn_varlen_func(queries, keys, values, cu_seqlens, cu_seqlens, max_seqlen, max_seqlen).reshape(
|
| 285 |
+
seq_length, -1
|
| 286 |
+
)
|
| 287 |
+
else:
|
| 288 |
+
batch_size = cu_seqlens.shape[0] - 1
|
| 289 |
+
outputs = []
|
| 290 |
+
cu = cu_seqlens.tolist()
|
| 291 |
+
for i in range(batch_size):
|
| 292 |
+
start_idx = cu[i]
|
| 293 |
+
end_idx = cu[i + 1]
|
| 294 |
+
# Each sequence is processed independently.
|
| 295 |
+
q_i = queries[start_idx:end_idx].unsqueeze(0)
|
| 296 |
+
k_i = keys[start_idx:end_idx].unsqueeze(0)
|
| 297 |
+
v_i = values[start_idx:end_idx].unsqueeze(0)
|
| 298 |
+
# (1, seq_len, num_heads, head_dim) ->
|
| 299 |
+
# (1, num_heads, seq_len, head_dim)
|
| 300 |
+
q_i, k_i, v_i = [x.transpose(1, 2) for x in (q_i, k_i, v_i)]
|
| 301 |
+
output_i = F.scaled_dot_product_attention(q_i,
|
| 302 |
+
k_i,
|
| 303 |
+
v_i,
|
| 304 |
+
dropout_p=0.0)
|
| 305 |
+
# (1, num_heads, seq_len, head_dim) -> (seq_len, embed_dim)
|
| 306 |
+
output_i = output_i.transpose(1, 2).reshape(-1, self.embed_dim)
|
| 307 |
+
outputs.append(output_i)
|
| 308 |
+
attn_output = torch.cat(outputs, dim=0)
|
| 309 |
+
|
| 310 |
+
attn_output = self.out_proj(attn_output)
|
| 311 |
+
return attn_output
|
| 312 |
+
|
| 313 |
+
class Siglip2MLP(nn.Module):
|
| 314 |
+
def __init__(self, config):
|
| 315 |
+
super().__init__()
|
| 316 |
+
self.config = config
|
| 317 |
+
self.activation_fn = ACT2FN[config.hidden_act]
|
| 318 |
+
self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
|
| 319 |
+
self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
|
| 320 |
+
|
| 321 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 322 |
+
hidden_states = self.fc1(hidden_states)
|
| 323 |
+
hidden_states = self.activation_fn(hidden_states)
|
| 324 |
+
hidden_states = self.fc2(hidden_states)
|
| 325 |
+
return hidden_states
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
class Siglip2EncoderLayer(nn.Module):
|
| 329 |
+
def __init__(self, config: Siglip2NavitConfig):
|
| 330 |
+
super().__init__()
|
| 331 |
+
self.embed_dim = config.hidden_size
|
| 332 |
+
self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
|
| 333 |
+
self.self_attn = Siglip2Attention(config)
|
| 334 |
+
self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
|
| 335 |
+
self.mlp = Siglip2MLP(config)
|
| 336 |
+
|
| 337 |
+
def forward(
|
| 338 |
+
self,
|
| 339 |
+
hidden_states: torch.Tensor,
|
| 340 |
+
cu_seqlens: torch.Tensor,
|
| 341 |
+
position_embeddings: torch.Tensor
|
| 342 |
+
) -> tuple[torch.FloatTensor]:
|
| 343 |
+
"""
|
| 344 |
+
Args:
|
| 345 |
+
hidden_states (`torch.FloatTensor`):
|
| 346 |
+
Input to the layer of shape `(batch, seq_len, embed_dim)`.
|
| 347 |
+
attention_mask (`torch.FloatTensor`):
|
| 348 |
+
Attention mask of shape `(batch, 1, q_len, k_v_seq_len)` where padding elements are indicated by very large negative values.
|
| 349 |
+
output_attentions (`bool`, *optional*, defaults to `False`):
|
| 350 |
+
Whether or not to return the attentions tensors of all attention layers. See `attentions` under
|
| 351 |
+
returned tensors for more detail.
|
| 352 |
+
"""
|
| 353 |
+
residual = hidden_states
|
| 354 |
+
|
| 355 |
+
hidden_states = self.layer_norm1(hidden_states)
|
| 356 |
+
hidden_states = self.self_attn(
|
| 357 |
+
hidden_states=hidden_states,
|
| 358 |
+
cu_seqlens=cu_seqlens,
|
| 359 |
+
position_embeddings=position_embeddings
|
| 360 |
+
)
|
| 361 |
+
hidden_states = residual + hidden_states
|
| 362 |
+
|
| 363 |
+
residual = hidden_states
|
| 364 |
+
hidden_states = self.layer_norm2(hidden_states)
|
| 365 |
+
hidden_states = self.mlp(hidden_states)
|
| 366 |
+
hidden_states = residual + hidden_states
|
| 367 |
+
|
| 368 |
+
return hidden_states
|
| 369 |
+
|
| 370 |
+
class Siglip2Encoder(nn.Module):
|
| 371 |
+
"""
|
| 372 |
+
Transformer encoder consisting of `config.num_hidden_layers` self attention layers. Each layer is a
|
| 373 |
+
[`Siglip2EncoderLayer`].
|
| 374 |
+
|
| 375 |
+
Args:
|
| 376 |
+
config: Siglip2NavitConfig
|
| 377 |
+
"""
|
| 378 |
+
|
| 379 |
+
def __init__(self, config: Siglip2NavitConfig):
|
| 380 |
+
super().__init__()
|
| 381 |
+
self.config = config
|
| 382 |
+
self.layers = nn.ModuleList([Siglip2EncoderLayer(config) for _ in range(config.num_hidden_layers)])
|
| 383 |
+
self.gradient_checkpointing = False
|
| 384 |
+
|
| 385 |
+
self.rotary_pos_emb = VisionRotaryEmbedding(config.hidden_size // config.num_attention_heads // 2)
|
| 386 |
+
self.patch_size = config.patch_size
|
| 387 |
+
self.hidden_stride = config.hidden_stride
|
| 388 |
+
self.window_size = config.window_size
|
| 389 |
+
self.spatial_merge_unit = config.hidden_stride * config.hidden_stride
|
| 390 |
+
self.fullatt_block_indexes = None if config.fullatt_block_indexes is None else [int(i) for i in config.fullatt_block_indexes.split('|')]
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
# copied from qwen2.5_vl
|
| 394 |
+
def rot_pos_emb(self, grid_thw):
|
| 395 |
+
pos_ids = []
|
| 396 |
+
for t, h, w in grid_thw:
|
| 397 |
+
hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
|
| 398 |
+
hpos_ids = hpos_ids.reshape(
|
| 399 |
+
h // self.hidden_stride,
|
| 400 |
+
self.hidden_stride,
|
| 401 |
+
w // self.hidden_stride,
|
| 402 |
+
self.hidden_stride,
|
| 403 |
+
)
|
| 404 |
+
hpos_ids = hpos_ids.permute(0, 2, 1, 3)
|
| 405 |
+
hpos_ids = hpos_ids.flatten()
|
| 406 |
+
|
| 407 |
+
wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
|
| 408 |
+
wpos_ids = wpos_ids.reshape(
|
| 409 |
+
h // self.hidden_stride,
|
| 410 |
+
self.hidden_stride,
|
| 411 |
+
w // self.hidden_stride,
|
| 412 |
+
self.hidden_stride,
|
| 413 |
+
)
|
| 414 |
+
wpos_ids = wpos_ids.permute(0, 2, 1, 3)
|
| 415 |
+
wpos_ids = wpos_ids.flatten()
|
| 416 |
+
pos_ids.append(torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
|
| 417 |
+
pos_ids = torch.cat(pos_ids, dim=0)
|
| 418 |
+
max_grid_size = grid_thw[:, 1:].max()
|
| 419 |
+
rotary_pos_emb_full = self.rotary_pos_emb(max_grid_size)
|
| 420 |
+
rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
|
| 421 |
+
return rotary_pos_emb
|
| 422 |
+
|
| 423 |
+
def get_window_index(self, grid_thw):
|
| 424 |
+
window_index: list = []
|
| 425 |
+
cu_window_seqlens: list = [0]
|
| 426 |
+
window_index_id = 0
|
| 427 |
+
vit_merger_window_size = self.window_size // self.hidden_stride // self.patch_size # patch (after merge) number in each window
|
| 428 |
+
|
| 429 |
+
for grid_t, grid_h, grid_w in grid_thw:
|
| 430 |
+
llm_grid_h, llm_grid_w = (
|
| 431 |
+
grid_h // self.hidden_stride, # number of patch after merge
|
| 432 |
+
grid_w // self.hidden_stride,
|
| 433 |
+
)
|
| 434 |
+
index = torch.arange(grid_t * llm_grid_h * llm_grid_w).reshape(grid_t, llm_grid_h, llm_grid_w)
|
| 435 |
+
pad_h = vit_merger_window_size - llm_grid_h % vit_merger_window_size
|
| 436 |
+
pad_w = vit_merger_window_size - llm_grid_w % vit_merger_window_size
|
| 437 |
+
num_windows_h = (llm_grid_h + pad_h) // vit_merger_window_size
|
| 438 |
+
num_windows_w = (llm_grid_w + pad_w) // vit_merger_window_size
|
| 439 |
+
index_padded = F.pad(index, (0, pad_w, 0, pad_h), "constant", -100)
|
| 440 |
+
index_padded = index_padded.reshape(
|
| 441 |
+
grid_t,
|
| 442 |
+
num_windows_h,
|
| 443 |
+
vit_merger_window_size,
|
| 444 |
+
num_windows_w,
|
| 445 |
+
vit_merger_window_size,
|
| 446 |
+
)
|
| 447 |
+
index_padded = index_padded.permute(0, 1, 3, 2, 4).reshape(
|
| 448 |
+
grid_t,
|
| 449 |
+
num_windows_h * num_windows_w,
|
| 450 |
+
vit_merger_window_size,
|
| 451 |
+
vit_merger_window_size,
|
| 452 |
+
)
|
| 453 |
+
seqlens = (index_padded != -100).sum([2, 3]).reshape(-1)
|
| 454 |
+
index_padded = index_padded.reshape(-1)
|
| 455 |
+
index_new = index_padded[index_padded != -100]
|
| 456 |
+
window_index.append(index_new + window_index_id)
|
| 457 |
+
cu_seqlens_tmp = seqlens.cumsum(0) * self.spatial_merge_unit + cu_window_seqlens[-1]
|
| 458 |
+
cu_window_seqlens.extend(cu_seqlens_tmp.tolist())
|
| 459 |
+
window_index_id += (grid_t * llm_grid_h * llm_grid_w).item()
|
| 460 |
+
window_index = torch.cat(window_index, dim=0)
|
| 461 |
+
|
| 462 |
+
return window_index, cu_window_seqlens
|
| 463 |
+
|
| 464 |
+
# Ignore copy
|
| 465 |
+
def forward(
|
| 466 |
+
self,
|
| 467 |
+
inputs_embeds,
|
| 468 |
+
grid_thws: torch.Tensor,
|
| 469 |
+
output_hidden_states: bool = False,
|
| 470 |
+
) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, ...]]]:
|
| 471 |
+
r"""
|
| 472 |
+
Args:
|
| 473 |
+
inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`):
|
| 474 |
+
Optionally, instead of passing `input_ids` you can choose to directly pass an embedded representation.
|
| 475 |
+
This is useful if you want more control over how to convert `input_ids` indices into associated vectors
|
| 476 |
+
than the model's internal embedding lookup matrix.
|
| 477 |
+
attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*):
|
| 478 |
+
Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`:
|
| 479 |
+
|
| 480 |
+
- 1 for tokens that are **not masked**,
|
| 481 |
+
- 0 for tokens that are **masked**.
|
| 482 |
+
|
| 483 |
+
[What are attention masks?](../glossary#attention-mask)
|
| 484 |
+
output_attentions (`bool`, *optional*):
|
| 485 |
+
Whether or not to return the attentions tensors of all attention layers. See `attentions` under
|
| 486 |
+
returned tensors for more detail.
|
| 487 |
+
output_hidden_states (`bool`, *optional*):
|
| 488 |
+
Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors
|
| 489 |
+
for more detail.
|
| 490 |
+
return_dict (`bool`, *optional*):
|
| 491 |
+
Whether or not to return a [`~utils.ModelOutput`] instead of a plain tuple.
|
| 492 |
+
"""
|
| 493 |
+
|
| 494 |
+
rotary_pos_emb = self.rot_pos_emb(grid_thws)
|
| 495 |
+
window_index, cu_window_seqlens = self.get_window_index(grid_thws)
|
| 496 |
+
cu_window_seqlens = torch.tensor(
|
| 497 |
+
cu_window_seqlens,
|
| 498 |
+
device=inputs_embeds.device,
|
| 499 |
+
dtype=grid_thws.dtype if torch.jit.is_tracing() else torch.int32,
|
| 500 |
+
)
|
| 501 |
+
cu_window_seqlens = torch.unique_consecutive(cu_window_seqlens)
|
| 502 |
+
|
| 503 |
+
seq_len, _ = inputs_embeds.size()
|
| 504 |
+
inputs_embeds = inputs_embeds.reshape(seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
|
| 505 |
+
inputs_embeds = inputs_embeds[window_index, :, :]
|
| 506 |
+
inputs_embeds = inputs_embeds.reshape(seq_len, -1)
|
| 507 |
+
rotary_pos_emb = rotary_pos_emb.reshape(seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
|
| 508 |
+
rotary_pos_emb = rotary_pos_emb[window_index, :, :]
|
| 509 |
+
rotary_pos_emb = rotary_pos_emb.reshape(seq_len, -1)
|
| 510 |
+
emb = torch.cat((rotary_pos_emb, rotary_pos_emb), dim=-1)
|
| 511 |
+
position_embeddings = (emb.cos(), emb.sin())
|
| 512 |
+
|
| 513 |
+
cu_seqlens = torch.repeat_interleave(grid_thws[:, 1] * grid_thws[:, 2], grid_thws[:, 0]).cumsum(
|
| 514 |
+
dim=0,
|
| 515 |
+
# Select dtype based on the following factors:
|
| 516 |
+
# - FA2 requires that cu_seqlens_q must have dtype int32
|
| 517 |
+
# - torch.onnx.export requires that cu_seqlens_q must have same dtype as grid_thw
|
| 518 |
+
# See https://github.com/huggingface/transformers/pull/34852 for more information
|
| 519 |
+
dtype=grid_thws.dtype if torch.jit.is_tracing() else torch.int32,
|
| 520 |
+
)
|
| 521 |
+
cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0)
|
| 522 |
+
|
| 523 |
+
reverse_indices = torch.argsort(window_index)
|
| 524 |
+
encoder_states = () if output_hidden_states else None
|
| 525 |
+
|
| 526 |
+
hidden_states = inputs_embeds
|
| 527 |
+
for index, block in enumerate(self.layers):
|
| 528 |
+
if self.fullatt_block_indexes is None or index in self.fullatt_block_indexes:
|
| 529 |
+
cu_seqlens_tmp = cu_seqlens
|
| 530 |
+
else:
|
| 531 |
+
cu_seqlens_tmp = cu_window_seqlens
|
| 532 |
+
if self.gradient_checkpointing and self.training:
|
| 533 |
+
hidden_states = self._gradient_checkpointing_func(block.__call__, hidden_states, cu_seqlens_tmp, position_embeddings)
|
| 534 |
+
else:
|
| 535 |
+
hidden_states = block(hidden_states, cu_seqlens_tmp, position_embeddings)
|
| 536 |
+
if output_hidden_states:
|
| 537 |
+
hidden_states_ = hidden_states.reshape(seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
|
| 538 |
+
encoder_states += (hidden_states_[reverse_indices, :].reshape(seq_len, -1),)
|
| 539 |
+
# tokens = self.post_trunk_norm(tokens)
|
| 540 |
+
hidden_states = hidden_states.reshape(seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
|
| 541 |
+
hidden_states = hidden_states[reverse_indices, :].reshape(seq_len, -1)
|
| 542 |
+
|
| 543 |
+
return hidden_states, encoder_states
|
| 544 |
+
|
| 545 |
+
class Siglip2VisionTransformer(nn.Module):
|
| 546 |
+
def __init__(self, config: Siglip2NavitConfig):
|
| 547 |
+
super().__init__()
|
| 548 |
+
self.config = config
|
| 549 |
+
embed_dim = config.hidden_size
|
| 550 |
+
|
| 551 |
+
self.embeddings = Siglip2VisionEmbeddings(config)
|
| 552 |
+
self.encoder = Siglip2Encoder(config)
|
| 553 |
+
self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
| 554 |
+
self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2"
|
| 555 |
+
|
| 556 |
+
def forward(
|
| 557 |
+
self,
|
| 558 |
+
pixel_values: torch.FloatTensor,
|
| 559 |
+
grid_thws: torch.LongTensor,
|
| 560 |
+
output_hidden_states: Optional[bool] = True,
|
| 561 |
+
return_dict: Optional[bool] = True,
|
| 562 |
+
) -> Union[
|
| 563 |
+
Tuple[torch.Tensor],
|
| 564 |
+
Tuple[torch.Tensor, Tuple[torch.Tensor, ...]],
|
| 565 |
+
BaseModelOutputWithNoAttention,
|
| 566 |
+
]:
|
| 567 |
+
r"""
|
| 568 |
+
spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`):
|
| 569 |
+
Tensor containing the spatial dimensions (height, width) of the input images.
|
| 570 |
+
"""
|
| 571 |
+
# output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
| 572 |
+
# output_hidden_states = (
|
| 573 |
+
# output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 574 |
+
# )
|
| 575 |
+
|
| 576 |
+
hidden_states = self.embeddings(pixel_values, grid_thws)
|
| 577 |
+
|
| 578 |
+
last_hidden_state, hidden_states = self.encoder(hidden_states, grid_thws, output_hidden_states)
|
| 579 |
+
last_hidden_state = self.post_layernorm(last_hidden_state)
|
| 580 |
+
|
| 581 |
+
if not return_dict:
|
| 582 |
+
output = (last_hidden_state,)
|
| 583 |
+
output += (hidden_states,) if output_hidden_states else ()
|
| 584 |
+
return output
|
| 585 |
+
|
| 586 |
+
return BaseModelOutputWithNoAttention(
|
| 587 |
+
last_hidden_state=last_hidden_state,
|
| 588 |
+
hidden_states=hidden_states
|
| 589 |
+
)
|
| 590 |
+
|
| 591 |
+
class Siglip2PreTrainedModel(PreTrainedModel):
|
| 592 |
+
config_class = Siglip2NavitConfig
|
| 593 |
+
base_model_prefix = "siglip2_navit"
|
| 594 |
+
supports_gradient_checkpointing = True
|
| 595 |
+
|
| 596 |
+
_no_split_modules = [
|
| 597 |
+
"Siglip2VisionEmbeddings",
|
| 598 |
+
"Siglip2EncoderLayer",
|
| 599 |
+
]
|
| 600 |
+
_supports_flash_attn_2 = True
|
| 601 |
+
_supports_sdpa = False
|
| 602 |
+
_supports_flex_attn = False
|
| 603 |
+
_supports_attention_backend = True
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
class Siglip2NavitModel(Siglip2PreTrainedModel):
|
| 607 |
+
config_class = Siglip2NavitConfig
|
| 608 |
+
main_input_name = "pixel_values"
|
| 609 |
+
|
| 610 |
+
def __init__(self, config: Siglip2NavitConfig):
|
| 611 |
+
super().__init__(config)
|
| 612 |
+
|
| 613 |
+
self.vision_model = Siglip2VisionTransformer(config)
|
| 614 |
+
|
| 615 |
+
def get_input_embeddings(self) -> nn.Module:
|
| 616 |
+
return self.vision_model.embeddings.patch_embedding
|
| 617 |
+
|
| 618 |
+
def forward(
|
| 619 |
+
self,
|
| 620 |
+
pixel_values: torch.FloatTensor,
|
| 621 |
+
grid_thws: torch.LongTensor,
|
| 622 |
+
output_hidden_states: Optional[bool] = None,
|
| 623 |
+
return_dict: Optional[bool] = None,
|
| 624 |
+
) -> Union[
|
| 625 |
+
Tuple[torch.Tensor],
|
| 626 |
+
Tuple[torch.Tensor, Tuple[torch.Tensor, ...]],
|
| 627 |
+
BaseModelOutputWithNoAttention,
|
| 628 |
+
]:
|
| 629 |
+
|
| 630 |
+
if output_hidden_states is None:
|
| 631 |
+
output_hidden_states = self.config.output_hidden_states
|
| 632 |
+
if return_dict is None:
|
| 633 |
+
return_dict = self.config.use_return_dict
|
| 634 |
+
|
| 635 |
+
return self.vision_model(
|
| 636 |
+
pixel_values=pixel_values,
|
| 637 |
+
grid_thws=grid_thws,
|
| 638 |
+
output_hidden_states=output_hidden_states,
|
| 639 |
+
return_dict=return_dict,
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
class OXREmbedding(torch.nn.Embedding):
|
| 643 |
+
"""
|
| 644 |
+
A visual embedding layer that can handle both discrete token IDs (long) and continuous
|
| 645 |
+
soft-token probabilities (float).
|
| 646 |
+
"""
|
| 647 |
+
|
| 648 |
+
def forward(self, visual_tokens: Tensor) -> Tensor:
|
| 649 |
+
if visual_tokens.dtype in [torch.int8, torch.int16, torch.int32, torch.int64, torch.long]:
|
| 650 |
+
return super().forward(visual_tokens)
|
| 651 |
+
# Handle soft tokens (probabilities) by matrix multiplication with the embedding weight
|
| 652 |
+
return torch.matmul(visual_tokens, self.weight)
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
class OXRVisualTokenizer(torch.nn.Module):
|
| 656 |
+
"""
|
| 657 |
+
Tokenizes images or videos into a sequence of continuous visual tokens.
|
| 658 |
+
"""
|
| 659 |
+
|
| 660 |
+
def __init__(self, vit, visual_vocab_size, image_processor_name_or_path, *args, **kwargs):
|
| 661 |
+
super().__init__(*args, **kwargs)
|
| 662 |
+
self.vit = vit
|
| 663 |
+
image_processor_kwargs = {"do_center_crop": False}
|
| 664 |
+
if int(TRANSFORMERS_VERSION.split(".", 1)[0]) >= 5:
|
| 665 |
+
image_processor_kwargs["backend"] = "pil"
|
| 666 |
+
else:
|
| 667 |
+
image_processor_kwargs["use_fast"] = False
|
| 668 |
+
self.image_processor = AutoImageProcessor.from_pretrained(
|
| 669 |
+
image_processor_name_or_path,
|
| 670 |
+
**image_processor_kwargs,
|
| 671 |
+
)
|
| 672 |
+
head_dim = visual_vocab_size - len(INDICATOR_IDS)
|
| 673 |
+
self.head = torch.nn.Sequential(
|
| 674 |
+
torch.nn.Linear(self.vit.config.hidden_size * self.vit.config.hidden_stride ** 2, head_dim, bias=False),
|
| 675 |
+
torch.nn.LayerNorm(head_dim)
|
| 676 |
+
)
|
| 677 |
+
|
| 678 |
+
def _encode(self, pixel_values, grid_thws):
|
| 679 |
+
output = self.vit(pixel_values, grid_thws, output_hidden_states=True, return_dict=True)
|
| 680 |
+
features = output.hidden_states[-1]
|
| 681 |
+
seq_len, _ = features.shape
|
| 682 |
+
features = features.reshape(seq_len // (self.vit.config.hidden_stride ** 2), -1)
|
| 683 |
+
return features
|
| 684 |
+
|
| 685 |
+
# Adapted from qwen2_vl
|
| 686 |
+
@staticmethod
|
| 687 |
+
def smart_resize(
|
| 688 |
+
height: int, width: int, factor: int = 28, min_pixels: int = 448 * 448, max_pixels: int = 1344 * 1792
|
| 689 |
+
):
|
| 690 |
+
"""Rescales the image so that the following conditions are met:
|
| 691 |
+
1. Both dimensions are divisible by 'factor'.
|
| 692 |
+
2. The total number of pixels is within ['min_pixels', 'max_pixels'].
|
| 693 |
+
3. The aspect ratio is maintained as closely as possible.
|
| 694 |
+
"""
|
| 695 |
+
if height < factor or width < factor:
|
| 696 |
+
if height < width:
|
| 697 |
+
width = round(factor / height * width)
|
| 698 |
+
height = factor
|
| 699 |
+
else:
|
| 700 |
+
height = round(factor / width * height)
|
| 701 |
+
width = factor
|
| 702 |
+
|
| 703 |
+
elif max(height, width) / min(height, width) > 200:
|
| 704 |
+
if height > width:
|
| 705 |
+
height = 200 * width
|
| 706 |
+
else:
|
| 707 |
+
width = 200 * height
|
| 708 |
+
|
| 709 |
+
h_bar = round(height / factor) * factor
|
| 710 |
+
w_bar = round(width / factor) * factor
|
| 711 |
+
if h_bar * w_bar > max_pixels:
|
| 712 |
+
beta = math.sqrt((height * width) / max_pixels)
|
| 713 |
+
h_bar = math.floor(height / beta / factor) * factor
|
| 714 |
+
w_bar = math.floor(width / beta / factor) * factor
|
| 715 |
+
elif h_bar * w_bar < min_pixels:
|
| 716 |
+
beta = math.sqrt(min_pixels / (height * width))
|
| 717 |
+
h_bar = math.ceil(height * beta / factor) * factor
|
| 718 |
+
w_bar = math.ceil(width * beta / factor) * factor
|
| 719 |
+
return h_bar, w_bar
|
| 720 |
+
|
| 721 |
+
def preprocess(
|
| 722 |
+
self,
|
| 723 |
+
image: Optional[PIL.Image.Image] = None,
|
| 724 |
+
video: Optional[List[PIL.Image.Image]] = None,
|
| 725 |
+
min_pixels: Optional[int] = None,
|
| 726 |
+
max_pixels: Optional[int] = None
|
| 727 |
+
):
|
| 728 |
+
patch_size = self.vit.config.patch_size
|
| 729 |
+
temporal_patch_size = self.vit.config.temporal_patch_size
|
| 730 |
+
hidden_stride = self.vit.config.hidden_stride
|
| 731 |
+
assert (image is None) ^ (video is None), "Invalid input: expect either image or video"
|
| 732 |
+
if image is not None:
|
| 733 |
+
images = [image]
|
| 734 |
+
else:
|
| 735 |
+
images = video
|
| 736 |
+
images = [image.convert("RGB") if image.mode != 'RGB' else image for image in images]
|
| 737 |
+
width, height = images[0].size
|
| 738 |
+
processed_images = []
|
| 739 |
+
for image in images:
|
| 740 |
+
resized_height, resized_width = self.smart_resize(
|
| 741 |
+
height,
|
| 742 |
+
width,
|
| 743 |
+
factor=patch_size * hidden_stride,
|
| 744 |
+
min_pixels=min_pixels,
|
| 745 |
+
max_pixels=max_pixels,
|
| 746 |
+
)
|
| 747 |
+
new_size = dict(height=resized_height, width=resized_width)
|
| 748 |
+
new_image = self.image_processor.preprocess(image, size=new_size, return_tensors="np")['pixel_values'][0]
|
| 749 |
+
processed_images.append(new_image)
|
| 750 |
+
|
| 751 |
+
patches = np.array(processed_images)
|
| 752 |
+
if patches.shape[0] % temporal_patch_size != 0:
|
| 753 |
+
repeats = np.repeat(patches[-1][np.newaxis], temporal_patch_size - 1, axis=0)
|
| 754 |
+
patches = np.concatenate([patches, repeats], axis=0)
|
| 755 |
+
channel = patches.shape[1]
|
| 756 |
+
grid_t = patches.shape[0] // temporal_patch_size
|
| 757 |
+
grid_h, grid_w = resized_height // patch_size, resized_width // patch_size
|
| 758 |
+
grid_thw = torch.tensor([[grid_t, grid_h, grid_w]])
|
| 759 |
+
|
| 760 |
+
patches = patches.reshape(
|
| 761 |
+
grid_t, temporal_patch_size, channel,
|
| 762 |
+
grid_h // hidden_stride, hidden_stride, patch_size,
|
| 763 |
+
grid_w // hidden_stride, hidden_stride, patch_size,
|
| 764 |
+
)
|
| 765 |
+
patches = patches.transpose(0, 3, 6, 4, 7, 2, 1, 5, 8)
|
| 766 |
+
flatten_patches = patches.reshape(
|
| 767 |
+
grid_t * grid_h * grid_w, channel * temporal_patch_size * patch_size * patch_size
|
| 768 |
+
)
|
| 769 |
+
flatten_patches = torch.tensor(flatten_patches)
|
| 770 |
+
|
| 771 |
+
return flatten_patches, grid_thw
|
| 772 |
+
|
| 773 |
+
def forward(
|
| 774 |
+
self, pixel_values, grid_thws
|
| 775 |
+
) -> torch.Tensor: # [BatchSize, ImageShape] -> [BatchSize, #Token, VocabSize]
|
| 776 |
+
features = self._encode(pixel_values, grid_thws)
|
| 777 |
+
logits = self.head(features)
|
| 778 |
+
tokens = torch.softmax(logits, dim=-1, dtype=torch.float32).to(logits.dtype)
|
| 779 |
+
|
| 780 |
+
token_len, _ = tokens.shape
|
| 781 |
+
padding_tensor = torch.zeros(size=(token_len, len(INDICATOR_IDS)),
|
| 782 |
+
dtype=tokens.dtype,
|
| 783 |
+
device=tokens.device,
|
| 784 |
+
layout=tokens.layout,
|
| 785 |
+
requires_grad=False)
|
| 786 |
+
tokens = torch.cat((tokens, padding_tensor), dim=1)
|
| 787 |
+
return tokens
|
| 788 |
+
|
| 789 |
+
|
| 790 |
+
class OXRPreTrainedModel(PreTrainedModel):
|
| 791 |
+
config_class = OXRConfig
|
| 792 |
+
base_model_prefix = "oxr"
|
| 793 |
+
|
| 794 |
+
|
| 795 |
+
class OXR(OXRPreTrainedModel):
|
| 796 |
+
_supports_flash_attn_2 = True
|
| 797 |
+
|
| 798 |
+
def __init__(self, config: OXRConfig, *inputs, **kwargs):
|
| 799 |
+
super().__init__(config, *inputs, **kwargs)
|
| 800 |
+
|
| 801 |
+
self.llm = AutoModelForCausalLM.from_config(self.config.llm_config)
|
| 802 |
+
assert self.config.hidden_size == self.llm.config.hidden_size, "hidden size mismatch"
|
| 803 |
+
self.text_tokenizer = AutoTokenizer.from_pretrained(
|
| 804 |
+
self.config.name_or_path,
|
| 805 |
+
trust_remote_code=True,
|
| 806 |
+
)
|
| 807 |
+
self.visual_tokenizer = OXRVisualTokenizer(vit=AutoModel.from_config(self.config.vit_config),
|
| 808 |
+
visual_vocab_size=self.config.visual_vocab_size,
|
| 809 |
+
image_processor_name_or_path=self.config.name_or_path)
|
| 810 |
+
|
| 811 |
+
self.vte = OXREmbedding(self.config.visual_vocab_size, self.config.hidden_size,
|
| 812 |
+
device=self.visual_tokenizer.vit.device, dtype=self.visual_tokenizer.vit.dtype)
|
| 813 |
+
indicator_token_indices = torch.arange(
|
| 814 |
+
self.config.visual_vocab_size - len(INDICATOR_IDS),
|
| 815 |
+
self.config.visual_vocab_size,
|
| 816 |
+
dtype=torch.long
|
| 817 |
+
)
|
| 818 |
+
self.register_buffer("indicator_token_indices", indicator_token_indices, persistent=False)
|
| 819 |
+
|
| 820 |
+
def _merge_modules(modules_list: tuple):
|
| 821 |
+
merged_modules = []
|
| 822 |
+
for modules in modules_list:
|
| 823 |
+
merged_modules.extend(modules if modules else [])
|
| 824 |
+
return merged_modules
|
| 825 |
+
|
| 826 |
+
# Standard model configurations for parallelism and device placement
|
| 827 |
+
self._no_split_modules = _merge_modules(
|
| 828 |
+
(self.llm._no_split_modules, self.visual_tokenizer.vit._no_split_modules))
|
| 829 |
+
self._skip_keys_device_placement = self.llm._skip_keys_device_placement
|
| 830 |
+
self._keep_in_fp32_modules = _merge_modules(
|
| 831 |
+
(self.llm._keep_in_fp32_modules, self.visual_tokenizer.vit._keep_in_fp32_modules))
|
| 832 |
+
self.is_parallelizable = all((
|
| 833 |
+
getattr(self.llm, "is_parallelizable", False),
|
| 834 |
+
getattr(self.visual_tokenizer.vit, "is_parallelizable", False),
|
| 835 |
+
))
|
| 836 |
+
self.supports_gradient_checkpointing = True
|
| 837 |
+
self.post_init()
|
| 838 |
+
|
| 839 |
+
def tie_weights(self, missing_keys: Optional[set[str]] = None, recompute_mapping: bool = True):
|
| 840 |
+
self.llm.tie_weights()
|
| 841 |
+
try:
|
| 842 |
+
return PreTrainedModel.tie_weights(
|
| 843 |
+
self,
|
| 844 |
+
missing_keys=missing_keys,
|
| 845 |
+
recompute_mapping=recompute_mapping,
|
| 846 |
+
)
|
| 847 |
+
except TypeError as exc:
|
| 848 |
+
if "unexpected keyword argument" not in str(exc):
|
| 849 |
+
raise
|
| 850 |
+
return PreTrainedModel.tie_weights(self)
|
| 851 |
+
|
| 852 |
+
def get_wte(self):
|
| 853 |
+
return self.llm.get_input_embeddings()
|
| 854 |
+
|
| 855 |
+
def forward(
|
| 856 |
+
self,
|
| 857 |
+
input_ids: torch.Tensor,
|
| 858 |
+
attention_mask: torch.Tensor,
|
| 859 |
+
pixel_values: Optional[torch.Tensor],
|
| 860 |
+
grid_thws: Optional[torch.Tensor],
|
| 861 |
+
labels: Optional[torch.Tensor] = None,
|
| 862 |
+
**kwargs
|
| 863 |
+
):
|
| 864 |
+
inputs_embeds = self.merge_multimodal(
|
| 865 |
+
input_ids=input_ids,
|
| 866 |
+
pixel_values=pixel_values,
|
| 867 |
+
grid_thws=grid_thws,
|
| 868 |
+
)
|
| 869 |
+
return self.llm(inputs_embeds=inputs_embeds, attention_mask=attention_mask, labels=labels, **kwargs)
|
| 870 |
+
|
| 871 |
+
def merge_multimodal(
|
| 872 |
+
self,
|
| 873 |
+
input_ids: torch.Tensor,
|
| 874 |
+
pixel_values: Optional[torch.Tensor],
|
| 875 |
+
grid_thws: Optional[torch.Tensor],
|
| 876 |
+
):
|
| 877 |
+
placeholder_token_mask = torch.lt(input_ids, 0)
|
| 878 |
+
multimodal_embeds = self.get_wte()(torch.masked_fill(input_ids, placeholder_token_mask, 0))
|
| 879 |
+
|
| 880 |
+
if pixel_values is not None:
|
| 881 |
+
visual_indicator_embeds = self.vte(self.indicator_token_indices).to(
|
| 882 |
+
dtype=multimodal_embeds.dtype, device=multimodal_embeds.device
|
| 883 |
+
)
|
| 884 |
+
visual_tokens = self.visual_tokenizer(pixel_values, grid_thws)
|
| 885 |
+
visual_embeds = self.vte(visual_tokens).to(dtype=multimodal_embeds.dtype, device=multimodal_embeds.device)
|
| 886 |
+
|
| 887 |
+
for i, indicator_id in enumerate(INDICATOR_IDS):
|
| 888 |
+
multimodal_embeds[input_ids == indicator_id] = visual_indicator_embeds[i]
|
| 889 |
+
multimodal_embeds[input_ids == VISUAL_ATOM_ID] = visual_embeds
|
| 890 |
+
|
| 891 |
+
return multimodal_embeds
|
| 892 |
+
|
| 893 |
+
def _merge_inputs(
|
| 894 |
+
self, raw_input_ids, placeholder_id, grid_thws, indicator_begin_id, indicator_end_id
|
| 895 |
+
):
|
| 896 |
+
input_ids = []
|
| 897 |
+
prev_index = 0
|
| 898 |
+
placeholder_indexes = [i for i, v in enumerate(raw_input_ids) if v == placeholder_id]
|
| 899 |
+
for placeholder_index, grid_thw in zip(placeholder_indexes, grid_thws):
|
| 900 |
+
input_ids.extend(raw_input_ids[prev_index:placeholder_index])
|
| 901 |
+
num_image_atoms = grid_thw.prod().item()
|
| 902 |
+
num_image_atoms //= self.visual_tokenizer.vit.config.hidden_stride ** 2
|
| 903 |
+
num_image_atoms //= self.visual_tokenizer.vit.config.temporal_patch_size
|
| 904 |
+
input_ids.extend([indicator_begin_id] + [VISUAL_ATOM_ID] * num_image_atoms + [indicator_end_id])
|
| 905 |
+
prev_index = placeholder_index + 1
|
| 906 |
+
input_ids.extend(raw_input_ids[prev_index:])
|
| 907 |
+
return input_ids
|
| 908 |
+
|
| 909 |
+
def _tokenize_with_visual_placeholder(self, text):
|
| 910 |
+
placeholder = VIDEO_PLACEHOLDER if VIDEO_PLACEHOLDER in text else IMAGE_PLACEHOLDER
|
| 911 |
+
placeholder_id = VIDEO_PLACEHOLDER_ID if VIDEO_PLACEHOLDER in text else IMAGE_PLACEHOLDER_ID
|
| 912 |
+
chunks = [self.text_tokenizer(chunk, add_special_tokens=False).input_ids for chunk in text.split(placeholder)]
|
| 913 |
+
input_ids = chunks[0]
|
| 914 |
+
for chunk in chunks[1:]:
|
| 915 |
+
input_ids.append(placeholder_id)
|
| 916 |
+
input_ids.extend(chunk)
|
| 917 |
+
return input_ids
|
| 918 |
+
|
| 919 |
+
def preprocess_inputs(
|
| 920 |
+
self,
|
| 921 |
+
messages: List[Union[str, Dict]],
|
| 922 |
+
min_pixels=448 * 448,
|
| 923 |
+
max_pixels=1344 * 1792,
|
| 924 |
+
add_generation_prompt=True,
|
| 925 |
+
enable_thinking=False
|
| 926 |
+
):
|
| 927 |
+
text = self.text_tokenizer.apply_chat_template(
|
| 928 |
+
messages,
|
| 929 |
+
tokenize=False,
|
| 930 |
+
add_generation_prompt=add_generation_prompt,
|
| 931 |
+
enable_thinking=enable_thinking
|
| 932 |
+
)
|
| 933 |
+
input_ids = self._tokenize_with_visual_placeholder(text)
|
| 934 |
+
images = []
|
| 935 |
+
videos = []
|
| 936 |
+
for message in messages:
|
| 937 |
+
content = message["content"]
|
| 938 |
+
if isinstance(content, list):
|
| 939 |
+
images.extend([item["image"] for item in content if item.get("image") is not None])
|
| 940 |
+
videos.extend([item["video"] for item in content if item.get("video") is not None])
|
| 941 |
+
if images and videos:
|
| 942 |
+
raise ValueError(
|
| 943 |
+
"Multiple visual input data types detected (both image and video provided). "
|
| 944 |
+
"This model supports only one type of visual input data at a time. "
|
| 945 |
+
"Please provide either image or video, but not both."
|
| 946 |
+
)
|
| 947 |
+
|
| 948 |
+
pixel_values, grid_thws = None, None
|
| 949 |
+
if images:
|
| 950 |
+
pixel_values, grid_thws = zip(
|
| 951 |
+
*(self.visual_tokenizer.preprocess(image=image, min_pixels=min_pixels, max_pixels=max_pixels)
|
| 952 |
+
for image in images)
|
| 953 |
+
)
|
| 954 |
+
input_ids = self._merge_inputs(
|
| 955 |
+
input_ids, IMAGE_PLACEHOLDER_ID, grid_thws, INDICATOR_IDS[0], INDICATOR_IDS[1]
|
| 956 |
+
)
|
| 957 |
+
pixel_values = torch.cat(pixel_values, dim=0)
|
| 958 |
+
grid_thws = torch.cat(grid_thws, dim=0)
|
| 959 |
+
elif videos:
|
| 960 |
+
assert len(videos) == 1, "only support single video"
|
| 961 |
+
pixel_values, grid_thws = self.visual_tokenizer.preprocess(
|
| 962 |
+
video=videos[0], min_pixels=min_pixels, max_pixels=max_pixels
|
| 963 |
+
)
|
| 964 |
+
input_ids = self._merge_inputs(
|
| 965 |
+
input_ids, VIDEO_PLACEHOLDER_ID, grid_thws, INDICATOR_IDS[2], INDICATOR_IDS[3]
|
| 966 |
+
)
|
| 967 |
+
|
| 968 |
+
input_ids = torch.tensor(input_ids, dtype=torch.long).unsqueeze(0)
|
| 969 |
+
|
| 970 |
+
return input_ids, pixel_values, grid_thws
|
| 971 |
+
|
| 972 |
+
def generate(
|
| 973 |
+
self,
|
| 974 |
+
inputs: Optional[torch.Tensor] = None,
|
| 975 |
+
**kwargs,
|
| 976 |
+
) -> Union[GenerateOutput, torch.LongTensor]:
|
| 977 |
+
attention_mask = torch.ne(inputs, self.text_tokenizer.pad_token_id).to(device=inputs.device)
|
| 978 |
+
inputs_embeds = self.merge_multimodal(
|
| 979 |
+
input_ids=inputs,
|
| 980 |
+
pixel_values=kwargs.pop('pixel_values', None),
|
| 981 |
+
grid_thws=kwargs.pop('grid_thws', None)
|
| 982 |
+
)
|
| 983 |
+
enable_thinking = kwargs.pop('enable_thinking', False)
|
| 984 |
+
enable_thinking_budget = kwargs.pop('enable_thinking_budget', False)
|
| 985 |
+
thinking_budget = kwargs.pop('thinking_budget', 1024)
|
| 986 |
+
|
| 987 |
+
if enable_thinking and enable_thinking_budget:
|
| 988 |
+
actual_max_new_tokens = kwargs['max_new_tokens']
|
| 989 |
+
kwargs['max_new_tokens'] = thinking_budget
|
| 990 |
+
generated_ids = self.llm.generate(inputs=None, inputs_embeds=inputs_embeds, attention_mask=attention_mask, **kwargs)
|
| 991 |
+
output_ids = generated_ids
|
| 992 |
+
output_ids_list = generated_ids[0]
|
| 993 |
+
|
| 994 |
+
# check if the generation has already finished (151645 is <|im_end|>)
|
| 995 |
+
if 151645 not in output_ids_list:
|
| 996 |
+
# check if the thinking process has finished (151668 is </think>)
|
| 997 |
+
# and prepare the second model input
|
| 998 |
+
if 151668 not in output_ids_list:
|
| 999 |
+
early_stopping_text = "\n\nConsidering the limited time by the user, I have to give the solution based on the thinking directly now.\n</think>\n\n"
|
| 1000 |
+
early_stopping_ids = self.text_tokenizer(early_stopping_text, return_tensors="pt", return_attention_mask=False).input_ids.to(inputs.device)
|
| 1001 |
+
input_ids_appendent = torch.cat([output_ids, early_stopping_ids], dim=-1)
|
| 1002 |
+
kwargs['streamer'].put(early_stopping_ids) if 'streamer' in kwargs else None
|
| 1003 |
+
else:
|
| 1004 |
+
input_ids_appendent = output_ids
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
# second generation
|
| 1008 |
+
new_inputs = torch.cat([inputs, input_ids_appendent], dim=-1)
|
| 1009 |
+
attention_mask = torch.ne(new_inputs, self.text_tokenizer.pad_token_id).to(device=inputs.device)
|
| 1010 |
+
inputs_embeds_appendent = self.merge_multimodal(
|
| 1011 |
+
input_ids=input_ids_appendent,
|
| 1012 |
+
pixel_values=None,
|
| 1013 |
+
grid_thws=None
|
| 1014 |
+
)
|
| 1015 |
+
new_inputs_embeds = torch.cat([inputs_embeds, inputs_embeds_appendent], dim=-2)
|
| 1016 |
+
|
| 1017 |
+
kwargs['max_new_tokens'] = inputs_embeds.size(-2) + actual_max_new_tokens - new_inputs_embeds.size(-2)
|
| 1018 |
+
generated_ids2 = self.llm.generate(inputs=None, inputs_embeds=new_inputs_embeds, attention_mask=attention_mask, **kwargs)
|
| 1019 |
+
kwargs['streamer'].manual_end() if 'streamer' in kwargs else None
|
| 1020 |
+
return torch.cat([input_ids_appendent, generated_ids2], dim=-1)
|
| 1021 |
+
|
| 1022 |
+
else:
|
| 1023 |
+
kwargs['streamer'].manual_end() if 'streamer' in kwargs else None
|
| 1024 |
+
return generated_ids
|
| 1025 |
+
|
| 1026 |
+
else:
|
| 1027 |
+
generated_ids = self.llm.generate(inputs=None, inputs_embeds=inputs_embeds, attention_mask=attention_mask, **kwargs)
|
| 1028 |
+
kwargs['streamer'].manual_end() if 'streamer' in kwargs else None
|
| 1029 |
+
return generated_ids
|
| 1030 |
+
|
| 1031 |
+
|
| 1032 |
+
AutoConfig.register('siglip2_navit', Siglip2NavitConfig)
|
| 1033 |
+
AutoModel.register(Siglip2NavitConfig, Siglip2NavitModel)
|
| 1034 |
+
try:
|
| 1035 |
+
AutoModelForCausalLM.register(OXRConfig, OXR)
|
| 1036 |
+
except ValueError:
|
| 1037 |
+
pass
|
plugins/vllm/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OXR vLLM plugin
|
| 2 |
+
|
| 3 |
+
Registers the OXR architecture through `vllm.general_plugins`. Requires
|
| 4 |
+
Python 3.12+, vLLM 0.25.1 and Transformers 5.13.1. It does not modify the
|
| 5 |
+
vLLM installation. The supported Docker runtime uses PyTorch 2.11.0+cu130.
|
| 6 |
+
|
| 7 |
+
Install from the model repository root in a Linux CUDA environment:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
python -m pip install ./plugins/vllm
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Start with the model files and weights in the repository root:
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
VLLM_PLUGINS=oxr vllm serve . \
|
| 17 |
+
--trust-remote-code \
|
| 18 |
+
--served-model-name oxr \
|
| 19 |
+
--chat-template ./chat_template.jinja \
|
| 20 |
+
--dtype bfloat16 \
|
| 21 |
+
--attention-config '{"backend":"TRITON_ATTN"}' \
|
| 22 |
+
--max-model-len 16384
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
The LA model in `layout_analysis/` is served separately by the OXR layout
|
| 26 |
+
analysis service, not by this plugin.
|
| 27 |
+
|
| 28 |
+
To roll back to vLLM 0.19.1, use the previous model plugin together with the
|
| 29 |
+
matching previous OXR launcher and Docker image. Do not install this plugin
|
| 30 |
+
into the old runtime.
|
plugins/vllm/oxr_vllm/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Out-of-tree OXR model registration for vLLM."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def register() -> None:
|
| 5 |
+
"""Register lazily so plugin discovery does not initialize CUDA."""
|
| 6 |
+
from vllm.model_executor.models.registry import ModelRegistry
|
| 7 |
+
|
| 8 |
+
ModelRegistry.register_model("OXR", "oxr_vllm.oxr_model:OXR")
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = ["register"]
|
plugins/vllm/oxr_vllm/oxr_model.py
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OXR model implementation for vLLM 0.25.1."""
|
| 2 |
+
|
| 3 |
+
from collections.abc import Iterable, Mapping
|
| 4 |
+
from functools import partial
|
| 5 |
+
from typing import Annotated, Literal
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from transformers import BaseImageProcessor, BatchFeature, PretrainedConfig
|
| 10 |
+
|
| 11 |
+
from vllm.config import VllmConfig
|
| 12 |
+
from vllm.config.multimodal import BaseDummyOptions
|
| 13 |
+
from vllm.inputs import MultiModalDataDict
|
| 14 |
+
from vllm.model_executor.layers.linear import ReplicatedLinear
|
| 15 |
+
from vllm.model_executor.layers.quantization import QuantizationConfig
|
| 16 |
+
from vllm.model_executor.models.interfaces import (
|
| 17 |
+
MultiModalEmbeddings,
|
| 18 |
+
SupportsMultiModal,
|
| 19 |
+
SupportsPP,
|
| 20 |
+
)
|
| 21 |
+
from vllm.model_executor.models.siglip2navit import Siglip2NavitModel
|
| 22 |
+
from vllm.model_executor.models.utils import (
|
| 23 |
+
AutoWeightsLoader,
|
| 24 |
+
flatten_bn,
|
| 25 |
+
init_vllm_registered_model,
|
| 26 |
+
maybe_prefix,
|
| 27 |
+
)
|
| 28 |
+
from vllm.multimodal import MULTIMODAL_REGISTRY
|
| 29 |
+
from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
|
| 30 |
+
from vllm.multimodal.parse import ImageSize, MultiModalDataItems
|
| 31 |
+
from vllm.multimodal.processing import (
|
| 32 |
+
BaseDummyInputsBuilder,
|
| 33 |
+
BaseMultiModalProcessor,
|
| 34 |
+
BaseProcessingInfo,
|
| 35 |
+
PromptReplacement,
|
| 36 |
+
)
|
| 37 |
+
from vllm.renderers import TokenizeParams
|
| 38 |
+
from vllm.sequence import IntermediateTensors
|
| 39 |
+
from vllm.utils.tensor_schema import TensorSchema, TensorShape
|
| 40 |
+
|
| 41 |
+
from .oxr_processor import OXRProcessor
|
| 42 |
+
|
| 43 |
+
IMAGE_TOKEN = "<image>"
|
| 44 |
+
VIDEO_TOKEN = "<video>"
|
| 45 |
+
IMAGE_PLACEHOLDER_ID = -200
|
| 46 |
+
VIDEO_PLACEHOLDER_ID = -201
|
| 47 |
+
VISUAL_ATOM_ID = -300
|
| 48 |
+
OXR_INDICATOR_IDS = [-301, -302, -303, -304]
|
| 49 |
+
|
| 50 |
+
IMAGE_PAD_TOKEN_MAP = {
|
| 51 |
+
"gemma2": "<unused0>",
|
| 52 |
+
"llama": "<|reserved_special_token_0|>",
|
| 53 |
+
"qwen2": "<|image_pad|>",
|
| 54 |
+
"qwen3": "<|image_pad|>",
|
| 55 |
+
}
|
| 56 |
+
IMAGE_PAD_TOKEN_ID_MAP = {
|
| 57 |
+
"gemma2": 7,
|
| 58 |
+
"llama": 128002,
|
| 59 |
+
"qwen2": 151655,
|
| 60 |
+
"qwen3": 151655,
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def get_llm_config(config: PretrainedConfig) -> PretrainedConfig:
|
| 65 |
+
llm_config = getattr(config, "llm_config", None)
|
| 66 |
+
if llm_config is not None:
|
| 67 |
+
return llm_config
|
| 68 |
+
|
| 69 |
+
text_config = getattr(config, "text_config", None)
|
| 70 |
+
if text_config is not None:
|
| 71 |
+
return text_config
|
| 72 |
+
|
| 73 |
+
get_text_config = getattr(config, "get_text_config", None)
|
| 74 |
+
if get_text_config is not None:
|
| 75 |
+
candidate = get_text_config()
|
| 76 |
+
if candidate is not config:
|
| 77 |
+
return candidate
|
| 78 |
+
|
| 79 |
+
raise ValueError("OXR config does not expose an LLM sub-config")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _oxr_field_config() -> Mapping[str, MultiModalFieldConfig]:
|
| 83 |
+
return dict(
|
| 84 |
+
pixel_values=MultiModalFieldConfig.batched("image"),
|
| 85 |
+
grids=MultiModalFieldConfig.batched("image"),
|
| 86 |
+
indicator_tokens=MultiModalFieldConfig.batched("image"),
|
| 87 |
+
video_pixel_values=MultiModalFieldConfig.batched("video"),
|
| 88 |
+
video_indicator_tokens=MultiModalFieldConfig.batched("video"),
|
| 89 |
+
video_grids=MultiModalFieldConfig.batched("video"),
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class OXRImagePatchInputs(TensorSchema):
|
| 94 |
+
type: Literal["image_patches"]
|
| 95 |
+
flat_data: Annotated[torch.Tensor, TensorShape("bnp", "patch_size")]
|
| 96 |
+
indicator_tokens: Annotated[torch.Tensor, TensorShape("indicators")]
|
| 97 |
+
patches_per_item: Annotated[list[int], TensorShape("bn")]
|
| 98 |
+
grids: Annotated[torch.Tensor, TensorShape("bn", 3)]
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
class OXRVideoPatchInputs(TensorSchema):
|
| 102 |
+
type: Literal["video_patches"]
|
| 103 |
+
flat_data: Annotated[torch.Tensor, TensorShape("bnp", "patch_size")]
|
| 104 |
+
indicator_tokens: Annotated[torch.Tensor, TensorShape("indicators")]
|
| 105 |
+
patches_per_item: Annotated[list[int], TensorShape("bn")]
|
| 106 |
+
grids: Annotated[torch.Tensor, TensorShape("bn", 3)]
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class OXREmbedding(torch.nn.Embedding):
|
| 110 |
+
def forward(self, visual_tokens: torch.Tensor) -> torch.Tensor:
|
| 111 |
+
if visual_tokens.dtype in (
|
| 112 |
+
torch.int8,
|
| 113 |
+
torch.int16,
|
| 114 |
+
torch.int32,
|
| 115 |
+
torch.int64,
|
| 116 |
+
torch.long,
|
| 117 |
+
):
|
| 118 |
+
return super().forward(visual_tokens)
|
| 119 |
+
return torch.matmul(visual_tokens, self.weight)
|
| 120 |
+
|
| 121 |
+
@property
|
| 122 |
+
def device(self) -> torch.device:
|
| 123 |
+
return self.weight.device
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def dtype(self) -> torch.dtype:
|
| 127 |
+
return self.weight.dtype
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class OXRVisualTokenizer(torch.nn.Module):
|
| 131 |
+
def __init__(
|
| 132 |
+
self,
|
| 133 |
+
config: PretrainedConfig,
|
| 134 |
+
visual_vocab_size: int,
|
| 135 |
+
quant_config: QuantizationConfig | None = None,
|
| 136 |
+
prefix: str = "",
|
| 137 |
+
):
|
| 138 |
+
super().__init__()
|
| 139 |
+
self.config = config
|
| 140 |
+
self.vit = self._init_backbone(
|
| 141 |
+
config=config,
|
| 142 |
+
quant_config=quant_config,
|
| 143 |
+
prefix=f"{prefix}.vit",
|
| 144 |
+
)
|
| 145 |
+
head_dim = visual_vocab_size - len(OXR_INDICATOR_IDS)
|
| 146 |
+
self.head = torch.nn.Sequential(
|
| 147 |
+
ReplicatedLinear(
|
| 148 |
+
self.config.hidden_size * self.config.hidden_stride**2,
|
| 149 |
+
head_dim,
|
| 150 |
+
bias=False,
|
| 151 |
+
return_bias=False,
|
| 152 |
+
quant_config=quant_config,
|
| 153 |
+
prefix=f"{prefix}.head.0",
|
| 154 |
+
),
|
| 155 |
+
torch.nn.LayerNorm(head_dim),
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
def _init_backbone(
|
| 159 |
+
self,
|
| 160 |
+
config: PretrainedConfig,
|
| 161 |
+
quant_config: QuantizationConfig | None = None,
|
| 162 |
+
prefix: str = "",
|
| 163 |
+
):
|
| 164 |
+
if config.model_type == "siglip2_navit":
|
| 165 |
+
return Siglip2NavitModel(
|
| 166 |
+
config=config,
|
| 167 |
+
quant_config=quant_config,
|
| 168 |
+
prefix=prefix,
|
| 169 |
+
)
|
| 170 |
+
raise ValueError(f"Unsupported visual tokenizer model_type: {config.model_type}")
|
| 171 |
+
|
| 172 |
+
@property
|
| 173 |
+
def dtype(self) -> torch.dtype:
|
| 174 |
+
return next(self.head.parameters()).dtype
|
| 175 |
+
|
| 176 |
+
@property
|
| 177 |
+
def device(self) -> torch.device:
|
| 178 |
+
return next(self.head.parameters()).device
|
| 179 |
+
|
| 180 |
+
def tokenize(self, logits: torch.Tensor) -> torch.Tensor:
|
| 181 |
+
return torch.softmax(logits, dim=-1, dtype=torch.float32).to(logits.dtype)
|
| 182 |
+
|
| 183 |
+
def encode(
|
| 184 |
+
self,
|
| 185 |
+
pixel_values: torch.Tensor,
|
| 186 |
+
grid_thws: torch.Tensor,
|
| 187 |
+
) -> torch.Tensor:
|
| 188 |
+
features = self.vit(pixel_values, grid_thws)
|
| 189 |
+
seq_len, _ = features.shape
|
| 190 |
+
return features.reshape(seq_len // (self.config.hidden_stride**2), -1)
|
| 191 |
+
|
| 192 |
+
def forward(
|
| 193 |
+
self,
|
| 194 |
+
pixel_values: torch.Tensor,
|
| 195 |
+
grid_thws: torch.Tensor,
|
| 196 |
+
) -> torch.Tensor:
|
| 197 |
+
features = self.encode(pixel_values, grid_thws)
|
| 198 |
+
logits = self.head(features)
|
| 199 |
+
tokens = self.tokenize(logits)
|
| 200 |
+
return torch.nn.functional.pad(
|
| 201 |
+
tokens,
|
| 202 |
+
(0, len(OXR_INDICATOR_IDS)),
|
| 203 |
+
mode="constant",
|
| 204 |
+
value=0,
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class OXRProcessingInfo(BaseProcessingInfo):
|
| 209 |
+
def get_hf_config(self):
|
| 210 |
+
return self.ctx.get_hf_config()
|
| 211 |
+
|
| 212 |
+
def get_hf_processor(self, **kwargs):
|
| 213 |
+
vit_config = self.get_hf_config().vit_config
|
| 214 |
+
return self.ctx.get_hf_processor(
|
| 215 |
+
OXRProcessor,
|
| 216 |
+
image_pad_token=self.get_image_pad_token(),
|
| 217 |
+
patch_size=vit_config.patch_size,
|
| 218 |
+
hidden_stride=vit_config.hidden_stride,
|
| 219 |
+
temporal_patch_size=vit_config.temporal_patch_size,
|
| 220 |
+
**kwargs,
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
def get_default_tok_params(self) -> TokenizeParams:
|
| 224 |
+
return super().get_default_tok_params().with_kwargs(add_special_tokens=False)
|
| 225 |
+
|
| 226 |
+
def get_image_pad_token(self) -> str:
|
| 227 |
+
text_model_type = get_llm_config(self.get_hf_config()).model_type
|
| 228 |
+
return IMAGE_PAD_TOKEN_MAP[text_model_type]
|
| 229 |
+
|
| 230 |
+
def get_image_processor(self) -> BaseImageProcessor:
|
| 231 |
+
return self.get_hf_processor().image_processor # type: ignore[return-value]
|
| 232 |
+
|
| 233 |
+
def get_supported_mm_limits(self) -> Mapping[str, int | None]:
|
| 234 |
+
return {"image": None, "video": 1}
|
| 235 |
+
|
| 236 |
+
def get_image_size_with_most_features(self) -> ImageSize:
|
| 237 |
+
return ImageSize(width=1792, height=1792)
|
| 238 |
+
|
| 239 |
+
def get_num_image_tokens(
|
| 240 |
+
self,
|
| 241 |
+
*,
|
| 242 |
+
image_width: int,
|
| 243 |
+
image_height: int,
|
| 244 |
+
num_frames: int = 1,
|
| 245 |
+
) -> int:
|
| 246 |
+
vit_config = self.get_hf_config().vit_config
|
| 247 |
+
padded_num_frames = num_frames + (-num_frames % vit_config.temporal_patch_size)
|
| 248 |
+
grid_t = max(padded_num_frames // vit_config.temporal_patch_size, 1)
|
| 249 |
+
grid_h = image_height // vit_config.patch_size
|
| 250 |
+
grid_w = image_width // vit_config.patch_size
|
| 251 |
+
return grid_t * grid_h * grid_w
|
| 252 |
+
|
| 253 |
+
def get_max_image_tokens(self) -> int:
|
| 254 |
+
target_width, target_height = self.get_image_size_with_most_features()
|
| 255 |
+
return self.get_num_image_tokens(
|
| 256 |
+
image_width=target_width,
|
| 257 |
+
image_height=target_height,
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
def _get_max_video_frames(self, max_tokens: int) -> int:
|
| 261 |
+
target_width, target_height = self.get_image_size_with_most_features()
|
| 262 |
+
num_frames = 0
|
| 263 |
+
while True:
|
| 264 |
+
next_num_frames = num_frames + 1
|
| 265 |
+
next_max_tokens = self.get_num_video_tokens(
|
| 266 |
+
image_width=target_width,
|
| 267 |
+
image_height=target_height,
|
| 268 |
+
num_frames=next_num_frames,
|
| 269 |
+
)
|
| 270 |
+
if next_max_tokens > max_tokens:
|
| 271 |
+
break
|
| 272 |
+
num_frames = next_num_frames
|
| 273 |
+
return num_frames
|
| 274 |
+
|
| 275 |
+
def get_num_frames_with_most_features(
|
| 276 |
+
self,
|
| 277 |
+
seq_len: int,
|
| 278 |
+
mm_counts: Mapping[str, int],
|
| 279 |
+
) -> int:
|
| 280 |
+
max_images = mm_counts.get("image", 0)
|
| 281 |
+
max_videos = mm_counts.get("video", 0)
|
| 282 |
+
max_image_tokens = self.get_max_image_tokens() * max_images
|
| 283 |
+
max_total_frames = self._get_max_video_frames(seq_len - max_image_tokens)
|
| 284 |
+
max_frames_per_video = max_total_frames // max(max_videos, 1)
|
| 285 |
+
return max(max_frames_per_video, 1)
|
| 286 |
+
|
| 287 |
+
def get_num_video_tokens(
|
| 288 |
+
self,
|
| 289 |
+
*,
|
| 290 |
+
image_width: int,
|
| 291 |
+
image_height: int,
|
| 292 |
+
num_frames: int,
|
| 293 |
+
) -> int:
|
| 294 |
+
return self.get_num_image_tokens(
|
| 295 |
+
image_width=image_width,
|
| 296 |
+
image_height=image_height,
|
| 297 |
+
num_frames=num_frames,
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
def get_max_video_tokens(
|
| 301 |
+
self,
|
| 302 |
+
seq_len: int,
|
| 303 |
+
mm_counts: Mapping[str, int],
|
| 304 |
+
) -> int:
|
| 305 |
+
target_width, target_height = self.get_image_size_with_most_features()
|
| 306 |
+
return self.get_num_video_tokens(
|
| 307 |
+
image_width=target_width,
|
| 308 |
+
image_height=target_height,
|
| 309 |
+
num_frames=self.get_num_frames_with_most_features(seq_len, mm_counts),
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
def get_mm_max_tokens_per_item(
|
| 313 |
+
self,
|
| 314 |
+
seq_len: int,
|
| 315 |
+
mm_counts: Mapping[str, int],
|
| 316 |
+
) -> Mapping[str, int]:
|
| 317 |
+
# Return a conservative upper bound so vLLM does not profile the
|
| 318 |
+
# visual tower with dozens of maximum-sized dummy images/videos.
|
| 319 |
+
return {
|
| 320 |
+
"image": self.get_max_image_tokens() + 2,
|
| 321 |
+
"video": self.get_max_video_tokens(seq_len, mm_counts) + 2,
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
class OXRDummyInputsBuilder(BaseDummyInputsBuilder[OXRProcessingInfo]):
|
| 326 |
+
def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
|
| 327 |
+
return IMAGE_TOKEN * mm_counts.get("image", 0) + VIDEO_TOKEN * mm_counts.get(
|
| 328 |
+
"video", 0
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
def get_dummy_mm_data(
|
| 332 |
+
self,
|
| 333 |
+
seq_len: int,
|
| 334 |
+
mm_counts: Mapping[str, int],
|
| 335 |
+
mm_options: Mapping[str, BaseDummyOptions],
|
| 336 |
+
) -> MultiModalDataDict:
|
| 337 |
+
num_images = mm_counts.get("image", 0)
|
| 338 |
+
num_videos = mm_counts.get("video", 0)
|
| 339 |
+
target_width, target_height = self.info.get_image_size_with_most_features()
|
| 340 |
+
target_num_frames = self.info.get_num_frames_with_most_features(
|
| 341 |
+
seq_len, mm_counts
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
return {
|
| 345 |
+
"image": self._get_dummy_images(
|
| 346 |
+
width=target_width,
|
| 347 |
+
height=target_height,
|
| 348 |
+
num_images=num_images,
|
| 349 |
+
overrides=mm_options.get("image"),
|
| 350 |
+
),
|
| 351 |
+
"video": self._get_dummy_videos(
|
| 352 |
+
width=target_width,
|
| 353 |
+
height=target_height,
|
| 354 |
+
num_frames=target_num_frames,
|
| 355 |
+
num_videos=num_videos,
|
| 356 |
+
overrides=mm_options.get("video"),
|
| 357 |
+
),
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
class OXRMultiModalProcessor(BaseMultiModalProcessor[OXRProcessingInfo]):
|
| 362 |
+
def visual_indicators_to_visual_tokens(
|
| 363 |
+
self,
|
| 364 |
+
visual_indicators: list[int],
|
| 365 |
+
) -> list[int]:
|
| 366 |
+
vocab_size = self.info.get_hf_config().visual_vocab_size
|
| 367 |
+
indicator_by_id = {token_id: idx for idx, token_id in enumerate(OXR_INDICATOR_IDS)}
|
| 368 |
+
return [
|
| 369 |
+
vocab_size - len(OXR_INDICATOR_IDS) + indicator_by_id[token_id]
|
| 370 |
+
for token_id in visual_indicators
|
| 371 |
+
if token_id in indicator_by_id
|
| 372 |
+
]
|
| 373 |
+
|
| 374 |
+
def _call_hf_processor(
|
| 375 |
+
self,
|
| 376 |
+
prompt: str,
|
| 377 |
+
mm_data: Mapping[str, object],
|
| 378 |
+
mm_kwargs: Mapping[str, object],
|
| 379 |
+
tok_kwargs: Mapping[str, object],
|
| 380 |
+
) -> BatchFeature:
|
| 381 |
+
if not mm_data:
|
| 382 |
+
tokenizer = self.info.get_tokenizer()
|
| 383 |
+
prompt_ids = tokenizer.encode(prompt, add_special_tokens=False)
|
| 384 |
+
return BatchFeature(dict(input_ids=[prompt_ids]), tensor_type="pt")
|
| 385 |
+
|
| 386 |
+
processed_outputs = super()._call_hf_processor(
|
| 387 |
+
prompt=prompt,
|
| 388 |
+
mm_data=mm_data,
|
| 389 |
+
mm_kwargs=mm_kwargs,
|
| 390 |
+
tok_kwargs=tok_kwargs,
|
| 391 |
+
)
|
| 392 |
+
hf_processor = self.info.get_hf_processor()
|
| 393 |
+
|
| 394 |
+
if "videos" in mm_data:
|
| 395 |
+
visual_indicators = [
|
| 396 |
+
hf_processor.construct_visual_indicators((1, 1, 1), True)
|
| 397 |
+
for _ in processed_outputs["video_grids"]
|
| 398 |
+
]
|
| 399 |
+
processed_outputs["video_indicator_tokens"] = torch.tensor(
|
| 400 |
+
[
|
| 401 |
+
self.visual_indicators_to_visual_tokens(indicators)
|
| 402 |
+
for indicators in visual_indicators
|
| 403 |
+
],
|
| 404 |
+
dtype=torch.long,
|
| 405 |
+
)
|
| 406 |
+
|
| 407 |
+
if "images" in mm_data:
|
| 408 |
+
visual_indicators = [
|
| 409 |
+
hf_processor.construct_visual_indicators((1, 1, 1), False)
|
| 410 |
+
for _ in processed_outputs["grids"]
|
| 411 |
+
]
|
| 412 |
+
processed_outputs["indicator_tokens"] = torch.tensor(
|
| 413 |
+
[
|
| 414 |
+
self.visual_indicators_to_visual_tokens(indicators)
|
| 415 |
+
for indicators in visual_indicators
|
| 416 |
+
],
|
| 417 |
+
dtype=torch.long,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
return processed_outputs
|
| 421 |
+
|
| 422 |
+
def _apply_hf_processor_tokens_only(self, prompt_tokens: list[int]) -> list[int]:
|
| 423 |
+
return prompt_tokens
|
| 424 |
+
|
| 425 |
+
def _get_mm_fields_config(
|
| 426 |
+
self,
|
| 427 |
+
hf_inputs: BatchFeature,
|
| 428 |
+
hf_processor_mm_kwargs: Mapping[str, object],
|
| 429 |
+
) -> Mapping[str, MultiModalFieldConfig]:
|
| 430 |
+
return _oxr_field_config()
|
| 431 |
+
|
| 432 |
+
def _get_prompt_updates(
|
| 433 |
+
self,
|
| 434 |
+
mm_items: MultiModalDataItems,
|
| 435 |
+
hf_processor_mm_kwargs: Mapping[str, object],
|
| 436 |
+
out_mm_kwargs: MultiModalKwargsItems,
|
| 437 |
+
) -> list[PromptReplacement]:
|
| 438 |
+
def get_replacement(item_idx: int, modality: str) -> list[int]:
|
| 439 |
+
if modality == "image":
|
| 440 |
+
grid = out_mm_kwargs["image"][item_idx]["grids"].data
|
| 441 |
+
elif modality == "video":
|
| 442 |
+
grid = out_mm_kwargs["video"][item_idx]["video_grids"].data
|
| 443 |
+
else:
|
| 444 |
+
raise ValueError(f"Unsupported modality: {modality}")
|
| 445 |
+
|
| 446 |
+
hf_processor = self.info.get_hf_processor()
|
| 447 |
+
return hf_processor.construct_visual_placeholders(
|
| 448 |
+
grid[0],
|
| 449 |
+
is_video=(modality == "video"),
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
return [
|
| 453 |
+
PromptReplacement(
|
| 454 |
+
modality=modality,
|
| 455 |
+
target=IMAGE_TOKEN if modality == "image" else VIDEO_TOKEN,
|
| 456 |
+
replacement=partial(get_replacement, modality=modality),
|
| 457 |
+
)
|
| 458 |
+
for modality in ("image", "video")
|
| 459 |
+
]
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
@MULTIMODAL_REGISTRY.register_processor(
|
| 463 |
+
OXRMultiModalProcessor,
|
| 464 |
+
info=OXRProcessingInfo,
|
| 465 |
+
dummy_inputs=OXRDummyInputsBuilder,
|
| 466 |
+
)
|
| 467 |
+
class OXR(nn.Module, SupportsMultiModal, SupportsPP):
|
| 468 |
+
@classmethod
|
| 469 |
+
def get_placeholder_str(cls, modality: str, i: int) -> str | None:
|
| 470 |
+
if modality.startswith("image"):
|
| 471 |
+
return IMAGE_TOKEN
|
| 472 |
+
if modality.startswith("video"):
|
| 473 |
+
return VIDEO_TOKEN
|
| 474 |
+
raise ValueError("Only image or video modality is supported")
|
| 475 |
+
|
| 476 |
+
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
|
| 477 |
+
super().__init__()
|
| 478 |
+
config = vllm_config.model_config.hf_config
|
| 479 |
+
quant_config = vllm_config.quant_config
|
| 480 |
+
|
| 481 |
+
self.config: PretrainedConfig = config
|
| 482 |
+
|
| 483 |
+
with self._mark_language_model(vllm_config):
|
| 484 |
+
self.llm = init_vllm_registered_model(
|
| 485 |
+
vllm_config=vllm_config.with_hf_config(get_llm_config(config)),
|
| 486 |
+
prefix=maybe_prefix(prefix, "llm"),
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
with self._mark_tower_model(vllm_config, {"image", "video"}):
|
| 490 |
+
self.visual_tokenizer = OXRVisualTokenizer(
|
| 491 |
+
config=config.vit_config,
|
| 492 |
+
visual_vocab_size=config.visual_vocab_size,
|
| 493 |
+
quant_config=quant_config,
|
| 494 |
+
prefix=f"{prefix}.visual_tokenizer",
|
| 495 |
+
)
|
| 496 |
+
self.vte = OXREmbedding(config.visual_vocab_size, config.hidden_size)
|
| 497 |
+
|
| 498 |
+
text_model_type = get_llm_config(config).model_type
|
| 499 |
+
self.image_pad_token_id = IMAGE_PAD_TOKEN_ID_MAP[text_model_type]
|
| 500 |
+
|
| 501 |
+
self.make_empty_intermediate_tensors = (
|
| 502 |
+
self.get_language_model().make_empty_intermediate_tensors
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
def _parse_and_validate_image_input(
|
| 506 |
+
self,
|
| 507 |
+
**kwargs: object,
|
| 508 |
+
) -> OXRImagePatchInputs | None:
|
| 509 |
+
pixel_values = kwargs.pop("pixel_values", None)
|
| 510 |
+
indicator_tokens = kwargs.pop("indicator_tokens", None)
|
| 511 |
+
grids = kwargs.pop("grids", None)
|
| 512 |
+
if pixel_values is None and indicator_tokens is None:
|
| 513 |
+
return None
|
| 514 |
+
|
| 515 |
+
if pixel_values is not None and indicator_tokens is not None:
|
| 516 |
+
if not isinstance(pixel_values, (torch.Tensor, list)):
|
| 517 |
+
raise ValueError(f"Incorrect type of pixel values: {type(pixel_values)}")
|
| 518 |
+
if not isinstance(indicator_tokens, (torch.Tensor, list)):
|
| 519 |
+
raise ValueError(
|
| 520 |
+
f"Incorrect type of indicator_tokens: {type(indicator_tokens)}"
|
| 521 |
+
)
|
| 522 |
+
if grids is None:
|
| 523 |
+
raise ValueError("Missing grids for image input")
|
| 524 |
+
|
| 525 |
+
return OXRImagePatchInputs(
|
| 526 |
+
type="image_patches",
|
| 527 |
+
flat_data=flatten_bn(pixel_values, concat=True),
|
| 528 |
+
patches_per_item=[
|
| 529 |
+
x.shape[0] // (self.config.vit_config.hidden_stride**2)
|
| 530 |
+
for x in pixel_values
|
| 531 |
+
],
|
| 532 |
+
indicator_tokens=flatten_bn(indicator_tokens, concat=True),
|
| 533 |
+
grids=flatten_bn(grids, concat=True),
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
raise AssertionError("This line should be unreachable.")
|
| 537 |
+
|
| 538 |
+
def _parse_and_validate_video_input(
|
| 539 |
+
self,
|
| 540 |
+
**kwargs: object,
|
| 541 |
+
) -> OXRVideoPatchInputs | None:
|
| 542 |
+
pixel_values = kwargs.pop("video_pixel_values", None)
|
| 543 |
+
indicator_tokens = kwargs.pop("video_indicator_tokens", None)
|
| 544 |
+
grids = kwargs.pop("video_grids", None)
|
| 545 |
+
if pixel_values is None and indicator_tokens is None:
|
| 546 |
+
return None
|
| 547 |
+
|
| 548 |
+
if pixel_values is not None and indicator_tokens is not None:
|
| 549 |
+
if not isinstance(pixel_values, (torch.Tensor, list)):
|
| 550 |
+
raise ValueError(f"Incorrect type of pixel values: {type(pixel_values)}")
|
| 551 |
+
if not isinstance(indicator_tokens, (torch.Tensor, list)):
|
| 552 |
+
raise ValueError(
|
| 553 |
+
f"Incorrect type of indicator_tokens: {type(indicator_tokens)}"
|
| 554 |
+
)
|
| 555 |
+
if grids is None:
|
| 556 |
+
raise ValueError("Missing grids for video input")
|
| 557 |
+
|
| 558 |
+
return OXRVideoPatchInputs(
|
| 559 |
+
type="video_patches",
|
| 560 |
+
flat_data=flatten_bn(pixel_values, concat=True),
|
| 561 |
+
patches_per_item=[
|
| 562 |
+
x.shape[0] // (self.config.vit_config.hidden_stride**2)
|
| 563 |
+
for x in pixel_values
|
| 564 |
+
],
|
| 565 |
+
indicator_tokens=flatten_bn(indicator_tokens, concat=True),
|
| 566 |
+
grids=flatten_bn(grids, concat=True),
|
| 567 |
+
)
|
| 568 |
+
|
| 569 |
+
raise AssertionError("This line should be unreachable.")
|
| 570 |
+
|
| 571 |
+
def _process_visual_input(
|
| 572 |
+
self,
|
| 573 |
+
visual_input: OXRImagePatchInputs | OXRVideoPatchInputs,
|
| 574 |
+
) -> MultiModalEmbeddings:
|
| 575 |
+
image_patches_flat = visual_input["flat_data"]
|
| 576 |
+
patches_per_item = visual_input["patches_per_item"]
|
| 577 |
+
indicator_tokens = visual_input["indicator_tokens"]
|
| 578 |
+
grid_thws = visual_input["grids"]
|
| 579 |
+
|
| 580 |
+
visual_tokens = self.visual_tokenizer(
|
| 581 |
+
image_patches_flat.to(self.visual_tokenizer.dtype),
|
| 582 |
+
grid_thws,
|
| 583 |
+
)
|
| 584 |
+
visual_embeds = self.vte(visual_tokens)
|
| 585 |
+
indicator_embeds = self.vte(indicator_tokens)
|
| 586 |
+
|
| 587 |
+
visual_embeds_per_item = visual_embeds.split(patches_per_item, dim=0)
|
| 588 |
+
indicator_embeds_per_item = indicator_embeds.split(
|
| 589 |
+
[2] * len(patches_per_item)
|
| 590 |
+
)
|
| 591 |
+
|
| 592 |
+
return tuple(
|
| 593 |
+
torch.cat([indicator[:1], visual, indicator[1:]], dim=0)
|
| 594 |
+
for indicator, visual in zip(
|
| 595 |
+
indicator_embeds_per_item,
|
| 596 |
+
visual_embeds_per_item,
|
| 597 |
+
)
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
|
| 601 |
+
modalities = {}
|
| 602 |
+
for input_key in kwargs:
|
| 603 |
+
if (
|
| 604 |
+
input_key in ("pixel_values", "indicator_tokens", "grids")
|
| 605 |
+
and "images" not in modalities
|
| 606 |
+
):
|
| 607 |
+
modalities["images"] = self._parse_and_validate_image_input(**kwargs)
|
| 608 |
+
if (
|
| 609 |
+
input_key
|
| 610 |
+
in ("video_pixel_values", "video_indicator_tokens", "video_grids")
|
| 611 |
+
and "videos" not in modalities
|
| 612 |
+
):
|
| 613 |
+
modalities["videos"] = self._parse_and_validate_video_input(**kwargs)
|
| 614 |
+
return modalities
|
| 615 |
+
|
| 616 |
+
def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
|
| 617 |
+
modalities = self._parse_and_validate_multimodal_inputs(**kwargs)
|
| 618 |
+
if not modalities:
|
| 619 |
+
return []
|
| 620 |
+
|
| 621 |
+
multimodal_embeddings: tuple[torch.Tensor, ...] = ()
|
| 622 |
+
for modality in modalities:
|
| 623 |
+
if modality == "images":
|
| 624 |
+
image_input = modalities["images"]
|
| 625 |
+
multimodal_embeddings += tuple(self._process_visual_input(image_input))
|
| 626 |
+
if modality == "videos":
|
| 627 |
+
video_input = modalities["videos"]
|
| 628 |
+
multimodal_embeddings += tuple(self._process_visual_input(video_input))
|
| 629 |
+
return multimodal_embeddings
|
| 630 |
+
|
| 631 |
+
def forward(
|
| 632 |
+
self,
|
| 633 |
+
input_ids: torch.Tensor | None,
|
| 634 |
+
positions: torch.Tensor,
|
| 635 |
+
intermediate_tensors: IntermediateTensors | None = None,
|
| 636 |
+
inputs_embeds: torch.Tensor | None = None,
|
| 637 |
+
**kwargs: object,
|
| 638 |
+
) -> torch.Tensor | IntermediateTensors:
|
| 639 |
+
if intermediate_tensors is not None:
|
| 640 |
+
inputs_embeds = None
|
| 641 |
+
|
| 642 |
+
return self.llm(
|
| 643 |
+
input_ids=input_ids,
|
| 644 |
+
positions=positions,
|
| 645 |
+
intermediate_tensors=intermediate_tensors,
|
| 646 |
+
inputs_embeds=inputs_embeds,
|
| 647 |
+
)
|
| 648 |
+
|
| 649 |
+
def compute_logits(
|
| 650 |
+
self,
|
| 651 |
+
hidden_states: torch.Tensor,
|
| 652 |
+
) -> torch.Tensor | None:
|
| 653 |
+
return self.llm.compute_logits(hidden_states)
|
| 654 |
+
|
| 655 |
+
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
| 656 |
+
loader = AutoWeightsLoader(self)
|
| 657 |
+
return loader.load_weights(weights)
|
| 658 |
+
|
| 659 |
+
def get_language_model(self) -> torch.nn.Module:
|
| 660 |
+
return self.llm
|
plugins/vllm/oxr_vllm/oxr_processor.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OXR processor for the vLLM 0.25.1 plugin."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from functools import cached_property
|
| 5 |
+
from typing import Optional, Union
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import PIL.Image
|
| 9 |
+
import torch
|
| 10 |
+
from transformers import AutoProcessor, BatchFeature
|
| 11 |
+
from transformers.image_utils import ImageInput
|
| 12 |
+
from transformers.processing_utils import ProcessingKwargs, ProcessorMixin, Unpack
|
| 13 |
+
from transformers.tokenization_utils_base import PreTokenizedInput, TextInput
|
| 14 |
+
|
| 15 |
+
IMAGE_TOKEN = "<image>"
|
| 16 |
+
VIDEO_TOKEN = "<video>"
|
| 17 |
+
MIN_PIXELS = 200704
|
| 18 |
+
MAX_PIXELS = 921600
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class OXRProcessorKwargs(ProcessingKwargs, total=False): # type: ignore[call-arg]
|
| 22 |
+
_defaults = {
|
| 23 |
+
"text_kwargs": {
|
| 24 |
+
"padding": False,
|
| 25 |
+
},
|
| 26 |
+
"images_kwargs": {
|
| 27 |
+
"do_convert_rgb": True,
|
| 28 |
+
},
|
| 29 |
+
"videos_kwargs": {
|
| 30 |
+
"do_convert_rgb": True,
|
| 31 |
+
},
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class OXRProcessor(ProcessorMixin):
|
| 36 |
+
attributes = ["image_processor", "tokenizer"]
|
| 37 |
+
valid_kwargs = ["chat_template", "image_pad_token"]
|
| 38 |
+
|
| 39 |
+
image_processor_class = "AutoImageProcessor"
|
| 40 |
+
tokenizer_class = "AutoTokenizer"
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
image_processor=None,
|
| 45 |
+
tokenizer=None,
|
| 46 |
+
chat_template=None,
|
| 47 |
+
image_pad_token=None,
|
| 48 |
+
patch_size=16,
|
| 49 |
+
hidden_stride=2,
|
| 50 |
+
temporal_patch_size=1,
|
| 51 |
+
**kwargs,
|
| 52 |
+
):
|
| 53 |
+
self.image_token = IMAGE_TOKEN
|
| 54 |
+
self.video_token = VIDEO_TOKEN
|
| 55 |
+
self.image_pad_token = image_pad_token or "<|image_pad|>"
|
| 56 |
+
self.patch_size = patch_size
|
| 57 |
+
self.hidden_stride = hidden_stride
|
| 58 |
+
self.temporal_patch_size = temporal_patch_size
|
| 59 |
+
super().__init__(image_processor, tokenizer, chat_template=chat_template)
|
| 60 |
+
|
| 61 |
+
@cached_property
|
| 62 |
+
def extra_special_tokens(self):
|
| 63 |
+
image_pad_token_id = self.tokenizer.get_vocab()[self.image_pad_token]
|
| 64 |
+
return {
|
| 65 |
+
"image_token": -200,
|
| 66 |
+
"video_token": -201,
|
| 67 |
+
"visual_atom": -300,
|
| 68 |
+
"image_start": -301,
|
| 69 |
+
"image_end": -302,
|
| 70 |
+
"video_start": -303,
|
| 71 |
+
"video_end": -304,
|
| 72 |
+
"image_pad": image_pad_token_id,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
def __call__(
|
| 76 |
+
self,
|
| 77 |
+
images: ImageInput = None,
|
| 78 |
+
videos: Union[np.ndarray, list[ImageInput]] = None,
|
| 79 |
+
text: Union[
|
| 80 |
+
TextInput,
|
| 81 |
+
PreTokenizedInput,
|
| 82 |
+
list[TextInput],
|
| 83 |
+
list[PreTokenizedInput],
|
| 84 |
+
] = None,
|
| 85 |
+
**kwargs: Unpack[OXRProcessorKwargs],
|
| 86 |
+
) -> BatchFeature:
|
| 87 |
+
output_kwargs = self._merge_kwargs(
|
| 88 |
+
OXRProcessorKwargs,
|
| 89 |
+
tokenizer_init_kwargs=self.tokenizer.init_kwargs,
|
| 90 |
+
**kwargs,
|
| 91 |
+
)
|
| 92 |
+
visual_features = {}
|
| 93 |
+
output = BatchFeature()
|
| 94 |
+
|
| 95 |
+
if images is not None:
|
| 96 |
+
processed_images = []
|
| 97 |
+
image_placeholders_list = []
|
| 98 |
+
grids = []
|
| 99 |
+
for image in images if isinstance(images, list) else [images]:
|
| 100 |
+
pixel_values, image_placeholders, grid = self.preprocess_multidata(
|
| 101 |
+
images=image,
|
| 102 |
+
**output_kwargs["images_kwargs"],
|
| 103 |
+
)
|
| 104 |
+
processed_images.append(pixel_values)
|
| 105 |
+
image_placeholders_list.append(image_placeholders)
|
| 106 |
+
grids.append(grid)
|
| 107 |
+
|
| 108 |
+
if processed_images:
|
| 109 |
+
visual_features["image_placeholders"] = image_placeholders_list
|
| 110 |
+
output["pixel_values"] = processed_images
|
| 111 |
+
output["grids"] = grids
|
| 112 |
+
|
| 113 |
+
if videos is not None:
|
| 114 |
+
processed_videos = []
|
| 115 |
+
video_placeholders_list = []
|
| 116 |
+
grids = []
|
| 117 |
+
for video in videos if isinstance(videos, list) else [videos]:
|
| 118 |
+
pixel_values, video_placeholders, grid = self.preprocess_multidata(
|
| 119 |
+
video=video,
|
| 120 |
+
**output_kwargs["videos_kwargs"],
|
| 121 |
+
)
|
| 122 |
+
processed_videos.append(pixel_values)
|
| 123 |
+
video_placeholders_list.append(video_placeholders)
|
| 124 |
+
grids.append(grid)
|
| 125 |
+
|
| 126 |
+
if processed_videos:
|
| 127 |
+
visual_features["video_placeholders"] = video_placeholders_list
|
| 128 |
+
output["video_pixel_values"] = processed_videos
|
| 129 |
+
output["video_grids"] = grids
|
| 130 |
+
|
| 131 |
+
if text is None:
|
| 132 |
+
return BatchFeature(data=visual_features)
|
| 133 |
+
|
| 134 |
+
if not isinstance(text, list):
|
| 135 |
+
text = [text]
|
| 136 |
+
tokenized_batched_text = self._tokenize_with_visual_symbol(text)
|
| 137 |
+
image_token_id = self.get_token_value("image_token")
|
| 138 |
+
video_token_id = self.get_token_value("video_token")
|
| 139 |
+
replaced_ids_list = []
|
| 140 |
+
image_idx = 0
|
| 141 |
+
video_idx = 0
|
| 142 |
+
for ids_tensor in tokenized_batched_text:
|
| 143 |
+
has_image_tokens = (
|
| 144 |
+
image_token_id in ids_tensor
|
| 145 |
+
and "image_placeholders" in visual_features
|
| 146 |
+
and image_idx < len(visual_features["image_placeholders"])
|
| 147 |
+
)
|
| 148 |
+
has_video_tokens = (
|
| 149 |
+
video_token_id in ids_tensor
|
| 150 |
+
and "video_placeholders" in visual_features
|
| 151 |
+
and video_idx < len(visual_features["video_placeholders"])
|
| 152 |
+
)
|
| 153 |
+
if has_image_tokens or has_video_tokens:
|
| 154 |
+
new_ids = []
|
| 155 |
+
for token_id in ids_tensor.tolist():
|
| 156 |
+
if token_id == image_token_id:
|
| 157 |
+
new_ids.extend(visual_features["image_placeholders"][image_idx])
|
| 158 |
+
image_idx += 1
|
| 159 |
+
elif token_id == video_token_id:
|
| 160 |
+
new_ids.extend(visual_features["video_placeholders"][video_idx])
|
| 161 |
+
video_idx += 1
|
| 162 |
+
else:
|
| 163 |
+
new_ids.append(token_id)
|
| 164 |
+
ids_tensor = torch.tensor(new_ids, dtype=torch.long)
|
| 165 |
+
replaced_ids_list.append(ids_tensor)
|
| 166 |
+
|
| 167 |
+
if replaced_ids_list:
|
| 168 |
+
output["input_ids"] = torch.stack(replaced_ids_list)
|
| 169 |
+
else:
|
| 170 |
+
output["input_ids"] = torch.tensor([], dtype=torch.long)
|
| 171 |
+
return output
|
| 172 |
+
|
| 173 |
+
def _tokenize_with_visual_symbol(self, text_list: list[str]) -> torch.LongTensor:
|
| 174 |
+
batch_token_ids = []
|
| 175 |
+
for text in text_list:
|
| 176 |
+
token_ids = []
|
| 177 |
+
video_token_id = self.get_token_value("video_token")
|
| 178 |
+
image_token_id = self.get_token_value("image_token")
|
| 179 |
+
video_split_texts = text.split(self.video_token)
|
| 180 |
+
|
| 181 |
+
for j, video_segment in enumerate(video_split_texts):
|
| 182 |
+
image_split_texts = video_segment.split(self.image_token)
|
| 183 |
+
text_chunks = [
|
| 184 |
+
self.tokenizer(chunk, add_special_tokens=False).input_ids
|
| 185 |
+
for chunk in image_split_texts
|
| 186 |
+
]
|
| 187 |
+
segment_tokens = []
|
| 188 |
+
for i, chunk in enumerate(text_chunks):
|
| 189 |
+
segment_tokens.extend(chunk)
|
| 190 |
+
if i < len(text_chunks) - 1:
|
| 191 |
+
segment_tokens.append(image_token_id)
|
| 192 |
+
token_ids.extend(segment_tokens)
|
| 193 |
+
if j < len(video_split_texts) - 1:
|
| 194 |
+
token_ids.append(video_token_id)
|
| 195 |
+
batch_token_ids.append(token_ids)
|
| 196 |
+
return torch.tensor(batch_token_ids, dtype=torch.long)
|
| 197 |
+
|
| 198 |
+
def smart_resize(
|
| 199 |
+
self,
|
| 200 |
+
height: int,
|
| 201 |
+
width: int,
|
| 202 |
+
factor: int = 32,
|
| 203 |
+
min_pixels: int = MIN_PIXELS,
|
| 204 |
+
max_pixels: int = MAX_PIXELS,
|
| 205 |
+
):
|
| 206 |
+
if height < factor or width < factor:
|
| 207 |
+
if height < width:
|
| 208 |
+
width = round(factor / height * width)
|
| 209 |
+
height = factor
|
| 210 |
+
else:
|
| 211 |
+
height = round(factor / width * height)
|
| 212 |
+
width = factor
|
| 213 |
+
elif max(height, width) / min(height, width) > 200:
|
| 214 |
+
if height > width:
|
| 215 |
+
height = 200 * width
|
| 216 |
+
else:
|
| 217 |
+
width = 200 * height
|
| 218 |
+
|
| 219 |
+
h_bar = round(height / factor) * factor
|
| 220 |
+
w_bar = round(width / factor) * factor
|
| 221 |
+
if h_bar * w_bar > max_pixels:
|
| 222 |
+
beta = math.sqrt((height * width) / max_pixels)
|
| 223 |
+
h_bar = math.floor(height / beta / factor) * factor
|
| 224 |
+
w_bar = math.floor(width / beta / factor) * factor
|
| 225 |
+
elif h_bar * w_bar < min_pixels:
|
| 226 |
+
beta = math.sqrt(min_pixels / (height * width))
|
| 227 |
+
h_bar = math.ceil(height * beta / factor) * factor
|
| 228 |
+
w_bar = math.ceil(width * beta / factor) * factor
|
| 229 |
+
return h_bar, w_bar
|
| 230 |
+
|
| 231 |
+
def get_token_value(self, token_name: str):
|
| 232 |
+
return self.extra_special_tokens[token_name]
|
| 233 |
+
|
| 234 |
+
def construct_visual_indicators(self, grid, is_video: bool = False):
|
| 235 |
+
if is_video:
|
| 236 |
+
start_token = self.get_token_value("video_start")
|
| 237 |
+
end_token = self.get_token_value("video_end")
|
| 238 |
+
else:
|
| 239 |
+
start_token = self.get_token_value("image_start")
|
| 240 |
+
end_token = self.get_token_value("image_end")
|
| 241 |
+
|
| 242 |
+
visual_indicators = [start_token, self.get_token_value("visual_atom")]
|
| 243 |
+
if grid[0] * grid[1] > 1:
|
| 244 |
+
for _ in range(grid[0]):
|
| 245 |
+
for _ in range(grid[1]):
|
| 246 |
+
visual_indicators.append(self.get_token_value("visual_atom"))
|
| 247 |
+
visual_indicators.append(end_token)
|
| 248 |
+
return visual_indicators
|
| 249 |
+
|
| 250 |
+
def construct_visual_placeholders(self, grid, is_video: bool = False):
|
| 251 |
+
visual_placeholders = self.construct_visual_indicators((1, 1), is_video)
|
| 252 |
+
visual_atom_token_id = self.get_token_value("visual_atom")
|
| 253 |
+
image_padding_token_id = self.get_token_value("image_pad")
|
| 254 |
+
|
| 255 |
+
num_image_atoms = grid[0] * grid[1] * grid[2]
|
| 256 |
+
num_image_atoms //= self.hidden_stride**2
|
| 257 |
+
num_image_atoms //= self.temporal_patch_size
|
| 258 |
+
|
| 259 |
+
vision_start_token_id = self.tokenizer.get_vocab().get(
|
| 260 |
+
"<|vision_start|>",
|
| 261 |
+
image_padding_token_id,
|
| 262 |
+
)
|
| 263 |
+
vision_end_token_id = self.tokenizer.get_vocab().get(
|
| 264 |
+
"<|vision_end|>",
|
| 265 |
+
image_padding_token_id,
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
padded_placeholder_tokens = []
|
| 269 |
+
for token in visual_placeholders:
|
| 270 |
+
if token == visual_atom_token_id:
|
| 271 |
+
padded_placeholder_tokens.extend(
|
| 272 |
+
[image_padding_token_id] * num_image_atoms
|
| 273 |
+
)
|
| 274 |
+
elif token in (
|
| 275 |
+
self.get_token_value("image_start"),
|
| 276 |
+
self.get_token_value("video_start"),
|
| 277 |
+
):
|
| 278 |
+
padded_placeholder_tokens.append(vision_start_token_id)
|
| 279 |
+
elif token in (
|
| 280 |
+
self.get_token_value("image_end"),
|
| 281 |
+
self.get_token_value("video_end"),
|
| 282 |
+
):
|
| 283 |
+
padded_placeholder_tokens.append(vision_end_token_id)
|
| 284 |
+
else:
|
| 285 |
+
padded_placeholder_tokens.append(image_padding_token_id)
|
| 286 |
+
return padded_placeholder_tokens
|
| 287 |
+
|
| 288 |
+
def preprocess_multidata(
|
| 289 |
+
self,
|
| 290 |
+
images: Optional[Union[PIL.Image.Image, list[PIL.Image.Image]]] = None,
|
| 291 |
+
video: Optional[Union[list[PIL.Image.Image], np.ndarray]] = None,
|
| 292 |
+
do_convert_rgb: Optional[bool] = True,
|
| 293 |
+
convert_to_rgb: Optional[bool] = None,
|
| 294 |
+
min_pixels: int = MIN_PIXELS,
|
| 295 |
+
max_pixels: int = MAX_PIXELS,
|
| 296 |
+
return_tensors: Optional[str] = "pt",
|
| 297 |
+
):
|
| 298 |
+
if convert_to_rgb is not None:
|
| 299 |
+
do_convert_rgb = convert_to_rgb
|
| 300 |
+
|
| 301 |
+
is_video = False
|
| 302 |
+
if images is not None:
|
| 303 |
+
if not isinstance(images, list):
|
| 304 |
+
images = [images]
|
| 305 |
+
elif video is not None:
|
| 306 |
+
is_video = True
|
| 307 |
+
if isinstance(video, np.ndarray):
|
| 308 |
+
images = [
|
| 309 |
+
PIL.Image.fromarray(video[i].astype(np.uint8))
|
| 310 |
+
for i in range(video.shape[0])
|
| 311 |
+
]
|
| 312 |
+
elif isinstance(video, list):
|
| 313 |
+
images = video
|
| 314 |
+
else:
|
| 315 |
+
raise ValueError("Expected either images or video")
|
| 316 |
+
|
| 317 |
+
min_pixels = min(
|
| 318 |
+
max_pixels if max_pixels is not None else MAX_PIXELS,
|
| 319 |
+
min_pixels if min_pixels is not None else MIN_PIXELS,
|
| 320 |
+
)
|
| 321 |
+
images = [
|
| 322 |
+
image.convert("RGB") if do_convert_rgb and image.mode != "RGB" else image
|
| 323 |
+
for image in images
|
| 324 |
+
]
|
| 325 |
+
|
| 326 |
+
width, height = images[0].size
|
| 327 |
+
resized_height, resized_width = height, width
|
| 328 |
+
processed_images = []
|
| 329 |
+
for image in images:
|
| 330 |
+
resized_height, resized_width = self.smart_resize(
|
| 331 |
+
height,
|
| 332 |
+
width,
|
| 333 |
+
factor=self.patch_size * self.hidden_stride,
|
| 334 |
+
min_pixels=min_pixels,
|
| 335 |
+
max_pixels=max_pixels,
|
| 336 |
+
)
|
| 337 |
+
image_pt = self.image_processor.preprocess(
|
| 338 |
+
image,
|
| 339 |
+
size=dict(height=resized_height, width=resized_width),
|
| 340 |
+
return_tensors="np",
|
| 341 |
+
)["pixel_values"][0]
|
| 342 |
+
processed_images.append(image_pt)
|
| 343 |
+
|
| 344 |
+
patches = np.array(processed_images)
|
| 345 |
+
frame_remainder = patches.shape[0] % self.temporal_patch_size
|
| 346 |
+
if frame_remainder != 0:
|
| 347 |
+
num_to_pad = self.temporal_patch_size - frame_remainder
|
| 348 |
+
repeats = np.repeat(patches[-1][np.newaxis], num_to_pad, axis=0)
|
| 349 |
+
patches = np.concatenate([patches, repeats], axis=0)
|
| 350 |
+
|
| 351 |
+
channel = patches.shape[1]
|
| 352 |
+
grid_t = patches.shape[0] // self.temporal_patch_size
|
| 353 |
+
grid_h = resized_height // self.patch_size
|
| 354 |
+
grid_w = resized_width // self.patch_size
|
| 355 |
+
|
| 356 |
+
patches = patches.reshape(
|
| 357 |
+
grid_t,
|
| 358 |
+
self.temporal_patch_size,
|
| 359 |
+
channel,
|
| 360 |
+
grid_h // self.hidden_stride,
|
| 361 |
+
self.hidden_stride,
|
| 362 |
+
self.patch_size,
|
| 363 |
+
grid_w // self.hidden_stride,
|
| 364 |
+
self.hidden_stride,
|
| 365 |
+
self.patch_size,
|
| 366 |
+
)
|
| 367 |
+
patches = patches.transpose(0, 3, 6, 4, 7, 2, 1, 5, 8)
|
| 368 |
+
flatten_patches = patches.reshape(
|
| 369 |
+
grid_t * grid_h * grid_w,
|
| 370 |
+
channel * self.temporal_patch_size * self.patch_size * self.patch_size,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
visual_placeholders = self.construct_visual_placeholders(
|
| 374 |
+
[grid_t, grid_h, grid_w],
|
| 375 |
+
is_video,
|
| 376 |
+
)
|
| 377 |
+
return (
|
| 378 |
+
torch.tensor(flatten_patches),
|
| 379 |
+
visual_placeholders,
|
| 380 |
+
torch.tensor([[grid_t, grid_h, grid_w]]),
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
AutoProcessor.register("OXRProcessor", OXRProcessor)
|
plugins/vllm/pyproject.toml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=69", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "oxr-vllm-plugin"
|
| 7 |
+
version = "0.2.0"
|
| 8 |
+
description = "Out-of-tree OXR model plugin for vLLM 0.25.1"
|
| 9 |
+
requires-python = ">=3.12"
|
| 10 |
+
dependencies = ["vllm==0.25.1", "transformers==5.13.1"]
|
| 11 |
+
|
| 12 |
+
[project.entry-points."vllm.general_plugins"]
|
| 13 |
+
oxr = "oxr_vllm:register"
|
| 14 |
+
|
| 15 |
+
[tool.setuptools.packages.find]
|
| 16 |
+
include = ["oxr_vllm", "oxr_vllm.*"]
|
preprocessor_config.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"do_convert_rgb": null,
|
| 3 |
+
"do_normalize": true,
|
| 4 |
+
"do_rescale": true,
|
| 5 |
+
"do_resize": true,
|
| 6 |
+
"image_mean": [
|
| 7 |
+
0.5,
|
| 8 |
+
0.5,
|
| 9 |
+
0.5
|
| 10 |
+
],
|
| 11 |
+
"image_processor_type": "SiglipImageProcessor",
|
| 12 |
+
"image_std": [
|
| 13 |
+
0.5,
|
| 14 |
+
0.5,
|
| 15 |
+
0.5
|
| 16 |
+
],
|
| 17 |
+
"processor_class": "SiglipProcessor",
|
| 18 |
+
"resample": 2,
|
| 19 |
+
"rescale_factor": 0.00392156862745098,
|
| 20 |
+
"size": {
|
| 21 |
+
"height": 512,
|
| 22 |
+
"width": 512
|
| 23 |
+
}
|
| 24 |
+
}
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
],
|
| 17 |
+
"eos_token": {
|
| 18 |
+
"content": "<|endoftext|>",
|
| 19 |
+
"lstrip": false,
|
| 20 |
+
"normalized": false,
|
| 21 |
+
"rstrip": false,
|
| 22 |
+
"single_word": false
|
| 23 |
+
},
|
| 24 |
+
"pad_token": {
|
| 25 |
+
"content": "<|endoftext|>",
|
| 26 |
+
"lstrip": false,
|
| 27 |
+
"normalized": false,
|
| 28 |
+
"rstrip": false,
|
| 29 |
+
"single_word": false
|
| 30 |
+
}
|
| 31 |
+
}
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4
|
| 3 |
+
size 11422654
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"151643": {
|
| 6 |
+
"content": "<|endoftext|>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"151644": {
|
| 14 |
+
"content": "<|im_start|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"151645": {
|
| 22 |
+
"content": "<|im_end|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": true
|
| 28 |
+
},
|
| 29 |
+
"151646": {
|
| 30 |
+
"content": "<|object_ref_start|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": true
|
| 36 |
+
},
|
| 37 |
+
"151647": {
|
| 38 |
+
"content": "<|object_ref_end|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": true
|
| 44 |
+
},
|
| 45 |
+
"151648": {
|
| 46 |
+
"content": "<|box_start|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": true
|
| 52 |
+
},
|
| 53 |
+
"151649": {
|
| 54 |
+
"content": "<|box_end|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": true
|
| 60 |
+
},
|
| 61 |
+
"151650": {
|
| 62 |
+
"content": "<|quad_start|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": true
|
| 68 |
+
},
|
| 69 |
+
"151651": {
|
| 70 |
+
"content": "<|quad_end|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"151652": {
|
| 78 |
+
"content": "<|vision_start|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"151653": {
|
| 86 |
+
"content": "<|vision_end|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"151654": {
|
| 94 |
+
"content": "<|vision_pad|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"151655": {
|
| 102 |
+
"content": "<|image_pad|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"151656": {
|
| 110 |
+
"content": "<|video_pad|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"151657": {
|
| 118 |
+
"content": "<tool_call>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": false
|
| 124 |
+
},
|
| 125 |
+
"151658": {
|
| 126 |
+
"content": "</tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"151659": {
|
| 134 |
+
"content": "<|fim_prefix|>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"151660": {
|
| 142 |
+
"content": "<|fim_middle|>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"151661": {
|
| 150 |
+
"content": "<|fim_suffix|>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"151662": {
|
| 158 |
+
"content": "<|fim_pad|>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"151663": {
|
| 166 |
+
"content": "<|repo_name|>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": false
|
| 172 |
+
},
|
| 173 |
+
"151664": {
|
| 174 |
+
"content": "<|file_sep|>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": false
|
| 180 |
+
},
|
| 181 |
+
"151665": {
|
| 182 |
+
"content": "<tool_response>",
|
| 183 |
+
"lstrip": false,
|
| 184 |
+
"normalized": false,
|
| 185 |
+
"rstrip": false,
|
| 186 |
+
"single_word": false,
|
| 187 |
+
"special": false
|
| 188 |
+
},
|
| 189 |
+
"151666": {
|
| 190 |
+
"content": "</tool_response>",
|
| 191 |
+
"lstrip": false,
|
| 192 |
+
"normalized": false,
|
| 193 |
+
"rstrip": false,
|
| 194 |
+
"single_word": false,
|
| 195 |
+
"special": false
|
| 196 |
+
},
|
| 197 |
+
"151667": {
|
| 198 |
+
"content": "<think>",
|
| 199 |
+
"lstrip": false,
|
| 200 |
+
"normalized": false,
|
| 201 |
+
"rstrip": false,
|
| 202 |
+
"single_word": false,
|
| 203 |
+
"special": false
|
| 204 |
+
},
|
| 205 |
+
"151668": {
|
| 206 |
+
"content": "</think>",
|
| 207 |
+
"lstrip": false,
|
| 208 |
+
"normalized": false,
|
| 209 |
+
"rstrip": false,
|
| 210 |
+
"single_word": false,
|
| 211 |
+
"special": false
|
| 212 |
+
}
|
| 213 |
+
},
|
| 214 |
+
"additional_special_tokens": [
|
| 215 |
+
"<|im_start|>",
|
| 216 |
+
"<|im_end|>",
|
| 217 |
+
"<|object_ref_start|>",
|
| 218 |
+
"<|object_ref_end|>",
|
| 219 |
+
"<|box_start|>",
|
| 220 |
+
"<|box_end|>",
|
| 221 |
+
"<|quad_start|>",
|
| 222 |
+
"<|quad_end|>",
|
| 223 |
+
"<|vision_start|>",
|
| 224 |
+
"<|vision_end|>",
|
| 225 |
+
"<|vision_pad|>",
|
| 226 |
+
"<|image_pad|>",
|
| 227 |
+
"<|video_pad|>"
|
| 228 |
+
],
|
| 229 |
+
"bos_token": null,
|
| 230 |
+
"clean_up_tokenization_spaces": false,
|
| 231 |
+
"eos_token": "<|endoftext|>",
|
| 232 |
+
"errors": "replace",
|
| 233 |
+
"extra_special_tokens": {},
|
| 234 |
+
"model_max_length": 131072,
|
| 235 |
+
"pad_token": "<|endoftext|>",
|
| 236 |
+
"processor_class": "SiglipProcessor",
|
| 237 |
+
"split_special_tokens": false,
|
| 238 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 239 |
+
"unk_token": null
|
| 240 |
+
}
|
vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|