"What are the three canonical dataset formats for LLM fine-tuning?"	"Raw messages (list of {role, content} dicts); instruction/response (instruction + output columns, the Alpaca/Vicuna shape); ShareGPT/conversational ({conversations: [{from, value}]} with human/gpt role names). All three normalize to the same internal messages representation."	c3::ft04::recall
"What is the internal lingua franca that all three formats normalize to?"	"A single 'messages' column: a list of {'role', 'content'} dicts with roles constrained to system, user, assistant, tool. Whatever your data arrives as, you convert it to this once; every downstream tool (apply_chat_template, TRL, the inspection loop) then works identically."	c3::ft04::recall
"What are the four valid role names in a messages list?"	"system, user, assistant, tool. Anything else (e.g., 'human', 'gpt', 'function') is a bug — those are ShareGPT-era names that must be mapped (human->user, gpt->assistant) during normalization."	c3::ft04::recall
"What does apply_chat_template() do, and why is it the only correct way to format conversations?"	"It renders messages into the model's native byte sequence by running the Jinja2 chat_template shipped with the tokenizer — the exact format the model was instruction-tuned against. It encodes the special tokens, their order, the whitespace, the system-prompt slot. It is the contract between the model and your data."	c3::ft04::recall
"What are the two key arguments to apply_chat_template and when do you use each?"	"tokenize=False returns a string (use when you want to inspect or tokenize separately); add_generation_prompt=True appends the assistant header for INFERENCE prompting (leaves the turn open for the model to fill). For TRAINING you use tokenize=False + add_generation_prompt=False (the full conversation including the closing special tokens)."	c3::ft04::recall
"What is the #1 silent bug in fine-tuning, and why is it silent?"	"Hand-concatenating strings instead of apply_chat_template. It is silent because the loss still drops and the model still trains — but it trains on a slightly out-of-distribution token sequence (wrong whitespace, specials-as-text, wrong role tokens). You blame the data/rank/optimizer for months before finding the one-character whitespace bug."	c3::ft04::analysis
"Name the three failure modes of hand-concatenating chat strings."	"(1) Token-boundary errors: whitespace differences change BPE merges, producing token sequences the model never saw during instruction tuning. (2) Special-token merging: '<|im_start|>' written as raw text becomes ordinary tokens instead of one special ID. (3) Wrong role tokens: ChatML delimiters on a Llama-3 model (or vice versa) fall back to unknown text."	c3::ft04::analysis
"What is the inspection loop, and why is it non-negotiable?"	"After apply_chat_template + tokenizer, call tokenizer.decode(inputs['input_ids'][0]) and READ the decoded text. It is the ground truth of what the model sees. The raw messages are not. Every format bug (missing EOS, trailing spaces, specials-as-text, wrong role tokens) is visible here; none is visible in loss curves. Ten seconds saves ten hours of debugging."	c3::ft04::application
"Show the minimal inspection-loop snippet."	"text = tokenizer.apply_chat_template(messages, tokenize=False); inputs = tokenizer(text, return_tensors='pt'); print(tokenizer.decode(inputs['input_ids'][0])). Read the output. Look for: missing EOS, specials rendered as literal text, trailing spaces, user turns that should be masked from loss."	c3::ft04::application
"How do you confirm a special token is registered as a single token ID (not ordinary text)?"	"Check tokenizer.added_tokens_encoder: if '<|im_start|>' is a key, it maps to a single token ID (correct). If absent, the template's delimiters will be tokenized as ordinary text — a catastrophic model/template mismatch. Print the dict for every special token the template uses before training."	c3::ft04::application
"How do you convert an instruction/response (Alpaca) dataset to messages?"	"Wrap instruction (+ optional input concatenated with a blank line) as a user message and output as an assistant message, via ds.map(): {'messages': [{'role':'user','content':instruction}, {'role':'assistant','content':output}]}. Remove the original columns. Do this once; never leave data as instruction/response for a messages-expecting trainer."	c3::ft04::application
"How do you convert a ShareGPT dataset to messages?"	"Map the from/value keys: {'human':'user','gpt':'assistant','system':'system','tool':'tool'} via ROLE_MAP, rename 'from'->'role' and 'value'->'content'. ds.map over the 'conversations' list. The result is identical to the raw-messages format — proving all three formats normalize to one shape."	c3::ft04::application
"What is the structural difference between SFT data and preference data?"	"SFT data has ONE correct response (messages ending in an assistant turn; cross-entropy loss on that turn). Preference data has TWO responses to the same prompt — chosen and rejected — and trains a contrastive objective (chosen ranked above rejected). The shape signals the objective; mixing them is a bug."	c3::ft04::analysis
"What are the two acceptable shapes for preference (DPO) data?"	"(A) Explicit prompt: {prompt: [{role,content}...], chosen: [{role,content}], rejected: [{role,content}]}. (B) Full conversations: {chosen: [user...assistant], rejected: [user...assistant]}. Both are accepted by TRL DPOTrainer. The defining property: chosen and rejected share the same prompt and differ ONLY in the final assistant turn."	c3::ft04::recall
"What happens if chosen and rejected in preference data differ earlier than the final assistant turn?"	"The dataset is malformed. DPO assumes a shared prompt with divergent completions; if the prefixes differ, the contrastive objective is meaningless and DPO produces nonsense. Validate that chosen and rejected are identical up to the last turn before training."	c3::ft04::analysis
"Why does mixing formats (half instruction/response, half messages) in one dataset cause silent failure?"	"Either the trainer errors (the lucky case) or it silently trains on malformed rows — some examples missing roles, some with wrong column names. The fix is to normalize every example to messages up front, in one pass, before any trainer sees the data."	c3::ft04::analysis
"What is the symptom and root cause of the 'missing EOS' format bug?"	"Symptom: the model never stops generating at inference (runs until max_tokens). Root cause: the last assistant turn was not closed with the model's end token (<|im_end|> for ChatML, <|eot_id|> for Llama-3). Caught by decoding input_ids and checking the final token."	c3::ft04::analysis
"What is the symptom and root cause of 'special tokens rendered as literal text'?"	"Symptom: the model trains on literal '<|im_start|>' as ordinary characters, learns a different vocabulary boundary, behaves like a different model post-training. Root cause: hand-concatenated the string instead of using apply_chat_template (which marks specials as single token IDs via added_tokens_decoder)."	c3::ft04::analysis
"Why does 'loss on user turns' happen, and how is it prevented?"	"The trainer computed loss on the user/system tokens, not just the assistant turn, so the model learns to echo the prompt. Prevented by masking non-assistant tokens: set labels to -100 on user/system turns (TRL's completion_only_loss or assistant_only_loss collators do this). Caught by inspecting labels, not just input_ids."	c3::ft04::analysis
"Why is it wrong to assume the tokenizer 'handles roles'?"	"The tokenizer knows tokens, not roles. The mapping from a role name (user/assistant) to a token sequence is what the chat template encodes. Skip the template and you are guessing at that mapping — and the model was tuned against a specific guess made by its publisher. apply_chat_template is the only way to reproduce that exact guess."	c3::ft04::analysis
"What does add_generation_prompt=True do, and when do you use it?"	"It appends the assistant-role header and leaves the turn open for the model to complete — used at INFERENCE to prompt a response. For TRAINING you set it False so the full conversation (including the closing special tokens / EOS) is rendered. Getting this backwards truncates training data or yields an incomplete inference prompt."	c3::ft04::application
"How does the format substrate connect to the FT00 thesis ('data is the steering wheel')?"	"FT00 says the dataset IS the steering wheel — bad data = bad direction, no matter the optimizer. FT04 is the first module of Pillar 1 (Data) because format errors make the wheel crooked: the model trains on slightly-wrong tokens and steers subtly off-course. apply_chat_template + the inspection loop is how you keep the wheel true."	c3::ft04::analysis
"Why is trailing whitespace before a closing special token a real bug, not cosmetic?"	"It changes the BPE merge at the boundary — the space may merge with the closing token or with the preceding word, producing a token sequence the model never saw during instruction tuning. The model learns assistant turns end with a space, which drifts its stopping behavior. Caught only by decoding input_ids and reading the boundary tokens."	c3::ft04::analysis
