Download scripts/log_to_tensorboard.py from Dengliming/StreamPIReal6_3w: direct link, hf CLI and curl.
- Browser
- Download file 4.9 kB
-
https://huggingface.co/Dengliming/StreamPIReal6_3w/resolve/main/scripts/log_to_tensorboard.py
- Command line
-
hf download hf://Dengliming/StreamPIReal6_3w/scripts/log_to_tensorboard.py
-
curl -L -o log_to_tensorboard.py https://huggingface.co/Dengliming/StreamPIReal6_3w/resolve/main/scripts/log_to_tensorboard.py
4.9 kB
| """Parse training log file and write metrics to TensorBoard event files. | |
| Usage: | |
| # One-shot conversion | |
| python scripts/log_to_tensorboard.py --log /tmp/streampi_t3_robodojo_30k_v1_20260909.log | |
| # Watch mode (continuously monitor log for new entries) | |
| python scripts/log_to_tensorboard.py --log /tmp/streampi_t3_robodojo_30k_v1_20260909.log --watch | |
| # Custom output directory | |
| python scripts/log_to_tensorboard.py --log /tmp/run.log --output-dir runs/my_run | |
| """ | |
| import argparse | |
| import os | |
| import re | |
| import time | |
| from pathlib import Path | |
| try: | |
| from torch.utils.tensorboard import SummaryWriter | |
| except ImportError: | |
| from tensorboardX import SummaryWriter | |
| def parse_step_line(line: str) -> dict | None: | |
| """Parse a log line like 'Step 0: grad_norm=4.0662, loss=0.4126, param_norm=1802.3864'. | |
| Returns a dict with 'step' (int) and metric key-value pairs (float), or None. | |
| """ | |
| match = re.match(r"Step\s+(\d+):\s*(.+)", line.strip()) | |
| if not match: | |
| return None | |
| step = int(match.group(1)) | |
| metrics_str = match.group(2) | |
| result: dict = {"step": step} | |
| for pair in metrics_str.split(","): | |
| pair = pair.strip() | |
| if "=" not in pair: | |
| continue | |
| key, value = pair.split("=", 1) | |
| key = key.strip() | |
| value = value.strip() | |
| try: | |
| result[key] = float(value) | |
| except ValueError: | |
| continue | |
| return result | |
| def parse_log_file(log_path: str, last_step: int = -1) -> list[dict]: | |
| """Return deduplicated entries with step > last_step. | |
| If a resumed run repeats a step, the last occurrence in the log is kept, | |
| because it belongs to the trajectory resumed from the latest checkpoint. | |
| """ | |
| entries_by_step: dict[int, dict] = {} | |
| with open(log_path, "r", encoding="utf-8", errors="ignore") as f: | |
| for line in f: | |
| parsed = parse_step_line(line) | |
| if parsed is None: | |
| continue | |
| step = parsed["step"] | |
| if step <= last_step: | |
| continue | |
| # Later occurrences replace pre-resume entries at the same step. | |
| entries_by_step[step] = parsed | |
| return [entries_by_step[step] for step in sorted(entries_by_step)] | |
| def write_entries(writer: SummaryWriter, entries: list[dict]) -> int: | |
| """Write parsed entries to TensorBoard. Returns the max step written.""" | |
| max_step = -1 | |
| for entry in entries: | |
| step = entry["step"] | |
| for key, value in entry.items(): | |
| if key == "step": | |
| continue | |
| writer.add_scalar(key, value, step) | |
| max_step = max(max_step, step) | |
| writer.flush() | |
| return max_step | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Convert training log to TensorBoard events.") | |
| parser.add_argument("--log", type=str, required=True, help="Path to the training log file.") | |
| parser.add_argument( | |
| "--output-dir", | |
| type=str, | |
| default=None, | |
| help="TensorBoard output directory. Defaults to runs/<log_filename_stem>.", | |
| ) | |
| parser.add_argument( | |
| "--watch", | |
| action="store_true", | |
| help="Continuously monitor the log file for new entries.", | |
| ) | |
| parser.add_argument( | |
| "--interval", | |
| type=float, | |
| default=10.0, | |
| help="Polling interval in seconds for --watch mode (default: 10).", | |
| ) | |
| args = parser.parse_args() | |
| log_path = args.log | |
| if not os.path.exists(log_path): | |
| raise FileNotFoundError(f"Log file not found: {log_path}") | |
| log_stem = Path(log_path).stem | |
| output_dir = args.output_dir or os.path.join("runs", log_stem) | |
| os.makedirs(output_dir, exist_ok=True) | |
| writer = SummaryWriter(log_dir=output_dir) | |
| print(f"TensorBoard log dir: {os.path.abspath(output_dir)}") | |
| print(f"Monitoring log: {log_path}") | |
| # One-shot parse | |
| entries = parse_log_file(log_path) | |
| max_step = write_entries(writer, entries) | |
| print(f"Wrote {len(entries)} entries (max step: {max_step})") | |
| if not args.watch: | |
| writer.close() | |
| print(f"\nDone. Run: tensorboard --logdir {os.path.abspath(output_dir)}") | |
| return | |
| # Watch mode | |
| print(f"Watching for new entries (interval={args.interval}s). Press Ctrl+C to stop.") | |
| try: | |
| while True: | |
| time.sleep(args.interval) | |
| new_entries = parse_log_file(log_path, last_step=max_step) | |
| if new_entries: | |
| max_step = write_entries(writer, new_entries) | |
| print(f" Updated: +{len(new_entries)} entries (max step: {max_step})") | |
| except KeyboardInterrupt: | |
| print("\nStopping watch mode.") | |
| finally: | |
| writer.close() | |
| print(f"TensorBoard events written to: {os.path.abspath(output_dir)}") | |
| print(f"Run: tensorboard --logdir {os.path.abspath(output_dir)}") | |
| if __name__ == "__main__": | |
| main() |