mcpField
L
lightning-studios

lightning-studios

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

lightning-studios について

lightning-studiosは、`lightning`コマンドラインとPython SDKでLightning AI上の永続的なクラウド開発マシンを管理するスキルです。Studioのライフサイクル、計算リソースの選択、ファイル転送を扱います。

主な機能

  1. 01Studioの作成と管理
  2. 02CPU・GPU計算リソースの起動と停止
  3. 03`lit://`パスによるファイル転送
  4. 04`lightning_sdk` Python APIの利用

活用シーン

  1. 01永続クラウド環境での開発作業
  2. 02Studioのワークロードに合わせた計算リソース選択
  3. 03プロジェクトファイルのアップロードや出力の取得

インストール

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

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

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

詳細

Lightning AI Studios

Studio は、lightning.ai 上の永続的なクラウド開発マシンです。ファイルシステムはセッションをまたいで保持され、コンピューティングリソース(CPU/GPU)は起動時に接続され、停止時に切断されます。以下ではすべて、lightning CLI と lightning_sdk Python パッケージを使用します。

セットアップと認証

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 set env vars for headless use:
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 argumentCLI が古すぎます。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/credentials.json に保存されます。

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

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

各 Studio は、組織またはユーザーが所有するチームスペースにあります。決して推測しないでください。 次の順に解決します:

  1. 明示的な --teamspace owner/teamspace フラグ(owner は組織名またはユーザー名)/Python の Teamspace("owner/teamspace")(個別の org=/user= 引数は非推奨です)。
  2. 環境変数 LIGHTNING_ORG、LIGHTNING_TEAMSPACE、LIGHTNING_USERNAME、または ~/.lightning/config.yaml の設定デフォルト(lightning config get teamspace)。
  3. それ以外の場合は選択肢を一覧表示し、使用する組織/チームスペースをユーザーに確認します:
lightning api /v1/memberships | jq -r '.memberships[] | [.ownerType, .name, .projectId] | @tsv'

ユーザーに再度尋ねなくて済むよう、選択内容を保存します:lightning config set teamspace <owner>/<teamspace>。

スコープ付き API キーを使用する場合(ユーザーに確認できないエージェント):--teamspace には所有者の slug が必要ですが、/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 リファレンス

--teamspace には常に owner/teamspace を指定します。対話型端末で --name/--teamspace を省略すると選択メニューが開きます。スクリプトでは必ず明示的に指定してください。

# create (registers the studio, does NOT attach compute)
lightning studio create --name my-studio --teamspace owner/teamspace [--cloud PROVIDER] [--studio-type TEMPLATE]

# start compute (blocking; --create makes it if missing)
lightning studio start --name my-studio --teamspace owner/teamspace --machine CPU [--create] [--interruptible]
lightning studio start --name my-studio --teamspace owner/teamspace --gpus L4:4    # --gpus and --machine are mutually exclusive

# manage
lightning studio list --teamspace owner/teamspace [--all] [--sort-by status]   # default lists only your studios
lightning studio switch --name my-studio --teamspace owner/teamspace --machine A100   # requires Running studio
lightning studio stop --name my-studio --teamspace owner/teamspace
lightning studio delete my-studio --teamspace owner/teamspace -y              # NAME is positional here; -y/--yes: required non-interactively

# ssh / one-shot connect (create + start + ssh)
lightning studio ssh --name my-studio --teamspace owner/teamspace
lightning studio connect my-studio --teamspace owner/teamspace --machine CPU

# machine names the CLI accepts (offline list; says nothing about availability)
lightning machine list [--json]
ファイル:lit:// パス

Studio のパスは lit://<owner>/<teamspace>/studios/<studio-name>/<path> の形式です。コピー元またはコピー先のどちらか一方だけに lit:// を指定します。ディレクトリには -r が必要です。

lightning cp ./train.py lit://owner/teamspace/studios/my-studio/train.py     # upload
lightning cp -r lit://owner/teamspace/studios/my-studio/logs/ ./logs         # download dir
lightning ls lit://owner/teamspace/studios/my-studio                         # list files (-r recursive, --json)
lightning rm lit://owner/teamspace/studios/my-studio/old.txt [-r] [-f]

