Skip to content

Commit a86be0c

Browse files
authored
Merge branch 'main' into add/juicefs-mounting
2 parents e827932 + b3f2f5d commit a86be0c

51 files changed

Lines changed: 2284 additions & 476 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/transformerlab-cli/SKILL.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,40 @@ lab --format json task info 42
8686
lab job list --format json
8787
```
8888

89+
## Profiles: talking to multiple servers in parallel
90+
91+
A **profile** bundles one server's URL, team, experiment, and API key. The `default` profile lives at the legacy location (`~/.lab/config.json`, `~/.lab/credentials`); named profiles live under `~/.lab/profiles/<name>/`. This lets two `lab` commands hit two different servers (with different teams / API keys) **at the same time** without clobbering each other.
92+
93+
Selection is **per-process** — there is no stored "current profile" to switch (so nothing one process can yank out from under another). Precedence (highest first):
94+
95+
1. `--profile <name>` — a root-level flag; like `--format` it MUST come before the subcommand: `lab --profile prod job list`
96+
2. `LAB_PROFILE` environment variable
97+
3. `default`
98+
99+
```bash
100+
# Create/authenticate a profile. `login` is the profile-creating command;
101+
# combine the global --profile flag with --server:
102+
lab --profile prod login --server https://prod.example.com:8338
103+
lab --profile staging login --server https://staging.example.com:8338
104+
105+
# Run against two servers in parallel — fully isolated, safe:
106+
LAB_PROFILE=prod lab job list &
107+
LAB_PROFILE=staging lab job list &
108+
109+
# One-off override without exporting the env var:
110+
lab --profile prod job list
111+
```
112+
113+
Manage profiles:
114+
115+
```bash
116+
lab profile list # all profiles; marks the active one + which have saved credentials
117+
lab profile show [name] # server/team/user/experiment for a profile (defaults to the active one)
118+
lab profile delete <name> # remove a named profile ('default' cannot be deleted)
119+
```
120+
121+
**For agents:** to run workstreams against different *servers* concurrently, give each its own profile and set `LAB_PROFILE` per process (or pass `--profile` on every call). This is the server/team/credentials analogue of the per-command `-e/--experiment` override — profiles scope which server+team+key you talk to, `-e` scopes which experiment *within* that server (see "Scoping a single command to an experiment"). Profiles do **not** scope the experiment; keep using `-e` for that.
122+
89123
## Core Workflow
90124

91125
The standard pattern for working with Transformer Lab:
@@ -308,7 +342,7 @@ lab notes append "..." -e exp-a
308342

309343
- **Pass `-e <exp>` on every `task` / `job` / `notes` command** for that workstream. Treat it as mandatory, not optional — a single omitted `-e` leaks into the global default.
310344
- **Never call `lab experiment set-default` (or `lab config set current_experiment`) mid-flight** while other experiments are in flight — it yanks the default out from under any command that forgot its `-e`.
311-
- There is **no environment variable** (e.g. `LAB_EXPERIMENT`) and **no per-session config file** to scope this once for a whole session — the override is strictly per-command. Plan for passing `-e` on each call.
345+
- There is **no environment variable** (e.g. `LAB_EXPERIMENT`) and **no per-session config file** to scope the *experiment* once for a whole session — the `-e` override is strictly per-command. Plan for passing `-e` on each call. (The *server/team/credentials* dimension is different: that **can** be scoped per-process via a profile + `LAB_PROFILE` — see "Profiles: talking to multiple servers in parallel". A profile does not pin the experiment, so even within a profile you still pass `-e` per command.)
312346

313347
For a single, sequential workflow it's fine to set the default once with `set-default` and omit `-e`. The `-e` discipline only becomes load-bearing when more than one experiment is active concurrently.
314348

@@ -596,6 +630,13 @@ lab --format json provider info PROVIDER_ID
596630
# Health check (verifies the CLI can reach the provider's backend)
597631
lab --format json provider check PROVIDER_ID
598632

633+
# Show the GPUs available on a provider (accepts an id OR a name, like `delete`).
634+
# Reports live availability where the backend can report it (Slurm, SkyPilot,
635+
# RunPod, Lambda, Vast, Local), otherwise the provider's catalog of launchable
636+
# GPU types (AWS, GCP, Azure, Nebius). Each row is a GPU name + count; an empty
637+
# result ("No GPU information available") is normal for dstack and CPU-only hosts.
638+
lab --format json provider gpus PROVIDER_ID_OR_NAME
639+
599640
# Toggle availability without deleting
600641
lab provider enable PROVIDER_ID
601642
lab provider disable PROVIDER_ID
@@ -952,6 +993,9 @@ This applies to launching jobs, fetching logs, checking cluster status, and ever
952993
| `lab logout` | Remove stored API key | No |
953994
| `lab whoami` | Show current user and team | No |
954995
| `lab version` | Show CLI version | No |
996+
| `lab profile list` | List CLI profiles (server+team+credentials sets); marks the active one and which have credentials | No |
997+
| `lab profile show [name]` | Show a profile's server/team/user/experiment (defaults to the active profile) | No |
998+
| `lab profile delete <name>` | Delete a named profile (`'default'` cannot be deleted; `--yes` to skip prompt) | No |
955999
| `lab experiment list` | List all experiments (current default marked with `*`) | No |
9561000
| `lab experiment create <name>` | Create a new experiment (`--set-default` to also switch to it) | No |
9571001
| `lab experiment delete <id>` | Delete an experiment (`--no-interactive` to skip prompt) | No |
@@ -990,6 +1034,7 @@ This applies to launching jobs, fetching logs, checking cluster status, and ever
9901034
| `lab provider update <id>` | Update provider config | No |
9911035
| `lab provider delete <id>` | Delete a provider (`--no-interactive` to skip prompt) | No |
9921036
| `lab provider check <id>` | Check provider health | No |
1037+
| `lab provider gpus <id_or_name>` | Show available GPUs on a provider (live where supported, else catalog) | No |
9931038
| `lab provider verify-lifecycle <id>` | Verify provider lifecycle via a storage probe (`--no-wait` to launch only; see `--help` for polling options) | No |
9941039
| `lab provider enable <id>` | Enable a provider | No |
9951040
| `lab provider disable <id>` | Disable a provider | No |

.agents/skills/transformerlab-cli/references/commands.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ Full reference for every CLI command. All commands are invoked via:
66
lab <command> [subcommand] [options]
77
```
88

