Add fleet dashboard monitoring
This commit is contained in:
1 parent
e35a20bceb
commit
9fbe84123d
7 files changed
+809
-43
No files matched your search
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.0
|
||||
|
||||
- Added project-local fleets for grouping multiple AutoDL/SSH accounts under one experiment workspace.
|
||||
- Added `fleet create`, `fleet add`, `fleet list`, and `fleet status`.
|
||||
- Added read-only `dashboard` generation for single-machine and fleet views.
|
||||
- Added `dashboard --watch <seconds> --lines <n>` so the page can refresh itself and display recent remote run logs without Codex copying logs into chat.
|
||||
- Added run-oriented commands: `run submit`, `run list`, `run status`, `run tail`, and `run note`.
|
||||
- Added run metadata fields for model, tags, stage, purpose, outputs, account, and remote root.
|
||||
- Added `--account` and `--remote` overrides for `exec`, `put`, `get`, `tree`, `ls`, `cat`, and `tail` so one local project can operate multiple remote machines.
|
||||
- Fixed detached remote execution so long-running jobs detach from the SSH session immediately.
|
||||
- Fixed stdin handling during remote temp-directory creation so `exec --script` and `exec --stdin` no longer upload empty scripts.
|
||||
- Moved SSH ControlMaster sockets to a short `/tmp` path by default to avoid OpenSSH `ControlPath too long` failures on macOS temporary homes.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
- Strengthened the Codex skill rules so AutoDL/SSH work defaults to `autodl-remote` instead of raw `ssh`/`expect` scripts.
|
||||
- Instructed Codex to use `exec --script` or `exec --stdin` for complex remote checks and to avoid printing credentials when bypassing the plugin.
|
||||
|
||||
## 0.6.2
|
||||
|
||||
- Added `autodl-remote --version`, `autodl-remote -V`, and `autodl-remote version`.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Fixed keychain/expect argument forwarding for SSH password prompts.
|
||||
- Reused SSH ControlMaster settings for `scp` fallback paths.
|
||||
- Improved `exec --stdin` and `exec --script` upload behavior.
|
||||
- Refined dangerous command protection to allow concrete file/subdirectory deletion while still blocking root/project-root deletion and raw shutdown-style commands.
|
||||
|
||||
## 0.6.0
|
||||
|
||||
- Added `autodl-remote shutdown` with shutdown/poweroff/halt fallback and SSH disconnect-aware success handling.
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
| ⬇️ 显式下载 | 用 `get` 或 `sync-down` 只拉回需要查看或提交的结果 |
|
||||
| 📜 日志查看 | 用 `tail` 查看远端训练日志,不必下载大文件 |
|
||||
| 🧾 Job 状态 | 用 `job status` 和 `job tail` 跟踪 `exec --detach --name` 创建的长任务 |
|
||||
| 📊 Dashboard | 用只读页面查看多台远端设备、run 状态和最近日志,减少聊天里反复粘贴日志 |
|
||||
| 🧠 脚本执行 | 用 `exec --script` 或 `exec --stdin` 避免复杂 quoting |
|
||||
| ⏻ 远端关机 | 用 `shutdown` 处理 AutoDL 关机和 SSH 断开返回码 |
|
||||
| 📝 项目约定 | 首次使用自动生成 `.autodl-remote/CONVENTIONS.md` 记录远端约定 |
|
||||
@@ -223,6 +224,20 @@ autodl-remote exec -- nvidia-smi
|
||||
|
||||
  这个边界很重要:**本地负责代码和修改痕迹,远端负责大模型、数据集、checkpoint 和 GPU 任务。** 插件不会默认下载模型权重,也不会默认把整个远端目录同步回来。
|
||||
|
||||
## 📊 Dashboard
|
||||
|
||||
<div align="center">
|
||||
<img src="./docs/images/autodl-remote-dashboard.png" alt="AutoDL Remote Dashboard 示例" width="100%">
|
||||
</div>
|
||||
|
||||
  Dashboard 是只读的本地页面:它通过 SSH 轮询远端状态,显示设备在线情况、GPU/磁盘信息、当前 run,以及远端日志的最近若干行。Codex 只需要启动 dashboard 命令,不需要把训练日志复制到聊天里。
|
||||
|
||||
```bash
|
||||
autodl-remote dashboard --fleet llm-exp --open --watch 5 --lines 120
|
||||
```
|
||||
|
||||
  为了让 dashboard 更有用,运行脚本建议输出结构化日志,例如 `[START]`、`[ENV]`、`[PROGRESS]`、`[METRIC]`、`[OUTPUT]`、`[DONE]`。Python 任务建议使用 `PYTHONUNBUFFERED=1 python -u ...`,这样日志能更快出现在页面里。