cp -r はフォルダー自体ではなく、フォルダーの内容をコピーします。 lightning cp -r ./proj lit://…/studios/my-studio/ を実行すると、どちらのパスが / で終わるかに関係なく、proj 内のファイルが Studio のホームに直接コピーされます。フォルダーを保持するには、コピー先にフォルダー名を指定します:lightning cp -r ./proj lit://…/studios/my-studio/proj/。ファイルが所定の場所にあることを前提とする処理を実行する前に、lightning ls -r で結果を確認してください。

アップロードしたファイルがマシンに届くまでには時間がかかります。Studio の準備が完了している場合も同様です。 lightning cp と studio.upload_file/upload_folder はストレージを経由し、ファイルが Studio のディスクに届く前に処理を返すため、直後に起動したコマンドは No such file or directory で失敗することがあります。ファイルを必要とする処理を実行する前に、ファイルが届くまで待ってください。Example workflows の upload_landed がこの処理を行います。

Python SDK

from lightning_sdk import Machine, Studio, Teamspace

ts = Teamspace("my-org/my-teamspace")                  # owner/teamspace; org=/user= are deprecated
studio = Studio(name="my-studio", teamspace=ts, create_ok=True)  # create_ok=False -> error if missing

studio.start(machine=Machine.CPU)          # blocking; interruptible=True for spot pricing
print(studio.status)                       # NotCreated|Pending|Running|Stopping|Stopped|Completed|Failed

# run commands — studio must be Running
out = studio.run("nvidia-smi")                                  # raises RuntimeError on non-zero exit
out, code = studio.run_with_exit_code("pytest -q")              # no raise on non-zero
out, code = studio.run_and_detach("python train.py", timeout=30)  # keeps running in background

studio.upload_file("train.py", remote_path="train.py")          # remote path relative to studio home
studio.download_file("outputs/model.ckpt", "model.ckpt")
studio.upload_folder("./src"); studio.download_folder("outputs/")

studio.switch_machine(Machine.A100)        # change machine while running
studio.set_env({"WANDB_API_KEY": "..."})   # env vars; partial=True merges
studio.stop()                              # releases compute, keeps filesystem
studio.delete()
マシンタイプ

Machine.<NAME> または文字列として渡します(Machine.from_str("A100") は名前またはスラッグを受け付けます)。

  • CPU:CPU_SMALL、CPU(デフォルト、4 コア)、CPU_X_2/4/8/16。大容量ディスク:DATA_PREP、DATA_PREP_MAX、DATA_PREP_ULTRA
  • GPU:T4_SMALL、T4、T4_X_2/4/8、L4、L4_X_2/4/8、L40S、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、B200_X_8
  • A100_40GB*/A100_80GB* バリアントは SDK に存在しますが、CLI の --machine では非表示です(studio switch および Python では使用できます)。

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 時間あたりの米ドル料金で、wait_time はマシンが空くまでの予想秒数です。 引数なしで list_machines() を実行すると複数のアカウントが統合され、各行がどのアカウントに属するかはわかりません。料金と待ち時間を添えて、上位の行をユーザーに表示してください。表示するのはソート済みリストの先頭であり、末尾ではありません。何よりも先に、最初の行がリクエストに合っているか確認してください。

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

GPU を起動する前に必ずユーザーに確認し、承諾を待ってください。 マシン、アカウント、1 時間あたりの料金、期限内に実行した場合の最大費用を提示してください。ユーザーが GPU を指定していたり、予算を提示していたりする場合でも確認が必要です。予算は上限であり、支出の承認ではありません。

**Studio のクラウドアカウントは作成時に固定されるため(studio switch では変更できません)、選択した行のアカウントで作成し、**その行のマシンに起動または切り替えてください。重要なのは、そのアカウントで該当 GPU を指定の台数で利用できることであり、名前ではありません。SDK では H200、lit-h200-1、lit-h200-141gb-1 が同じマシンに対応付けられます。

