Train a model
Create an export and train a model in one call - detection, segmentation, keypoint, or classification, on a managed GPU.
A training run is always on an export, never on a dataset directly. Create the export, check what went into it, then train it. Nothing is created behind your back, and an export you can see is an export you can re-train from.
Source: resources/training.py
from pictograph import Client, TrainingRun
client = Client()
client.exports.create(
dataset_name="road-signs",
name="road-signs-v1",
format="pictograph",
include_images=True,
wait=True,
)
run: TrainingRun = client.training.create(
dataset_name="road-signs",
export_name="road-signs-v1",
pipeline_type="yolox",
name="road-signs-detector",
gpu_type="a10g",
config={"epochs": 50, "batch_size": 16},
)
if run.model_id:
client.models.download(
model_id=run.model_id,
output_path="./yolox.onnx",
)
With wait=True (the default) the call blocks until the run reaches a terminal status
and run.model_id is populated.
Signature
client.training.create(
dataset_name: str,
export_name: str,
*,
pipeline_type: PipelineType,
name: str,
config: dict[str, Any] | str | Path | None = None,
gpu_type: GpuType = "a10g",
gpu_count: int = 1,
version_of_model_id: str | None = None,
wait: bool = True,
poll_interval: float = 5.0,
timeout: float = 7200.0,
) -> TrainingRun
| Argument | Default | Purpose |
|---|---|---|
dataset_name |
required | Dataset the export belongs to |
export_name |
required | A completed export - training is always on an export |
pipeline_type |
required | Which pipeline to train |
name |
required | Run name |
config |
{} |
Hyperparameters (epochs, batch_size, learning_rate, image_size) |
gpu_type |
a10g |
a10g / a100 / h100 / auto |
gpu_count |
1 |
GPUs per run |
version_of_model_id |
None |
Train a new version of an existing model |
wait |
True |
Block until the run terminates |
poll_interval |
5.0 |
Seconds between polls |
timeout |
7200 |
Max seconds to wait (2 hours) |
Pipelines
pipeline_type |
Task | Best for |
|---|---|---|
yolox |
Object detection (boxes) | Speed, edge deployment, small datasets |
sm_pytorch |
Semantic segmentation | Pixel-wise class maps |
classification |
Image classification | Tag-style labels with no geometry |
rfdetr_detection |
Object detection | Higher mAP than YOLOX on harder data |
rfdetr_segmentation |
Instance segmentation (polygons + masks) | Best per-instance mask accuracy |
rfdetr_keypoint |
Keypoint / pose detection | A structured joint set per instance |
GPU tiers
gpu_type |
When |
|---|---|
a10g (default) |
YOLOX, classification, RF-DETR detection |
a100 |
Large RF-DETR, big batch sizes |
h100 |
Last resort, only when A100 runs out of memory |
auto |
Let the platform pick the cheapest tier the config fits |
How much data you need
There is no fixed image minimum. The matched images are split into train / val / test
with at least one image in train and one in val; below three images everything goes into
train. classification needs at least two classes; every other pipeline needs one.
The practical floor is data, not policy: a handful of images trains, but it will not generalise.
Async usage
Pass wait=False to fire-and-forget:
run = client.training.create(
dataset_name="road-signs",
export_name="road-signs-v1",
pipeline_type="yolox",
name="road-signs-detector",
wait=False,
)
print("queued:", run.id)
# Poll yourself later.
run = client.training.get(
run_id=run.id,
)
if run.status == "completed":
model = client.models.get(
model_id=run.model_id,
)
Hyperparameters
config keys are pipeline-specific. Common ones across pipelines:
| Key | Type | Typical |
|---|---|---|
epochs |
int | 30–100 |
batch_size |
int | 8 / 16 / 32 |
learning_rate |
float | 0.001–0.01 |
image_size |
int | 640 (YOLOX), 1024 (segmentation) |
Unsupported keys are ignored.
Keypoint (pose) training
rfdetr_keypoint trains on keypoint annotations grouped into objects: a set of
joints per object, not a box or a mask. Three things are worth knowing first.
Grouping is the supervision signal. RF-DETR Keypoint is query-based and top-down, so
it needs to know which points belong to the same object. That is what instance_id
carries; the editor assigns it per class as you draw, so a pose is simply every joint
sharing an instance_id. Points with no instance_id train as single-joint objects.
Name left and right explicitly. A joint’s class name travels into training, and
RF-DETR derives its horizontal-flip pairs by matching left_* to right_*. Without that
pairing, flip augmentation mirrors the image but keeps the joint identities, teaching the
model that a left wrist is a right one. Joint names with no counterpart map to identity.
Every object still needs a box. The box is derived from each instance’s placed joints automatically, so you never draw one. An object of a keypoint class with no joints placed yet still trains as a plain detection, with its keypoints masked out of the loss rather than dropped from the dataset.
run = client.training.create(
dataset_name="gymnasts",
export_name="gymnasts-v1",
pipeline_type="rfdetr_keypoint",
name="gymnast-pose",
gpu_type="a10g",
config={"epochs": 30},
)
print(run.metrics["OKS"]) # keypoint AP is measured over OKS, not box IoU
Predictions come back as keypoint annotations carrying instance_id, the same shape
the editor writes, so they render, export and re-open for correction like hand-drawn
poses. Accuracy is reported as OKS (object keypoint similarity) rather than mAP; the
two are not comparable, which is why they have separate names.
Errors
| Status | Exception | Cause |
|---|---|---|
| 404 | NotFoundError |
Dataset missing or has no status_filter-matching images |
| 422 | ValidationError |
Pipeline or GPU invalid, or too few classes for the pipeline |
| 402 | PaymentRequiredError |
Balance below the minimum needed to launch a GPU run |
| 408 | PollTimeoutError |
wait=True and timeout elapsed (the run continues; poll later) |
| 5xx | ApiError |
Training run failed - inspect run.error_message |
A run is charged for the GPU minutes it actually used, once it succeeds - there is no up-front charge and no refund to chase if it fails. The 402 is a start-time balance floor, not a pre-charge.
See also
- Training - lower-level
create / list / get / cancelprimitives - Models - download the trained weights
- Credits -
estimate("training_<gpu>")for the live USD price - Local inference - run the model you just trained
- Deployments - serve it behind a URL