Long Haul by TEI: simple local CLI for preparing and training MLX fine-tunes.
Project description
Long Haul by TEI
longhaul is the CLI for Long Haul by TEI. It prepares datasets, compiles MLX-ready training files, and runs small fine-tunes on Apple Silicon.
What v0.1 does
- Validates a simple structured JSONL dataset format.
- Resolves tool definitions from a shared catalog.
- Compiles canonical records into MLX-compatible
chatortoolsJSONL. - Applies Qwen-specific
conservativeandexperttraining presets. - Runs one local MLX job at a time with resumable artifacts and reports.
- Uses deterministic auto-splitting with validation/test fallback rules that work on small datasets.
Install
From PyPI:
python3 -m pip install longhaul
python3 -m pip install "longhaul[train]"
From a local checkout:
python3 -m pip install ".[train]"
That installs the longhaul CLI plus MLX training dependencies.
If you only want dataset validation and compilation:
python3 -m pip install .
Quick start
longhaul init --template chat
longhaul validate job.yaml
longhaul compile -c job.yaml
longhaul train -c job.yaml
Use --template tools if you want a starter tool-calling dataset instead of plain chat.
GGUF export
longhaul can export a completed MLX LoRA job to GGUF for llama.cpp and other GGUF runtimes.
Requirements:
- A
llama.cppcheckout withconvert_hf_to_gguf.py - A built
llama-quantize(orquantize) binary - Either
outputs.gguf.llama_cpp_dirinjob.yamlorLLAMA_CPP_DIRin the environment
Manual export:
longhaul export /path/to/job
longhaul export /path/to/job --quant q8 --quant q4_k_m --quant bf16
Default aliases:
q8->Q8_0q4->Q4_K_M
Recommended config snippet:
{
"outputs": {
"jobs_dir": "jobs",
"sample_prompts": [
"Summarize the assistant behavior you were trained for.",
"Respond to a user request in the target style."
],
"gguf": {
"enabled": true,
"quants": ["q8", "q4"],
"base_outtype": "f16",
"llama_cpp_dir": "../llama.cpp"
}
}
}
When outputs.gguf.enabled is true, longhaul train and longhaul resume automatically export GGUF artifacts after evaluation finishes. Exported files are written under jobs/<job>/exports/gguf/, and the job report includes the generated GGUF paths.
Included examples
See the examples guide:
Release
This repo includes GitHub Actions Trusted Publishing in
.github/workflows/publish.yml.
To publish:
- Create the
longhaulproject on PyPI if it does not already exist. - In PyPI project settings, add this GitHub repo as a Trusted Publisher.
- Create a GitHub release, or run the publish workflow manually.
Prepared subset example
examples/glm5-plain-100/ contains a real plain-chat subset prepared from the Hugging Face dataset Jackrong/GLM-5.1-Reasoning-1M-Cleaned.
- The subset uses 100 examples total with explicit
90/5/5train/valid/test splits. <think>...</think>reasoning blocks are stripped before training.- The prep script filters out oversized records to keep the subset suitable for a small Qwen 2.5 1.5B run.
To regenerate that subset locally:
PYTHONPATH=src python3.10 scripts/prepare_glm5_reasoning_subset.py \
--count 100 \
--max-estimated-tokens 1800 \
--output-dir examples/glm5-plain-100/data
Canonical dataset format
Each line is one JSON object:
{
"messages": [
{ "type": "message", "role": "user", "content": "Create PLAN.md" },
{
"type": "tool_call",
"name": "write",
"arguments": {
"path": "PLAN.md",
"content": "# Plan\n..."
}
},
{
"type": "tool_result",
"tool_call_id": "call_abc123",
"name": "write",
"content": "Wrote PLAN.md",
"is_error": false
},
{
"type": "message",
"role": "assistant",
"content": "The plan has been saved."
}
],
"tools": ["write"]
}
Assistant reasoning can be stored separately:
{
"type": "message",
"role": "assistant",
"thinking": "First I should write the file, then confirm it.",
"content": "The file is ready."
}
By default, thinking is omitted from the compiled training set.
Profiles
conservativeis the default and keeps the LoRA config intentionally small.expertraises the default LoRA rank, effective batch size, and trainable layers. It is still overrideable per field injob.yaml.
Auto split behavior
- Single-file datasets are split deterministically.
- Datasets with fewer than 10 records use train+valid only by default.
- Once the dataset reaches 10 or more records, Long Haul creates train, valid, and test splits.
- If no test split exists,
longhaul evalfalls back to the validation split instead of silently doing nothing.
Artifact policy
Commit source files and example datasets. Do not commit generated run artifacts.
Ignored by default:
jobs/examples/*/jobs/.smoke/.longhaul-validate/__pycache__/*.egg-info/
Notes
- The generated
job.yamltemplate is JSON text with a.yamlextension. JSON is valid YAML, which keeps the bootstrap dependency-free. - v0.1 validates a small allowlist of Qwen model IDs rather than claiming blanket support.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file longhaul-0.1.0.tar.gz.
File metadata
- Download URL: longhaul-0.1.0.tar.gz
- Upload date:
- Size: 32.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14e65ec53964d520aed52cb818eaeecdb893741d64db83fcb3c8f8b8bc9db2f2
|
|
| MD5 |
f1d0c3f3f575c91b24c2c0f727c7d769
|
|
| BLAKE2b-256 |
c59ff1abbbfe56401ebe64cf8fd3dc7b8194fe96b78bc1b1d5cf0456e1e39d0e
|
Provenance
The following attestation bundles were made for longhaul-0.1.0.tar.gz:
Publisher:
publish.yml on owenqwenstarsky/longhaul
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
longhaul-0.1.0.tar.gz -
Subject digest:
14e65ec53964d520aed52cb818eaeecdb893741d64db83fcb3c8f8b8bc9db2f2 - Sigstore transparency entry: 1436428019
- Sigstore integration time:
-
Permalink:
owenqwenstarsky/longhaul@b701a1e6444b64d9e56475db39459e64627cea11 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/owenqwenstarsky
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b701a1e6444b64d9e56475db39459e64627cea11 -
Trigger Event:
push
-
Statement type:
File details
Details for the file longhaul-0.1.0-py3-none-any.whl.
File metadata
- Download URL: longhaul-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f0208102cd92ff5959c87e6f8b2e118b83f136d59585ff77cf639086ab6d1bc
|
|
| MD5 |
bef9c648235ab62deaa6b9d225e7a25c
|
|
| BLAKE2b-256 |
f12e7075c15603ba1561300eb2de4190ca44dc8487b3c048c44d857ad2121aa0
|
Provenance
The following attestation bundles were made for longhaul-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on owenqwenstarsky/longhaul
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
longhaul-0.1.0-py3-none-any.whl -
Subject digest:
1f0208102cd92ff5959c87e6f8b2e118b83f136d59585ff77cf639086ab6d1bc - Sigstore transparency entry: 1436428056
- Sigstore integration time:
-
Permalink:
owenqwenstarsky/longhaul@b701a1e6444b64d9e56475db39459e64627cea11 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/owenqwenstarsky
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b701a1e6444b64d9e56475db39459e64627cea11 -
Trigger Event:
push
-
Statement type: