Skip to main content

Training studies

A training study is the default way to train a model. It trains a small, fixed grid of ordinary training runs on one dataset, picks the best one using validation data only, evaluates that one model once, and tells you how it compares with the platform's internal quality safeguards. You do not choose hyperparameters by hand and you do not touch the test split while you tune.

A study never creates a bundle, acknowledges a gate, promotes a model or activates a slot. Those stay explicit calls under your promotion policy.

Use POST /runs instead when you want exactly one configuration.

What a study is​

A study expands a search space into children. A search space is fixed, versioned data. It is never edited: a change is a new id. There is no random, adaptive or pruned search, so the same space always gives the same children in the same order.

The default space is tier_s_cpu_v1: tier S on the CPU compute classes. It has eight children, the product of three axes:

AxisValues
trainer.global_batch_tokens32,768 and 131,072
label_weight1 and 10 (applied to every selected label)
readoutmean_pool and last_event

Each child also sets trainer.max_tokens_per_microbatch to the cap that follows its batch (see Batch size, schedule and label weights), so the batch axis changes the real size of an update. Every other setting is the run default, and every child uses the study's seed.

Children are numbered grid_index 0 to 7. The first axis changes slowest.

Every child is an ordinary run. It is listed by GET /runs, readable with GET /runs/{id}, metered like any run, and emits the usual run.completed webhook. A study adds no field to a run or a model: the membership lives only in the study.

An ordinary POST /runs never returns a run a study created. A request for the same configuration, including a clone of a study child, starts its own ordinary run (evaluated automatically, never archived by a study) instead of returning the study's run; the duplicate compute is accepted.

Create a study​

{
"dataset_id": "ds_00000000000000000000000000",
"seed": 7
}
curl -s -X POST "$TESSERA_API_BASE/training-studies" -H "authorization: Bearer $TESSERA_API_KEY" \
-H "content-type: application/json" -H "Idempotency-Key: study-1" \
-d '{"dataset_id": "'"$DATASET_ID"'", "seed": 7}'

With the planned Python SDK:

request = models.CreateTrainingStudyRequest(dataset_id=dataset_id, seed=7)
study = client.api.create_training_study(request)
print(study.study_id, study.status.value, study.budget.estimated_node_hours)

The answer is 202 with the admitted study: queued only while no child could launch yet, otherwise running or later (a study that supersedes one whose children it all reuses can already be evaluating or completed).

FieldMeaning
dataset_idRequired. A succeeded dataset build with an empty violation report.
search_spaceA published space id. Default tier_s_cpu_v1. An unknown id is invalid_argument.
labelsA subset of the dataset's labels to train and select on. Default: all of them.
seedThe seed of every child. Default 0.
compute_classA compute class of the space's family, or auto (the default).
max_node_hoursYour own budget guard. When set, a study estimated above it is refused. When omitted, any estimate up to the space's ceiling (96 for tier_s_cpu_v1) is admitted and recorded in budget, and an estimate above the ceiling is refused because the ceiling protects shared capacity. A value above the ceiling is invalid_argument.
baseline_fallbackDefault true. See The baseline candidate.

Admission errors​

A request that cannot run is refused before anything trains. Refusals about the dataset or the budget are 400 failed_precondition, and error.details[0].reason names the cause:

details[0].reasonCauseWhat to do
dataset_rebuild_requiredThe dataset was built before dataset builder 1.2.0, so its manifest does not record the longest train row. The grid's two batch sizes are distinct configurations only when the microbatch cap follows the batch.Rebuild the dataset. A rebuild is a new dataset version.
study_requires_labelsThe dataset has no labels to train on. A study selects on validation label metrics.Add a label and rebuild, or train one run with POST /runs.
study_budget_exceededThe estimate is above the max_node_hours you set (the detail carries estimated_node_hours and max_node_hours), or, when you set none, above the space's ceiling (the detail carries estimated_node_hours and ceiling).For your own budget: raise it (up to the ceiling) or omit it. For the ceiling: use a smaller dataset, or train one run with POST /runs.

A dataset that is missing, belongs to another project, or is not succeeded is refused as for POST /runs (not_found, or failed_precondition). Warn-level dataset findings never refuse a study. The study returns them as dataset_findings_count (the exact count) and dataset_findings (up to 50 entries with code, severity, event_type, attr_key, label and detail, no evidence values), the study page shows them, and the training_study.create audit event records the same facts, as for POST /runs.

The budget estimate​

The estimate is a deterministic upper bound. It uses the dataset manifest and the space's constants, never a clock:

estimated_node_hours = sum over children of
epochs × train_tokens / (nominal_tokens_per_second_per_node × 3600)

For tier_s_cpu_v1, nominal_tokens_per_second_per_node is 5,000, below the 5,100 to 8,500 tokens per second measured on the cpu-standard class, and every child runs the default ten epochs. The estimate is therefore train_tokens / 225,000 node-hours. A dataset with 3,807,223 train tokens estimates about 16.9 node-hours, well under the ceiling of 96, which admits about 21.6 million train tokens. Set max_node_hours lower to make it your own guard.

Early stopping usually ends a child sooner, so the real cost is lower. The estimate and the budget are echoed on the study as budget; budget.max_node_hours_source says whether max_node_hours is your own guard (request) or the space's ceiling because you set none (ceiling).

Idempotency​

A study is content-addressed by its dataset fingerprint, search space fingerprint, resolved labels, seed and resolved compute class.

  • An identical request is admitted again (its own budget included) and then returns the existing study, with its stored values, while that study is queued, running or evaluating with no cancel in progress, or completed. A study whose cancel is in progress is superseded like a cancelled one.
  • A failed or cancelled study is superseded: the same request creates a new study with a new stu_ id.

Because every child is one run config, a new study reuses work already done. A child is reused (reused: true, no new Job) when your project already has a succeeded run with the same dataset, config and seed, whose model is not archived and whose validation record exists. A reused run is never modified. A child whose exact config is running under a run this study did not create waits for that run to end, then reuses it or launches its own.

A failed or cancelled study archives nothing, so the next study can reuse its completed children. When a study completes, it archives the losers its own lineage created, including those of the failed or cancelled studies it superseded. It never archives the winner, or a run another study still uses.

Plan limits​

Every child counts toward your plan's concurrent_training_runs exactly like a run. A study launches children in grid order as capacity allows. When the plan is full, the study waits. It does not fail, and it launches the next child when a slot frees. The status stays queued until the first child launches, then running.

How the winner is chosen​

Selection uses validation data only.

  1. For each completed child, take the validation record at the child's shipped step, the checkpoint the product ships (the best_step of the run).
  2. The child's selection value is the mean over the study's labels of each label's primary validation metric: AUC for binary labels, macro-F1 for multiclass labels, R² for regression labels.
  3. The winner has the greatest selection value. An exact tie goes to the child with fewer optimizer updates, then to the lower grid_index.

No test-split number, evaluation report value, gate result, comparator, clock or cost enters selection. The grids and the budget use none either: nothing trains or tunes toward the gate.

A child that finishes without a usable finite validation value is recorded failed with error.code = selection_value_unavailable. A child whose run fails or is cancelled is recorded with child_run_failed. A failed child does not fail the study. If no child completes, the study ends failed with error.code = no_completed_candidate.

Validation also drives each run's early stopping, so the selection values are optimistic. The single evaluation of the winner below is the unbiased check.

The winner is evaluated once​

Study children are never evaluated automatically. When selection is done, the study evaluates only the winner, once, with the internal quality safeguards included, and is evaluating until the report is recorded. The test split is therefore touched once per study, not once per child.

evaluation.holdout_touch says whether this study's evaluation touched the test split. It is false when the winner is a reused run already evaluated under the current report identity, whose report the study reads without a new evaluation.

If the winner's evaluation fails for good, the study ends failed with error.code = winner_evaluation_failed. It never falls back to the runner-up. A new study reuses the completed children.

Outcome​

When the report is recorded, the study is completed and outcome says what the study found. It is information, never a block: it comes from the winner's G1 competitiveness records (each label against an internal reference model), and the winner's verdict stays in evaluation.promotion.

outcomeMeaning
competitiveEvery label is competitive with the internal reference model.
not_competitiveAt least one label is not competitive with the internal reference model.
not_assessableThe internal reference check could not run for a label, the evaluation predates the competitiveness check or cannot be read, or a measurement-integrity gate (G4, G5 or G6) failed.

When some labels are not competitive and the internal reference check could not run for others, the outcome is not_competitive: a measured shortfall is the stronger fact. An unreadable or integrity-blocked evaluation is always not_assessable. A temporary failure to read the winner's report is not a fact: the study stays evaluating and retries the read.

outcome is present exactly when status is completed. Nothing changes until you promote a model: promoting the winner (acknowledging its findings) or a baseline candidate is your explicit choice, through the usual bundle and slot steps. The study records the plain findings, negative ones included.