9-
Global option available on all commands:
10-
- `--format pretty|json` — Output format (default: `pretty`). **Must come before the subcommand.**
9+
Global options available on all commands (**must come before the subcommand**):
10+
- `--format pretty|json` — Output format (default: `pretty`).
11+
- `--profile <name>` — Select the profile (server + team + credentials) for this invocation. Overrides the `LAB_PROFILE` env var; defaults to `default`. See [`profile`](#profile) below.
1112

1213
```bash
1314
# Correct
1415
lab --format json task list
16+
lab --profile prod job list
1517

1618
# Wrong — flag will be ignored
1719
lab task list --format json
@@ -61,9 +63,15 @@ Authenticate with the server. Also configures `server`, `user_email`, `team_id`,
6163
| `--api-key <key>` | API key (prompted if omitted) |
6264
| `--server <url>` | Server URL (prompted if omitted) |
6365

66+
To log into a **named profile**, combine the global `--profile` flag with `--server` — the credentials and config are written under that profile instead of `default`:
67+
68+
```bash
69+
lab --profile prod login --server https://prod.example.com:8338
70+
```
71+
6472
### `logout`
6573

66-
Remove stored API key.
74+
Remove the stored API key for the active profile (selected via `--profile` / `LAB_PROFILE`; defaults to `default`).
6775

6876
### `whoami`
6977

@@ -74,6 +82,31 @@ Show current user, team, and server.
7482
{"email": "user@example.com", "team_id": "...", "team_name": "...", "server": "http://localhost:8338"}
7583
```
7684

85+
### `profile`
86+
87+
Manage CLI **profiles** — independent `(server + team + experiment + API key)` sets, so two `lab` commands can talk to two different servers in parallel. The `default` profile lives at the legacy root (`~/.lab/config.json`, `~/.lab/credentials`); named profiles live under `~/.lab/profiles/<name>/`.
88+
89+
Selection is **per-process** (there is no `use` subcommand / stored "current profile"). Precedence: `--profile <name>` (root-level flag, before the subcommand) > `LAB_PROFILE` env var > `default`.
90+
91+
```bash
92+
lab profile list # list all profiles; marks active + which have credentials
93+
lab profile show [name] # show a profile's config (defaults to the active profile)
94+
lab profile delete <name> --yes # delete a named profile ('default' cannot be deleted)
95+
96+
# Create/authenticate a profile via login (see `login` above):
97+
lab --profile prod login --server https://prod.example.com:8338
98+
99+
# Run two servers in parallel:
100+
LAB_PROFILE=prod lab job list &
101+
LAB_PROFILE=staging lab job list &
102+
```
103+
104+
| Subcommand | Description |
105+
|---|---|
106+
| `list` | List all profiles. JSON: array of `{name, active, has_credentials}`. |
107+
| `show [name]` | Show `name`/`server`/`team_id`/`team_name`/`user_email`/`current_experiment`/`has_credentials` for a profile (defaults to the active one). |
108+
| `delete <name>` | Delete a named profile directory. Refuses `default`. `--yes`/`-y` skips the confirm prompt. |
109+
77110
---
78111

79112
## Experiment Commands
@@ -498,6 +531,28 @@ Delete a compute provider.
498531

499532
Check connectivity and health of a provider.
500533

534+
### `provider gpus <provider_id_or_name>`
535+
536+
Show the GPUs available on a provider. The argument accepts either a provider id
537+
or a name (resolved the same way as `provider delete`). Output is a `GPU | Count`
538+
table, or `--format json` returns `{provider_id, provider_type, gpus: [{gpu, count}]}`.
539+
540+
Semantics: **live availability where the backend can report it** — Slurm (free
541+
GPUs per node), SkyPilot (catalog across enabled clouds), RunPod, Lambda
542+
(regions with capacity), Vast.ai (rentable offers), Local (detected GPUs) —
543+
otherwise the provider's **catalog of launchable GPU types** (AWS, GCP, Azure,
544+
Nebius, and the fallback for the live providers). `count` is the available
545+
quantity for live sources, or the max launchable count per node for catalog
546+
sources; there is no live-vs-catalog flag in the output. An empty list
547+
(`No GPU information available`) is expected for dstack (no enumeration endpoint)
548+
and CPU-only local hosts. The command never errors on backend failures — it
549+
degrades to the catalog or an empty list.
550+
551+
```bash
552+
lab provider gpus my-skypilot
553+
lab --format json provider gpus PROVIDER_ID
554+
```
555+
501556
### `provider enable <provider_id>`
502557

