Skip to content
View as Markdown

Datum

A Datum is one training example: the tokens the model reads, plus the tensors its loss function needs.

  • model_input — the input token sequence, of length N.
  • loss_fn_inputs — a dict of per-token tensors, each with one row per input position: shape (N,), or (N, K) for inputs that carry K values per position. It always holds target_tokens, plus whatever else the loss takes (weights for cross-entropy; logprobs and advantages for RL losses).

Position i of the input predicts token i + 1, so both are built from the full sequence, shifted by one:

full_sequence = prompt_tokens + completion_tokens

datum = types.Datum(
    model_input=types.ModelInput.from_ints(full_sequence[:-1]),
    loss_fn_inputs={
        "target_tokens": full_sequence[1:],
        "weights": [0.0] * (len(prompt_tokens) - 1) + [1.0] * len(completion_tokens),
    },
)

Everything the loss needs is in the datum, so each datum is self-contained.

Provenance

Some features need information captured when tokens were sampled. To use it at training time, the datum has to identify the sampled sequence behind each of its tokens. That annotation is the datum's provenance.

Provenance works only within one session

A training request can only name sequences sampled in its own session. Sample and train in the same session.

Every sequence a sampling request returns carries a sequence_id. Provenance is a list of runs naming those IDs:

  • PromptProvenanceSpan(sequence_id, offset, length) — tokens [offset, offset + length) of the sequence's prompt.
  • SampledProvenanceSpan(sequence_id, offset, length) — tokens [offset, offset + length) of the sequence's sampled tokens.

Datum.with_provenance attaches two lists of runs: one for model_input and one for target_tokens. Each list tiles its field: runs cover consecutive positions, and their lengths sum to the field's length. For a prompt and one sampled sequence, the input covers the prompt and all but the last sampled token, and the targets cover the same tokens shifted by one:

from tinker import types

prompt_tokens = prompt.to_ints()
sampled_tokens = sequence.tokens
sid = sequence.sequence_id
n_prompt, n_sampled = len(prompt_tokens), len(sampled_tokens)

datum = types.Datum(
    model_input=types.ModelInput.from_ints(prompt_tokens + sampled_tokens[:-1]),
    loss_fn_inputs={
        "target_tokens": prompt_tokens[1:] + sampled_tokens,
        "logprobs": [0.0] * (n_prompt - 1) + sequence.logprobs,
        "advantages": [0.0] * (n_prompt - 1) + [advantage] * n_sampled,
    },
).with_provenance(
    model_input=[
        types.PromptProvenanceSpan(sequence_id=sid, length=n_prompt),
        # A one-token completion has no sampled token in the input; omit this run then.
        types.SampledProvenanceSpan(sequence_id=sid, length=n_sampled - 1),
    ],
    loss_fn_inputs=[
        types.PromptProvenanceSpan(sequence_id=sid, offset=1, length=n_prompt - 1),
        types.SampledProvenanceSpan(sequence_id=sid, length=n_sampled),
    ],
)

Any loss accepts provenance, and the server checks it on every request. Each run must fit inside the sequence it names, and a run covering a whole sampled sequence must carry exactly that sequence's tokens, as must a model input run covering a whole prompt.

Features that require provenance: