Upload validate_collator.py with huggingface_hub
Browse files- validate_collator.py +103 -0
validate_collator.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# /// script
|
| 2 |
+
# dependencies = [
|
| 3 |
+
# "transformers>=5.14.0",
|
| 4 |
+
# "datasets>=4.0",
|
| 5 |
+
# "trackio",
|
| 6 |
+
# "Pillow>=10.0",
|
| 7 |
+
# "torch>=2.5",
|
| 8 |
+
# "accelerate>=1.0",
|
| 9 |
+
# "num2words",
|
| 10 |
+
# ]
|
| 11 |
+
# ///
|
| 12 |
+
"""CPU pre-flight for the Gemma 4 training collator.
|
| 13 |
+
|
| 14 |
+
Runs the EXACT collate path from train_sft.py on a few real dataset rows, using
|
| 15 |
+
the processor + model config (no 16GB weights), then decodes one example to
|
| 16 |
+
prove which tokens are supervised vs masked. Catches chat-template, token-id,
|
| 17 |
+
and shape bugs on cpu-basic (~$0.01) before we burn GPU minutes.
|
| 18 |
+
|
| 19 |
+
python gemma4/validate_collator.py --dataset khalidFlex/gui-agent-smoke
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
from datasets import load_dataset
|
| 26 |
+
from transformers import AutoConfig, AutoProcessor
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def main():
|
| 30 |
+
ap = argparse.ArgumentParser()
|
| 31 |
+
ap.add_argument("--model", default="google/gemma-4-E4B-it")
|
| 32 |
+
ap.add_argument("--dataset", default="khalidFlex/gui-agent-smoke")
|
| 33 |
+
ap.add_argument("--n", type=int, default=4)
|
| 34 |
+
args = ap.parse_args()
|
| 35 |
+
|
| 36 |
+
print(f"[val] processor + config for {args.model}")
|
| 37 |
+
processor = AutoProcessor.from_pretrained(args.model)
|
| 38 |
+
config = AutoConfig.from_pretrained(args.model)
|
| 39 |
+
|
| 40 |
+
image_token_ids = {
|
| 41 |
+
tid for tid in [
|
| 42 |
+
getattr(config, "image_token_id", None),
|
| 43 |
+
getattr(config, "boi_token_id", None),
|
| 44 |
+
getattr(config, "eoi_token_id", None),
|
| 45 |
+
] if isinstance(tid, int)
|
| 46 |
+
}
|
| 47 |
+
print(f"[val] image/structural token ids: {sorted(image_token_ids)}")
|
| 48 |
+
processor.tokenizer.padding_side = "right"
|
| 49 |
+
|
| 50 |
+
ds = load_dataset(args.dataset, split=f"train[:{args.n}]")
|
| 51 |
+
print(f"[val] loaded {len(ds)} rows")
|
| 52 |
+
|
| 53 |
+
def to_messages(ex):
|
| 54 |
+
return [
|
| 55 |
+
{"role": "user", "content": [
|
| 56 |
+
{"type": "image"},
|
| 57 |
+
{"type": "text", "text": f"{ex['system']}\n\n{ex['user']}"},
|
| 58 |
+
]},
|
| 59 |
+
{"role": "assistant", "content": [{"type": "text", "text": ex["assistant"]}]},
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
full_texts, prompt_texts, images = [], [], []
|
| 63 |
+
for ex in ds:
|
| 64 |
+
msgs = to_messages(ex)
|
| 65 |
+
full_texts.append(processor.apply_chat_template(msgs, tokenize=False, add_generation_prompt=False))
|
| 66 |
+
prompt_texts.append(processor.apply_chat_template(msgs[:-1], tokenize=False, add_generation_prompt=True))
|
| 67 |
+
images.append([ex["image"].convert("RGB")])
|
| 68 |
+
|
| 69 |
+
batch = processor(text=full_texts, images=images, return_tensors="pt", padding=True)
|
| 70 |
+
prompt_batch = processor(text=prompt_texts, images=images, return_tensors="pt", padding=True)
|
| 71 |
+
prompt_lens = prompt_batch["attention_mask"].sum(dim=1)
|
| 72 |
+
|
| 73 |
+
labels = batch["input_ids"].clone()
|
| 74 |
+
labels[batch["attention_mask"] == 0] = -100
|
| 75 |
+
for tid in image_token_ids:
|
| 76 |
+
labels[labels == tid] = -100
|
| 77 |
+
for i, plen in enumerate(prompt_lens):
|
| 78 |
+
labels[i, : int(plen)] = -100
|
| 79 |
+
|
| 80 |
+
print("\n[val] === batch shapes ===")
|
| 81 |
+
for k, v in batch.items():
|
| 82 |
+
if isinstance(v, torch.Tensor):
|
| 83 |
+
print(f" {k}: {tuple(v.shape)} {v.dtype}")
|
| 84 |
+
|
| 85 |
+
# Prove supervision: decode only the non-masked (label != -100) tokens of row 0.
|
| 86 |
+
row0 = labels[0]
|
| 87 |
+
supervised = batch["input_ids"][0][row0 != -100]
|
| 88 |
+
n_sup = int((row0 != -100).sum())
|
| 89 |
+
n_tot = int((batch["attention_mask"][0] == 1).sum())
|
| 90 |
+
print(f"\n[val] row0 supervised tokens: {n_sup}/{n_tot} real tokens")
|
| 91 |
+
print(f"[val] supervised text (should be ONLY the assistant answer):")
|
| 92 |
+
print(" ", repr(processor.tokenizer.decode(supervised)))
|
| 93 |
+
|
| 94 |
+
assert n_sup > 0, "no supervised tokens — masking is wrong!"
|
| 95 |
+
assert n_sup < n_tot, "everything supervised — prompt not masked!"
|
| 96 |
+
decoded = processor.tokenizer.decode(supervised)
|
| 97 |
+
assert "<code>" in decoded or "click" in decoded or "final_answer" in decoded, \
|
| 98 |
+
"supervised span doesn't look like an action — offset is off"
|
| 99 |
+
print("\n[val] ✓ collator OK — prompt+image masked, assistant supervised.")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
if __name__ == "__main__":
|
| 103 |
+
main()
|