503558
Enable a disabled provider.

.agents/skills/transformerlab-cli/references/troubleshooting.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ lab config set server http://correct-host:8338
5353
lab status
5454
```
5555

56+
If you regularly switch between servers, `lab config set server` (which mutates the shared `default` profile) is the wrong tool — use **profiles** instead so each server keeps its own URL + team + key, and select per command with `--profile <name>` / `LAB_PROFILE`. See "Profiles: talking to multiple servers in parallel" in `SKILL.md`.
57+
5658
---
5759

5860
## Authentication Errors (401/403)

api/test/api/test_request_logs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def list_jobs(self, cluster_name):
6161
def check(self):
6262
return True
6363

64+
def show_gpus(self):
65+
return []
66+
6467

6568
def _make_job_dict(
6669
job_id: str = "test-1",

api/test/test_remote_workdir_cd.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Tests for prepend_remote_workdir_cd.
2+
3+
Regression coverage for the `//main.py` quirk: on remote providers the run command
4+
executes from the provider's default cwd (e.g. `/` for Lambda cloud-init), not the
5+
directory where lab.copy_file_mounts() synced the task files. prepend_remote_workdir_cd
6+
prefixes a `cd` into the recorded workdir so a bare `python main.py` resolves.
7+
"""
8+
9+
from transformerlab.services.compute_provider.launch_credentials import WORKDIR_FILE_PATH
10+
from transformerlab.services.compute_provider.launch_template import prepend_remote_workdir_cd
11+
from transformerlab.shared.models.models import ProviderType
12+
13+
EXPECTED_PREFIX = f'cd "$(cat {WORKDIR_FILE_PATH} 2>/dev/null || echo "$HOME")" && '
14+
15+
16+
def test_prefixes_cd_for_remote_provider_with_file_mounts():
17+
for provider_type in (
18+
ProviderType.LAMBDA.value,
19+
ProviderType.RUNPOD.value,
20+
ProviderType.SKYPILOT.value,
21+
ProviderType.VASTAI.value,
22+
ProviderType.DSTACK.value,
23+
):
24+
out = prepend_remote_workdir_cd(
25+
"python main.py",
26+
task_id="task-123",
27+
file_mounts=True,
28+
provider_type=provider_type,
29+
)
30+
assert out == EXPECTED_PREFIX + "python main.py", provider_type
31+
32+
33+
def test_local_provider_is_not_prefixed():
34+
# Local already forces cwd == $HOME == the file-drop dir.
35+
out = prepend_remote_workdir_cd(
36+
"python main.py",
37+
task_id="task-123",
38+
file_mounts=True,
39+
provider_type=ProviderType.LOCAL.value,
40+
)
41+
assert out == "python main.py"
42+
43+
44+
def test_no_prefix_when_file_mounts_not_enabled():
45+
# No synced files (inlined run, or dict-form file_mounts handled natively):
46+
# copy_file_mounts isn't injected, so there's no workdir file and nothing to cd into.
47+
for file_mounts in (False, None, {"/remote": "/local"}):
48+
out = prepend_remote_workdir_cd(
49+
"python main.py",
50+
task_id="task-123",
51+
file_mounts=file_mounts,
52+
provider_type=ProviderType.LAMBDA.value,
53+
)
54+
assert out == "python main.py", file_mounts
55+
56+
57+
def test_no_prefix_without_task_id():
58+
out = prepend_remote_workdir_cd(
59+
"python main.py",
60+
task_id=None,
61+
file_mounts=True,
62+
provider_type=ProviderType.LAMBDA.value,
63+
)
64+
assert out == "python main.py"
65+
66+
67+
def test_preserves_shell_operators_in_command():
68+
# The whole command is later shlex.quoted as one payload to tfl-remote-trap, so
69+
# operators must be carried through verbatim after the cd prefix.
70+
cmd = "python main.py --epochs 3 && echo done"
71+
out = prepend_remote_workdir_cd(
72+
cmd,
73+
task_id="task-123",
74+
file_mounts=True,
75+
provider_type=ProviderType.RUNPOD.value,
76+
)
77+
assert out == EXPECTED_PREFIX + cmd

0 commit comments

Comments
 (0)