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 lengthN.loss_fn_inputs— a dict of per-token tensors, each with one row per input position: shape(N,), or(N, K)for inputs that carryKvalues per position. It always holdstarget_tokens, plus whatever else the loss takes (weightsfor cross-entropy;logprobsandadvantagesfor 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: