ster tune merge
Fold a LoRA adapter into the base weights and write a standalone checkpoint directory that ster --model, and every other tool, accepts unchanged.
Invocation and purpose#
ster tune merge --model <MODEL> --adapter <ADAPTER> --output <OUTPUT>
[--revision <REVISION>] [--device <DEVICE>]An adapter is the right shape while it is being trained and while it is one of several a caller might swap between, and the wrong shape once it is finished and permanent: it costs two extra matmuls per adapted projection per token forever, and it means the model cannot be handed to anything that does not know what a Ster artifact is. Merging computes W + (alpha / rank) * B @ A once and writes the result as an ordinary checkpoint.
The output is deliberately not a Ster format. It is model.safetensors beside the source's own config.json and tokenizer.json, which is precisely what --model accepts and what every other tool in the ecosystem accepts too. A merge that produced something only Ster could read would have converted a portable adapter into an unportable model.
ster tune merge --model ster-toy-model --adapter calm-dpo.safetensors \
--output ./calm-mergedOn the toy checkpoint that run reports 8 merged tensors, 31 copied, 39 in total, 156608 parameters, a scale of 2.0 and a dtype of F32. The merged directory then generates character-identical greedy output to the base checkpoint with the same adapter attached, while differing from the unadapted base.
Inputs and options#
| Input | Required | Meaning |
|---|---|---|
--model <MODEL> | yes | Hugging Face model id or local model directory. |
--adapter <ADAPTER> | yes | The adapter to fold in. It must have been trained for this exact model and must be a generation adapter rather than a reward model. |
--output <OUTPUT> | yes | Directory to write. It is created if missing and receives model.safetensors, config.json and tokenizer.json. |
--revision <REVISION> | no | Immutable Hugging Face revision; Hub resolution defaults to main. |
--device <DEVICE> | no | cpu (default), metal, or cuda. Metal and CUDA are build-time features. |
No decoder is built. Merging rewrites tensors and never runs the model, so it resolves the same files through the same Hub path and the same architecture refusal and maps nothing. --device is accepted for consistency with every other model-loading command; the arithmetic happens on the CPU either way, because nothing here is a size a matmul accelerator would help with and the result goes straight to disk.
The delta is accumulated in F32 and cast back to whatever the source weight was, so a BF16 checkpoint merges to a BF16 checkpoint of the same size. Accumulating in the source dtype instead would round twice and, at BF16's eight bits of mantissa, would quietly discard small updates entirely.
The config and the tokenizer are copied rather than regenerated. A merged checkpoint that tokenized differently from the model it was merged from would be a different model wearing its name.
Output and state#
Writes a directory, not an artifact pair. model.safetensors holds every tensor of the source checkpoint with the adapted projections replaced; config.json and tokenizer.json are byte copies of the source's own. The source checkpoint and the adapter are both untouched.
Stdout is a pretty JSON document with a single report key — there is no path, because the answer is a directory and its contents. The report carries model, model_revision, adapter, output, rank, alpha, scale, targets, layers, hidden_size, merged_tensors, copied_tensors, total_tensors, parameters, dtype, and files.
reading 1 weight file(s) from ster-toy-model
merged 8 projections at scale 2
wrote model.safetensors, config.json, tokenizer.json to ./calm-mergedRefusals#
- A reward artifact:
adapter artifact is a reward model, not a generation adapter. Baking a reward model's adapters into a checkpoint and dropping its head would produce a model that generates, trained by an objective that never asked it to. - An adapter for another checkpoint:
adapter was trained for model "…", current model is "…". - A width mismatch:
adapter width 64 does not match model width 2048. - A layer the model does not have:
layer 9 is outside the model's 0..3 range. - A missing adapter:
failed to read adapter <path>, and a weights file with no sidecar beside it names both paths. - A sharded source whose shards disagree:
model weight model.layers.0.self_attn.q_proj.weight appears in more than one weight file. Silently keeping the last one would merge half a model. - A projection the checkpoint does not carry:
checkpoint has no tensor model.layers.0.self_attn.q_proj.weight to merge into. - The architecture refusal applies even though no decoder is built, because a merge that produced a directory
ster --modelthen rejects would be worse than no merge.