Claude Cursor Skill

dataset-curation

Prepare, format, and validate datasets for supervised fine-tuning and preference training. Use when converting raw data into training format, applying chat templates, configuring sequence packing, generating synthetic training data, or writing a dataset card before a run.

LLM Mart · 0 points · 15 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wshobson-agents-plugins_llm-finetuning_skills_dataset-curation-554237f.zip · 12 KB
Part of wshobson/agents — 170 skills

Install

skills CLI npx skills add https://github.com/wshobson/agents/tree/main/plugins/llm-finetuning/skills/dataset-curation
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wshobson-agents@llmmart
Git git clone https://github.com/wshobson/agents.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wshobson/agents collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Dataset Curation

This skill assumes finetuning-method-selection already routed here — the next step is preparing data, not choosing a method. What follows: format selection by target method, the template/packing mechanics behind the most common silent training failures, rules for mixing in synthetic data without collapse, and the dataset card that closes out Phase 2 before a run starts.

Input: raw examples (demonstrations, preference judgments, or task prompts) plus a routing decision from finetuning-method-selection. Output format: a formatted, packed, validated JSONL dataset plus a completed dataset card — the Phase 2 artifact /finetune checks before launching training.

Format Selection

Method Shape Rows
SFT, single-turn Instruct (instruction/response or prompt/completion) ~1,000+ floor
SFT, multi-turn Conversation / ChatML messages list ~1,000+ floor
DPO / ORPO Preference pair (prompt, chosen, rejected) Method-dependent, see preference-optimization
KTO Unpaired (prompt, completion, label) Method-dependent, see preference-optimization
GRPO / RLVR Prompt-only (prompt + verifier metadata) Method-dependent, see grpo-rlvr-training
  • ~1,000+ rows is the recommended floor for SFT, not a target. Below it, a handful of low-quality or duplicate examples can dominate the gradient; above it, quality over quantity — a smaller verified, deduplicated set beats a larger noisy one.

  • The ChatML shape, for orientation; the other four formats plus a ShareGPT conversion note live in references/formats-and-templates.md:

    {"messages": [
      {"role": "user", "content": "..."},
      {"role": "assistant", "content": "..."}
    ]}
    

Chat Templates and Loss Masking

Apply the target model's chat template before any concatenation or packing, never after — packing raw text and templating the packed blob afterward corrupts turn boundaries, landing role markers in the wrong place relative to each example.

  • Train on assistant responses only. Mask the loss (-100 in the labels tensor) over system/user turns and the template's own role markers — only assistant-turn content tokens contribute to loss.

  • Template/tokenizer mismatches are a top silent failure mode. A model trained against one chat template but served or evaluated with a different one degrades without erroring. Verify the same template string used in training is applied at inference and eval time.

  • Keep the dataset in messages shape and let the trainer template and mask it (assistant_only_loss=True in current TRL) — pre-rendering to a flat text field destroys the turn boundaries masking needs. Full code sketch: references/formats-and-templates.md. Sanity-check before training — decode only unmasked positions; expect only assistant text:

    keep = batch["labels"][0] != -100
    print(tokenizer.decode(batch["input_ids"][0][keep]))
    

Packing

Without packing, 40–70% of compute is spent on padding — variable-length examples batched at a fixed sequence length waste the gap between each example's length and the batch's max. Packing concatenates multiple examples into one sequence up to the max length, cutting most of that waste.

  • Packing changes batch semantics. A packed sequence can contain several original examples, so "steps per epoch" and any LR schedule keyed to example count shift once packing is on — recompute schedule milestones against packed-sequence count.

  • MANDATORY: decode and manually inspect 5–10 packed sequences before scaling to a full run. Confirm example boundaries land where expected, template markers are intact per sub-example, and the loss mask is still assistant-only within each packed sequence. Not optional — packing bugs are silent (the loss curve looks normal) and only surface in eval quality, hours later:

    for seq in packed_dataset.select(range(10)):
        print(tokenizer.decode(seq["input_ids"]))
    

