Skip to main content

Finetuning Training Reference

This is the precise reference for what happens between "Pay & start training" and "Your endpoint is live". The guide covers the journey; this page covers the mechanics.

The one rule that decides everything

You never pick a training objective. The shape of your records picks it. Every file is validated before payment, the validator detects the record shape, and that shape maps to exactly one objective. One shape per file, declared by the first record; mixing shapes is a fatal mixed_output_objectives error.

chronicle — the output field selects among four objectives:

Output shapeObjectiveLossThe tuned model returns
"output": { "series": ... }Quantile forecastingPinball on the canonical grid (0.1 … 0.9)A quantile fan: median values + full quantiles
"output": { "text": ... }Text generationCross-entropyGenerated text
"output": { "series": ..., "text": ... }Both, jointlyPinball + cross-entropyFan and text, split by special tokens
"output": { "label": "..." }ClassificationSelectable (mse/mae/huber)The predicted label
"output": { "value": 1.5 } (or array)RegressionSelectable (mse/mae/huber)The predicted value

Time-series output always trains the quantile objective — the same quantile-fan contract as the production forecast model — and text output always trains cross-entropy; the loss knob applies only to classification/regression. A record may not mix target families (label next to series is a fatal invalid_target).

:::info You split the series; we don't For forecasting records, you decide where history ends and the target begins: input.series is the observed history window, and output.series is the future values the model should learn to predict from it. The platform never auto-splits a single series into input and target — a record whose output.series merely repeats the tail of input.series teaches the model nothing and will lose to the frozen base on the held-out eval. Build one record per forecast window (sliding windows over a long series are fine), and keep the target length consistent with the horizon you plan to request at serving time. :::

inertialai-embed — contrastive, or classification/regression:

