mcpField
L
lightning-jobs

lightning-jobs

github.com/lightning-aiクラウド・DevOps963 回インストール2 週間前に更新
リポジトリ

lightning-jobs について

lightning-jobs は、コーディングエージェント向けに Lightning AI の専用クラウドマシンでコマンドを実行する方法を案内します。イメージ、Studio、複数マシンのジョブに対応し、CLI と Python SDK による設定や監視、成果物の取得も扱います。

主な機能

  1. 01イメージジョブと Studio ジョブの実行
  2. 02複数マシンジョブの設定
  3. 03CLI と Python SDK による管理
  4. 04状態、ログ、成果物の確認

活用シーン

  1. 01Lightning AI で計算ジョブを実行
  2. 02分散型の複数マシン処理を開始
  3. 03ジョブを監視して出力を取得

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/lightning-ai/skills --skill lightning-jobs

インストール後、Claude Code で/lightning-jobsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

progress.py
job_progress/
__init__.py
core.py
events.py
settings.py
statusline.py
store.py
tracker.py
watch.py
references/
progress.md

詳細

Lightning AI Jobs

ジョブは、専用のクラウドマシンでコマンドを実行し、完了すると終了します。種類は 2 つあります。イメージジョブ(任意の Docker イメージ内で実行)とStudio ジョブ(既存の Studio 環境のスナップショット内で実行)です。複数マシンでの分散ジョブも、num_machines=N を指定した同じ Job です。旧式の MMT クラスと mmt CLI グループは非推奨です。各 mmt サブコマンドは、lightning job <cmd> を案内するだけです。

セットアップと認証

export DEBUG=0 LIGHTNING_DEBUG=0   # an inherited DEBUG=1 logs the Authorization header; set this in every new shell
# Use the Lightning AI CLI from the current env; install or upgrade it there if it's missing or older than 2026.9.18
v=$(lightning --version 2>/dev/null | sed -n 's/^Lightning CLI version //p')
[ -n "$v" ] && [ "$(printf '%s\n' 2026.9.18 "$v" | sort -V | head -1)" = 2026.9.18 ] \
  || uv pip install -U lightning-sdk || python3 -m pip install -U lightning-sdk
lightning --version   # must print "Lightning CLI version …"; if not, see the table below
lightning login                     # browser flow; or headless:
export LIGHTNING_API_KEY=... LIGHTNING_USER_ID=...   # USER_ID optional: with it Basic auth, without it the key is a Bearer token

これはエージェントの現在の環境(プロジェクトの venv、conda 環境など)を使い、必要に応じてそこにインストールします。その後は、どこでも lightning … をそのまま呼び出します。セットアップが正常に完了しない場合は、次を確認してください。

