トレーナー制御
start、wait、cancel、abortSignal、再接続の仕組み。
createTrainer は 3 メソッドを持つ Trainer オブジェクトを返します。arkor start と Studio の "Run training" ボタンはどちらも start() の後に wait() を呼びます。これらを自分で呼ぶのは、学習を自前のコード(サーバー、スクリプト、独自 CLI)に組み込むときだけです。
シグネチャー
interface Trainer {
readonly name: string;
start(): Promise<{ jobId: string }>;
wait(): Promise<TrainingResult>;
cancel(): Promise<void>;
}
interface TrainingResult {
job: TrainingJob;
artifacts: unknown[];
}start()
const { jobId } = await trainer.start();- ジョブをクラウド API に投入し、バックエンドが受理した時点で resolve。返ってくる
jobIdは Studio で見えるもの、SDK のTrainingJob.idと同じです。 - 冪等: 同じトレーナーで
start()を 2 回目に呼んでも再投入せず同じjobIdを返します(packages/arkor/src/core/trainer.ts:275-289)。 - イベントストリームを開かず、コールバックを発火 しません。それをするのは
wait()です。
wait()
const { job, artifacts } = await trainer.wait();- 学習の SSE イベントストリームを開き、各フレームをコールバックにディスパッチし、ストリームが
training.completedかtraining.failedを報告したときに終端のTrainingResultで resolve します。 - まだ
start()を呼んでいなければ代わりに呼んでくれます。 - 5 つのライフサイクルコールバックはすべて
wait()内から発火します。start()を呼んでwait()を呼ばないと、学習がバックエンドで進行していてもコールバックは動きません。 - 一過性の SSE エラーでは再接続します(下記)。
cancel()
await trainer.cancel();start()がまだ呼ばれていなければcancel()は何もしません(:388-389で早期 return)。- そうでなければバックエンドにキャンセルリクエストを送ります。
- ベストエフォート。 SDK は終端ステータスでショートサーキットしません。学習が既に completed / failed / cancelled なら、バックエンドは non-2xx を返すことがあり
cancel()は reject します。投機的に呼ぶならtry / catchで囲んでください。
abortSignal
const controller = new AbortController();
const trainer = createTrainer({
name: "with-timeout",
model: "unsloth/gemma-4-E4B-it",
dataset: { type: "huggingface", name: "arkorlab/triage-demo" },
abortSignal: controller.signal,
});
// あとで、どこからでも:
controller.abort();abortSignal は あくまでローカルの wait() ループ をコントロールします。シグナルが Abort すると:
- 進行中の SSE fetch が Abort される(
trainer.ts:325-328)。 - 動作中の再接続バックオフ
delayがsignal.reasonで reject される(trainer.ts:178)。 - シグナルが Abort されている場合、
handleFailureが再 throw する(trainer.ts:308)。 - 結果として
wait()は Abort 時に resolve ではなく reject する。
これは cancel() を呼ばず、バックエンドにも何も送りません。マネージド側ではジョブが GPU 時間を使い続けます。
両方の効果(ローカルでの待機停止と、バックエンドの学習停止)が欲しいなら別々にやります:
try {
await trainer.wait();
} catch (err) {
if (controller.signal.aborted) {
// 想定通り: wait() を止めるよう頼んだ
} else {
throw err;
}
}
await trainer.cancel(); // ベストエフォート、上記参照「この学習を待つのはもういい」(リクエストタイムアウト、親プロセス終了)なら abortSignal。「バックエンド側で学習を止めたい」なら cancel()。
再接続
wait() はデフォルトで一過性の障害を超えて SSE ストリームを生かし続けます:
- 少なくとも 1 本のパース可能な実データフレームを受信した後(keepalive の
pingフレームと不正なフレームは進捗として数えません)のクリーンなストリーム EOF は、即時再接続をベース遅延(initialReconnectDelayMs、デフォルト 1000 ms)で起こし、失敗回数にはカウントしません。ストリームはLast-Event-IDで再開します。 - 接続エラー、またはパース可能なデータフレームを 1 本も受信せずの EOF(keepalive ping やパース不能なゴミだけを送って EOF した場合を含む)は失敗としてカウントされ
handleFailureを経由します: 指数バックオフはinitialReconnectDelayMs * 2 ** attempt、各試行の遅延はmaxReconnectDelayMs(デフォルト 60 000 ms)にクランプ、連続失敗カウントはmaxReconnectAttemptsで上限。これは意図的です: ping やゴミだけを送って EOF するような壊れた中継が、ベース遅延で無限ループしないように失敗として計上します。 - 恒久的な接続エラーはリトライループの例外です:
401/403(認証)、410(gone)、426(アップグレード必須)のレスポンスは、再接続しても同じ結果になるためwait()を即座に reject します。404は一過性として扱いリトライします(作成直後のジョブのイベントストリームがまだ可視でない場合があるため)。408、429、5xxも同様にリトライします。 maxReconnectAttemptsのデフォルトはundefined(連続失敗無制限)。TrainerInputから設定はできず、reconnectDelayMsとmaxReconnectDelayMsも含めcreateTrainerの第 2 引数context(@internal注釈付き、変更され得る)からのみ設定できます。多くのプロジェクトでこれは、ジョブが走っている限り一過性 SSE 失敗が黙ってリトライされ続ける、ということを意味します。
この経路は あくまで トランスポート障害のためのものです。ユーザーコールバックの throw はこの経路を通りません。再接続もリトライもせず、即座に wait() を reject します(ライフサイクルコールバック § 例外ハンドリング を参照)。throw を再接続経由にすると、既に進んだ失敗イベントの Last-Event-ID の先から再開してしまい、エラーを握り潰すことになります。決定的で致命的でないエラーハンドリングが必要なら、wait() の reject に頼るのではなくコールバック内で catch してください。
ツープロセスパターン
CLI 以外で使うときの典型的な形は、長寿命のトレーナー参照を持って自前のコードで start、wait、cancel を制御するものです:
import { createTrainer } from "arkor";
const controller = new AbortController();
process.on("SIGINT", () => controller.abort());
const trainer = createTrainer({
/* ... */
abortSignal: controller.signal, // abort() で wait() が実際に reject するよう接続
});
const { jobId } = await trainer.start();
console.log(`Started ${jobId}`);
try {
const { artifacts } = await trainer.wait();
console.log(`Finished with ${artifacts.length} artifact(s).`);
} catch (err) {
if (controller.signal.aborted) {
await trainer.cancel().catch(() => {});
throw new Error("Aborted by signal");
}
throw err;
}これは runTrainer のエントリー解決を除けば、arkor start がやっているのと機能的に同じです。
関連項目
createTrainer: このTrainerを返す入力の型- ライフサイクルコールバック:
wait()内から発火するもの runTrainer:start()+wait()をラップしてエントリー解決まで行うヘルパー- プログラム実行レシピ: サーバーやスクリプトに組み込む完全な配線