cloud=/--cloud に指定するのはアカウント ID(cluster_id)であり、表示名ではありません。 Studio(..., cloud="Lightning Cloud") は 400 clusterID Lightning Cloud is invalid で失敗します。

ログイン前に料金を確認するには、認証不要の GET /v1/core/accelerators?cloudProvider=<PROVIDER> を使用してください(lightning-cost-estimation スキルにプロバイダーの値と料金計算の方法が記載されています)。カタログのエンドポイントを作り上げないでください。/v1/accelerators、/v1/accelerator-catalog、/v1/pricing、/v1/compute/accelerators はすべて code: 5 を返します。

Interruptible(スポット)はフラグであり、マシンタイプではありません。--interruptible/interruptible=True を使用します。

GPU での起動

Studio のクラウドで販売されていない GPU を指定すると、studio start は「hasn't been vetted」という警告だけを出して CPU で起動します。 デフォルトのクラウドでは、すべての GPU をすべての台数で販売しているわけではありません(例:AWS では 1 台の H200 は提供されていません)。以下の最初のワークフローでは、これを防ぎます。

  1. 指定した台数の GPU を販売しているアカウント(上記の一覧)で Studio を作成します。 再利用する Studio は以前のクラウドを維持します。lightning studio list で確認し、アカウントが異なる場合は新しく作成してください。
  2. セットアップは、そのアカウントの CPU で行ってください。インストール、モデルのダウンロード、CUDA ソースのビルドは GPU 料金で請求されるためです。 ビルドでは、対象を設定する必要があります。例:H100/H200 の場合は TORCH_CUDA_ARCH_LIST=9.0。
  3. その行の Machine に切り替えた後、Studio がコマンドを実行できるようになるまで待ち、ハードウェアを確認します。 GPU がなければ Studio を停止し、ステップ 1 に戻ってください。再試行しないでください。

切り離した実行のライブ進捗(必須)

Studio で切り離す実行にはすべて、lightning-jobs/progress.py によるライブ進捗バー、予想所要時間、遅延の追跡が付きます。 以下のサンプルワークフローだけでなく、自分で作成したヘルパースクリプトから起動する場合も含め、すべての起動が対象です。 待機ループでログ行を出力したり、ユーザーにファイルの tail -f を依頼したりしても、要件を満たしたことにはなりません。 会話が圧縮された後、次に起動する前にこのスキルを再度読み込んでください。 要約には progress.py の呼び出し方は残りますが、このルールは残らないため、それ以降の起動が監視されなくなります。

progress.py は lightning-jobs スキルに同梱されており、このスキルの隣にインストールされています(プラグイン経由および npx skills add 経由)。<LIGHTNING_JOBS_SKILL_DIR> は、lightning-jobs が読み込まれている場合は Claude Code が指定したベースディレクトリ、そうでない場合は <THIS_SKILL_DIR>/../lightning-jobs です。 <LIGHTNING_JOBS_SKILL_DIR>/progress.py が存在することを確認し、そのコピーを使用してください。別の progress.py をディスク上で探さないでください。他のチェックアウトは古い可能性があり、最後に起動したウォッチャーが、すべてのバーを描画するコピーを決定します。その後、このスキルの ライブ進捗、予想所要時間、遅延 のセクションに従ってください。PROGRESS 行の形式、events に対するモニター、ステータス行の案内を確認します。Studio では、起動方法と watch コマンドだけが異なります。

Studio で起動する場合は(例:studio.run_and_detach を使用)、スクリプトのフォルダーから、バッファリングされないように -u を指定して実行し、実行中に PROGRESS 行がログに届くようにします。また、クラッシュと成功を区別できるよう、ログの末尾に PROGRESS_EXIT を出力します。

cd ~/src && nohup sh -c 'python -u train.py > train.log 2>&1; echo PROGRESS_EXIT $? >> train.log' </dev/null >/dev/null 2>&1

自分のマシンから監視する場合は、エージェントのサンドボックス外でバックグラウンドコマンドとして実行します。--log は Studio のホームディレクトリからの相対パスなので、起動時に書き込まれるファイルは src/train.log です。

