onnx-diagnostic 0.8.5__py3-none-any.whl → 0.8.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- onnx_diagnostic/__init__.py +1 -1
- onnx_diagnostic/_command_lines_parser.py +46 -0
- onnx_diagnostic/ci_models/__init__.py +0 -0
- onnx_diagnostic/ci_models/ci_helpers.py +430 -0
- onnx_diagnostic/ci_models/export_qwen25_vl.py +560 -0
- onnx_diagnostic/export/cf_simple_loop_for.py +352 -0
- onnx_diagnostic/export/control_flow_onnx.py +23 -17
- onnx_diagnostic/ext_test_case.py +14 -0
- onnx_diagnostic/torch_export_patches/onnx_export_errors.py +16 -0
- onnx_diagnostic/torch_export_patches/patches/_patch_transformers_qwen2_5.py +75 -3
- onnx_diagnostic/torch_export_patches/patches/patch_transformers.py +1 -0
- onnx_diagnostic/torch_models/hghub/hub_data_cached_configs.py +29 -8
- onnx_diagnostic/torch_onnx/compare.py +357 -0
- {onnx_diagnostic-0.8.5.dist-info → onnx_diagnostic-0.8.6.dist-info}/METADATA +1 -1
- {onnx_diagnostic-0.8.5.dist-info → onnx_diagnostic-0.8.6.dist-info}/RECORD +18 -15
- onnx_diagnostic/export/control_flow.py +0 -214
- onnx_diagnostic/export/control_flow_research.py +0 -140
- {onnx_diagnostic-0.8.5.dist-info → onnx_diagnostic-0.8.6.dist-info}/WHEEL +0 -0
- {onnx_diagnostic-0.8.5.dist-info → onnx_diagnostic-0.8.6.dist-info}/licenses/LICENSE.txt +0 -0
- {onnx_diagnostic-0.8.5.dist-info → onnx_diagnostic-0.8.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Export visual and embedding parts of Qwen/Qwen2.5-VL-7B-Instruct
|
|
3
|
+
================================================================
|
|
4
|
+
|
|
5
|
+
Requirements
|
|
6
|
+
++++++++++++
|
|
7
|
+
|
|
8
|
+
::
|
|
9
|
+
|
|
10
|
+
git+https://github.com/sdpython/experimental-experiment.git # optional
|
|
11
|
+
huggingface_hub>=1.2.1
|
|
12
|
+
onnx-diagnostic>=0.8.6
|
|
13
|
+
onnxruntime>=1.23
|
|
14
|
+
torch>=2.9 # weekly is better
|
|
15
|
+
tqdm
|
|
16
|
+
transformers>=4.57
|
|
17
|
+
|
|
18
|
+
Examples
|
|
19
|
+
++++++++
|
|
20
|
+
|
|
21
|
+
.. code-block:: bash
|
|
22
|
+
|
|
23
|
+
python -m onnx_diagnostic.ci_models.export_qwen25_vl \
|
|
24
|
+
-m Qwen/Qwen2.5-VL-7B-Instruct \
|
|
25
|
+
--device cpu --dtype float32 --exporter onnx-dynamo --pretrained --second-input --zip
|
|
26
|
+
|
|
27
|
+
To choose a specific Attention schema:
|
|
28
|
+
|
|
29
|
+
.. code-block:: bash
|
|
30
|
+
|
|
31
|
+
QWEN25ATTENTION=LOOPMHA python -m onnx_diagnostic.ci_models.export_qwen25_vl \
|
|
32
|
+
-m Qwen/Qwen2.5-VL-7B-Instruct \
|
|
33
|
+
--device cpu --dtype float32 --exporter onnx-dynamo --pretrained --second-input --zip
|
|
34
|
+
|
|
35
|
+
Cheat sheet for tar commands. To make a tar:
|
|
36
|
+
``tar -czvf model.tar.gz model.onnx model.data``
|
|
37
|
+
And to untar:
|
|
38
|
+
``tar -xzvf model.tar.gz``.
|
|
39
|
+
|
|
40
|
+
Rewritings
|
|
41
|
+
++++++++++
|
|
42
|
+
|
|
43
|
+
* `overview <https://sdpython.github.io/doc/onnx-diagnostic/dev/status/patches_diff.html#auto-patch-transformers-qwen2-5-vlforconditionalgeneration-prepare-inputs-for-generation-patched-qwen2-5-vlforconditionalgeneration-prepare-inputs-for-generation>`_
|
|
44
|
+
* code: `_patch_transformers_qwen2_5.py <https://github.com/sdpython/onnx-diagnostic/blob/main/onnx_diagnostic/torch_export_patches/patches/_patch_transformers_qwen2_5.py>`_
|
|
45
|
+
|
|
46
|
+
Attention
|
|
47
|
+
+++++++++
|
|
48
|
+
|
|
49
|
+
The attention is either implemented with ``MultiHeadAttention`` in a loop,
|
|
50
|
+
either with ``PackedMultiHeadAttention``. The choice is made based on the device.
|
|
51
|
+
It is possible to overwrite this by by setting environment variable
|
|
52
|
+
``QWEN25ATTENTION`` to:
|
|
53
|
+
|
|
54
|
+
* ``PACKED``: PackedMultiHeadAttention
|
|
55
|
+
* ``LOOPMHA``: Loop over MultiHeadAttention
|
|
56
|
+
* ``LOOPA23``: Loop over Attention(23), needs opset 23+.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
import os
|
|
60
|
+
import sys
|
|
61
|
+
import time
|
|
62
|
+
from typing import Any, Dict, List, Tuple
|
|
63
|
+
from .ci_helpers import (
|
|
64
|
+
check_for_discrepancies_and_log_everything_into_a_json_file,
|
|
65
|
+
compute_expected_outputs,
|
|
66
|
+
get_parser,
|
|
67
|
+
get_torch_dtype_from_command_line_args,
|
|
68
|
+
remove_inplace_body_last_input_output_type_for_loop_because_they_might_be_sequences,
|
|
69
|
+
simplify_model_id_for_a_filename,
|
|
70
|
+
zip_model_and_data_into_a_single_file,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_untrained_model(model_id: str, second_input: bool, verbose: int) -> Dict[str, Any]:
|
|
75
|
+
"""
|
|
76
|
+
Returns an untrained model.
|
|
77
|
+
|
|
78
|
+
:param model_id: model id
|
|
79
|
+
:param second_input: second input set
|
|
80
|
+
:param verbose: verbosity
|
|
81
|
+
:return: model and data
|
|
82
|
+
"""
|
|
83
|
+
from ..torch_models.hghub.model_inputs import get_untrained_model_with_inputs
|
|
84
|
+
|
|
85
|
+
if model_id == "arnir0/Tiny-LLM":
|
|
86
|
+
# used to run a unit test
|
|
87
|
+
_config_reduction = None
|
|
88
|
+
else:
|
|
89
|
+
|
|
90
|
+
def _config_reduction(config, task):
|
|
91
|
+
return {
|
|
92
|
+
# "num_hidden_layers": 2,
|
|
93
|
+
"vision_config": {"depth": 2},
|
|
94
|
+
"text_config": {
|
|
95
|
+
"num_hidden_layers": 2,
|
|
96
|
+
"layer_types": ["full_attention", "full_attention"],
|
|
97
|
+
},
|
|
98
|
+
# "_attn_implementation": "flash_attention_2",
|
|
99
|
+
"_attn_implementation": "sdpa",
|
|
100
|
+
"dtype": "float16",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
config_reduction = _config_reduction
|
|
104
|
+
data = get_untrained_model_with_inputs(
|
|
105
|
+
model_id,
|
|
106
|
+
verbose=verbose,
|
|
107
|
+
add_second_input=second_input,
|
|
108
|
+
config_reduction=config_reduction,
|
|
109
|
+
)
|
|
110
|
+
return data
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_inputs_for_part(
|
|
114
|
+
part: str,
|
|
115
|
+
torch_dtype: "torch.dtype", # noqa: F821
|
|
116
|
+
device: str,
|
|
117
|
+
second_input: bool,
|
|
118
|
+
image_token_id: int,
|
|
119
|
+
bos_token_id: int,
|
|
120
|
+
eos_token_id: int,
|
|
121
|
+
) -> Tuple[Dict[str, "torch.Tensor"], List[Dict[str, "torch.Tensor"]]]: # noqa: F821
|
|
122
|
+
import torch
|
|
123
|
+
|
|
124
|
+
if part == "visual":
|
|
125
|
+
export_inputs = dict(
|
|
126
|
+
pixel_values=torch.randn((1292, 1176), dtype=torch_dtype).to(device),
|
|
127
|
+
image_grid_thw=torch.tensor([[1, 34, 38]], dtype=torch.int64).to(device),
|
|
128
|
+
)
|
|
129
|
+
other_inputs = []
|
|
130
|
+
if second_input:
|
|
131
|
+
other_inputs = [
|
|
132
|
+
dict(
|
|
133
|
+
pixel_values=torch.randn((1292, 1176), dtype=torch_dtype).to(device),
|
|
134
|
+
image_grid_thw=torch.tensor([[1, 34, 38]], dtype=torch.int64).to(device),
|
|
135
|
+
),
|
|
136
|
+
dict(
|
|
137
|
+
pixel_values=torch.rand((1292, 1176), dtype=torch_dtype).to(device),
|
|
138
|
+
image_grid_thw=torch.tensor([[1, 34, 38]], dtype=torch.int64).to(device),
|
|
139
|
+
),
|
|
140
|
+
dict(
|
|
141
|
+
pixel_values=torch.randn((14308, 1176), dtype=torch_dtype).to(device),
|
|
142
|
+
image_grid_thw=torch.tensor([[1, 98, 146]], dtype=torch.int64).to(device),
|
|
143
|
+
),
|
|
144
|
+
dict(
|
|
145
|
+
pixel_values=torch.rand((14308, 1176), dtype=torch_dtype).to(device),
|
|
146
|
+
image_grid_thw=torch.tensor([[1, 98, 146]], dtype=torch.int64).to(device),
|
|
147
|
+
),
|
|
148
|
+
]
|
|
149
|
+
return export_inputs, other_inputs
|
|
150
|
+
|
|
151
|
+
if part == "embedding":
|
|
152
|
+
|
|
153
|
+
def fix(inputs):
|
|
154
|
+
img_start_index = 3
|
|
155
|
+
img_end_index = img_start_index + patches_per_image # 3 + 3577 = 3580
|
|
156
|
+
|
|
157
|
+
# Fill in with image token index
|
|
158
|
+
inputs["input_ids"][0][2] = bos_token_id # <start_of_image>
|
|
159
|
+
inputs["input_ids"][0][img_start_index:img_end_index] = image_token_id # <image>
|
|
160
|
+
inputs["input_ids"][0][img_end_index] = eos_token_id # <end_of_image>
|
|
161
|
+
|
|
162
|
+
inputs["input_ids"][1][2] = bos_token_id # <start_of_image>
|
|
163
|
+
inputs["input_ids"][1][img_start_index:img_end_index] = image_token_id # <image>
|
|
164
|
+
inputs["input_ids"][1][img_end_index] = eos_token_id # <end_of_image>
|
|
165
|
+
return inputs
|
|
166
|
+
|
|
167
|
+
batch_size, sequence_length, patches_per_image, out_hidden_size = (2, 3606, 3577, 3584)
|
|
168
|
+
num_logical_patches = batch_size * patches_per_image
|
|
169
|
+
|
|
170
|
+
def draw():
|
|
171
|
+
return {
|
|
172
|
+
"input_ids": torch.randint(
|
|
173
|
+
low=0,
|
|
174
|
+
high=image_token_id,
|
|
175
|
+
size=(batch_size, sequence_length),
|
|
176
|
+
dtype=torch.int64,
|
|
177
|
+
).to(device),
|
|
178
|
+
"image_features": torch.randn(
|
|
179
|
+
num_logical_patches, out_hidden_size, dtype=torch_dtype
|
|
180
|
+
).to(device),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return fix(draw()), ([fix(draw()), fix(draw())] if second_input else [])
|
|
184
|
+
|
|
185
|
+
raise NotImplementedError(f"No inputs yet implement for part={part!r}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main(
|
|
189
|
+
model_id: str = "Qwen/Qwen2.5-VL-7B-Instruct",
|
|
190
|
+
device: str = "cpu",
|
|
191
|
+
dtype: str = "float32",
|
|
192
|
+
exporter: str = "onnx-dynamo",
|
|
193
|
+
pretrained: bool = True,
|
|
194
|
+
second_input: bool = True,
|
|
195
|
+
make_zip: bool = False,
|
|
196
|
+
output_folder: str = "dump_models",
|
|
197
|
+
existing_onnx: str | None = None,
|
|
198
|
+
part: str = "visual",
|
|
199
|
+
atol: float = 0.01,
|
|
200
|
+
mismatch01: float = 0.1,
|
|
201
|
+
profile_exporter: bool = False,
|
|
202
|
+
):
|
|
203
|
+
"""
|
|
204
|
+
Exports model Qwen/Qwen2.5-VL-7B-Instruct or pieces of it.
|
|
205
|
+
The script applies as well to other models based on the same architecture.
|
|
206
|
+
|
|
207
|
+
The function saves everything on disk. It does not generate new inputs
|
|
208
|
+
on the second run but reuses the saved ones. Same goes for the expected
|
|
209
|
+
outputs with are also saved on disk.
|
|
210
|
+
|
|
211
|
+
:param model_id: model id
|
|
212
|
+
:param device: device
|
|
213
|
+
:param dtype: dtype
|
|
214
|
+
:param exporter: exportor to use
|
|
215
|
+
:param pretrained: pretrained=False is usually used to test
|
|
216
|
+
:param second_input: checks discrepancies on more examples
|
|
217
|
+
:param make_zip: creates a zip at the end
|
|
218
|
+
:param output_folder: output folder
|
|
219
|
+
:param part: "" to export the whole model, ``"visual"`` for visual part,
|
|
220
|
+
``"embedding"`` for the embedding part
|
|
221
|
+
:param atol: raises an exception if tolerance is above that threshold
|
|
222
|
+
:param mismatch01: raises an exception if the ratio of mismatches
|
|
223
|
+
is above that threshold
|
|
224
|
+
:param profile_exporter: profiles the exporter
|
|
225
|
+
"""
|
|
226
|
+
prefix = simplify_model_id_for_a_filename(model_id)
|
|
227
|
+
if "QWEN25ATTENTION" in os.environ:
|
|
228
|
+
prefix = f"{prefix}.{os.environ['QWEN25ATTENTION']}"
|
|
229
|
+
basename = os.path.join(
|
|
230
|
+
output_folder, f"model.{prefix}.{part}.{device}.{dtype}.{exporter}"
|
|
231
|
+
)
|
|
232
|
+
filename = f"{basename}.onnx"
|
|
233
|
+
stat_file = f"{basename}.stats"
|
|
234
|
+
|
|
235
|
+
print("------------------------------------------------------------------")
|
|
236
|
+
print(f"-- model_id={model_id}")
|
|
237
|
+
print(f"-- part={part}")
|
|
238
|
+
print(f"-- device={device}")
|
|
239
|
+
print(f"-- dtype={dtype}")
|
|
240
|
+
print(f"-- exporter={exporter}")
|
|
241
|
+
print(f"-- pretrained={pretrained}")
|
|
242
|
+
print(f"-- second_input={second_input}")
|
|
243
|
+
print(f"-- make_zip={make_zip}")
|
|
244
|
+
print(f"-- output_folder={output_folder}")
|
|
245
|
+
print(f"-- atol={atol}")
|
|
246
|
+
print(f"-- mismatch01={mismatch01}")
|
|
247
|
+
print(f"-- profile_exporter={profile_exporter}")
|
|
248
|
+
print("------------------------------------------------------------------")
|
|
249
|
+
print(f"-- prefix={prefix}")
|
|
250
|
+
print(f"-- export in {filename!r}")
|
|
251
|
+
print("------------------------------------------------------------------")
|
|
252
|
+
|
|
253
|
+
if os.path.exists(stat_file) and not existing_onnx:
|
|
254
|
+
print(f"-- skipping because {stat_file!r} already exists")
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
print("-- import torch and others")
|
|
258
|
+
import torch
|
|
259
|
+
from transformers import AutoModel, AutoProcessor
|
|
260
|
+
from ..helpers import string_type
|
|
261
|
+
from ..torch_export_patches.patches._patch_transformers_qwen2_5 import (
|
|
262
|
+
PLUGS,
|
|
263
|
+
)
|
|
264
|
+
from ..torch_export_patches import torch_export_patches
|
|
265
|
+
from ..export.api import to_onnx
|
|
266
|
+
|
|
267
|
+
if output_folder and output_folder != ".":
|
|
268
|
+
os.makedirs(output_folder, exist_ok=True)
|
|
269
|
+
|
|
270
|
+
print(f"-- create model {model_id!r}")
|
|
271
|
+
print(
|
|
272
|
+
f"-- device={device!r}, dtype={dtype!r}, exporter={exporter!r}, "
|
|
273
|
+
f"pretrained={pretrained!r}"
|
|
274
|
+
)
|
|
275
|
+
torch_dtype = get_torch_dtype_from_command_line_args(dtype)
|
|
276
|
+
|
|
277
|
+
if pretrained:
|
|
278
|
+
print("-- pretrained model")
|
|
279
|
+
model = AutoModel.from_pretrained(
|
|
280
|
+
model_id, device_map=device, dtype=torch_dtype, attn_implementation="sdpa"
|
|
281
|
+
).eval()
|
|
282
|
+
data = dict(model=model)
|
|
283
|
+
config = model.config
|
|
284
|
+
else:
|
|
285
|
+
print("-- random model")
|
|
286
|
+
data = get_untrained_model(model_id, second_input=second_input, verbose=1)
|
|
287
|
+
model = data["model"]
|
|
288
|
+
config = data["configuration"]
|
|
289
|
+
|
|
290
|
+
assert (
|
|
291
|
+
hasattr(config, "bos_token_id") and config.bos_token_id
|
|
292
|
+
), f"missing 'bos_token_id' from config\n{config}"
|
|
293
|
+
assert (
|
|
294
|
+
hasattr(config, "eos_token_id") and config.eos_token_id
|
|
295
|
+
), f"missing 'eos_token_id' from config\n{config}"
|
|
296
|
+
model = model.to(device).to(getattr(torch, dtype))
|
|
297
|
+
|
|
298
|
+
print(f"-- config._attn_implementation={model.config._attn_implementation}")
|
|
299
|
+
print(f"-- model.dtype={model.dtype}")
|
|
300
|
+
print(f"-- model.device={model.device}")
|
|
301
|
+
processor = AutoProcessor.from_pretrained(model_id, use_fast=True)
|
|
302
|
+
print(f"-- processor={type(processor)}")
|
|
303
|
+
|
|
304
|
+
export_inputs, other_inputs = None, None
|
|
305
|
+
if not part:
|
|
306
|
+
# used to unit test
|
|
307
|
+
from ..helpers.torch_helper import to_any
|
|
308
|
+
|
|
309
|
+
assert "inputs" in data, f"key 'inputs' is missing from data (available {set(data)})"
|
|
310
|
+
model_to_export = data["model"]
|
|
311
|
+
export_inputs = to_any(to_any(data["inputs"], device), torch_dtype)
|
|
312
|
+
other_inputs = [
|
|
313
|
+
v for k, v in data.items() if k.startswith("inputs_") if k != "inputs_prompt"
|
|
314
|
+
]
|
|
315
|
+
dynamic_shapes = data["dynamic_shapes"]
|
|
316
|
+
assert other_inputs, f"No other inputs was found from data (available {set(data)})"
|
|
317
|
+
|
|
318
|
+
elif part == "visual":
|
|
319
|
+
|
|
320
|
+
class VisualPart(torch.nn.Module):
|
|
321
|
+
def __init__(self, model):
|
|
322
|
+
super().__init__()
|
|
323
|
+
self.model = model
|
|
324
|
+
|
|
325
|
+
def forward(self, pixel_values, image_grid_thw):
|
|
326
|
+
return model.get_image_features(pixel_values, image_grid_thw)
|
|
327
|
+
|
|
328
|
+
assert hasattr(
|
|
329
|
+
model, "get_image_features"
|
|
330
|
+
), f"get_image_features not found in class {type(model)}"
|
|
331
|
+
model_to_export = VisualPart(model)
|
|
332
|
+
|
|
333
|
+
dynamic_shapes = dict(
|
|
334
|
+
pixel_values={0: "hidden_width", 1: "hidden_height"},
|
|
335
|
+
image_grid_thw={}, # {0: "n_images"}, # TODO: fix
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
elif part == "embedding":
|
|
339
|
+
|
|
340
|
+
class EmbeddingPart(torch.nn.Module):
|
|
341
|
+
def __init__(self, model):
|
|
342
|
+
super().__init__()
|
|
343
|
+
self.model = model
|
|
344
|
+
|
|
345
|
+
def forward(self, input_ids, image_features):
|
|
346
|
+
inputs_embeds = None
|
|
347
|
+
|
|
348
|
+
if inputs_embeds is None:
|
|
349
|
+
inputs_embeds = self.model.get_input_embeddings()(input_ids)
|
|
350
|
+
|
|
351
|
+
def process_image(inputs_embeds, image_features):
|
|
352
|
+
image_embeds = image_features
|
|
353
|
+
image_embeds = torch.cat((image_embeds,), dim=0).to(
|
|
354
|
+
inputs_embeds.device, inputs_embeds.dtype
|
|
355
|
+
)
|
|
356
|
+
image_mask, _ = self.model.model.get_placeholder_mask(
|
|
357
|
+
input_ids, inputs_embeds=inputs_embeds, image_features=image_embeds
|
|
358
|
+
)
|
|
359
|
+
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
|
|
360
|
+
return inputs_embeds
|
|
361
|
+
|
|
362
|
+
return torch.cond(
|
|
363
|
+
image_features.shape[0] == 0,
|
|
364
|
+
(lambda embs, _imgf: embs.clone()),
|
|
365
|
+
process_image,
|
|
366
|
+
[inputs_embeds, image_features],
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
assert hasattr(
|
|
370
|
+
model, "get_image_features"
|
|
371
|
+
), f"get_image_features not found in class {type(model)}"
|
|
372
|
+
model_to_export = EmbeddingPart(model)
|
|
373
|
+
|
|
374
|
+
dynamic_shapes = {
|
|
375
|
+
"input_ids": {0: "batch_size", 1: "sequence_length"},
|
|
376
|
+
"image_features": {0: "num_logical_patches"},
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
else:
|
|
380
|
+
raise NotImplementedError(f"no export yet for part={part!r}")
|
|
381
|
+
|
|
382
|
+
print(f"-- part={part!r}")
|
|
383
|
+
print(f"-- model_to_export={type(model_to_export)}")
|
|
384
|
+
print(f"-- dynamic_shapes={dynamic_shapes}")
|
|
385
|
+
print("-- ############")
|
|
386
|
+
print("-- INPUT/OUTPUT")
|
|
387
|
+
print("-- ############")
|
|
388
|
+
|
|
389
|
+
input_filename = os.path.join(output_folder, f"inputs.{prefix}.{part}.{device}.{dtype}.pt")
|
|
390
|
+
if os.path.exists(input_filename):
|
|
391
|
+
print(f"-- restore inputs from {input_filename!r}")
|
|
392
|
+
data = torch.load(input_filename, weights_only=False)
|
|
393
|
+
export_inputs = data["export_inputs"]
|
|
394
|
+
other_inputs = data["other_inputs"]
|
|
395
|
+
dynamic_shapes = data["dynamic_shapes"]
|
|
396
|
+
elif export_inputs is not None:
|
|
397
|
+
data = dict(
|
|
398
|
+
export_inputs=export_inputs,
|
|
399
|
+
other_inputs=other_inputs,
|
|
400
|
+
dynamic_shapes=dynamic_shapes,
|
|
401
|
+
)
|
|
402
|
+
print(f"-- dump inputs into {input_filename!r}")
|
|
403
|
+
torch.save(data, input_filename)
|
|
404
|
+
else:
|
|
405
|
+
export_inputs, other_inputs = get_inputs_for_part(
|
|
406
|
+
part,
|
|
407
|
+
torch_dtype,
|
|
408
|
+
device,
|
|
409
|
+
second_input,
|
|
410
|
+
image_token_id=config.image_token_id,
|
|
411
|
+
bos_token_id=config.bos_token_id,
|
|
412
|
+
eos_token_id=config.eos_token_id,
|
|
413
|
+
)
|
|
414
|
+
data = dict(
|
|
415
|
+
export_inputs=export_inputs,
|
|
416
|
+
other_inputs=other_inputs,
|
|
417
|
+
dynamic_shapes=dynamic_shapes,
|
|
418
|
+
)
|
|
419
|
+
print(f"-- dump inputs into {input_filename!r}")
|
|
420
|
+
torch.save(data, input_filename)
|
|
421
|
+
|
|
422
|
+
print(f"-- export_inputs={string_type(export_inputs, with_shape=True, with_device=True)}")
|
|
423
|
+
print(f"-- other_inputs={string_type(other_inputs, with_shape=True, with_device=True)}")
|
|
424
|
+
print(f"-- dynamic_shapes={dynamic_shapes}")
|
|
425
|
+
output_filename = os.path.join(
|
|
426
|
+
output_folder, f"expected.{prefix}.visual.{device}.{dtype}.pt"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
print("-- ##################")
|
|
430
|
+
print("-- # EXPECTED_OUTPUTS")
|
|
431
|
+
print("-- ##################")
|
|
432
|
+
|
|
433
|
+
compute_expected_outputs(output_filename, model_to_export, input_filename)
|
|
434
|
+
|
|
435
|
+
if existing_onnx and os.path.exists(existing_onnx):
|
|
436
|
+
print("-- ######")
|
|
437
|
+
print(f"-- USING EXISTING ONNX {existing_onnx!r}")
|
|
438
|
+
print("-- ######")
|
|
439
|
+
|
|
440
|
+
exporter = existing_onnx
|
|
441
|
+
filename = existing_onnx
|
|
442
|
+
target_opset = None
|
|
443
|
+
else:
|
|
444
|
+
print("-- ######")
|
|
445
|
+
print("-- EXPORT")
|
|
446
|
+
print("-- ######")
|
|
447
|
+
|
|
448
|
+
if exporter != "custom":
|
|
449
|
+
import packaging.version as pv
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
import onnxscript
|
|
453
|
+
|
|
454
|
+
v_onnxscript = onnxscript.__version__
|
|
455
|
+
if pv.Version(v_onnxscript) <= pv.Version("0.5.6"):
|
|
456
|
+
print(f"-- onnxscript=={v_onnxscript} not recent enough")
|
|
457
|
+
print("-- stop.")
|
|
458
|
+
return
|
|
459
|
+
except AttributeError:
|
|
460
|
+
pass
|
|
461
|
+
except ImportError:
|
|
462
|
+
print("-- missing onnxscript, cannot continue")
|
|
463
|
+
print("-- stop.")
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
begin = time.perf_counter()
|
|
467
|
+
|
|
468
|
+
target_opset = 22
|
|
469
|
+
if (
|
|
470
|
+
exporter == "onnx-dynamo"
|
|
471
|
+
and device == "cuda"
|
|
472
|
+
and "QWEN25ATTENTION" not in os.environ
|
|
473
|
+
):
|
|
474
|
+
os.environ["QWEN25ATTENTION"] = "PACKED"
|
|
475
|
+
elif "QWEN25ATTENTION" in os.environ and os.environ["QWEN25ATTENTION"] == "LOOPA23":
|
|
476
|
+
target_opset = 23
|
|
477
|
+
|
|
478
|
+
with torch_export_patches(
|
|
479
|
+
patch_torch=False,
|
|
480
|
+
patch_sympy=False,
|
|
481
|
+
patch_transformers=True,
|
|
482
|
+
verbose=1,
|
|
483
|
+
stop_if_static=2,
|
|
484
|
+
profile=(f"{basename}.profile.html" if profile_exporter else None),
|
|
485
|
+
):
|
|
486
|
+
to_onnx(
|
|
487
|
+
model_to_export,
|
|
488
|
+
kwargs=export_inputs,
|
|
489
|
+
dynamic_shapes=dynamic_shapes,
|
|
490
|
+
filename=filename,
|
|
491
|
+
exporter=exporter,
|
|
492
|
+
verbose=1,
|
|
493
|
+
save_ep=None,
|
|
494
|
+
target_opset=target_opset,
|
|
495
|
+
optimize=True,
|
|
496
|
+
onnx_plugs=PLUGS,
|
|
497
|
+
)
|
|
498
|
+
export_duration = time.perf_counter() - begin
|
|
499
|
+
|
|
500
|
+
if exporter == "onnx-dynamo":
|
|
501
|
+
# onnx-dynamo fails at producing function body with sequences as input / output.
|
|
502
|
+
# They are replaced by tensor type one step in the model.
|
|
503
|
+
print("-- remove_body_last_input_output_for_loop")
|
|
504
|
+
remove_inplace_body_last_input_output_type_for_loop_because_they_might_be_sequences(
|
|
505
|
+
filename
|
|
506
|
+
)
|
|
507
|
+
print("-- done.")
|
|
508
|
+
|
|
509
|
+
print("-- ###############")
|
|
510
|
+
print("-- # DISCREPANCIES")
|
|
511
|
+
print("-- ###############")
|
|
512
|
+
|
|
513
|
+
info = {
|
|
514
|
+
"model_id": model_id,
|
|
515
|
+
"part": part,
|
|
516
|
+
"device": device,
|
|
517
|
+
"dtype": dtype,
|
|
518
|
+
"exporter": exporter,
|
|
519
|
+
"pretrained": pretrained,
|
|
520
|
+
"attention": os.environ.get("QWEN25ATTENTION", "default"),
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
check_for_discrepancies_and_log_everything_into_a_json_file(
|
|
524
|
+
agg_stat_file=os.path.join(output_folder, "collection_statistics.js"),
|
|
525
|
+
stat_file=stat_file,
|
|
526
|
+
export_duration=export_duration,
|
|
527
|
+
device=device,
|
|
528
|
+
model_file=filename,
|
|
529
|
+
cached_inputs=input_filename,
|
|
530
|
+
cached_expected_outputs=output_filename,
|
|
531
|
+
main_info=info,
|
|
532
|
+
atol=atol,
|
|
533
|
+
mismatch01=mismatch01,
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
if make_zip:
|
|
537
|
+
print("-- #####")
|
|
538
|
+
print("-- # ZIP")
|
|
539
|
+
print("-- #####")
|
|
540
|
+
zip_model_and_data_into_a_single_file(f"{basename}.zip", filename)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
if __name__ == "__main__":
|
|
544
|
+
parser = get_parser("qwen25")
|
|
545
|
+
args = parser.parse_args(sys.argv[1:])
|
|
546
|
+
main(
|
|
547
|
+
model_id=args.mid,
|
|
548
|
+
device=args.device,
|
|
549
|
+
dtype=args.dtype,
|
|
550
|
+
exporter=args.exporter,
|
|
551
|
+
pretrained=args.pretrained,
|
|
552
|
+
second_input=args.second_input,
|
|
553
|
+
make_zip=args.zip,
|
|
554
|
+
output_folder=args.output_folder,
|
|
555
|
+
existing_onnx=args.existing_onnx,
|
|
556
|
+
part=args.part,
|
|
557
|
+
atol=args.atol,
|
|
558
|
+
mismatch01=args.mismatch01,
|
|
559
|
+
profile_exporter=args.profile_exporter,
|
|
560
|
+
)
|