Model Export
Export fine-tuned models to various formats for deployment.
Merge LoRA Adapter
Merge a LoRA adapter with its base model into a standalone model:
# Auto-detect base model from adapter_config.json
soup merge --adapter ./output --output ./merged
# Specify base model and dtype
soup merge --adapter ./output --base meta-llama/Llama-3.1-8B --dtype bfloat16--save-format 4bit writes a single bitsandbytes 4-bit merged checkpoint without a dequantize, merge, requantize cycle. --save-format 4bit_forced quantizes every linear module, including lm_head; through v0.75.2 transformers' default skip list left lm_head in full precision, so the "forced" checkpoint was not fully forced. A model with tied embeddings (tie_word_embeddings=True) cannot hold a 4-bit lm_head that shares the embedding weights, so 4bit_forced refuses it by name once the fp16 merge has been staged and before the 4-bit reload (Merge failed: ..., exit 1); use --save-format 4bit for those (#1606).
GGUF (llama.cpp / Ollama)
# Export LoRA adapter (auto-merges with base, then converts)
soup export --model ./output --format gguf --quant q4_k_m
# Different quantizations
soup export --model ./output --format gguf --quant q8_0
soup export --model ./output --format gguf --quant f16
# Export a full (already merged) model
soup export --model ./merged --format ggufSupported quantizations: q4_0, q4_k_m, q5_k_m, q8_0, f16, f32
Without --output, the file is named <model-name>.<quant>.gguf and written next to the model directory, not inside it: --model ./output with --quant q4_k_m produces ./output.q4_k_m.gguf. Pass --output ./my-model.q4_k_m.gguf to choose the name. Since v0.76.0 the directory that will hold the output is created up front; through v0.75.2 a quantized export into a directory that did not exist yet crashed after the adapter had been merged, and the cleanup deleted the merged model (#1445).
Use with Ollama:
echo 'FROM ./output.q4_k_m.gguf' > Modelfile
ollama create my-model -f Modelfile
ollama run my-modelONNX
pip install "soup-cli[onnx]"
soup export --model ./output --format onnx
soup export --model ./output --format onnx --output ./model_onnxTensorRT-LLM
pip install "soup-cli[tensorrt]"
soup export --model ./output --format tensorrt
soup export --model ./output --format tensorrt --output ./model_trtThis path now checks itself before it does any work, and on a current TensorRT-LLM it stops there (v0.74.0). Soup shelled out to
python -m tensorrt_llm.commands.convert_checkpoint, a module absent from every current TensorRT-LLM release: that package ships onlybench,build,eval,prune,refitandserve, and checkpoint conversion now lives as a per-architectureexamples/<arch>/convert_checkpoint.pyscript in NVIDIA's source tree instead. Before v0.74.0 the export silently produced zero artifact bytes; it now fails immediately with a message naming the missing entry point, before the LoRA merge and the output directory are touched. If you exported to TensorRT on an earlier release, check the directory actually holds an engine. Installingtensorrt_llmcan also downgrade a training environment'storch,transformers,numpyanddatasetspins, so keep it out of the environment you train in.
TorchAO post-training quantization
pip install "soup-cli[qat]"
soup export --model ./output --format torchao --quant-config quant.yaml--quant-config is required with this format (without it the command exits 2). The YAML names one scheme in a top-level scheme: key and, for the schemes that take one, a group_size:
scheme: Int4WeightOnly
group_size: 128The four schemes are Int4WeightOnly, Int8DynActInt4, Float8DynActFloat8 and NVFP4. The names are Soup's own and are case-sensitive; each is mapped to the torchao class that implements it. group_size is accepted for Int4WeightOnly and Int8DynActInt4, and the other two take no extra keys. Any other key is refused with the allowed list. The output goes to <model-name>.torchao.<scheme> next to the model unless you pass --output.
The [qat] extra now requires torchao>=0.18.0 (it was 0.4.0), because 0.18.0 is the release the class locations were verified against; an older torchao is no longer accepted by the extra. Through v0.75.2, three of the four schemes (Int8DynActInt4, Float8DynActFloat8 and NVFP4) asked torchao for classes it does not export, so they could not build a config at all, and the refusal told you to upgrade torchao when nothing newer existed (#826). Two of the four classes (Int8DynActInt4 and NVFP4) currently live in torchao's prototype namespace, which torchao does not promise to keep stable; Soup tries the public path first, and if a torchao release moves a class the error names every path it tried. inner_k_tiles was removed from the Int4WeightOnly keys because torchao's config raises TypeError for it. The quantization step itself needs transformers and torchao and was not run for this page; the class mapping, floor and keys are read from the v0.76.0 code, and the flag handling (exit 2 without --quant-config, the scheme-name check) was run; the key allowlist is read from the code, because it runs after transformers is imported.
AWQ Quantization (v0.23.0+)
Deprecated in v0.76.0; upstream says removal comes in v0.77.
soup export --format awqand--format gptq, andsoup quantize --to awq|gptq(which only prints thesoup exportcommand to run), still work as before. Both upstream projects, AutoAWQ and AutoGPTQ, are archived. Upstream's documentation says neither the[awq]nor the[gptq]extra can be installed next to[train], that AWQ export was measured working only withtransformers4.52.4 or older while[train]needs 5.16.1 or newer, and thatauto-gptqpublishes no Python 3.12 wheel, so on a normal install the two formats could not be reached; run them from a separate environment. The command now begins with a yellowDeprecated:line, before anything can fail on the missing extra:--format awq is deprecated and will be removed in Soup v0.77: AutoAWQ is archived upstream and the awq extra cannot be installed next to the train extra. Use --format gguf, onnx, tensorrt, bitnet or tq1_0 instead.The alternatives aregguf,onnx,tensorrt,bitnetandtq1_0. Training LoRA on top of an already-AWQ or GPTQ-quantized base (training.quantization: awq | gptq) is a different feature and is not covered by this notice.
pip install "soup-cli[awq]"
soup export --model ./output --format awq --calibration-data calib.jsonl
soup export --model ./output --format awq --calibration-data calib.jsonl --output ./model_awqGPTQ Quantization (v0.23.0+)
Deprecated in v0.76.0; upstream says removal comes in v0.77. See the notice under AWQ above: it applies to
--format gptqandsoup quantize --to gptqin the same way, withAutoGPTQin the message. Move togguf,onnx,tensorrt,bitnetortq1_0.
pip install "soup-cli[gptq]"
soup export --model ./output --format gptq --calibration-data calib.jsonl
soup export --model ./output --format gptq --calibration-data calib.jsonl --output ./model_gptq--bits (4 or 8, default 4) and --group-size (default 128) set the AWQ and GPTQ quantization width, and they are what the output shard name records.
--calibration-data is required for BOTH formats, and it arrived as a breaking change in each. GPTQ first, in v0.74.0: the flagless form used to be accepted and then died inside auto-gptq with object is not iterable, because quantize() wants tokenized examples and was handed a bare tokenizer. AWQ followed in v0.75.0, for the opposite reason: AWQ did have a fallback, and that was the problem. A flagless AWQ export silently downloaded AutoAWQ's large default calibration dataset, so the calibration inputs behind a shipped artifact were whatever that download happened to contain. Both formats now refuse a missing or unusable JSONL before the quantizer is imported and before the model is loaded, so the refusal costs nothing. A file with zero usable samples is refused up front too, and --calibration-samples caps how many rows are read (default 128).
The same release repaired the exported directory. save_quantized writes its own gptq_model-<bits>bit-<group>g.safetensors shard, which AutoModelForCausalLM.from_pretrained does not look for, so a GPTQ export written before v0.74.0 does not reload by the standard path. The directory now also carries a standard model.safetensors.
Deploy to Ollama (v0.18.0+)
Deploy a GGUF model directly to your local Ollama instance:
# Deploy a GGUF model
soup deploy ollama --model ./output.q4_k_m.gguf --name soup-my-model
# Deploy with system prompt and parameters
soup deploy ollama --model ./model.gguf --name soup-chat \
--system "You are a helpful assistant." \
--template chatml \
--parameter temperature=0.7 \
--parameter top_p=0.9
# Export + deploy in one command
soup export --model ./output --format gguf --deploy ollama
# List Soup-deployed models
soup deploy ollama --list
# Remove a model
soup deploy ollama --remove soup-my-modelAuto-detected chat templates: chatml, llama, mistral, vicuna, zephyr (or auto to infer from soup.yaml).
soup export --deploy is for the GGUF family only. --deploy ollama works with gguf, gguf-ud, bitnet and tq1_0 (the last three since v0.76.0; they are described under Quant Menu II and Modality II). For onnx, tensorrt, awq, gptq and torchao it fails before any export work, with exit 1 and --deploy is not supported for format '<fmt>', because Ollama cannot load those files. Any target other than ollama is refused as well, also with exit 1.
--registry-id records the export, whatever the format. soup export --registry-id <id> attaches the exported artifacts to a local registry entry; without it Soup tries to match the entry from the source model's output directory. Since v0.76.0 this works for every format (gguf, gguf-ud, bitnet, tq1_0, onnx, tensorrt, awq, gptq, torchao); through v0.75.2 only the plain gguf path recorded anything and the flag was accepted and ignored for the others (#1679). When an export produces a directory (multi-shard safetensors, ONNX data files, TensorRT engine files in <output>/engine/*.engine) every produced weight and engine file is attached, and the success line names them. Registration never decides the exit code: with no matching entry and no --registry-id nothing is recorded and nothing is printed, and a failed attach prints a yellow Could not attach export to registry warning while the command still exits 0, because the export itself succeeded.
Naming and --list. soup deploy ollama needs both --model and --name and uses the name exactly as you give it. soup export --deploy ollama names the Ollama model soup-<model-name> unless you pass --deploy-name. --list is a filter over ollama list on the soup- name prefix (a name:tag form counts when its name part starts with soup-), so a model deployed with --name my-model exists in Ollama but does not appear in soup deploy ollama --list. --remove <name> has no such filter and removes any Ollama model by name, asking for confirmation unless you pass --yes.
Push to HuggingFace Hub
soup push --model ./output --repo your-username/my-model
soup push --model ./output --repo your-username/my-model --privateAuto-generates a model card with training details.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.