python3 <LIGHTNING_JOBS_SKILL_DIR>/progress.py watch --studio exp-1 --log src/train.log --teamspace my-org/my-teamspace

progress.py が見つからない場合は、lightning-jobs がインストールされていません。インストールするとライブバーが使えることをユーザーに一度だけ伝えてください。それまでは数分おきに、最後の PROGRESS 行と、ログの tail -n 40 によるエラーを報告してください。

ワークフローの例

このスキルが対応するプロンプト:「GPU Studio を起動してトレーニングスクリプトを実行して」、「このリポジトリを Studio にコピーして、長時間の実行を開始して」、「exp-studio に SSH 接続して」、「Studio がアイドル状態なので停止して」。

CPU でセットアップし、GPU に切り替え、実行し、結果を収集して停止する。 Studio は Running になってからでないとコマンドを実行できません。また、studio.start() / lightning studio start はコマンドを実行できる状態になった後もブロックし続ける場合や、応答が返らない場合があります。そのため、バックグラウンドで開始し、期限を設けて準備完了をポーリングします(「注意点」を参照)。その他の SDK 呼び出し(run*、switch_machine、stop)には独自の時間制限がなくポーリングするため、以下のすべての呼び出しは bounded を介して行います。呼び出しが停止すると例外が発生し、課金がいつまでも続くことを防ぎます。また、except で Studio を停止します。lightning-jobs スキルでは、以下のヘルパーをインポートして再利用します。

import hashlib, os, re, shlex, threading, time
from lightning_sdk import Machine, Studio, Teamspace

def bounded(fn, seconds):
    """fn()'s result, or TimeoutError after `seconds`: the SDK's own polls have no deadline."""
    done, box = threading.Event(), {}
    def _go():
        try:
            box["ok"] = fn()
        except BaseException as e:
            box["err"] = e
        finally:
            done.set()
    threading.Thread(target=_go, daemon=True).start()
    if not done.wait(seconds):
        raise TimeoutError(f"{getattr(fn, '__name__', 'call')} still blocking after {seconds}s")
    if "err" in box:
        raise box["err"]
    return box.get("ok")

def run_if_up(studio, cmd, seconds=120):
    """(output, exit code), or ("", -1) while the Studio isn't Running (starting, between machines)
    or the command doesn't answer within `seconds`."""
    try:
        return bounded(lambda: studio.run_with_exit_code(cmd), seconds)
    except (RuntimeError, TimeoutError):
        return "", -1

def stop_quietly(studio):
    """Stop billing; fall back to `lightning studio stop` if this raises."""
    if str(studio.status).endswith(("Running", "Pending")):
        bounded(studio.stop, 300)

def wait_ready(studio, minutes=10, failed=()):
    deadline = time.time() + minutes * 60
    while time.time() < deadline and not failed:
        out, code = run_if_up(studio, "python -c pass")        # not `true`: see Gotchas
        if code == 0 and "setting things up" not in out:
            return
        time.sleep(10)
    raise failed[0] if failed else TimeoutError(f"{studio.name} never finished setup: use a new Studio")

def start_ready(studio, machine, minutes=10):
    """Start on `machine` in the background and return once it can run commands."""
    if str(studio.status).endswith("Running") and studio.machine != machine:
        raise RuntimeError(f"{studio.name} is already running on {studio.machine}: stop it or use a new name")
    failed = []                                                # start() errors surface here, not in the thread
    def _start():
        try:
            studio.start(machine=machine)
        except Exception as e:
            failed.append(e)
    threading.Thread(target=_start, daemon=True).start()
    wait_ready(studio, minutes, failed)

