opensportslib 0.2.0.dev1__py3-none-any.whl → 0.2.0.dev2__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.
- opensportslib/apis/__init__.py +2 -0
- opensportslib/apis/vqa.py +313 -0
- opensportslib/cli.py +4 -0
- opensportslib/configs/vqa/qwen.yaml +228 -0
- opensportslib/configs/vqa/xvars_lora.yaml +243 -0
- opensportslib/core/config/accessors.py +234 -0
- opensportslib/core/config/validate.py +15 -0
- opensportslib/core/trainer/vqa_trainer.py +797 -0
- opensportslib/core/utils/config.py +6 -2
- opensportslib/core/utils/ddp.py +3 -2
- opensportslib/core/utils/hf_runtime.py +524 -0
- opensportslib/datasets/builder.py +4 -0
- opensportslib/datasets/vqa_dataset.py +165 -0
- opensportslib/metrics/vqa_metric.py +217 -0
- opensportslib/models/base/qwen_xvars.py +476 -0
- opensportslib/models/base/video_chatgpt_compat.py +298 -0
- opensportslib/models/base/xvars_videochatgpt.py +1115 -0
- opensportslib/models/builder.py +34 -0
- opensportslib/models/utils/vqa_prediction_priors.py +105 -0
- opensportslib/models/utils/vqa_prompting.py +56 -0
- opensportslib/models/utils/xvars_clip_index.py +99 -0
- opensportslib/setup/setup.py +36 -2
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/METADATA +31 -1
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/RECORD +40 -21
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/WHEEL +1 -1
- tests/conftest.py +140 -1
- tests/test_config_architecture.py +6 -0
- tests/test_config_utils_smoke.py +38 -0
- tests/test_extract_xvars_features.py +134 -0
- tests/test_public_apis_smoke.py +15 -0
- tests/test_task_model_api_contract.py +519 -2
- tests/test_vqa_metrics_semantic.py +122 -0
- tests/test_vqa_training_lora.py +563 -0
- tests/test_vqa_xvars_videochatgpt.py +1387 -0
- tools/convert/build_xvars_indexes.py +200 -0
- tools/convert/extract_xvars_clip_features.py +374 -0
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/entry_points.txt +0 -0
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/licenses/LICENSE-COMMERCIAL +0 -0
- {opensportslib-0.2.0.dev1.dist-info → opensportslib-0.2.0.dev2.dist-info}/top_level.txt +0 -0
opensportslib/apis/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
from opensportslib.apis.base_task_model import BaseTaskModel
|
|
5
5
|
from opensportslib.apis.classification import ClassificationModel
|
|
6
6
|
from opensportslib.apis.localization import LocalizationModel
|
|
7
|
+
from opensportslib.apis.vqa import VQAModel
|
|
7
8
|
import warnings
|
|
8
9
|
warnings.filterwarnings("ignore")
|
|
9
10
|
|
|
@@ -12,4 +13,5 @@ __all__ = [
|
|
|
12
13
|
"BaseTaskModel",
|
|
13
14
|
"ClassificationModel",
|
|
14
15
|
"LocalizationModel",
|
|
16
|
+
"VQAModel",
|
|
15
17
|
]
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Public API for VQA tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from opensportslib.apis.base_task_model import BaseTaskModel
|
|
10
|
+
from opensportslib.core.config.accessors import get_split_annotation_path, get_system_gpu_count, get_train_execution, get_vqa_backend
|
|
11
|
+
from opensportslib.core.utils.config import expand, resolve_config_omega
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _set_model_checkpoint_path(config, weights: str | None) -> None:
|
|
15
|
+
if weights is None:
|
|
16
|
+
return
|
|
17
|
+
model = getattr(config, "MODEL", None)
|
|
18
|
+
if model is None:
|
|
19
|
+
return
|
|
20
|
+
load = getattr(model, "load", None)
|
|
21
|
+
if load is None:
|
|
22
|
+
from types import SimpleNamespace
|
|
23
|
+
|
|
24
|
+
load = SimpleNamespace()
|
|
25
|
+
setattr(model, "load", load)
|
|
26
|
+
if isinstance(load, dict):
|
|
27
|
+
load["checkpoint_path"] = weights
|
|
28
|
+
else:
|
|
29
|
+
setattr(load, "checkpoint_path", weights)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VQAModel(BaseTaskModel):
|
|
33
|
+
"""Top-level task wrapper for VQA."""
|
|
34
|
+
|
|
35
|
+
def _init_wandb(self, use_wandb: bool) -> None:
|
|
36
|
+
from opensportslib.core.utils.wandb import init_wandb
|
|
37
|
+
|
|
38
|
+
init_wandb(
|
|
39
|
+
self.config_path,
|
|
40
|
+
self.config,
|
|
41
|
+
run_id=os.environ["RUN_ID"],
|
|
42
|
+
use_wandb=use_wandb,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _worker_ddp(
|
|
47
|
+
rank,
|
|
48
|
+
world_size,
|
|
49
|
+
config_path,
|
|
50
|
+
config,
|
|
51
|
+
return_queue=None,
|
|
52
|
+
train_set=None,
|
|
53
|
+
valid_set=None,
|
|
54
|
+
use_wandb=False,
|
|
55
|
+
):
|
|
56
|
+
import torch
|
|
57
|
+
from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
|
|
58
|
+
from opensportslib.core.utils.ddp import ddp_cleanup, ddp_setup
|
|
59
|
+
from opensportslib.core.utils.wandb import init_wandb
|
|
60
|
+
from opensportslib.datasets.builder import build_dataset
|
|
61
|
+
|
|
62
|
+
logging.basicConfig(
|
|
63
|
+
level=logging.INFO,
|
|
64
|
+
format=f"[RANK {rank}] %(asctime)s | %(levelname)s | %(message)s",
|
|
65
|
+
force=True,
|
|
66
|
+
)
|
|
67
|
+
if rank != 0:
|
|
68
|
+
logging.getLogger().setLevel(logging.ERROR)
|
|
69
|
+
# Keep INFO on all ranks while debugging multi-GPU startup/hangs.
|
|
70
|
+
# This makes DDP failures visible instead of appearing as a silent stall.
|
|
71
|
+
#logging.getLogger().setLevel(logging.INFO)
|
|
72
|
+
|
|
73
|
+
is_ddp = world_size > 1
|
|
74
|
+
if is_ddp:
|
|
75
|
+
os.environ["RANK"] = str(rank)
|
|
76
|
+
os.environ["WORLD_SIZE"] = str(world_size)
|
|
77
|
+
os.environ["LOCAL_RANK"] = str(rank)
|
|
78
|
+
if "TORCH_DISTRIBUTED_DEBUG" not in os.environ:
|
|
79
|
+
os.environ["TORCH_DISTRIBUTED_DEBUG"] = "INFO"
|
|
80
|
+
torch.cuda.set_device(rank)
|
|
81
|
+
ddp_setup(rank, world_size)
|
|
82
|
+
logging.info(
|
|
83
|
+
"Initialized VQA DDP worker | rank=%s | world_size=%s | torch_distributed_debug=%s",
|
|
84
|
+
rank,
|
|
85
|
+
world_size,
|
|
86
|
+
os.environ.get("TORCH_DISTRIBUTED_DEBUG"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
if rank == 0:
|
|
91
|
+
init_wandb(
|
|
92
|
+
config_path,
|
|
93
|
+
config,
|
|
94
|
+
run_id=os.environ["RUN_ID"],
|
|
95
|
+
use_wandb=use_wandb,
|
|
96
|
+
)
|
|
97
|
+
train_data = build_dataset(config, train_set, None, split="train")
|
|
98
|
+
valid_data = build_dataset(config, valid_set, None, split="valid")
|
|
99
|
+
trainer = Trainer_VQA(config)
|
|
100
|
+
ckpt = trainer.train(
|
|
101
|
+
None,
|
|
102
|
+
train_data,
|
|
103
|
+
valid_data,
|
|
104
|
+
rank=rank,
|
|
105
|
+
world_size=world_size,
|
|
106
|
+
use_wandb=use_wandb,
|
|
107
|
+
)
|
|
108
|
+
if rank == 0 and return_queue is not None:
|
|
109
|
+
return_queue.put(ckpt)
|
|
110
|
+
finally:
|
|
111
|
+
if is_ddp:
|
|
112
|
+
ddp_cleanup()
|
|
113
|
+
|
|
114
|
+
def _resolve_split_path(self, split: str, override: str | None = None) -> str:
|
|
115
|
+
if override is not None:
|
|
116
|
+
return expand(override)
|
|
117
|
+
path = get_split_annotation_path(self.config, split)
|
|
118
|
+
if not path:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Could not resolve path for split '{split}'. "
|
|
121
|
+
f"Expected DATA.common.splits.{split}.annotation_path."
|
|
122
|
+
)
|
|
123
|
+
return expand(path)
|
|
124
|
+
|
|
125
|
+
def load_weights(self, weights: str | None = None, **kwargs) -> None:
|
|
126
|
+
del kwargs
|
|
127
|
+
if weights is None:
|
|
128
|
+
raise ValueError("`weights` must be provided to load_weights().")
|
|
129
|
+
from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
|
|
130
|
+
|
|
131
|
+
_set_model_checkpoint_path(self.config, weights)
|
|
132
|
+
self.trainer = Trainer_VQA(self.config)
|
|
133
|
+
self.trainer.load(weights)
|
|
134
|
+
self.last_loaded_weights = weights
|
|
135
|
+
self.best_checkpoint = weights
|
|
136
|
+
|
|
137
|
+
def train(
|
|
138
|
+
self,
|
|
139
|
+
train_set: str | None = None,
|
|
140
|
+
valid_set: str | None = None,
|
|
141
|
+
weights: str | None = None,
|
|
142
|
+
use_wandb: bool = True,
|
|
143
|
+
**kwargs,
|
|
144
|
+
) -> str | None:
|
|
145
|
+
del kwargs
|
|
146
|
+
import torch
|
|
147
|
+
import torch.multiprocessing as mp
|
|
148
|
+
|
|
149
|
+
self.config = resolve_config_omega(self.config, weights=weights)
|
|
150
|
+
if get_vqa_backend(self.config) == "qwen_xvars_infer":
|
|
151
|
+
raise ValueError("The 'qwen_xvars_infer' backend is inference-only and does not support train().")
|
|
152
|
+
train_set = self._resolve_split_path("train", train_set)
|
|
153
|
+
valid_set = self._resolve_split_path("valid", valid_set)
|
|
154
|
+
execution = get_train_execution(self.config)
|
|
155
|
+
backend = str(execution.get("training_backend", "placeholder")).lower()
|
|
156
|
+
if backend == "xvars_videochatgpt_lora":
|
|
157
|
+
world_size = torch.cuda.device_count() or get_system_gpu_count(self.config)
|
|
158
|
+
requested_gpus = get_system_gpu_count(self.config)
|
|
159
|
+
use_ddp = world_size > 1 and int(requested_gpus) > 1
|
|
160
|
+
logging.info(
|
|
161
|
+
"VQA train launch | mode=%s | world_size=%s",
|
|
162
|
+
"ddp" if use_ddp else "single",
|
|
163
|
+
world_size if use_ddp else 1,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
ctx = mp.get_context("spawn")
|
|
167
|
+
queue = ctx.SimpleQueue()
|
|
168
|
+
if use_ddp:
|
|
169
|
+
mp.spawn(
|
|
170
|
+
VQAModel._worker_ddp,
|
|
171
|
+
args=(world_size, self.config_path, self.config, queue, train_set, valid_set, use_wandb),
|
|
172
|
+
nprocs=world_size,
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
VQAModel._worker_ddp(
|
|
176
|
+
rank=0,
|
|
177
|
+
world_size=1,
|
|
178
|
+
config_path=self.config_path,
|
|
179
|
+
config=self.config,
|
|
180
|
+
return_queue=queue,
|
|
181
|
+
train_set=train_set,
|
|
182
|
+
valid_set=valid_set,
|
|
183
|
+
use_wandb=use_wandb,
|
|
184
|
+
)
|
|
185
|
+
ckpt = queue.get()
|
|
186
|
+
self.best_checkpoint = ckpt
|
|
187
|
+
self.last_loaded_weights = ckpt
|
|
188
|
+
return ckpt
|
|
189
|
+
|
|
190
|
+
raise ValueError(
|
|
191
|
+
f"Unsupported VQA training backend '{backend}'. "
|
|
192
|
+
"Only 'xvars_videochatgpt_lora' is supported."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def infer(
|
|
196
|
+
self,
|
|
197
|
+
test_set: str | None = None,
|
|
198
|
+
weights: str | None = None,
|
|
199
|
+
use_wandb: bool = True,
|
|
200
|
+
video_path: str | None = None,
|
|
201
|
+
question: str | None = None,
|
|
202
|
+
**kwargs,
|
|
203
|
+
) -> dict:
|
|
204
|
+
del kwargs
|
|
205
|
+
from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
|
|
206
|
+
from opensportslib.datasets.builder import build_dataset
|
|
207
|
+
from opensportslib.models.builder import build_model
|
|
208
|
+
from opensportslib.core.utils.config import select_device
|
|
209
|
+
|
|
210
|
+
direct_requested = video_path is not None or question is not None
|
|
211
|
+
if direct_requested and test_set is not None:
|
|
212
|
+
raise ValueError("Provide either `test_set` or `video_path`/`question`, not both.")
|
|
213
|
+
if direct_requested and (not video_path or not str(question or "").strip()):
|
|
214
|
+
raise ValueError("Direct VQA inference requires both `video_path` and a non-empty `question`.")
|
|
215
|
+
|
|
216
|
+
self.config = resolve_config_omega(self.config, weights=weights)
|
|
217
|
+
backend = get_vqa_backend(self.config)
|
|
218
|
+
effective_weights = weights if weights is not None else self.last_loaded_weights
|
|
219
|
+
if backend == "qwen_xvars_infer" and effective_weights is not None:
|
|
220
|
+
raise ValueError("The 'qwen_xvars_infer' backend does not support adapter weights for infer().")
|
|
221
|
+
_set_model_checkpoint_path(self.config, effective_weights)
|
|
222
|
+
self.trainer = Trainer_VQA(self.config)
|
|
223
|
+
if effective_weights is not None:
|
|
224
|
+
# Validate OpenSportsLib adapter metadata before allocating the base model.
|
|
225
|
+
self.trainer.load(effective_weights)
|
|
226
|
+
resolved_video_path = None
|
|
227
|
+
if direct_requested:
|
|
228
|
+
resolved_video_path = expand(str(video_path))
|
|
229
|
+
if not os.path.isfile(resolved_video_path):
|
|
230
|
+
raise FileNotFoundError(f"Video file not found: {resolved_video_path}")
|
|
231
|
+
device = select_device(self.config.SYSTEM)
|
|
232
|
+
model, _ = build_model(self.config, device)
|
|
233
|
+
if direct_requested:
|
|
234
|
+
test_data = [
|
|
235
|
+
{
|
|
236
|
+
"id": os.path.splitext(os.path.basename(resolved_video_path))[0],
|
|
237
|
+
"question": str(question).strip(),
|
|
238
|
+
"references": [],
|
|
239
|
+
"video_path": resolved_video_path,
|
|
240
|
+
"video_spatio_temporal_features": None,
|
|
241
|
+
"prior_prediction_text": "",
|
|
242
|
+
"labels": {},
|
|
243
|
+
"metadata": {},
|
|
244
|
+
"_xvars_demo_parity_direct_infer": True,
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
else:
|
|
248
|
+
test_set = self._resolve_split_path("test", test_set)
|
|
249
|
+
test_data = build_dataset(self.config, test_set, None, split="test")
|
|
250
|
+
self._init_wandb(use_wandb=use_wandb)
|
|
251
|
+
return self.trainer.infer(model, test_data, use_wandb=use_wandb)
|
|
252
|
+
|
|
253
|
+
def evaluate(
|
|
254
|
+
self,
|
|
255
|
+
test_set: str | None = None,
|
|
256
|
+
weights: str | None = None,
|
|
257
|
+
predictions: str | dict | None = None,
|
|
258
|
+
use_wandb: bool = True,
|
|
259
|
+
**kwargs,
|
|
260
|
+
) -> dict | str | None:
|
|
261
|
+
del kwargs
|
|
262
|
+
from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
|
|
263
|
+
from opensportslib.datasets.builder import build_dataset
|
|
264
|
+
|
|
265
|
+
self.config = resolve_config_omega(self.config, weights=weights)
|
|
266
|
+
test_set = self._resolve_split_path("test", test_set)
|
|
267
|
+
test_data = build_dataset(self.config, test_set, None, split="test")
|
|
268
|
+
self._init_wandb(use_wandb=use_wandb)
|
|
269
|
+
if predictions is None:
|
|
270
|
+
predictions = self.infer(test_set=test_set, weights=weights, use_wandb=use_wandb)
|
|
271
|
+
elif isinstance(predictions, str):
|
|
272
|
+
import json
|
|
273
|
+
|
|
274
|
+
with open(expand(predictions), encoding="utf-8") as f:
|
|
275
|
+
predictions = json.load(f)
|
|
276
|
+
self.trainer = self.trainer or Trainer_VQA(self.config)
|
|
277
|
+
return self.trainer.evaluate(predictions, test_data, use_wandb=use_wandb)
|
|
278
|
+
|
|
279
|
+
def save_predictions(
|
|
280
|
+
self,
|
|
281
|
+
output_path: str,
|
|
282
|
+
predictions: dict,
|
|
283
|
+
output_format: str = "osl",
|
|
284
|
+
) -> str:
|
|
285
|
+
"""Persist VQA predictions, optionally as X-VARS-compatible rows."""
|
|
286
|
+
|
|
287
|
+
if str(output_format).lower() != "xvars":
|
|
288
|
+
return super().save_predictions(output_path, predictions)
|
|
289
|
+
|
|
290
|
+
import json
|
|
291
|
+
|
|
292
|
+
payload = self._to_xvars_prediction_rows(predictions)
|
|
293
|
+
dst = expand(output_path)
|
|
294
|
+
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
|
|
295
|
+
with open(dst, "w", encoding="utf-8") as f:
|
|
296
|
+
json.dump(payload, f)
|
|
297
|
+
return dst
|
|
298
|
+
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _to_xvars_prediction_rows(predictions: dict[str, Any]) -> list[dict[str, Any]]:
|
|
301
|
+
rows = []
|
|
302
|
+
for item in predictions.get("data", []) if isinstance(predictions, dict) else []:
|
|
303
|
+
video_path = str(item.get("video_path") or "")
|
|
304
|
+
video_name = os.path.splitext(os.path.basename(video_path))[0] if video_path else str(item.get("id"))
|
|
305
|
+
rows.append(
|
|
306
|
+
{
|
|
307
|
+
"id": item.get("id"),
|
|
308
|
+
"video_name": video_name,
|
|
309
|
+
"Q": item.get("question"),
|
|
310
|
+
"pred": item.get("answer_text"),
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
return rows
|
opensportslib/cli.py
CHANGED
|
@@ -11,6 +11,8 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|
|
11
11
|
parser.add_argument("command", choices=["setup"])
|
|
12
12
|
parser.add_argument("--pyg", action="store_true")
|
|
13
13
|
parser.add_argument("--dali", action="store_true")
|
|
14
|
+
parser.add_argument("--xvars", action="store_true")
|
|
15
|
+
parser.add_argument("--qwen", action="store_true")
|
|
14
16
|
|
|
15
17
|
args = parser.parse_args(argv)
|
|
16
18
|
|
|
@@ -18,6 +20,8 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|
|
18
20
|
setup(
|
|
19
21
|
pyg=args.pyg,
|
|
20
22
|
dali=args.dali,
|
|
23
|
+
xvars=args.xvars,
|
|
24
|
+
qwen=args.qwen
|
|
21
25
|
)
|
|
22
26
|
return 0
|
|
23
27
|
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
TASK: VQA
|
|
2
|
+
VERSION: 2
|
|
3
|
+
|
|
4
|
+
SYSTEM:
|
|
5
|
+
paths:
|
|
6
|
+
log_dir: ./logs
|
|
7
|
+
save_dir: ./checkpoints_vqa_qwen
|
|
8
|
+
work_dir: ./checkpoints_vqa_qwen
|
|
9
|
+
device: cuda
|
|
10
|
+
gpu:
|
|
11
|
+
count: 1
|
|
12
|
+
id: 0
|
|
13
|
+
reproducibility:
|
|
14
|
+
use_seed: true
|
|
15
|
+
seed: 42
|
|
16
|
+
|
|
17
|
+
DATA:
|
|
18
|
+
common:
|
|
19
|
+
dataset_name: OSL-XFoul
|
|
20
|
+
data_root: /home/vorajv/dataset/OSL-XFoul
|
|
21
|
+
feature_index: /home/vorajv/dataset/OSL-XFoul/feature_index.json
|
|
22
|
+
prediction_index: /home/vorajv/dataset/OSL-XFoul/prediction_index.json
|
|
23
|
+
runtime:
|
|
24
|
+
loader_backend: opencv
|
|
25
|
+
splits:
|
|
26
|
+
train:
|
|
27
|
+
annotation_path: /home/vorajv/dataset/OSL-XFoul/train.json
|
|
28
|
+
source_path: /home/vorajv/dataset/OSL-XFoul
|
|
29
|
+
dataloader:
|
|
30
|
+
batch_size: 1
|
|
31
|
+
shuffle: true
|
|
32
|
+
num_workers: 0
|
|
33
|
+
pin_memory: false
|
|
34
|
+
mp_context: spawn
|
|
35
|
+
persistent_workers: false
|
|
36
|
+
valid:
|
|
37
|
+
annotation_path: /home/vorajv/dataset/OSL-XFoul/valid.json
|
|
38
|
+
source_path: /home/vorajv/dataset/OSL-XFoul
|
|
39
|
+
dataloader:
|
|
40
|
+
batch_size: 1
|
|
41
|
+
shuffle: false
|
|
42
|
+
num_workers: 0
|
|
43
|
+
pin_memory: false
|
|
44
|
+
mp_context: spawn
|
|
45
|
+
persistent_workers: false
|
|
46
|
+
test:
|
|
47
|
+
annotation_path: /home/vorajv/dataset/OSL-XFoul/test.json
|
|
48
|
+
source_path: /home/vorajv/dataset/OSL-XFoul
|
|
49
|
+
dataloader:
|
|
50
|
+
batch_size: 1
|
|
51
|
+
shuffle: false
|
|
52
|
+
num_workers: 0
|
|
53
|
+
pin_memory: false
|
|
54
|
+
mp_context: spawn
|
|
55
|
+
persistent_workers: false
|
|
56
|
+
inputs:
|
|
57
|
+
video:
|
|
58
|
+
modality: video
|
|
59
|
+
representation: raw
|
|
60
|
+
source:
|
|
61
|
+
format: mp4
|
|
62
|
+
sampling:
|
|
63
|
+
num_frames: 100
|
|
64
|
+
input_fps: 25
|
|
65
|
+
target_fps: 17
|
|
66
|
+
start_frame: 63
|
|
67
|
+
end_frame: 87
|
|
68
|
+
transform: {}
|
|
69
|
+
augmentations: {}
|
|
70
|
+
params: {}
|
|
71
|
+
question:
|
|
72
|
+
modality: text
|
|
73
|
+
representation: raw
|
|
74
|
+
source:
|
|
75
|
+
format: json
|
|
76
|
+
sampling: {}
|
|
77
|
+
transform: {}
|
|
78
|
+
augmentations: {}
|
|
79
|
+
params: {}
|
|
80
|
+
|
|
81
|
+
MODEL:
|
|
82
|
+
runtime:
|
|
83
|
+
dtype: fp16
|
|
84
|
+
device: auto
|
|
85
|
+
compile: false
|
|
86
|
+
freeze: false
|
|
87
|
+
load:
|
|
88
|
+
checkpoint_path: null
|
|
89
|
+
pretrained: false
|
|
90
|
+
strict: true
|
|
91
|
+
map_location: null
|
|
92
|
+
format: auto
|
|
93
|
+
components:
|
|
94
|
+
video_encoder:
|
|
95
|
+
kind: encoder
|
|
96
|
+
source:
|
|
97
|
+
provider: opensportslib
|
|
98
|
+
name: xvars_clip_features
|
|
99
|
+
load:
|
|
100
|
+
weights_path: /home/vorajv/X-VARS/weights/14_model.pth.tar
|
|
101
|
+
params:
|
|
102
|
+
feature_source: indexed_or_raw_clip
|
|
103
|
+
vision_tower: openai/clip-vit-large-patch14
|
|
104
|
+
feature_dim: 1024
|
|
105
|
+
overrides: {}
|
|
106
|
+
mm_projector:
|
|
107
|
+
kind: projector
|
|
108
|
+
source:
|
|
109
|
+
provider: opensportslib
|
|
110
|
+
params:
|
|
111
|
+
input_dim: 1024
|
|
112
|
+
overrides: {}
|
|
113
|
+
llm_decoder:
|
|
114
|
+
kind: decoder
|
|
115
|
+
source:
|
|
116
|
+
provider: huggingface
|
|
117
|
+
# Qwen/Qwen2.5-7B-Instruct or Qwen/Qwen3.5-9B-Base
|
|
118
|
+
name: Qwen/Qwen3.5-9B-Base
|
|
119
|
+
params:
|
|
120
|
+
# Qwen/Qwen2.5-7B-Instruct or Qwen/Qwen3.5-9B-Base
|
|
121
|
+
repo_id: Qwen/Qwen3.5-9B-Base
|
|
122
|
+
overrides: {}
|
|
123
|
+
topology:
|
|
124
|
+
- from: video_encoder
|
|
125
|
+
to: mm_projector
|
|
126
|
+
- from: mm_projector
|
|
127
|
+
to: llm_decoder
|
|
128
|
+
metadata:
|
|
129
|
+
backend: qwen_xvars_infer
|
|
130
|
+
|
|
131
|
+
IO:
|
|
132
|
+
inputs:
|
|
133
|
+
video: video_encoder
|
|
134
|
+
question: llm_decoder
|
|
135
|
+
outputs:
|
|
136
|
+
answer_text: llm_decoder
|
|
137
|
+
explanation_text: llm_decoder
|
|
138
|
+
|
|
139
|
+
TRAIN:
|
|
140
|
+
trainer:
|
|
141
|
+
type: vqa
|
|
142
|
+
|
|
143
|
+
epochs: 3
|
|
144
|
+
|
|
145
|
+
criterion:
|
|
146
|
+
type: CrossEntropyLoss
|
|
147
|
+
|
|
148
|
+
optimizer:
|
|
149
|
+
type: AdamW
|
|
150
|
+
lr: 0.0002
|
|
151
|
+
weight_decay: 0.001
|
|
152
|
+
|
|
153
|
+
scheduler:
|
|
154
|
+
type: constant
|
|
155
|
+
|
|
156
|
+
execution:
|
|
157
|
+
enabled: true
|
|
158
|
+
training_backend: xvars_videochatgpt_lora
|
|
159
|
+
feature_backend: xvars_clip
|
|
160
|
+
view_sampling_policy: random_train_deterministic_eval
|
|
161
|
+
acc_grad_iter: 8
|
|
162
|
+
log_interval: 1
|
|
163
|
+
dry_run: false
|
|
164
|
+
|
|
165
|
+
xvars:
|
|
166
|
+
feature_mode: strict_xvars
|
|
167
|
+
projection_path: null
|
|
168
|
+
|
|
169
|
+
prompt:
|
|
170
|
+
style: detailed
|
|
171
|
+
system_prompt: You are a football video assistant. Answer the VQA question using the provided video context and referee priors.
|
|
172
|
+
include_priors: true
|
|
173
|
+
prediction_prior_adapter: xvars_referee
|
|
174
|
+
prior_fields: [action, offence, contact, bodypart]
|
|
175
|
+
video_token_len: 300
|
|
176
|
+
|
|
177
|
+
generation:
|
|
178
|
+
max_new_tokens: 128
|
|
179
|
+
temperature: 0.0
|
|
180
|
+
|
|
181
|
+
eval_profile:
|
|
182
|
+
metric_set: [exact_match, contains_match, token_f1, referee_semantic]
|
|
183
|
+
aggregation: mean
|
|
184
|
+
exclusions: []
|
|
185
|
+
|
|
186
|
+
sft:
|
|
187
|
+
max_seq_length: 480
|
|
188
|
+
include_video_tokens: true
|
|
189
|
+
disable_tqdm: false
|
|
190
|
+
reference_mode: all
|
|
191
|
+
append_eos_token: true
|
|
192
|
+
gradient_checkpointing: true
|
|
193
|
+
save_strategy: epoch
|
|
194
|
+
|
|
195
|
+
hf:
|
|
196
|
+
tokenizer_id: ${MODEL.components.llm_decoder.params.repo_id}
|
|
197
|
+
prefer_cuda: true
|
|
198
|
+
local_files_only: false
|
|
199
|
+
device_map: auto
|
|
200
|
+
offload_folder: ./hf_offload_qwen
|
|
201
|
+
|
|
202
|
+
lora:
|
|
203
|
+
r: 16
|
|
204
|
+
alpha: 32
|
|
205
|
+
dropout: 0.05
|
|
206
|
+
bias: none
|
|
207
|
+
prepare_kbit: true
|
|
208
|
+
target_modules: [mm_projector, upsample_features, up_proj, down_proj, gate_proj, k_proj, q_proj, v_proj, o_proj]
|
|
209
|
+
exclude_modules: '^base_lm\.model\.mm_projector$'
|
|
210
|
+
|
|
211
|
+
quantization:
|
|
212
|
+
enabled: false
|
|
213
|
+
load_in_4bit: true
|
|
214
|
+
bnb_4bit_quant_type: nf4
|
|
215
|
+
compute_dtype: float16
|
|
216
|
+
bnb_4bit_use_double_quant: true
|
|
217
|
+
|
|
218
|
+
checkpoint:
|
|
219
|
+
save_adapter: true
|
|
220
|
+
merge_and_save: false
|
|
221
|
+
|
|
222
|
+
selection:
|
|
223
|
+
monitor: loss
|
|
224
|
+
mode: min
|
|
225
|
+
|
|
226
|
+
checkpoint:
|
|
227
|
+
save_every: 1
|
|
228
|
+
save_best: true
|