coach (0.1.0)
Installation
[registry]
default = "forgejo"
[registries.forgejo]
index = "sparse+ " # Sparse index
# index = " " # Git
[net]
git-fetch-with-cli = truecargo add coach@0.1.0About this package
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 loginto the registry); it's cached after that. - Inference: the onnxruntime shared library at run time —
ortis load-dynamic, found viaORT_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 |