---
schema_version: "1.0"
kind: library
entrypoints: [crates/aprender-contracts-cli]
contract_count: 1837
---
<p align="center">
<img src="docs/hero.svg" alt="aprender — Next-generation ML framework in pure Rust" width="100%">
</p>
<p align="center">
<a href="https://crates.io/crates/aprender">
<img src="https://img.shields.io/crates/v/aprender.svg" alt="crates.io">
</a>
<a href="https://docs.rs/aprender">
<img src="https://docs.rs/aprender/badge.svg" alt="docs.rs">
</a>
<a href="https://github.com/paiml/aprender/actions/workflows/ci.yml">
<img src="https://github.com/paiml/aprender/actions/workflows/ci.yml/badge.svg" alt="CI">
</a>
<a href="LICENSE">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
</a>
</p>
## Quick Start
Pre-built and ready to use (Linux x86_64/aarch64 only today — other platforms fall
through to `cargo install` below):
```text
To read the script before it runs, download it with `curl -LsSf https://paiml.com/apr/install.sh -o install.sh`, review it, then run `sh install.sh`. The installer is POSIX `sh` and checks the downloaded release archive against its published `.sha256` before installing.
Compile and build:
```text
cargo install aprender # CPU ONLY - no GPU backend is compiled in
cargo install aprender --features cuda # NVIDIA GPU acceleration
cargo install aprender --features full # everything (training, visualization, zram)
apr pull hf://Qwen/Qwen2.5-Coder-0.5B-Instruct
apr list
apr run ~/.cache/pacha/models/1e3be9285aea6923.safetensors "2+2?"
```
full log of output
<details>
```text
apr pull hf://Qwen/Qwen2.5-Coder-0.5B-Instruct
== APR Pull ===
Model: hf://Qwen/Qwen2.5-Coder-0.5B-Instruct/model.safetensors
Downloading (streaming)...
10% 20% 30% 40% 50% 60% 70% 80% 90% 100%
✓ Downloaded successfully
Path: ~/.cache/pacha/models/1e3be9285aea6923.safetensors
Size: 942.3 MB
...
Usage:
apr run ~/.cache/pacha/models/1e3be9285aea6923.safetensors
apr serve ~/.cache/pacha/models/1e3be9285aea6923.safetensors
apr list
=== Cached Models ===
NAME SIZE FORMAT PATH
--------------------------------------------------------------------------------------------------------
1e3be9285aea6923 942.3 MB SafeTensors ~/.cache/pacha/models/1e3be9285aea6923.safetensors (orphan)
Total: 1 models (0 tracked + 1 orphans), 942.3 MB used
apr run ~/.cache/pacha/models/1e3be9285aea6923.safetensors "what is 2+2?"
=== APR Run ===
Source: ~/.cache/pacha/models/1e3be9285aea6923.safetensors
[GH-189] Loaded tokenizer from ~/.cache/pacha/models/1e3be9285aea6923.tokenizer.json: 22 special tokens
Output:
2 + 2 equals 4.
Completed in 4.82s (cached)
apr run ~/.cache/pacha/models/1e3be9285aea6923.safetensors "can you help me summarize quick sort algorithm, in less than 100 words"
=== APR Run ===
Source: ~/.cache/pacha/models/1e3be9285aea6923.safetensors
[GH-189] Loaded tokenizer from ~/.cache/pacha/models/1e3be9285aea6923.tokenizer.json: 22 special tokens
Output:
Quick sort is a divide-and-conquer algorithm that sorts an array of elements. It works by selecting a 'pivot' element from the array and partitioning the
Completed in 7.03s (cached)
$
```
</details>
For release notes see [GitHub Releases](https://github.com/paiml/aprender/releases).
## What is Aprender?
A complete ML framework in pure Rust. One `cargo install`, one `apr` binary,
the full model lifecycle — inference, training, quantization, profiling,
publishing — all backed by YAML provable contracts that fail CI on drift.
### At HEAD
| Workspace crates | **81** workspace crates | `cargo metadata --no-deps` (NOT `ls crates/` — 4 are `exclude`d, 1 has no Cargo.toml) |
| Provable contracts | **2005** provable contracts | `contracts/census.json` `.n_files` — the set `pv lint` walks (`pv census`, ONT-001 ONT-1; regenerated by `make contracts`, written by `make readme-sync`, guarded by `scripts/check_readme_claims.sh`) |
| CLI commands | **113** CLI commands | `contracts/apr-cli-commands-v1.yaml` §`commands` (parse the list; `apr --help` also lists `help` itself, and `grep -c '^ - name:'` gives 119 — other same-indent `name:` keys exist in the file) |
| Book CLI chapters | **114** chapters | `ls book/src/cli/*.md` |
| Book lib chapters | **73** chapters | `ls book/src/lib/*.md` (parity with `pub mod`) |
These numbers are enforced by [`contracts/readme-claims-v1.yaml`](contracts/readme-claims-v1.yaml).
Drift between this table and live repo state fails `bash scripts/check_readme_claims.sh`
→ see [FALSIFY-README-001..006](contracts/readme-claims-v1.yaml).
### Verified this release
Verified matrix for **0.69.1**, from the ladder receipts:
A `pass` cell means `apr run` completed on that host without falling back and its golden output matched. It does **not** mean `chat`, `code` and `serve` passed: the ladder records those verbs but its `green` does not read them.
A non-`pass` cell names the gates the receipt records, which is not always the cause: a reason `apr qa` computed in the same run is not carried here unless the receipt accounts for it (`qa_rc` is the tell).
| `qwen2-1.5b-q4km` | pass | pass |
| `qwen3-1.7b-q4km` | pass | pass |
| `qwen3-8b-q4km` | pass | pass |
| `qwen35-0.8b-q4km` | pass | pass |
| `qwen35-2b-q4km` | pass | pass |
| `qwen35-4b-q4km` | pass | pass |
| `qwen35-9b-q4km` | pass | pass |
| `qwen35-27b-q4km` | **FAIL** | pass |
| `inv:Qwen3-30B-A3B-Instruct-2507-Q4_K_M.gguf` | **FAIL** | **FAIL** |
| `inv:Qwen3-Coder-30B-A3B-Instruct-Q4_K_M.gguf` | **FAIL** | **FAIL** |
| `inv:qwen2.5-coder-0.5b-instruct-q4_k_m.gguf` | pass | pass |
| `inv:qwen2.5-coder-1.5b-instruct-fp16.apr` | pass | — |
| `inv:qwen2.5-coder-1.5b-instruct-q4_k_m.apr` | pass | — |
| `inv:qwen2.5-coder-1.5b-instruct-q4_k_m.gguf` | pass | pass |
| `inv:qwen2.5-coder-1.5b-instruct-q4k.apr` | pass | pass |
| `inv:qwen2.5-coder-32b-instruct-q4_k_m.gguf` | pass | — |
| `inv:qwen2.5-coder-7b-instruct-q4_k_m.gguf` | pass | pass |
| `inv:Qwen2.5-0.5B-Instruct-IQ3_M.gguf` | — | pass |
| `inv:Qwen2.5-0.5B-Instruct-IQ4_XS.gguf` | — | pass |
| `inv:Qwen2.5-0.5B-Instruct-f16.gguf` | — | **FAIL** |
| `inv:Qwen3.5-0.8B-IQ4_XS.gguf` | — | **FAIL** |
| `inv:Qwen3.5-0.8B-UD-IQ2_XXS.gguf` | — | **FAIL** |
| `inv:Qwen3.5-35B-A3B-UD-IQ4_XS.gguf` | — | **FAIL** |
| `inv:Qwen3.5-4B-UD-Q4_K_XL.gguf` | — | pass |
| `inv:qwen2.5-coder-0.5b-instruct.apr` | — | **FAIL** |
| `inv:qwen2.5-coder-1.5b-instruct-q4k-v2.apr` | — | pass |
| `inv:qwen2.5-coder-1.5b-instruct-st.apr` | — | pass |
| `inv:qwen2.5-coder-1.5b-q4k.apr` | — | pass |
| `inv:tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf` | — | pass |
- **gx10**: 17 rung(s), 17 required, 3 not green — `apr 0.69.1 (9b7739951)`
- **lambda**: 26 rung(s), 26 required, 7 not green — `apr 0.69.1 (9b7739951)`
> **Known issue — Qwen3 MoE (30B-A3B) on GB10: not claimed for 0.70; planned for 0.70.1 (#3715).** Every verb
> times out at 600 s on the 20k-token rung on GB10 (sm_121), so qwen3moe on GB10 is withdrawn from this
> release's claims (`cells.declaimed`).
> **Known issue — GB10 dense models: prompt processing unbatched; planned for 0.70.1 (#4590).** Output is correct; only prefill speed is affected. On GB10 (sm_121) every dense
> model (Qwen2.5, Qwen3) is withdrawn from this release's claims (`cells.declaimed` in
> [`contracts/model-capability-ladder-v1.yaml`](contracts/model-capability-ladder-v1.yaml)); Qwen3.5 on
> GB10 and every model on other hosts are still claimed and gated.
### Command surface
| **Inference** | `apr run`, `apr chat`, `apr serve` |
| **Training** | `apr finetune`, `apr train`, `apr pretrain`, `apr distill` |
| **Model ops** | `apr convert`, `apr quantize`, `apr merge`, `apr export`, `apr compile` |
| **Inspection** | `apr inspect`, `apr validate`, `apr tensors`, `apr diff`, `apr trace`, `apr lint` |
| **Profiling** | `apr profile`, `apr bench`, `apr qa` |
| **Registry** | `apr pull`, `apr list`, `apr rm`, `apr publish`, `apr registry` |
| **GPU** | `apr gpu`, `apr parity`, `apr ptx` |
| **Observability** | `apr tui`, `apr monitor`, `apr cbtop` |
## Cookbook
End-to-end recipes (data prep → train → quantize → publish → serve) live in
[`paiml/apr-cookbook`](https://github.com/paiml/apr-cookbook) — **1,825** worked examples (apr-cookbook, 2026-09-12),
with local `book/src/` walkthroughs. The count is the
`[[example]]` entries the cookbook declares, pinned and gated by
[`contracts/readme-claims-v1.yaml`](contracts/readme-claims-v1.yaml) — it lives in another
repository, so it carries the date it was measured rather than pretending to be live.
```text
git clone https://github.com/paiml/apr-cookbook
cd apr-cookbook
cargo run --example bundle_static_model # any example
mdbook serve book # walkthrough docs
```
## Install
```text
cargo install aprender # installs the `apr` binary
apr --version
```
## A Qwen story
Eight beats, one narrative, every core command group. Anchored on the Qwen
series so the story scales from a 494-MB safetensors model to a 30 B-parameter
MoE GGUF. Every beat is a falsifier in
[`contracts/qwen-story-v1.yaml`](contracts/qwen-story-v1.yaml); the runnable
form is [`scripts/qwen-story.sh`](scripts/qwen-story.sh); nightly cron is
[`.github/workflows/qwen-story-daily.yml`](.github/workflows/qwen-story-daily.yml);
the dogfood gate is `/apr-dogfood` Gate 18.
```text
# Reproduce locally (uses ~/models cache; ~3-5 min on RTX 4090):
bash scripts/qwen-story.sh
```
### Beat 1 — Discover (Registry)
```text
apr pull hf://Qwen/Qwen2.5-Coder-0.5B-Instruct # 494 MB safetensors
apr list # confirm cached
```
### Beat 2 — Trust (QA gates)
```text
apr qa qwen2.5-coder-1.5b-instruct-q4k # 12 falsifiable gates
apr validate qwen2.5-coder-1.5b-instruct-q4k --quality # scores the checks that ran
apr lint qwen2.5-coder-1.5b-instruct-q4k # best-practice signals
```
### Beat 3 — Explore (Inspection)
```text
apr inspect --json qwen2.5-coder-1.5b-instruct-q4k # arch, params, tensors
apr tensors --json qwen2.5-coder-1.5b-instruct-q4k # 339 tensors with shapes
apr tree qwen2.5-coder-1.5b-instruct-q4k # layer architecture
```
### Beat 4 — Adapt (Model ops)
```text
apr export qwen2.5-coder-1.5b-instruct-q4k --format gguf -o roundtrip.gguf
apr diff qwen2.5-coder-1.5b-instruct-q4k roundtrip.gguf # tensor-by-tensor delta
apr convert model.safetensors --quantize q4_k -o quantized.apr
```
### Beat 5 — Use (Inference)
```text
apr run qwen2.5-coder-1.5b-instruct-q4k "fn sum(a: i32, b: i32) -> i32 {" --max-tokens 16
apr chat qwen2.5-coder-1.5b-instruct-q4k # interactive REPL
apr code -p "review this Python function" --max-turns 1 # agent mode (PMAT-182)
```
### Beat 6 — Serve (REST API)
```text
apr serve run qwen2.5-coder-1.5b-instruct-q4k --port 8080
curl -s localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"qwen","messages":[{"role":"user","content":"What is 2+2?"}],"max_tokens":8}'
# → {"choices":[{"message":{"content":"2 + 2 equals 4."}}],...}
```
### Beat 7 — Operate (Profiling)
```text
apr profile qwen2.5-coder-7b-instruct-q4_k_m # Roofline analysis
apr gpu --json # VRAM, sm_*, cuda version
apr serve plan qwen2.5-coder-7b-instruct-q4_k_m # capacity plan before run
```
### Beat 8 — Scale (MoE introspection)
```text
apr inspect --json Qwen3-Coder-30B-A3B-Instruct # arch=qwen3moe, 30 B params
apr tensors --json Qwen3-Coder-30B-A3B-Instruct # 579 tensors (MoE expert layout)
```
> **0.69.1 does not support `qwen3moe` on CUDA.** Mixture-of-experts models
> (`Qwen3-30B-A3B-Instruct-2507`, `Qwen3-Coder-30B-A3B-Instruct`) run on the
> **CPU only**: `apr run <model>` works, and `apr run --gpu <model>` **refuses
> before loading**, naming the architecture and exiting 12 rather than
> generating on the CPU and reporting a fallback. The MoE GPU forward is
> [#3714](https://github.com/paiml/aprender/issues/3714) and lands in 0.70.0.
> Introspection above (`inspect`, `tensors`) is unaffected — it reads the
> header, not a forward.
### Publish (separate flow)
```text
# Publish a derived model to HuggingFace Hub (see SPEC-HF-PUBLISH-001 for the 12-file pipeline)
apr stamp ckpt.apr --tokenizer /path/to/qwen-tokenizer --license Apache-2.0 -o staging/model.apr
apr export staging/model.apr --format gguf --quantize int4 -o staging/model-q4k.gguf
apr publish staging/ paiml/my-model-v1 --library-name aprender --license Apache-2.0
```
When run with `PMAT_HUNT=1` (default), each beat emits a manifest of high-risk
untested code in the command modules it just exercised. A nightly cron opens an
issue when this manifest grows so untested branches in command handlers can't
accumulate quietly. See [`contracts/qwen-story-v1.yaml`](contracts/qwen-story-v1.yaml).
Publishing a model? See [SPEC-HF-PUBLISH-001](docs/specifications/aprender-train/model-hf-publish-pipeline-spec.md)
for the 12-file integration pipeline, three-path verification protocol, and HF API
gotchas (NDJSON commits, LFS batch sizing, Q4_K stride constraints).
## Library usage
```toml
[dependencies]
aprender = "0.35"
```
```rust
use aprender::linear_regression::LinearRegression;
use aprender::traits::Estimator;
let model = LinearRegression::new();
model.fit(&x_train, &y_train)?;
let predictions = model.predict(&x_test)?;
```
Algorithms: Linear/Logistic Regression, Decision Trees, Random Forest, GBM,
Naive Bayes, KNN, SVM, K-Means, PCA, ARIMA, ICA, GLMs, graph algorithms,
Bayesian inference, text + audio processing.
## Architecture
Monorepo, flat `crates/aprender-*` layout (same pattern as
[Polars](https://github.com/pola-rs/polars),
[Burn](https://github.com/tracel-ai/burn),
[Nushell](https://github.com/nushell/nushell)):
```
paiml/aprender/
├── Cargo.toml # Workspace root + `cargo install aprender`
├── crates/
│ ├── aprender-core/ # ML library (use aprender::*)
│ ├── apr-cli/ # CLI logic (count in the table above)
│ ├── aprender-compute/ # SIMD/GPU compute kernels
│ ├── aprender-gpu/ # CUDA PTX
│ ├── aprender-serve/ # Inference server
│ ├── aprender-train/ # Training loops
│ ├── aprender-orchestrate/ # Agents + RAG
│ ├── aprender-contracts/ # Provable contracts engine
│ ├── aprender-profile/ # Profiling
│ ├── aprender-db/ aprender-graph/ aprender-rag/
│ └── ... (**84** directories — NOT the crate count: see the table above)
├── contracts/ # provable YAML contracts (count in the table above)
└── book/ # mdBook documentation
```
## Performance
Every published throughput or comparator figure lives with the run that produced
it. The measurement ledger is [`evidence/parity/LEDGER.md`](evidence/parity/LEDGER.md);
the rules a figure must satisfy before it may be quoted anywhere — including on
this page — are in
[`docs/specifications/PP-LLAMA-001-MASTER.md`](docs/specifications/PP-LLAMA-001-MASTER.md).
This section carried a three-row table of `tok/s` figures until 2026-09-02. None
of the three cited a receipt, and the RTX 4090 row asserted a rate the tree's own
paired measurement contradicts, so all three are withdrawn rather than restated
(PP-LLAMA-001 §2.1, PP-12). A withdrawal that reprints the number leaves the
number on the page.
The harnesses that reproduce the comparisons, without their numbers:
[candle-vs-apr](https://github.com/paiml/candle-vs-apr) for inference and
[ground-truth-apr-ludwig](https://github.com/paiml/ground-truth-apr-ludwig) for
training. A figure from either belongs on this page only once it carries a
receipt in the ledger.
## Provable contracts
Every CLI command and kernel is bound to a YAML contract with equations,
preconditions, postconditions, and falsification tests:
```yaml
equations:
validate_exit_code:
formula: exit_code = if score < 50 then 5 else 0
invariants:
- score < 50 implies exit_code != 0
falsification_tests:
- id: FALSIFY-CLI-001
prediction: apr validate bad-model.apr exits non-zero
```
The tree carries <!-- CONTRACT_COUNT_START -->2005<!-- CONTRACT_COUNT_END --> contracts across inference, training, quantization, attention, FFN,
tokenization, model formats, CLI safety — and this README itself.
## Migration from old crates
| Old | New | Status |
|-----|-----|--------|
| `trueno = "0.18"` | `aprender-compute = "0.33"` | Shim available |
| `entrenar = "0.7"` | `aprender-train = "0.33"` | Shim available |
| `realizar = "0.8"` | `aprender-serve = "0.33"` | Shim available |
| `batuta = "0.7"` | `aprender-orchestrate = "0.33"` | Shim available |
Old repositories are archived. All development happens here.
## Contributing
```text
git clone https://github.com/paiml/aprender
cd aprender
cargo test --workspace --lib
cargo check --workspace
apr --help
```
The README's own gate — a merge-path CI step runs exactly this line, which is what lets this fence stay `bash`:
```bash
setsid --wait bash scripts/check_readme_claims.sh # README contract gate (as CI runs it)
```
## License
MIT