|
||||
|
||||
## 🛠️ Commands
|
||||
|
||||
| 命令 | 用途 |
|
||||
@@ -243,6 +258,8 @@ autodl-remote exec -- nvidia-smi
|
||||
| `autodl-remote job list` | 列出本地记录的远端长任务 |
|
||||
| `autodl-remote job status train` | 查看长任务 PID、日志、远端状态和退出码 |
|
||||
| `autodl-remote job tail train` | 查看命名长任务日志 |
|
||||
| `autodl-remote fleet status exp` | 查看多台远端设备状态 |
|
||||
| `autodl-remote dashboard --fleet exp --watch 5 --lines 120` | 打开只读 dashboard,持续展示远端状态和日志 |
|
||||
| `autodl-remote model-dir --mkdir` | 输出并创建项目模型目录 |
|
||||
| `autodl-remote shutdown` | 请求远端关机,并把 SSH 断开视为可能成功 |
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "autodl-remote",
|
||||
"version": "0.6.1",
|
||||
"description": "Bash-only SSH account, command, upload, download, job, and shutdown tool layer for AutoDL.",
|
||||
"version": "0.7.0",
|
||||
"description": "Bash-only SSH account, fleet, command, upload, download, job, dashboard, and shutdown tool layer for AutoDL.",
|
||||
"author": {
|
||||
"name": "haibarazz",
|
||||
"url": "https://github.com/haibarazz/AutoDL-Remote"
|
||||
@@ -21,7 +21,7 @@
|
||||
"interface": {
|
||||
"displayName": "AutoDL Remote",
|
||||
"shortDescription": "Control AutoDL over SSH from local Codex.",
|
||||
"longDescription": "Provides SSH account management, remote command execution, explicit file upload/download primitives, remote job tracking, script execution, and shutdown handling. Codex decides what to inspect, push, pull, and run.",
|
||||
"longDescription": "Provides SSH account and fleet management, remote command execution, explicit file upload/download primitives, remote job/run tracking, read-only dashboard generation, script execution, and shutdown handling. Codex decides what to inspect, push, pull, and run.",
|
||||
"developerName": "haibarazz",
|
||||
"category": "Developer Tools",
|
||||
"capabilities": [
|
||||
@@ -37,6 +37,7 @@
|
||||
"Upload this edited file to AutoDL and run training.",
|
||||
"Tail the latest AutoDL training log.",
|
||||
"Check the status of my detached AutoDL job.",
|
||||
"Show a read-only dashboard for my AutoDL devices and runs.",
|
||||
"Shut down the remote AutoDL machine when the run is finished."
|
||||
],
|
||||
"brandColor": "#2563EB"
|
||||
|
||||
@@ -37,6 +37,12 @@ Passwords are never written to project files.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Check the installed CLI version:
|
||||
|
||||
```bash
|
||||
autodl-remote --version
|
||||
```
|
||||
|
||||
Create or choose an account:
|
||||
|
||||
```bash
|
||||
@@ -64,9 +70,10 @@ autodl-remote model-dir
|
||||
autodl-remote tree . --depth 2
|
||||
autodl-remote exec -- pwd
|
||||
autodl-remote exec -- python train.py
|
||||
autodl-remote exec --detach --name train -- python train.py
|
||||
autodl-remote job status train
|
||||
autodl-remote job tail train
|
||||
autodl-remote exec --detach --name train --model baseline --stage training -- python train.py
|
||||
autodl-remote run status train
|
||||
autodl-remote run tail train
|
||||
autodl-remote dashboard
|
||||
```
|
||||
|
||||
Move files only when needed:
|
||||
@@ -92,6 +99,60 @@ Shutdown the remote machine after a run:
|
||||
autodl-remote shutdown
|
||||
```
|
||||
|
||||
## Fleet and Dashboard
|
||||
|
||||
The dashboard is read-only. It shows local run metadata, lightweight remote status from SSH polling, and the latest remote log lines for recorded runs. It does not stop jobs, delete files, upload, download, or shut down machines.
|
||||
|
||||

|
||||
|
||||
Single-machine dashboard for the current project binding:
|
||||
|
||||
```bash
|
||||
autodl-remote dashboard
|
||||
autodl-remote dashboard --open
|
||||
autodl-remote dashboard --open --watch 5 --lines 120
|
||||
```
|
||||
|
||||
Create a project-local fleet:
|
||||
|
||||
```bash
|
||||
autodl-remote fleet create rag-exp
|
||||
autodl-remote fleet add rag-exp nmb1 --account autodl-nmb1 --remote /root/autodl-tmp/LLM-RAG --tags a800,rag
|
||||
autodl-remote fleet add rag-exp cqa1 --account autodl-cqa1 --remote /root/autodl-tmp/LLM-RAG --tags 4090,rag
|
||||
autodl-remote fleet status rag-exp
|
||||
autodl-remote dashboard --fleet rag-exp --open
|
||||
autodl-remote dashboard --fleet rag-exp --open --watch 5 --lines 120
|
||||
```
|
||||
|
||||
Run metadata lets the dashboard show what Codex intended to run:
|
||||
|
||||
```bash
|
||||
autodl-remote exec --account autodl-nmb1 --remote /root/autodl-tmp/LLM-RAG \
|
||||
--detach --name melu-fold3 --model MeLU --stage training --tag fold=3 \
|
||||
--purpose "no-leak 5-fold experiment" -- python train.py model=melu fold=3
|
||||
|
||||
autodl-remote run list
|
||||
autodl-remote run note melu-fold3 --stage predicting --output outputs/melu_fold3.csv
|
||||
```
|
||||
|
||||
For long-running Python jobs, prefer unbuffered logs so the dashboard updates quickly:
|
||||
|
||||
```bash
|
||||
PYTHONUNBUFFERED=1 python -u train.py
|
||||
```
|
||||
|
||||
Useful log markers:
|
||||
|
||||
```text
|
||||
[START] model=... device=... cwd=...
|
||||
[ENV] python=... cuda=...
|
||||
[PROGRESS] epoch=... step=... loss=...
|
||||
[METRIC] val_auc=...
|
||||
[OUTPUT] path=...
|
||||
[DONE] exit_code=0
|
||||
[ERROR] ...
|
||||
```
|
||||
|
||||
## Accounts
|
||||
|
||||
List accounts:
|
||||
@@ -146,20 +207,30 @@ autodl-remote account password-delete <name>
|
||||
autodl-remote bind [--account name] --remote /remote/project/root
|
||||
autodl-remote doctor
|
||||
autodl-remote model-dir [--mkdir]
|
||||
autodl-remote fleet create <name>
|
||||
autodl-remote fleet add <fleet> <device> [--account name] [--remote path] [--tags tags]
|
||||
autodl-remote fleet list
|
||||
autodl-remote fleet status [fleet]
|
||||
autodl-remote dashboard [--fleet name] [--output path] [--open] [--watch seconds] [--lines 120]
|
||||
|
||||
autodl-remote exec [--detach] [--cwd path] [--name name] [--script local_script] [--stdin] -- <command>
|
||||
autodl-remote exec [--account name] [--remote path] [--detach] [--cwd path] [--name name] [--model name] [--tag k=v] [--stage stage] [--purpose text] [--script local_script] [--stdin] -- <command>
|
||||
autodl-remote put-run <local-path>... -- <command>
|
||||
autodl-remote put <local-path> [remote-path]
|
||||
autodl-remote get <remote-path> [local-path]
|
||||
autodl-remote sync-up <local-path> [remote-path]
|
||||
autodl-remote sync-down <remote-path> [local-path]
|
||||
autodl-remote tree [remote-path] [--depth 3] [--limit 500]
|
||||
autodl-remote ls [remote-path]
|
||||
autodl-remote cat [--lines 200] -- <remote-path>
|
||||
autodl-remote tail [--lines 120] [--follow] -- <remote-path>
|
||||
autodl-remote put [--account name] [--remote path] <local-path> [remote-path]
|
||||
autodl-remote get [--account name] [--remote path] <remote-path> [local-path]
|
||||
autodl-remote sync-up [--account name] [--remote path] <local-path> [remote-path]
|
||||
autodl-remote sync-down [--account name] [--remote path] <remote-path> [local-path]
|
||||
autodl-remote tree [--account name] [--remote path] [remote-path] [--depth 3] [--limit 500]
|
||||
autodl-remote ls [--account name] [--remote path] [remote-path]
|
||||
autodl-remote cat [--account name] [--remote path] [--lines 200] -- <remote-path>
|
||||
autodl-remote tail [--account name] [--remote path] [--lines 120] [--follow] -- <remote-path>
|
||||
autodl-remote job list
|
||||
autodl-remote job status <name>
|
||||
autodl-remote job tail [--lines 120] [--follow] <name>
|
||||
autodl-remote run submit [metadata/options] -- <command>
|
||||
autodl-remote run list
|
||||
autodl-remote run status <name>
|
||||
autodl-remote run tail [--lines 120] [--follow] <name>
|
||||
autodl-remote run note <name> [--model name] [--tag k=v] [--stage stage] [--purpose text] [--output path]
|
||||
autodl-remote shutdown [--wait seconds]
|
||||
autodl-remote shell
|
||||
autodl-remote history
|
||||
@@ -176,4 +247,7 @@ autodl-remote history
|
||||
- Keep large models, datasets, checkpoints, and training outputs on AutoDL unless the user explicitly pulls them.
|
||||
- Use `exec --script` or `exec --stdin` for multi-line shell/Python snippets.
|
||||
- Use `job status` and `job tail` for detached training jobs created with `exec --detach --name`.
|
||||
- Use `fleet` and `dashboard` for multi-device visibility; the dashboard is display-only.
|
||||
- Use `dashboard --watch <seconds> --lines <n>` when the user wants to watch remote logs without spending chat tokens.
|
||||
- Use run metadata flags (`--model`, `--tag`, `--stage`, `--purpose`) when Codex launches experiments so later dashboards are understandable.
|
||||
- Use `shutdown` instead of guessing provider-specific poweroff commands.
|
||||
@@ -2,6 +2,7 @@
|
||||
set -u
|
||||
|
||||
PROGRAM="autodl-remote"
|
||||
PROGRAM_VERSION="0.7.0"
|
||||
APP_DIR="$HOME/.autodl-remote"
|
||||
ACCOUNTS_DIR="$APP_DIR/accounts"
|
||||
DEFAULT_ACCOUNT_FILE="$APP_DIR/default-account"
|
||||
@@ -22,6 +23,10 @@ usage() {
|
||||
cat <<'EOF'
|
||||
usage: autodl-remote <command> [options]
|
||||
|
||||
general:
|
||||
--version
|
||||
version
|
||||
|
||||
account:
|
||||
account add <name> [--target root@host | --host host --user root] [--port 22] [--key path] [--auth prompt|keychain|ssh-key] [--default-remote path]
|
||||
account list
|
||||
@@ -35,21 +40,31 @@ project:
|
||||
bind [--account name] --remote /remote/project/root
|
||||
doctor
|
||||
model-dir [--mkdir]
|
||||
fleet create <name>
|
||||
fleet add <fleet> <device> [--account name] [--remote path] [--tags tags]
|
||||
fleet list
|
||||
fleet status [fleet]
|
||||
dashboard [--fleet name] [--output path] [--open] [--watch seconds] [--lines 120]
|
||||
|
||||
remote:
|
||||
exec [--detach] [--cwd path] [--name name] [--script local_script] [--stdin] [--allow-dangerous] -- <command>
|
||||
exec [--account name] [--remote path] [--detach] [--cwd path] [--name name] [--model name] [--tag k=v] [--stage stage] [--purpose text] [--script local_script] [--stdin] [--allow-dangerous] -- <command>
|
||||
put-run <local-path>... -- <command>
|
||||
put <local-path> [remote-path]
|
||||
get <remote-path> [local-path]
|
||||
sync-up <local-path> [remote-path]
|
||||
sync-down <remote-path> [local-path]
|
||||
tree [remote-path] [--depth 3] [--limit 500]
|
||||
ls [remote-path]
|
||||
cat [--lines 200] -- <remote-path>
|
||||
tail [--lines 120] [--follow] -- <remote-path>
|
||||
put [--account name] [--remote path] <local-path> [remote-path]
|
||||
get [--account name] [--remote path] <remote-path> [local-path]
|
||||
sync-up [--account name] [--remote path] <local-path> [remote-path]
|
||||
sync-down [--account name] [--remote path] <remote-path> [local-path]
|
||||
tree [--account name] [--remote path] [remote-path] [--depth 3] [--limit 500]
|
||||
ls [--account name] [--remote path] [remote-path]
|
||||
cat [--account name] [--remote path] [--lines 200] -- <remote-path>
|
||||
tail [--account name] [--remote path] [--lines 120] [--follow] -- <remote-path>
|
||||
job list
|
||||
job status <name>
|
||||
job tail [--lines 120] [--follow] <name>
|
||||
run submit [metadata/options] -- <command>
|
||||
run list
|
||||
run status <name>
|
||||
run tail [--lines 120] [--follow] <name>
|
||||
run note <name> [--model name] [--tag k=v] [--stage stage] [--purpose text] [--output path]
|
||||
shutdown [--wait seconds]
|
||||
shell
|
||||
history [--lines 20]
|
||||
@@ -57,6 +72,10 @@ remote:
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd_version() {
|
||||
printf '%s %s\n' "$PROGRAM" "$PROGRAM_VERSION"
|
||||
}
|
||||
|
||||
shell_quote() {
|
||||
printf '%q' "$1"
|
||||
}
|
||||
@@ -80,6 +99,12 @@ $1
|
||||
EOF
|
||||
}
|
||||
|
||||
html_escape() {
|
||||
sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g' <<EOF
|
||||
$1
|
||||
EOF
|
||||
}
|
||||
|
||||
valid_name() {
|
||||
case "$1" in
|
||||
""|*/*|*..*|*[\ \ ]*) return 1 ;;
|
||||
@@ -87,6 +112,15 @@ valid_name() {
|
||||
return 0
|
||||
}
|
||||
|
||||
append_csv() {
|
||||
local current="$1" value="$2"
|
||||
if [ -n "$current" ]; then
|
||||
printf '%s,%s\n' "$current" "$value"
|
||||
else
|
||||
printf '%s\n' "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_app_dirs() {
|
||||
mkdir -p "$ACCOUNTS_DIR" "$APP_DIR"
|
||||
}
|
||||
@@ -213,17 +247,24 @@ job_path() {
|
||||
}
|
||||
|
||||
write_job() {
|
||||
local name="$1" pid="$2" log="$3" status_path="$4" cwd="$5" command="$6" file
|
||||
local name="$1" pid="$2" log="$3" status_path="$4" cwd="$5" command="$6" model="${7:-}" tags="${8:-}" purpose="${9:-}" stage="${10:-}" outputs="${11:-}" file
|
||||
mkdir -p "$(jobs_dir)"
|
||||
file="$(job_path "$name")"
|
||||
cat > "$file" <<EOF
|
||||
NAME="$(config_quote "$name")"
|
||||
PID="$(config_quote "$pid")"
|
||||
ACCOUNT="$(config_quote "$ACCOUNT_NAME")"
|
||||
REMOTE_ROOT="$(config_quote "$REMOTE_ROOT")"
|
||||
LOG_PATH="$(config_quote "$log")"
|
||||
STATUS_PATH="$(config_quote "$status_path")"
|
||||
CWD="$(config_quote "$cwd")"
|
||||
STARTED_AT="$(date +%Y-%m-%dT%H:%M:%S%z)"
|
||||
COMMAND="$(config_quote "$command")"
|
||||
MODEL="$(config_quote "$model")"
|
||||
TAGS="$(config_quote "$tags")"
|
||||
PURPOSE="$(config_quote "$purpose")"
|
||||
STAGE="$(config_quote "$stage")"
|
||||
OUTPUTS="$(config_quote "$outputs")"
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -232,6 +273,110 @@ job_get() {
|
||||
config_get_from "$file" "$key"
|
||||
}
|
||||
|
||||
config_set_in_file() {
|
||||
local file="$1" key="$2" value="$3" quoted tmp
|
||||
quoted="$(config_quote "$value")"
|
||||
tmp="$file.tmp"
|
||||
awk -F= -v key="$key" -v quoted="$quoted" '
|
||||
BEGIN { done=0 }
|
||||
$1 == key {
|
||||
if (!done) {
|
||||
print key "=\"" quoted "\""
|
||||
done=1
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!done) print key "=\"" quoted "\""
|
||||
}
|
||||
' "$file" > "$tmp"
|
||||
mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
apply_context_override() {
|
||||
local account="${1:-}" remote="${2:-}"
|
||||
if [ -n "$account" ]; then
|
||||
load_account "$account"
|
||||
PROJECT_ACCOUNT="$ACCOUNT_NAME"
|
||||
if [ -z "$remote" ] && [ -n "$ACCOUNT_DEFAULT_REMOTE" ]; then
|
||||
remote="$ACCOUNT_DEFAULT_REMOTE"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$remote" ]; then
|
||||
REMOTE_ROOT="${remote%/}"
|
||||
validate_remote_root "$REMOTE_ROOT"
|
||||
PROJECT_MODEL_DIR="$(default_model_dir)"
|
||||
fi
|
||||
}
|
||||
|
||||
fleet_root() {
|
||||
printf '%s/%s/fleets\n' "$PROJECT_ROOT" "$HISTORY_DIR"
|
||||
}
|
||||
|
||||
fleet_dir() {
|
||||
local name="$1"
|
||||
valid_name "$name" || die "Invalid fleet name: $name"
|
||||
printf '%s/%s\n' "$(fleet_root)" "$name"
|
||||
}
|
||||
|
||||
fleet_device_path() {
|
||||
local fleet="$1" device="$2"
|
||||
valid_name "$device" || die "Invalid device name: $device"
|
||||
printf '%s/%s.conf\n' "$(fleet_dir "$fleet")" "$device"
|
||||
}
|
||||
|
||||
device_get() {
|
||||
local file="$1" key="$2"
|
||||
config_get_from "$file" "$key"
|
||||
}
|
||||
|
||||
apply_job_context() {
|
||||
local file="$1" account remote
|
||||
account="$(job_get "$file" ACCOUNT)"
|
||||
remote="$(job_get "$file" REMOTE_ROOT)"
|
||||
[ -n "$account" ] || account="${PROJECT_BOUND_ACCOUNT:-$PROJECT_ACCOUNT}"
|
||||
[ -n "$remote" ] || remote="${PROJECT_BOUND_REMOTE_ROOT:-$REMOTE_ROOT}"
|
||||
apply_context_override "$account" "$remote"
|
||||
}
|
||||
|
||||
job_remote_status_summary() {
|
||||
local file="$1" pid status_path output rc status alive
|
||||
apply_job_context "$file"
|
||||
pid="$(job_get "$file" PID)"
|
||||
status_path="$(job_get "$file" STATUS_PATH)"
|
||||
output="$(ssh_capture "if [ -f $(shell_quote "$status_path") ]; then cat $(shell_quote "$status_path"); elif kill -0 $(shell_quote "$pid") 2>/dev/null; then printf 'status=running\npid=%s\n' $(shell_quote "$pid"); else printf 'status=unknown\npid=%s\n' $(shell_quote "$pid"); fi; if kill -0 $(shell_quote "$pid") 2>/dev/null; then echo alive=yes; else echo alive=no; fi" 2>/dev/null)"
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
printf 'unreachable\n'
|
||||
return 0
|
||||
fi
|
||||
status="$(printf '%s\n' "$output" | awk -F= '$1=="status"{print $2; exit}')"
|
||||
alive="$(printf '%s\n' "$output" | awk -F= '$1=="alive"{print $2; exit}')"
|
||||
[ -n "$status" ] || status="unknown"
|
||||
if [ "$alive" = "yes" ] && [ "$status" = "unknown" ]; then
|
||||
status="running"
|
||||
fi
|
||||
printf '%s\n' "$status"
|
||||
}
|
||||
|
||||
remote_probe() {
|
||||
local output rc
|
||||
output="$(ssh_capture "root=$(shell_quote "$REMOTE_ROOT"); printf 'online=yes\n'; printf 'host='; hostname 2>/dev/null || echo unknown; printf 'remote_root=%s\n' \"\$root\"; printf 'disk='; df -h \"\$root\" 2>/dev/null | awk 'NR==2{print \$3 \"/\" \$2 \" \" \$5}' || echo unknown; if command -v nvidia-smi >/dev/null 2>&1; then printf 'gpu='; nvidia-smi --query-gpu=name,memory.used,memory.total,utilization.gpu --format=csv,noheader,nounits 2>/dev/null | head -n 1 | awk -F, '{gsub(/^ +| +$/, \"\", \$1); gsub(/^ +| +$/, \"\", \$2); gsub(/^ +| +$/, \"\", \$3); gsub(/^ +| +$/, \"\", \$4); print \$1 \" \" \$2 \"/\" \$3 \" MiB \" \$4 \"%\"}'; else echo 'gpu=unavailable'; fi; printf 'processes='; pgrep -af 'python|torchrun|accelerate' 2>/dev/null | head -n 3 | tr '\n' '|' | sed 's/|$//'; printf '\n'" 2>&1)"
|
||||
rc=$?
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
printf '%s\n' "$output"
|
||||
else
|
||||
printf 'online=no\n'
|
||||
printf 'error=%s\n' "$(printf '%s' "$output" | head -n 1)"
|
||||
fi
|
||||
}
|
||||
|
||||
kv_get() {
|
||||
local data="$1" key="$2"
|
||||
printf '%s\n' "$data" | awk -F= -v key="$key" '$1==key{print substr($0, length(key)+2); exit}'
|
||||
}
|
||||
|
||||
load_account() {
|
||||
local name="$1" path
|
||||
valid_name "$name" || die "Invalid account name: $name"
|
||||
@@ -293,12 +438,15 @@ load_project() {
|
||||
esac
|
||||
PROJECT_MODEL_DIR="${PROJECT_MODEL_DIR%/}"
|
||||
validate_remote_root "$PROJECT_MODEL_DIR"
|
||||
PROJECT_BOUND_ACCOUNT="$PROJECT_ACCOUNT"
|
||||
PROJECT_BOUND_REMOTE_ROOT="$REMOTE_ROOT"
|
||||
ensure_project_conventions
|
||||
}
|
||||
|
||||
ssh_common_args() {
|
||||
SSH_ARGS=(-p "$ACCOUNT_PORT" -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$APP_DIR/ssh-%C")
|
||||
mkdir -p "$APP_DIR"
|
||||
local control_path
|
||||
control_path="$(ssh_control_path)"
|
||||
SSH_ARGS=(-p "$ACCOUNT_PORT" -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$control_path")
|
||||
[ -n "$ACCOUNT_KEY" ] && SSH_ARGS+=(-i "$ACCOUNT_KEY")
|
||||
if [ "$ACCOUNT_AUTH" != "keychain" ] && [ "$ACCOUNT_AUTH" != "prompt" ]; then
|
||||
SSH_ARGS+=(-o BatchMode=yes)
|
||||
@@ -306,8 +454,9 @@ ssh_common_args() {
|
||||
}
|
||||
|
||||
scp_common_args() {
|
||||
SCP_ARGS=(-P "$ACCOUNT_PORT" -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$APP_DIR/ssh-%C")
|
||||
mkdir -p "$APP_DIR"
|
||||
local control_path
|
||||
control_path="$(ssh_control_path)"
|
||||
SCP_ARGS=(-P "$ACCOUNT_PORT" -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$control_path")
|
||||
[ -n "$ACCOUNT_KEY" ] && SCP_ARGS+=(-i "$ACCOUNT_KEY")
|
||||
if [ "$ACCOUNT_AUTH" != "keychain" ] && [ "$ACCOUNT_AUTH" != "prompt" ]; then
|
||||
SCP_ARGS+=(-o BatchMode=yes)
|
||||
@@ -315,8 +464,9 @@ scp_common_args() {
|
||||
}
|
||||
|
||||
rsync_ssh_command() {
|
||||
local cmd
|
||||
cmd="ssh -p $(shell_quote "$ACCOUNT_PORT") -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o ControlPath=$(shell_quote "$APP_DIR/ssh-%C")"
|
||||
local cmd control_path
|
||||
control_path="$(ssh_control_path)"
|
||||
cmd="ssh -p $(shell_quote "$ACCOUNT_PORT") -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ControlMaster=auto -o ControlPersist=600 -o ControlPath=$(shell_quote "$control_path")"
|
||||
[ -n "$ACCOUNT_KEY" ] && cmd="$cmd -i $(shell_quote "$ACCOUNT_KEY")"
|
||||
if [ "$ACCOUNT_AUTH" != "keychain" ] && [ "$ACCOUNT_AUTH" != "prompt" ]; then
|
||||
cmd="$cmd -o BatchMode=yes"
|
||||
@@ -324,6 +474,18 @@ rsync_ssh_command() {
|
||||
printf '%s\n' "$cmd"
|
||||
}
|
||||
|
||||
ssh_control_dir() {
|
||||
local base
|
||||
base="${AUTODL_REMOTE_CONTROL_DIR:-/tmp/autodl-remote-${UID:-user}}"
|
||||
mkdir -p "$base"
|
||||
chmod 700 "$base" 2>/dev/null || true
|
||||
printf '%s\n' "$base"
|
||||
}
|
||||
|
||||
ssh_control_path() {
|
||||
printf '%s/ssh-%%C\n' "$(ssh_control_dir)"
|
||||
}
|
||||
|
||||
keychain_password() {
|
||||
command -v security >/dev/null 2>&1 || die "macOS security command is not available."
|
||||
security find-generic-password -a "$ACCOUNT_NAME" -s "$KEYCHAIN_SERVICE" -w 2>/dev/null
|
||||
@@ -425,7 +587,7 @@ remote_has_rsync() {
|
||||
|
||||
ensure_remote_dir() {
|
||||
local dir="$1"
|
||||
ssh_capture "mkdir -p $(shell_quote "$dir")" >/dev/null
|
||||
ssh_capture "mkdir -p $(shell_quote "$dir")" </dev/null >/dev/null
|
||||
}
|
||||
|
||||
upload_local_file() {
|
||||
@@ -779,12 +941,20 @@ cmd_doctor() {
|
||||
|
||||
cmd_exec() {
|
||||
load_project
|
||||
local detach=false cwd="$REMOTE_ROOT" name="" allow_dangerous=false script="" stdin_mode=false remote_script=""
|
||||
local detach=false cwd="" cwd_arg="" name="" allow_dangerous=false script="" stdin_mode=false remote_script=""
|
||||
local account_override="" remote_override="" model="" tags="" purpose="" stage="" outputs=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--detach) detach=true; shift ;;
|
||||
--cwd) cwd="$(remote_join "$2")"; shift 2 ;;
|
||||
--cwd) cwd_arg="$2"; shift 2 ;;
|
||||
--name) name="$2"; shift 2 ;;
|
||||
--model) model="$2"; shift 2 ;;
|
||||
--tag) tags="$(append_csv "$tags" "$2")"; shift 2 ;;
|
||||
--purpose) purpose="$2"; shift 2 ;;
|
||||
--stage) stage="$2"; shift 2 ;;
|
||||
--output) outputs="$(append_csv "$outputs" "$2")"; shift 2 ;;
|
||||
--script) script="$2"; shift 2 ;;
|
||||
--stdin) stdin_mode=true; shift ;;
|
||||
--allow-dangerous) allow_dangerous=true; shift ;;
|
||||
@@ -793,6 +963,9 @@ cmd_exec() {
|
||||
esac
|
||||
done
|
||||
[ "$stdin_mode" != "true" ] || [ -z "$script" ] || die "Use either --script or --stdin, not both."
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
cwd="$REMOTE_ROOT"
|
||||
[ -z "$cwd_arg" ] || cwd="$(remote_join "$cwd_arg")"
|
||||
|
||||
local cmd="" tmp_name result pid status_path remote_cmd detach_rc
|
||||
if [ -n "$script" ]; then
|
||||
@@ -838,14 +1011,28 @@ cmd_exec() {
|
||||
status_path="$REMOTE_ROOT/.autodl-remote/jobs/$name.status"
|
||||
info "log=$log"
|
||||
if [ "$detach" = "true" ]; then
|
||||
remote_cmd="mkdir -p $(shell_quote "$(dirname "$log")") $(shell_quote "$(dirname "$status_path")") && ( started_at=\$(date +%Y-%m-%dT%H:%M:%S%z); pid=\$BASHPID; printf 'status=running\npid=%s\nstarted_at=%s\nlog=%s\ncwd=%s\n' \"\$pid\" \"\$started_at\" $(shell_quote "$log") $(shell_quote "$cwd") > $(shell_quote "$status_path"); cd $(shell_quote "$cwd") && bash -lc $(shell_quote "$cmd") > $(shell_quote "$log") 2>&1; rc=\$?; finished_at=\$(date +%Y-%m-%dT%H:%M:%S%z); printf 'status=exited\npid=%s\nstarted_at=%s\nfinished_at=%s\nexit_code=%s\nlog=%s\ncwd=%s\n' \"\$pid\" \"\$started_at\" \"\$finished_at\" \"\$rc\" $(shell_quote "$log") $(shell_quote "$cwd") > $(shell_quote "$status_path"); ) < /dev/null & printf 'pid=%s\nlog=%s\nstatus=%s\n' \"\$!\" $(shell_quote "$log") $(shell_quote "$status_path")"
|
||||
local runner
|
||||
runner="$REMOTE_ROOT/.autodl-remote/tmp/$name.runner.sh"
|
||||
upload_stdin_to_remote "$runner" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set +e
|
||||
started_at=\$(date +%Y-%m-%dT%H:%M:%S%z)
|
||||
pid=\$BASHPID
|
||||
printf 'status=running\npid=%s\nstarted_at=%s\nlog=%s\ncwd=%s\n' "\$pid" "\$started_at" $(shell_quote "$log") $(shell_quote "$cwd") > $(shell_quote "$status_path")
|
||||
cd $(shell_quote "$cwd") && bash -lc $(shell_quote "$cmd") > $(shell_quote "$log") 2>&1
|
||||
rc=\$?
|
||||
finished_at=\$(date +%Y-%m-%dT%H:%M:%S%z)
|
||||
printf 'status=exited\npid=%s\nstarted_at=%s\nfinished_at=%s\nexit_code=%s\nlog=%s\ncwd=%s\n' "\$pid" "\$started_at" "\$finished_at" "\$rc" $(shell_quote "$log") $(shell_quote "$cwd") > $(shell_quote "$status_path")
|
||||
exit "\$rc"
|
||||
EOF
|
||||
remote_cmd="mkdir -p $(shell_quote "$(dirname "$log")") $(shell_quote "$(dirname "$status_path")") && chmod +x $(shell_quote "$runner") && if command -v setsid >/dev/null 2>&1; then setsid bash $(shell_quote "$runner") < /dev/null > /dev/null 2>&1 & else nohup bash $(shell_quote "$runner") < /dev/null > /dev/null 2>&1 & fi; printf 'pid=%s\nlog=%s\nstatus=%s\n' \"\$!\" $(shell_quote "$log") $(shell_quote "$status_path")"
|
||||
result="$(ssh_capture "bash -lc $(shell_quote "$remote_cmd")" 2>&1)"
|
||||
detach_rc=$?
|
||||
printf '%s\n' "$result"
|
||||
[ "$detach_rc" -eq 0 ] || return "$detach_rc"
|
||||
pid="$(printf '%s\n' "$result" | awk -F= '$1=="pid"{print $2; exit}')"
|
||||
[ -n "$pid" ] || die "Remote detach did not return a pid."
|
||||
write_job "$name" "$pid" "$log" "$status_path" "$cwd" "$cmd"
|
||||
write_job "$name" "$pid" "$log" "$status_path" "$cwd" "$cmd" "$model" "$tags" "$purpose" "$stage" "$outputs"
|
||||
write_history exec-detach "$cmd log=$log"
|
||||
else
|
||||
local inner rc
|
||||
@@ -859,15 +1046,18 @@ cmd_exec() {
|
||||
|
||||
cmd_put() {
|
||||
load_project
|
||||
local dry_run=false delete=false
|
||||
local dry_run=false delete=false account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--dry-run) dry_run=true; shift ;;
|
||||
--delete) delete=true; shift ;;
|
||||
--) shift; break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
[ $# -ge 1 ] || die "put requires <local-path> [remote-path]"
|
||||
local local_arg="$1" remote_arg="${2:-}" local_path remote_path
|
||||
local_path="$(local_join "$local_arg")"
|
||||
@@ -900,14 +1090,17 @@ cmd_put() {
|
||||
|
||||
cmd_get() {
|
||||
load_project
|
||||
local dry_run=false
|
||||
local dry_run=false account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--dry-run) dry_run=true; shift ;;
|
||||
--) shift; break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
[ $# -ge 1 ] || die "get requires <remote-path> [local-path]"
|
||||
local remote_arg="$1" local_arg="${2:-}" remote_path local_path kind
|
||||
remote_path="$(remote_join "$remote_arg")"
|
||||
@@ -944,9 +1137,11 @@ cmd_get() {
|
||||
|
||||
cmd_tree() {
|
||||
load_project
|
||||
local path="." depth=3 limit=500
|
||||
local path="." depth=3 limit=500 account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--depth) depth="$2"; shift 2 ;;
|
||||
--limit) limit="$2"; shift 2 ;;
|
||||
--) shift; break ;;
|
||||
@@ -954,6 +1149,7 @@ cmd_tree() {
|
||||
*) path="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
local remote_path
|
||||
remote_path="$(remote_join "$path")"
|
||||
ssh_capture "cd $(shell_quote "$remote_path") && { find . -maxdepth $depth | sed 's#^./##' | sort | head -n $limit; true; }"
|
||||
@@ -961,36 +1157,52 @@ cmd_tree() {
|
||||
|
||||
cmd_ls() {
|
||||
load_project
|
||||
[ "${1:-}" = "--" ] && shift
|
||||
local account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--) shift; break ;;
|
||||
-*) die "unknown ls option: $1" ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
local path="${1:-.}"
|
||||
ssh_run "ls -lah $(shell_quote "$(remote_join "$path")")"
|
||||
}
|
||||
|
||||
cmd_cat() {
|
||||
load_project
|
||||
local lines=200
|
||||
local lines=200 account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--lines) lines="$2"; shift 2 ;;
|
||||
--) shift; break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
[ $# -gt 0 ] || die "cat requires a path"
|
||||
ssh_run "sed -n '1,${lines}p' $(shell_quote "$(remote_join "$1")")"
|
||||
}
|
||||
|
||||
cmd_tail() {
|
||||
load_project
|
||||
local lines=120 follow=false
|
||||
local lines=120 follow=false account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; shift 2 ;;
|
||||
--remote) remote_override="$2"; shift 2 ;;
|
||||
--lines) lines="$2"; shift 2 ;;
|
||||
--follow|-f) follow=true; shift ;;
|
||||
--) shift; break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
apply_context_override "$account_override" "$remote_override"
|
||||
[ $# -gt 0 ] || die "tail requires a path"
|
||||
local flag=""
|
||||
[ "$follow" = "true" ] && flag="-f"
|
||||
@@ -1031,8 +1243,11 @@ cmd_job_status() {
|
||||
cwd="$(job_get "$file" CWD)"
|
||||
command="$(job_get "$file" COMMAND)"
|
||||
[ -n "$pid" ] || die "Job has no recorded pid: $name"
|
||||
apply_job_context "$file"
|
||||
|
||||
info "name=$name"
|
||||
info "account=$ACCOUNT_NAME"
|
||||
info "remote_root=$REMOTE_ROOT"
|
||||
info "pid=$pid"
|
||||
info "cwd=$cwd"
|
||||
info "log=$log"
|
||||
@@ -1066,6 +1281,7 @@ cmd_job_tail() {
|
||||
file="$(job_path "$name")"
|
||||
[ -f "$file" ] || die "Job not found: $name"
|
||||
log="$(job_get "$file" LOG_PATH)"
|
||||
apply_job_context "$file"
|
||||
[ "$follow" = "true" ] && flag="-f"
|
||||
ssh_run "tail -n $lines $flag $(shell_quote "$log")"
|
||||
}
|
||||
@@ -1082,6 +1298,409 @@ cmd_job() {
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_run_list() {
|
||||
load_project
|
||||
local dir file name account remote model stage tags started status seen=false
|
||||
dir="$(jobs_dir)"
|
||||
mkdir -p "$dir"
|
||||
if ls "$dir"/*.conf >/dev/null 2>&1; then
|
||||
printf '%-24s %-14s %-12s %-12s %-16s %-20s %s\n' "run" "account" "status" "model" "stage" "started_at" "tags"
|
||||
for file in "$dir"/*.conf; do
|
||||
seen=true
|
||||
name="$(job_get "$file" NAME)"
|
||||
account="$(job_get "$file" ACCOUNT)"
|
||||
remote="$(job_get "$file" REMOTE_ROOT)"
|
||||
model="$(job_get "$file" MODEL)"
|
||||
stage="$(job_get "$file" STAGE)"
|
||||
tags="$(job_get "$file" TAGS)"
|
||||
started="$(job_get "$file" STARTED_AT)"
|
||||
[ -n "$account" ] || account="${PROJECT_BOUND_ACCOUNT:-$PROJECT_ACCOUNT}"
|
||||
[ -n "$remote" ] || remote="${PROJECT_BOUND_REMOTE_ROOT:-$REMOTE_ROOT}"
|
||||
status="$(job_remote_status_summary "$file")"
|
||||
printf '%-24s %-14s %-12s %-12s %-16s %-20s %s\n' "$name" "$account" "$status" "${model:--}" "${stage:--}" "$started" "${tags:--}"
|
||||
done
|
||||
fi
|
||||
[ "$seen" = "true" ] || info "No runs recorded."
|
||||
}
|
||||
|
||||
cmd_run_note() {
|
||||
load_project
|
||||
[ $# -gt 0 ] || die "run note requires a run name"
|
||||
local name file model="" tags="" purpose="" stage="" outputs=""
|
||||
name="$(sanitize_name "$1")"
|
||||
shift
|
||||
file="$(job_path "$name")"
|
||||
[ -f "$file" ] || die "Run not found: $name"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--model) model="$2"; shift 2 ;;
|
||||
--tag) tags="$(append_csv "$tags" "$2")"; shift 2 ;;
|
||||
--purpose) purpose="$2"; shift 2 ;;
|
||||
--stage) stage="$2"; shift 2 ;;
|
||||
--output) outputs="$(append_csv "$outputs" "$2")"; shift 2 ;;
|
||||
*) die "unknown run note option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[ -z "$model" ] || config_set_in_file "$file" MODEL "$model"
|
||||
if [ -n "$tags" ]; then
|
||||
local existing_tags
|
||||
existing_tags="$(job_get "$file" TAGS)"
|
||||
[ -n "$existing_tags" ] && tags="$(append_csv "$existing_tags" "$tags")"
|
||||
config_set_in_file "$file" TAGS "$tags"
|
||||
fi
|
||||
[ -z "$purpose" ] || config_set_in_file "$file" PURPOSE "$purpose"
|
||||
[ -z "$stage" ] || config_set_in_file "$file" STAGE "$stage"
|
||||
if [ -n "$outputs" ]; then
|
||||
local existing_outputs
|
||||
existing_outputs="$(job_get "$file" OUTPUTS)"
|
||||
[ -n "$existing_outputs" ] && outputs="$(append_csv "$existing_outputs" "$outputs")"
|
||||
config_set_in_file "$file" OUTPUTS "$outputs"
|
||||
fi
|
||||
info "Updated run: $name"
|
||||
}
|
||||
|
||||
cmd_run_submit() {
|
||||
local args=(--detach) uploads=() account_override="" remote_override=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account_override="$2"; args+=("$1" "$2"); shift 2 ;;
|
||||
--remote) remote_override="$2"; args+=("$1" "$2"); shift 2 ;;
|
||||
--sync-up) uploads+=("$2"); shift 2 ;;
|
||||
--) shift; break ;;
|
||||
*) args+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
[ $# -gt 0 ] || die "run submit requires a command after --"
|
||||
local path
|
||||
for path in "${uploads[@]}"; do
|
||||
local put_args=()
|
||||
[ -z "$account_override" ] || put_args+=(--account "$account_override")
|
||||
[ -z "$remote_override" ] || put_args+=(--remote "$remote_override")
|
||||
cmd_put "${put_args[@]}" "$path"
|
||||
done
|
||||
cmd_exec "${args[@]}" -- "$@"
|
||||
}
|
||||
|
||||
cmd_run() {
|
||||
[ $# -gt 0 ] || die "run requires a subcommand"
|
||||
local sub="$1"
|
||||
shift
|
||||
case "$sub" in
|
||||
submit) cmd_run_submit "$@" ;;
|
||||
list|ls) cmd_run_list "$@" ;;
|
||||
status) cmd_job_status "$@" ;;
|
||||
tail) cmd_job_tail "$@" ;;
|
||||
note) cmd_run_note "$@" ;;
|
||||
*) die "unknown run command: $sub" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_fleet_create() {
|
||||
load_project
|
||||
[ $# -gt 0 ] || die "fleet create requires a name"
|
||||
local name dir
|
||||
name="$1"
|
||||
valid_name "$name" || die "Invalid fleet name: $name"
|
||||
dir="$(fleet_dir "$name")"
|
||||
mkdir -p "$dir"
|
||||
info "Created fleet: $name"
|
||||
}
|
||||
|
||||
cmd_fleet_add() {
|
||||
load_project
|
||||
[ $# -ge 2 ] || die "fleet add requires <fleet> <device>"
|
||||
local fleet="$1" device="$2" account="$PROJECT_ACCOUNT" remote="$REMOTE_ROOT" tags="" file
|
||||
shift 2
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--account) account="$2"; shift 2 ;;
|
||||
--remote) remote="$2"; shift 2 ;;
|
||||
--tags) tags="$2"; shift 2 ;;
|
||||
*) die "unknown fleet add option: $1" ;;
|
||||
esac
|
||||
done
|
||||
load_account "$account"
|
||||
[ -n "$remote" ] || remote="$ACCOUNT_DEFAULT_REMOTE"
|
||||
[ -n "$remote" ] || die "fleet add requires --remote or an account default remote."
|
||||
remote="${remote%/}"
|
||||
validate_remote_root "$remote"
|
||||
mkdir -p "$(fleet_dir "$fleet")"
|
||||
file="$(fleet_device_path "$fleet" "$device")"
|
||||
cat > "$file" <<EOF
|
||||
FLEET="$(config_quote "$fleet")"
|
||||
NAME="$(config_quote "$device")"
|
||||
ACCOUNT="$(config_quote "$account")"
|
||||
REMOTE_ROOT="$(config_quote "$remote")"
|
||||
TAGS="$(config_quote "$tags")"
|
||||
ADDED_AT="$(date +%Y-%m-%dT%H:%M:%S%z)"
|
||||
EOF
|
||||
info "Added $device -> $account:$remote to fleet $fleet"
|
||||
}
|
||||
|
||||
cmd_fleet_list() {
|
||||
load_project
|
||||
local root fleet_dir file fleet device account remote tags seen=false
|
||||
root="$(fleet_root)"
|
||||
mkdir -p "$root"
|
||||
if [ -n "$(find "$root" -mindepth 2 -maxdepth 2 -name '*.conf' -print -quit 2>/dev/null)" ]; then
|
||||
printf '%-16s %-18s %-14s %-34s %s\n' "fleet" "device" "account" "remote_root" "tags"
|
||||
while IFS= read -r file; do
|
||||
[ -n "$file" ] || continue
|
||||
seen=true
|
||||
fleet="$(device_get "$file" FLEET)"
|
||||
device="$(device_get "$file" NAME)"
|
||||
account="$(device_get "$file" ACCOUNT)"
|
||||
remote="$(device_get "$file" REMOTE_ROOT)"
|
||||
tags="$(device_get "$file" TAGS)"
|
||||
printf '%-16s %-18s %-14s %-34s %s\n' "$fleet" "$device" "$account" "$remote" "${tags:--}"
|
||||
done <<EOF
|
||||
$(find "$root" -mindepth 2 -maxdepth 2 -name '*.conf' | sort)
|
||||
EOF
|
||||
fi
|
||||
[ "$seen" = "true" ] || info "No fleets recorded."
|
||||
}
|
||||
|
||||
latest_run_for_device() {
|
||||
local account="$1" remote="$2" dir file name latest_name="" latest_started="" started job_account job_remote
|
||||
dir="$(jobs_dir)"
|
||||
if ls "$dir"/*.conf >/dev/null 2>&1; then
|
||||
for file in "$dir"/*.conf; do
|
||||
job_account="$(job_get "$file" ACCOUNT)"
|
||||
job_remote="$(job_get "$file" REMOTE_ROOT)"
|
||||
[ -n "$job_account" ] || job_account="${PROJECT_BOUND_ACCOUNT:-$PROJECT_ACCOUNT}"
|
||||
[ -n "$job_remote" ] || job_remote="${PROJECT_BOUND_REMOTE_ROOT:-$REMOTE_ROOT}"
|
||||
[ "$job_account" = "$account" ] || continue
|
||||
[ "$job_remote" = "$remote" ] || continue
|
||||
started="$(job_get "$file" STARTED_AT)"
|
||||
name="$(job_get "$file" NAME)"
|
||||
if [ -z "$latest_started" ] || [ "$started" \> "$latest_started" ]; then
|
||||
latest_started="$started"
|
||||
latest_name="$name"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
printf '%s\n' "${latest_name:--}"
|
||||
}
|
||||
|
||||
print_device_status() {
|
||||
local device="$1" account="$2" remote="$3" tags="$4" probe online host gpu disk current
|
||||
load_account "$account"
|
||||
REMOTE_ROOT="${remote%/}"
|
||||
validate_remote_root "$REMOTE_ROOT"
|
||||
probe="$(remote_probe)"
|
||||
online="$(kv_get "$probe" online)"
|
||||
host="$(kv_get "$probe" host)"
|
||||
gpu="$(kv_get "$probe" gpu)"
|
||||
disk="$(kv_get "$probe" disk)"
|
||||
current="$(latest_run_for_device "$account" "$REMOTE_ROOT")"
|
||||
printf '%-18s %-8s %-14s %-28s %-22s %-18s %s\n' "$device" "${online:-no}" "$account" "${gpu:--}" "${disk:--}" "$current" "${tags:--}"
|
||||
[ "${online:-no}" = "yes" ] || printf ' error: %s\n' "$(kv_get "$probe" error)"
|
||||
[ -n "$host" ] && printf ' host: %s remote: %s\n' "$host" "$REMOTE_ROOT"
|
||||
}
|
||||
|
||||
cmd_fleet_status() {
|
||||
load_project
|
||||
local fleet="" file device account remote tags
|
||||
[ $# -gt 0 ] && fleet="$1"
|
||||
printf '%-18s %-8s %-14s %-28s %-22s %-18s %s\n' "device" "online" "account" "gpu" "disk" "current_run" "tags"
|
||||
if [ -z "$fleet" ]; then
|
||||
print_device_status "current" "$PROJECT_ACCOUNT" "$REMOTE_ROOT" "project"
|
||||
return 0
|
||||
fi
|
||||
[ -d "$(fleet_dir "$fleet")" ] || die "Fleet not found: $fleet"
|
||||
for file in "$(fleet_dir "$fleet")"/*.conf; do
|
||||
[ -f "$file" ] || continue
|
||||
device="$(device_get "$file" NAME)"
|
||||
account="$(device_get "$file" ACCOUNT)"
|
||||
remote="$(device_get "$file" REMOTE_ROOT)"
|
||||
tags="$(device_get "$file" TAGS)"
|
||||
print_device_status "$device" "$account" "$remote" "$tags"
|
||||
done
|
||||
}
|
||||
|
||||
cmd_fleet() {
|
||||
[ $# -gt 0 ] || die "fleet requires a subcommand"
|
||||
local sub="$1"
|
||||
shift
|
||||
case "$sub" in
|
||||
create) cmd_fleet_create "$@" ;;
|
||||
add) cmd_fleet_add "$@" ;;
|
||||
list|ls) cmd_fleet_list "$@" ;;
|
||||
status) cmd_fleet_status "$@" ;;
|
||||
*) die "unknown fleet command: $sub" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
dashboard_device_row() {
|
||||
local device="$1" account="$2" remote="$3" tags="$4" probe online gpu disk current esc
|
||||
load_account "$account"
|
||||
REMOTE_ROOT="${remote%/}"
|
||||
validate_remote_root "$REMOTE_ROOT"
|
||||
probe="$(remote_probe)"
|
||||
online="$(kv_get "$probe" online)"
|
||||
gpu="$(kv_get "$probe" gpu)"
|
||||
disk="$(kv_get "$probe" disk)"
|
||||
current="$(latest_run_for_device "$account" "$REMOTE_ROOT")"
|
||||
printf '<tr><td>%s</td><td class="%s">%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n' \
|
||||
"$(html_escape "$device")" \
|
||||
"$([ "${online:-no}" = "yes" ] && printf online || printf offline)" \
|
||||
"$(html_escape "${online:-no}")" \
|
||||
"$(html_escape "$account")" \
|
||||
"$(html_escape "${gpu:--}")" \
|
||||
"$(html_escape "${disk:--}")" \
|
||||
"$(html_escape "$current")" \
|
||||
"$(html_escape "${tags:--}")"
|
||||
}
|
||||
|
||||
remote_log_tail() {
|
||||
local account="$1" remote="$2" log="$3" lines="$4" output rc
|
||||
[ -n "$log" ] || { printf '[log missing]\n'; return 0; }
|
||||
load_account "$account"
|
||||
REMOTE_ROOT="${remote%/}"
|
||||
validate_remote_root "$REMOTE_ROOT"
|
||||
output="$(ssh_capture "if [ -f $(shell_quote "$log") ]; then tail -n $lines $(shell_quote "$log"); else printf '[log missing] %s\n' $(shell_quote "$log"); fi" 2>&1)"
|
||||
rc=$?
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
printf '%s\n' "$output"
|
||||
else
|
||||
printf '[log unavailable]\n%s\n' "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
dashboard_run_rows() {
|
||||
local scope_fleet="$1" lines="$2" dir file name account remote model stage tags started status log purpose include log_tail
|
||||
dir="$(jobs_dir)"
|
||||
if ls "$dir"/*.conf >/dev/null 2>&1; then
|
||||
for file in "$dir"/*.conf; do
|
||||
name="$(job_get "$file" NAME)"
|
||||
account="$(job_get "$file" ACCOUNT)"
|
||||
remote="$(job_get "$file" REMOTE_ROOT)"
|
||||
model="$(job_get "$file" MODEL)"
|
||||
stage="$(job_get "$file" STAGE)"
|
||||
tags="$(job_get "$file" TAGS)"
|
||||
started="$(job_get "$file" STARTED_AT)"
|
||||
log="$(job_get "$file" LOG_PATH)"
|
||||
purpose="$(job_get "$file" PURPOSE)"
|
||||
[ -n "$account" ] || account="${PROJECT_BOUND_ACCOUNT:-$PROJECT_ACCOUNT}"
|
||||
[ -n "$remote" ] || remote="${PROJECT_BOUND_REMOTE_ROOT:-$REMOTE_ROOT}"
|
||||
include=true
|
||||
if [ -z "$scope_fleet" ]; then
|
||||
if [ "$account" != "${PROJECT_BOUND_ACCOUNT:-$PROJECT_ACCOUNT}" ] || [ "$remote" != "${PROJECT_BOUND_REMOTE_ROOT:-$REMOTE_ROOT}" ]; then
|
||||
include=false
|
||||
fi
|
||||
else
|
||||
include=false
|
||||
local fleet_file fleet_account fleet_remote
|
||||
for fleet_file in "$(fleet_dir "$scope_fleet")"/*.conf; do
|
||||
[ -f "$fleet_file" ] || continue
|
||||
fleet_account="$(device_get "$fleet_file" ACCOUNT)"
|
||||
fleet_remote="$(device_get "$fleet_file" REMOTE_ROOT)"
|
||||
if [ "$fleet_account" = "$account" ] && [ "$fleet_remote" = "$remote" ]; then
|
||||
include=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
[ "$include" = "true" ] || continue
|
||||
status="$(job_remote_status_summary "$file")"
|
||||
printf '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td><code>%s</code><div class="muted">%s</div></td></tr>\n' \
|
||||
"$(html_escape "$name")" "$(html_escape "$account")" "$(html_escape "$status")" \
|
||||
"$(html_escape "${model:--}")" "$(html_escape "${stage:--}")" "$(html_escape "$started")" \
|
||||
"$(html_escape "${tags:--}")" "$(html_escape "$log")" "$(html_escape "$purpose")"
|
||||
if [ "$lines" -gt 0 ]; then
|
||||
log_tail="$(remote_log_tail "$account" "$remote" "$log" "$lines")"
|
||||
printf '<tr class="log-row"><td colspan="8"><div class="log-title">%s · last %s lines</div><pre class="log">%s</pre></td></tr>\n' \
|
||||
"$(html_escape "$name")" "$(html_escape "$lines")" "$(html_escape "$log_tail")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
render_dashboard() {
|
||||
local fleet="$1" output="$2" lines="$3" refresh_seconds="${4:-}" generated_at title
|
||||
mkdir -p "$(dirname "$output")"
|
||||
generated_at="$(date +%Y-%m-%dT%H:%M:%S%z)"
|
||||
if [ -n "$fleet" ]; then
|
||||
[ -d "$(fleet_dir "$fleet")" ] || die "Fleet not found: $fleet"
|
||||
title="Fleet $fleet"
|
||||
else
|
||||
title="Current Remote"
|
||||
fi
|
||||
{
|
||||
printf '<!doctype html><html><head><meta charset="utf-8"><title>AutoDL Remote Dashboard</title><style>'
|
||||
printf 'body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;margin:24px;background:#f6f7f9;color:#172033}h1{font-size:24px}table{border-collapse:collapse;width:100%%;background:#fff;margin:16px 0;border:1px solid #d9dee7}th,td{text-align:left;padding:9px 10px;border-bottom:1px solid #edf0f5;font-size:14px;vertical-align:top}th{background:#eef2f7}.online{color:#087f5b;font-weight:700}.offline{color:#c92a2a;font-weight:700}.muted{color:#667085;font-size:12px;margin-top:4px}code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.summary{color:#475467;margin-bottom:18px}.log-row td{background:#0f172a;color:#dbeafe;padding:0}.log-title{font-size:12px;color:#93c5fd;padding:10px 12px;border-bottom:1px solid #1e293b}.log{margin:0;padding:12px;white-space:pre-wrap;overflow:auto;max-height:360px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.45}.badge{display:inline-block;border:1px solid #d0d5dd;border-radius:999px;padding:2px 8px;font-size:12px;color:#475467;background:#fff}'
|
||||
printf '</style>'
|
||||
if [ -n "$refresh_seconds" ]; then
|
||||
printf '<meta http-equiv="refresh" content="%s">' "$(html_escape "$refresh_seconds")"
|
||||
fi
|
||||
printf '</head><body>'
|
||||
printf '<h1>AutoDL Remote Dashboard</h1><div class="summary">%s · generated %s · project %s' "$(html_escape "$title")" "$(html_escape "$generated_at")" "$(html_escape "$PROJECT_ROOT")"
|
||||
if [ -n "$refresh_seconds" ]; then
|
||||
printf ' · <span class="badge">refresh every %ss</span>' "$(html_escape "$refresh_seconds")"
|
||||
fi
|
||||
printf '</div>'
|
||||
printf '<h2>Devices</h2><table><thead><tr><th>Device</th><th>Online</th><th>Account</th><th>GPU</th><th>Disk</th><th>Current Run</th><th>Tags</th></tr></thead><tbody>'
|
||||
if [ -n "$fleet" ]; then
|
||||
local device_file device account remote tags
|
||||
for device_file in "$(fleet_dir "$fleet")"/*.conf; do
|
||||
[ -f "$device_file" ] || continue
|
||||
device="$(device_get "$device_file" NAME)"
|
||||
account="$(device_get "$device_file" ACCOUNT)"
|
||||
remote="$(device_get "$device_file" REMOTE_ROOT)"
|
||||
tags="$(device_get "$device_file" TAGS)"
|
||||
dashboard_device_row "$device" "$account" "$remote" "$tags"
|
||||
done
|
||||
else
|
||||
dashboard_device_row "current" "$PROJECT_ACCOUNT" "$REMOTE_ROOT" "project"
|
||||
fi
|
||||
printf '</tbody></table>'
|
||||
printf '<h2>Runs</h2><table><thead><tr><th>Run</th><th>Account</th><th>Status</th><th>Model</th><th>Stage</th><th>Started</th><th>Tags</th><th>Log / Notes</th></tr></thead><tbody>'
|
||||
dashboard_run_rows "$fleet" "$lines"
|
||||
printf '</tbody></table>'
|
||||
printf '</body></html>\n'
|
||||
} > "$output"
|
||||
}
|
||||
|
||||
cmd_dashboard() {
|
||||
load_project
|
||||
local fleet="" output="" open_file=false watch_seconds="" lines=120 opened=false
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--fleet) fleet="$2"; shift 2 ;;
|
||||
--output) output="$2"; shift 2 ;;
|
||||
--open) open_file=true; shift ;;
|
||||
--watch) watch_seconds="$2"; shift 2 ;;
|
||||
--lines) lines="$2"; shift 2 ;;
|
||||
*) die "unknown dashboard option: $1" ;;
|
||||
esac
|
||||
done
|
||||
case "$lines" in
|
||||
""|*[!0-9]*) die "--lines must be a non-negative integer" ;;
|
||||
esac
|
||||
if [ -n "$watch_seconds" ]; then
|
||||
case "$watch_seconds" in
|
||||
""|0|*[!0-9]*) die "--watch must be a positive integer number of seconds" ;;
|
||||
esac
|
||||
fi
|
||||
[ -z "$output" ] && output="$PROJECT_ROOT/$HISTORY_DIR/dashboard.html"
|
||||
if [ -n "$watch_seconds" ]; then
|
||||
while :; do
|
||||
render_dashboard "$fleet" "$output" "$lines" "$watch_seconds"
|
||||
info "Dashboard written: $output"
|
||||
if [ "$open_file" = "true" ] && [ "$opened" = "false" ]; then
|
||||
command -v open >/dev/null 2>&1 && open "$output" || info "open command not available."
|
||||
opened=true
|
||||
fi
|
||||
sleep "$watch_seconds"
|
||||
done
|
||||
fi
|
||||
render_dashboard "$fleet" "$output" "$lines"
|
||||
info "Dashboard written: $output"
|
||||
if [ "$open_file" = "true" ]; then
|
||||
command -v open >/dev/null 2>&1 && open "$output" || info "open command not available."
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_shutdown() {
|
||||
load_project
|
||||
local wait_seconds=8 output rc
|
||||
@@ -1176,10 +1795,13 @@ main() {
|
||||
shift
|
||||
case "$cmd" in
|
||||
-h|--help|help) usage ;;
|
||||
-V|--version|version) cmd_version ;;
|
||||
account) cmd_account "$@" ;;
|
||||
bind) cmd_bind "$@" ;;
|
||||
doctor) cmd_doctor "$@" ;;
|
||||
model-dir) cmd_model_dir "$@" ;;
|
||||
fleet) cmd_fleet "$@" ;;
|
||||
dashboard) cmd_dashboard "$@" ;;
|
||||
exec) cmd_exec "$@" ;;
|
||||
put-run) cmd_put_run "$@" ;;
|
||||
put|sync-up) cmd_put "$@" ;;
|
||||
@@ -1189,6 +1811,7 @@ main() {
|
||||
cat) cmd_cat "$@" ;;
|
||||
tail) cmd_tail "$@" ;;
|
||||
job) cmd_job "$@" ;;
|
||||
run) cmd_run "$@" ;;
|
||||
shutdown) cmd_shutdown "$@" ;;
|
||||
shell) cmd_shell "$@" ;;
|
||||
history) cmd_history "$@" ;;
|
||||
|
||||
@@ -10,16 +10,24 @@ Use this skill when the user wants Codex to work locally while controlling AutoD
|
||||
## Operating Model
|
||||
|
||||
- The plugin is a thin SSH tool layer.
|
||||
- For AutoDL or remote SSH work, first verify the tool with `autodl-remote --version`, then use `autodl-remote` as the default gateway for remote commands and file transfer.
|
||||
- Do not hand-write raw `ssh`, `expect`, `scp`, or `rsync` workflows for AutoDL work unless `autodl-remote` is unavailable, the command fails after a reasonable retry, or the user explicitly asks to bypass the plugin.
|
||||
- If bypassing `autodl-remote`, state the concrete reason before doing so and avoid printing passwords or embedding credentials in shell history/logs.
|
||||
- For complex remote checks or multi-line scripts, use `autodl-remote exec --script` or `autodl-remote exec --stdin -- bash`; do not pack nested Python heredocs or long quoted scripts into a raw SSH command.
|
||||
- Do not decide that local or remote is the source of truth globally.
|
||||
- Do not use `local-main`, `remote-main`, manifests, sparse-cache modes, or automatic code ownership rules.
|
||||
- Codex decides the smallest useful action each time: inspect, upload, download, run, or tail logs.
|
||||
- Do not download LLM weights, checkpoints, datasets, or large outputs unless the user explicitly asks.
|
||||
- Prefer reading remote files/logs in place with `cat`, `tail`, `ls`, and `tree`.
|
||||
- Use detached job commands for long training runs instead of manually guessing process state.
|
||||
- Use run metadata (`--model`, `--tag`, `--stage`, `--purpose`) when launching experiments so `run list` and the dashboard explain what is running.
|
||||
- Use `fleet` for multi-device projects and `dashboard` for display-only visibility. The dashboard must not be treated as an operation surface.
|
||||
- When the user wants to watch progress, run `autodl-remote dashboard --watch <seconds> --lines <n> --open` instead of copying logs into chat.
|
||||
- Use `shutdown` when the user asks to stop the remote machine; treat SSH disconnect during shutdown as likely success.
|
||||
- Before using AutoDL Remote in a project, read `.autodl-remote/CONVENTIONS.md` if it exists. Treat it as the project-specific source for runtime, paths, sync rules, and notes.
|
||||
- When the user and Codex discuss or decide any project-specific remote configuration, runtime environment, path convention, model/data/output location, sync preference, or safety rule, update `.autodl-remote/CONVENTIONS.md` so future sessions can reuse it.
|
||||
- Keep `.autodl-remote/CONVENTIONS.md` concise and project-specific; do not turn it into a generic manual.
|
||||
- When writing remote run scripts, emit clear stdout logs because dashboard content comes from the remote log, not from Codex narration. Prefer markers such as `[START]`, `[ENV]`, `[PROGRESS]`, `[METRIC]`, `[OUTPUT]`, `[DONE]`, and `[ERROR]`. For Python, prefer `PYTHONUNBUFFERED=1 python -u ...`.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -57,12 +65,21 @@ autodl-remote sync-down logs/train.log ./logs/train.log
|
||||
|
||||
autodl-remote exec -- pwd
|
||||
autodl-remote exec -- python train.py
|
||||
autodl-remote exec --detach --name train -- python train.py
|
||||
autodl-remote exec --detach --name train --model baseline --stage training -- python train.py
|
||||
autodl-remote exec --script scripts/remote_check.sh
|
||||
cat scripts/remote_check.sh | autodl-remote exec --stdin -- bash
|
||||
autodl-remote job list
|
||||
autodl-remote job status train
|
||||
autodl-remote job tail train
|
||||
autodl-remote run list
|
||||
autodl-remote run note train --stage evaluating
|
||||
autodl-remote fleet create rag-exp
|
||||
autodl-remote fleet add rag-exp nmb1 --account autodl-nmb1 --remote /root/autodl-tmp/LLM-RAG --tags a800,rag
|
||||
autodl-remote fleet status rag-exp
|
||||
autodl-remote tree --account autodl-nmb1 --remote /root/autodl-tmp/LLM-RAG . --depth 2
|
||||
autodl-remote dashboard
|
||||
autodl-remote dashboard --fleet rag-exp --open
|
||||
autodl-remote dashboard --fleet rag-exp --open --watch 5 --lines 120
|
||||
autodl-remote shutdown
|
||||
```
|
||||
|
||||
@@ -74,6 +91,11 @@ autodl-remote shutdown
|
||||
- If both local and remote have similar files, do not assume one should overwrite the other. Compare or inspect first, then choose `put` or `get`.
|
||||
- Use `exec --detach` for long training jobs and then `tail` the log path.
|
||||
- Prefer `job status <name>` and `job tail <name>` for detached jobs created with `--name`.
|
||||
- Prefer `run list`, `run status`, `run tail`, and `run note` when the user is thinking in terms of experiments/models rather than raw jobs.
|
||||
- For multiple machines, use `fleet add/list/status` and `dashboard --fleet <name>` instead of manually tracking devices in chat.
|
||||
- When inspecting a non-default fleet device, pass `--account` and `--remote` to `tree`, `ls`, `cat`, `tail`, `put`, `get`, and `exec` instead of rebinding the project.
|
||||
- The dashboard is read-only and can be used for one device or many devices. Do not add operational assumptions just because the dashboard exists.
|
||||
- Dashboard logs are pulled from remote run log files. Add useful stdout logging to scripts instead of expecting Codex to summarize progress in chat.
|
||||
- Prefer `exec --script` or `exec --stdin` for complex multi-line commands, Python heredocs, env vars, JSON, awk, or sed.
|
||||
- Use `put-run` when the workflow is "upload these local paths, then immediately run this remote command".
|
||||
- `model-dir` prints the project model directory convention. The first project command creates `.autodl-remote/CONVENTIONS.md` locally for concise project notes.
|
||||
|
||||
Reference in new issue
Block a user