manifest: {
id: "train";
jobs: readonly [
{ gpu: "exclusive"; isolation: "process"; kind: "finetune" },
];
panel: {
entry: "@mlx-bun/module-train/panel";
framed: false;
path: "/finetune";
tag: "mlx-train-panel";
title: "Fine-tune";
};
requires: readonly ["jobs", "storage", "catalog"];
routes: readonly [
{
id: "inspect-dataset";
method: "POST";
mount: "root";
path: "/api/finetune/inspect-dataset";
response: "json";
summary: "Count a dataset directory's train and validation rows and detect its format before submitting";
},
{
id: "submit";
method: "POST";
mount: "root";
path: "/api/finetune/submit";
response: "json";
summary: "Queue a fine-tune job (SFT, DPO or ORPO); answers with the job id and the adapter directory";
},
];
storage: readonly [
{
key: "adapters";
kind: "directory";
path: "adapters";
purpose: "Fine-tuned adapters: one directory per run, with checkpoints and metrics";
},
{
key: "models";
kind: "directory";
path: "models";
purpose: "Fused models and trained drafters: plain model directories, indexed beside the hub cache";
},
{
key: "datasets";
kind: "directory";
path: "datasets";
purpose: "Drafter regeneration shards, one directory per target model";
},
];
summary: "Fine-tune LoRA adapters (SFT, DPO, ORPO), fuse them into a model and train speculative-decoding drafters: the managed finetune job, its web routes, and the train, train-watch, fuse and draft verbs.";
title: "Train";
verbs: readonly [
{
name: "train";
options: readonly [
{
name: "query";
summary: "Model to fine-tune when no positional query is supplied (auto-picks the default model if omitted)";
type: "string";
},
{
name: "data";
summary: "Dataset dir with train.jsonl (+ optional valid.jsonl); rows are {prompt, chosen, rejected} for dpo/orpo, {messages|text} for sft (required)";
type: "string";
},
{
name: "method";
summary: "sft | dpo | orpo [default: orpo]";
type: "string";
},
{
name: "adapter";
summary: "Output adapter dir [default: ~/.mlx-bun/adapters/<method>-<model>]";
type: "string";
},
{
name: "iters";
summary: "Training iterations [default: 100]";
type: "string";
},
{
name: "lr";
summary: "Learning rate [default: orpo 1e-5 · dpo 5e-5 · sft 2e-4]";
type: "string";
},
{
name: "rank";
summary: "LoRA rank [default: orpo 16 · else 8]";
type: "string";
},
{
name: "scale";
summary: "LoRA scale [default: orpo 2.0 · else 1.0]";
type: "string";
},
{
name: "seq";
summary: "Max sequence length [default: the model's own; 4096 when it declares none]";
type: "string";
},
{ name: "batch"; summary: "Batch size [default: 1]"; type: "string" },
{
name: "grad-accum";
summary: "Gradient accumulation steps (effective batch = batch × grad-accum at batch-size-1 memory) [default: 1]";
type: "string";
},
{
name: "grad-clip";
summary: "Gradient-norm clip (0 = off) [default: 1.0]";
type: "string";
},
{
name: "seed";
summary: "Data-shuffle / init seed [default: 0]";
type: "string";
},
{
name: "val-size";
summary: "Max validation examples per eval [default: 256]";
type: "string";
},
{
name: "lambda";
summary: "ORPO odds-ratio weight [default: 0.1]";
type: "string";
},
{
name: "sft-scope";
summary: "ORPO chosen-NLL scope: full (paper/TRL-faithful, prompt+response) | response (pre-2026-07 runs, bit-exact) [default: full]";
type: "string";
},
{
name: "seg";
summary: "Layers per segment (segmented backward; orpo default 2)";
type: "string";
},
{
name: "save-every";
summary: "Crash-safe mountable checkpoint every n steps";
type: "string";
},
{
name: "resume";
summary: "Warm-start LoRA weights from a checkpoint/adapter dir";
type: "string";
},
{
name: "no-flash";
summary: "Disable the flash-CCE Metal head (use the MLX fused head)";
type: "boolean";
},
{
name: "no-prefix";
summary: "Disable prefix-sharing (two-forward branches)";
type: "boolean";
},
{
name: "no-segment";
summary: "Disable the segmented backward (hold all activations)";
type: "boolean";
},
{
name: "num-layers";
summary: "Layers to adapt, counted from the last; -1 = all [default: -1]";
type: "string";
},
{
name: "steps-per-report";
summary: "Steps between loss reports [default: 1]";
type: "string";
},
{
name: "steps-per-eval";
summary: "Steps between validations [default: every --save-every steps, else never]";
type: "string";
},
{
name: "dropout";
summary: "LoRA dropout [default: 0]";
type: "string";
},
{
name: "weight-decay";
summary: "AdamW weight decay [default: 0.01]";
type: "string";
},
{
name: "grad-checkpoint";
summary: "Recompute activations in the backward pass to save memory";
type: "boolean";
},
{
name: "dry-run";
summary: "Inspect the dataset + print the resolved plan, don't train";
type: "boolean";
},
];
positional: readonly [
{
name: "model";
summary: "The model to fine-tune: a local path or a downloaded model; the automatic choice when omitted";
},
];
summary: "Fine-tune a LoRA adapter on your data (sft | dpo | orpo)";
},
{
details: "Produce the drafters `mlx-bun serve --draft-model` mounts. A DSpark drafter is\ntrained against ONE frozen target, in three stages, each a subcommand:\n\n regen <model> --topics <file> The target answers each topic with its own\n greedy generation; one tapped forward per\n sequence records the drafter's context\n hiddens (shards under ~/.mlx-bun/datasets)\n train <model> --data <shards> Train the drafter on the shards; the best\n held-out τ is saved as a directory in\n ~/.mlx-bun/models that serve mounts\n calibrate <model> --drafter <dir> --data <prompts.jsonl>\n Fit per-position confidence thresholds by\n speculative runs; written into dspark.json\n so serving prunes positions the head cannot call\n quantize <drafter-dir> Affine-quantize a released DeepSpec\n (Gemma4DSparkModel) drafter; its\n confidence_head stays bf16\n\nThe target and `--tap-layers` MUST be the same for regen and train. Drafter\nnumerics only move acceptance, never correctness (the target verifies every\ndraft): gate a drafter, or a quantized one, with `bun scripts/drafter-ab.ts`.\nStages run in the foreground on the GPU; do not run one while a server or\nanother training run holds it.";
name: "draft";
options: readonly [
{
name: "model";
summary: "Target model (regen, train, calibrate) or DeepSpec drafter directory (quantize); the option spelling of the second positional";
type: "string";
},
{
name: "topics";
summary: "regen: text file with one topic per line the target writes an article about (required)";
type: "string";
},
{
name: "data";
summary: "train: shard directory from regen (required) · calibrate: JSONL of {prompt} rows, a string or chat messages (required)";
type: "string";
},
{
name: "out";
summary: "Output directory [default: regen ~/.mlx-bun/datasets/dspark-<model> · train ~/.mlx-bun/models/<model>-dspark · quantize ~/.mlx-bun/models/<drafter>-affine-q<bits>-g<group> · calibrate: in place]";
type: "string";
},
{
name: "tap-layers";
summary: "regen/train: target layers the drafter reads, comma separated, the last normally the layer count (post-final-norm). MUST match between regen and train [default: 20,31,41,42 (gemma-4 e4b)]";
type: "string";
},
{
name: "max-resp";
summary: "regen: generated tokens per topic [default: 320]";
type: "string";
},
{
name: "seqs-per-shard";
summary: "regen: sequences per shard file [default: 32]";
type: "string";
},
{
name: "min-resp";
summary: "regen: drop responses shorter than this [default: 6]";
type: "string";
},
{
name: "iters";
summary: "train: optimizer steps [default: 6000]";
type: "string";
},
{
name: "batch";
summary: "train: anchors per step [default: 8]";
type: "string";
},
{
name: "gamma";
summary: "train: draft block length [default: 5]";
type: "string";
},
{
name: "max-ctx";
summary: "train: cap on the prefix context each anchor attends [default: 512]";
type: "string";
},
{
name: "lr";
summary: "train: peak learning rate [default: 1.5e-3]";
type: "string";
},
{
name: "warmup";
summary: "train: warmup steps [default: 150]";
type: "string";
},
{
name: "eval-every";
summary: "train: steps between held-out evaluations; the best held-out τ is what is saved [default: 500]";
type: "string";
},
{
name: "eval-anchors";
summary: "train: anchors per held-out evaluation [default: 256]";
type: "string";
},
{
name: "seed";
summary: "train: init and sampling seed [default: 0]";
type: "string";
},
{
name: "d-draft";
summary: "train: drafter width [default: 1024]";
type: "string";
},
{
name: "n-heads";
summary: "train: drafter attention heads [default: 8]";
type: "string";
},
{
name: "layers";
summary: "train: drafter backbone depth [default: 5]";
type: "string";
},
{
name: "markov-rank";
summary: "train: sequential-head rank [default: 256]";
type: "string";
},
{
name: "seq-head";
summary: "train: sequential head, markov (Eq 5) | rnn (Eq 6) [default: markov]";
type: "string";
},
{
name: "resume";
summary: "train: warm-start weights from an existing checkpoint in --out (the optimizer and schedule restart)";
type: "boolean";
},
{
name: "drafter";
summary: "calibrate: the trained drafter directory whose dspark.json receives the thresholds (required)";
type: "string";
},
{
name: "n";
summary: "calibrate: prompts to run [default: 32]";
type: "string";
},
{
name: "max-tokens";
summary: "calibrate: generated tokens per prompt [default: 128]";
type: "string";
},
{
name: "precision";
summary: "calibrate: acceptance precision each kept position must reach, in (0, 1] [default: 0.5]";
type: "string";
},
{
name: "min-samples";
summary: "calibrate: positions with fewer observations are never pruned [default: 50]";
type: "string";
},
{
name: "force";
summary: "calibrate: recalibrate a drafter that already carries thresholds";
type: "boolean";
},
{
name: "bits";
summary: "quantize: 4 | 8 [default: 4]";
type: "string";
},
{
name: "group-size";
summary: "quantize: 32 | 64 [default: 64]";
type: "string";
},
];
positional: readonly [
{
name: "action";
required: true;
summary: "regen, train, calibrate or quantize";
},
{
name: "model";
summary: "The target model (regen, train, calibrate) or the DeepSpec drafter directory (quantize); --model is the option spelling";
},
];
summary: "Produce a speculative-decoding drafter: regen | train | calibrate | quantize";
usage: "usage: mlx-bun draft <regen|train|calibrate|quantize> <model|drafter-dir> [options]";
},
{
name: "train-watch";
options: readonly [
{
name: "adapter";
summary: "Adapter directory to watch; accepted for the positional [default: the latest run in ~/.mlx-bun/adapters]";
type: "string";
},
];
positional: readonly [
{
name: "adapter-dir";
summary: "The adapter directory to watch; the latest run when omitted";
},
];
summary: "Live dashboard for a training run (tails <adapter-dir>/metrics.jsonl)";
},
{
name: "fuse";
options: readonly [
{
name: "model";
summary: "Base model (registry query or a snapshot path); the mlx_lm.fuse spelling of the positional";
type: "string";
},
{
name: "adapter";
summary: "Adapter directory (adapters.safetensors + adapter_config.json) [default: adapters]";
type: "string";
},
{
name: "adapter-path";
summary: "mlx_lm.fuse alias for --adapter";
type: "string";
},
{
name: "save-path";
summary: "Output model directory [default: ~/.mlx-bun/models/<model>-fused]";
type: "string";
},
{
name: "dequantize";
summary: "Write dense weights instead of the base's quantized layout (drops the config's quantization block)";
type: "boolean";
},
{
name: "export-gguf";
summary: "Not supported (mlx_lm.fuse flag); the command exits with an error";
type: "boolean";
},
{
name: "gguf-path";
summary: "Not supported (mlx_lm.fuse flag); the command exits with an error";
type: "string";
},
{
name: "upload-repo";
summary: "Push the fused model to this Hugging Face repo afterwards (write token checked first)";
type: "string";
},
];
positional: readonly [
{
name: "model";
summary: "The base model: a local path or a downloaded model";
},
];
summary: "Merge a LoRA adapter into the base weights (writes a standalone snapshot)";
},
];
} = ...
Type Declaration
-
Readonlyid: "train"
-
Readonlyjobs: readonly [{ gpu: "exclusive"; isolation: "process"; kind: "finetune" }]
-
Readonlypanel: {
entry: "@mlx-bun/module-train/panel";
framed: false;
path: "/finetune";
tag: "mlx-train-panel";
title: "Fine-tune";
}
-
Readonlyrequires: readonly ["jobs", "storage", "catalog"]
-
Readonlyroutes: readonly [
{
id: "inspect-dataset";
method: "POST";
mount: "root";
path: "/api/finetune/inspect-dataset";
response: "json";
summary: "Count a dataset directory's train and validation rows and detect its format before submitting";
},
{
id: "submit";
method: "POST";
mount: "root";
path: "/api/finetune/submit";
response: "json";
summary: "Queue a fine-tune job (SFT, DPO or ORPO); answers with the job id and the adapter directory";
},
]
-
Readonlystorage: readonly [
{
key: "adapters";
kind: "directory";
path: "adapters";
purpose: "Fine-tuned adapters: one directory per run, with checkpoints and metrics";
},
{
key: "models";
kind: "directory";
path: "models";
purpose: "Fused models and trained drafters: plain model directories, indexed beside the hub cache";
},
{
key: "datasets";
kind: "directory";
path: "datasets";
purpose: "Drafter regeneration shards, one directory per target model";
},
]
-
Readonlysummary: "Fine-tune LoRA adapters (SFT, DPO, ORPO), fuse them into a model and train speculative-decoding drafters: the managed finetune job, its web routes, and the train, train-watch, fuse and draft verbs."
-
Readonlytitle: "Train"
-
Readonlyverbs: readonly [
{
name: "train";
options: readonly [
{
name: "query";
summary: "Model to fine-tune when no positional query is supplied (auto-picks the default model if omitted)";
type: "string";
},
{
name: "data";
summary: "Dataset dir with train.jsonl (+ optional valid.jsonl); rows are {prompt, chosen, rejected} for dpo/orpo, {messages|text} for sft (required)";
type: "string";
},
{
name: "method";
summary: "sft | dpo | orpo [default: orpo]";
type: "string";
},
{
name: "adapter";
summary: "Output adapter dir [default: ~/.mlx-bun/adapters/<method>-<model>]";
type: "string";
},
{
name: "iters";
summary: "Training iterations [default: 100]";
type: "string";
},
{
name: "lr";
summary: "Learning rate [default: orpo 1e-5 · dpo 5e-5 · sft 2e-4]";
type: "string";
},
{
name: "rank";
summary: "LoRA rank [default: orpo 16 · else 8]";
type: "string";
},
{
name: "scale";
summary: "LoRA scale [default: orpo 2.0 · else 1.0]";
type: "string";
},
{
name: "seq";
summary: "Max sequence length [default: the model's own; 4096 when it declares none]";
type: "string";
},
{ name: "batch"; summary: "Batch size [default: 1]"; type: "string" },
{
name: "grad-accum";
summary: "Gradient accumulation steps (effective batch = batch × grad-accum at batch-size-1 memory) [default: 1]";
type: "string";
},
{
name: "grad-clip";
summary: "Gradient-norm clip (0 = off) [default: 1.0]";
type: "string";
},
{
name: "seed";
summary: "Data-shuffle / init seed [default: 0]";
type: "string";
},
{
name: "val-size";
summary: "Max validation examples per eval [default: 256]";
type: "string";
},
{
name: "lambda";
summary: "ORPO odds-ratio weight [default: 0.1]";
type: "string";
},
{
name: "sft-scope";
summary: "ORPO chosen-NLL scope: full (paper/TRL-faithful, prompt+response) | response (pre-2026-07 runs, bit-exact) [default: full]";
type: "string";
},
{
name: "seg";
summary: "Layers per segment (segmented backward; orpo default 2)";
type: "string";
},
{
name: "save-every";
summary: "Crash-safe mountable checkpoint every n steps";
type: "string";
},
{
name: "resume";
summary: "Warm-start LoRA weights from a checkpoint/adapter dir";
type: "string";
},
{
name: "no-flash";
summary: "Disable the flash-CCE Metal head (use the MLX fused head)";
type: "boolean";
},
{
name: "no-prefix";
summary: "Disable prefix-sharing (two-forward branches)";
type: "boolean";
},
{
name: "no-segment";
summary: "Disable the segmented backward (hold all activations)";
type: "boolean";
},
{
name: "num-layers";
summary: "Layers to adapt, counted from the last; -1 = all [default: -1]";
type: "string";
},
{
name: "steps-per-report";
summary: "Steps between loss reports [default: 1]";
type: "string";
},
{
name: "steps-per-eval";
summary: "Steps between validations [default: every --save-every steps, else never]";
type: "string";
},
{
name: "dropout";
summary: "LoRA dropout [default: 0]";
type: "string";
},
{
name: "weight-decay";
summary: "AdamW weight decay [default: 0.01]";
type: "string";
},
{
name: "grad-checkpoint";
summary: "Recompute activations in the backward pass to save memory";
type: "boolean";
},
{
name: "dry-run";
summary: "Inspect the dataset + print the resolved plan, don't train";
type: "boolean";
},
];
positional: readonly [
{
name: "model";
summary: "The model to fine-tune: a local path or a downloaded model; the automatic choice when omitted";
},
];
summary: "Fine-tune a LoRA adapter on your data (sft | dpo | orpo)";
},
{
details: "Produce the drafters `mlx-bun serve --draft-model` mounts. A DSpark drafter is\ntrained against ONE frozen target, in three stages, each a subcommand:\n\n regen <model> --topics <file> The target answers each topic with its own\n greedy generation; one tapped forward per\n sequence records the drafter's context\n hiddens (shards under ~/.mlx-bun/datasets)\n train <model> --data <shards> Train the drafter on the shards; the best\n held-out τ is saved as a directory in\n ~/.mlx-bun/models that serve mounts\n calibrate <model> --drafter <dir> --data <prompts.jsonl>\n Fit per-position confidence thresholds by\n speculative runs; written into dspark.json\n so serving prunes positions the head cannot call\n quantize <drafter-dir> Affine-quantize a released DeepSpec\n (Gemma4DSparkModel) drafter; its\n confidence_head stays bf16\n\nThe target and `--tap-layers` MUST be the same for regen and train. Drafter\nnumerics only move acceptance, never correctness (the target verifies every\ndraft): gate a drafter, or a quantized one, with `bun scripts/drafter-ab.ts`.\nStages run in the foreground on the GPU; do not run one while a server or\nanother training run holds it.";
name: "draft";
options: readonly [
{
name: "model";
summary: "Target model (regen, train, calibrate) or DeepSpec drafter directory (quantize); the option spelling of the second positional";
type: "string";
},
{
name: "topics";
summary: "regen: text file with one topic per line the target writes an article about (required)";
type: "string";
},
{
name: "data";
summary: "train: shard directory from regen (required) · calibrate: JSONL of {prompt} rows, a string or chat messages (required)";
type: "string";
},
{
name: "out";
summary: "Output directory [default: regen ~/.mlx-bun/datasets/dspark-<model> · train ~/.mlx-bun/models/<model>-dspark · quantize ~/.mlx-bun/models/<drafter>-affine-q<bits>-g<group> · calibrate: in place]";
type: "string";
},
{
name: "tap-layers";
summary: "regen/train: target layers the drafter reads, comma separated, the last normally the layer count (post-final-norm). MUST match between regen and train [default: 20,31,41,42 (gemma-4 e4b)]";
type: "string";
},
{
name: "max-resp";
summary: "regen: generated tokens per topic [default: 320]";
type: "string";
},
{
name: "seqs-per-shard";
summary: "regen: sequences per shard file [default: 32]";
type: "string";
},
{
name: "min-resp";
summary: "regen: drop responses shorter than this [default: 6]";
type: "string";
},
{
name: "iters";
summary: "train: optimizer steps [default: 6000]";
type: "string";
},
{
name: "batch";
summary: "train: anchors per step [default: 8]";
type: "string";
},
{
name: "gamma";
summary: "train: draft block length [default: 5]";
type: "string";
},
{
name: "max-ctx";
summary: "train: cap on the prefix context each anchor attends [default: 512]";
type: "string";
},
{
name: "lr";
summary: "train: peak learning rate [default: 1.5e-3]";
type: "string";
},
{
name: "warmup";
summary: "train: warmup steps [default: 150]";
type: "string";
},
{
name: "eval-every";
summary: "train: steps between held-out evaluations; the best held-out τ is what is saved [default: 500]";
type: "string";
},
{
name: "eval-anchors";
summary: "train: anchors per held-out evaluation [default: 256]";
type: "string";
},
{
name: "seed";
summary: "train: init and sampling seed [default: 0]";
type: "string";
},
{
name: "d-draft";
summary: "train: drafter width [default: 1024]";
type: "string";
},
{
name: "n-heads";
summary: "train: drafter attention heads [default: 8]";
type: "string";
},
{
name: "layers";
summary: "train: drafter backbone depth [default: 5]";
type: "string";
},
{
name: "markov-rank";
summary: "train: sequential-head rank [default: 256]";
type: "string";
},
{
name: "seq-head";
summary: "train: sequential head, markov (Eq 5) | rnn (Eq 6) [default: markov]";
type: "string";
},
{
name: "resume";
summary: "train: warm-start weights from an existing checkpoint in --out (the optimizer and schedule restart)";
type: "boolean";
},
{
name: "drafter";
summary: "calibrate: the trained drafter directory whose dspark.json receives the thresholds (required)";
type: "string";
},
{
name: "n";
summary: "calibrate: prompts to run [default: 32]";
type: "string";
},
{
name: "max-tokens";
summary: "calibrate: generated tokens per prompt [default: 128]";
type: "string";
},
{
name: "precision";
summary: "calibrate: acceptance precision each kept position must reach, in (0, 1] [default: 0.5]";
type: "string";
},
{
name: "min-samples";
summary: "calibrate: positions with fewer observations are never pruned [default: 50]";
type: "string";
},
{
name: "force";
summary: "calibrate: recalibrate a drafter that already carries thresholds";
type: "boolean";
},
{
name: "bits";
summary: "quantize: 4 | 8 [default: 4]";
type: "string";
},
{
name: "group-size";
summary: "quantize: 32 | 64 [default: 64]";
type: "string";
},
];
positional: readonly [
{
name: "action";
required: true;
summary: "regen, train, calibrate or quantize";
},
{
name: "model";
summary: "The target model (regen, train, calibrate) or the DeepSpec drafter directory (quantize); --model is the option spelling";
},
];
summary: "Produce a speculative-decoding drafter: regen | train | calibrate | quantize";
usage: "usage: mlx-bun draft <regen|train|calibrate|quantize> <model|drafter-dir> [options]";
},
{
name: "train-watch";
options: readonly [
{
name: "adapter";
summary: "Adapter directory to watch; accepted for the positional [default: the latest run in ~/.mlx-bun/adapters]";
type: "string";
},
];
positional: readonly [
{
name: "adapter-dir";
summary: "The adapter directory to watch; the latest run when omitted";
},
];
summary: "Live dashboard for a training run (tails <adapter-dir>/metrics.jsonl)";
},
{
name: "fuse";
options: readonly [
{
name: "model";
summary: "Base model (registry query or a snapshot path); the mlx_lm.fuse spelling of the positional";
type: "string";
},
{
name: "adapter";
summary: "Adapter directory (adapters.safetensors + adapter_config.json) [default: adapters]";
type: "string";
},
{
name: "adapter-path";
summary: "mlx_lm.fuse alias for --adapter";
type: "string";
},
{
name: "save-path";
summary: "Output model directory [default: ~/.mlx-bun/models/<model>-fused]";
type: "string";
},
{
name: "dequantize";
summary: "Write dense weights instead of the base's quantized layout (drops the config's quantization block)";
type: "boolean";
},
{
name: "export-gguf";
summary: "Not supported (mlx_lm.fuse flag); the command exits with an error";
type: "boolean";
},
{
name: "gguf-path";
summary: "Not supported (mlx_lm.fuse flag); the command exits with an error";
type: "string";
},
{
name: "upload-repo";
summary: "Push the fused model to this Hugging Face repo afterwards (write token checked first)";
type: "string";
},
];
positional: readonly [
{
name: "model";
summary: "The base model: a local path or a downloaded model";
},
];
summary: "Merge a LoRA adapter into the base weights (writes a standalone snapshot)";
},
]