def upload_landed(studio, local, remote, minutes=3, skip=(".git", "__pycache__", ".venv", "node_modules")):
    """Upload a file or folder (paths relative to the Studio home), hidden files included, then
    wait until the Studio's disk has every file with the right content: the upload returns before
    it lands (see *Files*). Folders named in `skip` stay behind."""
    files = [local]
    if os.path.isdir(local):
        files = []
        for root, dirs, names in os.walk(local):
            dirs[:] = [d for d in dirs if d not in skip]
            files += [os.path.join(root, n) for n in names]
    dest = {f: remote if f == local else f"{remote}/{os.path.relpath(f, local).replace(os.sep, '/')}"
            for f in files}
    for f in files:
        bounded(lambda: studio.upload_file(f, dest[f], progress_bar=False), 600)
    want = [hashlib.md5(open(f, "rb").read()).hexdigest() for f in files]
    check = "cd ~ && md5sum " + " ".join(shlex.quote(dest[f]) for f in files)
    deadline = time.time() + minutes * 60
    while time.time() < deadline:
        out, code = run_if_up(studio, check)
        if code == 0 and re.findall(r"^[0-9a-f]{32}", out, re.M) == want:
            return
        time.sleep(5)
    raise TimeoutError(f"{local} hasn't fully reached {studio.name} after {minutes} min")

ts = Teamspace("my-org/my-teamspace")
# the fastest, then cheapest, 1x H200 across the teamspace's cloud accounts (see *Machine types*)
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 == "H200" and m.accelerator_count == 1]
if not rows:
    raise SystemExit("no account here sells a 1x H200: ask the user for another GPU or teamspace")
wait, cost, acct, gpu = min(rows, key=lambda r: (r[0] is None, r[0] or 0, r[1] or 0))
print(acct, gpu.name, f"${cost}/h", f"wait ~{wait}s")        # confirm with the user before the GPU starts

studio = Studio("exp-1", teamspace=ts, cloud=acct, create_ok=True)
# an existing Studio of that name is reused as is, on whatever cloud it was created on
assert studio.cloud_account == acct, f"{studio.name} is on {studio.cloud_account}: pick a new name"

try:                                                          # any failure from here stops the Studio
    start_ready(studio, Machine.CPU)
    upload_landed(studio, "./src", "src")                     # returns once the files are on disk
    out, code = run_if_up(studio, "cd ~/src && pip install -r requirements.txt", 30 * 60)  # at CPU rates
    assert code == 0, out or "pip install failed or ran over 30 min"
    try:                                                      # the row picked from this account's list
        bounded(lambda: studio.switch_machine(gpu), 15 * 60)  # TimeoutError if it never lands
    except Exception as e:                                    # it can report this and switch anyway
        if "cannot switch to a Studio" not in str(e):
            raise
    for _ in range(30):                                       # so trust nvidia-smi, not the call
        out, code = run_if_up(studio, "nvidia-smi --query-gpu=name --format=csv,noheader")
        gpus = out.splitlines() if code == 0 else []
        if gpus:
            break
        time.sleep(10)
    print(gpus)                                               # e.g. ['NVIDIA H200']
    assert len(gpus) == 1 and "H200" in gpus[0], "wrong hardware: pick another account"
    wait_ready(studio)                                        # a new machine: wait until it can run commands
    # the exit code goes to train.exit for this loop and to the log for progress.py;
    # clear the last run's before launching
    bounded(lambda: studio.run_and_detach("cd ~/src && rm -f train.exit && nohup sh -c 'python -u train.py > train.log 2>&1; c=$?; echo PROGRESS_EXIT $c >> train.log; echo $c > train.exit' </dev/null >/dev/null 2>&1", timeout=30), 120)
    print(run_if_up(studio, "tail -n 40 ~/src/train.log")[0])          # within the first minute: crashes show up in seconds
    # now start the watcher (*Live progress for detached runs*); this loop only guards the deadline
    deadline = time.time() + 2 * 3600                         # a bit over the expected run time
    while run_if_up(studio, "test -f ~/src/train.exit")[1] != 0:
        if time.time() > deadline:
            raise TimeoutError(run_if_up(studio, "tail -n 40 ~/src/train.log")[0])
        time.sleep(60)
    assert run_if_up(studio, "cat ~/src/train.exit")[0].strip() == "0", \
        run_if_up(studio, "tail -n 40 ~/src/train.log")[0]
