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
Download code/fastfill/v2/matching.py from XXXpilar/Fastfill-test: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/XXXpilar/Fastfill-test/resolve/main/code/fastfill/v2/matching.py
- Command line
-
hf download hf://XXXpilar/Fastfill-test/code/fastfill/v2/matching.py
-
curl -L -o matching.py https://huggingface.co/XXXpilar/Fastfill-test/resolve/main/code/fastfill/v2/matching.py
12.1 kB
| """Detached assignment only within explicitly certified exchangeable groups.""" | |
| from __future__ import annotations | |
| import json | |
| import math | |
| import torch | |
| from scipy.optimize import linear_sum_assignment | |
| def _rename_references(row, mapping, scalar_fields, list_fields=()): | |
| """Copy a schema row, renaming only its explicit object-reference fields. | |
| IDs share string representations with ordinary descriptions and attributes; | |
| those values, dictionary keys, group labels and asset-local surface IDs are | |
| independent namespaces. Nested metadata must never be interpreted as refs. | |
| ``object_ids`` is the existing top-level constraint reference-list extension. | |
| """ | |
| renamed = {} | |
| for key, value in row.items(): | |
| if key in scalar_fields and isinstance(value, str): | |
| renamed[key] = mapping.get(value, value) | |
| elif key in list_fields and isinstance(value, list): | |
| renamed[key] = [mapping.get(ref, ref) if isinstance(ref, str) else ref for ref in value] | |
| else: | |
| renamed[key] = value | |
| return renamed | |
| def certify_group(objects, constraints, indices): | |
| """Check conditioning invariance under generators of the group's permutations.""" | |
| fingerprints = [json.dumps({k: v for k, v in objects[i].items() if k not in {"id", "exchangeable_group"}}, | |
| sort_keys=True) for i in indices] | |
| if len(set(fingerprints)) != 1: | |
| raise ValueError("exchangeable group has distinct descriptions/requirements/roles") | |
| canonical = lambda rows: sorted(json.dumps(r, sort_keys=True) for r in rows) | |
| original = canonical(constraints) | |
| object_graph = canonical(objects) | |
| for i in indices[1:]: | |
| mapping = {objects[indices[0]]["id"]: objects[i]["id"], objects[i]["id"]: objects[indices[0]]["id"]} | |
| renamed_constraints = [_rename_references(c, mapping, ("object_id", "target_id", "parent_id"), | |
| ("target_ids", "object_ids")) for c in constraints] | |
| renamed_objects = [_rename_references(o, mapping, ("id", "support_parent")) for o in objects] | |
| if canonical(renamed_constraints) != original or canonical(renamed_objects) != object_graph: | |
| raise ValueError("exchange would change a constraint or support role; use fixed identities") | |
| def group_labels(objects, constraints, position_valid): | |
| """C5 exchangeable labels per request: identical non-id fields, complete position labels, certified swap. | |
| A signature set (identical request fields except ``id``) whose whole swap fails certification | |
| falls back to its members that no constraint reference field (the ones ``certify_group`` renames) | |
| and no ``support_parent`` names; that remainder always certifies and is grouped under the set's | |
| label when it has at least two members. | |
| """ | |
| candidates = {} | |
| for i, obj in enumerate(objects): | |
| if all(position_valid[i]): | |
| signature = json.dumps({k: v for k, v in obj.items() if k != "id"}, sort_keys=True) | |
| candidates.setdefault(signature, []).append(i) | |
| referenced = {o.get("support_parent") for o in objects} | |
| for c in constraints: | |
| referenced.update(c.get(k) for k in ("object_id", "target_id", "parent_id")) | |
| referenced.update(ref for k in ("target_ids", "object_ids") for ref in c.get(k) or ()) | |
| labels = [None] * len(objects) | |
| for number, indices in enumerate(candidates.values()): | |
| for members in (indices, [i for i in indices if objects[i]["id"] not in referenced]): | |
| if len(members) < 2: | |
| break | |
| try: | |
| certify_group(objects, constraints, members) | |
| except ValueError: | |
| continue | |
| for i in members: | |
| labels[i] = f"anonymous_{number}" | |
| break | |
| return labels | |
| def validate_matching_options(enabled, alpha_position, alpha_size): | |
| if not isinstance(enabled, bool): | |
| raise ValueError("matching enabled/hungarian flag must be boolean") | |
| weights = (alpha_position, alpha_size) | |
| if (any(type(value) not in {int, float} or not math.isfinite(value) or value < 0 for value in weights) | |
| or alpha_position + alpha_size <= 0): | |
| raise ValueError("matching weights must be native finite nonnegative numbers and not both zero") | |
| def match_batch(predictions, batch, enabled=True, alpha_position=1., alpha_size=1., *, loss_config=None): | |
| """Groups come from collate's ``batch["exchangeable_group"]`` (per sample, per | |
| request slot), never from the rendered condition objects. A group needs | |
| complete position labels on every member; otherwise it keeps fixed identity. | |
| Position cost (times ``alpha_position``): the decoded L1 for regression | |
| predictions; for grid_residual predictions (``position_cell_logits``) each | |
| (prediction slot, target) pair costs exactly the loss's ``position`` terms, | |
| ``losses._grid_position`` under ``loss_config`` (default ``LossConfig()``): | |
| ``position_cell * CE + position_residual * GT-cell residual + z / 3`` on the | |
| prediction slot's learned (not ``fixed_position_mask``) coordinates, summed in | |
| float64 (on the CPU for Metal, which has none). | |
| Size enters the cost only when every member also has a complete size label; | |
| a pair whose target object is box-symmetric (its own | |
| ``batch["size_axis_swap_allowed"]``) costs the minimum log-size error over the | |
| target's (sx, sy) and (sy, sx) orders, every other pair the written order. | |
| Members marked in ``batch["given_pose_mask"]`` (masked layout training) keep | |
| fixed identity: the rest of their group matches among itself. | |
| """ | |
| validate_matching_options(enabled, alpha_position, alpha_size) | |
| if enabled and "exchangeable_group" not in batch: | |
| raise ValueError("exchangeable matching requires batch exchangeable_group from collate") | |
| grid = "position_cell_logits" in predictions | |
| if grid: | |
| from fastfill.v2.losses import GRID_KEYS, LossConfig, _grid_position # losses imports this module | |
| cfg = LossConfig() if loss_config is None else loss_config | |
| valid = batch["slot_mask"] | |
| bsz, slots = valid.shape | |
| assignments = [] | |
| for b in range(bsz): | |
| indices = list(range(slots)) | |
| objects = batch["objects"][b] | |
| if len(objects) != int(valid[b].sum()) or not valid[b, :len(objects)].all(): | |
| raise ValueError("slots must be contiguous and bound to every request") | |
| groups = {} | |
| if enabled: | |
| labels = batch["exchangeable_group"][b] | |
| if len(labels) != len(objects) or any(label is not None and not isinstance(label, str) for label in labels): | |
| raise ValueError("exchangeable_group must hold one string-or-null label per request slot") | |
| for i, label in enumerate(labels): | |
| if label: | |
| groups.setdefault(label, []).append(i) | |
| if any(len(group) > 1 for group in groups.values()) and "conditions" not in batch: | |
| raise ValueError("exchangeable matching requires original conditions and constraint graph") | |
| conditions = batch.get("conditions", [{} for _ in range(bsz)]) | |
| for group in groups.values(): | |
| certify_group(objects, conditions[b].get("constraints", []), group) | |
| if "given_pose_mask" in batch: # a given pose was revealed for its own target only | |
| group = [i for i in group if not bool(batch["given_pose_mask"][b, i])] | |
| if len(group) < 2 or not batch["validity"]["position"][b, group].all(): | |
| continue | |
| pos, gt_pos = predictions["position_normalized"][b, group], batch["targets"]["position_normalized"][b, group] | |
| if not torch.isfinite(pos).all() or not torch.isfinite(gt_pos).all(): | |
| raise ValueError("nonfinite position geometry in matching group") | |
| if grid: # pair (i, j) at flat i * n + j: prediction slot group[i], target group[j] | |
| n = len(group) | |
| rows = torch.tensor(group, device=valid.device).repeat_interleave(n) | |
| cols = torch.tensor(group, device=valid.device).repeat(n) | |
| learn = ~batch.get("fixed_position_mask", torch.zeros_like(batch["validity"]["position"]))[b, rows] | |
| parts = _grid_position({k: predictions[k][b, rows] for k in ("position_normalized", *GRID_KEYS)}, | |
| batch["targets"]["position_normalized"][b, cols], torch.ones_like(rows, dtype=torch.bool), learn, cfg) | |
| # Metal has no float64: its pair cost is built on the CPU; CPU/CUDA keep their device. | |
| host = torch.device("cpu") if valid.device.type == "mps" else valid.device | |
| part, learn = (lambda name: parts[name].to(host).double()), learn.to(host) | |
| pair = torch.zeros(n * n, dtype=torch.float64, device=host) | |
| pair[learn[:, 0] & learn[:, 1]] = (cfg.position_cell * part("position_cell") + | |
| cfg.position_residual * part("position_residual")) | |
| pair[learn[:, 2]] += part("position_z") | |
| cost = alpha_position * pair.view(n, n) | |
| else: | |
| cost = alpha_position * (pos[:, None] - gt_pos[None]).abs().sum(-1) | |
| if batch["validity"]["size"][b, group].all(): | |
| size, gt_size = predictions["size"][b, group], batch["targets"]["size"][b, group] | |
| if not torch.isfinite(size).all() or not torch.isfinite(gt_size).all() or (size <= 0).any() or (gt_size <= 0).any(): | |
| raise ValueError("nonfinite or nonpositive size geometry in matching group") | |
| size_cost = (size.log()[:, None] - gt_size.log()[None]).abs().sum(-1) | |
| if "size_axis_swap_allowed" in batch: # columns are targets: each pair follows its target's flag | |
| swapped = (size.log()[:, None] - gt_size.log()[None, :, [1, 0, 2]]).abs().sum(-1) | |
| size_cost = torch.where(batch["size_axis_swap_allowed"][b, group][None], torch.minimum(size_cost, swapped), size_cost) | |
| cost = cost + (alpha_size * size_cost).to(cost.device) # a no-op except for an mps grid cost | |
| if not torch.isfinite(cost).all(): | |
| raise ValueError("matching cost is not finite") | |
| # SciPy deterministic ordered input; exact ties use its row/column order. | |
| rows, cols = linear_sum_assignment(cost.cpu().double().numpy()) | |
| chosen = {group[int(row)]: group[int(col)] for row, col in zip(rows, cols)} | |
| indices = [chosen.get(k, old) for k, old in enumerate(indices)] | |
| assignments.append(indices) | |
| return torch.tensor(assignments, dtype=torch.long, device=valid.device) | |
| def permute_relations(assignment, parents=None, relation_matrix=None): | |
| """Negative parent indices represent external supports and remain unchanged.""" | |
| if (assignment.ndim != 1 or assignment.dtype not in {torch.int32, torch.int64} or | |
| not torch.equal(assignment.sort().values, torch.arange(len(assignment), device=assignment.device, dtype=assignment.dtype))): | |
| raise ValueError("relation assignment must be a one-to-one permutation") | |
| if parents is not None and parents.shape != assignment.shape: | |
| raise ValueError("parent targets must have one value per assigned object") | |
| if relation_matrix is not None and relation_matrix.shape != (len(assignment), len(assignment)): | |
| raise ValueError("relation matrix shape must match assigned objects") | |
| inverse = assignment.argsort() | |
| new_parents = None | |
| if parents is not None: | |
| selected = parents[assignment] | |
| external = selected < 0 | |
| if ((selected >= len(assignment)) & ~external).any(): | |
| raise ValueError("parent index out of range") | |
| new_parents = torch.where(external, selected, inverse[selected.clamp_min(0)]) | |
| matrix = None if relation_matrix is None else relation_matrix[assignment][:, assignment] | |
| return new_parents, matrix | |