Skip to content

Commit cef4a58

Browse files
abidlabsclaudegradio-pr-bot
authored
Add remote HF Space support to CLI (#445)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
1 parent e9478fa commit cef4a58

13 files changed

Lines changed: 609 additions & 165 deletions

File tree

.agents/skills/trackio/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Use the `trackio` command to query logged metrics and alerts:
5353

5454
**Key concept**: Add `--json` for programmatic output suitable for automation and LLM agents.
5555

56+
**Remote Spaces**: Add `--space <space_id_or_url>` to any `list`/`get` command to query a remote HF Space instead of local data. Use `--hf-token` for private Spaces.
57+
5658
→ See [retrieving_metrics.md](retrieving_metrics.md) for all commands, workflows, and JSON output formats.
5759

5860
## Minimal Logging Setup
@@ -71,6 +73,9 @@ trackio.finish()
7173
```bash
7274
trackio list projects --json
7375
trackio get metric --project my-project --run my-run --metric loss --json
76+
77+
# Query a remote Space
78+
trackio list projects --space username/my-space --json
7479
```
7580

7681
## Autonomous ML Experiment Workflow

.agents/skills/trackio/retrieving_metrics.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Retrieving Metrics with Trackio CLI
22

3-
The `trackio` CLI provides direct terminal access to query Trackio experiment tracking data locally without needing to start the MCP server.
3+
The `trackio` CLI provides direct terminal access to query Trackio experiment tracking data without needing to start the MCP server. Commands work against local data by default, or against a remote HF Space when `--space` is provided.
44

55
## Quick Command Reference
66

@@ -18,6 +18,7 @@ The `trackio` CLI provides direct terminal access to query Trackio experiment tr
1818
| Get metric around step | `trackio get metric ... --metric <name> --around <N> --window <W>` |
1919
| Get all metrics snapshot | `trackio get snapshot --project <name> --run <name> --step <N>` |
2020
| Get system metrics | `trackio get system-metric --project <name> --run <name>` |
21+
| Query remote Space | `trackio list projects --space <space_id_or_url>` |
2122
| Show dashboard | `trackio show [--project <name>]` |
2223
| Sync to Space | `trackio sync --project <name> --space-id <space_id>` |
2324

@@ -68,6 +69,17 @@ trackio get system-metric --project <name> --run <name> --metric <name> # Speci
6869
trackio get system-metric --project <name> --run <name> --json
6970
```
7071

72+
### Remote Space Queries
73+
74+
All `list` and `get` commands support querying a remote HF Space with `--space`:
75+
76+
```bash
77+
trackio list projects --space user/my-space # Space ID
78+
trackio list projects --space https://user-my-space.hf.space # Space URL
79+
trackio get metric --project <name> --run <name> --metric loss --space user/my-space
80+
trackio list projects --space user/private-space --hf-token hf_xxx # Private Space
81+
```
82+
7183
### Dashboard Commands
7284

7385
```bash
@@ -185,6 +197,8 @@ All errors exit with non-zero status code and write to stderr.
185197
- `--run`: Run name (required for run-specific commands)
186198
- `--metric`: Metric name (required for metric-specific commands)
187199
- `--json`: Output in JSON format instead of human-readable
200+
- `--space`: HF Space ID (e.g. `user/space`) or Space URL to query remotely (for `list`/`get` commands)
201+
- `--hf-token`: HF token for accessing private Spaces (for `list`/`get` commands with `--space`)
188202
- `--step`: Exact step filter (for `get metric`, `get snapshot`)
189203
- `--around`: Center step for window filter (for `get metric`, `get snapshot`)
190204
- `--at-time`: Center ISO timestamp for window filter (for `get metric`, `get snapshot`)

.changeset/calm-suits-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trackio": minor
3+
---
4+
5+
feat:Add remote HF Space support to CLI

autonomous-experiments/01_finding_the_best_learning_rate/train_nanogpt.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import torch
4444
import torch.nn as nn
4545
import torch.nn.functional as F
46+
4647
import trackio
4748

4849
# =====================================================================
@@ -206,7 +207,9 @@ class Muon(torch.optim.Optimizer):
206207
"""
207208

208209
def __init__(self, params, lr=0.02, momentum=0.95, weight_decay=0.0, ns_steps=5):
209-
defaults = dict(lr=lr, momentum=momentum, weight_decay=weight_decay, ns_steps=ns_steps)
210+
defaults = dict(
211+
lr=lr, momentum=momentum, weight_decay=weight_decay, ns_steps=ns_steps
212+
)
210213
super().__init__(params, defaults)
211214

212215
@torch.no_grad()
@@ -279,7 +282,9 @@ def _load_token_shard(path):
279282
header = np.memmap(path, dtype=np.int32, mode="r", shape=(256,))
280283
if int(header[0]) == 20240520:
281284
num_tokens = int(header[2])
282-
return np.memmap(path, dtype=np.uint16, mode="r", offset=256 * 4, shape=(num_tokens,))
285+
return np.memmap(
286+
path, dtype=np.uint16, mode="r", offset=256 * 4, shape=(num_tokens,)
287+
)
283288
return np.memmap(path, dtype=np.uint16, mode="r")
284289

285290
def __init__(self, data_dir, split, batch_size, seq_len, device, vocab_size):
@@ -298,7 +303,9 @@ def __init__(self, data_dir, split, batch_size, seq_len, device, vocab_size):
298303
total_tokens = sum(len(s) for s in self.shards)
299304
print(f" {split}: {len(files)} shard(s), {total_tokens / 1e6:.0f}M tokens")
300305

301-
sample_tokens = np.concatenate([s[: min(4096, len(s))] for s in self.shards[:2]])
306+
sample_tokens = np.concatenate(
307+
[s[: min(4096, len(s))] for s in self.shards[:2]]
308+
)
302309
sample_max = int(sample_tokens.max()) if len(sample_tokens) > 0 else -1
303310
assert sample_max < self.vocab_size, (
304311
f"Token id {sample_max} exceeds vocab_size={self.vocab_size}. "
@@ -456,9 +463,7 @@ def set_lr(step):
456463
def main():
457464
import argparse
458465

459-
parser = argparse.ArgumentParser(
460-
description="Minimal NanoGPT training on FineWeb"
461-
)
466+
parser = argparse.ArgumentParser(description="Minimal NanoGPT training on FineWeb")
462467

463468
parser.add_argument(
464469
"--optimizer",
@@ -467,9 +472,13 @@ def main():
467472
choices=["muon", "adamw"],
468473
help="Optimizer: muon (Muon+AdamW) or adamw (pure AdamW)",
469474
)
470-
parser.add_argument("--learning_rate", type=float, default=None,
471-
help="Primary learning rate. Overrides --adam_lr for adamw, "
472-
"or both --muon_lr and --adam_lr (scaled) for muon.")
475+
parser.add_argument(
476+
"--learning_rate",
477+
type=float,
478+
default=None,
479+
help="Primary learning rate. Overrides --adam_lr for adamw, "
480+
"or both --muon_lr and --adam_lr (scaled) for muon.",
481+
)
473482
parser.add_argument("--muon_lr", type=float, default=0.02)
474483
parser.add_argument("--adam_lr", type=float, default=6e-4)
475484
parser.add_argument("--weight_decay", type=float, default=0.01)
@@ -495,8 +504,12 @@ def main():
495504
parser.add_argument("--log_interval", type=int, default=10)
496505
parser.add_argument("--heartbeat_seconds", type=int, default=30)
497506
parser.add_argument("--seed", type=int, default=42)
498-
parser.add_argument("--run_name", type=str, default=None,
499-
help="Override the auto-generated Trackio run name.")
507+
parser.add_argument(
508+
"--run_name",
509+
type=str,
510+
default=None,
511+
help="Override the auto-generated Trackio run name.",
512+
)
500513

501514
args = parser.parse_args()
502515

@@ -580,8 +593,10 @@ def main():
580593
optimizers, set_lr = build_optimizers(model, args)
581594

582595
tokens_per_step = args.batch_size * args.seq_len * args.grad_accum_steps
583-
print(f"\nTraining:")
584-
print(f" Batch size: {args.batch_size} x {args.grad_accum_steps} accum = {args.batch_size * args.grad_accum_steps} effective")
596+
print("\nTraining:")
597+
print(
598+
f" Batch size: {args.batch_size} x {args.grad_accum_steps} accum = {args.batch_size * args.grad_accum_steps} effective"
599+
)
585600
print(f" Sequence length: {args.seq_len}")
586601
print(f" Tokens/step: {tokens_per_step:,}")
587602
print(f" Max steps: {args.max_steps}")
@@ -621,17 +636,28 @@ def main():
621636
dt = time.time() - t0
622637
tokens_seen = (step + 1) * tokens_per_step
623638
tps = tokens_seen / dt if dt > 0 else 0
624-
trackio.log({"train_loss": loss_accum, "lr": current_lr, "tok_per_sec": tps, "step": step})
639+
trackio.log(
640+
{
641+
"train_loss": loss_accum,
642+
"lr": current_lr,
643+
"tok_per_sec": tps,
644+
"step": step,
645+
}
646+
)
625647
last_heartbeat_t = time.time()
626-
elif args.heartbeat_seconds > 0 and (time.time() - last_heartbeat_t) >= args.heartbeat_seconds:
648+
elif (
649+
args.heartbeat_seconds > 0
650+
and (time.time() - last_heartbeat_t) >= args.heartbeat_seconds
651+
):
627652
dt = time.time() - t0
628-
pct = (step + 1) / args.max_steps * 100
629653
last_heartbeat_t = time.time()
630654

631655
if step > 0 and step % args.eval_interval == 0:
632656
val_loss = evaluate(model, val_loader, args.eval_steps)
633657
best_val_loss = min(best_val_loss, val_loss)
634-
trackio.log({"val_loss": val_loss, "best_val_loss": best_val_loss, "step": step})
658+
trackio.log(
659+
{"val_loss": val_loss, "best_val_loss": best_val_loss, "step": step}
660+
)
635661
if prev_val_loss is not None and val_loss > prev_val_loss:
636662
trackio.alert(
637663
title="Val loss increasing",
@@ -656,7 +682,14 @@ def main():
656682
| Total tokens | {total_tokens / 1e6:.0f}M |
657683
| Throughput | {total_tokens / total_time / 1e3:.1f}K tok/s |
658684
""")
659-
trackio.log({"val_loss": val_loss, "best_val_loss": best_val_loss, "step": args.max_steps, "report": report})
685+
trackio.log(
686+
{
687+
"val_loss": val_loss,
688+
"best_val_loss": best_val_loss,
689+
"step": args.max_steps,
690+
"report": report,
691+
}
692+
)
660693

661694
trackio.finish()
662695

autonomous-experiments/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Autonomous Experiments
2+
3+
This directory contains scripts and prompt files that can be provided to coding agents (for example, Claude Code) to run autonomous machine learning experiments.

docs/source/cli_commands.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
# CLI Commands
22

3-
Trackio provides a comprehensive set of CLI commands that enable you to query project, run, and metric information locally without needing to start the MCP server. This is particularly useful for LLM agents and automation scripts. With structured JSON output and programmatic access to all experiment data, Trackio is designed to support autonomous ML experiments run by LLMs.
3+
Trackio provides a comprehensive set of CLI commands that enable you to query project, run, and metric information without needing to start the MCP server. Commands work against local data by default, or against a remote HF Space when `--space` is provided. This is particularly useful for LLM agents and automation scripts. With structured JSON output and programmatic access to all experiment data, Trackio is designed to support autonomous ML experiments run by LLMs.
4+
5+
## Querying Remote Spaces
6+
7+
All `list` and `get` commands support querying a remote Hugging Face Space instead of local data. Pass the `--space` flag with either a Space ID or full URL:
8+
9+
```sh
10+
# Using a Space ID
11+
trackio list projects --space username/my-space
12+
13+
# Using a Space URL
14+
trackio list projects --space https://username-my-space.hf.space
15+
16+
# Works with any list/get command
17+
trackio get metric --project "my-project" --run "my-run" --metric "loss" --space username/my-space
18+
```
19+
20+
For private Spaces, pass `--hf-token` or ensure you are logged in via `huggingface-cli login`:
21+
22+
```sh
23+
trackio list projects --space username/private-space --hf-token hf_xxxxx
24+
```
25+
26+
> **Note:** The `show`, `status`, `sync`, and `skills` commands are local-only and do not support `--space`.
427
528
## List Commands
629

