Files
icassp2027-experiment/test_local_turn_features.py
T

510 lines
30 KiB
Python
Raw Normal View History

"""CPU tests for B1 alignment, causal pooling, fusion, and train/infer round trips."""
from __future__ import annotations
import copy
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from types import SimpleNamespace
2026-08-27 15:24:59 +08:00
from unittest.mock import patch
import numpy as np
import torch
2026-08-27 15:08:38 +08:00
from PIL import Image, ImageDraw
from easycom_head import TurnPredictionHead, checkpoint_head_config
from extract_turn_local_features import (
FrozenQwenLocalEncoder, ego4d_clip_boxes, extract_local,
validate_source_timeline,
model_config_digest,
2026-08-27 15:08:38 +08:00
box_coordinate_offset, frame_crops, parse_args, preview_boxes,
2026-08-27 15:24:59 +08:00
TESTED_TRANSFORMERS_VERSIONS, validate_local_transformers_version,
main as extract_main,
)
from local_turn_features import (
LOCAL_FORMAT, causal_frame_ticks, crop_box, easycom_boxes, load_local_features,
local_head_dimensions, pooled_recent_visual, reference_alignment,
)
from train_turn_head import _load_examples
def fixture(n=20, name="Session_4", dataset="easycom"):
times = (torch.arange(n, dtype=torch.float64) + 1) / 5
reference = {"states": torch.randn(n, 4, 8), "label_indices": torch.arange(n),
"times": times, "mode": "open-stream-replay", "start": 0}
side = {"format": LOCAL_FORMAT, "dataset": dataset, "sample_id": name,
"times": times.clone(), "label_indices": torch.arange(n), "partial": False,
"visual": torch.randn(n, 6), "audio": torch.randn(n, 5),
"visual_valid": torch.ones(n, dtype=torch.bool),
"audio_valid": torch.ones(n, dtype=torch.bool),
"config": {"visual_dim": 6, "audio_dim": 5, "encoder_id": "synthetic-test",
"causal_policy": "half-open-trailing-window", "uses_speech_labels": False}}
side["visual_valid"][0] = False
side["visual"][0] = 0
labels = np.tile(np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 1]], dtype=np.float32), (n // 4 + 1, 1))[:n]
return reference, side, labels
class LocalTests(unittest.TestCase):
def setUp(self):
torch.set_num_threads(1)
2026-08-27 15:24:59 +08:00
def test_tested_transformers_versions(self):
for version in ("4.52.3", "4.57.6"):
self.assertEqual(validate_local_transformers_version(version), version)
for version in ("4.50.0.dev0", "4.57.6.dev0", "5.0.0"):
with self.subTest(version=version), self.assertRaisesRegex(RuntimeError, "found " + version):
validate_local_transformers_version(version)
def test_half_open_grid_and_pooling(self):
self.assertEqual(list(causal_frame_ticks(.2, 2)), [0])
self.assertEqual(list(causal_frame_ticks(1., 2)), [0, 1])
ts = torch.tensor([0., .5, 1., 1.5], dtype=torch.float64)
vectors = torch.tensor([[2.], [4.], [90.], [100.]])
valid = torch.ones(4, dtype=torch.bool)
value, present = pooled_recent_visual(ts, vectors, valid, 1., 1.)
self.assertTrue(present)
self.assertEqual(value.item(), 3.) # no frame at t=1
value, _ = pooled_recent_visual(ts, vectors, valid, 1.2, 1.)
self.assertEqual(value.item(), 47.) # .5 and 1.0, not 0.0
value, present = pooled_recent_visual(ts, vectors, valid * False, 1., 1.)
self.assertFalse(present)
self.assertEqual(value.item(), 0.)
def test_box_formats_and_crop(self):
person = {"Participant_ID": -1, "x1": 2, "y1": 3, "x2": 7, "y2": 9}
faces = easycom_boxes([{"Frame_Number": 1, "Participants": [person]}], "face")
heads = easycom_boxes([{"Frame_Number": 0, "Participants": person}], "head")
self.assertEqual(faces, heads)
self.assertEqual(crop_box([202., 3., 207., 9.], 1920, 1080, offset_x=200), (2, 3, 7, 9))
self.assertIsNone(crop_box([4., 4., 3., 5.], 10, 10))
self.assertIsNone(crop_box([float("nan"), 4., 9., 5.], 10, 10))
def test_ego4d_clip_frames(self):
boxes = [{"frame": 3, "clip_frame": None, "x": 1, "y": 2, "width": 3, "height": 4}]
person = {"person_id": "x", "camera_wearer": False, "tracking_paths": [{"track": boxes}, {"track": boxes}]}
wearer = {**person, "camera_wearer": True}
actual = ego4d_clip_boxes({"persons": [wearer, person]})
self.assertEqual(actual, {3: [{"id": "x", "xyxy": [1., 2., 4., 6.]}]})
2026-08-27 15:08:38 +08:00
def test_dataset_specific_coordinate_defaults_and_explicit_overrides(self):
required = ["--data-root", "/raw", "--feature-root", "/global",
"--target-root", "/targets", "--output-root", "/local", "--samples", "4"]
self.assertEqual(parse_args(required).box_coordinate_space, "encoded")
self.assertEqual(parse_args(required + ["--dataset", "ego4d"]).box_coordinate_space, "camera")
self.assertEqual(parse_args(required + ["--box-coordinate-space", "camera"]).box_coordinate_space, "camera")
# An appended correction overrides an old LOCAL_ARGS array in the shell.
self.assertEqual(parse_args(required + ["--box-coordinate-space", "camera",
"--box-coordinate-space", "encoded"]).box_coordinate_space, "encoded")
def test_frame_crops_subtract_each_clips_banner_before_encoding(self):
expected = (100, 80, 160, 140)
image = Image.new("RGB", (1920, 1080))
ImageDraw.Draw(image).rectangle(expected, fill="red")
clips, annotations = [], []
for width in (2123, 2124, 2203, 1920):
offset = width - 1920
clips.append(SimpleNamespace(crop=(offset, 0, 1920, 1080), dense_start=float(len(clips))))
annotations.append({0: [{"id": "3", "xyxy": [100 + offset, 80, 160 + offset, 140]}]})
media = SimpleNamespace(clips=clips,
_source_frame_for_time=lambda t: (int(t), 0, 0.),
frame_at_or_before=lambda t: image.copy())
args = SimpleNamespace(box_coordinate_space="encoded", crop_expansion=1.)
for index, clip in enumerate(clips):
with self.subTest(crop=clip.crop):
_, crops, records = frame_crops(media, annotations, float(index), args)
self.assertEqual(records[0]["box"], expected)
self.assertEqual(records[0]["coordinate_offset"], [clip.crop[0], 0])
self.assertEqual(records[0]["annotation_box"], annotations[index][0][0]["xyxy"])
self.assertEqual(crops[0].size, (60, 60))
self.assertEqual(crops[0].getpixel((30, 30)), (255, 0, 0))
def test_cropped_camera_annotations_and_uncropped_ego4d_are_not_shifted_twice(self):
clip = SimpleNamespace(crop=(204, 12, 1920, 1080), dense_start=0.)
self.assertEqual(box_coordinate_offset(clip, "encoded"), (204, 12))
self.assertEqual(box_coordinate_offset(clip, "camera"), (0, 0))
self.assertEqual(box_coordinate_offset(SimpleNamespace(crop=None), "encoded"), (0, 0))
with self.assertRaisesRegex(ValueError, "coordinate space"):
box_coordinate_offset(clip, "typo")
media = SimpleNamespace(clips=[clip], _source_frame_for_time=lambda t: (0, 0, 0.),
frame_at_or_before=lambda t: Image.new("RGB", (1920, 1080)))
annotations = [{0: [{"id": "3", "xyxy": [100, 80, 160, 140]}]}]
args = SimpleNamespace(box_coordinate_space="camera", crop_expansion=1.)
_, _, records = frame_crops(media, annotations, .1, args)
self.assertEqual(records[0]["box"], (100, 80, 160, 140))
# Encoded boxes translate on BOTH axes, then clip at the visible edge.
annotations = [{0: [{"id": "3", "xyxy": [190, 4, 230, 50]}]}]
args.box_coordinate_space = "encoded"
_, _, records = frame_crops(media, annotations, .1, args)
self.assertEqual(records[0]["box"], (0, 0, 26, 38))
def test_preview_records_the_same_transformation_used_for_extraction(self):
clip = SimpleNamespace(path=Path("source.mp4"), width=2124, height=1080,
crop=(204, 0, 1920, 1080), dense_start=0.)
media = SimpleNamespace(clips=[clip], _source_frame_for_time=lambda t: (0, 0, 0.),
frame_at_or_before=lambda t: Image.new("RGB", (1920, 1080)))
annotations = [{0: [{"id": "3", "xyxy": [304, 80, 364, 140]}]}]
with tempfile.TemporaryDirectory() as directory:
args = SimpleNamespace(box_coordinate_space="encoded", crop_expansion=1.,
video_fps=2., output_root=Path(directory))
preview_boxes(media, annotations, args, "Session_4", .2)
folder = Path(directory) / "Session_4_preview"
audit = json.loads((folder / "boxes.json").read_text())
self.assertEqual(audit[0]["coordinate_offset"], [204, 0])
self.assertEqual(audit[0]["boxes"][0]["box"], [100, 80, 160, 140])
self.assertTrue((folder / audit[0]["image"]).is_file())
def test_sidecar_alignment_contract_and_modalities(self):
reference, side, _ = fixture()
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "Session_4.pt"
for mode, width in (("visual", 7), ("audio", 6), ("av", 13)):
torch.save(side, path)
features, contract = load_local_features(path, reference, sample_id="Session_4", dataset="easycom", modalities=mode)
self.assertEqual(features.shape, (20, width))
dims = local_head_dimensions(contract)
self.assertEqual(dims["local_visual_dim"], 0 if mode == "audio" else 6)
for field, value in (("times", side["times"] + .2), ("label_indices", torch.arange(20) + 1),
("sample_id", "Session_5"), ("partial", True),
("visual", torch.full((20, 6), float("nan"))),
("visual_valid", torch.ones(20))):
broken = {**side, field: value}
torch.save(broken, path)
with self.subTest(field=field), self.assertRaises(ValueError):
load_local_features(path, reference, sample_id="Session_4", dataset="easycom", modalities="av")
torch.save(side, path)
wrong_contract = copy.deepcopy(contract)
wrong_contract["config"]["encoder_id"] = "other-weights"
with self.assertRaisesRegex(ValueError, "contract"):
load_local_features(path, reference, sample_id="Session_4", dataset="easycom", modalities="av", expected_contract=wrong_contract)
with self.assertRaisesRegex(ValueError, "times"):
reference_alignment({**reference, "times": reference["times"] + .2})
def test_head_shapes_gradients_missing_and_roundtrip(self):
states = torch.randn(3, 4, 8)
for v, a in ((0, 0), (6, 0), (0, 5), (6, 5)):
for projection in (0, 3):
head = TurnPredictionHead(hidden_size=8, num_input_layers=4, layer_fusion="concat-mlp",
local_visual_dim=v, local_audio_dim=a, local_projection_dim=projection,
transition_auxiliary=True, dropout=0)
width = sum(d + 1 for d in (v, a) if d)
local = torch.randn(3, width)
offset = 0
for d in (v, a):
if d:
local[:, offset + d] = torch.tensor([0., 1., 1.])
offset += d + 1
logits, transition = head.forward_with_transition(states, local)
self.assertEqual(logits.shape, (3, 10, 3))
self.assertEqual(transition.shape, (3, 10))
(logits.square().mean() + transition.square().mean()).backward()
self.assertIsNotNone(head.net[1].weight.grad)
if width:
# Changing invalid raw features cannot change the readout.
changed = local.clone()
offset = 0
for d in (v, a):
if d:
changed[0, offset:offset + d] = 1000
offset += d + 1
self.assertTrue(torch.equal(head(states, local)[0], head(states, changed)[0]))
with self.assertRaisesRegex(ValueError, "requires aligned"):
head(states)
config = {"hidden_size": 8, "num_input_layers": 4, "layer_fusion": "concat-mlp",
"local_visual_dim": v, "local_audio_dim": a, "local_projection_dim": projection,
"transition_auxiliary": True, "dropout": 0}
restored = TurnPredictionHead(**checkpoint_head_config({"head_config": config}))
restored.load_state_dict(head.state_dict())
self.assertTrue(torch.equal(restored(states, local), head(states, local)))
def test_legacy_head_parameter_keys_and_values(self):
torch.manual_seed(42)
old = torch.nn.Sequential(torch.nn.LayerNorm(8), torch.nn.Linear(8, 4), torch.nn.GELU(),
torch.nn.Dropout(.1), torch.nn.Linear(4, 30))
torch.manual_seed(42)
head = TurnPredictionHead(hidden_size=8)
self.assertEqual(set(head.state_dict()), {f"net.{key}" for key in old.state_dict()})
for key, value in old.state_dict().items():
self.assertTrue(torch.equal(value, head.state_dict()[f"net.{key}"]))
def test_sparse_selection_keeps_local_rows(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for folder in ("features", "local", "targets"):
(root / folder).mkdir()
reference, side, targets = fixture()
torch.save(reference, root / "features/Session_4.pt")
torch.save(side, root / "local/Session_4.pt")
np.save(root / "targets/Session_4.npy", targets)
data = _load_examples(root / "features", root / "targets", ["Session_4"], "open-stream-replay",
"easycom", {"Session_4": {1, 4}}, root / "local", "audio")
self.assertTrue(torch.equal(data[-1][:, :5], side["audio"][[1, 4]]))
self.assertTrue(torch.equal(data[0], reference["states"][[1, 4]]))
self.assertTrue(torch.equal(data[1][:, 0], torch.from_numpy(targets[[2, 5]])))
def test_source_inventory_rejects_different_clip_order(self):
source = {"path": "/old/00-00-000.mp4", "source_frames": 1200, "dense_start": 0., "duration": 60.}
media = SimpleNamespace(clips=[object()], metadata=lambda: {"source_clips": [source]})
validate_source_timeline({"direct_media": {"source_clips": [source]}}, media)
bad = {**source, "path": "/old/01-00-000.mp4"}
with self.assertRaisesRegex(ValueError, "timeline differs"):
validate_source_timeline({"direct_media": {"source_clips": [bad]}}, media)
def test_config_digest_ignores_only_checkpoint_location(self):
a = {"_name_or_path": "/old/model", "audio_config": {"_name_or_path": "/old/model", "output_dim": 8}}
b = {"_name_or_path": "/new/model", "audio_config": {"_name_or_path": "/new/model", "output_dim": 8}}
self.assertEqual(model_config_digest(a), model_config_digest(b))
b["audio_config"]["output_dim"] = 16
self.assertNotEqual(model_config_digest(a), model_config_digest(b))
def test_encoder_image_pool_splits_people_before_mean(self):
encoder = FrozenQwenLocalEncoder.__new__(FrozenQwenLocalEncoder)
encoder.visual_dim, encoder.device, encoder.image_batch_size, encoder.max_pixels = 2, torch.device("cpu"), 8, 50176
encoder.processor = SimpleNamespace(image_processor=lambda **kwargs: {
"pixel_values": torch.ones(12, 2), "image_grid_thw": torch.tensor([[1, 2, 2], [1, 2, 4]])})
encoder.model = SimpleNamespace(visual=SimpleNamespace(dtype=torch.float32),
config=SimpleNamespace(vision_config=SimpleNamespace(spatial_merge_size=2)),
get_image_features=lambda *_: torch.tensor([[1., 1.], [3., 3.], [5., 5.]]))
actual = encoder.images([object(), object()])
self.assertTrue(torch.equal(actual, torch.tensor([[1., 1.], [4., 4.]])))
2026-08-27 15:24:59 +08:00
# A future API returning per-image tensors must not silently change
# the pooling rule or crash with an uninformative AttributeError.
encoder.model.get_image_features = lambda *_: (torch.ones(1, 2), torch.ones(2, 2))
with self.assertRaisesRegex(RuntimeError, "Unexpected image encoder output"):
encoder.images([object(), object()])
def test_extraction_saves_runtime_version_and_rejects_mixed_versions(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for folder in ("features", "targets", "local"):
(root / folder).mkdir()
reference, side, labels = fixture()
torch.save(reference, root / "features/Session_4.pt")
np.save(root / "targets/Session_4.npy", labels)
media = SimpleNamespace(clips=[], metadata=lambda: {})
argv = ["extract_turn_local_features.py", "--data-root", str(root),
"--feature-root", str(root / "features"), "--target-root", str(root / "targets"),
"--output-root", str(root / "local"), "--samples", "4", "--overwrite"]
contracts = []
for version in TESTED_TRANSFORMERS_VERSIONS:
fake_encoder = SimpleNamespace(transformers_version=version, visual_dim=6, audio_dim=5,
model_config_sha256="same-checkpoint")
with patch.object(sys, "argv", argv), \
patch.dict(sys.modules, {"transformers": SimpleNamespace(__version__=version)}), \
patch("extract_turn_local_features.build_media_and_boxes", return_value=(media, [])), \
patch("extract_turn_local_features.FrozenQwenLocalEncoder", return_value=fake_encoder), \
patch("extract_turn_local_features.extract_local", return_value=copy.deepcopy(side)):
extract_main()
output = root / "local/Session_4.pt"
_, contract = load_local_features(output, reference, sample_id="Session_4",
dataset="easycom", modalities="av")
self.assertEqual(contract["config"]["transformers"], version)
contracts.append(contract)
with self.assertRaisesRegex(ValueError, "contract differs"):
load_local_features(output, reference, sample_id="Session_4", dataset="easycom",
modalities="av", expected_contract=contracts[0])
def test_unsupported_version_fails_before_media_and_preview_still_works(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
reference, _, labels = fixture()
torch.save(reference, root / "Session_4.pt")
np.save(root / "Session_4.npy", labels)
argv = ["extract_turn_local_features.py", "--data-root", str(root),
"--feature-root", str(root), "--target-root", str(root),
"--output-root", str(root / "preview"), "--samples", "4"]
with patch.object(sys, "argv", argv), \
patch.dict(sys.modules, {"transformers": SimpleNamespace(__version__="5.0.0")}), \
patch("extract_turn_local_features.build_media_and_boxes") as build:
with self.assertRaisesRegex(RuntimeError, "found 5.0.0"):
extract_main()
build.assert_not_called()
media = SimpleNamespace(clips=[], metadata=lambda: {})
with patch.object(sys, "argv", argv + ["--preview-only"]), \
patch.dict(sys.modules, {"transformers": SimpleNamespace(__version__="5.0.0")}), \
patch("extract_turn_local_features.build_media_and_boxes", return_value=(media, [])), \
patch("extract_turn_local_features.preview_boxes") as preview, \
patch("extract_turn_local_features.FrozenQwenLocalEncoder") as encoder:
extract_main()
preview.assert_called_once()
encoder.assert_not_called()
def test_extraction_never_encodes_future_audio_and_saves_ragged_people(self):
class Media:
clips = [SimpleNamespace(crop=None, dense_start=0.)]
def _source_frame_for_time(self, t):
return 0, round(t * 20), t
def frame_at_or_before(self, t):
return Image.new("RGB", (16, 16))
def audio_slice(self, start, end):
return np.arange(round(start * 16000), round(end * 16000), dtype=np.float32)
class Encoder:
visual_dim = audio_dim = 2
def __init__(self):
self.ends = []
def images(self, crops):
return torch.ones(len(crops), 2)
def audio(self, waveform):
self.ends.append(float(waveform[-1]))
return torch.tensor([float(waveform[0]), float(waveform[-1])])
encoder = Encoder()
args = SimpleNamespace(video_fps=2., visual_window_seconds=1., audio_window_seconds=1.,
box_coordinate_space="camera", crop_expansion=1., no_progress=True)
annotations = [{0: [{"id": "1", "xyxy": [0., 0., 8., 8.]}]}]
result = extract_local(Media(), annotations, encoder, torch.arange(1, 11, dtype=torch.float64) / 5, args)
self.assertEqual(encoder.ends, [3200 * (i + 1) - 1 for i in range(10)])
self.assertEqual(result["persons"]["frame_offsets"].tolist(), [0, 1, 1, 1, 1])
self.assertTrue(result["visual_valid"][:5].all())
self.assertFalse(result["visual_valid"][5:].any())
self.assertTrue(result["audio_valid"].all())
2026-08-27 15:24:59 +08:00
@unittest.skipUnless(importlib.util.find_spec("transformers"), "optional: install a tested Transformers version")
class TinyQwenEncoderTests(unittest.TestCase):
2026-08-27 15:24:59 +08:00
def setUp(self):
import transformers
2026-08-27 15:24:59 +08:00
if transformers.__version__ not in TESTED_TRANSFORMERS_VERSIONS:
self.skipTest(f"Encoder API tests require one of {TESTED_TRANSFORMERS_VERSIONS}")
torch.set_num_threads(1)
def tiny_model_and_processor(self):
from transformers import (
Qwen2_5OmniThinkerConfig, Qwen2_5OmniThinkerForConditionalGeneration,
Qwen2VLImageProcessor, WhisperFeatureExtractor,
)
config = Qwen2_5OmniThinkerConfig(
audio_config=dict(d_model=16, encoder_layers=1, encoder_attention_heads=2,
encoder_ffn_dim=32, output_dim=8),
vision_config=dict(depth=1, hidden_size=16, intermediate_size=32, num_heads=2,
out_hidden_size=8, fullatt_block_indexes=[0]),
text_config=dict(vocab_size=32, hidden_size=8, intermediate_size=16,
num_hidden_layers=1, num_attention_heads=2, num_key_value_heads=2),
)
2026-08-27 15:24:59 +08:00
model = Qwen2_5OmniThinkerForConditionalGeneration(config).eval().requires_grad_(False)
processor = SimpleNamespace(image_processor=Qwen2VLImageProcessor(),
feature_extractor=WhisperFeatureExtractor(feature_size=128))
return model, processor
def test_real_encoder_apis_without_downloading_weights(self):
encoder = FrozenQwenLocalEncoder.__new__(FrozenQwenLocalEncoder)
2026-08-27 15:24:59 +08:00
encoder.model, encoder.processor = self.tiny_model_and_processor()
encoder.model.model = None
encoder.model.lm_head = None
encoder.visual_dim = encoder.audio_dim = 8
encoder.device, encoder.max_pixels, encoder.image_batch_size = torch.device("cpu"), 224 * 224, 8
crops = [Image.new("RGB", (56, 84), "red"), Image.new("RGB", (112, 56), "blue")]
batch = encoder.images(crops)
single = encoder.images(crops[:1])
self.assertEqual(batch.shape, (2, 8))
self.assertTrue(torch.allclose(batch[0], single[0], atol=1e-5))
for n in (3200, 6400, 16000):
audio = encoder.audio(np.sin(np.arange(n, dtype=np.float32) / 10))
self.assertEqual(audio.shape, (8,))
self.assertTrue(torch.isfinite(audio).all())
self.assertFalse(audio.requires_grad)
2026-08-27 15:24:59 +08:00
@unittest.skipUnless(importlib.util.find_spec("accelerate"), "optional: install accelerate for checkpoint loading")
def test_constructor_loads_local_checkpoint_and_preserves_encoder_weights(self):
import transformers
model, processor = self.tiny_model_and_processor()
expected = {key: value.clone() for key, value in model.state_dict().items()
if key.startswith(("visual.", "audio_tower."))}
with tempfile.TemporaryDirectory() as directory:
model.save_pretrained(directory)
args = SimpleNamespace(model_id=directory, device="cpu", dtype="float32",
flash_attention_2=False, max_pixels=224 * 224, image_batch_size=8)
with patch.object(transformers.Qwen2_5OmniProcessor, "from_pretrained", return_value=processor) as load:
encoder = FrozenQwenLocalEncoder(args)
load.assert_called_once_with(directory, use_fast=False)
self.assertEqual(encoder.transformers_version, transformers.__version__)
self.assertIsNone(encoder.model.model)
self.assertIsNone(encoder.model.lm_head)
self.assertFalse(encoder.model.training)
self.assertFalse(any(p.requires_grad for p in encoder.model.parameters()))
for key, value in encoder.model.state_dict().items():
self.assertTrue(torch.equal(value, expected[key]), key)
self.assertEqual(encoder.images([Image.new("RGB", (56, 84), "red")]).shape, (1, 8))
self.assertEqual(encoder.audio(np.zeros(3200, dtype=np.float32)).shape, (8,))
def test_constructor_rejects_incomplete_encoder_weights(self):
import transformers
args = SimpleNamespace(model_id="unused", device="cpu", dtype="float32", flash_attention_2=False)
for info in ({"missing_keys": ["visual.patch_embed.proj.weight"]},
{"missing_keys": ["audio_tower.conv1.weight"]},
{"mismatched_keys": [("visual.weight", (2,), (3,))]},
{"error_msgs": ["broken checkpoint"]}):
with self.subTest(info=info), \
patch.object(transformers.Qwen2_5OmniThinkerForConditionalGeneration,
"from_pretrained", return_value=(None, info)), \
self.assertRaisesRegex(RuntimeError, "incompletely loaded weights"):
FrozenQwenLocalEncoder(args)
class RoundTripTests(unittest.TestCase):
def test_train_infer_easycom_and_ego4d(self):
repo = Path(__file__).resolve().parent
env = dict(os.environ, OMP_NUM_THREADS="1", MKL_NUM_THREADS="1", OPENBLAS_NUM_THREADS="1")
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for folder in ("features", "local", "targets"):
(root / folder).mkdir()
train_uid = "00000000-0000-0000-0000-000000000004"
test_uid = "00000000-0000-0000-0000-000000000001"
manifest = root / "ego4d.tsv"
manifest.write_text(f"split\tuid\ntrain\t{train_uid}\ntest\t{test_uid}\n")
for name in ("Session_1", "Session_4", "Session_5", train_uid, test_uid):
dataset = "easycom" if name.startswith("Session") else "ego4d"
reference, side, targets = fixture(name=name, dataset=dataset)
torch.save(reference, root / "features" / f"{name}.pt")
torch.save(side, root / "local" / f"{name}.pt")
np.save(root / "targets" / f"{name}.npy", targets)
common = ["--feature-root", str(root / "features"), "--target-root", str(root / "targets"),
"--device", "cpu", "--no-progress", "--expected-mode", "open-stream-replay"]
for mode in ("baseline", "audio", "visual", "av", "ego4d"):
checkpoint = root / f"{mode}.pth"
train = [sys.executable, str(repo / "train_turn_head.py"), *common,
"--epochs", "1", "--warmup-epochs", "0", "--batch-size", "4",
"--layer-fusion", "concat-mlp", "--output", str(checkpoint)]
infer = [sys.executable, str(repo / "infer_turn_head.py"), *common,
"--checkpoint", str(checkpoint), "--output-dir", str(root / f"result_{mode}")]
if mode == "ego4d":
train += ["--dataset", "ego4d", "--manifest", str(manifest)]
infer += ["--dataset", "ego4d", "--manifest", str(manifest)]
else:
train += ["--sessions", "4", "5"]
infer += ["--sessions", "1"]
if mode == "visual":
train += ["--dev-sessions", "5", "--local-projection-dim", "0"]
if mode == "av":
train += ["--transition-objective", "r1-supervised"]
if mode != "baseline":
train += ["--local-feature-root", str(root / "local"), "--local-modalities", "av" if mode == "ego4d" else mode]
infer += ["--local-feature-root", str(root / "local")]
for command in (train, infer):
completed = subprocess.run(command, cwd=repo, env=env, capture_output=True, text=True, timeout=60)
self.assertEqual(completed.returncode, 0, f"{mode}: {completed.stdout}\n{completed.stderr}")
metrics = json.loads((root / f"result_{mode}/metrics.json").read_text())
self.assertEqual(metrics["checkpoint_epoch"], 1)
self.assertEqual(bool(metrics["local_contract"]), mode != "baseline")
if mode == "av":
self.assertTrue(metrics["transition_auxiliary"])
# The reporting command uses the same metric keys as inference.
completed = subprocess.run(
[sys.executable, str(repo / "summarize_local_baselines.py"), "--root", str(root)],
cwd=repo, capture_output=True, text=True, timeout=20,
)
self.assertEqual(completed.returncode, 0, completed.stderr)
self.assertIn("Mean ± sample standard deviation", completed.stdout)
if __name__ == "__main__":
unittest.main()