Skip to main content
A fine-tune trains a model of your own on a dataset of reviewed labels. The first fine-tune starts a family: a line of models trained on one dataset, each from the one before. You need:
  • A representative dataset, with its ID in SQWISH_DATASET_ID.
  • training_available set to true in /v1/onehot.
Two calls train a model, and both live under families: Each queues a fine-tune job, which you follow under /v1/fine-tuning/jobs.

Estimate the fee

To get the exact fee, send the body you will start the family with to its estimate. Leave out max_price_usd:
The estimate reads and counts the dataset exactly like the start. It queues and holds nothing. The fee is exact. The same body trains the same rows in the same order, which seed sets. seed is 42 when left out. The estimate refuses what the start would refuse about the dataset, the family’s name, the base, the rows and the steps. Credit, storage and queue room are checked only when the job starts. The first estimate or start of a very large dataset version may answer 503 tokens_counting with Retry-After. Its training tokens are still being counted, and nothing is started or held. After that many seconds, send the same request with the same Idempotency-Key.

How the fee is counted

A fine-tune is priced per million training tokens, by base model, with a minimum per job. pricing.fine_tune_million_tokens_usd gives the price per million, and pricing.fine_tune_minimum_usd the minimum. Training tokens are your rows’ text, each time a step trains on it. They are counted like a request’s input tokens:
  • A row counts its whole text once for each of its decisions. Its whole text is its context and every decision with its outcomes. So outcomes that several decisions share count in each of them.
  • Each outcome you name counts at least 8 tokens. A row of n decisions counts at least 24 × n tokens for each of them.
  • Earlier rows mixed in are counted too.
  • Two passes over a dataset count its tokens twice.
A job shows the count as split.trained_tokens and the fee as price_usd. The fee is those tokens at its base model’s price, or the minimum if that is more. For example: Pricing lists the standard price per million training tokens for each base model, and the minimum. pricing on GET /v1/onehot gives the prices in force for your account.

Start the family

Send the estimate’s price_usd as max_price_usd (USD amounts). Send its dataset_version too, so rows added meanwhile wait for the next model. The clients read both from the estimate. In cURL, "2.500000000" and 1 stand for them:
The response is a job, not a model you can call yet. Save its id, inspect its resolved hyperparameters and split, and poll /v1/fine-tuning/jobs/{job_id}. If the reply is lost, send the same request with the same Idempotency-Key. You get the same job back, with the response header Idempotency-Replayed: true. The example uses expected answers. A dataset of outcome scores needs method: "reward". The job holds exactly the estimate’s price_usd, while the prices, your account, the dataset and the family stay as they were. If one of them changed and the job would cost more, it gets 409 price_changed and holds nothing. Ask for the estimate again. A fine-tune is charged when it succeeds, even if its model fails the quality check (gate_failed). A run that fails or is cancelled is free. Decision Lab costs covers holds and the other fees. POST /v1/fine-tuning/jobs and its estimate answer 410 route_gone, naming these routes.

What the start checks

  • A decision asked twice about one context counts once towards the 30.
  • When you start from one of your models, decisions about contexts its line trained on don’t count. Nor does a reward decision whose rewards are all equal.
  • A fine-tuned model takes requests up to the same 65,536-token input limit as every other model.
  • A model available for inference isn’t necessarily trainable.
  • The job records the training limits and the split it trained with.
The family’s name stays yours once a model has joined it. A first job that failed or was cancelled also leaves the name taken. To train again, continue the family. To give the name to another family, delete this one. A continuation from a model trains only on the rows added since, so the row range doesn’t apply to it. Continuing a family that has no model yet trains every row, so the range applies.

Hyperparameters

Leave them out at first to use the base model’s defaults. To set one, put it inside hyperparameters, as in {"hyperparameters": {"steps": 20}}. That works when you start or continue a family, and in both estimates. A field sent at the top level gets 422.

