Configuration reference¶
One YAML file drives every stage of the pipeline. The schema lives in sommelier/config.py as pydantic models with extra="forbid" on every section, so an unknown or misspelled key anywhere in the file is a ConfigError (SOM201, exit 2), never a silently ignored setting. This page lists every field, its type, its default in code, and what it controls.
Three rules apply before any field is read:
- Duplicate YAML keys are rejected. PyYAML would normally let the last duplicate silently replace earlier values. Sommelier fails with
ConfigErrorinstead, at any mapping depth, before schema or secret validation can observe an overwritten document. - Secrets are rejected.
load_configscans both the raw YAML and the validated dump for secret-shaped values and sensitive key names. A hit raisesSecurityPolicyError(SOM006, exit 5), because config files end up inside published artifacts. Patterns and allowlists are in Security. artifact_rootmust stay relative and contained. Absolute paths and any..component fail schema validation. Before a command uses the path, it resolves the configured root against the config directory and rejects symlinks that escape it. A checkout can move without editing the config, and a configured output cannot be aimed outside it by accident. The explicitrelease preflight --artifact-rootoverride is a separate operator-selected scan target and may intentionally live elsewhere.- The resolved config is an artifact. Every run writes the validated config back out as
config.resolved.yamlat the run root and records its SHA-256 digest asconfig_sha256in every stage manifest and evaluation report. The comparison gate refuses to compare two evaluation reports unless they carry the same digest. Editing one field between the base eval and the adapter eval does not skew the numbers; it prevents the report from existing.
To validate a file without running anything:
Add --write-resolved <dir> to also write config.resolved.yaml and inspect exactly what a run would record. See the CLI reference for the full command surface.
Why so many fields are pinned¶
Several fields are typed as a Literal with exactly one allowed value: data.dedupe_key, formatting.template_policy, formatting.target_format, train.scheduler, train.quantization, train.compute_dtype, eval.split, and eval.parser_version. These are the parts of the reference contract that the published evidence depends on. Pinning them in the type system means loosening one is a code change reviewed in the open, not a config edit that quietly changes what the numbers mean while the file still looks like a Sommelier config. The reasoning per field is in Design decisions.
Top level¶
The file must start with the schema declaration:
| Field | Type | Default | Meaning |
|---|---|---|---|
schema_version |
"sommelier.config.v2" |
required | Identifies the config schema. Any other value fails validation, with one exception below. |
Every section below is required except tracking, which defaults to disabled when omitted.
A file declaring sommelier.config.v1 still loads. The loader upgrades it in memory and emits a DeprecationWarning: the single dataset section becomes the one en entry under datasets, and train.languages and eval.slices resolve to English only. The resolved config written into the run directory is always the v2 form, so two runs of the same settings produce the same config_sha256 whether the file on disk was v1 or v2.
project¶
| Field | Type | Default | Meaning |
|---|---|---|---|
name |
str |
required | A label for the configuration. It travels in config.resolved.yaml; no stage branches on it. |
artifact_root |
Path |
required | Root directory for all run artifacts, relative to and resolved inside the config file's directory. Absolute paths, parent traversal, and escaping symlinks are rejected. |
seed |
int |
42 |
The single seed for the whole run: split shuffling in data prepare, the trainer seed in training, and the seed field recorded in manifests and reports. |
model¶
| Field | Type | Default | Meaning |
|---|---|---|---|
base_model_id |
str |
required | Hugging Face model id of the base model, e.g. nvidia/Llama-3.1-Nemotron-Nano-8B-v1. |
base_model_revision |
str |
required | Revision to load model weights from. Required so the run records what it actually used. |
tokenizer_revision |
str |
required | Revision to load the tokenizer (and its chat template) from. Pinned separately because the prompt rendering depends on it. |
allow_remote_code |
bool |
false |
Passed as trust_remote_code everywhere the tokenizer or model is loaded (formatting, evaluation, training). |
remote_code_reason |
str \| null |
null |
Required whenever allow_remote_code is true; validation fails without it. |
The allow_remote_code and remote_code_reason coupling exists so that executing repository code from the Hub is never a one-character change. Turning it on forces you to write down why, and the reason ships inside the resolved config with everything else.
datasets¶
A non-empty list of per-language dataset sources. Each entry:
| Field | Type | Default | Meaning |
|---|---|---|---|
language |
str |
required | Two-letter lowercase ISO 639-1 code (en, fr, he). At most one source per language. |
dataset_id |
str |
required | Source dataset id, e.g. Salesforce/xlam-function-calling-60k. |
dataset_revision |
str |
required | Dataset revision; stamped into every prepared example as source_revision. |
query_column |
str |
"query" |
Column holding the user query, read when raw rows are exported from the source dataset. |
tools_column |
str |
"tools" |
Column holding the tool schemas as a JSON string. |
answers_column |
str |
"answers" |
Column holding the gold tool calls as a JSON string. |
source_id_column |
str \| null |
null |
When set, names the source-dataset column holding the root row reference; like the other column fields it is read when raw rows are exported, mapping into the canonical source_example_id raw-row field. When null, this is the root source. |
Exactly one source must omit source_id_column: the root source, which gets independent split assignment during data prepare. Every other source is paired, row by row, to root rows through the column this field names. The pairing exists so that a translated variant of a query can never land in a different split than its original, which would leak test content into training. How prepare enforces this is described in Data policy.
For a remote full run, every paired source must use an exact 40- or
64-character Hugging Face dataset commit. A Hebrew publication must contain
the paired rows plus the translation summary, locked semantic-review template,
finalized review, and publication manifest that bind those rows. main, a tag, or a direct
translation-run staging override is rejected before preparation. Smoke runs
may stage a matching diagnostic translation instead.
data¶
| Field | Type | Default | Meaning |
|---|---|---|---|
n_train |
int |
15000 |
Training split size. |
n_validation |
int |
1000 |
Validation split size. |
n_test |
int |
1000 |
Held-out test split size; the only split evaluation reads. |
min_query_chars |
int |
10 |
Rows with shorter queries are dropped (reason query_too_short). |
max_query_chars |
int |
2000 |
Rows with longer queries are dropped (reason query_too_long). |
dedupe_key |
"normalized_query" |
pinned | Deduplication key: the SHA-256 of the query after casefolding, stripping, and whitespace collapsing. Duplicates drop with reason duplicate_query. |
One model-level validator guards this section: the three split counts must be positive, min_query_chars must be positive, and max_query_chars must be strictly greater than min_query_chars. If the surviving rows cannot fill n_train + n_validation + n_test, data prepare fails rather than shrinking a split. The full drop-reason taxonomy is in Data policy.
formatting¶
| Field | Type | Default | Meaning |
|---|---|---|---|
system_prompt |
str |
required | Prepended to the system message, followed by the canonical JSON of the available tools. |
template_policy |
"tokenizer_chat_template" |
pinned | Prompts are rendered by the tokenizer's own chat template, never by a hand-rolled format string. |
target_format |
"json_tool_call" |
pinned | The training target is the canonical JSON of the gold call and nothing else. |
train¶
| Field | Type | Default | Meaning |
|---|---|---|---|
epochs |
int |
2 |
Training epochs over the train split. |
per_device_batch_size |
int |
8 |
Per-device micro-batch size. |
gradient_accumulation_steps |
int |
2 |
Accumulation steps; effective batch is the product of the two. |
learning_rate |
float |
2e-4 |
Peak learning rate. |
scheduler |
"cosine" |
pinned | Learning rate schedule. |
warmup_ratio |
float |
0.03 |
Fraction of steps spent warming up. |
max_sequence_length |
int |
2048 |
Token budget per rendered example. Truncation that would remove every target token is an error, never a silent trim. |
quantization |
"nf4-4bit" |
pinned | Base model quantization for QLoRA. |
compute_dtype |
"bfloat16" |
pinned | Compute dtype during training. |
lora_rank |
int |
16 |
LoRA rank. |
lora_alpha |
int |
32 |
LoRA alpha. |
lora_dropout |
float |
0.05 |
LoRA dropout. |
target_modules |
list[str] |
required | Projection modules the adapter attaches to. No default: the choice is model-specific and belongs in the record. |
languages |
list[str] |
all configured | Languages whose examples feed training. Empty or omitted resolves to every language under datasets, in configuration order; the resolved config always records the explicit list. Every entry must name a configured source. |
None of these are ever adjusted at runtime. If training runs out of GPU memory, the failure is a ResourceError whose hint quotes your current per_device_batch_size, gradient_accumulation_steps, max_sequence_length, and remote.gpu and tells you to change one of them yourself. See Training and Errors.
eval¶
| Field | Type | Default | Meaning |
|---|---|---|---|
split |
"test" |
pinned | Evaluation only ever reads the held-out test split. |
slices |
list[str] |
["en"] |
Language slices to evaluate. Every entry must name a configured source under datasets; duplicates are rejected. |
temperature |
float |
0.0 |
Must be exactly 0.0 at run time; anything else raises EvaluationError. |
do_sample |
bool |
false |
Must be false at run time; true raises EvaluationError. |
max_new_tokens |
int |
512 |
Generation budget per prompt. Must be positive. |
parser_version |
"sommelier.parser.v1" |
pinned | The parser identity recorded in every report; one of the fields the comparison gate matches on. |
temperature and do_sample are plain types rather than Literals because their values are recorded in the decoding block of every evaluation report, which the comparison gate checks field by field. The eval stage still refuses to run with non-deterministic settings; it errors instead of coercing, so the config you wrote is always the config that ran.
remote¶
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled |
bool |
true |
Declares that this config targets remote execution. The current wrapper reads the GPU and planning estimates from this section but does not branch on this flag. |
gpu |
str |
required | Modal GPU type, e.g. A10G or L40S. Recorded in runtime metadata and quoted in out-of-memory hints. |
data_timeout_seconds |
int |
1800 |
Legacy-named planning estimate for data/format/tokenization; not an enforced stage deadline. |
train_timeout_seconds |
int |
14400 |
Legacy-named planning estimate for training; not an enforced stage deadline. A dependency-reported timeout may quote it for context. |
eval_timeout_seconds |
int |
7200 |
Legacy-named planning estimate for each evaluation arm; not an enforced stage deadline. The admission sum counts both arms. |
The three names are retained for config compatibility. The current pipeline is one remote function: it installs no data, train, or eval watchdog. Instead, the driver sums the estimates into an outer-timeout admission floor: data + train + 2 × eval, or data + 2 × eval when evaluating an external adapter. A stage may exceed its estimate and continue as long as the provider-enforced outer function timeout has not expired. The English-only default full estimate totals 45,000 seconds; the Hebrew full estimate totals 81,000 seconds. Their documented launches use the provider maximum of 86,400 seconds. The driver rejects an outer timeout below the applicable planning sum or above the provider limit before loading data or models. Runtime metadata records per_stage_watchdogs_enforced: false so downstream reports cannot mistake the estimates for observed deadlines. Details are in Remote execution.
report¶
| Field | Type | Default | Meaning |
|---|---|---|---|
retain_raw_generations |
bool |
true |
Declared in the schema; the v1 pipeline always writes raw generations regardless, because the evidence requires them. |
redact_fields |
list[str] |
[] |
Key names to replace with [redacted] anywhere in the evaluation and comparison reports before they are written. |
tracking¶
The only optional section. Omitting it is equivalent to writing enabled: false.
| Field | Type | Default | Meaning |
|---|---|---|---|
enabled |
bool |
false |
Opt-in experiment tracking. Disabled is a strict no-op; nothing is imported. |
provider |
"wandb" |
pinned | The only supported provider. Enabling it without wandb installed is an ExternalDependencyError. |
project |
str |
"sommelier" |
Tracking project name. The run URL, when one exists, is recorded in the run manifest. |
The example configs¶
Runnable English-only smoke/full defaults plus a Hebrew v3 smoke/full pair ship in
examples/, plus
config.invalid.yaml,
which exists to demonstrate a validation failure (n_train: -100). All four
use the Nemotron-Nano-8B base, immutable base/tokenizer/root identities, seed
42, the same tool-calling prompt, QLoRA objective, and target modules. The v3
pair adds a Hebrew paired source, tokenizer-cost evidence, and the following
full-run budgets:
| Setting | config.smoke.yaml |
config.full.yaml |
|---|---|---|
| Purpose | Prove the chain end to end, cheaply | Runnable English-only v1/default full run |
| Languages / eval slices | en / en |
en / en |
| Splits (train/val/test) | 100 / 20 / 20 | 15,000 / 1,000 / 1,000 |
| Epochs | 1 | 2 |
| Batch · accumulation | 2 · 1 | 4 · 4 (effective batch 16) |
max_sequence_length |
2048 | 4096 |
| LoRA rank · alpha | 8 · 16 | 16 · 32 |
eval.max_new_tokens |
256 | 512 |
| GPU | A10G | L40S |
| Planning estimates (data/train/each eval) | 900 / 3600 / 1800 s | 1800 / 28800 / 7200 s |
| Outer admission floor from estimates | 8,100 s | 45,000 s; documented launch at 86,400 s |
tracking section |
omitted (disabled by default) | explicit, disabled |
config.full.yaml is the current runnable English-only default. It preserves the published reference run's scale and QLoRA/evaluation settings while pinning immutable base, tokenizer, and root-dataset revisions; any new launch receives its own run identity. The published run's resolved config and checksummed split/report artifacts remain the authority for the historical numbers.
The French v2 result is deliberately not encoded in this default. Its historical paired-dataset commit has a v1 translation summary and no current publication manifest, so strict reproduction under the current full-run contract is unavailable until a provenance-complete immutable republish exists. A future French full config must pin that new commit. Start English changes from config.smoke.yaml or config.full.yaml, and paired Hebrew work from the v3 pair.
| Setting | config.v3-he-smoke.yaml |
config.v3-he-full.yaml |
|---|---|---|
| Purpose | Diagnostic bilingual wiring only | Preregistered Hebrew evidence run |
| Splits (train/val/test) | 100 / 20 / 20 | 15,000 / 1,000 / 1,000 |
| Epochs | 1 | 2 |
| Sequence budget | 2,048 | 4,096 |
| GPU | A10G | L40S |
| Planning estimates (data/train/each eval) | 900 / 3,600 / 1,800 s | 1,800 / 43,200 / 18,000 s |
| Outer admission floor from estimates | 8,100 s | 81,000 s; launch at 86,400 s |
The Hebrew dataset revision remains main only as a visible preregistration
placeholder. It must be replaced with the immutable, provenance-complete
publication commit before any full command can run or any result can be cited.