grasp / tools /validate_graspanything_pp_webdataset.py
stumpferWinkel's picture
Upload cleaned LLMF2 code and OCID-VLG multiple-split weights
320c5b2 verified
Raw History Blame Contribute Delete
19.4 kB
#!/usr/bin/env python3
"""Exhaustively validate full Grasp-Anything++ streaming shards."""
import argparse
import gzip
import json
import pickle
import tarfile
import time
import zlib
from pathlib import Path
SPLITS = ("train", "seen", "unseen")
SUFFIX_ORDER = ("jpg", "ptz", "pklz", "npyz", "json")
def log(message):
print(time.strftime("[%Y-%m-%d %H:%M:%S]"), message, flush=True)
def parse_args():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--root", type=Path,
default=Path("/anvme/workspace/b305bb10-vlagrasp/graspanything_full"))
parser.add_argument("--report", type=Path)
parser.add_argument("--progress-every", type=int, default=100000)
parser.add_argument("--worker-count", type=int, default=1)
parser.add_argument("--worker-id", type=int, default=0)
parser.add_argument("--manifest-only", action="store_true")
parser.add_argument("--aggregate-workers", type=int, default=0)
args = parser.parse_args()
if args.worker_count <= 0 or not 0 <= args.worker_id < args.worker_count:
parser.error("require worker-count > 0 and 0 <= worker-id < worker-count")
return args
def atomic_json(path, value):
partial = path.with_name(path.name + ".partial")
partial.write_text(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8")
partial.replace(path)
def validate_object_manifest(root, expected, represented_objects):
path = root / "manifests/objects.jsonl.gz"
counts = {split: 0 for split in SPLITS}
previous = None
manifest_objects = {split: set() for split in SPLITS}
with gzip.open(str(path), "rt", encoding="utf-8") as fp:
for line_number, line in enumerate(fp, 1):
item = json.loads(line)
required = {"object_id", "scene_id", "obj_id", "category", "split"}
if set(item) != required:
raise ValueError("Object manifest line {} fields differ: {}".format(
line_number, sorted(item)))
object_id = "{}_{}".format(item["scene_id"], item["obj_id"])
if item["object_id"] != object_id or item["split"] not in SPLITS:
raise ValueError("Invalid object manifest line {}".format(line_number))
order_key = (item["scene_id"], item["obj_id"])
if previous is not None and order_key <= previous:
raise ValueError("Object manifest is not strictly ordered at line {}".format(line_number))
previous = order_key
split = item["split"]
if object_id in manifest_objects[split]:
raise ValueError("Duplicate object {}".format(object_id))
manifest_objects[split].add(object_id)
counts[split] += 1
if counts != expected:
raise ValueError("Object manifest counts {} != {}".format(counts, expected))
for split in SPLITS:
if manifest_objects[split] != represented_objects[split]:
raise ValueError("{} object manifest/tar mismatch".format(split))
return counts
def validate_manifests_only(args, summary):
root = args.root
manifest_root = root / "manifests"
samples = {split: set() for split in SPLITS}
objects = {split: set() for split in SPLITS}
categories = {split: set() for split in SPLITS}
report = {
"format": "graspanything-pp-full-validation-v1",
"dataset_format": summary.get("format"),
"root": str(root),
"splits": {},
"checks": {},
}
for split in SPLITS:
expected = summary["splits"][split]
path = manifest_root / (split + ".jsonl")
with path.open("rb") as fp:
for line_number, line in enumerate(fp, 1):
metadata = json.loads(line)
sample_id = metadata.get("sample_id")
if not isinstance(sample_id, str) or metadata.get("split") != split:
raise ValueError("Invalid {} manifest line {}".format(split, line_number))
object_id = sample_id.rsplit("_", 1)[0]
if metadata.get("object_id") != object_id:
raise ValueError("Object mismatch on {} line {}".format(split, line_number))
category = metadata.get("category")
if not isinstance(category, str) or not category:
raise ValueError("Empty category on {} line {}".format(split, line_number))
if sample_id in samples[split]:
raise ValueError("Duplicate sample {}".format(sample_id))
samples[split].add(sample_id)
objects[split].add(object_id)
categories[split].add(category)
if args.progress_every and line_number % args.progress_every == 0:
log("{}: audited {:,}/{:,} manifest rows".format(
split, line_number, expected["samples"]))
if len(samples[split]) != expected["samples"]:
raise ValueError("{} manifest sample count mismatch".format(split))
if len(objects[split]) != expected["objects"]:
raise ValueError("{} manifest object count mismatch".format(split))
if len(categories[split]) != expected["categories"]:
raise ValueError("{} manifest category count mismatch".format(split))
report["splits"][split] = {
"samples": len(samples[split]), "objects": len(objects[split]),
"categories": len(categories[split]), "shards": expected["shards"],
"bytes": expected["bytes"],
}
for index, left in enumerate(SPLITS):
for right in SPLITS[index + 1:]:
if samples[left] & samples[right] or objects[left] & objects[right]:
raise ValueError("Global sample/object overlap between {} and {}".format(left, right))
if (categories["train"] | categories["seen"]) & categories["unseen"]:
raise ValueError("Global seen/unseen category overlap")
expected_objects = {split: summary["splits"][split]["objects"] for split in SPLITS}
validate_object_manifest(root, expected_objects, objects)
if args.aggregate_workers:
worker_root = manifest_root / "validation_workers"
for split in SPLITS:
shard_ids = set()
worker_samples = 0
worker_bytes = 0
for worker_id in range(args.aggregate_workers):
worker = json.loads((worker_root / "worker-{:02d}.json".format(worker_id)).read_text(
encoding="utf-8"))
if worker.get("worker_count") != args.aggregate_workers:
raise ValueError("Validation worker-count mismatch")
item = worker["splits"][split]
overlap = shard_ids & set(item["shard_indices"])
if overlap:
raise ValueError("Validation workers overlap on {} shards".format(split))
shard_ids.update(item["shard_indices"])
worker_samples += item["samples"]
worker_bytes += item["bytes"]
if shard_ids != set(range(summary["splits"][split]["shards"])):
raise ValueError("Validation workers did not cover all {} shards".format(split))
if worker_samples != summary["splits"][split]["samples"]:
raise ValueError("Validation worker {} sample sum mismatch".format(split))
if worker_bytes != summary["splits"][split]["bytes"]:
raise ValueError("Validation worker {} byte sum mismatch".format(split))
report["checks"] = {
"all_zlib_payloads_decoded": bool(args.aggregate_workers),
"all_jpeg_markers_valid": bool(args.aggregate_workers),
"all_instructions_nonempty_strings": bool(args.aggregate_workers),
"all_tar_member_groups_exact": bool(args.aggregate_workers),
"all_tar_json_matches_combined_manifest": bool(args.aggregate_workers),
"sample_ids_pairwise_disjoint": True,
"object_ids_pairwise_disjoint": True,
"seen_unseen_categories_disjoint": True,
"object_manifest_matches_represented_objects": True,
"partial_files": 0,
"payload_worker_count": args.aggregate_workers,
}
report["completed_at_unix"] = int(time.time())
report_path = args.report or manifest_root / "validation_report.json"
atomic_json(report_path, report)
log("Global manifest and worker-coverage validation OK; report: {}".format(report_path))
def main():
args = parse_args()
root = args.root
manifest_root = root / "manifests"
if not (manifest_root / "_SUCCESS").is_file():
raise ValueError("Missing manifests/_SUCCESS")
partials = list(root.rglob("*.partial"))
if partials:
raise ValueError("Found partial outputs, first entries: {}".format(partials[:10]))
summary = json.loads((manifest_root / "dataset_summary.json").read_text(encoding="utf-8"))
if summary.get("uses_official_object_id_manifests") is not False:
raise ValueError("Summary does not affirm full non-manifest selection")
if args.manifest_only:
validate_manifests_only(args, summary)
return
report = {
"format": "graspanything-pp-full-validation-v1",
"dataset_format": summary.get("format"),
"root": str(root),
"splits": {},
"checks": {},
}
split_samples = {split: set() for split in SPLITS}
split_objects = {split: set() for split in SPLITS}
split_categories = {split: set() for split in SPLITS}
for split in SPLITS:
expected = summary["splits"][split]
shard_paths = sorted((root / "shards" / split).glob("ga-pp-{}-*.tar".format(split)))
if len(shard_paths) != expected["shards"]:
raise ValueError("{} shard count {} != {}".format(
split, len(shard_paths), expected["shards"]))
expected_names = ["ga-pp-{}-{:05d}.tar".format(split, index)
for index in range(len(shard_paths))]
if [path.name for path in shard_paths] != expected_names:
raise ValueError("{} shard sequence has gaps or extra files".format(split))
actual_bytes = sum(path.stat().st_size for path in shard_paths)
if actual_bytes != expected["bytes"]:
raise ValueError("{} bytes {} != {}".format(split, actual_bytes, expected["bytes"]))
count = 0
assigned_expected = 0
assigned_bytes = 0
assigned_shards = []
manifest_path = manifest_root / (split + ".jsonl")
with manifest_path.open("rb") as manifest_fp:
for shard_number, shard_path in enumerate(shard_paths):
shard_expected = min(expected["samples"] - shard_number * expected["samples_per_shard"],
expected["samples_per_shard"])
if args.worker_count > 1 and shard_number % args.worker_count != args.worker_id:
for _ in range(shard_expected):
if not manifest_fp.readline():
raise ValueError("{} manifest ended while worker skipped shard".format(split))
continue
assigned_expected += shard_expected
assigned_bytes += shard_path.stat().st_size
assigned_shards.append(shard_number)
per_shard = 0
pending_id = None
pending_suffixes = []
pending_json = None
with tarfile.open(str(shard_path), "r:") as archive:
for member in archive:
if not member.isfile() or "." not in member.name:
raise ValueError("Unexpected member {} in {}".format(
member.name, shard_path))
sample_id, suffix = member.name.rsplit(".", 1)
expected_suffix = SUFFIX_ORDER[len(pending_suffixes)]
if pending_id is None:
pending_id = sample_id
if sample_id != pending_id or suffix != expected_suffix:
raise ValueError("Member order/group mismatch in {} at {}".format(
shard_path, member.name))
extracted = archive.extractfile(member)
if extracted is None:
raise ValueError("Cannot extract {}".format(member.name))
payload = extracted.read()
if suffix == "jpg":
if not (payload.startswith(b"\xff\xd8") and payload.endswith(b"\xff\xd9")):
raise ValueError("Invalid JPEG {}".format(member.name))
elif suffix in ("ptz", "pklz", "npyz"):
decoded = zlib.decompress(payload)
if not decoded:
raise ValueError("Empty decoded payload {}".format(member.name))
if suffix == "pklz":
instruction = pickle.loads(decoded)
if not isinstance(instruction, str) or not instruction.strip():
raise ValueError("Invalid instruction {}".format(member.name))
else:
pending_json = json.loads(payload)
pending_suffixes.append(suffix)
if len(pending_suffixes) != len(SUFFIX_ORDER):
continue
metadata = pending_json
if (metadata is None or metadata.get("sample_id") != pending_id or
metadata.get("split") != split):
raise ValueError("Metadata mismatch for {}".format(pending_id))
manifest_line = manifest_fp.readline()
if not manifest_line:
raise ValueError("{} combined manifest ended early".format(split))
if json.loads(manifest_line) != metadata:
raise ValueError("{} manifest/tar JSON mismatch for {}".format(
split, pending_id))
if pending_id in split_samples[split]:
raise ValueError("Duplicate sample {}".format(pending_id))
split_samples[split].add(pending_id)
object_id = pending_id.rsplit("_", 1)[0]
if metadata.get("object_id") != object_id:
raise ValueError("Object ID mismatch for {}".format(pending_id))
split_objects[split].add(object_id)
split_categories[split].add(metadata.get("category"))
count += 1
per_shard += 1
pending_id = None
pending_suffixes = []
pending_json = None
if args.progress_every and count % args.progress_every == 0:
log("{}: validated {:,}/{:,} samples".format(
split, count, expected["samples"]))
if pending_id is not None:
raise ValueError("Incomplete last sample in {}".format(shard_path))
if per_shard != shard_expected:
raise ValueError("{} samples {} != {}".format(
shard_path, per_shard, shard_expected))
if manifest_fp.readline():
raise ValueError("{} combined manifest has extra rows".format(split))
target_count = expected["samples"] if args.worker_count == 1 else assigned_expected
if count != target_count:
raise ValueError("{} sample count {} != {}".format(split, count, target_count))
if args.worker_count == 1 and len(split_objects[split]) != expected["objects"]:
raise ValueError("{} object count {} != {}".format(
split, len(split_objects[split]), expected["objects"]))
if args.worker_count == 1 and len(split_categories[split]) != expected["categories"]:
raise ValueError("{} category count {} != {}".format(
split, len(split_categories[split]), expected["categories"]))
report["splits"][split] = {
"samples": count,
"objects": len(split_objects[split]),
"categories": len(split_categories[split]),
"shards": len(assigned_shards) if args.worker_count > 1 else len(shard_paths),
"shard_indices": assigned_shards if args.worker_count > 1 else list(range(len(shard_paths))),
"bytes": assigned_bytes if args.worker_count > 1 else actual_bytes,
}
log("{}: exhaustive shard validation OK".format(split))
if args.worker_count > 1:
report["worker_id"] = args.worker_id
report["worker_count"] = args.worker_count
report["checks"] = {
"assigned_zlib_payloads_decoded": True,
"assigned_jpeg_markers_valid": True,
"assigned_instructions_nonempty_strings": True,
"assigned_tar_member_groups_exact": True,
"assigned_tar_json_matches_manifest": True,
}
worker_root = manifest_root / "validation_workers"
worker_root.mkdir(parents=True, exist_ok=True)
atomic_json(worker_root / "worker-{:02d}.json".format(args.worker_id), report)
log("Payload worker {}/{} complete".format(args.worker_id, args.worker_count))
return
for index, left in enumerate(SPLITS):
for right in SPLITS[index + 1:]:
sample_overlap = split_samples[left] & split_samples[right]
object_overlap = split_objects[left] & split_objects[right]
if sample_overlap or object_overlap:
raise ValueError("Overlap {} / {}: samples={}, objects={}".format(
left, right, len(sample_overlap), len(object_overlap)))
base_categories = split_categories["train"] | split_categories["seen"]
category_overlap = base_categories & split_categories["unseen"]
if category_overlap:
raise ValueError("Seen/unseen category overlap: {}".format(sorted(category_overlap)))
object_counts = {split: summary["splits"][split]["objects"] for split in SPLITS}
validate_object_manifest(root, object_counts, split_objects)
report["checks"] = {
"all_zlib_payloads_decoded": True,
"all_jpeg_markers_valid": True,
"all_instructions_nonempty_strings": True,
"all_tar_member_groups_exact": True,
"combined_manifests_match_tar_json": True,
"sample_ids_pairwise_disjoint": True,
"object_ids_pairwise_disjoint": True,
"seen_unseen_categories_disjoint": True,
"object_manifest_matches_represented_objects": True,
"partial_files": 0,
}
report["completed_at_unix"] = int(time.time())
report_path = args.report or manifest_root / "validation_report.json"
atomic_json(report_path, report)
log("All splits passed exhaustive validation; report: {}".format(report_path))
if __name__ == "__main__":
main()