Instructions to use XXXpilar/Fastfill-test with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use XXXpilar/Fastfill-test with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
File size: 9,667 Bytes
e6f80f3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | """Actual objective eligibility and optimizer-window regressions."""
from copy import deepcopy
import json
import pytest
import torch
from fastfill.v2 import train
from fastfill.v2.batch import TinyTokenizer
from fastfill.v2.losses import LossConfig
from fastfill.v2.model import ModelConfig
from fastfill.v2.tests.test_execution import sample
def _yaw_config():
return {"model": {"backbone": "tiny", "decoder_dim": 16, "decoder_heads": 2,
"decoder_layers": 1, "tiny_hidden_size": 16, "lora_rank": 0},
"loss": {"position": 0., "size": 0., "yaw_cls": 1., "yaw_reg": 1., "hungarian": False},
"training": {"cpu": True, "steps": 1, "batch_size": 1, "learning_rate": .01,
"max_length": 4096, "checkpoint_every": 0, "validate_every": 0, "cpu_threads": 1},
"augmentation": NO_AUGMENTATION}
NO_AUGMENTATION = {"rotate90": False, "mirror": False, "shuffle_objects": False,
"drop_constraints_p": 0., "drop_support_p": 0., "category_only_description_p": 0., "minimal_form_p": 0.,
"given_pose_p": 0., "given_pose_corrupt_p": 0., "given_pose_noise_m": 0., "given_pose_yaw_noise_deg": 0.,
"given_pose_size_noise": 0.}
def _row(identity, yaw=True):
row = sample()
return {**row, "validity": {**row["validity"], "yaw": [yaw]},
"provenance": {**row["provenance"], "scene_id": identity}}
def _data(tmp_path, rows):
path = tmp_path / "train.jsonl"
path.write_text("".join(json.dumps(row) + "\n" for row in rows))
return path
def test_actual_yaw_only_no_labels_fails_before_model_or_run_publication(tmp_path, monkeypatch):
data = _data(tmp_path, [_row("empty", False)])
builds = []
monkeypatch.setattr(train, "build_model", lambda cfg: builds.append(cfg))
with pytest.raises(ValueError, match="supervised|objective"):
train.run_training(_yaw_config(), data, tmp_path / "run")
assert not builds and not (tmp_path / "run").exists()
def test_actual_mixed_density_preflight_records_objective_exclusion(tmp_path):
data = _data(tmp_path, [_row("empty", False), _row("valid")])
train.run_training(_yaw_config(), data, tmp_path / "run")
manifest = json.loads((tmp_path / "run/run_manifest.json").read_text())
assert manifest["supervised_samples"] == 1
assert manifest["rejected"][0]["reason"] == "no_active_objective"
assert manifest["rejected"][0]["provenance"]["scene_id"] == "empty"
def test_default_four_terms_still_accept_position_size_without_yaw(tmp_path):
cfg = {**_yaw_config(), "loss": {"hungarian": False}}
train.run_training(cfg, _data(tmp_path, [_row("without-yaw", False)]), tmp_path / "run")
manifest = json.loads((tmp_path / "run/run_manifest.json").read_text())
assert manifest["supervised_samples"] == 1 and manifest["steps_completed"] == 1
log = json.loads((tmp_path / "run/training_log.json").read_text())[0]
assert log["active_objective_count"] == 2
@pytest.mark.parametrize("kind", ["fixed_size", "partial_position", "box_without_yaw", "collision_singleton"])
def test_preflight_uses_enabled_terms_and_complete_learning_coordinates(kind):
row = _row(kind, False)
weights = {key: 0. for key in ("position", "size", "yaw_cls", "yaw_reg")}
if kind == "fixed_size":
weights["size"] = 1.
row["condition"]["objects"][0]["fixed_size_local_m"] = [1., 1., 1.]
elif kind == "partial_position":
weights["position"] = 1.
row["validity"]["position"] = [[True, True, False]]
elif kind == "box_without_yaw":
weights["box"] = 1.
else:
weights["collision"] = 1.
with pytest.raises(ValueError, match="supervised|objective"):
train._preflight([row], TinyTokenizer(), ModelConfig(backbone="tiny", lora_rank=0),
{"max_length": 4096}, LossConfig(**weights))
def _dynamic_batches(monkeypatch, invalid_ids):
"""Inject missing labels after preflight, exercising the defensive loop guard."""
original_collate, original_preflight, original_loader = train.collate_samples, train._preflight, train.DataLoader
state = {"after_preflight": False}
def collate(rows, *args, **kwargs):
batch = original_collate(rows, *args, **kwargs)
if state["after_preflight"]:
batch = {**batch, "validity": {**batch["validity"], "yaw": batch["validity"]["yaw"].clone()}}
for index, row in enumerate(rows):
if row["provenance"]["scene_id"] in invalid_ids:
batch["validity"]["yaw"][index] = False
return batch
def preflight(*args, **kwargs):
state["after_preflight"] = False
result = original_preflight(*args, **kwargs)
state["after_preflight"] = True
return result
def loader(rows, **kwargs):
return original_loader(rows, **{**kwargs, "shuffle": False})
monkeypatch.setattr(train, "collate_samples", collate)
monkeypatch.setattr(train, "_preflight", preflight)
monkeypatch.setattr(train, "DataLoader", loader)
def test_accumulation_preserves_prior_valid_gradient_when_last_microbatch_empty(tmp_path, monkeypatch):
_dynamic_batches(monkeypatch, {"last-empty"})
cfg = _yaw_config()
cfg["training"]["gradient_accumulation_steps"] = 2
logs = train.run_training(cfg, _data(tmp_path, [_row("first-valid"), _row("last-empty")]), tmp_path / "run")
assert logs[0]["active_objective_count"] == 1
assert logs[0]["gradient_norms"]["yaw_logits_head"] > 0
assert logs[0]["step"] == 1
def test_globally_empty_window_does_not_decay_or_increment_update(tmp_path, monkeypatch):
_dynamic_batches(monkeypatch, {"empty-a", "empty-b"})
cfg = _yaw_config()
cfg["training"]["gradient_accumulation_steps"] = 2
models, before = [], []
original_build = train.build_model
def build(config):
model = original_build(config)
models.append(model)
before.append(deepcopy(model.state_dict()))
return model
monkeypatch.setattr(train, "build_model", build)
data = _data(tmp_path, [_row("empty-a"), _row("empty-b")])
with pytest.raises(RuntimeError, match="no active objective|no supervised optimizer"):
train.run_training(cfg, data, tmp_path / "run")
assert all(torch.equal(before[0][name], value) for name, value in models[0].state_dict().items())
assert not (tmp_path / "run/model").exists()
def test_empty_window_is_skipped_before_later_valid_window(tmp_path, monkeypatch):
_dynamic_batches(monkeypatch, {"empty-a", "empty-b"})
cfg = _yaw_config()
cfg["training"]["gradient_accumulation_steps"] = 2
rows = [_row(name) for name in ("empty-a", "empty-b", "valid-a", "valid-b")]
logs = train.run_training(cfg, _data(tmp_path, rows), tmp_path / "run")
assert len(logs) == 1 and logs[0]["step"] == 1
assert logs[0]["active_objective_count"] == 2
manifest = json.loads((tmp_path / "run/run_manifest.json").read_text())
assert manifest["skipped_no_objective_windows"] == 1
def test_zero_numeric_loss_with_eligible_supervision_still_updates(tmp_path, monkeypatch):
# With K=1, CE is exactly zero. Zero residual weights predict the real GT
# residual exactly, so the unmodified criterion returns zero numeric loss.
original_build = train.build_model
models, before = [], []
def build(config):
model = original_build(config)
with torch.no_grad():
model.yaw_residual_head.weight.zero_()
model.yaw_residual_head.bias.zero_()
models.append(model)
before.append(deepcopy(model.state_dict()))
return model
monkeypatch.setattr(train, "build_model", build)
config = _yaw_config()
config["model"]["yaw_bins"] = 1
logs = train.run_training(config, _data(tmp_path, [_row("valid")]), tmp_path / "run")
assert logs[0]["loss"] == 0 and logs[0]["active_objective_count"] == 1
assert logs[0]["step"] == 1
assert any(not torch.equal(before[0][name], value) for name, value in models[0].state_dict().items())
def test_short_epoch_flushes_eligible_partial_accumulation_window(tmp_path):
config = _yaw_config()
config["training"]["gradient_accumulation_steps"] = 3
logs = train.run_training(config, _data(tmp_path, [_row("valid")]), tmp_path / "run")
assert len(logs) == 1 and logs[0]["step"] == 1
assert logs[0]["active_objective_count"] == 1
assert logs[0]["gradient_norms"]["yaw_logits_head"] > 0
@pytest.mark.parametrize("term", ["boundary", "collision"])
def test_regularizer_only_preflight_keeps_label_free_eligible_geometry(term):
row = _row("label-free", False)
row["validity"] = {"position": [[False] * 3], "size": [[False] * 3], "yaw": [False]}
if term == "collision":
row["condition"]["objects"].append({"id": "b", "category": "chair", "description": "a chair"})
config = LossConfig(position=0., size=0., yaw_cls=0., yaw_reg=0., **{term: 1.})
kept, rejected = train._preflight([row], TinyTokenizer(), ModelConfig(backbone="tiny", lora_rank=0),
{"max_length": 4096}, config)
assert kept == [row] and not rejected
def test_collision_only_support_exempt_pair_has_no_eligible_term():
row = _row("supported", False)
row["condition"]["objects"].append({"id": "b", "category": "cup", "description": "a cup", "support_parent": "a"})
config = LossConfig(position=0., size=0., yaw_cls=0., yaw_reg=0., collision=1.)
with pytest.raises(ValueError, match="supervised|objective"):
train._preflight([row], TinyTokenizer(), ModelConfig(backbone="tiny", lora_rank=0), {"max_length": 4096}, config)
|