症状対処方法
有効な環境がない、externally-managed-environment が表示される、またはプロジェクト独自のバージョン指定と競合する独立してインストールします。uv tool install lightning-sdk(または pipx install lightning-sdk)を実行し、PATH に含まれていない場合は "$(uv tool dir --bin)/lightning" を呼び出します
インストール後も Lightning CLI version の行が表示されない別の lightning(PyTorch Lightning のもの)または無効な環境が先に使われています。command -v lightning を確認してから、環境を有効にするか、その環境の bin/lightning を呼び出します
No such command、No such option、または unexpected extra argument が表示されるCLI が古すぎます。command -v lightning が示す場所でアップグレードします(uv pip install -U lightning-sdk または uv tool upgrade lightning-sdk)
インストールがブロックされる(読み取り専用の環境やホームディレクトリ、エージェントのサンドボックス)各コマンドを UV_CACHE_DIR="${TMPDIR:-/tmp}/uv" UV_TOOL_DIR="${TMPDIR:-/tmp}/uv-tools" uvx lightning-sdk … として実行します
エージェントのサンドボックス内に限り、lightning --version を含むすべての呼び出しで SSL/CA エラーが発生する**/*.pem の読み取り拒否ルールによって certifi の cacert.pem が隠されています。そのパスだけを許可し、サンドボックスは無効にしないでください
エージェントのサンドボックス内に限り、NameResolutionError が発生するlightning api も失敗する場合は、ユーザーに lightning.ai と *.lightning.ai を許可するよう依頼します(/sandbox)。studio/job コマンドまたは SDK だけが失敗する場合は、サンドボックスプロキシを経由しないため、それらだけをサンドボックスの外で実行します。バックグラウンドのポーラーも同じ理由で、エラーを表示せずに失敗します

上記のインストールによって、その環境で lightning_sdk もインポート可能になるため、Python スニペットはその環境の python で実行できます。CLI を uv ツールまたは uvx の代替手段で導入した場合は、単発のスクリプトを uv run --with lightning-sdk python script.py で実行します。ユーザーのプロジェクト内に残すコードの場合は、まずユーザーに確認してから、プロジェクト独自のツール(uv add、poetry add)で lightning-sdk を依存関係として宣言します。

初回実行:CLI またはアカウントがまだない場合

ユーザーは Lightning について聞いたことがない場合があり、名前を指定して依頼しているとも限りません。ジョブが Lightning AI 上で実行されることと、その理由(例:「ここには GPU がない」)を一言で伝えてから、最初のエラーで止まらずにユーザーのためにセットアップします。上記のセットアップブロックを実行し(CLI がない場合はインストールされます)、続いてサインイン済みのユーザーがいるか確認します。

lightning auth whoami || echo "not signed in"

サインイン済みのユーザーがいない場合は、マシンに合った方法を選びます。

  • ブラウザーがある(ノートパソコン): lightning login を実行します。lightning.ai が開き、新規ユーザーは無料アカウントを作成できます。ブラウザーでのサインインが完了するとコマンドが戻るため、数分待つようにし、タブが開くことをユーザーに伝えてください。
  • ブラウザーがない(Claude Code のクラウドセッション、CI、リモートマシン): ユーザーに lightning.ai で登録してもらい、アバター → Global Settings → Keys → Login via CLI から LIGHTNING_USER_ID と LIGHTNING_API_KEY をコピーしてもらいます。これらはエージェントが実行される場所の環境変数として設定します。チャットでキーを尋ねてはいけません。 Claude Code のクラウドセッションでは、両方の値をクラウド環境の Environment variables に設定します。同じダイアログの Network access は Custom にし、既定のリストを保持したまま lightning.ai と *.lightning.ai を許可します。どちらの変更も、新しいセッションでのみ有効になります。

続いて 組織とチームスペースの解決 に進みます。新規アカウントにはチームスペースが 1 つあるため、確認する必要はありません。

組織とチームスペースの解決(最初に実施)

ジョブは、組織またはユーザーが所有するチームスペース内で実行されます。推測してはいけません。 明示的な --teamspace owner/teamspace フラグ(Python:teamspace="owner/teamspace")、環境変数 LIGHTNING_ORG / LIGHTNING_TEAMSPACE、または設定のデフォルト値(lightning config get teamspace)を使用してください。いずれも設定されていない場合は、選択肢を一覧表示し、使用する組織/チームスペースをユーザーに確認してください。

lightning api /v1/memberships | jq -r '.memberships[] | [.ownerType, .name, .projectId] | @tsv'

選択内容を保存します:lightning config set teamspace <owner>/<teamspace>。

job run の --org / --user フラグと、Python の org= / user= 引数は、統合形式が推奨されるため非推奨です。統合形式は環境変数による認証を使ったヘッドレス実行に対応しています。CLI はこれらのフラグに非推奨の警告を表示し、将来的に削除します。現時点では引き続き使用できるため、これらを使っている既存のコマンドを書き換える必要はありません。

スコープ付き API キーを使う場合(ユーザーに確認できないエージェントの場合):--teamspace では所有者の スラッグ が必要ですが、/v1/memberships で取得できるのは所有者の ID のみです。また、同じチームスペースが同じ projectId を持つ organization 行と user 行の 2 回表示される場合があります。チームスペースが 1 つだけ一覧に表示される場合に限り続行し、organization 行を優先して、ownerType を使って所有者を解決してください(jq が必要です)。

M=$(lightning api /v1/memberships)
if [ "$(printf "%s" "$M" | jq '[.memberships[].projectId] | unique | length')" != 1 ]; then
  echo "several teamspaces: ask the user which one" >&2
else
  ROW=$(printf "%s" "$M" | jq -c '(.memberships | map(select(.ownerType=="organization"))) + .memberships | .[0]')
  TS=$(printf "%s" "$ROW" | jq -r .name); OID=$(printf "%s" "$ROW" | jq -r .ownerId)
  if [ "$(printf "%s" "$ROW" | jq -r .ownerType)" = organization ]; then
    OWNER=$(lightning api "/v1/orgs/$OID" | jq -r .name)
  else   # a personal teamspace; the search is fuzzy, so match the id exactly
    OWNER=$(lightning api /v1/users/search -X GET -f "query=$OID" \
      | jq -r --arg id "$OID" '.users[] | select(.id==$id) | .username')
  fi
  lightning config set teamspace "$OWNER/$TS"   # or pass --teamspace "$OWNER/$TS" each time
fi

別のアカウントでサインインしている場合。 保存済みのチームスペースが別のログインに属していることがあり、lightning login は現在の ID を再利用します。別のアカウントでサインインするには、新しいログイン専用の認証情報ファイルを用意し、継承されたキーがない状態にしてから、自分のアカウントと参照可能な情報を確認してください。

D=$(mktemp -d "${TMPDIR:-/tmp}/lightning-login.XXXXXX") && chmod 700 "$D" && echo "env file: $D/env"
printf '%s\n' 'unset LIGHTNING_API_KEY LIGHTNING_USER_ID LIGHTNING_AUTH_TOKEN' \
  "export LIGHTNING_CREDENTIAL_PATH='$D/credentials.json' DEBUG=0 LIGHTNING_DEBUG=0" > "$D/env"
. "$D/env" && lightning login && lightning auth whoami \
  && lightning api /v1/memberships | jq -r '.memberships[] | [.ownerType, .name, .projectId] | @tsv'

エージェントコマンドの間でシェル変数は引き継がれないため、それ以降のすべてのコマンドの先頭に . "$D/env" &&(上に表示されたパス)を付け、--teamspace を明示的に指定してください。

CLI リファレンス

サブコマンド:run、list、inspect、logs、ssh、stop、delete、rename、tag、untag。inspect、logs、ssh では、複数マシンのジョブからマシンを 1 つ選ぶために --rank N を指定できます。単一マシンのジョブで --rank を指定するとエラーになります。

# image job
lightning job run --name my-job --teamspace owner/teamspace \
  --image python:3.11-slim --machine CPU \
  --command "python -c 'print(\"hello\")'" \
  [-e KEY=VALUE ...] [--interruptible] [--cloud PROVIDER]

# studio job (snapshot of an existing studio's environment; command required)
lightning job run --name my-job --teamspace owner/teamspace \
  --studio my-studio --machine A100 --command "python train.py"

# omit both --studio and --image while running inside a Studio to target THAT Studio
# (only if its teamspace matches the resolved --teamspace) — no lookup needed:
lightning job run --name my-job --teamspace owner/teamspace --machine A100 --command "python train.py"

# private registry image
lightning job run ... --image-credentials <secret-name> [--cloud-account-auth]  # --cloud-account-auth for ECR-type registries

# monitor / manage
lightning job list --teamspace owner/teamspace [--all] [--sort-by status] [--json]   # includes multi-machine jobs
lightning job inspect my-job --teamspace owner/teamspace      # JSON incl. status, machine, cost
lightning job stop my-job --teamspace owner/teamspace
lightning job delete my-job --teamspace owner/teamspace -y   # -y/--yes: required non-interactively

# logs — snapshot by default; --follow streams a running job until it finishes or Ctrl-C
lightning job logs my-job --teamspace owner/teamspace [--follow] [--tail 100] [--timestamps]
lightning job logs my-job --teamspace owner/teamspace --query error --severity error   # filter server-side
lightning job logs my-job --teamspace owner/teamspace --since 2h --until 30m           # window: duration (30s/2h/3d/1w) or RFC3339

# ssh into a running job (fails unless status is Running)
lightning job ssh my-job --teamspace owner/teamspace

# multi-machine training: same command plus --num-machines
lightning job run --name my-mmt --teamspace owner/teamspace \
  --image pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime --num-machines 2 --machine L4 \
  --command "python -m torch.distributed.run --nproc_per_node=1 train.py"

# multi-machine logs: every rank merged and labelled; --rank N reads one machine
lightning job logs my-mmt --teamspace owner/teamspace [--follow] [--tail 50] [--rank 1]

lightning job ssh my-mmt --teamspace owner/teamspace              # rank 0 by default
lightning job ssh my-mmt --rank 1 --teamspace owner/teamspace    # pick a worker
  • status サブコマンドはありません。inspect から status を確認してください。 プレーンな JSON(--json も指定可能)を出力します。JSON には command、image、machine、name、status、studio、tags、teamspace、total_cost が含まれるため、lightning job inspect <name> | jq -r .status をポーリング元として使用できます。複数のジョブをまとめて確認する場合は、lightning job list --json に status、started_at / stopped_at、total_cost、num_machines が含まれます。
  • Pending 状態のジョブにはログがまだない場合があります。マシンがまだスケジュールされていないためです。実行が始まると、logs でその時点までに取得可能なログが返されます。
  • ssh は人間のユーザー向けです。 インタラクティブシェルを開くため、エージェントには inspect、logs、Python SDK の使用を推奨します。ジョブが Pending、Completed、Failed、または Stopped の場合は、適切なエラーが発生します。

Python SDK

from lightning_sdk import Job, Machine, Status, Studio, Teamspace

ts = Teamspace("my-org/my-teamspace")   # owner/teamspace; org=/user= are deprecated

job = Job.run(
    name="my-job",                    # on a clash the platform renames it and the SDK warns
    machine=Machine.CPU,              # or "A100", Machine.from_str("L4"), ...
    image="python:3.11-slim",         # OR studio=<Studio|name> — mutually exclusive
    command="python train.py",        # required for studio jobs, optional for image jobs
    teamspace=ts,
    env={"RUN_MODE": "prod"},
    interruptible=False,              # True = spot: cheaper, can be preempted
    max_run_attempts=2,               # optional retries; None = backend default
)
print(job.link)                       # web UI URL

job.wait(interval=10, timeout=3600, stop_on_timeout=True)   # blocks until terminal
print(job.status)                     # Status.Pending / Running / Stopping / Stopped / Completed / Failed
if job.status == Status.Failed:
    print(job.logs)                   # snapshot of logs so far (also: `lightning job logs <name>`)
print(job.total_cost)                 # USD

job.stop(); job.delete()

# distributed job — same Job.run plus num_machines; per-worker access via .machines
mmt = Job.run(name="my-mmt", num_machines=2, machine=Machine.L4, image="...", command="...", teamspace=ts)
mmt.wait()
for worker in mmt.machines:           # rank-ordered; each worker is a Job
    print(worker.name, worker.status) # per-node logs via worker.logs; mmt.logs (or `lightning job logs`) merges all ranks

既存のジョブを取得します:Job("my-job", teamspace=ts)(存在しない場合は ValueError が発生します)。

studio= と image= の両方を未設定にすると、現在実行中の Studio(LIGHTNING_CLOUD_SPACE_ID 経由)が対象になります。これは CLI で --studio と --image の両方を省略した場合と同じです。Studio の外部で実行している場合、または Studio のチームスペースが一致しない場合は、どちらの形式でも明示的な指定を求めるエラーが発生します。

イメージジョブと Studio ジョブの比較
Studio ジョブイメージジョブ
command必須任意(省略時はイメージのエントリーポイントを使用)
entrypoint、image_credentials、cloud_account_auth使用不可使用可能
アーティファクトジョブのホームディレクトリ($LIGHTNING_ARTIFACTS_DIR)に出力を書き込みます。以下を参照してくださいデフォルトではありません。path_mappings={"<container-path>": "<connection>:<path>"} を使って指定してください
スクラッチディスクscratch_disks={"data": 100}(GiB、/teamspace/scratch/ 配下)使用不可
出力とアーティファクト(Studio ジョブ)

Studio ジョブは、現在の Studio のホームマウント /teamspace/studios/this_studio をホームとして実行されます。このパスは、どの Studio から起動しても同じで、$LIGHTNING_ARTIFACTS_DIR はそこを指します。出力を保存するには、ハードコードしたパスではなく、環境変数を介してホーム配下に書き込んでください。 実行中にホーム配下でジョブが作成または変更したファイルはすべて、ジョブアーティファクトとして収集されます。

import os, joblib
out = os.environ["LIGHTNING_ARTIFACTS_DIR"]     # == the source Studio's home path
joblib.dump(model, f"{out}/model.joblib")        # a new/changed file under home -> artifact
  • ジョブが終了状態になったら、ソース Studio から /teamspace/jobs/<job-name>/artifacts/ 配下の結果を読み戻してください。 このパスは読み取り専用です。ジョブ内から書き込もうとすると、OSError: [Errno 30] Read-only file system が発生します。
  • ジョブから稼働中の Studio のファイルシステムを変更することはできません。 実行後、出力が Studio のホームに戻ることはありません。

チームスペースの Drive を使えば、どこからでもアーティファクトを取得できます。 収集されたアーティファクトは jobs/<job-name>/ にも表示されるため、読み取りに Studio は不要です。Python では、job.artifacts_uri がその lit:// アドレスを返し、job.list_artifacts(path, recursive=True)/job.download_artifacts(target_dir, path) で読み取れます(複数マシンのジョブではマシンごとに取得されます)。シェルからは次のようにします。

lightning cp lit://<owner>/<teamspace>/jobs/<job-name>/model.joblib ./model.joblib   # one file
lightning cp -r lit://<owner>/<teamspace>/jobs/<job-name>/outputs/ ./outputs         # a folder

jobs/<job-name>/ のルートではなく、特定のサブフォルダーを一覧表示してコピーしてください。 収集された内容には、ドットファイルやキャッシュも含め、ジョブのホーム全体が含まれる場合があります。ルートに対する lightning ls はハングすることがありますが、lightning ls lit://<owner>/<teamspace>/jobs/<job-name>/outputs はすぐに返ります。Python では、job.list_artifacts("outputs", recursive=True) は即座に実行され、job.download_artifacts("outputs", "outputs") はジョブの outputs/ の内容をローカルの ./outputs に書き込みます。ジョブを削除すると、このディレクトリツリーも削除されます。

イメージ(docker)ジョブでは、ホームのアーティファクトは収集されず、コンテナ内に Lightning の認証情報もありません。 コンテナには LIGHTNING_CLOUD_URL、ジョブ ID とプロジェクト ID、LIGHTNING_USERNAME が渡されますが、LIGHTNING_API_KEY は渡されないため、そこから Drive への lightning cp は失敗します。小さな結果はログに出力して(lightning job logs で読み戻せます)、path_mappings で出力先をマウントするか、ファイルを戻す必要がある場合は Studio ジョブを使用してください。

マシン

CPU_SMALL, CPU, CPU_X_2/4/8/16, DATA_PREP(_MAX/_ULTRA), T4_SMALL, T4(_X_2/4/8), L4(_X_2/4/8), L40S(_X_2/4/8), RTXP_6000(_X_2/4/8), A100(_X_2/4/8), H100(_X_2/4/8), H200(_X_2/4/8), B200(_X_8)。マルチ GPU の _X_N バリアントでは GPU N 台分が課金されます。マルチマシンのジョブでは、マシン 1 台あたりの料金 × num_machines が課金されます。

上記の名称は執筆時点でのものであり、SKU は追加されるため、このリストを網羅的な一覧ではなく、出発点として扱ってください。lightning machine list では、インストール済み CLI が受け付ける名称が表示されますが、これはオフラインのリストであり、チームスペースで起動できるものはわかりません。

クラウドアカウントごとに、ライブデータからマシンを選んでください。 チームスペースでは複数のクラウドアカウントで起動でき、同じ GPU でもアカウントによって名称、価格、待ち時間が異なります。各アカウントで今すぐ起動できるものを、速い順に一覧表示します。

from lightning_sdk import Teamspace
ts = Teamspace("my-org/my-teamspace")
FAMILY = "H100"   # the GPU family the workload needs
OWN = {"lightning-baremetal", "lightning-public-prod"}   # Lightning's own accounts sort first
# Accounts go by id: ts.cloud_accounts holds display names, and list_machines() returns [] for a name
rows = [(m.wait_time, m.cost, a.cluster_id, m) for a in ts.cloud_account_objs
        for m in ts.list_machines(cloud_account=a.cluster_id) if m.family == FAMILY]
# then real waits before missing or placeholder ones (300 s, 10800 s), then fastest, then cheapest
key = lambda r: (r[2] not in OWN, r[0] in (None, 300, 10800), r[0] or 0, r[1] or 0)
for wait, cost, acct, m in sorted(rows, key=key)[:15]:
    print(f"{acct:34} {m.name:28} x{m.accelerator_count}  ${cost}/h  wait ~{wait}s")

cost は 1 時間あたりの USD 料金で、wait_time はマシンが空くまでの予想秒数です。その行のマシンを、その行のアカウントで起動してください(Job.run(machine=m, cloud=acct, …)、または cloud=acct を指定して作成した Studio):重要なのは、そのアカウントがその台数の GPU を提供していることです。SDK は GPU の名称(H200、lit-h200-1、lit-h200-141gb-1)を 1 つのマシンに対応付けます。引数なしの list_machines() は複数アカウントの情報をまとめますが、各行がどのアカウントのものかは示さないため、どこで起動すべきか判断できません。価格と待ち時間を添えて、上位の行をユーザーに提示してください。ソート済みリストの先頭部分を示し、末尾部分は示さないでください。また、ほかのことをする前に、先頭の行がリクエストに合っているか確認してください。

Lightning 独自のアカウント(lightning-baremetal、lightning-public-prod)を優先してください。 開発用およびテスト用アカウントには、起動できない GPU が表示されることがあります。リージョンの不具合、単なる HTTP 500、no price available、GPU なしで起動する Studio などです。wait_time が正確に 300 または 10800 秒の場合は、予測値ではなくプレースホルダーです。アカウントで起動に失敗した場合は、再試行せず、次の行を選んでください。

GPU を起動する前に必ず確認を求め、承諾を待ってください。 マシン、アカウント、1 時間あたりの価格、そして期限内でその実行にかかり得る最大料金を提示してください。ユーザーが GPU を指定した場合や予算を伝えた場合でも、必ず確認してください。予算は上限であり、支出の承認ではありません。ログイン前に見積もるには、認証不要の GET /v1/core/accelerators?cloudProvider=<PROVIDER> を使用します(プロバイダーの値と料金算出手順は lightning-cost-estimation スキルに記載されています)。カタログのエンドポイントを推測で作らないでください。/v1/accelerators、/v1/accelerator-catalog、/v1/pricing、/v1/compute/accelerators はすべて code: 5 を返します。

GPU ジョブを起動する前に、クラウドアカウントを選んでください。 デフォルトのクラウドでは、あらゆる GPU をあらゆる台数で提供しているわけではありません(AWS では H200 1 台を利用できません)。アカウントでリクエストに対応できない場合、マシンの名称に関係なく accelerator … not found for this AWS cluster で失敗することがあります。一方、Studio は GPU を指定しても CPU で起動することがあるため(lightning-studios を参照)、明確なエラーが出るとは限りません。上記の一覧からアカウントの cluster_id を使用してください。Teamspace.cloud_accounts の Lightning Cloud などの表示名は使用しないでください。Studio(cloud=…) に表示名を渡すと、clusterID Lightning Cloud is invalid で失敗します。CLI では、GPU 名とともにアカウントを --cloud で指定します。

lightning job run --name my-job --teamspace owner/teamspace --machine H200 --cloud lightning-baremetal \
  --image python:3.12-slim --command "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
起動後の最初の 1 分

ジョブの失敗のほとんどは、最初の数秒で発生します。マシンの指定ミス、パッケージ不足、引数名の変更などが原因です。ジョブが Running になったら、待つ前にログを確認し、最初のトレースバックが出た時点で停止してください。そのまま放置しないでください。

lightning job logs my-job --teamspace owner/teamspace --tail 40   # confirm the GPU line and the first steps
lightning job stop my-job --teamspace owner/teamspace             # only if the log shows a crash or the wrong machine

ジョブ自身のコマンドの最初に、ハードウェアとフォルダーを確認する処理を入れてください。たとえば、nvidia-smi --query-gpu=name,memory.total --format=csv,noheader && pwd && ls && python train.py のようにします。ログの最初の行で、実際に割り当てられたものと、コマンドの開始位置にスクリプトがあるかどうかがわかります。コマンドは bash ではなく、通常の POSIX sh で実行されます。set -o pipefail、[[ … ]]、配列はスクリプトの実行前に失敗するため、&& でつないだコマンドだけを使用してください。

使用例

このスキルで対応できるプロンプトの例:「このスクリプトを A100 でバッチジョブとして実行して」、「自分の Docker イメージを lightning で起動して」、「ジョブが失敗した理由は?ログを表示して」、「実行中のジョブに SSH 接続して」、「マルチマシンジョブの rank 1 に SSH 接続して」、「2 ノードの分散トレーニングを実行して」、「トレーニングの進行状況を教えて」、「このジョブの進捗バーを表示して」。

ローカルのスクリプトを GPU で実行し、出力ファイルを持ち帰ります(スタジオジョブです。イメージジョブではファイルを単独で返せないためです)。Machines の最新の一覧から ACCOUNT と MACHINE を選び、まずユーザーに価格を確認してください。

import time
from lightning_sdk import Job, Machine, Studio
# start_ready, upload_landed, stop_quietly: from the lightning-studios skill (*Set up on CPU, switch to a GPU*),
# with its other helpers and imports

studio = Studio("run-train", teamspace="my-org/my-teamspace", cloud=ACCOUNT, create_ok=True)
# an existing Studio of that name is reused as is, on whatever cloud it was created on
assert studio.cloud_account == ACCOUNT, f"{studio.name} is on {studio.cloud_account}: pick a new name"
try:                                                  # the Studio stops on any failure, and once the job starts
    start_ready(studio, Machine.CPU)                  # CPU is enough: it only holds the files
    upload_landed(studio, "train.py", "train.py")     # the Studio home, the job's working dir; the
                                                      # snapshot is taken at Job.run, so it must be there
    job = Job.run(name=f"train-{int(time.time())}", machine=MACHINE, studio=studio,  # no teamspace=,
                  cloud=ACCOUNT,                      # else the default cloud wins and can mismatch the Studio's
                  command="env -u UV_LIGHTNING_VIRTUALENV_ROOT uv run train.py",       # see Gotchas
                  max_run_attempts=1)
    deadline = time.time() + 20 * 60                  # the first job from a Studio waits ~5 min for a snapshot
    while str(job.status).endswith("Pending"):
        if time.time() > deadline:
            job.stop()
            raise TimeoutError(f"{job.name} still Pending after 20 min")
        time.sleep(15)
finally:                                              # the job runs on the snapshot, not the Studio
    stop_quietly(studio)
job.wait(interval=15, timeout=2 * 3600, stop_on_timeout=True)
print(job.status, job.total_cost)
job.download_artifacts("outputs", "outputs")         # whatever train.py wrote under ./outputs

同じ Studio から後続のジョブを実行すると、そのスナップショットが再利用され、Pending の状態が早く終了します。

**単発実行(トレーニング、評価、バッチ)では、Studio よりジョブを優先してください。**コマンドが終了すれば、クラッシュした場合も含め、ジョブの課金は停止します。Studio で実行がクラッシュすると、誰かが気づくまで GPU の課金がアイドル状態のまま続きます。例外は、期限が迫っている短時間の実行です。この場合、スナップショットを待つよりも、GPU で直接起動し、期限によって停止する Studio のほうが早く完了します(lightning-studios スキルを参照してください)。計画を提示する前にどちらかを選び、出力先とともに計画に明記してください。ジョブの出力先はチームスペースの Drive にある jobs/<job-name>/、Studio の出力先はホームディレクトリです。Studio のホームは、Studio の停止中もダウンロードできます。片方を約束して、もう片方を実行しないでください。

パラメータースイープ:1 つのループから複数のジョブを実行します。

for lr in ["1e-3", "3e-4", "1e-4"]:
    Job.run(name=f"sweep-lr-{lr}", machine=Machine.T4, studio="exp-1",
            command=f"python train.py --lr {lr}", env={"WANDB_RUN": f"lr-{lr}"},
            teamspace="my-org/my-teamspace", interruptible=True)
# each writes outputs to home ($LIGHTNING_ARTIFACTS_DIR); read them from the Studio under /teamspace/jobs/<name>/artifacts

進捗、完了予定時刻、後退

進捗を確認する価値があるほど時間のかかるジョブでは、どこまで進んだか、いつ完了する見込みか、失敗によって何が無駄になったかをユーザーに示します。プラットフォームが報告するのはジョブのステータスのみなので、ジョブ自身が進捗を出力し、このファイルと同じ場所にある progress.py がそれをステータスラインのバーとチャットイベントに変換します。references/progress.md には、手順ごとの詳細、行の形式、Studio のログ、バーのレイアウト、後退の分類方法が記載されています。<SKILL_DIR> はこのスキルのディレクトリで、Claude Code がスキルを読み込んだときに指定したベースディレクトリです。そこにある progress.py を使ってください。別のコピーは古い可能性があり、すべてのバーを表示するため、ディスクを検索して探さないでください。会話が圧縮された後は、次の起動前にこのスキルを再度読み込んでください。 要約には progress.py の呼び出し方は残りますが、毎回の実行で必要なことは残らないため、以降の起動は監視されなくなります。

job ── PROGRESS 450/1000 ──► progress.py watch (background, no tokens)
                               ├─► ~/.local/state/lightning-progress/state/<run>.json ─► status line (terminal)
                               └─► ~/.local/state/lightning-progress/events.jsonl ────► Monitor (terminal + desktop)
  1. ジョブに進捗を出力させます:合計がわかったらすぐに PROGRESS <step>/<total> を出力し、その後は各ステップ、または数ステップごとに出力します。出力するのはランク 0 のみとし、実行終了の基準となる単位で数えます(終了条件が時間予算なら秒単位)。学習以外の処理も行うジョブでは、ステージに PROGRESS_PHASE <name> <i>/<n> を付けます。tqdm バーは代替手段です。

    print(f"PROGRESS {step}/{total_steps}", flush=True)
    

    キューの場合(たとえば、8 つのチェックポイント × 7 つの評価)、起動スクリプトでキュー全体を通して数え、最初の項目からバーが動くようにします。処理中の項目名しか記録しないログでは、バーが数えられません。

    echo "PROGRESS_PHASE ckpt-376:mmlu 1/56"
    
  2. ポーラーをバックグラウンドの Bash コマンドとして起動します。エージェントのサンドボックス外で実行してください(SDK のリクエストはサンドボックス内では失敗するため、サンドボックス外での実行をユーザーに承認してもらいます)。どの python3 でも使えます。最初に表示される実行名をメモしてください。通常はジョブ名ですが、別のワークロードが使用中の場合は修飾されます。

    python3 <SKILL_DIR>/progress.py watch train-run-42 --teamspace my-org/my-teamspace
    

    Studio 内の作業では、ログの末尾が PROGRESS_EXIT <code> になるよう起動し、ログファイルを直接監視します。watch --studio <name> --log <path> を使ってください(リファレンスを参照)。

    実行中であることをユーザーに伝える前に、バーに数値が表示されることを確認します。python3 <SKILL_DIR>/progress.py statusline で描画できます。作業が始まった後も実行に no progress reported yet とだけ表示される場合、ログにバーが読み取れる内容がありません。ジョブの出力内容を修正してください。

  3. watch が実行名を表示したら、Monitor で python3 <SKILL_DIR>/progress.py events --run <RUN> を最大タイムアウトで実行し、タイムアウト時に再設定してイベントを監視します(前回の停止位置から続行されます)。各イベントが発生したら、その都度報告してください。返信が必要な内容(停滞、後退、失敗、再起動、最終状態)のみが出力され、実行終了時に終了します。ステータスラインのバーがない場合(デスクトップアプリ、IDE)には --all を追加します。

  4. watch がステータスラインが設定されていないと示した場合は、ステータスラインのバーを一度だけ、独立したユーザーへの質問として提案します。すべてのプロジェクトに適用する場合(こちらを推奨)は statusline --config --user、このプロジェクトのみに適用する場合は statusline --config --project-dir <the directory Claude Code was started in> を使います。出力されたブロックを、指定された設定ファイルにマージしてください。ユーザーの既存のステータスラインはそのまま動作します。

  5. 失敗した実行は同じ実行に再起動し、履歴を引き継ぎます。ポーラーは 30 分間待機します。修正したジョブを起動し、次に python3 <SKILL_DIR>/progress.py watch <new-job> --run <RUN> --note "<what changed>" を実行します。中止するには progress.py abandon <RUN> を使います。これもサンドボックス外で実行してください。進捗フォルダーに書き込みますが、サンドボックスからは書き込めないためです。

Raw API のフォールバック

CLI でラップされていない機能、主に正確なコストの JSON やその他の生のリソースフィールドを扱う場合に使います。

リストエンドポイントはプレーンな GET として呼び出し、-q を使ってクライアント側で絞り込みます。 lightning api は、-f/-F フィールドを指定し、-X を指定しないリクエストを POST(作成呼び出し)として送信します。そのため、-X GET を指定せずに /jobs へ -F limit=… を渡すと 400 "spec is required" が返され、/multi-machine-jobs では 400 "name is required" が返されます。

PROJECT_ID=$(lightning api /v1/memberships | jq -r '.memberships[] | select(.name=="<teamspace>") | .projectId' | head -1)

# list jobs (id + name) — plain GET, no -F
lightning api "/v1/projects/${PROJECT_ID}/jobs" -q '.jobs[] | [.id, .name] | @tsv'

# inspect one job as JSON (status, machine, cost, timestamps) — by JOB ID (job_...), not name
lightning api "/v1/projects/${PROJECT_ID}/jobs/${JOB_ID}"

# list multi-machine jobs — also a plain GET, no -F
lightning api "/v1/projects/${PROJECT_ID}/multi-machine-jobs" -q '.multiMachineJobs[].name'

JOB_ID はリスト呼び出しで返される job_... 形式の ID です。ジョブごとのエンドポイントは、人間が読む名前では 404 になります。 名前でジョブを 1 件探すには、リストを絞り込みます。 /jobs/find ルートは、最後に実環境でテストしたとき 501 Not Implemented を返しました。SDK の Job("<name>") は GET .../jobs/find?name=<name> として呼び出します。この形式なら動作する可能性がありますが、ここでは未テストです。日常的な用途では CLI を使ってください:lightning job list --json、lightning job inspect <name>、lightning job logs <name>。

注意点

  • 継承された DEBUG=1 によって認証情報が漏えいします。 SDK の HTTP 診断では、設定の読み取り時であっても Authorization ヘッダーが出力されます。そのため、セットアップのブロックでは DEBUG=0 LIGHTNING_DEBUG=0 を設定しています。このようなログは決して公開しないでください。キーが出力された場合は、ユーザーにローテーションするよう伝えてください(アバター → Global Settings → Keys)。
  • 保存済みのチームスペースは、別のログインアカウントに属していることがあります。 解決に失敗する場合は、lightning auth whoami と lightning api /v1/memberships を比較してください。また、自分の判断で組織を切り替えてはいけません。別の組織で唯一のメンバーシップが確認できても、要求された組織の代わりにはなりません。Signed in as the wrong account を参照してください。
  • ジョブは割り当て中、マシンの稼働時間に応じて課金されます。GPU を使う場合や num_machines > 1 で起動する場合は、必ず事前にユーザーに確認してください(Machines を参照)。また、暴走したジョブを停止できるよう、wait(..., stop_on_timeout=True) を優先してください。
  • 無反応のモニターは失敗として扱ってください。 期限のないポーラー、進捗行だけに一致するポーラー、または lightning.ai に接続できないポーラー(サンドボックス内のバックグラウンドコマンド)は、クラッシュしても無反応のままです。期限を設けて job.status をポーリングし、Completed だけでなく Failed/Stopped にも対処してください。また、ポーラーが最初の行を出力することを確認してください。
  • max_runtime は時間や支出の上限ではありません。 これは DWS の予約期間であり、dws_supported/dws_only に対応する非スポットマシンにのみ適用され、それ以外では何もしません(L40S での実行で効果がないことを確認済みです)。job.wait(timeout=..., stop_on_timeout=True) はジョブを停止しますが、呼び出し元のプロセスが生きている間に限られます。したがって、通常のマシンで無人実行中の上限を保証しないでください。何が上限を適用するのか説明し、job.stop() を呼び出すプロセスを稼働させておいてください。
  • Pending の状態で起動に時間がかかっても失敗ではありませんが、Pending は課金されないことを意味しません。 イメージジョブはマシンがスケジュールされるまで、Pending/creating のまま長時間待機することがあり、このキュー時間は無料です。プロビジョニング中も Pending と表示され、課金されます。課金対象は startedAt→stoppedAt であり、イメージのプルも含まれるため(lightning-cost-estimation を参照)、プロビジョニング途中で停止しても無料にはなりません。マシン一覧の wait_time でキュー時間を予測できます。期限までに諦める場合は、job.stop() の後に job.delete() を実行してください。
  • -y を付けずに lightning job delete を実行すると、削除されずに終了します。 非対話モードでは、閉じられた stdin からプロンプトを読み取り、Are you sure you want to delete? [y/N]: Aborted. と出力したまま、ジョブは一覧に残り、課金も続きます。ログではこの失敗を見落としやすいので注意してください。
  • --query と --severity では、すべての終了済みジョブを絞り込めるわけではありません。 ジョブのログがどこに保存されているかで決まります。ログ行が新しいログストレージにない場合、終了済みジョブは保存済みのログファイルにフォールバックしますが、このファイルはサーバー側で絞り込めません。その場合、CLI と SDK は何も返さずに終わるのではなく、This job's logs are only available as a saved file ... filter locally を発生させます。絞り込みなしで取得し、ローカルで grep を使ってください。--timestamps はどちらの方法でも使えます。ジョブがまだ Running の間はサーバー側で絞り込みが行われ、機能します。
  • job inspect には終了コードも失敗メッセージもないため、ジョブが失敗した理由を知るには、生のレコードの message フィールドを確認してください。 lightning api "/v1/projects/$PID/jobs" -q '.jobs[] | select(.name=="<name>") | .message'。開始/停止時刻は Job.started_at/Job.stopped_at と job list --json にあります。
  • Studio ジョブの Studio は、ジョブと同じチームスペースおよびクラウドアカウントに属している必要があります。
  • Python では、Studio オブジェクトと一緒に teamspace= を渡さないでください。 Job.run(studio=s, teamspace="owner/ts") は、Studio 自身のチームスペースを指定した場合でも、ValueError: Studio teamspace does not match provided teamspace を発生させます。teamspace は省略してください。ジョブは Studio からチームスペースを取得します。
  • Studio ジョブ内では uv run が失敗し、 error: failed to symlink file from /system/conda/miniconda3/uv/cache/… to /system/conda/miniconda3/uv/venvs/…: No such file or directory と表示されます。Studio の UV_LIGHTNING_VIRTUALENV_ROOT が、Studio 内に存在しないフォルダーを指しています(新しい Studio 内でも同じことが起きます)。env -u UV_LIGHTNING_VIRTUALENV_ROOT uv run … を実行してください(すべての UV_* 変数を解除しても同様です)。
  • ジョブは Running → Pending → Completed(または Failed)と遷移します。2 回目の Pending は再試行ではありません。 コマンドの終了後、プラットフォームがジョブの出力を保存している状態です。その時点でジョブを停止しないでください。終端状態になるまで待機する job.wait() を使ってください。
  • job.logs は文字列ではありません。 print(job.logs) は動作しますが、スライス(job.logs[-2000:])すると TypeError: '_Logs' object is not subscriptable が発生します。ログの末尾を取得するには lightning job logs <name> --tail N を使ってください。
  • 使用済みのジョブ名は通知なく置き換えられるため、job.name を読み戻してください。 名前はチームスペースごとに一意です。同じ名前がある場合、プラットフォームは新しい名前でジョブを作成し、SDK は the job was created as '<new>' instead と警告します。--name を省略すると名前が自動生成されます。
  • --machine は大文字と小文字を区別します(--machine a100 は Invalid value for '--machine' で失敗します)。上記の正確な名前を使ってください。A100_40GB/A100_80GB のバリアントは SDK でのみ利用でき、CLI には表示されません。
  • job.stop() は、ジョブが終端状態になるまで(1 秒ごとにポーリングして)ブロックします。
  • -e/env= の値は、チームスペース内の全員に表示されます。 トークン(Hugging Face、W&B、API キー)をこの方法で渡さないでください。渡してしまった場合は、ユーザーにローテーションするよう伝えてください。ジョブにはシークレット用のオプションがないため、トークンが必要な作業は、ユーザー自身がログインする Studio(hf auth login)で実行するか、トークン不要のモデルまたはデータセットを選んでください。
  • より大きな GPU で測定したメモリのピーク値は、より小さな GPU には当てはまりません。 ウォームアップのステップ(アテンションのオートチューニングなど)やアロケーターが空きメモリを使っていきます。H200 で測定した 64 GB のピーク値では、グラディエントチェックポイントの有無にかかわらず、80 GB の H100 でメモリ不足になりました。測定に使った GPU で実行するか、フル実行の前に小さい GPU で数ステップをテストしてください。
  • job.logs(follow=True) は、接続のたびに保存済みのログ行を再生し、ジョブの実行中は since を無視します。 再接続をまたいでログを追跡する処理では、すでに確認した行を除外してください。progress.py は timestamps=True を指定し、処理済みの行をスキップします。これがないと、再生された過去の進捗行が後退したように見えます。
  • Lightning を直接ポーリングする Monitor は、Claude Code のサンドボックス内では失敗します。 SDK と CLI のリクエストが外部に接続できず、sleep 60; lightning … のようなコマンドチェーンもブロックされるためです。ネットワーク通信はサンドボックス外の progress.py watch に集約し、Monitor は ~/.local/state/lightning-progress/events.jsonl のみを読み取る progress.py events に向けてください。ステータス行も同じフォルダーを読み取ります。どちらもこのフォルダーに書き込む必要はないため、サンドボックスから書き込み可能な場所に移動する必要はありません。セッションのスクラッチパッド内のフォルダーはセッション終了時に消えてしまい、進捗バーが空になります。watch --query PROGRESS を使うと、ログが非常に多いジョブの通信量を減らせますが、エラー行も非表示になり、停止の原因を特定しにくくなります。

コメント