The study page names the winner's verdict by its class. Failed quality gates (G1, G2, G3) are findings you can acknowledge with a reason when you promote. A failed measurement-integrity gate (G4, G5, G6), an unreadable report, or a gate result that is malformed or missing makes the evaluation not valid: it cannot be acknowledged, so the page offers no promotion and points you to re-evaluate or train again. A quality gate your project's own promotion policy made blocking (strict mode or a raise-only override) is blocked by policy: the page names the rule and who set it. The page names the class from the winner's live decision (its model page's); when that cannot be read, it shows the verdict recorded at evaluation without naming a policy cause. The page opens the baseline candidate's model page, where its own verdict and the promote choice are shown.

The baseline candidate​

fallback is present on every study. fallback.mode is fixed at creation.

  • keep_incumbent: no baseline candidate is requested (baseline_fallback: false). state is not_applicable.
  • baseline_candidate: when the outcome is not_competitive or not_assessable, the study asks the entity baseline family for a baseline candidate, trained beside the winner so you can compare and choose. fallback.state follows the candidate through pending, training, evaluating and evaluated, or failed with an error. fallback.model_id is the candidate's model and fallback.arms names the fallback candidate per label. fallback.arm_bases says how each label's candidate was named: competitiveness_record (taken from the label's competitiveness record in the winner's report) or strongest_completed_arm (the report recorded no competitiveness for that label, so a completed internal candidate was substituted). The candidate is offered, never promoted.

In either mode the incumbent bundle and slot are unchanged until you promote a model. The console says so plainly:

Nothing changes until you promote a model.

Train a configuration as a run​

Only the winner is evaluated, so the test split is touched once per study. If you want another child's configuration as a model of its own, use Train this configuration as a run on the study page (or clone the child's run). That is an ordinary run, evaluated automatically: one more, explicit and counted, use of the holdout.

Read, list and cancel​

curl -s "$TESSERA_API_BASE/training-studies/$STUDY_ID" -H "authorization: Bearer $TESSERA_API_KEY"
curl -s "$TESSERA_API_BASE/training-studies?status=running" -H "authorization: Bearer $TESSERA_API_KEY"
curl -s -X POST "$TESSERA_API_BASE/training-studies/$STUDY_ID:cancel" -H "authorization: Bearer $TESSERA_API_KEY" \
-H "Idempotency-Key: cancel-study-1"

The same calls in Python:

study = client.api.get_training_study(study_id)
print(study.status.value, [child.selection_value for child in study.children])
page = client.api.list_training_studies(status="running")
study = client.api.cancel_training_study(study_id)

GET /training-studies lists newest first and filters by status and dataset_id. No webhook marks completion. Poll GET /training-studies/{id}. Each child still emits run.completed.

TrainingStudy carries:

  • status: queued, running, evaluating, completed, failed or cancelled. The last three are terminal.
  • budget: the child count, epochs, train tokens, the throughput constant, the estimate, the admitted maximum and whether it is your guard or the ceiling (max_node_hours_source).
  • children: one entry per grid point, in grid_index order, with axes, run_id, model_id, status, reused, shipped_step, label_metrics (each label's metric, value and target count), selection_value, selected, archived and error.
  • winner, evaluation (with the model's promotion verdict), outcome and fallback.
  • trial_log_fingerprint: the fingerprint of the stored trial log.
  • dataset_findings_count and dataset_findings: the dataset's warn-level findings when the study was admitted (the exact count and up to 50 entries). Information only: they never refuse a study.

The trial log​

When a study ends, it stores a trial log with one record per child: the grid index, axes, config fingerprint, run and model ids, whether it was reused, its status, shipped step, validation values, selection value, whether it was selected, whether it was archived and any error. children on the study is the same data in structured form, and trial_log_fingerprint identifies the stored log.

Cancel​

POST /training-studies/{id}:cancel stops the queued and running children the study launched and marks the study cancelled. Reused runs are not touched. The models it created are not archived, so a later identical study can reuse them.

Cancelling a study that is already completed with a baseline candidate still pending, training or evaluating cancels that candidate (fallback.state: cancelled) and keeps the status and outcome. Cancelling any other terminal study is 400 failed_precondition.

In the console​

Training → New study is the default way to start. Choose the dataset, the labels, the seed and the compute class. The form shows the number of children, the estimated node-hours and your plan limit before you start. Single run (advanced) opens the existing run form, which creates one run with POST /runs.

The study page (/runs/studies/{study_id}) shows each child with its axes, status, validation value, and its reused and archived flags. It also shows the winner, the evaluation verdict and the fallback. The list of studies is at /runs/studies.