@@ -293,6 +316,18 @@ trackio list reports --project "my-project" --run "my-run"
293316
trackio get report --project "my-project" --run "my-run" --report "training_report"
294317
```
295318

319+
### Querying a Remote Space
320+
321+
The same workflow works against a remote Space — just add `--space`:
322+
323+
```sh
324+
# List projects on a remote Space
325+
trackio list projects --space username/my-space
326+
327+
# Get metric values from a remote Space
328+
trackio get metric --project "my-project" --run "my-run" --metric "loss" --space username/my-space --json
329+
```
330+
296331
## Use Cases
297332

298333
### LLM Agents
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import json
2+
import secrets
3+
import subprocess
4+
5+
6+
def test_cli_remote_list_and_get(test_space_id):
7+
import trackio
8+
9+
project_name = f"test_cli_remote_{secrets.token_urlsafe(8)}"
10+
run_name = "cli_run"
11+
12+
trackio.init(project=project_name, name=run_name, space_id=test_space_id)
13+
trackio.log({"loss": 0.5, "acc": 0.8})
14+
trackio.log({"loss": 0.3, "acc": 0.9})
15+
trackio.finish()
16+
17+
def cli(*args):
18+
result = subprocess.run(
19+
["trackio", *args, "--space", test_space_id, "--json"],
20+
capture_output=True,
21+
text=True,
22+
)
23+
assert result.returncode == 0, f"CLI failed: {result.stderr}"
24+
return json.loads(result.stdout)
25+
26+
projects = cli("list", "projects")
27+
assert project_name in projects["projects"]
28+
29+
runs = cli("list", "runs", "--project", project_name)
30+
assert run_name in runs["runs"]
31+
32+
metrics = cli("list", "metrics", "--project", project_name, "--run", run_name)
33+
assert "loss" in metrics["metrics"]
34+
assert "acc" in metrics["metrics"]
35+
36+
values = cli(
37+
"get",
38+
"metric",
39+
"--project",
40+
project_name,
41+
"--run",
42+
run_name,
43+
"--metric",
44+
"loss",
45+
)
46+
assert len(values["values"]) == 2
47+
assert values["values"][0]["value"] == 0.5
48+
assert values["values"][1]["value"] == 0.3
49+
50+
summary = cli("get", "run", "--project", project_name, "--run", run_name)
51+
assert summary["num_logs"] == 2
52+
assert "loss" in summary["metrics"]

0 commit comments

Comments
 (0)