wtpsplit 2.2.1__tar.gz → 2.2.2__tar.gz
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.
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/PKG-INFO +42 -1
- wtpsplit-2.2.1/wtpsplit.egg-info/PKG-INFO → wtpsplit-2.2.2/README.md +38 -37
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/pyproject.toml +5 -1
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/setup.py +2 -1
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/__init__.py +43 -1
- wtpsplit-2.2.2/wtpsplit/aitune_integration.py +279 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/extract.py +133 -14
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/models.py +16 -4
- wtpsplit-2.2.2/wtpsplit/train/hf_compat.py +31 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/transformers_trainer_imports.py +8 -4
- wtpsplit-2.2.1/README.md → wtpsplit-2.2.2/wtpsplit.egg-info/PKG-INFO +78 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit.egg-info/SOURCES.txt +1 -2
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit.egg-info/requires.txt +4 -0
- wtpsplit-2.2.1/wtpsplit/data/dummy_lora_smoke.pth +0 -0
- wtpsplit-2.2.1/wtpsplit/data/priors_generated.py +0 -250
- wtpsplit-2.2.1/wtpsplit/train/hf_compat.py +0 -18
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/LICENSE +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/setup.cfg +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/configs.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/__init__.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/language_info.csv +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/punctuation.json +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/punctuation.txt +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/punctuation_xlmr.txt +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/punctuation_xlmr_unk.txt +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data/sentence_stats.json +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data_acquisition/__init__.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data_acquisition/extract_all_data.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data_acquisition/extract_eval_data.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data_acquisition/extract_sentences.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/data_acquisition/extract_shared_task_data.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/__init__.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/adapt.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/evaluate_sepp_nlg_subtask1.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/extrinsic.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/intrinsic_baselines.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/intrinsic_baselines_multilingual.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/intrinsic_pairwise.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/intrinsic_ted.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/legal_baselines.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/llm_sentence.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/punct_annotation.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/evaluation/punct_annotation_wtp.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/extract_batched.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/__init__.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/adaptertrainer.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/evaluate.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/train.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/train_SM.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/train_lora.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/trainer.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/train/utils.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/__init__.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/clean_tweets.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/constraints.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/create_dummy_data.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/download_spacy.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/priors.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit/utils/remove_unks.py +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit.egg-info/dependency_links.txt +0 -0
- {wtpsplit-2.2.1 → wtpsplit-2.2.2}/wtpsplit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wtpsplit
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Universal Robust, Efficient and Adaptable Sentence Segmentation
|
|
5
5
|
Home-page: https://github.com/segment-any-text/wtpsplit
|
|
6
6
|
Author: Markus Frohmann, Igor Sterner, Benjamin Minixhofer
|
|
@@ -23,6 +23,9 @@ Provides-Extra: onnx-gpu
|
|
|
23
23
|
Requires-Dist: onnxruntime-gpu>=1.13.1; extra == "onnx-gpu"
|
|
24
24
|
Provides-Extra: onnx-cpu
|
|
25
25
|
Requires-Dist: onnxruntime>=1.13.1; extra == "onnx-cpu"
|
|
26
|
+
Provides-Extra: aitune
|
|
27
|
+
Requires-Dist: aitune>=0.3.0; extra == "aitune"
|
|
28
|
+
Requires-Dist: requests; extra == "aitune"
|
|
26
29
|
Dynamic: author
|
|
27
30
|
Dynamic: author-email
|
|
28
31
|
Dynamic: description
|
|
@@ -88,6 +91,44 @@ sat_adapted.split("This is a test This is another test.")
|
|
|
88
91
|
# returns ['This is a test ', 'This is another test']
|
|
89
92
|
```
|
|
90
93
|
|
|
94
|
+
## Faster PyTorch inference (`torch.compile` / TorchInductor)
|
|
95
|
+
|
|
96
|
+
For repeated inference without exporting to ONNX, compile the PyTorch model with [TorchInductor](https://pytorch.org/docs/stable/torch.compiler.html) via `optimize()` (PyTorch 2.0+). This is **optional** - the first `split` after `optimize()` can be slow while graphs are built. Call `optimize()` **after** `to()` / `half()` so the compiled graph matches *your* device and dtype.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from wtpsplit import SaT
|
|
100
|
+
|
|
101
|
+
sat = SaT("sat-3l-sm")
|
|
102
|
+
sat.half().to("cuda")
|
|
103
|
+
sat.optimize() # backend="inductor" (default); dynamic shapes on
|
|
104
|
+
|
|
105
|
+
sat.split("This is a test This is another test.")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **SaT** and **WtP** PyTorch checkpoints only — not available with `ort_providers` / ONNX.
|
|
109
|
+
- `backend` synonyms: `"inductor"`, `"torchinductor"`.
|
|
110
|
+
- Chunk length and the last batch size change between calls, so `dynamic=True` is the default. `mode="reduce-overhead"` (CUDA graphs) is faster only when every forward uses the same shapes; otherwise use the default or `mode="max-autotune-no-cudagraphs"`.
|
|
111
|
+
- Optional on NVIDIA Ampere+: `torch.set_float32_matmul_precision("high")` before inference (faster fp32 matmuls).
|
|
112
|
+
|
|
113
|
+
### NVIDIA AITune (auto backend selection, CUDA)
|
|
114
|
+
|
|
115
|
+
[NVIDIA AITune](https://github.com/ai-dynamo/aitune) benchmarks backends (TensorRT, Torch-TensorRT, Torch Inductor, …) on your GPU and picks a fast path. Requires Linux, CUDA, and a separate install:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install wtpsplit[aitune] --extra-index-url https://pypi.nvidia.com
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
sat = SaT("sat-3l-sm")
|
|
123
|
+
sat.half().to("cuda")
|
|
124
|
+
# first_wins tries TensorRT → Torch-TensorRT → Inductor when available
|
|
125
|
+
sat.optimize(backend="aitune")
|
|
126
|
+
# or only Inductor via AITune (faster tuning, no TensorRT dependency):
|
|
127
|
+
# sat.optimize(backend="aitune", aitune_strategy="inductor_only", aitune_max_batches=4)
|
|
128
|
+
|
|
129
|
+
sat.split("This is a test This is another test.")
|
|
130
|
+
```
|
|
131
|
+
|
|
91
132
|
## ONNX Support
|
|
92
133
|
|
|
93
134
|
🚀 You can now enable even faster ONNX inference for `sat` and `sat-sm` models! 🚀
|
|
@@ -1,40 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: wtpsplit
|
|
3
|
-
Version: 2.2.1
|
|
4
|
-
Summary: Universal Robust, Efficient and Adaptable Sentence Segmentation
|
|
5
|
-
Home-page: https://github.com/segment-any-text/wtpsplit
|
|
6
|
-
Author: Markus Frohmann, Igor Sterner, Benjamin Minixhofer
|
|
7
|
-
Author-email: markus.frohmann@gmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Requires-Python: >=3.9
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: transformers>=4.22.2
|
|
13
|
-
Requires-Dist: huggingface-hub
|
|
14
|
-
Requires-Dist: numpy>=1.0
|
|
15
|
-
Requires-Dist: scikit-learn>=1
|
|
16
|
-
Requires-Dist: tqdm
|
|
17
|
-
Requires-Dist: skops
|
|
18
|
-
Requires-Dist: pandas>=1
|
|
19
|
-
Requires-Dist: mosestokenizer
|
|
20
|
-
Provides-Extra: adapters
|
|
21
|
-
Requires-Dist: adapters>=1.0.1; extra == "adapters"
|
|
22
|
-
Provides-Extra: onnx-gpu
|
|
23
|
-
Requires-Dist: onnxruntime-gpu>=1.13.1; extra == "onnx-gpu"
|
|
24
|
-
Provides-Extra: onnx-cpu
|
|
25
|
-
Requires-Dist: onnxruntime>=1.13.1; extra == "onnx-cpu"
|
|
26
|
-
Dynamic: author
|
|
27
|
-
Dynamic: author-email
|
|
28
|
-
Dynamic: description
|
|
29
|
-
Dynamic: description-content-type
|
|
30
|
-
Dynamic: home-page
|
|
31
|
-
Dynamic: license
|
|
32
|
-
Dynamic: license-file
|
|
33
|
-
Dynamic: provides-extra
|
|
34
|
-
Dynamic: requires-dist
|
|
35
|
-
Dynamic: requires-python
|
|
36
|
-
Dynamic: summary
|
|
37
|
-
|
|
38
1
|
<h1 align="center">wtpsplit🪓</h1>
|
|
39
2
|
<h3 align="center">Segment any Text - Robustly, Efficiently, Adaptably⚡</h3>
|
|
40
3
|
|
|
@@ -88,6 +51,44 @@ sat_adapted.split("This is a test This is another test.")
|
|
|
88
51
|
# returns ['This is a test ', 'This is another test']
|
|
89
52
|
```
|
|
90
53
|
|
|
54
|
+
## Faster PyTorch inference (`torch.compile` / TorchInductor)
|
|
55
|
+
|
|
56
|
+
For repeated inference without exporting to ONNX, compile the PyTorch model with [TorchInductor](https://pytorch.org/docs/stable/torch.compiler.html) via `optimize()` (PyTorch 2.0+). This is **optional** - the first `split` after `optimize()` can be slow while graphs are built. Call `optimize()` **after** `to()` / `half()` so the compiled graph matches *your* device and dtype.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from wtpsplit import SaT
|
|
60
|
+
|
|
61
|
+
sat = SaT("sat-3l-sm")
|
|
62
|
+
sat.half().to("cuda")
|
|
63
|
+
sat.optimize() # backend="inductor" (default); dynamic shapes on
|
|
64
|
+
|
|
65
|
+
sat.split("This is a test This is another test.")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **SaT** and **WtP** PyTorch checkpoints only — not available with `ort_providers` / ONNX.
|
|
69
|
+
- `backend` synonyms: `"inductor"`, `"torchinductor"`.
|
|
70
|
+
- Chunk length and the last batch size change between calls, so `dynamic=True` is the default. `mode="reduce-overhead"` (CUDA graphs) is faster only when every forward uses the same shapes; otherwise use the default or `mode="max-autotune-no-cudagraphs"`.
|
|
71
|
+
- Optional on NVIDIA Ampere+: `torch.set_float32_matmul_precision("high")` before inference (faster fp32 matmuls).
|
|
72
|
+
|
|
73
|
+
### NVIDIA AITune (auto backend selection, CUDA)
|
|
74
|
+
|
|
75
|
+
[NVIDIA AITune](https://github.com/ai-dynamo/aitune) benchmarks backends (TensorRT, Torch-TensorRT, Torch Inductor, …) on your GPU and picks a fast path. Requires Linux, CUDA, and a separate install:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install wtpsplit[aitune] --extra-index-url https://pypi.nvidia.com
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
sat = SaT("sat-3l-sm")
|
|
83
|
+
sat.half().to("cuda")
|
|
84
|
+
# first_wins tries TensorRT → Torch-TensorRT → Inductor when available
|
|
85
|
+
sat.optimize(backend="aitune")
|
|
86
|
+
# or only Inductor via AITune (faster tuning, no TensorRT dependency):
|
|
87
|
+
# sat.optimize(backend="aitune", aitune_strategy="inductor_only", aitune_max_batches=4)
|
|
88
|
+
|
|
89
|
+
sat.split("This is a test This is another test.")
|
|
90
|
+
```
|
|
91
|
+
|
|
91
92
|
## ONNX Support
|
|
92
93
|
|
|
93
94
|
🚀 You can now enable even faster ONNX inference for `sat` and `sat-sm` models! 🚀
|
|
@@ -4,7 +4,11 @@ target-version = ["py39", "py310", "py311", "py312"]
|
|
|
4
4
|
|
|
5
5
|
[tool.ruff]
|
|
6
6
|
line-length = 120
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
[tool.ruff.lint]
|
|
9
|
+
# Pre-0.16 default. Ruff 0.16 enables 413 rules by default; CI installs latest ruff.
|
|
10
|
+
select = ["E4", "E7", "E9", "F"]
|
|
11
|
+
ignore = ["E741"]
|
|
8
12
|
|
|
9
13
|
[tool.ruff.lint.per-file-ignores]
|
|
10
14
|
"test.py" = ["E501"]
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="wtpsplit",
|
|
5
|
-
version="2.2.
|
|
5
|
+
version="2.2.2",
|
|
6
6
|
packages=find_packages(),
|
|
7
7
|
description="Universal Robust, Efficient and Adaptable Sentence Segmentation",
|
|
8
8
|
long_description=open("README.md", encoding="utf-8").read(),
|
|
@@ -25,6 +25,7 @@ setup(
|
|
|
25
25
|
"adapters": ["adapters>=1.0.1"],
|
|
26
26
|
"onnx-gpu": ["onnxruntime-gpu>=1.13.1"],
|
|
27
27
|
"onnx-cpu": ["onnxruntime>=1.13.1"],
|
|
28
|
+
"aitune": ["aitune>=0.3.0", "requests"],
|
|
28
29
|
},
|
|
29
30
|
url="https://github.com/segment-any-text/wtpsplit",
|
|
30
31
|
package_data={"wtpsplit": ["data/*"]},
|
|
@@ -30,7 +30,7 @@ from wtpsplit.utils.constraints import (
|
|
|
30
30
|
)
|
|
31
31
|
from wtpsplit.utils.priors import create_prior_function
|
|
32
32
|
|
|
33
|
-
__version__ = "2.2.
|
|
33
|
+
__version__ = "2.2.2"
|
|
34
34
|
|
|
35
35
|
# suppress docopt syntax warnings (triggered in Python 3.14+)
|
|
36
36
|
warnings.filterwarnings("ignore", category=SyntaxWarning, module="docopt")
|
|
@@ -228,6 +228,27 @@ class WtP:
|
|
|
228
228
|
assert hasattr(self, "model")
|
|
229
229
|
return getattr(self.model, name)
|
|
230
230
|
|
|
231
|
+
def optimize(
|
|
232
|
+
self,
|
|
233
|
+
backend: str = "inductor",
|
|
234
|
+
*,
|
|
235
|
+
mode: str = None,
|
|
236
|
+
fullgraph: bool = False,
|
|
237
|
+
dynamic: bool = True,
|
|
238
|
+
**compile_kwargs,
|
|
239
|
+
):
|
|
240
|
+
"""Compile the PyTorch model with ``torch.compile`` (TorchInductor by default). Not available for ONNX."""
|
|
241
|
+
if not isinstance(self.model, PyTorchWrapper):
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"optimize() only applies to PyTorch models loaded from a checkpoint. "
|
|
244
|
+
"It does not apply to ONNX Runtime (set ort_providers=None) or to ad-hoc model objects "
|
|
245
|
+
"that are not wrapped as expected."
|
|
246
|
+
)
|
|
247
|
+
self.model.optimize(
|
|
248
|
+
backend=backend, mode=mode, fullgraph=fullgraph, dynamic=dynamic, **compile_kwargs
|
|
249
|
+
)
|
|
250
|
+
return self
|
|
251
|
+
|
|
231
252
|
def predict_proba(
|
|
232
253
|
self,
|
|
233
254
|
text_or_texts,
|
|
@@ -908,6 +929,27 @@ class SaT:
|
|
|
908
929
|
assert hasattr(self, "model")
|
|
909
930
|
return getattr(self.model, name)
|
|
910
931
|
|
|
932
|
+
def optimize(
|
|
933
|
+
self,
|
|
934
|
+
backend: str = "inductor",
|
|
935
|
+
*,
|
|
936
|
+
mode: str = None,
|
|
937
|
+
fullgraph: bool = False,
|
|
938
|
+
dynamic: bool = True,
|
|
939
|
+
**compile_kwargs,
|
|
940
|
+
):
|
|
941
|
+
"""Compile the PyTorch model with ``torch.compile`` (TorchInductor by default). Not available for ONNX."""
|
|
942
|
+
if not isinstance(self.model, PyTorchWrapper):
|
|
943
|
+
raise ValueError(
|
|
944
|
+
"optimize() only applies to PyTorch models loaded from a checkpoint. "
|
|
945
|
+
"It does not apply to ONNX Runtime (set ort_providers=None) or to ad-hoc model objects "
|
|
946
|
+
"that are not wrapped as expected."
|
|
947
|
+
)
|
|
948
|
+
self.model.optimize(
|
|
949
|
+
backend=backend, mode=mode, fullgraph=fullgraph, dynamic=dynamic, **compile_kwargs
|
|
950
|
+
)
|
|
951
|
+
return self
|
|
952
|
+
|
|
911
953
|
def predict_proba(
|
|
912
954
|
self,
|
|
913
955
|
text_or_texts,
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Optional NVIDIA AITune integration for :meth:`PyTorchWrapper.optimize`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _require_aitune():
|
|
9
|
+
try:
|
|
10
|
+
import aitune.torch as ait # noqa: F401
|
|
11
|
+
except ImportError as e:
|
|
12
|
+
raise ImportError(
|
|
13
|
+
"backend='aitune' requires NVIDIA AITune. Install with:\n"
|
|
14
|
+
" pip install wtpsplit[aitune] --extra-index-url https://pypi.nvidia.com"
|
|
15
|
+
) from e
|
|
16
|
+
return ait
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _build_calibration_dataset(model, device) -> list[dict[str, Any]]:
|
|
20
|
+
"""Synthetic unbatched samples covering the chunk lengths ``extract`` actually feeds.
|
|
21
|
+
|
|
22
|
+
AITune stacks these on dim 0, so each tensor is one example: ``(seq,)`` or
|
|
23
|
+
``(seq, num_hashes)``. Lengths stay inside the position-embedding window and
|
|
24
|
+
on the character-model downsampling grid.
|
|
25
|
+
"""
|
|
26
|
+
import torch
|
|
27
|
+
|
|
28
|
+
config = model.config
|
|
29
|
+
model_type = getattr(config, "model_type", "") or ""
|
|
30
|
+
rate = int(getattr(config, "downsampling_rate", 1) or 1)
|
|
31
|
+
lengths = tuple(length for length in (64, 128, 256, 512) if length % rate == 0) or (rate,)
|
|
32
|
+
uses_lang = getattr(config, "language_adapter", "off") in {"on", "shared"}
|
|
33
|
+
samples: list[dict[str, Any]] = []
|
|
34
|
+
|
|
35
|
+
for seq_len in lengths:
|
|
36
|
+
if "xlm" in model_type:
|
|
37
|
+
# CLS/SEP are added around the chunk; stay within the 512 position window.
|
|
38
|
+
length = min(seq_len + 2, 512)
|
|
39
|
+
sample = {
|
|
40
|
+
"input_ids": torch.randint(0, 50000, (length,), dtype=torch.long, device=device),
|
|
41
|
+
"attention_mask": torch.ones(length, dtype=torch.float32, device=device),
|
|
42
|
+
}
|
|
43
|
+
else:
|
|
44
|
+
num_hashes = getattr(config, "num_hash_functions", 8)
|
|
45
|
+
num_buckets = getattr(config, "num_hash_buckets", 10000)
|
|
46
|
+
sample = {
|
|
47
|
+
"hashed_ids": torch.randint(0, num_buckets, (seq_len, num_hashes), dtype=torch.long, device=device),
|
|
48
|
+
"attention_mask": torch.ones(seq_len, dtype=torch.float32, device=device),
|
|
49
|
+
}
|
|
50
|
+
if uses_lang and "xlm" not in model_type:
|
|
51
|
+
# Batched to shape (batch,) by AITune, matching ``extract``.
|
|
52
|
+
sample["language_ids"] = torch.zeros((), dtype=torch.long, device=device)
|
|
53
|
+
samples.append(sample)
|
|
54
|
+
|
|
55
|
+
return samples
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _backend_cls(*names: str):
|
|
59
|
+
for name in names:
|
|
60
|
+
factory = _try_backend("aitune.torch.backend", name)
|
|
61
|
+
if factory is not None:
|
|
62
|
+
return factory
|
|
63
|
+
raise ImportError(
|
|
64
|
+
"Could not import an AITune TorchInductor backend "
|
|
65
|
+
f"(tried {', '.join(names)}). Upgrade with: pip install -U 'aitune>=0.4'"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _strategy_cls(*names: str):
|
|
70
|
+
for name in names:
|
|
71
|
+
factory = _try_backend("aitune.torch.tune_strategy", name)
|
|
72
|
+
if factory is not None:
|
|
73
|
+
return factory
|
|
74
|
+
raise ImportError(
|
|
75
|
+
"Could not import an AITune tune strategy "
|
|
76
|
+
f"(tried {', '.join(names)}). Upgrade with: pip install -U 'aitune>=0.4'"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _inductor_backend(*, mode: str | None, fullgraph: bool, dynamic: bool):
|
|
81
|
+
"""TorchInductor backend, using the current JIT class and falling back to older names."""
|
|
82
|
+
backend_cls = _backend_cls("TorchInductorJitBackend", "TorchInductorBackend")
|
|
83
|
+
config_cls = _try_backend("aitune.torch.backend", "TorchInductorJitBackendConfig")
|
|
84
|
+
if config_cls is None:
|
|
85
|
+
return backend_cls()
|
|
86
|
+
config_kwargs: dict[str, Any] = {"fullgraph": fullgraph, "dynamic": dynamic}
|
|
87
|
+
if mode is not None:
|
|
88
|
+
config_kwargs["mode"] = mode
|
|
89
|
+
try:
|
|
90
|
+
return backend_cls(config=config_cls(**config_kwargs))
|
|
91
|
+
except TypeError:
|
|
92
|
+
return backend_cls()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _resolve_strategy(
|
|
96
|
+
strategy_name: str | None,
|
|
97
|
+
backends: list | None,
|
|
98
|
+
*,
|
|
99
|
+
mode: str | None,
|
|
100
|
+
fullgraph: bool,
|
|
101
|
+
dynamic: bool,
|
|
102
|
+
):
|
|
103
|
+
first_wins = _strategy_cls("FirstWinsStrategy")
|
|
104
|
+
one_backend = _strategy_cls("OneBackendStrategy")
|
|
105
|
+
|
|
106
|
+
if backends is not None:
|
|
107
|
+
return first_wins(backends=backends)
|
|
108
|
+
|
|
109
|
+
key = (strategy_name or "first_wins").lower().replace("-", "_")
|
|
110
|
+
inductor = _inductor_backend(mode=mode, fullgraph=fullgraph, dynamic=dynamic)
|
|
111
|
+
if key in ("inductor", "inductor_only", "torch_inductor"):
|
|
112
|
+
return one_backend(backend=inductor)
|
|
113
|
+
|
|
114
|
+
available = _default_aitune_backends(inductor)
|
|
115
|
+
if key in ("highest_throughput", "best", "max_throughput"):
|
|
116
|
+
strategy_cls = _strategy_cls("MaxThroughputStrategy", "HighestThroughputStrategy")
|
|
117
|
+
try:
|
|
118
|
+
return strategy_cls(backends=available)
|
|
119
|
+
except TypeError:
|
|
120
|
+
return strategy_cls()
|
|
121
|
+
|
|
122
|
+
return first_wins(backends=available)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _default_aitune_backends(inductor_backend) -> list:
|
|
126
|
+
"""Backends tried in order for ``first_wins`` (TensorRT first when importable)."""
|
|
127
|
+
backends = []
|
|
128
|
+
for class_name in ("TensorRTBackend", "TorchTensorRTJitBackend"):
|
|
129
|
+
factory = _try_backend("aitune.torch.backend", class_name)
|
|
130
|
+
if factory is not None:
|
|
131
|
+
backends.append(factory())
|
|
132
|
+
backends.append(inductor_backend)
|
|
133
|
+
return backends
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _try_backend(module_name: str, class_name: str):
|
|
137
|
+
try:
|
|
138
|
+
import importlib
|
|
139
|
+
|
|
140
|
+
mod = importlib.import_module(module_name)
|
|
141
|
+
return getattr(mod, class_name)
|
|
142
|
+
except Exception:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def pop_aitune_kwargs(compile_kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
147
|
+
"""Extract AITune-specific keys from ``compile_kwargs`` (mutates the dict)."""
|
|
148
|
+
keys = (
|
|
149
|
+
"aitune_batch_sizes",
|
|
150
|
+
"aitune_max_batches",
|
|
151
|
+
"aitune_strategy",
|
|
152
|
+
"aitune_backends",
|
|
153
|
+
"aitune_dry_run",
|
|
154
|
+
"aitune_calibration",
|
|
155
|
+
)
|
|
156
|
+
out = {}
|
|
157
|
+
for key in keys:
|
|
158
|
+
if key in compile_kwargs:
|
|
159
|
+
out[key] = compile_kwargs.pop(key)
|
|
160
|
+
return out
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def apply_aitune(model, *, mode: str | None = None, fullgraph: bool = False, dynamic: bool = True, **aitune_kwargs):
|
|
164
|
+
"""Wrap ``model`` with AITune and run ahead-of-time tuning. Returns the wrapped module."""
|
|
165
|
+
import logging
|
|
166
|
+
|
|
167
|
+
import torch
|
|
168
|
+
|
|
169
|
+
ait = _require_aitune()
|
|
170
|
+
from aitune.torch.dataloader import DynamicShapeDataset
|
|
171
|
+
|
|
172
|
+
logger = logging.getLogger(__name__)
|
|
173
|
+
|
|
174
|
+
if not torch.cuda.is_available():
|
|
175
|
+
raise RuntimeError(
|
|
176
|
+
"AITune requires a CUDA GPU. Move the model with `.to('cuda')` before `optimize(backend='aitune')`."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
device = getattr(model, "device", None)
|
|
180
|
+
if device is None or getattr(device, "type", None) != "cuda":
|
|
181
|
+
raise RuntimeError(
|
|
182
|
+
"AITune requires the model on CUDA. Call `.to('cuda')` before `optimize(backend='aitune')`."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
model.eval()
|
|
186
|
+
calibration = aitune_kwargs.pop("aitune_calibration", None)
|
|
187
|
+
dataset = (
|
|
188
|
+
calibration if calibration is not None else DynamicShapeDataset(_build_calibration_dataset(model, device))
|
|
189
|
+
)
|
|
190
|
+
batch_sizes = aitune_kwargs.pop("aitune_batch_sizes", None) or [1, 2]
|
|
191
|
+
max_batches = aitune_kwargs.pop("aitune_max_batches", 4)
|
|
192
|
+
strategy_name = aitune_kwargs.pop("aitune_strategy", None)
|
|
193
|
+
backends = aitune_kwargs.pop("aitune_backends", None)
|
|
194
|
+
dry_run = aitune_kwargs.pop("aitune_dry_run", False)
|
|
195
|
+
if len(set(batch_sizes)) < 2:
|
|
196
|
+
logger.warning(
|
|
197
|
+
"AITune needs at least two batch sizes to mark the batch axis as dynamic; "
|
|
198
|
+
"using [1, 2] so later split() batch sizes still match."
|
|
199
|
+
)
|
|
200
|
+
batch_sizes = [1, 2]
|
|
201
|
+
|
|
202
|
+
strategy = _resolve_strategy(
|
|
203
|
+
strategy_name, backends, mode=mode, fullgraph=fullgraph, dynamic=dynamic
|
|
204
|
+
)
|
|
205
|
+
name = f"{model.__class__.__module__}.{model.__class__.__qualname__}".replace("/", "_")
|
|
206
|
+
wrapped = ait.Module(model, name=name, strategy=strategy)
|
|
207
|
+
|
|
208
|
+
ait.tune(
|
|
209
|
+
wrapped,
|
|
210
|
+
dataset,
|
|
211
|
+
batch_sizes=batch_sizes,
|
|
212
|
+
max_num_batches_per_batch_size=max_batches,
|
|
213
|
+
device=device,
|
|
214
|
+
dry_run=dry_run,
|
|
215
|
+
)
|
|
216
|
+
if not dry_run:
|
|
217
|
+
_install_metadata_fallback(wrapped)
|
|
218
|
+
return wrapped
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _install_metadata_fallback(wrapped):
|
|
222
|
+
"""Allow AITune dynamic-shape metadata to match runtime samples.
|
|
223
|
+
|
|
224
|
+
AITune records dynamic axes during tuning, but its runtime lookup can still
|
|
225
|
+
require exact ``SampleMetadata`` dictionary membership. SaT/WtP chunk lengths
|
|
226
|
+
vary naturally, so fall back to the compatible ``TensorSpec.matches`` check
|
|
227
|
+
before reporting that no backend was found.
|
|
228
|
+
"""
|
|
229
|
+
import types
|
|
230
|
+
|
|
231
|
+
from aitune.torch.module.sample_metadata import SampleMetadata
|
|
232
|
+
|
|
233
|
+
tuned = getattr(wrapped, "_self_wrapper", None)
|
|
234
|
+
if tuned is None or not hasattr(tuned, "_backends"):
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
def metadata_matches(expected, actual):
|
|
238
|
+
if expected == actual:
|
|
239
|
+
return True
|
|
240
|
+
if expected.llm_phase != actual.llm_phase:
|
|
241
|
+
return False
|
|
242
|
+
if expected.other_data != actual.other_data:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
expected_specs = {spec.name: spec for spec in expected.tensor_specs}
|
|
246
|
+
actual_specs = {spec.name: spec for spec in actual.tensor_specs}
|
|
247
|
+
if expected_specs.keys() != actual_specs.keys():
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
for name, actual_spec in actual_specs.items():
|
|
251
|
+
expected_spec = expected_specs[name]
|
|
252
|
+
if expected_spec.dtype != actual_spec.dtype:
|
|
253
|
+
return False
|
|
254
|
+
if not expected_spec.matches(actual_spec):
|
|
255
|
+
return False
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
def safe_call_backend_with_dynamic_match(self, sample):
|
|
259
|
+
args, kwargs = sample
|
|
260
|
+
# AITune 0.6+ takes one named-input dict. Older releases took (args, kwargs).
|
|
261
|
+
if hasattr(self, "_forward_signature"):
|
|
262
|
+
forward_inputs = self._forward_signature.normalize(args, kwargs)
|
|
263
|
+
sample_metadata = SampleMetadata.from_inputs(
|
|
264
|
+
forward_inputs.arguments, strict=self._config.strict_mode
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
sample_metadata = SampleMetadata.from_inputs(args, kwargs, strict=self._config.strict_mode)
|
|
268
|
+
backend = self._backends.get(sample_metadata)
|
|
269
|
+
if backend is None:
|
|
270
|
+
for candidate_metadata, candidate_backend in self._backends.items():
|
|
271
|
+
if metadata_matches(candidate_metadata, sample_metadata):
|
|
272
|
+
backend = candidate_backend
|
|
273
|
+
break
|
|
274
|
+
if backend is None:
|
|
275
|
+
raise RuntimeError(self.ERROR_NO_BACKEND_FOUND.format(sample_metadata))
|
|
276
|
+
return backend.infer(*args, **kwargs)
|
|
277
|
+
|
|
278
|
+
tuned.safe_call_backend = types.MethodType(safe_call_backend_with_dynamic_match, tuned)
|
|
279
|
+
tuned._backend_func = tuned.safe_call_backend
|