Fine-tuning¶
laya.train is imported on its own (from laya.train import finetune, TrainConfig); it is not
re-exported from the top-level package. laya-train is its command line. The
fine-tuning guide shows both in use.
Running a fine-tune¶
TrainConfig
dataclass
¶
TrainConfig(
epochs: int = 4,
micro_batch: int = 8,
grad_accum: int = 8,
encoder_lr: float = 2.5e-05,
head_lr: float = 0.0001,
min_lr: float = 1e-06,
weight_decay: float = 0.01,
grad_clip: float = 1.0,
loss: str = "rlcd",
rl_samples: int = 4,
sigma_start: float = 0.4,
sigma_end: float = 0.1,
w_sph: float = 0.75,
w_rps: float = 1.0,
shuffle_options: Tuple[str, ...] = (),
option_layout: Optional[str] = None,
freeze_encoder: bool = False,
calib_max: int = 400,
calib_frac: float = 0.1,
calib_seed: int = 20260922,
seed: int = 0,
max_len: Optional[int] = None,
head_max_len: Optional[int] = None,
amp: Optional[bool] = None,
gradient_checkpointing: Optional[bool] = None,
log_every: int = 100,
label_smoothing: float = 0.0,
text_column: str = "text",
label_column: str = "label",
question_id: str = "label",
instructions: Optional[str] = None,
eval_data: Optional[str] = None,
target_error: float = 0.1,
min_abstain_n: int = 10,
)
Training knobs. The defaults train the way the published notebook does.
The notebook's effective batch of 64 is 8 per micro-batch x 2 GPUs x 4 accumulation steps;
on one device that is micro_batch=8, grad_accum=8.
max_len / head_max_len default to the checkpoint's own config. Whatever is used is
written into the saved config, so inference sees the same budgets training did.
amp defaults to on for CUDA only; gradient_checkpointing defaults to following amp.
option_layout defaults to the checkpoint's own ("sequential" for every published one);
"parallel" trains on common.parallel_layout and is written into the saved config.
finetune ¶
finetune(
data: str,
model_dir: str,
output_dir: str,
config: Optional[TrainConfig] = None,
device: Optional[str] = "auto",
) -> Dict[str, Any]
Preprocess, train, calibrate and save; returns a summary of the run.
data is a JSONL file of {state, questions, gold} or {state, questions, expected} rows,
or a CSV file with text and label columns.
model_dir is a local checkpoint directory -- the layout laya.load reads. The result in
output_dir loads with laya.load(output_dir).
dry_run ¶
Inspect dataset and build items without loading model weights; returns dataset stats.
The two halves of finetune¶
train_model ¶
train_model(
model,
tok,
items: Sequence[Dict[str, Any]],
config: TrainConfig,
device: device,
max_len: int,
head_max_len: int,
on_epoch_end: Optional[Callable[[int, float], None]] = None,
parallel: bool = False,
) -> List[float]
Train model in place on items; returns the mean loss of each epoch.
calibration_records ¶
calibration_records(
model,
tok,
items: Sequence[Dict[str, Any]],
device: device,
max_len: int,
head_max_len: int,
batch_size: int = 16,
parallel: bool = False,
)
(qtype, logits, target, k) records for fit_temperature_map, options in canonical order.
calibration_report ¶
calibration_report(
records: Sequence[Tuple[int, Any, Any, int]],
temperature: Sequence[float],
) -> Dict[str, Dict[str, Any]]
Per question type: how many calibration items there were, what was fitted, and what to doubt.
records are the calibration_records the temperatures were fitted on, and temperature
the per-type scalars fit_temperature_map returned. Each type gets items, temperature,
accuracy and mean_confidence (top probability at the fitted temperature) on the
calibration items, and a list of issues, empty when nothing looks wrong:
- fewer than
MIN_TYPE_Nitems: the temperature was not fitted and stays 1.0; - fewer than
CALIB_WARN_Nitems: it was fitted, on little evidence; - it landed on the
[TEMP_MIN, TEMP_MAX]clamp; - it came back unchanged at 1.0 although it was fitted, which usually means the items gave the fit nothing to correct -- what a model already certain and right on them produces.
A type with no calibration items at all has no issues; it was not in the data.
Reading data¶
read_data ¶
read_data(
path: str,
text_column: str = "text",
label_column: str = "label",
question_id: str = "label",
instructions: Optional[str] = None,
) -> List[Dict[str, Any]]
Read training/eval rows from either a JSONL file or a CSV file.
rows_from_csv ¶
rows_from_csv(
path: str,
text_column: str = "text",
label_column: str = "label",
question_id: str = "label",
instructions: Optional[str] = None,
) -> List[Dict[str, Any]]
Read a CSV file with one labelled column per row into {state, questions, expected} rows.
Discovers distinct labels in label_column (preserving order of appearance) and
builds a choice question with those labels as a list of criteria to render each option once.
Uses utf-8-sig encoding so UTF-8 BOM headers from Excel are handled transparently.
Building training items¶
items_from_rows ¶
items_from_rows(
tok,
rows: Iterable[Dict[str, Any]],
max_len: int,
head_max_len: int,
label_smoothing: float = 0.0,
) -> Tuple[List[Dict[str, Any]], Dict[str, int]]
Items from {state, questions, gold} or {state, questions, expected} rows; returns (items, skipped).
skipped counts, by reason, questions that were labelled but could not become an item:
empty_text, empty_label, invalid_question and invalid_target (a ValueError from
to_internal, target_from_gold or target_from_expected), plus the reasons make_item
gives. Questions with neither a gold nor an expected entry are not counted -- the row
simply does not label them.
target_from_gold ¶
The normalised target distribution for an internal question (to_internal), in option order.
gold_q["probabilities"] is keyed by choice label, by "false"/"true" for noul, and by
the level index as a string for score -- the {state, questions, gold} schema in
docs/finetune.md. A choice label that is not a string is also looked up as its JSON key.
A row whose probabilities sum to zero becomes uniform, as in the scripts. A negative or
non-finite probability raises ValueError.
target_from_expected ¶
target_from_expected(
q: Dict[str, Any], expected_val: Any, label_smoothing: float = 0.0
) -> List[float]
The normalised target distribution from a single expected answer, in option order.
expected_val is:
- a choice label (str or value matching a criterion) for choice questions
- a boolean for noul questions
- an integer level index or level string for score questions
When label_smoothing > 0, smooths probability mass uniformly:
target[i] = (1 - eps) * one_hot[i] + eps / K.
to_internal ¶
The question as inference sees it: Agent._check_question then Agent._to_internal.
Training on any other rendering would teach the model option texts it never sees at
inference -- a list-form choice, custom noul labels, a non-string instruction.
make_item ¶
make_item(
tok,
q: Dict[str, Any],
target: Sequence[float],
state_ids: List[int],
head_max_len: int,
max_len: Optional[int] = None,
) -> Tuple[Optional[Dict[str, Any]], Optional[str]]
(item, None), or (None, reason) when the question cannot be trained on as written.
q is an internal question (to_internal). The reasons are target_mismatch (the target
does not have one entry per option), options_collapsed (the head budget left two options
with the same token span, #538) and, when max_len is given, options_beyond_max_len (the
head is longer than max_len, so build_sequence drops the markers of the last options --
the case Agent refuses at inference with "only N of its M option markers fit"). Training on
any of them would teach the model to tell apart options it cannot see as different, or crash
the batch on a target longer than its markers.
encode_item ¶
encode_item(
tok,
item: Dict[str, Any],
max_len: int,
head_max_len: int,
option_order: Optional[List[int]] = None,
parallel: bool = False,
) -> Dict[str, Any]
The model input for an item, with options in option_order and the target to match.
build_sequence puts option option_order[s] in slot s, so the slot-ordered target is
target[option_order[s]] -- the inverse of what unpermute_probs does at inference.
draw_option_order ¶
draw_option_order(
item: Dict[str, Any], rng: Random, shuffle_types: Sequence[str]
) -> Optional[List[int]]
A random permutation for items whose type is in shuffle_types, else None (canonical).
split_calibration ¶
split_calibration(
items: Sequence[Any], calib_max: int, calib_frac: float, seed: int
) -> Tuple[List[Any], List[Any]]
(train, calibration), with the calibration slice taken before any training.
Fitting temperatures on items the run has trained on measures the fit, not the calibration
(docs/finetune.md). The split depends only on seed and the item count, so every DDP rank
and every rerun hold out the same items.
Losses¶
soft_ce_loss ¶
Cross-entropy against a target distribution, over each row's own options only.
rlcd_loss ¶
rlcd_loss(
logits: Tensor,
target: Tensor,
mask: Tensor,
qtype: Tensor,
sigma: float,
samples: int = 4,
w_sph: float = 0.75,
w_rps: float = 1.0,
) -> torch.Tensor
The notebook objective: a policy-gradient term over noisy logits plus soft cross-entropy.
Zero-mean Gaussian noise (scaled by sigma, projected onto each row's options) is added to
detached logits; each sample is rewarded by proper_reward against the target, and the
normalised advantage weights the Gaussian log-density of the sample.
Checkpoints¶
resolve_checkpoint_dir ¶
Resolve a local path, model alias ('multilingual', 'english') or Hub repo ID to a local directory.
load_checkpoint ¶
(model, tokenizer, cfg) from a local checkpoint directory or Hub ID, model on CPU in fp32.
save_checkpoint ¶
Write the layout laya.load reads: weights in fp16, encoder config, tokenizer, config.