sft-launch
SkillAI & modelsLaunch SFT via `python -m hpc.launch --job_type sft` on any cluster (JSC Jupiter GH200, CINECA Leonardo A100, TACC Vista GH200), with EITHER backend — LLaMA-Factory (default) or axolotl (`--sft_backend axolotl`) — including Delphi tool-calling models (delphi template, tokenizer prep, jinja-as-ground-truth masking). This skill is the cluster-AGNOSTIC core (backend choice, Delphi handling, config maps, node-scaling, dataset mixing, cleanup recognition, common traps). Per-cluster particulars (preamble, paths, QOS/wall, sbatch patches, no-internet handling, HF-upload mechanics) live in `.agents/ops/<cluster>/ops.md §SFT`. Use when asked to SFT / launch a finetune / train a model on Jupiter, Leonardo, or TACC. Reference: notes/ot-agent/sft_experiments.md, CLAUDE.md.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the sft-launch skill
What this skill tells your AI
The instructions your AI receives, as published by open-thoughts/openthoughts-agent in .agents/skills/sft-launch/SKILL.md and read by ahel’s review.
⚠ Local clone = ground truth (CLAUDE.md §Always). ALL code/config/sbatch edits go in the local Mac checkout (
~/Documents/OpenThoughts-Agent) → commit → push →git pullon the cluster. NEVER hand-edit,git commit, or leave divergent/ untracked changes on a cluster; no patch-by-rsync. New/changed configs are authored locally + synced, never on the cluster. Bake this into every subagent you dispatch.
SFT runs through python -m hpc.launch --job_type sft on both backends and all clusters. Read
.agents/ops/<cluster>/ops.md §SFT for the cluster preamble, paths, QOS/wall, and cleanup mechanics.
1. Pick backend, cluster, env — then launch
| LLaMA-Factory (default) | axolotl (--sft_backend axolotl) | |
|---|---|---|
| When | Everything today; the validated production path | Delphi jinja-as-ground-truth SFT; when you want axolotl's template/plugin stack |
| Launcher runs | accelerate + DeepSpeed ZeRO-3 (multi-node) / torchrun | -m axolotl.cli.train |
| Conda env | otagent (or sft-qwen35 for Qwen3.5 hybrid arch) | sft-axolotl (--conda_env sft-axolotl) |
| Flag-off contract | — | --sft_backend llamafactory (default) is byte-identical to before the backend existed |
Universal launch shape (fill per cluster from ops/<cluster>/ops.md §SFT):
python -m hpc.launch --job_type sft [--sft_backend axolotl] \
--train_config_path sft/<lf_configs|axolotl_configs>/<cfg>.yaml \
--num_nodes N --gpus_per_node <4|1> --time_limit <cluster max> \
--dataset <hf-dataset> --role_tag role --user_tag user --assistant_tag assistant --content_tag content \
--hub_model_id laion/<name> [--conda_env <env>]
Always --dry_run the first cell and inspect model, template, epochs, LR, role tags, push_to_hub, and
output_dir in <exp>/configs/*_train_config.yaml.
2. Backend: axolotl specifics
- aarch64 clusters (TACC Vista / Jupiter GH200): use SDPA. Set
attn_implementation: sdpaand installtorchao==0.17.0without dependencies insft-axolotl. - The launcher rebuilds the dataset block from flags. Pass
--datasetand schema flags at launch; hand-authoreddatasets:applies only to directaxolotl.cli.preprocess. - On internet-node clusters, set
WANDB_MODE=disabled— the launcher setsreport_to=wandb; wandb 0.28.x crashes on the compute-node service socket (WANDB_MODE=disabledmakes it a no-op; loss still logs totrainer_state.json). - Precision:
pure_bf16: true(fp32-master OOMs an 8B on 96 GiB). - Validated on TACC Vista (Stage 3 smoke, Stage 4 footgun-through-launcher, delphi
masking canary). Full backend gotcha list →
.agents/projects/axolotl/axolotl.md.
3. Delphi model SFT (both backends)
Delphi checkpoints use the Llama-3 tokenizer with reasoning/tool tokens. Both backends require:
- Prep the tokenizer FIRST (single-token delphi specials):
python sft/delphi/prepare_delphi_tokenizer.py --model <ckpt> --output <dir>(reserved-slot rename + mean-init →<|start_think|>/<|end_think|>/<|tool_call|>/<|tool_result|>become single tokens). Launch with--model_path <dir>. - Use a Llama-3-family template, NEVER
qwen3. Thedelphitemplate = Llama-3 header/turn format (<|start_header_id|>…<|eot_id|>, EOS<|eot_id|>)- the reasoning/tool tokens.
qwen3(ChatML<|im_start|>) would shred every example. This template×tokenizer mismatch is the #1 silent ruin —--dry_run - eyeball the first rendered example of EACH source (an instruction turn AND a
<think>warmup example) before launching.
- the reasoning/tool tokens.
- Datasets registered in
sft/delphi/dataset_info.json(per-dataset schema tags — the instruction sets use heterogeneous ShareGPT schemas). Launch with--dataset_dir sft/delphiand the 90/10 mix:--dataset <instr>,delphi_warmup --mix_strategy interleave_under --interleave_probs 0.9,0.1. - Axolotl delphi path = jinja-as-ground-truth (train == serve). Config:
chat_template: delphi+tokenizer_save_jinja_files: false+ thetemplate_integrityplugin (embeds the chat_template intotokenizer_config.json, covering the per-checkpoint dirs the flag ignores). Validate the loss mask withaxolotl.cli.preprocess <cfg> --debug(assistant +<|start_think|>…<|end_think|>trained, user/system masked, 0Last turn is not trainableskips). Canary:sft/axolotl_configs/delphi_canary.yaml(validated, TACC job 802053). - The Delphi RL-scaling-laws grid (#6279) is HF-upload ONLY —
enable_db_registration: false; do NOT runmanual_db_push.py. LR = shared conventional SFT LR (2e-5) across all cells for comparability.
4. Config maps (cluster-agnostic)
- Qwen3-8B (
sft/lf_configs/qwen3/+…/extra/):32k_base.yaml(default 32k thinking),32k_base_nothink.yaml,131k_base.yaml;extra/32k_base_bs96.yaml(node-scaling, §4b),extra/32k_base_bs96_opt1k.yaml(small <1k-row: 7ep/lr4e-5),extra/32k_base_bs96_opt100k.yaml(large ≈11k+: 5ep/lr4e-5), plus other sizes/coder. - Qwen3-32B (
…/32k_base_32b*.yaml): DeepSpeed ZeRO-3, writes shardedglobal_stepN/, NOT root safetensors → launch WITHOUT--hub_model_id, then consolidate → upload (§6 +ops/<cluster>/ops.md §SFT). - Qwen3.5 hybrid (9B/27B) (
sft/lf_configs/qwen3_5/*.yaml): GDN+Attention arch not in transformers 4.x → needs thesft-qwen35env (transformers ≥5.3) +DISABLE_VERSION_CHECK=1. 9B → root safetensors (SKIP consolidate, like 8B); 27B → 32B consolidate flow. Copypreprocessor_config.jsonfrom base into the ckpt before upload (LF doesn't emit it; vLLM needs it). - axolotl (
sft/axolotl_configs/):smoke.yaml,parity_llama3.yaml,delphi_canary.yaml,marin/delphi_all3.yaml(all-3-plugins). aarch64 → SDPA.
4b. Node-scaling — the bs96 configs
bs96 fixes global_batch_size: 96 and derives gradient_accumulation_steps = 96 / (num_nodes*gpus).
Keep 96 % (num_nodes*gpus_per_node) == 0.
5. Dataset mixing & the parse-tags rule
--datasetis repeatable. Concatenate:--dataset A --dataset B --mix_strategy concat. Interleave:--mix_strategy interleave_under|interleave_over --interleave_probs 0.7,0.3(weights in dataset order).- Role tags are mandatory for Harbor/DCAgent datasets:
--role_tag role --user_tag user --assistant_tag assistant --content_tag content. Older ShareGPT usesfrom/human/gpt/value. - Mixed-schema MIXES: a single global
--role_tagsilently yields 0 assistant turns on the mismatched source. Register per-datasetcolumns/tagsin adataset_info.jsonand launch with--dataset_dir <registry>(how the Delphi mix works — §3).
6. Cleanup — recognize the path, then follow the cluster's §SFT
After training, check the checkpoint root:
ls $CHECKPOINTS_DIR/<job>/ | grep -E 'safetensors|global_step':
model-*.safetensorsat root → 8B path (also Qwen3.5-9B): drop intermediatecheckpoint-*+.cache, upload, DB-register.global_stepN/+zero_to_fp32.py, no root safetensors → 32B path (ZeRO-3 shards): consolidate first (--job_type consolidate), then upload fromfinal_repo/.
DB registration is a manual cleanup step via scripts/database/manual_db_push.py. HF uploads default public to laion/.
Per-series no-DB exception: HF-upload-only series (e.g. Delphi #6279,
enable_db_registration: false) SKIP manual_db_push.py. The mechanics (which
node uploads, tunnels, cert) are cluster-specific → ops/<cluster>/ops.md §SFT.
Live status: tail the .out for {'loss':…, 'grad_norm':…} step lines
(trainer_log.jsonl is unreliable mid-run).
7. Common traps (all clusters)
AF_UNIX path too longat dataset tokenization — the HF-datasetsSyncManagerbinds a socket under$TMPDIR(108-bytesun_pathcap). The launcher redirects TMPDIR to a short/tmp/sft_<job>for BOTH backends; if you still see it: confirm the rendered sbatch's_TMPROOT/TMPDIRis short, orexport SFT_KEEP_TMPDIR_LOCAL=1before launch.overwrite_output_dirrejected by HfArgumentParser (transformers v5) — the launcher strips this launcher-only key from the LF config before write.grep -c overwrite_output_dir <exp>/configs/*_train_config.yamlmust print0. The--overwrite_output_dir trueCLI flag still works (⊥--max_restarts).- Multi-node "24h timeout" that never checkpointed — usually the per-node
HF-datasets cache RACE, not slow tokenization (~65s). Fix: a config with
data_shared_file_system: true(global barrier; same tokens/loss). Diagnose in order: dsfs → schema-keyKeyError→ only then suspect genuinely-slow tokenization (--pretokenize). Details inops/leonardo/ops.md §SFT. - axolotl multi-node bring-up dies with
OSError [Errno 37] No locks available(ENOLCK) /[Errno 116] Stale file handle(ESTALE) during dataset load (masquerades as aC10d RendezvousConnectionErrorin the log tail — that's teardown noise; the real error is upstream,datasets/builder.py:821FileLock and/or the axolotlFileLockLoaderatutils/data/lock.py).data_shared_file_system:truedoes NOT save axolotl (axolotl's lock.py always locks). Fixes:- Interim (big-node-local-/tmp clusters, e.g. TACC Vista gh=261G): route ALL
per-rank WRITE caches node-local —
export SFT_KEEP_TMPDIR_LOCAL=1(the sbatch write-cache guard pointsHF_DATASETS_CACHE/TRITON_CACHE_DIR/TORCHINDUCTOR/RAY/TMPDIR/XDGat/tmp/otsft_$JOBID, per-node) +dataset_prepared_path: /tmp/...in the axolotl config; keepHF_HUB_CACHEshared+populated (pre-download once) so node-local arrow builds read cached parquet (each rank builds uncontended). - Durable (portable, incl. small-/tmp clusters): pretokenize-once into a SHARED
dataset_prepared_path+ a lock-free persistent-sentinel fast-path in axolotllock.py. Full saga:agent_logs/2026-07-08_sft-815251-c10d-rendezvous-fail.md.
- Interim (big-node-local-/tmp clusters, e.g. TACC Vista gh=261G): route ALL
per-rank WRITE caches node-local —
- Template × tokenizer mismatch — §3; the top silent ruin for delphi/Llama-3-family models.
8. Per-cluster particulars — READ before launching
| Cluster | Env | Wall | ops §SFT |
|---|---|---|---|
| JSC Jupiter (GH200, 4/node, aarch64) | otagent / sft-qwen35 / sft-axolotl | 12h booster (11:59:00) | .agents/ops/jupiter/ops.md §SFT |
| CINECA Leonardo (A100-64GB, 4/node, no-internet-compute) | otagent / sft-qwen35 | 24h (23:59:00) | .agents/ops/leonardo/ops.md §SFT |
| TACC Vista (GH200, aarch64) — axolotl path | sft-axolotl / otagent | per-partition | .agents/ops/tacc/ops.md |
Each ops §SFT has the required preamble, paths, QOS/account rules, post-patches, offline handling, and upload mechanics.
Signals
- GitHub stars
- 289
- Forks
- 40
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
sft-launch- Source
- github.com/open-thoughts/openthoughts-agent