Spaces:
Sleeping
Sleeping
added HF upload script
Browse files- mltau/scripts/upload_model_hf.py +148 -0
- model_card.md +82 -0
mltau/scripts/upload_model_hf.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import argparse
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from huggingface_hub import HfApi
|
| 7 |
+
|
| 8 |
+
# Set timeout for large file uploads
|
| 9 |
+
os.environ["HF_HUB_ETAG_TIMEOUT"] = "1000"
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def get_dir_size(path, ignore_patterns=None):
|
| 13 |
+
"""Calculate the total size of a directory in bytes."""
|
| 14 |
+
from fnmatch import fnmatch
|
| 15 |
+
|
| 16 |
+
total = 0
|
| 17 |
+
for p in Path(path).rglob("*"):
|
| 18 |
+
if p.is_file():
|
| 19 |
+
if ignore_patterns:
|
| 20 |
+
skip = False
|
| 21 |
+
for pattern in ignore_patterns:
|
| 22 |
+
if fnmatch(p.name, pattern) or fnmatch(str(p.relative_to(path)), pattern):
|
| 23 |
+
skip = True
|
| 24 |
+
break
|
| 25 |
+
if skip:
|
| 26 |
+
continue
|
| 27 |
+
total += p.stat().st_size
|
| 28 |
+
return total
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def format_size(num, suffix="B"):
|
| 32 |
+
"""Convert bytes to human-readable format."""
|
| 33 |
+
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
|
| 34 |
+
if abs(num) < 1024.0:
|
| 35 |
+
return f"{num:3.1f}{unit}{suffix}"
|
| 36 |
+
num /= 1024.0
|
| 37 |
+
return f"{num:.1f}Yi{suffix}"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def main():
|
| 41 |
+
parser = argparse.ArgumentParser(description="Upload ML-Tau model checkpoints to HuggingFace.")
|
| 42 |
+
parser.add_argument("experiment_dir", help="Path to the experiment directory (output_dir)")
|
| 43 |
+
parser.add_argument("--repo", default="HEP-KBFI/fcc-tau", help="HF repository ID")
|
| 44 |
+
parser.add_argument("--name", help="Custom descriptive name for the experiment in the repo (defaults to directory name)")
|
| 45 |
+
parser.add_argument("--path-prefix", default="", help="Prefix path in the repository (e.g. 'cld/0609_single')")
|
| 46 |
+
parser.add_argument("--dry-run", action="store_true", help="Print actions without executing")
|
| 47 |
+
parser.add_argument("--repo-type", default="model", help="HF repository type (default: model)")
|
| 48 |
+
|
| 49 |
+
args = parser.parse_args()
|
| 50 |
+
|
| 51 |
+
exp_path = Path(args.experiment_dir)
|
| 52 |
+
if not exp_path.is_dir():
|
| 53 |
+
print(f"Error: Experiment directory {exp_path} not found.")
|
| 54 |
+
return
|
| 55 |
+
|
| 56 |
+
exp_name = args.name if args.name else exp_path.name
|
| 57 |
+
|
| 58 |
+
if args.path_prefix:
|
| 59 |
+
remote_path = f"{args.path_prefix.strip('/')}/{exp_name}"
|
| 60 |
+
else:
|
| 61 |
+
remote_path = f"{exp_name}"
|
| 62 |
+
|
| 63 |
+
print(f"Preparing upload of {exp_path} to {args.repo}/{remote_path}...")
|
| 64 |
+
|
| 65 |
+
# Find the best checkpoint
|
| 66 |
+
models_dir = exp_path / "models"
|
| 67 |
+
best_checkpoint = models_dir / "ParT-model_best.ckpt"
|
| 68 |
+
|
| 69 |
+
if not best_checkpoint.exists():
|
| 70 |
+
print(f"Warning: Best checkpoint {best_checkpoint} not found. Searching for any .ckpt in {models_dir}...")
|
| 71 |
+
checkpoints = sorted(list(models_dir.glob("*.ckpt")), key=lambda x: x.stat().st_mtime)
|
| 72 |
+
if checkpoints:
|
| 73 |
+
best_checkpoint = checkpoints[-1]
|
| 74 |
+
print(f"Using latest checkpoint: {best_checkpoint.name}")
|
| 75 |
+
else:
|
| 76 |
+
print(f"Error: No checkpoints found in {models_dir}")
|
| 77 |
+
return
|
| 78 |
+
|
| 79 |
+
api = HfApi() if not args.dry_run else None
|
| 80 |
+
|
| 81 |
+
total_size = 0
|
| 82 |
+
|
| 83 |
+
# Upload best weights
|
| 84 |
+
if args.dry_run:
|
| 85 |
+
print(f"[DRY-RUN] Would upload {best_checkpoint} as {remote_path}/models/{best_checkpoint.name}")
|
| 86 |
+
else:
|
| 87 |
+
print(f"Uploading {best_checkpoint} as {remote_path}/models/{best_checkpoint.name}...")
|
| 88 |
+
api.upload_file(
|
| 89 |
+
path_or_fileobj=str(best_checkpoint),
|
| 90 |
+
path_in_repo=f"{remote_path}/models/{best_checkpoint.name}",
|
| 91 |
+
repo_id=args.repo,
|
| 92 |
+
repo_type=args.repo_type,
|
| 93 |
+
)
|
| 94 |
+
total_size += best_checkpoint.stat().st_size
|
| 95 |
+
|
| 96 |
+
# Directories to upload
|
| 97 |
+
# (local_name, remote_name, optional_ignore_patterns)
|
| 98 |
+
dirs_to_upload = [
|
| 99 |
+
("logs", "logs"),
|
| 100 |
+
("tensorboard", "tensorboard"),
|
| 101 |
+
("predictions", "predictions"),
|
| 102 |
+
(".hydra", "config"),
|
| 103 |
+
]
|
| 104 |
+
|
| 105 |
+
# Upload folders
|
| 106 |
+
for item in dirs_to_upload:
|
| 107 |
+
local_dir_name = item[0]
|
| 108 |
+
remote_dir_name = item[1]
|
| 109 |
+
ignore_patterns = item[2] if len(item) > 2 else None
|
| 110 |
+
|
| 111 |
+
local_dir_path = exp_path / local_dir_name
|
| 112 |
+
if local_dir_path.exists():
|
| 113 |
+
size = get_dir_size(local_dir_path, ignore_patterns=ignore_patterns)
|
| 114 |
+
total_size += size
|
| 115 |
+
if args.dry_run:
|
| 116 |
+
print(f"[DRY-RUN] Would upload folder {local_dir_path} ({format_size(size)}) to {remote_path}/{remote_dir_name}")
|
| 117 |
+
else:
|
| 118 |
+
print(f"Uploading folder {local_dir_path} ({format_size(size)}) to {remote_path}/{remote_dir_name}...")
|
| 119 |
+
api.upload_folder(
|
| 120 |
+
folder_path=str(local_dir_path),
|
| 121 |
+
path_in_repo=f"{remote_path}/{remote_dir_name}",
|
| 122 |
+
repo_id=args.repo,
|
| 123 |
+
repo_type=args.repo_type,
|
| 124 |
+
ignore_patterns=ignore_patterns,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# Upload all PDF files in the experiment directory recursively
|
| 128 |
+
# This captures validation plots
|
| 129 |
+
for pdf_file in exp_path.rglob("*.pdf"):
|
| 130 |
+
# Relative path from exp_path to preserve structure if they are in subdirs
|
| 131 |
+
rel_path = pdf_file.relative_to(exp_path)
|
| 132 |
+
if args.dry_run:
|
| 133 |
+
print(f"[DRY-RUN] Would upload {pdf_file} as {remote_path}/{rel_path}")
|
| 134 |
+
else:
|
| 135 |
+
print(f"Uploading {pdf_file} as {remote_path}/{rel_path}...")
|
| 136 |
+
api.upload_file(
|
| 137 |
+
path_or_fileobj=str(pdf_file),
|
| 138 |
+
path_in_repo=f"{remote_path}/{rel_path}",
|
| 139 |
+
repo_id=args.repo,
|
| 140 |
+
repo_type=args.repo_type,
|
| 141 |
+
)
|
| 142 |
+
total_size += pdf_file.stat().st_size
|
| 143 |
+
|
| 144 |
+
print(f"Total size processed: {format_size(total_size)}")
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
if __name__ == "__main__":
|
| 148 |
+
main()
|
model_card.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
tags:
|
| 3 |
+
- particle-physics
|
| 4 |
+
- tau-reconstruction
|
| 5 |
+
- fcc-ee
|
| 6 |
+
- key4hep
|
| 7 |
+
- particle-transformer
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# ML-Tau Model Card
|
| 11 |
+
|
| 12 |
+
This repository contains models for tau reconstruction and identification at future colliders (FCC), based on the Particle Transformer (ParT) architecture.
|
| 13 |
+
|
| 14 |
+
## Dataset
|
| 15 |
+
- **Name:** `0509_dsinphi_to_sindphi`
|
| 16 |
+
- **Source:** Preprocessed jet-based FCC dataset for hadronic tau reconstruction.
|
| 17 |
+
- **Physics Processes:**
|
| 18 |
+
- **Signal:** $Z \to \tau^+\tau^-$ events.
|
| 19 |
+
- **Background:** $Z \to q\bar{q}$ (light quarks and gluons).
|
| 20 |
+
- **Generation & Simulation:**
|
| 21 |
+
- **Generator:** Pythia8
|
| 22 |
+
- **Detector Model:** CLD (`CLD_o2_v07`) for FCC-ee.
|
| 23 |
+
- **Simulation:** Geant4 (via `ddsim`).
|
| 24 |
+
- **Software Stack:** [Key4hep Project](https://github.com/key4hep) (release 2025-05-29). [Key4hep-sim (v1.2.5)](https://github.com/HEP-KBFI/key4hep-sim/tree/v1.2.5)
|
| 25 |
+
- **Reconstruction:** Standard CLD reconstruction (`CLDReconstruction.py`).
|
| 26 |
+
- **Split:** 70% Training, 10% Validation, 20% Testing.
|
| 27 |
+
- **Input Features:** 17 candidate-level features (kinematics, identification, etc.).
|
| 28 |
+
- **Jet Composition:** Maximum of 20 candidates per jet.
|
| 29 |
+
|
| 30 |
+
## Dataset Statistics
|
| 31 |
+
- **Total Jets:** 9,049,163
|
| 32 |
+
- **Signal (Tau) Jets:** 1,187,870
|
| 33 |
+
- **Background (Quark/Gluon) Jets:** 7,861,293
|
| 34 |
+
- **Training Set:** 7,075,163 background + 1,069,083 signal jets
|
| 35 |
+
- **Test Set:** 786,130 background + 118,787 signal jets
|
| 36 |
+
|
| 37 |
+
## Model Architecture
|
| 38 |
+
The models utilize the **Particle Transformer (ParT)** architecture, which uses a combination of particle-level and pair-level features to learn jet representations.
|
| 39 |
+
|
| 40 |
+
### Variants
|
| 41 |
+
- **MultiParTau:** A multi-task learning model that simultaneously performs four tasks:
|
| 42 |
+
- **Tau Identification (`is_tau`):** Binary classification (Signal tau vs. Quark/Gluon jet).
|
| 43 |
+
- **Charge Classification:** Identification of the tau charge (+1 or -1).
|
| 44 |
+
- **Decay Mode Classification:** 6-class classification of tau decay modes.
|
| 45 |
+
- **Kinematics Regression:** Prediction of 5 kinematic corrections: `[log(pt_gen/pt_reco), delta_eta, delta_sin(phi), delta_cos(phi), log(m_gen/m_reco)]`.
|
| 46 |
+
- **SingleParTau:** Specialized models trained for one of the above tasks individually.
|
| 47 |
+
|
| 48 |
+
### Hyperparameters
|
| 49 |
+
- **Embedding Dimensions:** `[256, 512, 256]`
|
| 50 |
+
- **Pair Embedding Dimensions:** `[64, 64, 64]`
|
| 51 |
+
- **Attention Heads:** 8
|
| 52 |
+
- **Transformer Layers:** 8 (default) / 2 (specific runs)
|
| 53 |
+
- **CLS Layers:** 2
|
| 54 |
+
- **Activation:** GELU
|
| 55 |
+
|
| 56 |
+
## Training Scheme
|
| 57 |
+
- **Optimizer:** AdamW with a weight decay of 1e-2.
|
| 58 |
+
- **Learning Rate:** 0.001 (base).
|
| 59 |
+
- **Scheduler:** `OneCycleLR` with cosine annealing.
|
| 60 |
+
- **Batch Size:** 12288.
|
| 61 |
+
- **Precision:** 16-mixed (FP16).
|
| 62 |
+
- **Multi-task Strategy (MultiParTau):** PCGrad (Projected Conflicting Gradients) is employed to handle gradient conflicts between different tasks during training.
|
| 63 |
+
- **Task Weighting:**
|
| 64 |
+
- Tau ID: 1.0
|
| 65 |
+
- Charge: 1.0
|
| 66 |
+
- Decay Mode: 1.0
|
| 67 |
+
- Kinematics: 2.0
|
| 68 |
+
|
| 69 |
+
## Trained Models & Git Hashes
|
| 70 |
+
The models located in the `outputs/0609_*` directories correspond to the following configurations and git hashes:
|
| 71 |
+
|
| 72 |
+
| Model Name | Task | Git Hash |
|
| 73 |
+
|------------|------|----------|
|
| 74 |
+
| `multipartau_full` | Multi-task | `af76c4c` |
|
| 75 |
+
| `single_charge` | Charge | `a5d7ed8` |
|
| 76 |
+
| `single_decaymode` | Decay Mode | `a5d7ed8` |
|
| 77 |
+
| `single_kinematics` | Kinematics | `a5d7ed8` |
|
| 78 |
+
| `single_tauid` | Tau ID | `a5d7ed8` |
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
*Generated based on training configurations and experiment outputs.*
|
| 82 |
+
|