Follow the job

  • output_model, metrics and progress may be null before they exist.
  • Once the job succeeds, its own record also has curve, the training loss over steps. Lists of jobs leave it out.
  • Read /events for lifecycle messages, and /records for its held-out decisions, page by page.
  • A client timeout or a stopped poll doesn’t cancel the job. Use the job’s /cancel operation to stop it.

Read the gate

The gate compares a model with its base or parent, and looks for regressions on the held-out data. It is a safety check, not a guarantee about future traffic.
  • A model that passes joins its family with the next number, such as support-router@1. A family that is switched on then moves to it.
  • A model that fails joins nothing. It gets no number and answers nothing, and its job’s outcome is gate_failed.
A job’s metrics.gate holds the quality check’s result. passed says whether its model passed, and reasons says why it failed. After a move, watch your own operational metrics, such as harmful actions or review workload. If they get worse, switch the family off. Inspect held-out probabilities beside targets or rewards. For supervised jobs, compare loss, accuracy and mistakes by task and class. For reward jobs, compare expected reward and the quality check’s reasons. A majority-class baseline or a simple rule can tell you more than a single headline accuracy. Each model that joins its family also gets a dataset_evaluation on the dataset’s fixed evaluation set. Verified training predictions are reused when they cover that set. Otherwise a separate evaluation runs after training. Its summary, and every other model’s results, are on the dataset page. These results are comparable across models (compare models). They are separate from the gate’s held-out rows, which also hold rows that earlier models of the family were judged on. An evaluation-only dataset can’t be used for training.

Continue a family

Add rows to the family’s dataset, then continue the family:
The next model trains from the family’s latest model. It trains on the rows the dataset gained since that model trained. The same number of earlier training rows is mixed in, so it keeps what earlier models learned.
  • After a rollback, or after a model that failed its gate, the new rows include the rows that the discarded or failed models trained on.
  • Its gate judges it on the new held-out rows, and on the held-out rows earlier models were judged on.
It trains on the dataset’s newest version. To train on an earlier one, send its number as dataset_version. The model learns the rows added up to that version, and later ones stay new for the next model. The version must be newer than the one the family’s last finished job trained on. Otherwise the request gets 409 no_new_data. Send latest, the ID of the family’s latest model you saw, to be sure the family hasn’t moved on. Send null when it had none. A continuation joins the family when its gate passes. A family that is switched on then moves to it. From the SDKs, families.wait(family, model) with the job’s output_model returns once the family serves it.

Check before you continue

GET /v1/families/support-router says what continuing would do, in continue. It works this out from the family’s dataset and jobs:
  • evaluation_decisions and rows are null while a job of the family is queued or training. They are also null when continuing is refused whatever the data: origin_gone, dataset_deleted, dataset_archived or fine_tuning_off.
  • continue doesn’t check credit, storage allowance or queue room. The start refuses those with insufficient_credit, storage_allowance, queue_full or tenant_queue_full.
  • GET /v1/families reads no dataset. There, new_data and retry only tell you that a version no finished job used is waiting. evaluation_decisions, rows and price_usd are null there, so read the family itself before you start.
  • The answers to a switch, keep warm or rollback leave continue out.

Estimate a continuation

For a priced continuation, send the body you will continue with to POST /v1/families/support-router/continue/estimate. Leave out max_price_usd. It answers with the same fields as a new family’s estimate. It works them out the way the continuation will train: on its new rows, and on the earlier rows it mixes in. Its seed, the family and the family’s newest job choose those earlier rows. Its price_usd is exact. Send it as max_price_usd when you continue.
  • Send the estimate the dataset_version you will continue on, and its rows counts that version’s rows.
  • A continuation after one that failed or was cancelled mixes in other earlier rows, so ask for the estimate again.

Errors

A job that failed or was cancelled leaves its rows for another try. A model that didn’t pass its gate used them, so add rows before you continue again. The family’s own read lists its jobs in jobs, newest first. Each has its outcome, price_usd and charge as the job shows them. Each also has reason: the gate’s first reason for a model that failed it, or the first line of a failed run’s error. Otherwise reason is null.