Training
Spawn, poll, and cancel training runs against a completed export.
Training is always on an export - build one first, then train it. See the training guide for the end-to-end flow.
Pipelines
pipeline_type |
Output |
|---|---|
yolox |
Object detection (boxes) |
sm_pytorch |
Semantic segmentation |
classification |
Image classification |
rfdetr_detection |
Object detection (RT-DETR) |
rfdetr_segmentation |
Instance segmentation (RT-DETR) |
rfdetr_keypoint |
Keypoint / pose detection (RT-DETR) |
GPU tiers
gpu_type |
Approx. cost | Pick for |
|---|---|---|
a10g (default) |
~$0.30/hr | YOLOX, classification, RF-DETR detection |
a100 |
~$2/hr | Large RF-DETR, big batches |
h100 |
~$4/hr | Last resort, only when A100 runs out of memory |
auto |
- | Resolved at submit to the cheapest tier whose memory fits the config. Runs always report the concrete tier they got. |
create
Spawn a run against an existing completed export.
Source: Training.create
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
export_name |
str |
required | Pre-built, completed export |
pipeline_type |
PipelineType |
required | See table above |
name |
str |
required | Human-readable label (1-100 chars) |
config |
dict | str | Path |
{} |
epochs, batch_size, learning_rate, image_size, class_overrides. A path loads a downloaded config.json |
gpu_type |
GpuType |
"a10g" |
"auto" picks the cheapest tier the config fits |
gpu_count |
int |
1 |
1-4, RF-DETR only. >1 bills gpu_count x the rate |
version_of_model_id |
str | None |
None |
Append to an existing model as a new version |
wait |
bool |
True |
When False, returns immediately with status="queued" |
poll_interval |
float |
5.0 |
Seconds between polls |
timeout |
float |
7200.0 |
Max poll seconds (2 hours) |
run = client.training.create(
dataset_name="road-signs",
export_name="road-signs-20260512-120000",
pipeline_type="yolox",
name="road-signs-detector",
config={"epochs": 50},
gpu_type="a10g",
wait=True,
)
print(run.status, run.model_id)
pictograph train start road-signs road-signs-20260512-120000 \
--pipeline yolox --name road-signs-detector --gpu a10g \
--config '{"epochs": 50}' --no-wait
curl -s -X POST "https://api.pictograph.io/api/v1/developer/training/" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dataset_name": "road-signs", "export_name": "road-signs-20260512-120000", "pipeline_type": "yolox", "name": "road-signs-detector", "config": {"epochs": 50}, "gpu_type": "a10g"}'
Returns TrainingRun
TrainingRun · 21 fields
class TrainingRun(BaseModel):
"""A single training job."""
id: str
organization_id: str
name: str
dataset_id: str | None = None
export_id: str | None = None
model_id: str | None = None
pipeline_type: Literal['yolox', 'sm_pytorch', 'classification', 'rfdetr_detection', 'rfdetr_segmentation', 'rfdetr_keypoint']
gpu_type: Optional[Literal['a10g', 'a100', 'h100', 'auto']] = None
status: Literal['pending', 'queued', 'running', 'completed', 'failed', 'cancelled']
progress: int = 0
current_epoch: int = 0
total_epochs: int | None = None
metrics: dict[str, Any] = {}
config: dict[str, Any] = {}
eta_seconds: int | None = None
training_time_seconds: int | None = None
error_message: str | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime
created_by: str | None = None
Training-time augmentation
Every pipeline accepts a standardized augmentation block inside config. The
ops are applied on the fly during training - no images are added to your
dataset - and each pipeline honors them through its framework’s native
machinery, so boxes, polygons, and masks stay geometry-correct automatically.
{
"augmentation": {
"ops": [
{ "op": "flip", "p": 0.5 },
{ "op": "brightness", "factor": 0.2, "p": 0.3 }
],
"framework": { "mosaic_prob": 1.0 }
}
}
ops- per-image transforms in the shared augment vocabulary:flip,vflip,rotate90,rotate,shear,brightness,contrast,saturation,hue_shift,grayscale,blur,noise,cutout. Each entry carries a probabilitypin[0, 1]plus that op’s strength parameters.framework- pipeline-specific extras applied inside the training loop: YOLOXmosaic_prob/mixup_prob/hsv_prob/degrees/translate/shear; semantic segmentationperspective. RF-DETR and classification have none.
Which ops a pipeline supports, and its defaults, are served by
GET /api/v1/training/augmentation-profiles. Omit the block and the pipeline
trains with its historical defaults; either way create records the resolved
values into the run’s stored config, so the model’s downloadable config.json
always says exactly which augmentation applied. An unsupported op, a probability
outside [0, 1], or an out-of-range strength is rejected with a 400 at submit.
list
One page of training runs in your organization.
Source: Training.list
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str | None |
None |
Restrict to one dataset |
status |
TrainingStatus | None |
None |
e.g. "running" |
limit |
int |
50 |
Server cap: 100 |
offset |
int |
0 |
Page offset |
for run in client.training.list(status="running", limit=20):
print(run.id, run.name, run.status, run.progress)
pictograph train list --status running --limit 20
curl -s "https://api.pictograph.io/api/v1/developer/training/?limit=20&status=running" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns list[TrainingRun]
TrainingRun · 21 fields
class TrainingRun(BaseModel):
"""A single training job."""
id: str
organization_id: str
name: str
dataset_id: str | None = None
export_id: str | None = None
model_id: str | None = None
pipeline_type: Literal['yolox', 'sm_pytorch', 'classification', 'rfdetr_detection', 'rfdetr_segmentation', 'rfdetr_keypoint']
gpu_type: Optional[Literal['a10g', 'a100', 'h100', 'auto']] = None
status: Literal['pending', 'queued', 'running', 'completed', 'failed', 'cancelled']
progress: int = 0
current_epoch: int = 0
total_epochs: int | None = None
metrics: dict[str, Any] = {}
config: dict[str, Any] = {}
eta_seconds: int | None = None
training_time_seconds: int | None = None
error_message: str | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime
created_by: str | None = None
iter
Auto-paging iterator across every run in your org. Same filters as list, plus
page_size and max_total. The CLI and REST page manually with limit and
offset.
Source: Training.iter
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str | None |
None |
Dataset name. |
status |
TrainingStatus | None |
None |
Only runs in this state, e.g. completed or failed. |
page_size |
int |
50 |
Rows fetched per underlying request. Tuning only - the iterator yields every item either way. |
max_total |
int | None |
None |
Stop after this many items. None walks everything. |
for run in client.training.iter(page_size=50):
print(run.id, run.status, run.progress)
# The CLI does not auto-page; this is a single page.
pictograph train list --limit 100
curl -s "https://api.pictograph.io/api/v1/developer/training/?limit=100&offset=100" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns OffsetPager[TrainingRun]
TrainingRun · 21 fields
class TrainingRun(BaseModel):
"""A single training job."""
id: str
organization_id: str
name: str
dataset_id: str | None = None
export_id: str | None = None
model_id: str | None = None
pipeline_type: Literal['yolox', 'sm_pytorch', 'classification', 'rfdetr_detection', 'rfdetr_segmentation', 'rfdetr_keypoint']
gpu_type: Optional[Literal['a10g', 'a100', 'h100', 'auto']] = None
status: Literal['pending', 'queued', 'running', 'completed', 'failed', 'cancelled']
progress: int = 0
current_epoch: int = 0
total_epochs: int | None = None
metrics: dict[str, Any] = {}
config: dict[str, Any] = {}
eta_seconds: int | None = None
training_time_seconds: int | None = None
error_message: str | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime
created_by: str | None = None
get
Fetch a run’s current status, metrics, and progress. status is one of
pending, queued, running, completed, failed, cancelled.
Source: Training.get
| Arg | Type | Default | Notes |
|---|---|---|---|
name |
str | None |
None |
Run name. Pass this or the positional run id. |
run_id |
str | None |
None |
Run id. |
run = client.training.get(
name="signs-detector",
)
print(run.status, run.progress, run.current_epoch, "/", run.total_epochs)
pictograph train status signs-detector
curl -s "https://api.pictograph.io/api/v1/developer/training/$RUN_ID" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns TrainingRun
TrainingRun · 21 fields
class TrainingRun(BaseModel):
"""A single training job."""
id: str
organization_id: str
name: str
dataset_id: str | None = None
export_id: str | None = None
model_id: str | None = None
pipeline_type: Literal['yolox', 'sm_pytorch', 'classification', 'rfdetr_detection', 'rfdetr_segmentation', 'rfdetr_keypoint']
gpu_type: Optional[Literal['a10g', 'a100', 'h100', 'auto']] = None
status: Literal['pending', 'queued', 'running', 'completed', 'failed', 'cancelled']
progress: int = 0
current_epoch: int = 0
total_epochs: int | None = None
metrics: dict[str, Any] = {}
config: dict[str, Any] = {}
eta_seconds: int | None = None
training_time_seconds: int | None = None
error_message: str | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime
created_by: str | None = None
wait_for_completion
Block until a run reaches a terminal status. Use this after create(wait=False).
Raises PollTimeoutError if timeout elapses; the run keeps going server-side.
Source: Training.wait_for_completion
| Arg | Type | Default | Notes |
|---|---|---|---|
name |
str | None |
None |
The run’s name (positional). A UUID works here too. |
run_id |
str | None |
None |
The run’s UUID - the keyword alternative to name. |
poll_interval |
float |
5.0 |
Seconds between checks (default 5s). |
timeout |
float |
7200.0 |
Maximum seconds to wait (default 7200 = 2h). |
run = client.training.wait_for_completion(
name="signs-detector",
poll_interval=5.0,
timeout=7200.0,
)
if run.status == "completed":
model = client.models.get(
model_id=run.model_id,
)
print(model.name, model.metrics)
pictograph train wait signs-detector --timeout 7200
# Poll the get endpoint until "status" reaches a terminal value.
curl -s "https://api.pictograph.io/api/v1/developer/training/$RUN_ID" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns TrainingRun
TrainingRun · 21 fields
class TrainingRun(BaseModel):
"""A single training job."""
id: str
organization_id: str
name: str
dataset_id: str | None = None
export_id: str | None = None
model_id: str | None = None
pipeline_type: Literal['yolox', 'sm_pytorch', 'classification', 'rfdetr_detection', 'rfdetr_segmentation', 'rfdetr_keypoint']
gpu_type: Optional[Literal['a10g', 'a100', 'h100', 'auto']] = None
status: Literal['pending', 'queued', 'running', 'completed', 'failed', 'cancelled']
progress: int = 0
current_epoch: int = 0
total_epochs: int | None = None
metrics: dict[str, Any] = {}
config: dict[str, Any] = {}
eta_seconds: int | None = None
training_time_seconds: int | None = None
error_message: str | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime
created_by: str | None = None
cancel
Stop a run in flight and mark it cancelled. Billing is charge-on-success, so a
run cancelled before it finishes is never charged. Requires member+ role.
Source: Training.cancel
| Arg | Type | Default | Notes |
|---|---|---|---|
name |
str | None |
None |
Run name. Pass this or the positional run id. |
run_id |
str | None |
None |
Run id. |
client.training.cancel(
name="signs-detector",
)
pictograph train cancel signs-detector --yes
curl -s -X POST "https://api.pictograph.io/api/v1/developer/training/$RUN_ID/cancel" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns TrainingRun
TrainingRun · 21 fields
class TrainingRun(BaseModel):
"""A single training job."""
id: str
organization_id: str
name: str
dataset_id: str | None = None
export_id: str | None = None
model_id: str | None = None
pipeline_type: Literal['yolox', 'sm_pytorch', 'classification', 'rfdetr_detection', 'rfdetr_segmentation', 'rfdetr_keypoint']
gpu_type: Optional[Literal['a10g', 'a100', 'h100', 'auto']] = None
status: Literal['pending', 'queued', 'running', 'completed', 'failed', 'cancelled']
progress: int = 0
current_epoch: int = 0
total_epochs: int | None = None
metrics: dict[str, Any] = {}
config: dict[str, Any] = {}
eta_seconds: int | None = None
training_time_seconds: int | None = None
error_message: str | None = None
started_at: datetime | None = None
completed_at: datetime | None = None
created_at: datetime
created_by: str | None = None
bulk_cancel
Cancel many runs in one org-scoped call. Already-terminal, foreign, or missing
ids land in not_found rather than failing the call.
Source: Training.bulk_cancel
| Arg | Type | Default | Notes |
|---|---|---|---|
run_ids |
Sequence[str] |
required | Run ids to act on. Ids that do not resolve are reported, not raised. |
result = client.training.bulk_cancel(
run_ids=[
"d1f2a3b4-5c6d-4e70-8f91-a2b3c4d5e6f7",
"e2a3b4c5-6d7e-4f81-9a02-b3c4d5e6f708",
],
)
print(result.count, "cancelled;", result.not_found, "skipped")
pictograph train cancel-batch \
d1f2a3b4-5c6d-4e70-8f91-a2b3c4d5e6f7 e2a3b4c5-6d7e-4f81-9a02-b3c4d5e6f708 --yes
curl -s -X POST "https://api.pictograph.io/api/v1/developer/training/bulk-cancel" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"run_ids": ["d1f2a3b4-5c6d-4e70-8f91-a2b3c4d5e6f7", "e2a3b4c5-6d7e-4f81-9a02-b3c4d5e6f708"]}'
Returns BulkActionResult
BulkActionResult · 3 fields
class BulkActionResult(BaseModel):
"""Result of a server-side bulk state-change (e.g. pause/resume)."""
succeeded: list[str] = []
not_found: list[str] = []
count: int = 0
Dataset size
The worker splits the export into train / val / test. Below three images it cannot split and trains on everything; three or more guarantees at least one image each in train and val. A handful of images will train, but it will not produce a usable model - treat a few dozen per class as the practical floor.
ds = client.datasets.get(
name="road-signs",
)
print(ds.completed_image_count, "images ready to train on")
Cost
Training is billed per GPU-minute in USD, charge-on-success: a run is charged
once, after it completes, for the actual GPU minutes used. A failed,
out-of-memory, or cancelled run is never charged, so there is no up-front
deduction to refund. create still gates on a minimum spendable balance at
submit.
estimate = client.credits.estimate(
operation="training_a10g",
quantity=30,
)
if not estimate.sufficient:
raise RuntimeError(f"Need ${estimate.total_usd:.2f}, have ${estimate.remaining_usd:.2f}")
pictograph credits estimate training_a10g --quantity 30
Common errors
| Status | Exception | Cause |
|---|---|---|
| 402 | PaymentRequiredError |
Below the minimum spendable balance |
| 404 | NotFoundError |
Dataset, export, or run missing |
| 422 | ValidationError |
Pipeline or GPU invalid, export not completed |
| 408 | PollTimeoutError |
wait=True exceeded timeout (run keeps going) |