Synthetic Data Rules

  • Keep ≥25% real data as a collapse guard. Training on a growing share of model-generated data without a real-data floor drives measurable quality collapse over successive generations — 25% real is the minimum that holds the line. General-domain replay rows count toward this floor — "real" means "not generated for this task from this student," not "human-authored." An all-synthetic-by-construction dataset can meet the ≥25% floor through replay alone (see references/synthetic-data.md's Replay-Mix Construction recipe); state which rows count as "real" in the dataset card rather than leaving the floor structurally unmeetable.
  • Magpie and rejection sampling are the workhorses. Magpie extracts prompts from the model's own template prior; rejection sampling generates several candidates per prompt and keeps only the ones a filter passes. Both beat naive single-shot generation.
  • Targeted, student-aware generation beats static generation by 1.3–2x sample efficiency — aiming at the student's actual failure modes hits a quality bar with fewer filtered examples.
  • Typical accept rates after filtering run 10–30%. Plan volume accordingly — a 10,000-row target at 15% accept needs ~65,000+ raw generations.
  • Generation-method ranking, filter funnel, replay- mix construction, and distillation pattern: references/synthetic-data.md.

The Dataset Card

Every dataset that reaches training gets a card — the required Phase 2 artifact /finetune checks before launching. The card is not free-form documentation; it MUST carry these fields:

  • Provenance — where every row came from (real source(s), synthetic method(s), or both), traceable to trace-to-training-data output.
  • Counts — total rows, and rows per split (train/eval/held-out) if split.
  • Synthetic/real ratio — the measured ratio, checked against the ≥25% real floor above.
  • Dedup method — exact-match, semantic (embedding threshold), or both; see the filter funnel in references/synthetic-data.md.
  • Template used — the exact chat template string/identifier, kept consistent through inference and eval — this is what ties an eval-harness-first run back to the checkpoint.
  • Packing config — whether packing was used, max sequence length, and confirmation the 5–10-sequence manual inspection above was done.

A dataset missing any of these six fields isn't ready for /finetune — the card is a gate, not a summary written after the fact.

Phase 2 Exit Checklist

Before handing off to /finetune, confirm:

  1. Format matches the method (table above).
  2. Template applied before concatenation.
  3. Loss masked to assistant turns only.
  4. 5–10 packed sequences decoded and read.
  5. ≥25% real data in the final mix.
  6. Dataset card complete — all six fields.

References

  • references/formats-and-templates.md — JSONL examples per format, current-TRL masking code, and the ShareGPT conversion note.
  • references/synthetic-data.md — generation-method ranking, filter funnel, replay-mix construction, and teacher→student distillation pattern.

Related skills: finetuning-method-selection routes here; lora-qlora-recipes, vision-sft, and preference-optimization consume the datasets this skill produces; trace-to-training-data is the provenance source for graded-trajectory datasets; eval-harness-first grades the resulting checkpoint.

Files (agents)
  • references
    • formats-and-templates.md 6.4 KB
      # Dataset Formats and Template Application
      
      Concrete JSONL examples for every format in
      `SKILL.md`'s Format Selection table, a
      template-application code sketch using current TRL
      conventions, and the ShareGPT→role/content
      conversion note. Base models are never named here —
      every code example uses a `BASE_MODEL` placeholder;
      see `finetuning-method-selection`'s
      `references/model-catalog.md` for which actual
      checkpoint to load.
      
      ## Instruct (SFT, Single-Turn)
      
      One JSONL row per example. Either key pair works;
      pick one and use it consistently across the dataset:
      
      ```json
      {"instruction": "Summarize the following text in one sentence.", "input": "Q3 revenue grew 14% year-over-year, driven primarily by...", "output": "Q3 revenue grew 14% YoY on strong core-segment demand."}
      ```
      
      ```json
      {"prompt": "Summarize the following text in one sentence: Q3 revenue grew 14%...", "completion": "Q3 revenue grew 14% YoY on strong core-segment demand."}
      ```
      
      ## ChatML Conversation (SFT, Multi-Turn)
      
      A `messages` list per row — the shape `SFTTrainer`
      templates and loss-masks natively (see Applying the
      Chat Template below):
      
      ```json
      {"messages": [
        {"role": "system", "content": "You are a concise technical assistant."},
        {"role": "user", "content": "What does a KV cache do?"},
        {"role": "assistant", "content": "It stores attention keys/values from prior tokens so decoding doesn't recompute them each step."},
        {"role": "user", "content": "Does it grow with context length?"},
        {"role": "assistant", "content": "Yes, linearly — that's why long-context serving is memory-bound on cache size, not compute."}
      ]}
      ```
      
      Only the final two `assistant` turns' content
      tokens should carry loss after masking — see
      `SKILL.md`'s Chat Templates and Loss Masking
      section.
      
      ## DPO / ORPO — Chosen/Rejected Pair
      
      ```json
      {"prompt": "Explain why the sky is blue.", "chosen": "Sunlight scatters off air molecules; shorter (blue) wavelengths scatter more, so blue dominates what reaches your eyes from all directions.", "rejected": "Because the sky reflects the ocean."}
      ```
      
      `chosen` and `rejected` are both full responses to
      the same `prompt` — not a diff or a ranking score.
      See `preference-optimization`'s Pair Construction
      section for how to select `rejected` from a graded
      trajectory set (μ−2σ of the reward distribution,
      not the naive minimum).
      
      ## KTO — Unpaired Binary Feedback
      
      ```json
      {"prompt": "Draft a one-line commit message for a null-check fix.", "completion": "Fix null pointer exception in user lookup", "label": true}
      ```
      
      ```json
      {"prompt": "Draft a one-line commit message for a null-check fix.", "completion": "misc changes", "label": false}
      ```
      
      No pairing between rows is required or expected —
      `label: true` marks desirable, `label: false`
      undesirable. A healthy KTO dataset needs both
      labels represented across the set.
      
      ## GRPO / RLVR — Prompt-Only
      
      ```json
      {"prompt": "Solve: 17 * 24 = ?", "answer": "408", "verifier": "exact_match"}
      ```
      
      No response is stored — GRPO samples completions
      from the policy at train time and scores them
      against `answer` via the named verifier (or a
      reward function). See `grpo-rlvr-training` for
      reward-function design and the manual-inspection
      requirement before a GRPO run.
      
      ## Applying the Chat Template (Current TRL API)
      
      **Keep the dataset in `messages` shape and let
      `SFTTrainer` apply the template.** Do not
      pre-render conversations to a flat text field —
      flattening destroys the message boundaries TRL
      needs to mask loss to assistant turns. Given a
      `messages`-shaped dataset, current TRL applies
      the tokenizer's chat template per example (before
      any packing concatenation, satisfying `SKILL.md`'s
      template-before-concatenation rule) and masks loss
      to assistant spans when `assistant_only_loss=True`:
      
      ```python
      from transformers import AutoTokenizer
      from trl import SFTConfig, SFTTrainer
      
      tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
      
      sft_args = SFTConfig(
          output_dir="./outputs-sft",
          max_length=2048,
          packing=True,               # see SKILL.md Packing section before enabling
          assistant_only_loss=True,   # mask loss to assistant turns
      )
      
      trainer = SFTTrainer(
          model=BASE_MODEL,
          args=sft_args,
          train_dataset=dataset,       # messages-shaped — no pre-rendered text field
          processing_class=tokenizer,  # current TRL — not tokenizer=
      )
      ```
      
      (`processing_class`, not `tokenizer=` — see
      `lora-qlora-recipes`'s
      `references/unsloth-trl-mapping.md` for the full
      Unsloth↔TRL kwarg mapping.)
      
      `assistant_only_loss=True` requires the tokenizer's
      chat template to mark assistant spans (the
      `{% generation %}` keyword). If the template lacks
      it, TRL raises rather than silently training on
      everything — fix the template, don't fall back to
      flat text.
      
      `apply_chat_template(..., tokenize=False)` is still
      the right tool for *inspecting* what the template
      produces — decode-and-read checks like the packing
      inspection in `SKILL.md` — just not for building
      the training dataset.
      
      ### The Flat-Text Path Does NOT Mask
      
      The older pattern — pre-rendering each conversation
      with `apply_chat_template(..., tokenize=False)`
      into a `text` column and pointing
      `SFTConfig(dataset_text_field="text")` at it —
      still runs, but computes loss over the **entire
      sequence**, user turns and template markers
      included. That is exactly the silent
      train-on-everything failure `SKILL.md`'s Chat
      Templates and Loss Masking section warns about.
      It is only appropriate when full-sequence loss is
      actually intended (CPT-style continued pretraining
      on raw text), never for conversational SFT.
      
      ## ShareGPT → role/content Conversion
      
      Older datasets often ship in ShareGPT's
      `conversations` shape (`from`/`value` keys, `human`/
      `gpt` roles) rather than the `messages`
      (`role`/`content`) shape current TRL expects.
      Convert before templating, not during:
      
      ```python
      ROLE_MAP = {"human": "user", "gpt": "assistant", "system": "system"}
      
      def sharegpt_to_messages(example):
          messages = [
              {"role": ROLE_MAP[turn["from"]], "content": turn["value"]}
              for turn in example["conversations"]
          ]
          return {"messages": messages}
      
      dataset = dataset.map(sharegpt_to_messages, remove_columns=["conversations"])
      ```
      
      Run this conversion — and spot-check a handful of
      converted rows — before the Chat Templates section's
      "apply before concatenation" rule applies; a
      ShareGPT dataset that gets packed or templated
      still in `from`/`value` shape produces malformed
      turns that a template call won't error on.
      
    • synthetic-data.md 12.5 KB
      Last verified: 2026-07-14
      
      # Synthetic Data: Generation, Filtering, Distillation
      
      Full detail backing `SKILL.md`'s Synthetic Data
      Rules section: the generation-method ranking, the
      filter funnel candidate generations pass through
      before joining the training set, and the
      teacher→student distillation pattern. Base models
      are never named as recommendations here — `TEACHER`
      and `STUDENT` are placeholders for whichever
      checkpoints a given run uses; see
      `finetuning-method-selection`'s
      `references/model-catalog.md` for actual model
      choice.
      
      ## Generation-Method Ranking
      
      Methods below are ordered roughly weakest to
      strongest for sample efficiency and downstream
      quality at the same generation budget. Each level
      subsumes the previous — a rejection-sampling
      pipeline typically generates its candidates with
      Magpie or persona-conditioned prompts underneath,
      rather than replacing them:
      
      1. **Self-Instruct** — bootstrap new prompts from a
         small seed set by having a model paraphrase and
         extend them. Cheapest, weakest: prompt diversity
         plateaus quickly and quality tracks the seed set
         closely.
      2. **Evol-Instruct** — iteratively rewrite prompts
         to increase complexity (add constraints, deepen
         reasoning, broaden scope) across generations.
         Improves difficulty coverage over Self-Instruct
         but still seed-dependent.
      3. **Magpie** — extract prompts directly from the
         target model's own chat-template prior by
         sampling from the template's user-turn position
         with no seed prompt at all. Removes seed-set bias
         entirely; this is why it's a workhorse rather
         than a niche technique.
      4. **Persona-conditioned generation** — condition
         prompt generation on a sampled persona/role
         description to broaden style and topic coverage
         beyond what a single generation policy produces
         unconditioned.
      5. **Rejection sampling / verifier-filtered** —
         generate multiple candidate completions per
         prompt and keep only those a filter, verifier, or
         judge accepts. This is the other workhorse from
         `SKILL.md`, and it composes with any of the
         prompt-generation methods above — it's a
         completion-side filter, not a prompt-generation
         method by itself.
      
      **Targeted, student-aware generation** — steering
      prompt or persona selection toward the current
      student model's actual failure modes rather than
      sampling uniformly — layers on top of any method
      above and is what delivers the 1.3–2x sample
      efficiency gain cited in `SKILL.md`. It requires an
      eval signal on the student to know what its failure
      modes currently are; without that signal, generation
      defaults to untargeted/static.
      
      ## Filter Funnel
      
      Apply filters in this order — each stage is
      cheaper than the next, so cheap stages should
      eliminate volume before expensive stages run on
      what's left:
      
      1. **Exact dedup.** Hash-based exact-match removal
         of identical rows (after normalization —
         whitespace/casing collapsed before hashing).
         Cheapest stage, run first, removes generation-loop
         repeats before anything downstream sees them.
      2. **Semantic dedup (~0.92 similarity threshold).**
         Embed each candidate and drop rows whose
         nearest-neighbor cosine similarity to an
         already-kept row exceeds ~0.92. Catches
         paraphrase-level duplicates exact dedup misses.
      3. **Length filter.** Drop candidates below a
         minimum or above a maximum token length for the
         task — too-short responses are usually degenerate,
         too-long ones are usually rambling or off-task.
      4. **Language ID filter.** Drop candidates that
         fail a language-ID check against the target
         language(s) — generation occasionally drifts
         language, especially from multilingual base
         models on under-specified prompts.
      5. **Score-based top-30% filter.** Score remaining
         candidates (reward model, heuristic, or
         self-consistency score) and keep roughly the
         top 30% — this is a coarse quality cut before the
         most expensive stage runs.
      6. **Judge ≥ threshold.** Run the most expensive
         check last, on the smallest remaining set: an
         LLM-judge or human-equivalent quality check
         against a fixed threshold. Rows that fail here
         are dropped regardless of how they scored
         upstream.
      
      The **10–30% typical accept rate** from `SKILL.md`
      is the funnel's end-to-end yield across all six
      stages, not any single stage's pass rate — budget
      raw generation volume against the full-funnel yield,
      not against any one stage's rate.
      
      ## Teacher→Student Distillation Pattern
      
      1. **Generate teacher traces.** Sample completions
         (with reasoning traces, where applicable) from
         `TEACHER` against the target task's prompt
         distribution.
      2. **Verify.** Run the traces through the Filter
         Funnel above — a distillation set is a synthetic
         dataset like any other and still needs the
         ≥25% real-data floor from `SKILL.md` respected
         in the final training mix, plus the same dedup
         and quality stages.
      3. **SFT the student.** Train `STUDENT` on the
         verified traces using the standard SFT format
         and template rules from `SKILL.md` and
         `references/formats-and-templates.md` — a
         distillation dataset is not a special format,
         it's a provenance label on an otherwise-ordinary
         instruct or ChatML dataset.
      
      Record `TEACHER` identity and generation
      configuration (sampling temperature, prompt
      template used to elicit traces) in the dataset
      card's provenance field — "distilled from `TEACHER`"
      is a provenance fact `/finetune` and downstream
      audits both expect to find there, not something to
      leave implicit.
      
      ## Replay-Mix Construction
      
      The implementation recipe behind
      `checkpoint-promotion`'s catastrophic-forgetting
      escalation ladder (`SKILL.md`'s owning document for
      *when* and *how far* to move the replay fraction —
      this section covers *how to build the rows*, the
      single most common REJECT remediation and the part
      most often improvised ad hoc under time pressure).
      Five decisions, in the order they come up:
      
      ### 1. General-Domain Source Selection
      
      Pick a source that is genuinely general-domain for
      the capability being protected, not a narrow slice
      that happens to be convenient. Two failure modes to
      avoid:
      
      - **Don't teach to the gate.** If the replay source
        is drawn from the exact same distribution as the
        drift suite's benchmarks (e.g. the same GSM8K
        train split the drift suite's test split comes
        from), the resulting drift score partially
        measures "did this model see similar items in
        training," not "did fine-tuning preserve the
        underlying capability." This is not automatically
        disallowed — see `checkpoint-promotion`'s
        instruction-reuse disclosure rule — but it must be
        disclosed, and a broader source (not scoped to the
        drift suite's own benchmarks) is the more
        defensible default when one exists.
      - **Match the source to the forgetting signature.**
        If error analysis on the failing checkpoint shows
        a specific lost capability (e.g. chain-of-thought
        math reasoning, not general knowledge), a replay
        source targeting that capability recovers it
        faster than a generic instruct-tuning mix — but
        narrows the "general-domain" claim; state in the
        dataset card which capability the replay mix
        targets and why.
      
      ### 2. Prompt Shape
      
      Decide what shape replay rows take in the
      messages-shaped SFT set — this is a real choice,
      not a detail:
      
      - **Natural instruction** — however the source
        data's own prompts are phrased. Lowest effort,
        least targeted.
      - **The drift harness's exact phrasing** — matches
        the eval's instruction wording. Most directly
        addresses an instruction-following forgetting
        signature (e.g. "ignores the show-your-work
        instruction"), but triggers the instruction-reuse
        disclosure rule in `checkpoint-promotion` and
        inflates the post-replay score on that specific
        benchmark.
      - **Bare input, no instruction wrapper** — closest
        to raw continued-pretraining signal; weakest at
        restoring instruction-following specifically.
      
      Pick based on the forgetting signature from error
      analysis, not by default — and disclose the choice
      in the dataset card regardless of which one.
      
      ### 3. Answer Reformatting
      
      Decide whether replay reference answers get
      reformatted toward the target task's output
      convention, or kept in the source format as-is.
      Example: rewriting a math dataset's `#### N`
      final-answer terminator to match the target
      task's own extraction convention. This is a
      judgment call that changes what the model learns
      to emit on replay-domain prompts — record the
      exact transformation applied (or "none — kept
      source format") in the dataset card, since it
      changes what a downstream error-analysis pass
      should expect to see.
      
      ### 4. Val-Split Treatment
      
      Decide whether the validation split gains replay
      rows or stays task-only:
      
      - **Task-only val split** keeps `eval_loss` directly
        comparable across runs that only differ in replay
        fraction — the training loop has zero visibility
        into replay fit, and replay recovery is only
        measurable at the next Phase 5 re-gate.
      - **Replay rows in val too** gives in-loop visibility
        into replay fit, at the cost of `eval_loss` no
        longer being an apples-to-apples comparison against
        a prior run's task-only val split.
      
      Neither is universally correct; state which was
      chosen and why in the dataset card, and don't
      compare `eval_loss` across runs that made different
      choices here without noting the confound.
      
      ### 5. Disjointness Verification
      
      Before training, verify replay rows don't overlap
      the drift suite or the goldens set — required, not
      optional, regardless of which source was picked in
      step 1:
      
      - **Split-level separation** — draw replay rows only
        from a source split (e.g. a train split) disjoint
        from whatever split the drift suite's items are
        drawn from.
      - **Exact-match text filter** — normalize and
        exact-match replay row question/prompt text
        against the drift suite's selected items and
        `eval/goldens.jsonl`; drop any hit. Record the
        overlap count found (expect 0) in the dataset card
        — a nonzero count found and silently dropped is
        still worth recording, since it signals the source
        pool needs a tighter split boundary next time.
      
      ### When a Later Run Changes the Replay Fraction: Swap, Don't Add
      
      If a checkpoint-promotion gate calls for moving the
      replay fraction (see `checkpoint-promotion`'s
      escalation ladder), implement the change by **swapping
      rows, not adding them**: drop target-task rows out of
      the training set as replay rows go in, so the total
      row/step count holds constant between the old and new
      run. Adding replay rows on top of the existing set
      changes replay fraction and total optimizer steps in
      the same move, making it impossible to attribute a
      later drift-score change to either variable alone —
      this confound has produced misleading run-to-run
      trajectories in practice, so treat swap-not-add as a
      hard rule for this recipe, not a style preference.
      
      **Row count is not token count.** Swapping rows
      1-for-1 holds the *row* count constant, but replay
      rows and target-task rows are rarely the same length —
      a swap can still shift total training tokens (and
      therefore `max_steps` under a fixed batch size and
      sequence-packing scheme) even though the row count
      didn't move. Hold total training tokens, or `max_steps`
      directly, constant between the old and new run — not
      just row count — and record the packed-token count
      for each run (not just the row count) in the dataset
      card before attributing a drift-score change to the
      replay-fraction change alone. A run that swapped rows
      but grew packed tokens 10% has the same attribution
      problem as one that added rows outright.
      
      ## Synthetic-Only Datasets and the ≥25% Real Floor
      
      `SKILL.md`'s Synthetic Data Rules require ≥25% real
      data as a collapse guard. When a training set is
      100% synthetic by construction (a greenfield task
      with no real-data pool to draw from at all — not
      merely a lot of synthetic augmentation on top of a
      real base), that floor is unmeetable by definition
      unless something in the mix counts as "real."
      
      **Resolution: general-domain replay rows count
      toward the ≥25% floor.** "Real" in this rule means
      "not generated for this specific task from this
      specific student model" — a replay row pulled from
      an existing general-instruct dataset (human-authored
      or otherwise pre-existing, not freshly generated by
      the student or its teacher for this run) satisfies
      that definition even though the target-task rows
      around it are 100% synthetic. Build the replay mix
      per the five decisions above, then compute the
      synthetic/real ratio the dataset card requires
      treating replay rows as the "real" share — and state
      explicitly in the card that this is how the ratio
      was met, so a later audit doesn't misread an
      all-synthetic-target-data run as having silently
      skipped the collapse guard.
      
  • SKILL.md 7.8 KB
    ---
    name: dataset-curation
    description: Prepare, format, and validate datasets for supervised fine-tuning and preference training. Use when converting raw data into training format, applying chat templates, configuring sequence packing, generating synthetic training data, or writing a dataset card before a run.
    ---
    
    # Dataset Curation
    
    This skill assumes `finetuning-method-selection`
    already routed here — the next step is preparing
    data, not choosing a method. What follows: format
    selection by target method, the template/packing
    mechanics behind the most common silent training
    failures, rules for mixing in synthetic data
    without collapse, and the dataset card that closes
    out Phase 2 before a run starts.
    
    **Input:** raw examples (demonstrations, preference
    judgments, or task prompts) plus a routing decision
    from `finetuning-method-selection`.
    **Output format:** a formatted, packed, validated
    JSONL dataset plus a completed dataset card — the
    Phase 2 artifact `/finetune` checks before launching
    training.
    
    ## Format Selection
    
    | Method | Shape | Rows |
    |---|---|---|
    | SFT, single-turn | Instruct (`instruction`/`response` or `prompt`/`completion`) | ~1,000+ floor |
    | SFT, multi-turn | Conversation / ChatML `messages` list | ~1,000+ floor |
    | DPO / ORPO | Preference pair (`prompt`, `chosen`, `rejected`) | Method-dependent, see `preference-optimization` |
    | KTO | Unpaired (`prompt`, `completion`, `label`) | Method-dependent, see `preference-optimization` |
    | GRPO / RLVR | Prompt-only (`prompt` + verifier metadata) | Method-dependent, see `grpo-rlvr-training` |
    
    - **~1,000+ rows is the recommended floor for SFT**,
      not a target. Below it, a handful of low-quality
      or duplicate examples can dominate the gradient;
      above it, **quality over quantity** — a smaller
      verified, deduplicated set beats a larger noisy one.
    - The ChatML shape, for orientation; the other four
      formats plus a ShareGPT conversion note live in
      `references/formats-and-templates.md`:
    
      ```json
      {"messages": [
        {"role": "user", "content": "..."},
        {"role": "assistant", "content": "..."}
      ]}
      ```
    
    ## Chat Templates and Loss Masking
    
    Apply the target model's chat template **before**
    any concatenation or packing, never after — packing
    raw text and templating the packed blob afterward
    corrupts turn boundaries, landing role markers in
    the wrong place relative to each example.
    
    - **Train on assistant responses only.** Mask the
      loss (`-100` in the labels tensor) over system/user
      turns and the template's own role markers — only
      assistant-turn content tokens contribute to loss.
    - **Template/tokenizer mismatches are a top silent
      failure mode.** A model trained against one chat
      template but served or evaluated with a different
      one degrades without erroring. Verify the same
      template string used in training is applied at
      inference and eval time.
    - **Keep the dataset in `messages` shape** and let
      the trainer template and mask it
      (`assistant_only_loss=True` in current TRL) —
      pre-rendering to a flat text field destroys the
      turn boundaries masking needs. Full code sketch:
      `references/formats-and-templates.md`. Sanity-check
      before training — decode only unmasked positions;
      expect only assistant text:
    
      ```python
      keep = batch["labels"][0] != -100
      print(tokenizer.decode(batch["input_ids"][0][keep]))
      ```
    
    ## Packing
    
    **Without packing, 40–70% of compute is spent on
    padding** — variable-length examples batched at a
    fixed sequence length waste the gap between each
    example's length and the batch's max. Packing
    concatenates multiple examples into one sequence
    up to the max length, cutting most of that waste.
    
    - **Packing changes batch semantics.** A packed
      sequence can contain several original examples, so
      "steps per epoch" and any LR schedule keyed to
      example count shift once packing is on — recompute
      schedule milestones against packed-sequence count.
    - **MANDATORY: decode and manually inspect 5–10
      packed sequences before scaling to a full run.**
      Confirm example boundaries land where expected,
      template markers are intact per sub-example, and
      the loss mask is still assistant-only within each
      packed sequence. Not optional — packing bugs are
      silent (the loss curve looks normal) and only
      surface in eval quality, hours later:
    
      ```python
      for seq in packed_dataset.select(range(10)):
          print(tokenizer.decode(seq["input_ids"]))
      ```
    
    ## Synthetic Data Rules
    
    - **Keep ≥25% real data as a collapse guard.**
      Training on a growing share of model-generated
      data without a real-data floor drives measurable
      quality collapse over successive generations —
      25% real is the minimum that holds the line.
      **General-domain replay rows
      count toward this floor** —
      "real" means "not generated
      for this task from this
      student," not "human-authored."
      An all-synthetic-by-construction
      dataset can meet the ≥25% floor
      through replay alone (see
      `references/synthetic-data.md`'s
      Replay-Mix Construction recipe);
      state which rows count as "real"
      in the dataset card rather than
      leaving the floor structurally
      unmeetable.
    - **Magpie and rejection sampling are the
      workhorses.** Magpie extracts prompts from the
      model's own template prior; rejection sampling
      generates several candidates per prompt and keeps
      only the ones a filter passes. Both beat naive
      single-shot generation.
    - **Targeted, student-aware generation beats static
      generation by 1.3–2x sample efficiency** — aiming
      at the student's actual failure modes hits a
      quality bar with fewer filtered examples.
    - **Typical accept rates after filtering run
      10–30%.** Plan volume accordingly — a 10,000-row
      target at 15% accept needs ~65,000+ raw generations.
    - Generation-method ranking, filter funnel, replay-
      mix construction, and distillation pattern:
      `references/synthetic-data.md`.
    
    ## The Dataset Card
    
    Every dataset that reaches training gets a card —
    the required Phase 2 artifact `/finetune` checks
    before launching. The card is not free-form
    documentation; it MUST carry these fields:
    
    - **Provenance** — where every row came from (real
      source(s), synthetic method(s), or both),
      traceable to `trace-to-training-data` output.
    - **Counts** — total rows, and rows per split
      (train/eval/held-out) if split.
    - **Synthetic/real ratio** — the measured ratio,
      checked against the ≥25% real floor above.
    - **Dedup method** — exact-match, semantic
      (embedding threshold), or both; see the filter
      funnel in `references/synthetic-data.md`.
    - **Template used** — the exact chat template
      string/identifier, kept consistent through
      inference and eval — this is what ties an
      `eval-harness-first` run back to the checkpoint.
    - **Packing config** — whether packing was used,
      max sequence length, and confirmation the
      5–10-sequence manual inspection above was done.
    
    A dataset missing any of these six fields isn't
    ready for `/finetune` — the card is a gate, not a
    summary written after the fact.
    
    ### Phase 2 Exit Checklist
    
    Before handing off to `/finetune`, confirm:
    
    1. Format matches the method (table above).
    2. Template applied before concatenation.
    3. Loss masked to assistant turns only.
    4. 5–10 packed sequences decoded and read.
    5. ≥25% real data in the final mix.
    6. Dataset card complete — all six fields.
    
    ## References
    
    - `references/formats-and-templates.md` — JSONL
      examples per format, current-TRL masking code,
      and the ShareGPT conversion note.
    - `references/synthetic-data.md` — generation-method
      ranking, filter funnel, replay-mix construction,
      and teacher→student distillation pattern.
    
    Related skills: `finetuning-method-selection` routes
    here; `lora-qlora-recipes`, `vision-sft`, and
    `preference-optimization` consume the datasets this
    skill produces; `trace-to-training-data` is the
    provenance source for graded-trajectory datasets;
    `eval-harness-first` grades the resulting checkpoint.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related