coach (0.1.0)

Published 2026-06-15 23:37:37 +00:00 by madsludvig

Installation

[registry]
default = "forgejo"

[registries.forgejo]
index = "sparse+" # Sparse index
# index = "" # Git

[net]
git-fetch-with-cli = true
cargo add coach@0.1.0

About this package

Run model training in isolated, resource-limited, reproducible containers, streaming typed JSONL progress. A registry of model recipes (SAM3 LoRA included).

coach

A small Rust library for training and running ML models in containers.

You define a model once — its dataset format, its training image, and how to run inference on the trained ONNX. coach runs the training in a podman container and streams its progress, then loads the result into an ONNX Runtime session for inference. It's a library used from another Rust app, not a CLI or a service.

How it works

Training runs in a container, one per run. coach turns a TrainingJob into a podman run, streams the container's stdout as typed JSON events, and collects the artifacts from the run's work dir.

Inference runs in-process: load the trained model.onnx into an ONNX Runtime session and call it. No container, no Python.

A model is a type that implements Trainable (how to train it) and Inferable (how to run it). Sam3 implements both. A trained model is just a run of a recipe — different datasets and hyperparameters are different Params, not new images.

Example

use coach::{Trainer, RunContext, Gpus, Dataset, CocoRoboflow};
use coach::models::sam3::{Sam3, Sam3Params, Sam3Event, LoraPreset};
use futures::StreamExt;

let trainer = Trainer::new();
let dataset = CocoRoboflow::validate("/data/datasets/ds-3".as_ref())?;

// Train. Progress streams as events; artifacts land in the work dir.
let ctx = RunContext::new("/data/runs/run-42", "coach-sam3-42").gpus(Gpus::All);
let mut events = Box::pin(trainer.train::<Sam3>(&dataset, ctx, Sam3Params {
    checkpoint: None, num_epochs: 20, batch_size: 2, learning_rate: 1e-4,
    lora_rank: 8, lora_alpha: 16, lora_preset: LoraPreset::Light, early_stop_patience: 0,
}));
while let Some(event) = events.next().await {
    if let Sam3Event::Step(s) = event? {
        println!("step {} loss {}", s.step, s.loss);
    }
}
// -> model.onnx, model.onnx_data, model.pt, checkpoint.pt, tokenizer.json

// Infer. Load once, run per input.
use coach::Inferable;
use coach::models::sam3::ImagePrompt;
let mut engine = Sam3::load("/data/runs/run-42".as_ref())?;
let detections = engine.infer(ImagePrompt {
    image, prompt: "scratch".into(), min_confidence: 0.5, max_detections: 100,
})?;

Adding a model

Each model architecture has a recipe in src/models/ and a container image in images/. Implement Trainable for training and Inferable for inference (a model usually does both — see src/models/sam3.rs). The dataset format is a Dataset impl in src/dataset/; the app produces a directory in that format and validates it.

The image bakes a Python venv with uv, so a training run needs no network or dependency resolution. To pin a run reproducibly, set the model's image constant (SAM3_IMAGE) to a pushed digest.

just build-sam3    # build the image
just push-sam3     # push it to the registry
just digest-sam3   # the digest to pin in SAM3_IMAGE
just fetch-base    # download the base checkpoint (to bake or mount)

Requirements

  • Training: podman, plus the NVIDIA container toolkit for GPUs. The host needs to pull the image once (podman login to the registry); it's cached after that.
  • Inference: the onnxruntime shared library at run time — ort is load-dynamic, found via ORT_DYLIB_PATH. Nothing onnxruntime is needed to build, and it's only loaded when you actually run inference.

Dependencies

ID Version
async-stream ^0.3.6
fast_image_resize ^5
futures ^0.3.31
image ^0.25
ndarray ^0.17
ort ^2.0.0-rc.12
serde ^1.0.228
serde_json ^1.0.140
thiserror ^2.0.18
tokenizers ^0.21
tokio ^1.52.3
tracing ^0.1.41
Details
Cargo
2026-06-15 23:37:37 +00:00
1
1.3 MiB
Assets (1)
Versions (1) View all
0.1.0 2026-06-15