except BaseException:
    stop_quietly(studio)
    raise
lightning cp -r lit://my-org/my-teamspace/studios/exp-1/src/outputs/ ./outputs
lightning studio stop --name exp-1 --teamspace my-org/my-teamspace

train.py を切り離したら、切り離した実行のライブ進捗 の説明に従ってウォッチャーを起動します。 progress.py watch --studio exp-1 --log src/train.log。

セットアップが簡単な短時間の実行(依存関係を自ら宣言する uv スクリプト)の場合は、CPU フェーズを省略できますが、try ブロックと期限は維持してください。start_ready(studio, gpu) を実行し、続いて upload_landed(studio, "train.py", "src/train.py") を実行します。pip、切り替え、nvidia-smi の手順を省き、python train.py の代わりに env -u UV_LIGHTNING_VIRTUALENV_ROOT uv run train.py を切り離して実行します(「注意点」を参照)。

SSH 接続してスクリプトを対話的に実行する(人間のユーザー向けです。ssh は対話型シェルを開くため、エージェントは上記の studio.run* を優先してください)。

lightning studio ssh --name exp-1 --teamspace my-org/my-teamspace          # interactive shell
lightning ssh configure --name exp-1 --teamspace my-org/my-teamspace      # writes ~/.ssh/config Host block...
ssh exp-1 'python ~/src/eval.py'                                          # ...then plain ssh runs one-off commands
lightning studio connect exp-1 --teamspace my-org/my-teamspace --machine CPU   # create+start+ssh in one shot

Raw API のフォールバック

CLI でラップされていない操作には、lightning api <path> で認証済みリクエストを送信できます(-X はメソッド、-F は型付きフィールド、-f は文字列フィールド、-q は jq フィルターです)。

lightning api /v1/memberships -q '.memberships[].name'
PROJECT_ID=$(lightning api /v1/memberships | jq -r '.memberships[] | select(.name=="<teamspace>") | .projectId' | head -1)
# studios are "cloudspaces" in the API — one word, and call it BARE (see Gotchas)
lightning api "/v1/projects/${PROJECT_ID}/cloudspaces" -q '.cloudspaces[].name'

