infosec-v1 / code /training /scripts /hf_download.py
adhikjoshi's picture
Super-squash branch 'main' using huggingface_hub
994182c
Raw
History Blame Contribute Delete
9.05 kB
#!/usr/bin/env python3
"""Probe or download Hugging Face dataset splits for the CyberGym SFT mix.
Two modes:
Probe (no heavy deps, just stdlib + network) -- inspect a dataset's real
schema via the public datasets-server before trusting the manifest:
python training/scripts/hf_download.py --probe colin/PrimeVul
python training/scripts/hf_download.py --probe-manifest training/configs/datasets.yaml
Download (requires `datasets`; runs on the GPU host) -- stream a split to
raw JSONL under data/download/<key>/raw.jsonl:
python training/scripts/hf_download.py --key primevul
python training/scripts/hf_download.py --all --profile pilot
python training/scripts/hf_download.py --hf-id colin/PrimeVul --split train --out data/download/primevul/raw.jsonl
Auth: set HF_TOKEN (or HUGGINGFACE_HUB_TOKEN) for gated datasets. Probe uses the
anonymous datasets-server; gated sets return HTTP 401 there (recorded in the
manifest `auth:` field).
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any
import yaml
DATASETS_SERVER = "https://datasets-server.huggingface.co"
def read_yaml(path: str | Path) -> dict[str, Any]:
with Path(path).open("r", encoding="utf-8") as fh:
payload = yaml.safe_load(fh) or {}
if not isinstance(payload, dict):
raise TypeError(f"Expected a YAML mapping in {path}")
return payload
def _http_get(url: str, timeout: int = 20) -> dict[str, Any]:
req = urllib.request.Request(url, headers={"User-Agent": "infosec-hf-probe"})
token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")
if token:
req.add_header("Authorization", f"Bearer {token}")
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read())
def probe(hf_id: str) -> dict[str, Any]:
"""Return {splits, features, sample_row} or {error} for a dataset."""
enc = urllib.parse.quote(hf_id)
try:
splits_payload = _http_get(f"{DATASETS_SERVER}/splits?dataset={enc}")
except Exception as exc: # noqa: BLE001 - surfaced to the caller
return {"hf_id": hf_id, "error": repr(exc)}
splits = splits_payload.get("splits", [])
if not splits:
return {"hf_id": hf_id, "error": "no splits", "raw": splits_payload}
first = splits[0]
cfg, sp = first["config"], first["split"]
try:
rows_payload = _http_get(
f"{DATASETS_SERVER}/first-rows?dataset={enc}"
f"&config={urllib.parse.quote(cfg)}&split={urllib.parse.quote(sp)}"
)
except Exception as exc: # noqa: BLE001
return {"hf_id": hf_id, "splits": splits, "error": f"first-rows: {exc!r}"}
features = [
{"name": f["name"], "type": f["type"].get("dtype", f["type"].get("_type"))}
for f in rows_payload.get("features", [])
]
sample = rows_payload.get("rows", [{}])[0].get("row", {}) if rows_payload.get("rows") else {}
return {
"hf_id": hf_id,
"splits": [{"config": s["config"], "split": s["split"]} for s in splits],
"config_used": cfg,
"split_used": sp,
"features": features,
"sample_row": sample,
}
def print_probe(result: dict[str, Any]) -> None:
print("=" * 72)
print(result["hf_id"])
if "error" in result and "features" not in result:
print(f" ERROR: {result['error']}")
return
print(f" splits: {result.get('splits')}")
print(f" used: {result.get('config_used')}/{result.get('split_used')}")
print(" features:")
for feat in result.get("features", []):
print(f" - {feat['name']}: {feat['type']}")
def resolve_sources(manifest: dict[str, Any]) -> dict[str, dict[str, Any]]:
return manifest.get("sources", {})
def download_split(
hf_id: str,
config: str | None,
split: str,
out_path: Path,
max_rows: int | None,
) -> dict[str, Any]:
try:
from datasets import load_dataset
except Exception as exc: # pragma: no cover - exercised only on GPU host
raise RuntimeError(
"The `datasets` package is required for download mode. "
"Install it on the training host (see requirements-cu126.txt)."
) from exc
out_path.parent.mkdir(parents=True, exist_ok=True)
token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")
stream = load_dataset(hf_id, config or None, split=split, streaming=True, token=token)
written = 0
with out_path.open("w", encoding="utf-8") as out:
for row in stream:
out.write(json.dumps(row, ensure_ascii=False, default=str) + "\n")
written += 1
if max_rows is not None and written >= max_rows:
break
return {"hf_id": hf_id, "split": split, "out": str(out_path), "rows": written}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--probe", metavar="HF_ID", help="Probe one dataset schema via the datasets-server.")
parser.add_argument("--probe-manifest", metavar="YAML", help="Probe every source in a manifest.")
parser.add_argument("--manifest", default="training/configs/datasets.yaml")
parser.add_argument("--key", help="Manifest source key to download.")
parser.add_argument("--all", action="store_true", help="Download every enabled manifest source.")
parser.add_argument("--eval", action="store_true", help="Download the eval_split instead of the train_split.")
parser.add_argument("--profile", choices=["full", "pilot"], default="full")
parser.add_argument("--hf-id", help="Ad-hoc download: dataset id (bypasses the manifest).")
parser.add_argument("--config", help="Ad-hoc download: config name.")
parser.add_argument("--split", help="Ad-hoc download: split name.")
parser.add_argument("--out", help="Ad-hoc download: output JSONL path.")
parser.add_argument("--max-rows", type=int, default=None)
parser.add_argument("--include-disabled", action="store_true", help="With --all, also fetch disabled sources.")
return parser.parse_args()
def _cap_for(source: dict[str, Any], defaults: dict[str, Any], profile: str) -> int | None:
if profile == "pilot":
return source.get("pilot_sample_cap", defaults.get("pilot_sample_cap"))
cap = source.get("sample_cap", defaults.get("sample_cap"))
return cap
def main() -> int:
args = parse_args()
if args.probe:
print_probe(probe(args.probe))
return 0
if args.probe_manifest:
manifest = read_yaml(args.probe_manifest)
for key, source in resolve_sources(manifest).items():
print(f"\n### {key} ({source.get('auth', 'public')}, enabled={source.get('enabled')})")
print_probe(probe(source["hf_id"]))
return 0
if args.hf_id:
out = Path(args.out or f"data/download/_adhoc/{args.hf_id.replace('/', '__')}.jsonl")
result = download_split(args.hf_id, args.config, args.split or "train", out, args.max_rows)
print(json.dumps(result, indent=2))
return 0
manifest = read_yaml(args.manifest)
defaults = manifest.get("defaults", {})
raw_dir = Path(defaults.get("raw_dir", "data/download"))
sources = resolve_sources(manifest)
keys: list[str]
if args.all:
keys = [
k
for k, s in sources.items()
if (s.get("enabled", False) or args.include_disabled)
]
elif args.key:
keys = [args.key]
else:
print("Nothing to do. Use --probe, --probe-manifest, --key, --all, or --hf-id.", file=sys.stderr)
return 2
results = []
for key in keys:
if key not in sources:
print(f"Unknown source key: {key}", file=sys.stderr)
return 2
source = sources[key]
if source.get("auth") == "gated" and not (os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")):
print(f"[skip] {key}: gated dataset and no HF_TOKEN set.", file=sys.stderr)
continue
split_key = "eval_split" if args.eval else "train_split"
split = source.get(split_key)
if not split:
print(f"[skip] {key}: no {split_key} configured.", file=sys.stderr)
continue
suffix = "eval" if args.eval else "raw"
out_path = raw_dir / key / f"{suffix}.jsonl"
max_rows = args.max_rows if args.max_rows is not None else _cap_for(source, defaults, args.profile)
print(f"[download] {key}: {source['hf_id']} [{split}] -> {out_path} (max_rows={max_rows})")
result = download_split(source["hf_id"], source.get("config"), split, out_path, max_rows)
result["key"] = key
results.append(result)
print(json.dumps(result))
print(json.dumps({"downloaded": results}, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())