Record shapeObjectiveThe tuned model is
{ "anchor": ..., "positive": ..., "negatives": [...] }Contrastive (retrieval alignment)A retrieval embedding model
{ "input": { text + series } } (input only)Contrastive (the record's two modalities are the pair)A domain-adapted embedding model
{ "input": ..., "output": { "label": "..." } }ClassificationA classifier via embeddings (nearest-centroid)
{ "input": ..., "output": { "value": ... } }RegressionA regressor via embeddings (k-NN)

Special tokens: how joint outputs stay parsable

Multi-modal outputs are serialized with reserved delimiter tokens:

<|ts|>14.2,13.9,15.1<|/ts|><|txt|>demand rising after the promo<|/txt|>

Training targets and served responses both round-trip through this codec, so a joint series+text output always splits deterministically back into its typed parts — the API response you receive has already been decoded into output.series and output.text; you never parse the stream yourself.

How each modality combination trains

Every input (and anchor/positive/negative) is a multi-modal object with text, series, or both. Features are extracted per modality and concatenated, so the objective is the same regardless of the combination — what changes is which signal the model can learn from:

Input modalitiesChronicle (any objective)Embed (any objective)
series onlyPure autoregressive: the lag window of the input series drives the target (quantile fan, text, label, or value)Embedding geometry driven by temporal/statistical structure
series + textContext-conditioned: text features condition the target (e.g. "promo launched" shifts the forecast fan)Joint embedding of both modalities
text onlyText-conditioned targets — mechanically supported, but weak without any series history; prefer including a seriesEmbedding geometry driven by text alone (labeled/pairs modes)

Special cases:

  • Unsupervised mode requires both text and series in every record — the objective is the correspondence between the two, so a record with one modality has nothing to align (fatal error).
  • Multivariate series (values as arrays + a channels list) are accepted everywhere a series is; forecasting reads the primary (first) channel, embeddings patch every channel, and the channel count must be consistent across the whole file.
  • Chronicle text generation is real next-token training on the Chronicle checkpoint: the target text trains cross-entropy over the model's full vocabulary (prompt positions masked out), and serving decodes free-form text from the tuned model.

The optimizer, precisely

Every model family trains through one centralized trainer: a standard mini-batch gradient loop over the trainable parameters (the LoRA adapters, a task head, or — for full finetunes — every weight):

  1. Each epoch iterates shuffled mini-batches (batch_size), computes the objective's masked loss, backpropagates, clips gradients (grad_clip), and steps the chosen optimizer (adamw default, adam, sgd with momentum) under the chosen scheduler (cosine default, constant).
  2. The loss follows the objective: masked quantile (pinball) for series targets — padded steps never contribute, exactly like the base models' pretraining objective — cross-entropy for text, contrastive/prototype losses for embeddings, and the selectable loss knob (mse default, mae, huber) for classification/regression heads. The stored training curves measure the loss per epoch on the real iterates.
  3. Everything is deterministic: the same data, seed, and knobs reproduce the same weights, curves, and eval.

Knobs and ranges (preset defaults in parentheses):

KnobRangeDefault
epochs1 – 200preset: 5 / 20 / 60
learning_rate1e-5 – 1.0preset: 3e-3 / 1e-3 / 3e-4
optimizeradamw, adam, sgdadamw
batch_size1 – 102432
weight_decay0 – 10.01
schedulercosine, constantcosine
grad_clip0 – 1001.0
lora_rank1 – 648
holdout_fraction0.05 – 0.50.1
lossmse, mae, hubermse
early_stopping / early_stop_patiencebool / 1 – 50false / 5
seed≥ 013
chronicle_stage (chronicle only)1, 22

With early_stopping: true, training halts once the validation loss stops improving (5 epochs of patience) and keeps the best iterate rather than the last one. The training curves record whether it triggered and which epoch was kept (early_stopped, best_epoch) — useful with high epoch counts, where the tail of the budget would otherwise train past the optimum.

LoRA vs full finetuning

Every job also declares a training method (training_method on the job, method= in the SDK):

  • lora (default) — trains low-rank adapters (rank lora_rank, default 8) on the base model's attention/MLP weights (Chronicle), the router's calibration heads (forecast), or a rank-limited residual head (embed). 75% off the full-finetune rate, and the right choice for almost all datasets.
  • full — updates every weight with no rank constraint, at the model's listed rate (the pricing page has the numbers — LoRA is 75% off it). Choose it when a LoRA run plateaus below the quality you need.

Both methods train on dedicated GPUs; the quote always states the method it was priced under.

Train vs validation data

Two ways to define the validation set — never both:

  • Random split (default): holdout_fraction of your records is held out with a deterministic seed. The held-out records never train.
  • Explicit validation file: upload a second file with the job. It must be the same format and the same record shape/mode as the training file (mismatch is fatal). Then all training records train, and your validation file alone drives the loss curves and the eval. Validation records are never billed.

The validation set does double duty: it is the val series of the training curves and the held-out set of the before/after eval — the tuned adapter versus the frozen base on records neither has seen. The eval metric follows the objective: mean pinball loss (quantile series), token-F1 (text), their average (both), accuracy (classification), NMAE (regression), retrieval recall@1 (pairs), cross-modal recall@1 (unsupervised). If the tuned model doesn't beat the base, the fee auto-refunds.

Inference: predicting vs generating

Chronicle endpoints — POST /v1/endpoints/{id}/predict. One endpoint, one contract; the response mirrors the objective the model was trained on:

{
"input": { "text": "optional context", "series": { "timestamps": [...], "values": [...], "freq": "1h" } },
"options": { "horizon": 24, "return_text": true }
}
  • Quantile objective (trained on output.series): output.series carries values (the median path continuing your input's timestamp grid) plus the full fan — quantiles (steps × 9) and quantile_levels (0.1 … 0.9), monotonic per step. This is generative time-series output. horizon may be 1 – 1000; beyond the trained horizon the frozen base extends the median and the trained quantile offsets extend the fan, so keep horizon ≤ your training target length for fully-tuned output.
  • Text objective (trained on output.text): output.text is generated from the cross-entropy head; output.series is null.
  • Both: series fan and text in one response; internally the joint output round-trips the special-token codec, so the split is exact. options.return_text: false suppresses the text block.
  • Classification / regression: output.label / output.value carry the prediction; series and horizon don't apply. (On quantile-objective models, return_text: true still yields a derived narration of the forecast.)
  • The same input modality rules as training apply — inputs the validator would reject return HTTP 422.

Embed endpoints — POST /v1/endpoints/{id}/embed. Always returns a 768-dim unit vector plus usage. What you do with it follows the training objective:

  • Classification finetune: classify a new record by nearest class centroid (or any downstream classifier) — the objective made same-label inputs cluster.
  • Regression finetune: predict by k-NN over your training embeddings — the objective aligned the geometry with your numeric target.
  • Contrastive finetune (pairs / unsupervised): cosine similarity reflects your pairing — store vectors in any vector DB and query across modalities.

Data constraints (training and validation files)

Validation runs on the entire file before you can pay; any fatal error blocks the job, warnings don't. The same checks apply to the optional validation file.

Container

  • JSONL (one JSON object per line), or a ZIP whose entries are .jsonl shards. Anything else is fatal.

Size limits (hitting one is a clear validation error, never a silent truncation)

  • 2 GiB per uploaded file. Larger datasets: shard into multiple JSONL files inside one ZIP.
  • A ZIP may contain up to 512 shards and decompress to at most 8 GiB in total; a single JSONL line may be at most 16 MiB.
  • Retained datasets count against a 10 GiB per-account storage allowance (free; deleting old jobs frees it — contact support to raise it).

Every series, everywhere

  • timestamps and values equal length; timestamps ISO-8601 and strictly increasing; values numeric (null allowed as missing, non-numeric fatal).
  • freq optional; if present and inconsistent with actual spacing → warning.
  • Multivariate: values rows all the same width, channels (if given) matching that width, and one channel count for the whole file.
  • Input-series length bounds: 8 – 10,000 steps (chronicle), 4 – 10,000 (embed). Output series are exempt from the minimum (a 1-step target is normal).

Per record shape

  • Chronicle: input and output required; input needs text and/or series; output needs exactly one target family — series and/or text, or a non-empty string label, or a numeric value (number or array of numbers). Mixing families in one record is fatal (invalid_target).
  • Embed labeled: input plus output.label (string, classification) or output.value (numeric, regression).
  • Pairs: anchor and positive required (each a text/series object); negatives optional array of the same.
  • Unsupervised: input only, with both text and series.
  • One shape/objective per file (fatal mixed_output_objectives otherwise); empty files are fatal.

Warnings (non-blocking, reported with counts)

  • Series shorter than 8 steps · >20 % missing (null) values · one label covering >90 % of records · duplicate records · freq/spacing mismatch.

Validation file extras

  • Same format (jsonl/zip) and same record shape/mode as the training file — a labeled training file with a chronicle-shaped validation file is fatal.
  • Not billed; the flat training fee is unaffected by a validation file.

At inference

  • Same modality rules as training; violations return 422 with the validator-style message. Every response carries a usage block.