注意点

  • 継承された DEBUG=1 によって認証情報が漏えいします。 SDK の HTTP 診断ログでは、設定の読み取り時であっても Authorization ヘッダーが出力されます。そのため、セットアップブロックでは DEBUG=0 LIGHTNING_DEBUG=0 を設定しています。このようなログを公開しないでください。キーが出力されてしまった場合は、ユーザーにローテーションするよう伝えてください(アバター → グローバル設定 → キー)。
  • 保存済みのチームスペースが、別のログインアカウントに属している場合があります。 解決に失敗した場合は、lightning auth whoami と lightning api /v1/memberships を比較してください。また、ユーザーに代わって組織を切り替えないでください。別の場所に所属先が一つしかなくても、要求された所属先の代わりにはなりません。誤ったアカウントでサインインしている場合を参照してください。
  • 使い終わったら Studio を停止してください。接続中のコンピュートには料金がかかり、GPU はさらに高額です。
  • 別のマシンですでに実行中の Studio に対して start() を呼び出すと、例外が発生します。 代わりに switch_machine を使用してください。
  • 自動スリープを無効にする(studio.auto_sleep = False)か、auto_sleep_time を設定すると、無料の CPU Studio が有料になります。
  • 1 回限りの実行(トレーニング、評価、バッチ処理)には、ジョブ(lightning-jobs)を優先してください。 ジョブはコマンドが終了すると、クラッシュした場合も含めて課金が停止します。Studio で実行中にクラッシュすると、GPU の課金は継続します。例外は、期限が迫っている短時間の実行です。Studio から初めてジョブを実行すると、スナップショットの作成に約 5 分かかるため、GPU で起動した Studio 上で、期限が来たら停止するようにして実行してください。計画を提示する前にどちらかを選び、出力先も含めて計画内で明示してください。
  • GPU で再起動する前に、前回の試行が GPU をまだ占有していないか確認してください。 占有したままだと、新しい実行で CUDA out of memory が発生します。nvidia-smi --query-compute-apps=pid,used_memory --format=csv,noheader の出力が空であることを確認してください。
  • デタッチした実行のログは最初の 1 分以内に読み取り、何も出力しないモニターは失敗として扱ってください。 期限がないポーラー、進捗行だけを照合するポーラー、または lightning.ai に到達できないポーラー(サンドボックス化されたバックグラウンドコマンド)は、クラッシュしても何も出力しません。期限を設定し、Traceback/Error/Killed/CUDA out of memory も照合し、最初の行が出力されることを確認してください。
  • 非対話モードで -y を付けずに lightning studio delete を実行しても、何も削除されません。 Are you sure you want to delete? [y/N]: Aborted. と表示されて終了し、Studio(および課金)はそのまま残ります。まずユーザーに確認してから、-y/--yes を渡してください。
  • Studio の一覧エンドポイントは /cloudspaces です。1 語で、-F フィールドは受け付けません。 ハイフン付きの /v1/projects/{pid}/cloud-spaces は HTTP 404 Not Found を返します。-F limit=20 を追加すると、HTTP 400 Bad Request になります。lightning api は -X を指定せずに -f/-F フィールドを含むリクエストを POST(作成呼び出し)として送信するためです。フィールドを指定せずに呼び出して -q で絞り込むか、-X GET を渡してフィールドをクエリパラメーターとして送信してください。
  • Studio 内で引数なしの Studio() を呼び出すと、現在の Studio が解決されます(LIGHTNING_CLOUD_SPACE_ID 経由)。
  • 新しい 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 が、まだ存在しないフォルダーを指しているためです。env -u UV_LIGHTNING_VIRTUALENV_ROOT uv run … を実行してください。
  • Running は準備完了を意味しません。セットアップ完了のメッセージが消えるまで、python コマンドでポーリングしてください。 起動直後は、Error: We are still setting things up for you, please try again after the progress bar at the top of the Studio disappears. というエラーでコマンドが失敗することがあります。python、pip、uv はシェルエイリアス(load_conda_and_run)であり、セットアップが完了するまでは実行されません。一方、単純な true はもっと早く成功するため、ワークフローの例と同様に studio.run_with_exit_code("python -c pass") でポーリングしてください。セットアップには通常 1~2 分かかります。数分経っても完了しない場合は Studio を停止して新しく作成してください。同じ Studio を再起動しても、停止したままになることがあります。
  • studio.switch_machine() は ApiException (400) … "cannot switch to a Studio" を発生させながら、切り替えが完了することがあります。 ワークフローの例と同様に、このエラーを捕捉して、ハードウェアが使えるようになるまで nvidia-smi をポーリングしてください。このエラーを理由に再起動したり、再度切り替えたりしないでください。
  • コマンドの開始フォルダーを決めつけないでください。 アップロード先のホームディレクトリからのパス(例のように cd ~/src)を使って、そのフォルダーに移動し、実際に実行する前にそこで ls を実行してスクリプトを確認してください。
  • nohup … </dev/null >/dev/null 2>&1 でデタッチしてください。 リダイレクトがないと、それ以降のすべての studio.run*() の出力が nohup: ignoring input で始まり、ログモニターに大量の出力が流れます。
  • コマンドの出力が有効な UTF-8 でない場合、studio.run*() は例外を発生させます。 複数バイト文字の途中で出力が切れると(é やプログレスバーを含むログに対する tail -c N、テキストファイルに対する head -c など)、コマンド自体は成功していても cloud_space_service_get_long_running_command_in_cloud_space(HTTP 500)で失敗します。ログの読み取りには tail -n N を使うか、iconv -c -f utf-8 -t utf-8 にパイプしてください。
  • teamspace="owner/teamspace" と非推奨の org=/user= の両方を渡すと、ValueError が発生します(Teamspace、Studio、Job が対象です)。古い引数だけなら引き続き動作しますが、DeprecationWarning が出力されます。

コメント