I. The Methodological Skeleton of Synthetic Data: Not a Cheap Substitute for Human Data

1.1 The Question This Article Is Trying to Answer

Nearly every team that does post-training runs into the same scenario at some point in the year: real production data suddenly runs short. Either collection costs too much, or the distribution of user behaviors is too narrow, or the new product form simply has no historical data to collect. The most natural move is then to "have the model make a batch of its own" and drop the generations into SFT. That step usually works, but nearly every team hits the same cluster of symptoms three months later: the more synthetic data you add, the stronger the model gets on the internal eval set, and the harder it falls on real traffic once deployed; after two or three rounds of iteration, the model starts making the same new mistake reliably — and that mistake is completely invisible in its own outputs.

These symptoms are not engineering accidents; they are a methodological gap. The real problem with synthetic data has never been "the quality is not good enough." It is that when you treat model-generated content as a training signal, you simultaneously treat the model's misunderstanding of the world as gold. A model can only generate what it considers possible; it cannot generate the things it does not know it does not know. This structural limitation determines one thing: the legitimacy of synthetic data does not come from who generated it, but from what signal adjudicates it.

So this article is not about "how to write better prompts so the model generates more data." It is about a complete chain: generate, verify, mix, monitor. The value of this framework is that it turns synthetic data from a one-off trick into a sustainable engineering system — every stage has an explicit failure mode, an explicit metric, and an explicit exit condition.

1.2 The Four Stages, and Why the Order Cannot Be Reversed

Break this chain apart and there are only four stages, but their order is a hard constraint:

  1. Generate: use some model to produce candidate samples from a pool of prompts. The key variable is the distribution of the prompt pool, not how clever the generating model is.
  2. Verify: use a signal independent of the generation process to adjudicate the correctness and quality of each candidate. The key variable is the externality of the verification signal — whether it is genuinely independent of the generator.
  3. Mix: place verified synthetic data alongside real data according to some ratio and some bucketing strategy. The key variable is that three independent axes get conflated.
  4. Monitor: keep observing on a real held-out set, detecting distribution collapse, capability forgetting, and verifier drift. The key variable is that the stopping condition must be defined on the real set rather than the synthetic set.

The order is a hard constraint because every downstream stage's metrics depend on the artifacts of the stage before it. The truncation rate and repetition rate at the generation layer are only meaningful when a verification layer filters on them; the misjudgment rate at the verification layer can only be calibrated against a real spot-check set; the bucketing strategy at the mixing layer can only be optimized once bucketing metrics exist; the stopping condition at the monitoring layer only becomes fully interpretable once data from the first four stages of the funnel is complete. Skip any stage and the "improvements" that follow usually just push the noise further downstream.

This article follows that order. The emphasis is not on the technical details of any single step, but on the failure modes of each step — because in a synthetic data pipeline, the overwhelming majority of "things got worse" events can be traced to a concrete failure mode at some layer, not to mysticism.

II. The Self-Distillation and Rejection Sampling Loop

2.1 The Four Elements of the Loop

The literal meaning of self-distillation is "train a model on data the model produced itself." In practice it never runs naked; it is always a loop:

  • Prompt source: a batch of seed tasks. Its distribution determines the ceiling of the entire pipeline — the model will not generate task types that do not exist in the prompt pool.
  • Sampling strategy: temperature, top-p, number of samples per problem, whether to sample independently multiple times. It determines how much probability mass the candidate set covers.
  • Verification signal: rule checks, unit tests, execution results, reference answer comparison, process-level scoring models, external tool returns. This is the only thing in the whole loop qualified to serve as "gold."
  • Feedback policy: which passing samples enter the training set, the deduplication rules, what bucketing ratio they are mixed in at, and whether historical data from multiple iterations is retained.

Rejection sampling (best-of-N) is the most plain and also the most effective form in this loop: generate N candidates, use the verification signal to select the ones that pass, discard the rest, and use the survivors as SFT data. Its value does not lie in "producing the right answer" — the right answer often already exists — but in concentrating the training distribution on the region near the model's capability boundary. This is the core idea the next section expands on, and the single most valuable lesson in the whole methodology.

The essence of rejection sampling is reweighting: moving gradient budget off the easy samples the model already masters one hundred percent of the time, and onto the region where the success rate runs from 10% to 50%. Under a limited training budget, the payoff from this shift is usually far larger than the payoff from generating more in-distribution data.

2.2 What STaR, ReST, SPIN, and RAFT Each Patch

There are several repeatedly cited variants along the self-distillation line. They are not different phrasings of the same thing; each one repairs a different hole in the baseline approach. Understanding how they differ is far more useful than memorizing the names.

STaR (Self-Taught Reasoner) addresses the problem "the right answer exists but the reasoning process is missing." Its approach is to let the model give the answer directly first, and if the answer is correct, require it to work backward into a reasoning chain that reaches that answer; then treat that chain as the supervision signal. Its implicit assumption is that although the model cannot solve the problem head-on, it can at least recognize what a correct solution looks like, so it can approximate one through "backward generation." That assumption holds up reasonably well on simple reasoning tasks and is fragile on tasks that require multi-step search.

ReST (Reinforced Self-Training) addresses the problem "data comes only from the current model, so the distribution never expands." It alternates between two phases: run several rounds of rejection sampling and retrain from scratch (the expectation-maximization step), then resample — but the new round of sampling can produce problems that only the model, after the previous round of training, is able to solve. That is bootstrapping. This "EM view" is apt: the expectation step locks in good experience, the maximization step expands the solvable set.

SPIN (Self-Play) addresses the problem "positive-example supervision is already exhausted." It uses worse outputs from the current policy as opponents, generates a "better" pseudo-label, and constructs preference data from self-play. Its implicit risk is very direct: if the judgment of "better" comes from the model itself, then the preference direction itself carries the model's bias, merely hardening that bias into a preference signal. SPIN pays off on general text fluency, but on tasks with an objective right and wrong it delivers far less than rejection sampling.

RAFT (Reward rAnked FineTuning) is the engineered version of rejection sampling: generate multiple samples, score them with a reward model, and keep the highest-reward portion for SFT. Its key contribution is turning "binary pass/fail" into "continuous ranking", which is especially useful when the verification signal is sparse — when you cannot decide pass/fail but you can compare which is better.

Put these four together and a pattern emerges: every evolution of self-distillation is patching the fundamental contradiction that "the supervision signal comes from the model itself." STaR routes around it with backward construction, ReST with iterative expansion, SPIN with relative comparison, RAFT with ranking. What they cannot route around is exactly what the next two sections address head-on.

2.3 The Learnable Frontier: Why the Middle of the Success-Rate Range Is the Most Valuable

If this article can leave behind only one actionable lesson, it is this one: the value of a training sample is inversely related to the probability that the model gets it right, up to a knee point.

Suppose you sort the prompt pool by the model's success rate from 0% to 100%, then look at gradient contribution. Samples with a success rate near 0 give the model no idea which direction to go; what positive-example supervision actually supplies is the one accidentally correct path it happened to guess, which is extremely noisy. Samples with a success rate near 100% are already mastered, the gradient approaches zero, and training on them is pure budget waste. The samples that actually produce learning gain are those whose success rate falls in the middle band — the model has some probability of getting them right, so the verification signal can reliably filter out the positive examples; but it has not fully mastered them either, so the gradient is still substantial.

This intuition can be formalized in one line: for a verification pass probability p, the expected information a positive example carries roughly varies with something like p * (1 - log p), rising quickly when p is low and saturating when p is high. So many teams settle on a rough but stable empirical band in practice: holding p between 0.1 and 0.5 is where synthetic data has the highest return on investment. This band is not a universal constant — it drifts with the task difficulty distribution, the model scale, and the optimizer configuration — but its shape is stable.

The corollary is direct: your prompt pool should be neither "a list of the hardest tasks possible" nor "the list of topics from a standard tutorial," but the batch of tasks that sits in the middle of the success-rate range relative to the current checkpoint. That means the prompt pool must roll forward as model capability grows — a problem that sits on the frontier for the v1 model has already graduated by v3 and needs to be replaced with something harder. At the same time, old problems that have been mastered cannot simply be thrown away; they must keep being replayed at low frequency with a reduced sampling ratio, as an antidote to forgetting.

This is also why "curriculum design" is not a nice-to-have in synthetic data but the backbone: it turns a static prompt pool into a dynamic system that adapts to model capability.

2.4 What Changed in 2026: Verifiable Rewards Merge Synthesis and Reinforcement Learning into One Line

For the past few years these were two separate things: synthetic data was used for SFT, and reinforcement learning was scored with a reward model or with human preferences. The most important structural change from 2025 to 2026 is that the spread of the verifiable rewards paradigm (RLVR, Reinforcement Learning with Verifiable Rewards) merged the two into the same pipeline: synthetic data is no longer a one-shot artifact that freezes once training is done, it becomes the sampling pool for reinforcement learning rollouts; and the verification signal is no longer just a filter, it directly serves as the reward function.

This merge brings three substantive changes, worth every team re-evaluating the design of its pipeline:

  • Positive examples are no longer the only data form. Under an RL framework, failed samples carry negative-gradient information and are equally valuable. Rejection sampling that keeps only passing samples throws away the half of the signal that says "where does the model systematically err," and that half is precisely the most direct raw material for constructing adversarial samples.
  • Process-level verification gains value. Outcome-level verification (is the answer right) has terrible credit assignment on multi-step tasks; the model does not know at which step it went astray. Process reward models (PRM) and the generative verifiers that appeared after 2025 (a model writes out its scoring rationale, then assigns a score) try to fill this gap. They cost clearly more than rule checking, but they can significantly improve credit assignment on long-chain tasks such as mathematical derivations, multi-hop question answering, and code debugging.
  • The verifier is upgraded from "filter" to "environment". In code, "generate a test case" becomes an executable environment: if the model writes it wrong, run it; if the run fails, you get a deterministic signal. In tool use, synthetic data becomes a complete trace sequence, including tool return values. This "executable synthetic data" is essentially synthesizing a training environment rather than synthesizing a dataset, and that shift opens up the imagination space for data generation by a full notch.

But the merge has a cost. The RL stage is far more sensitive to reward hacking than the SFT stage: the model will actively hunt for holes in the verifier rather than trying hard to solve the problem. An RLVR pipeline must therefore run verifier audits earlier and more densely than a pure SFT pipeline — we will come back to this in section nine.

2.5 Convergence of the Loop: Self-Confirmation and Reward Hacking

Assembling everything above yields a somewhat uncomfortable conclusion: a loop that trains a model on its own generated data, and verifies with itself or a near-relative, is not guaranteed to converge.

The intuition goes like this. Each round of iteration, the model distribution contracts toward "the sample distribution the verifier approves of." The contraction itself is not the problem — that is exactly the targeted capability gain we want. The problem is that when the generator and the verifier share enough of their training sources, their architectural preferences, and their stylistic habits, the verifier's criteria drift along with the generator. The endpoint of that drift is a model that starts generating "what the verifier is willing to score high" instead of "what is actually correct." At that point the loop still looks excellent on the metrics (pass rate rises, training loss falls), but it has already decoupled from the real objective.

There are four conventional ways to break convergence, and they can be stacked:

  • External anchors: each round, mix in a fixed portion of real data that has never participated in the synthesis pipeline, and do not let this portion go through any filtering. Its job is to provide a coordinate system that does not drift.
  • Verifier rotation: periodically retrain the verifier on a fresh batch of data, or use multi-verifier voting with a hard requirement that at least one "heterogeneous" verifier agrees. Heterogeneous means a different architecture, different training data, a different objective system.
  • Prompt pool rotation: periodically introduce hand-written high-value tasks, especially in areas where the model fails systematically. The volume of human samples does not need to be large; the job is to "push the ceiling back up."
  • Convergence detection and circuit breaking: see the stopping condition in section nine. The key is to define the circuit-breaker signal on the real held-out set, and to require that this signal persist for several consecutive rounds before it triggers, so that training noise does not mislead it.

2.6 A Reusable Loop Implementation

The code below spells out the core logic of the loop described above. It does not depend on any particular framework and can plug into any generation backend and any verification backend. Note a few deliberate design choices: verification results keep failed samples (with a labeled reason), semantic deduplication happens before pooling, and there is an explicit learnable-frontier filter.

import json, hashlib, random
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Callable, Iterable

@dataclass
class Candidate:
    task_id: str
    prompt: str
    output: str
    passed: bool
    verifier_score: float = 0.0
    failure_type: str = ""          # 失败原因分类,仅在 passed=False 时有意义
    difficulty_bucket: str = ""     # 由 seed taxonomy 继承
    domain: str = ""
    fingerprint: str = field(default="")

def semantic_fingerprint(text: str) -> str:
    """归一化后取哈希,作为精确去重的第一道闸门。"""
    norm = "".join(text.lower().split())
    return hashlib.sha1(norm.encode("utf-8")).hexdigest()[:16]

def rejection_sampling_loop(
    prompts: Iterable[str],
    generate: Callable[[str, int, float], list[str]],
    verify: Callable[[str, str], tuple[bool, float, str]],
    n_samples: int = 8,
    temperature: float = 1.0,
    frontier_low: float = 0.10,
    frontier_high: float = 0.55,
) -> list[Candidate]:
    """生成 -> 验证 -> 统计成功率 -> 只保留可学习前沿的题目产出。"""
    kept: list[Candidate] = []
    seen: set[str] = set()

    for prompt in prompts:
        domain = prompt.split("|", 1)[0].strip()
        task_id = semantic_fingerprint(prompt)
        outs = generate(prompt, n_samples, temperature)
        results = []
        for out in outs:
            ok, score, ftype = verify(prompt, out)
            results.append((out, ok, score, ftype))

        pass_rate = sum(1 for r in results if r[1]) / max(1, len(results))
        # 能力边界之外:全对说明已掌握,全错说明无梯度可用
        if pass_rate < frontier_low or pass_rate > frontier_high:
            # 失败的极端仍然有用,标注后进入对抗样本池,而不是直接丢弃
            if pass_rate < frontier_low:
                for out, ok, score, ftype in results:
                    if not ok:
                        kept.append(Candidate(task_id, prompt, out, False,
                                              score, ftype, "very_hard", domain,
                                              semantic_fingerprint(out)))
            continue

        bucket = f"p={round(pass_rate, 1)}"
        for out, ok, score, ftype in results:
            if not ok:
                continue
            fp = semantic_fingerprint(out)
            if fp in seen:            # 精确去重
                continue
            seen.add(fp)
            kept.append(Candidate(task_id, prompt, out, True, score, "",
                                  bucket, domain, fp))

    return kept


def diagnose_pipeline(cands: list[Candidate]) -> dict:
    """用少量诊断指标替代单一通过率,用于早期发现闭环自证。"""
    by_domain = defaultdict(list)
    for c in cands:
        by_domain[c.domain].append(c)

    report = {
        "total": len(cands),
        "pass_rate_by_domain": {},
        "failure_type_hist": defaultdict(int),
        "verifier_score_mean": 0.0,
    }
    if cands:
        report["verifier_score_mean"] = sum(c.verifier_score for c in cands) / len(cands)
    for c in cands:
        if not c.passed and c.failure_type:
            report["failure_type_hist"][c.failure_type] += 1

    for dom, group in by_domain.items():
        ok = sum(1 for c in group if c.passed)
        report["pass_rate_by_domain"][dom] = ok / len(group)

    # 告警:某个领域的失败类型高度集中,通常意味着验证器有系统性缺陷
    for dom, group in by_domain.items():
        if len(group) < 30:
            continue
        hist = defaultdict(int)
        for c in group:
            if not c.passed and c.failure_type:
                hist[c.failure_type] += 1
        if hist:
            top, cnt = max(hist.items(), key=lambda kv: kv[1])
            if cnt / max(1, len(group)) > 0.8:
                report.setdefault("alerts", []).append(
                    f"{dom}: 80% 以上失败集中于 {top},疑似验证器缺陷而非模型缺陷")
    return report

There are two details in this code that reviewers tend to flag but that are worth keeping. First, successful samples above the frontier_high upper bound are discarded rather than kept in the training set to "consolidate them" — counter-intuitive but correct, because the gradient on high-success-rate samples approaches zero, and leaving them in dilutes batch quality. Second, failures at extreme difficulty are not discarded but enter a separate pool tagged with their failure type; they are the main source of adversarial samples in section seven. Most failure data is worthless not because it is wrong, but because nobody recorded why it was wrong.

III. Why Model-Generated Data Cannot Be Used as Gold Directly

3.1 Distribution Collapse Is a Probability-Quality Problem, Not a Correctness Problem

The problem in a synthetic data pipeline that is most often misdiagnosed is distribution collapse. A team sees a 90% pass rate, a pretty loss curve, and gains on the offline eval, and concludes the data is fine. But collapse never arrives in the form of "the data became wrong"; it arrives in the form of "the data became narrow": every sample is correct in isolation, yet taken together the dataset covers only a very small slice of the real distribution.

This distinction matters enormously, because it determines how you diagnose. Correctness problems can be found by inspecting samples one by one; distribution problems can only be found with statistics. The typical collapse signals are:

  • The distribution of nearest-neighbor distances between the synthetic set and the real set in embedding space is significantly right-skewed — meaning the synthetic samples all "clump" in a few regions of the real distribution.
  • The token type-token ratio (TTR) curve of the synthetic set drops abnormally fast, indicating that surface-level lexical diversity is being rapidly exhausted.
  • The self-similarity between a new batch of synthetic data and the previous batch rises significantly, while its similarity to the previous batch of real data actually falls.
  • The length distribution contracts. Models tend to produce output of increasingly uniform length once they have learned some pattern.

What these signals have in common is that they are completely invisible in the per-sample correctness rate. So if you monitor only the pass rate, you will not discover the collapse until something breaks in production.

3.2 Recursive Self-Consumption: Why Iteration Accelerates the Loss

There is already fairly solid research giving a mechanistic account of how recursive training degrades a model. Taken together, these results yield a more precise picture than the crude conclusion of "just don't use the model's own data."

The distribution tail is progressively deleted. Recursive generation is equivalent to repeatedly resampling and filtering the original distribution. Every filtering pass deletes low-probability samples — precisely the most distinctive and most informative ones. After n rounds, n layers have been shaved off the tail of the distribution, and the model's capability boundary contracts inward. This is the classic mechanism behind what is called "model collapse": in an approximate sense, recursive training drives the model toward a shrunken distribution rather than the original one.

The self-consuming paradox (the "Go MAD" phenomenon). A more severe class of cases is the alternating form of recursion — "feed a new model with model data, train it, and use its output as the input to the next round." Experiments show that this kind of training quickly produces distributional anomalies, with the model emitting content almost unrelated to real data. The key mechanism here is that collapse is not gradual; it is accelerating. Once the model has lost a pattern, subsequent self-sampling can never rebuild it, because rebuilding requires generating samples from that pattern with enough probability mass — which is precisely the thing that cannot happen.

The implication for rejection sampling is important. Rejection sampling looks naturally immune to this problem — it only uses samples that pass verification. But it is immune under one precondition: the verifier's coverage is wide enough to keep fishing up low-probability but correct samples. If the verifier only works on task forms the model is familiar with, then what it filters out is the core of the distribution rather than its margins, and collapse proceeds as usual, just more slowly. The verifier's coverage is therefore the real bottleneck on synthetic data quality — not generation scale.

3.3 Error Amplification: Why Filtering for "Correct" Can Still Teach the Model Nothing

The problem discussed in this section is the easiest to overlook and also the highest-yield. The common confusion is: every one of my samples passed verification, so why did the model still learn a mistake?

The answer lies in the fact that the verifier's criterion and the training objective are not the same thing. The verifier decides whether "the conclusion of this reasoning chain is correct," while training optimizes "every token of this reasoning chain." Between the two lies an enormous difference in degrees of freedom: a chain with a correct conclusion can contain countless kinds of faulty reasoning in between — skipped steps, wrong analogies, wrong attributions, even several errors that cancel each other out. All of these are completely invisible to the verifier's criterion.

This produces a counterintuitive but observable phenomenon: the model reliably learns how to reach a correct conclusion by way of a wrong path. Such errors are extremely persistent, because they are invisible at the level of conclusions — to the verifier, to the offline eval, and even to spot checks by human reviewers. The only way to expose them is to look at the reasoning chain itself.

The second class of problem is even messier: systematic bias survives filtering at a higher rate. Random errors are blocked in large numbers by the verifier, because a substantial share of random errors lead to an incorrect conclusion. Systematic errors are different — they are a stable bias in the model's cognition that acts consistently on every chain it generates. The share of conclusion errors such a bias produces is often lower than for random errors, which makes it more likely to pass verification.

For example, if the model has a stable 10% error rate in some unit conversion, then roughly 1 of the 10 chains it generates will be wrong and 9 will pass verification — and in those 9 "luckily correct" chains, the handling of units is still incoherent, so the rule the model extracts from them is incoherent too. Such errors are entirely invisible on the pass-rate curve, and can only be found by spot-checking the reasoning chains of passing samples.

The corresponding engineering countermeasures number three. None is complex, and all are frequently skipped:

  • Process-level verification beats outcome-level verification. At minimum, inspect the reasoning chain on critical tasks instead of only reading the conclusion. You are trading cost for accuracy.
  • Run "error-enriched" spot checks on passing samples. Not random sampling, but targeted inspection of samples whose reasoning structure looks suspicious: too many skipped steps, key intermediate quantities never made explicit, too large a gap between conclusion and premises.
  • Cross-validate with a second, independent method. For example, re-ask the question via a reversibly constructed equivalent problem, or use a different verifier, and see whether the answers are self-consistent. Errors that this kind of self-consistency check cannot catch are, frankly, not worth spending more money on.

3.4 Verifier Assimilation: The Loop Degrades Into Self-Certification

What we discussed above are risks at the data layer. This section is a more dangerous layer: when the verifier itself is contaminated, the loop degrades into a self-certifying cycle.

There are three paths to assimilation, and all are common:

  • Shared training corpus. A verifier and a generator trained on similar pretraining corpora share language preferences, factual blind spots, and intuitions about "what looks like a correct answer." The more homologous the two are, the higher the correlation between their scores — and a high score correlation is precisely the signal of collapse.
  • Shared annotation source. If the verifier's training labels come partly from the generator's output (same-source distillation), what it learns is the generator's judgment criteria rather than an objective one.
  • Isomorphic inductive bias. The same architecture, the same training recipe, and even the same alignment data cause a model to systematically overestimate outputs in "its own style." This explains why self-distillation pays off clearly on fluency tasks and barely at all on tasks that demand precision.

Diagnosing assimilation is straightforward: compute the correlation between verifier scores and the generator's own confidence (logprob or self-rated probability). If that correlation is extremely high — say, a Spearman correlation above 0.8 — then the verifier is supplying close to zero information, and the loop is in substance the model grading its own homework. A healthy verifier should correlate moderately with generator confidence (because hard problems really are harder), but must not be nearly identical. An adversarial verifier should deliberately manufacture this disagreement: it should occasionally mark wrong where the model is confident, and occasionally mark right where the model is hesitating.

A practical criterion: if you swap out the generating model while keeping the verifier, and the score ordering barely changes, the verifier is genuinely independent; if the scores swing wildly when you swap the generating model, then the verifier is mostly measuring "how much does this look like that generator."

3.5 The Legitimacy of Gold: It Comes from the Adjudicating Signal, Not the Generator

Collapsing sections 3.1 through 3.4 into a single principle:

Whether a sample can serve as training gold depends on what signal adjudicates it, not on which model generated it.

This principle has strong practical corollaries. The same "model-generated chain" is high-value gold if it passes a unit test; if it merely "looks reasonable," it is harmful noise. The identity of the generating model only affects prior quality — that is, the difficulty distribution of what passes — not the legitimacy of the gold. Conflating the two leads to two classic resource misallocations: overinvesting in "generate with a bigger model" while neglecting verifier quality, or compromising on "filter with a weaker verifier" and pouring noise into the training set.

Under a budget constraint, the verifier should almost always be funded before the generator. Generation capability is usually the easiest thing to procure (switch models, add inference budget); verification capability is the genuinely scarce thing that determines success or failure.

IV. Balancing Quality and Diversity: Quantifiable and Adjustable

4.1 Three Quality Dimensions, Not One Score

The most common mistake in practice is to filter data with a single quality score — for example, taking the top 10% by verifier score. This strategy seems reasonable intuitively, but in practice it systematically cuts diversity: high-quality scores are heavily concentrated on "the most typical, most common" kind of sample, so sorting by score is essentially repeated sampling from the core of the distribution.

The correct approach is to decompose quality into at least three independent dimensions, measured separately and thresholded separately:

  • Correctness: does the conclusion pass verification by an independent signal. This is a hard constraint — if it fails, discard, with no room for compromise. This dimension answers "is it right."
  • Completeness: are all constraints satisfied — format, length bounds, required elements, resolvable references, runnable code with no undefined variables. A great deal of data that "looks like it passes" dies on this dimension, especially in scenarios with heavy format constraints.
  • Informativeness: does this sample carry something the model has not seen before. This dimension is the gatekeeper of diversity, and also the one most easily overlooked.

The relation among the three is not one of parallel equals. Correctness is the admission threshold, completeness is the filter net, and informativeness is the basis for ranking and quota adjustment. Collapse the three into a single weighted score and the hardness of the threshold is lost — data with a low weighted score that is still acceptable gets killed by mistake, and trying to raise the score unconsciously pushes sampling toward the core of the distribution.

4.2 Three Diversity Metrics and Their Failure Modes

Diversity has to be measured, otherwise "improving diversity" is just a slogan. The three common classes of metrics each have their uses, and each has its own trap.

4.2.1 Lexical Diversity: Cheap, but Easy to Fool

The most basic metric is the distinct n-gram ratio: the proportion of unique n-tuples (such as bigrams and trigrams) across the whole corpus. Its implementation cost is extremely low, which makes it suitable for real-time monitoring on the pipeline.

But its failure mode must be understood: it punishes necessary repetition. In code, mathematics, formatted output, and terminology-dense domains, high-quality data should repeat large amounts of fixed token sequences (function names, unit symbols, template structure). In that setting, a drop in the distinct n-gram ratio need not indicate degradation at all. The correct usage is therefore: compute it per bucket, then look at the differences across buckets, rather than looking at a single number for the whole corpus. The same metric falling 5% in the code bucket while rising 20% in the natural-language bucket may leave the overall figure flat, but the two movements mean completely different things.

4.2.2 Semantic Diversity: The Primary Metric, but It Needs Calibration

Embed the samples, cluster them, and you get the cluster count, the cluster size distribution, and the tightness within each cluster. This is currently the most practical diversity metric, because it maps directly onto "how many different problems are covered."

Its failure mode is this: embedding models are overly sensitive to surface paraphrase and insufficiently sensitive to substantive difference. The same problem rephrased may be judged as "new content," while the same category of problem in a different domain context may be judged as "duplicate." There are two mitigations. First, use domain-adapted embedding models (general-purpose embedding models differ enormously in performance across code, Chinese, legal, and other domains). Second, run a small-scale human calibration via "paraphrase-identity judgment" — have people judge whether adjacent clusters in the clustering result really are different, and adjust the similarity threshold accordingly. The threshold is the hyperparameter most in need of calibration, and a fixed threshold is the most common form of laziness.

4.2.3 Structural Diversity: The Most Overlooked and the Highest-Yielding

Structural diversity refers to differences in task shape: different numbers of reasoning steps, different lengths of tool-call chains, different counts of constraints, different combinations of input modalities, different output formats. Relevant metrics include: the distribution of tool-call round counts, the distribution of constraint clause counts, whether multi-turn clarification is required, and the proportion of distractor information in the input.

Why does this dimension yield the most? Because a model's capability boundary is determined mainly by structural complexity, not by topic. A model may be strong at "geography Q&A" and weak at "information integration requiring three rounds of tool calls," and the latter has no topic relationship whatsoever to the former. A sampling strategy that buckets only by domain will leave the latter permanently untrained, because it occupies very little of the natural distribution.

An empirical check: rebucket the training set by task shape (rather than by domain) and look at the sample count and pass rate in each bucket. If some bucket has a pass rate that stays near 0 while its sample count is also near 0, that is not the model having learned it — it is your sampling strategy having excluded that capability.

4.3 From Weighted Sum to Constrained Optimization: Why the Latter Is More Controllable

The most common engineering implementation of diversity is "quality score minus a diversity penalty," in the spirit of a Self-BLEU penalty or MMR-style reranking. That route works, but it has a fundamental defect: the weight on the penalty term has no interpretable dimension. You can slide the weight from 0.3 to 0.5 and the effect changes smoothly, with no point along the way telling you "diversity is now adequate." This turns hyperparameter tuning into blind trial and error, and makes the system brittle to dataset changes.

A more controllable framework writes it as a constrained optimization problem: maximize quality subject to hard diversity constraints. The constraints usually take this form:

maximize    sum_i  quality(s_i) * w_i
subject to  cluster_k:  n_samples(k) <= cap_k          # 每簇上限
            ngram:      distinct_3gram >= tau_3          # 表层多样性下限
            structure:  P(struct_bin=j) <= rho_j         # 结构分布上界
            domain:     coverage(domain_d) >= 1 - eps_d  # 领域覆盖下限
            novelty:    nns_dist(s_i) >= delta_i          # 近邻距离下限

The benefit of writing it this way is very practical: each constraint corresponds to an independently monitorable alarm signal. The per-cluster cap constraint corresponds to the alarm "a single cluster exceeds the threshold share"; the novelty constraint corresponds to the alarm "the median nearest-neighbor distance from new samples to the existing set keeps falling." Alarms can be wired directly into the training pipeline, without depending on any composite score.

It is worth emphasizing why the per-cluster cap constraint matters so much. Default sampling behavior tilts toward high-density regions — because quality scores are high there, verification pass rates are high there, and generation cost is low there. The result is that the data distribution collapses toward the core. An explicit per-cluster cap is the only effective countermeasure to that tendency, because it acts on the selection stage, whereas every quality-oriented mechanism acts on the scoring stage. The scoring stage is inherently biased toward the core, so an independent constraint is needed to balance it.

4.4 Six Knobs That Can Be Adjusted in Real Time

Compressing everything above into six knobs that can be adjusted dynamically inside the training loop. Their value is that they can be turned into a control system that feeds back automatically from real-time metrics, without manual intervention every round.

  1. Sampling temperature: the most basic and the most underrated. Raising the temperature directly widens the range of the distribution covered, at the cost of a lower pass rate. The correct usage is to set temperature per difficulty bucket: use a low temperature on buckets already in the middle band to guarantee the pass rate, and a high temperature on buckets near the boundary to fish out rarer solutions. A single global temperature cannot achieve this layered adjustment.
  2. Per-cluster sampling cap: directly controls the rate of distribution collapse; see the constraint discussed above.
  3. Deduplication threshold: the similarity threshold for semantic deduplication. Too loose kills homologous but necessary variants, and the cost of too loose is especially large in long-tail buckets; too tight is equivalent to having none. Set it per bucket, and separately track the share of samples killed by deduplication — if some bucket is losing 60% of its samples, the generation diversity in that bucket has already gone wrong.
  4. Verification threshold: raised as the iteration count increases. This is one of the most effective anti-collapse measures, because as the model gets stronger, samples that used to pass will increasingly pass under increasingly lenient criteria. Tightening the threshold gradually forces new data to be better than old data, which is equivalent to adding a ramp to the loop.
  5. Historical data retention ratio: the share of the previous round's data retained on each round of feedback. Too low causes forgetting; too high accumulates noise and makes optimization hard. Empirically, start at 30% to 50%, paired with the curriculum replay mechanism below.
  6. Heterogeneous data injection ratio: a fixed portion of real data that does not go through filtering, mixed in every round. Its job is to anchor the coordinate system, and the ratio can be small (5% to 10% is often enough), but it must be injected on a fixed schedule — you cannot temporarily reduce it because "real data performed badly this round," since that is precisely the moment it is most needed.

4.5 A Practical Tool for Detecting Imbalance

The code below generates no data; it only performs diagnosis. Its design goal is to raise a signal before collapse becomes irreversible — specifically, to detect the characteristic combination of "quality rising while diversity falls," because that is the most typical early form of collapse.

import math
from collections import Counter, defaultdict
from dataclasses import dataclass

@dataclass
class DiversitySnapshot:
    round_id: int
    pass_rate: float
    ngram_3: float              # distinct 3-gram 比
    n_clusters: int
    top_cluster_share: float    # 最大簇占比
    median_nns: float           # 到最近邻的中位距离
    struct_entropy: float       # 结构桶分布的香农熵

def shannon_entropy(counts) -> float:
    total = sum(counts)
    if total == 0:
        return 0.0
    h = 0.0
    for c in counts:
        if c <= 0:
            continue
        p = c / total
        h -= p * math.log(p)
    return h

def detect_collapse(history: list[DiversitySnapshot],
                    ngram_floor: float = 0.35,
                    cluster_floor: int = 40,
                    entropy_floor: float = 1.10) -> list[str]:
    """在塌缩不可逆之前报警。判据是组合式的,避免单指标误报。"""
    alerts: list[str] = []
    if len(history) < 3:
        return alerts

    cur, prev = history[-1], history[-2]

    # 模式一:质量上升的同时多样性下降 —— 塌缩最典型的形态
    if cur.pass_rate > prev.pass_rate + 0.02:
        if cur.ngram_3 < prev.ngram_3 or cur.median_nns < prev.median_nns:
            alerts.append(
                f"round {cur.round_id}: 通过率上升而近邻距离下降,"
                f"疑似向高密度区过度集中")

    # 模式二:表层多样性触底
    if cur.ngram_3 < ngram_floor:
        alerts.append(f"round {cur.round_id}: 3-gram 多样性 {cur.ngram_3:.3f} 低于下限")

    # 模式三:覆盖范围收缩
    if cur.n_clusters < cluster_floor:
        alerts.append(f"round {cur.round_id}: 语义簇数 {cur.n_clusters} 低于下限")
    if cur.top_cluster_share > 0.45:
        alerts.append(
            f"round {cur.round_id}: 单簇占比 {cur.top_cluster_share:.2f} 过高")

    # 模式四:任务形态分布变窄 —— 最容易被忽略但最严重
    if cur.struct_entropy < entropy_floor:
        alerts.append(
            f"round {cur.round_id}: 结构熵 {cur.struct_entropy:.2f} 过低,"
            f"复杂任务形态正在被采样策略排除")

    # 模式五:多样性已经连续三轮单调下降
    tail = history[-3:]
    if len(tail) == 3 and all(tail[i].n_clusters > tail[i + 1].n_clusters
                             for i in range(2)):
        alerts.append(
            f"round {cur.round_id}: 语义簇数连续三轮下降,处于不可逆塌缩风险区")

    return alerts

def suggested_next_params(snap: DiversitySnapshot) -> dict:
    """根据快照给出下一轮的旋钮建议。这是一个粗粒度的规则控制器。"""
    advice = {"temperature_delta": 0.0, "cluster_cap_delta": 0,
              "verify_threshold_delta": 0.0, "real_data_inject": 0.08}

    if snap.top_cluster_share > 0.40:
        advice["temperature_delta"] += 0.15
        advice["cluster_cap_delta"] -= 0.10
    if snap.struct_entropy < 1.20:
        # 结构熵低说明高难度复杂形态没被采到,需要提高温度并定向补采
        advice["temperature_delta"] += 0.10
        advice["real_data_inject"] += 0.04
    if snap.ngram_3 < 0.40:
        # 表层多样性低但通过率可能仍高,此时应收紧质量阈值逼出更难的题
        advice["verify_threshold_delta"] += 0.02
    if snap.pass_rate > 0.80:
        # 整体太轻松,学习前沿已经上移,需要更激进的边界探测
        advice["temperature_delta"] += 0.10
        advice["verify_threshold_delta"] += 0.03

    advice["temperature_delta"] = min(0.4, max(-0.2, advice["temperature_delta"]))
    advice["cluster_cap_delta"] = min(-0.05, max(-0.30, advice["cluster_cap_delta"]))
    advice["verify_threshold_delta"] = min(0.10, max(-0.05,
                                        advice["verify_threshold_delta"]))
    advice["real_data_inject"] = min(0.20, advice["real_data_inject"])
    return advice

This diagnostic layer should be a permanent component of the pipeline, not something assembled in a panic when something goes wrong. Its value is that it turns "collapse" from a vague phenomenon that needs a post-hoc explanation into an engineering metric that has a number every round, can be charted as a trend, and can be paired with alarms. Of the alarm rules above, "structural entropy" and "nearest-neighbor distance" are the two most worth implementing first — they are markedly more sensitive to collapse than pass rate and the n-gram ratio, because they directly measure "what was covered" rather than "how many phrasings were covered."

Five: Difficulty Curriculum — Making Data Adaptive to the Model

5.1 Why a fixed difficulty set must eventually fail

The "learnable frontier" principle introduced in the second section has a direct corollary: difficulty is not an intrinsic property of a task, it is a property of the task relative to the current model's capability. The same question sits on the frontier for a v1 model and has already graduated by v3. This means any fixed-difficulty prompt pool stops working after a few rounds of iteration — not because it got worse, but because the model caught up with it.

A subtler failure mode is "gradient dilution". Once a fully mastered area keeps consuming training budget, it does not merely stop producing useful gradients, it also crowds out the share of every other sample in the batch. In practice this shows up as slower training loss descent, worse forgetting, and a final convergence point below the level that should have been reachable. This is especially common in pipelines that sample along the "natural difficulty distribution", because the natural distribution keeps flooding in large quantities of easy samples.

5.2 Four ways to estimate difficulty, and what they cost

Scoring a task's difficulty requires measurement, and the measurement cost different scenarios can absorb varies enormously. Ordered from lowest to highest cost:

5.2.1 Static proxy metrics: nearly free, but only good for coarse ranking

Use features that can be computed without running the model as a first-pass screen: token length, nesting depth, number of constraint clauses, number of entities involved, an estimate of required reasoning steps (count the steps of a single greedy decode), static analysis results of the tool call chain.

The value of this method is not its precision but the fact that it can stratify prompts before generation even starts, concentrating the expensive verification budget on the range that looks promising. A sensible workflow in practice is: split into 3 to 4 tiers with static metrics, probe each tier with a small batch only, then decide where to invest next. It is not sufficient on its own, because static metrics correlate only moderately with real difficulty — a very short sentence can be an extremely hard problem.

5.2.2 Active probing: concentrating measurement cost where information is highest

Sample each task k times (k between 4 and 8), tally the pass rate, and you have an empirical difficulty. This is the best value-for-money option and the one used in the code in the second section. A k that is too small makes the difficulty estimate too noisy (at k=4 the confidence interval on the pass rate is wide); too large raises the cost linearly. The compromise is two-stage probing: first use k=2 to quickly eliminate the obvious extremes (all-correct / all-wrong), then top the remaining tasks up to k=8. That concentrates measurement precision in the middle of the difficulty range while the edges only get a coarse judgement — and the edges happen to be exactly the region where you no longer need to spend effort.

5.2.3 Cross-model probing: anchoring against an external difficulty baseline

Run the same batch of tasks through a different model (a different architecture or a different generation) to get a difficulty ranking. If a task's difficulty differs a lot between the two models, it depends on some model-specific capability pattern — such tasks usually have low training value for a single model, but they can be flagged for separate observation. Conversely, tasks that are hard for both models are often genuinely hard general problems and deserve focused investment.

The extra benefit of this method is that it naturally filters out tasks that are "overfit to a specific model's weakness". Such tasks look valuable under single-model probing (they sit right at the boundary), but training on them only reinforces the specific biases that model already has and does little for generalization.

5.2.4 Learning curve extrapolation: highest precision, highest cost

Track how the pass rate of a small set of high-value tasks moves across multiple rounds of training, fit a learning curve, and use extrapolation to judge "how many more rounds until this graduates". This gives precise budget allocation — down-weight tasks that are about to graduate in advance, up-weight the ones that just got promoted. The cost is having to track a fixed batch of tasks over a long horizon, and the fitting method itself is noisy. It fits the stage where the pipeline is already running stably and you are about to do fine-grained tuning, not the early phase.

5.3 Three design principles for curriculum orchestration

Combining difficulty measurement with training order gives three principles worth stating explicitly:

Principle one: the main payoff of a curriculum is not "easy to hard" but "continuously concentrating budget on the frontier". Classical curriculum learning emphasizes order — going easy before hard speeds up convergence. But in the synthetic data setting the more important source of payoff is the continuous reallocation of budget. A linearly ramping curriculum only delivers a one-time ordering benefit, while dynamic frontier tracking delivers a correct budget allocation on every single round. In implementation the latter is actually easier: you do not need a preset difficulty ladder, only re-bucketing each round and adjusting sampling ratios according to the bucketing result.

Principle two: difficulty must be layered, it cannot be one-dimensional. Break complexity into several orthogonal dimensions and track them separately: reasoning depth (number of steps), constraint density (how many conditions must hold at once), knowledge breadth (how many distinct domains are involved), tool chain length (number of interaction turns), distractor intensity (how much irrelevant information sits in the input). A model may have plenty of reasoning depth and be completely hopeless with tool chains. Running the curriculum on a single composite difficulty score averages the second case away, so it never receives targeted training.

Principle three: graduating does not mean discarding — it means down-weighting and keeping. For an area that has already been mastered, sample volume should drop to a very low but non-zero level (typically 2% to 5% of the total) and stay there. Dropping to zero causes catastrophic forgetting — and the generate-train loop of synthetic data aggravates forgetting on its own, because the data distribution was bounded by the model's capability circle to begin with, so the forgotten part has no natural source that would replenish it. Keeping a low non-zero level serves to refresh continuously without consuming budget.

5.4 Three common traps in curriculum implementation

  • The lag of difficulty labels. Difficulty is measured against the previous round's checkpoint, so after a round of training every label reads low. The fix is to shift the bucketing thresholds up by an empirical factor (for example lowering the "mastered" pass rate threshold from 0.95 to 0.90), or to run a quick re-measurement at the start of every round. Ignoring this makes the actual pace of the curriculum much faster than expected: the model switches difficulty tiers before it is ready.
  • Leakage across difficulty tiers. If different variants of the same task end up in the training set and the validation set, difficulty statistics are contaminated and training metrics look artificially good. Bucketing must be completed before variants are generated, and it must bucket by task ID rather than by sample.
  • Over-chasing the frontier makes training unstable. Putting the overwhelming majority of budget on the region with a 10% pass rate produces very high gradient noise, and training may fail to converge. A reasonable configuration lets the "frontier bucket" take the largest but not overwhelming share (say 40% to 50%), keeps 30% to 40% on already-mastered buckets (those samples have small gradients but also small variance, acting as a stabilizer), and leaves the rest for exploration outside the frontier. The stabilizer role is routinely underestimated — late-stage training where more than half the gradients are high-noise keeps losing ground.

Six: Bucket Sampling by Domain and Difficulty — Turning Ratios from Guesswork into Computation

6.1 The two dimensions of bucketing: domain and difficulty are not the same thing

Domain is the subject of a task (legal, medical, code, customer support); difficulty is its complexity. The two are orthogonal, yet in practice they are frequently collapsed into one treatment — most commonly "bucket by domain, then sort by pass rate inside each bucket and take the top". The problem is that if a domain is uniformly too hard, taking the top is the same as taking everything; if a domain is uniformly too easy, the top is full of uninformative samples. The within-domain difficulty differences get eaten by the bucket boundary.

The correct approach is two-dimensional bucketing: cut by domain first, then by difficulty within each domain. Buckets must be fine-grained enough that each "domain × difficulty" combination has enough samples for a statistical judgement. A rough scale reference is at least 200 to 500 valid samples per bucket; otherwise the confidence interval on the in-bucket pass rate is too wide to guide sampling decisions.

6.2 Tempered resampling: why you want an "unnatural" distribution

The most direct ratio strategy is "sample according to the real business distribution". That strategy is right for the first round — it lets the model adapt to the real distribution first. But it has a fatal problem: in the real distribution, the sample volume of high-value regions is usually too small to support learning. In a real business only 3% of tickets need more than three rounds of tool calls, and the model will never learn that; while 60% are simple one-sentence queries that the model could train on a hundred times and still barely improve.

The effective approach is tempered resampling. If the true proportion of bucket b is p_b and the actual sampling ratio is q_b, then:

q_b  proportional to  p_b ** (1 / tau)        # tau < 1 时更均匀,tau > 1 时更集中

# tau = 1.0 时,采样分布等于真实分布
# tau = 0.5 时,q_b proportional to p_b^2,长尾桶被进一步压缩
# tau = 2.0 时,q_b proportional to sqrt(p_b),长尾桶被显著放大
# tau -> inf 时,趋于均匀分布,所有桶被平均对待

import math

def tempered_sampling_weights(bucket_stats, tau: float) -> dict:
    """bucket_stats: {bucket: {"prior": 真实占比, "value": 训练价值分}}"""
    num = {}
    for b, s in bucket_stats.items():
        prior = max(s["prior"], 1e-9)
        num[b] = prior ** (1.0 / tau)
    total = sum(num.values())
    return {b: v / total for b, v in num.items()}


def value_adjusted_weights(bucket_stats, tau: float,
                           kl_cap: float = 0.35) -> dict:
    """在温度化之后再按训练价值调整,并用 KL 约束限制偏离真实分布的程度。

    纯价值驱动的分配会产出不自然的分布(某些桶被完全排除),
    部署时出现 OOD 崩塌。KL cap 是这个问题的安全阀。
    """
    base = tempered_sampling_weights(bucket_stats, tau)

    # 价值分归一化到 [0.5, 1.5],避免价值项完全压过先验
    vals = [s["value"] for s in bucket_stats.values()]
    lo, hi = min(vals), max(vals)
    span = (hi - lo) or 1.0

    raw = {}
    for b, w in base.items():
        v = bucket_stats[b]["value"]
        v_norm = 0.5 + (v - lo) / span          # [0.5, 1.5]
        raw[b] = w * v_norm
    total = sum(raw.values())
    adjusted = {b: v / total for b, v in raw.items()}

    # 逐步向真实分布回拉,直到 KL 低于上限
    for b in list(adjusted):
        adjusted[b] = 0.5 * adjusted[b] + 0.5 * bucket_stats[b]["prior"]
        t = sum(adjusted.values())
        adjusted = {k: v / t for k, v in adjusted.items()}
        kl = sum(p * math.log(p / q)
                 for p, q in zip(adjusted.values(), base.values()) if p > 0)
        if kl <= kl_cap:
            break

    return adjusted

The KL constraint here deserves its own explanation. A purely value-driven ratio produces a distribution that looks reasonable and is actually dangerous: it naturally crushes low-value buckets toward zero. And those low-value buckets are precisely what the model is guaranteed to meet in real business but performs badly on because "it never saw them during training". This does not surface in evaluation (because evaluation also comes from an approximation of the training distribution) — it only surfaces at deployment. The KL cap forces the allocation to stay not too far from the real distribution, managing that risk explicitly.

6.3 Stratified batches: more effective than global ratios

The global ratio (which decides the composition of the whole dataset) is the right level, but it is not enough. The reason is the variance of the gradient estimate: even if the global proportion of a bucket is 30%, that bucket's samples still appear at random inside every mini-batch, and the high variance drowns that bucket's learning signal in noise.

Stratified batching makes every batch satisfy the ratio internally: for example, a batch of 256 always contains 40 samples from "code × high difficulty" and 30 from "support × medium difficulty", and so on. The remaining slots are left to a randomly drawn portion.

The payoff is twofold: it lowers gradient variance, and it also removes a subtle problem — batch-level distribution shift. Under random batching, the model sees a skewed distribution on certain batches and adapts to it, which is itself a source of noise. Stratified batching removes that noise.

The cost of stratified batching is that you must fix the bucket enumeration up front and construct every batch. This implementation cost is usually overestimated — in essence it is a quota split followed by a shuffle, and the construction overhead of a single batch is negligible in a modern data loader. What really needs care is that the number of buckets must not grow too large, otherwise a single bucket has too few samples within a batch to carry statistical meaning. Empirically, keep it between 10 and 30 active buckets.

6.4 Dynamic adjustment of ratios: from a static table to a controller

A static ratio table starts failing after the first round, because it does not reflect changes in model capability. A reasonable closed loop adjusts bucket weights each round based on the following signals:

  • Pass rate. A bucket whose pass rate drops close to 0 is above the current difficulty level and should be down-weighted with a re-probe scheduled; a bucket whose pass rate stays near 1 for a long time should be down-weighted to a replay level.
  • Sample volume and marginal return. If a bucket takes in more samples for several consecutive rounds while the real held-out score stops improving, that bucket has saturated and the budget should move elsewhere. This "marginal return" signal guides budget allocation better than pass rate does, because it measures the input-output ratio directly rather than a state.
  • Structural coverage gaps. Even if a bucket's marginal return is zero, as long as the task form it represents is a blank spot in the overall capability profile, it should keep a minimum sample volume and continue to be sampled. Because gaps in the capability profile are usually invisible offline, they cannot be measured.

When you turn these three signals into adjustment rules, one principle deserves emphasis: down-weighting is easy, up-weighting is the bottleneck. The system naturally leans toward buckets that already perform well (because the returns are visible there), so high-value but hard buckets never get budget. The fix is to give "breakthrough candidates" a floor quota — decided by their historical worst performance rather than their current performance — which avoids falling into a local optimum. This is fundamentally a multi-armed bandit problem with an exploration term, and a synthetic data pipeline is in essence doing bandit decisions, except that most implementations degenerate into greedy.

Seven: Error Injection and Adversarial Samples — Deliberately Manufacturing the Model's Blind Spots

7.1 Why you have to manufacture the mistakes yourself

The implicit assumption running through the previous sections is "the verifier can filter out what is correct". This section handles the opposite direction: systematically collecting, classifying and amplifying the model's errors, and turning them into a training asset.

Collecting errors passively (waiting for the model to fail validation and then looking at the failed samples) has a fundamental defect: it only collects errors that fall inside the model's current capability range, that is, errors it already knows it makes. The truly dangerous errors are the ones the model does not know it makes — it completes them confidently, fluently and wrongly. Those errors are invisible in the validation failure list, because the verifier may be unable to tell that they are wrong (the error is itself plausible), or because they happened to slip through some lenient check.

So adversarial samples must be constructed deliberately: first work out "where the model might fail, why it would fail, what the failure looks like", then build against that. This is essentially moving the red-teaming methodology accumulated over years in security and robustness onto general capability training.

7.2 The construction principle: minimal edit distance plus a single controlled variable

The most important engineering principle when constructing adversarial samples is: change only one controlled variable at a time. If a single edit introduces several changes (reworded phrasing + changed numbers + changed constraints), you end up with a sample the model answers incorrectly, but you do not know which change caused the failure, so the training signal is ambiguous.

Concretely, the recipes are:

  • Keep the original problem, change only the answer. Generate a confident but incorrect solution, used to train recognition ability.
  • Keep the original problem, add only one interfering condition. For example adding "what if X does not hold", or turning an already given condition into a contradictory one.
  • Keep the answer, change the problem so that the answer no longer holds. This class of sample is especially valuable, because what it trains is the model's sensitivity to input conditions.
  • Change only one number or entity. This follows the "perturbation test" idea: change some number in the problem statement and see whether the model's answer changes with it. Answers that do not change (even if they were originally correct) have the answer anchored in the wrong place, which is a very hidden failure mode.

In the code domain, the minimal edit distance rule is especially critical and especially easy to violate. When generating "buggy code", if you let the model improvise, it will write a piece of erroneous code with a completely different style and a completely different structure — such samples carry almost no information, because they share no structure at all with the correct code. The right approach is to start from the correct code and make a single-point modification (change one operator, delete one bounds check, introduce one off-by-one), so that the model must locate exactly that one spot.

7.3 Five construction techniques and where they apply

7.3.1 Mutation: controlled perturbation, testing stability

Technique: keep the semantics unchanged, vary the expression, word order, redundant information, or formatting. Purpose: train robustness to surface-level change, preventing the model from learning surface features instead of semantics.

Synthetic data in this direction has a hidden downside worth watching: over-training makes the model excessively insensitive to input changes, which is harmful on tasks that demand precision (for example when "restricted to category A" becomes "restricted to category B", the model has learned to ignore those qualifiers). So the proportion of mutation data needs careful control, and the level of mutation has to be distinguished — syntactic mutation is good, condition mutation is bad.

7.3.2 Contradiction injection: testing conflict detection

Technique: give a problem that is self-consistent in itself, then plant a condition in the statement that conflicts with the premise, or two requirements that contradict each other. Purpose: train the model to recognize unsatisfiable inputs and explain why, instead of stubbornly producing an answer.

This kind of data is extremely valuable in real business, because user input is full of contradictions and mutually conflicting requirements in the first place. An assistant that can recognize "these two requirements cannot both be satisfied" is far more useful than one that always picks a condition and forces it through. And this ability is hard to learn from positive data — positive data contains almost no "contradiction detected" behavior pattern, because positive samples are all solvable.

7.3.3 Defect injection: testing error detection

Technique: inject a specific type of defect and let the model locate and fix it. Purpose: train critique ability and error localization ability. Applicable domains: code (logging, computation, logic defects), data (consistency, outliers), documents (factual errors, logical breaks, broken references), plans (missing steps, violated constraints).

This category can be made highly verifiable: a code defect can be judged fixed by test cases, a data defect by validation rules, a document defect by fact-checking. Compared with positive generation, verifying defect injection is usually cheaper, because it turns the problem into a binary decision. That is why it is easier to industrialize than the other categories.

7.3.4 Out-of-distribution construction: testing generalization boundaries

Technique: transfer problems from a known domain across domains (legal provisions applied to biomedicine, mathematical structures applied to linguistic problems), or use newly emerging concepts/terminology/formats. Purpose: train the model not to apply wrong priors when it meets unfamiliar input.

The key requirement is: cross-domain transfer must preserve the inner structure of the task and change only the surface domain. If the structure changes too, then you are testing a completely different ability. The acceptance criterion for this construction is "a human expert would consider this a reasonable problem", which is why its quality bar is higher than other categories and why it is harder to generate at scale.

7.3.5 Boundary violations and refusals: testing boundary recognition

Technique: construct requests that ought to be refused — beyond the capability range, involving harmful content, demanding fabricated citations, or demanding handling of confidential material. Purpose: train correct refusal and referral behavior.

This kind of data has one special requirement: it must be extremely precise. A mislabeled refusal sample costs in both directions — labeling an answerable request as a refusal makes the model incompetent and annoying, while labeling a refusal case as answerable is a safety problem outright. Neither error is easy to catch in automatic validation. The practical handling is that generation of this data need not scale up (a few thousand to a few tens of thousands is usually enough), but the proportion of human review must approach 100%. This is the category least suited to a fully automated pipeline.

7.4 How to use negative samples: do not throw them away as garbage

Negative samples need more design in training than positive samples do, because they have several mutually incompatible uses, and mixing them is directly harmful:

  • Do not use negative samples as negative labels for SFT. Feeding a wrong answer to SFT with the label "this is wrong" pollutes the gradients in most implementations. SFT has no concept of a negative label.
  • The correct use of negative samples is building preference pairs. Positive/negative pairs of the same problem feed preference optimization, or you construct correction targets from critiques.
  • Use them to train the verifier/discriminator. This use is severely underestimated. The verifier in a synthetic pipeline needs training data, and you happen to have a large quantity of negative samples annotated with failure types — this is a data advantage unique to you that others cannot get. Training a discriminator on this is far more effective than training it on generic preference data.
  • Use them to train critique ability. Under an RLVR framework, negative samples are the main carrier of the reward signal rather than waste to be discarded.

Empirically, the long-term value of negative samples often exceeds that of a small number of same-source positive samples. Positive samples let a model get a particular problem right; negative samples let the model know "this class of mistake is one I make". The latter contributes more to generalization.

7.5 An implementation of adversarial sample construction and pairing

The code below implements the "minimal edit plus a single controlled variable" construction approach and does positive/negative pairing. It deliberately turns the mutation operation into an explicit, traceable record, because the interpretability of an adversarial sample directly determines whether it can be human reviewed — and this is exactly the kind of data that needs human review the most.

import difflib
import random
from dataclasses import dataclass, field

# 受控编辑操作。每一个操作只改一个语义维度,便于归因。
EDIT_OPS = {
    "negate_condition":   "翻转题目中的一个条件约束",
    "swap_entity":        "把关键实体替换为同类但不同的实体",
    "perturb_number":     "对某个数值做局部扰动",
    "add_contradiction":  "加入与既有前提冲突的条件",
    "weaken_constraint":  "把一个硬约束改成软约束",
    "remove_prerequisite": "删除推理所需的前置条件",
}

@dataclass
class AdversarialPair:
    task_id: str
    domain: str
    good_question: str
    good_answer: str
    bad_question: str
    bad_answer: str
    injected_defect: str = ""     # 注入的缺陷类型
    edit_distance: float = 0.0    # 编辑距离占原文长度的比例
    control_ops: list = field(default_factory=list)  # 对照组操作

def make_adversarial(task_id, domain, question, answer, defect_type,
                     rng: random.Random) -> AdversarialPair:
    """从一条正确样本派生一条对抗样本。

    关键约束:只做一次受控编辑,且编辑后的样本必须仍然
    "看起来像一个合理的问题" —— 否则模型只需识别题面异常,
    训练信号就浪费了。
    """
    if defect_type not in EDIT_OPS:
        raise ValueError(f"unknown defect type: {defect_type}")

    # 这一步需要真实的编辑算子实现;此处保留接口与约束校验
    bad_q, bad_a = _apply_edit(question, answer, defect_type, rng)

    dist = difflib.SequenceMatcher(None, question, bad_q).ratio()
    edit_distance = 1.0 - dist

    pair = AdversarialPair(
        task_id=task_id,
        domain=domain,
        good_question=question,
        good_answer=answer,
        bad_question=bad_q,
        bad_answer=bad_a,
        injected_defect=defect_type,
        edit_distance=edit_distance,
    )

    # 编辑距离过大说明改得太狠,模型只需靠表面异常识别,不提供信息量
    if edit_distance > 0.35:
        pair.injected_defect = "TOO_LARGE_EDIT"
    return pair


def audit_pairs(pairs: list[AdversarialPair],
                human_label_fn=None) -> dict:
    """上线前的质量闸门。人工标注函数存在时用它做二次过滤。"""
    stats = {
        "total": len(pairs),
        "too_large_edit": 0,
        "defect_hist": {},
        "domain_hist": {},
    }
    good_pairs = []

    for p in pairs:
        stats["defect_hist"][p.injected_defect] = \
            stats["defect_hist"].get(p.injected_defect, 0) + 1
        stats["domain_hist"][p.domain] = stats["domain_hist"].get(p.domain, 0) + 1

        if p.injected_defect == "TOO_LARGE_EDIT":
            stats["too_large_edit"] += 1
            continue

        if human_label_fn is not None:
            verdict = human_label_fn(p)
            # verdict: "valid" | "defect_not_real" | "question_broken"
            if verdict != "valid":
                p.injected_defect = f"REJECTED_{verdict}"
                continue

        good_pairs.append(p)

    # 缺陷类型分布检查:任何单一缺陷占比过高说明生成有偏
    if good_pairs:
        hist = {}
        for p in good_pairs:
            hist[p.injected_defect] = hist.get(p.injected_defect, 0) + 1
        n = len(good_pairs)
        top_defect, top_cnt = max(hist.items(), key=lambda kv: kv[1])
        if top_cnt / n > 0.5:
            print(f"[WARN] 单一缺陷类型 {top_defect} 占比 {top_cnt/n:.0%},"
                  f"对抗数据存在构造偏差")

    return stats, good_pairs

The two gates in audit_pairs are the most important part of this code. The first is the edit distance gate: it blocks samples that were "edited too hard". The harm of such samples is that they teach the model a shortcut — spotting anomalies in the problem statement instead of spotting reasoning defects. The model therefore performs superbly on adversarial tests and has no resistance whatsoever against real adversaries. The second gate is human labeling: defect_not_real means the injected defect does not actually constitute a defect (for example the original answer still holds under the new conditions), and samples like that, if left in the training set, are plain mislabeled data.

What has to be acknowledged is that both gates are expensive. So constructing adversarial samples does not suit a fully automated, large-scale production route; its correct form is small batches, heavy review, and targeted top-up by defect type — use one real failure case to define which class of defect you need, then concentrate on building that class. This kind of "construct on demand" is far more efficient than blind mass generation.

Eight: Mixing Synthetic Data with Real Data

8.1 Three stages with completely different ratios

There is no universal optimal ratio, because the optimal value changes dramatically with the training stage. Treating it as a constant is one of the most common mistakes in practice.

Cold start phase (SFT cold start). The goal is not the capability ceiling but basic behavioral alignment. Real data should take the overwhelming majority here — no less than 50% is recommended, and 70% or more is common practice. The reasoning: the failure modes in cold start are usually not "cannot do a certain task" but "does not respond the way you asked" — format, tone, structure, the way refusals are phrased. Synthetic data can offer almost no help at this stage, because its behavioral distribution is sampled from the model's own tendencies, which in effect hardens the model's current preferences, including the ones it should not have.

Capability expansion phase. This is the window where synthetic data is worth the most: the model needs a large amount of practice at the edge of its capability. Synthetic data can account for 60% to 80% here. But that ratio varies enormously by domain: domains with strong verification signals and scarce real data (mathematical derivation, code execution verification, structured tasks) can go higher; domains that depend on real-world knowledge and long-tail expression (open-ended QA, customer support dialogue, creative writing) should have a significantly lower synthetic proportion, because these tasks have no reliable verifier and the quality ceiling of synthetic data is locked by that verifier.

Convergence and calibration phase. The synthetic proportion should fall back below 30%, with the remainder used for real-data distribution calibration. This step is often skipped, and the cost is that distribution shift on real scenarios persists — every round of added synthetic data moves the model closer to the synthetic distribution and a little further from the real one. This drift is cumulative and monotonic; it does not disappear on its own without calibration.

A repeatedly validated piece of experience: synthetic data is good for "learning a new ability" or "making an ability stable", not for "keeping an ability you already have". Real data is significantly more efficient at retention, because it carries distribution properties the model cannot generate by itself.

8.2 Mixing is not concatenation: three operations that must stay separate

The most common confusion when configuring ratios is compressing three independent decisions into a single number. They need to be set separately:

  • Sampling proportion: how often each source appears in the data loader. It affects the composition of the gradient estimate.
  • Sample weight: the multiplier by which each sample contributes to the loss. It affects how much an identical signal is amplified. Sampling proportion and weight must be used together for precise control: actual contribution = sampling proportion × weight. When you want to fine-tune a bucket's contribution without changing the batch composition, adjusting the weight alone is the correct move.
  • Cleaning and retention rate: the proportion of real data retained before mixing, and whether it gets filtered in the same way. This one is often overlooked, yet it decides whether those "noise-like" samples in real data (colloquial speech, typos, unconventional expressions) are kept or cleaned away. These are precisely the samples the model most needs to handle in real scenarios.

A specific warning about the third item. Synthetic data usually comes from a prompt pool and is naturally "clean", while real data contains a large amount of "unclean" but perfectly valid samples. If the pipeline applies the same cleaning standard to real data as to synthetic data for the sake of uniformity (length truncation, format normalization, deduplication thresholds), it systematically erodes the long tail of the real distribution. The result is a model that performs well on "standardized" input and poorly on real input — and the latter is what actually happens in production.

8.3 Validating the ratio: it must be done on real held-out data

A common mistake when tuning ratios is validating with an "evaluation set drawn from the synthetic distribution". If the evaluation set comes from the same generation process and the same prompt pool, then differences between ratio variants on it are severely distorted — because raising the synthetic proportion simultaneously raises how well both training and evaluation fit the same batch of distribution.

The correct validation protocol:

  • The evaluation set must come from the real distribution and be fixed before the pipeline starts, never participating in any generation or filtering.
  • The evaluation set must be stratified; the total score alone is not enough. The total score hides structural weaknesses: the composite score may be flat while the score for the long tail of tool-call turns drops by 10 points. Local regressions of exactly this kind are what synthetic ratio adjustments trigger most easily.
  • Keep a purely human-written, very small evaluation set (say 200 to 500 items) that covers structural diversity. Its job is to detect distribution drift — an automated process can hardly discover this drift by itself, because it still runs on the same set of assumptions.

The last item corresponds to a routinely underestimated mechanism: the evaluation set of a synthetic pipeline drifts along with the pipeline. Every round of tasks generated from the prompt pool reinforces the pipeline's inherent preferences, and if the evaluation set is sampled from the prompt pool it gradually aligns with that preference too. The process is slow and usually takes 4 to 6 rounds to become visible, but once visible the team concludes "metrics are steadily improving, everything is fine". The only real signal is the score on real held-out data, and by then it has usually already been dropping for several rounds.

Nine: Evaluating and Degradation-Proofing the Pipeline — Making Synthetic Data Observable and Stoppable

9.1 Each stage needs its own independent evaluation set

Evaluating a synthetic data pipeline cannot rest on a single set of metrics. Broken down by stage, you need at least three evaluation sets carrying different responsibilities:

  • Generation evaluation set: measures "whether the generated samples are good in themselves", without involving training. It assesses prompt pool quality, the reasonableness of sampling parameters, and whether the generation model needs replacing. Metrics include format compliance rate, truncation rate, and semantic consistency with the prompt.
  • Verification evaluation set: measures "whether the verifier judges accurately". This is the most worth building and the most often omitted. The practice is to hand-label a batch of verification results (on the order of 1000 to 5000 items) as ground truth and measure the verifier's accuracy, false negative rate (a correct sample judged wrong, which wastes good data), and false positive rate (a wrong sample judged correct, which contaminates the training set). The false positive rate is the fatal metric, because it directly determines the reliability of your gold labels, whereas a false negative is only an efficiency loss.
  • Training evaluation set: held-out data from the real distribution, measuring final effectiveness. The requirements for this one were already covered in 8.3 — it must be stratified, fixed, and include a small human-written subset.

The relationship among the three is this: the verification evaluation set determines the credibility of the generation evaluation set, and the generation evaluation set determines how cleanly you can attribute results in the training evaluation set. Skipping the second means building the entire pipeline on an uncalibrated filter, and when something breaks you cannot attribute it — you cannot tell whether generation was poor or verification was wrong.

9.2 Five concrete mechanisms against degradation

9.2.1 Independent auditing of the verifier

Verifier assimilation (section 3.4) is the most dangerous failure mode in the loop because it does not trigger any conventional alarm. There are three layers of defense:

  • Continuously monitor the score correlation between verifier and generator. A rising correlation is an early signal of assimilation. A sudden drop in correlation also deserves attention — it usually means the verifier has a bug.
  • Rotate the source periodically. Use external real labels (even a small amount of high-quality human annotation) to keep updating the verifier. Note that this is not retraining — it is adding a correction term based on an external anchor to the verifier's score, and that is often enough.
  • Keep an "adversarial verifier". Its training objective is specifically to identify problems the main verifier would let through (for example output that is excessively fluent but logically broken). Its role is not to replace the main verifier but to keep feeding it negative feedback.

9.2.2 Data fingerprints and duplicate detection

A risk specific to synthetic data is hidden duplication across rounds. The same batch of prompts gets used repeatedly across iterations, and the model eventually ends up trained on those tasks — and because each round of sampling is random, they look like "new data" on the surface while actually being a resampling of the same batch. This produces inflated training metrics, stagnant real generalization, and is very hard to detect, because it violates none of the existing monitoring items.

The detection method is to maintain a cross-round fingerprint store: for every sample entering the pool, record an exact fingerprint (hash of the normalized text), a semantic fingerprint (coarse quantization of the embedding vector), and a provenance fingerprint (prompt pool ID + variant ID). Run three-level deduplication before each round's ingestion. The provenance level is especially useful, because it directly tells you "what proportion of this round is a resampling of last round's tasks" — if that number exceeds 50%, you can basically conclude that this round produced no new information.

9.2.3 Versioning and lineage management

Synthetic data must be managed like code. The concrete requirements:

  • Every sample records its full generation lineage: prompt pool version, generation model version, sampling parameters, verifier version, round ID.
  • Datasets are themselves versioned, and each version freezes an immutable set of sample IDs.
  • Training runs record the exact dataset version and commit hash they used.
  • Any metric anomaly can be traced back within minutes to "which batch of data, which verifier, which parameter set" caused it.

This one looks like engineering fastidiousness but is actually a core anti-degradation capability. Synthetic pipelines iterate fast; without lineage tracking, metric anomalies are nearly impossible to attribute and the team ends up relying on trial and error, which has a very low success rate in a pipeline where many variables change at once.

9.2.4 Human injection: a fixed anchor against drift

Every round must inject a certain amount of human-written samples, sourced from: real production failure cases, domains where the system fails systematically, and knowledge blind spots identified by domain experts. The value of this data is not in its scale (a proportion of 5% to 10% is usually enough) but in the fact that it is the only distribution that cannot possibly be assimilated into the pipeline's own preferences. It is the immune system of the whole system.

A practical recommendation is to make the human sample set a long-term maintained collection, only augmented and never removed, and to track its evaluation performance separately. If even the score on the human sample set is declining, that means the model has experienced fundamental capability degradation and you must trip the circuit breaker immediately.

9.2.5 Circuit breakers and stopping conditions

The pipeline's stopping conditions must be defined before it starts, otherwise every time it is time to stop there will be the temptation to "run one more round". Stopping conditions at three levels:

  • Single-metric circuit breaker: the real held-out score drops for two consecutive rounds by more than a preset threshold (say 0.5 points) — pause the pipeline and run attribution analysis. This threshold can be set fairly sensitive, because a false alarm costs little: just one extra round of analysis time.
  • Diversity circuit breaker: any of the collapse alarms in the code in the fourth section fires and has not recovered by the next round — pause the pipeline. This breaker fires earlier than the single-metric one, because collapse usually starts 2 to 3 rounds before the metric drops.
  • Marginal return circuit breaker: across three consecutive rounds, a bucket's sample volume doubled while the real held-out score moved within noise. That means the bottleneck is no longer data volume but the strength of the verification signal or the base model's capability; adding more synthetic data is waste.

The last one is the most valuable of the three breakers, because it answers the fundamental question of "should we continue". Many teams keep investing after the pipeline has entered a plateau, because "the last round still improved". Marginal return monitoring makes that curve explicit, turning stopping into a data-based judgment rather than an emotional one.

9.3 A circuit breaker decision table

Observed symptomMost likely causeFirst thing to checkResponse
Pass rate rises, real held-out dropsDistribution collapse; the model is fitting the synthetic distributionSemantic cluster count, nearest-neighbor distance distribution, share of the top clusterRaise the temperature, tighten the cluster cap, temporarily raise the real-data blending ratio
Pass rate rises, real held-out unchangedVerifier assimilation; the loop is self-certifyingCorrelation between verifier score and generator confidenceIntroduce an out-of-source verifier or an external correction term
Pass rate unchanged, real held-out dropsThe difficulty frontier moved up as a whole and the sampling distribution did not followPer-bucket pass rate distribution, the lag of difficulty labelsShift the bucketing thresholds per the method in 5.4 and re-probe
One class of tasks drops sharply, the rest normalStructural coverage gap; the bucketing policy excluded that formStructural entropy distribution, sample volume and round trend for that structureSet a dedicated floor quota for that form, bypassing value-driven allocation
Score on the human sample set declinesFundamental capability degradation, not a distribution problemReal data proportion, forgetting curve, training roundsTrip the breaker immediately; check the ratio and the replay mechanism
Verifier accuracy declines across roundsTask distribution drift; insufficient verifier coverageStratified scores on the verification evaluation set, newly emerging failure typesExpand the stratified evaluation set, retrain or rotate the verifier
Human review rejection rate is highSynthetic quality is slipping, or there is construction biasShare per defect type, edit distance distributionTop up in a targeted way per defect type; pause the biased types

9.4 When you should give up on synthetic data entirely

There are three situations that a synthetic data methodology cannot solve, where you should switch paths outright:

  1. The task has no constructible verification signal. If a task's correctness is essentially subjective, or can only be confirmed through real-world interaction, then the quality ceiling of synthetic data is locked at the ceiling of the verifier. Forcing it produces a model that "looks right but is systematically biased". The correct approach for such tasks is to invest in real annotation and use synthetic data for data augmentation (providing variants and near-neighbor samples) rather than as a replacement.
  2. The base capability itself is insufficient. If the model cannot exceed some level on any form of synthetic data, continuing to invest in synthetic data is waste. The basis for judgment is the shape of the marginal return circuit-breaker curve: rising early, flattening in the middle, declining later — the declining segment means model capacity or base model quality is the bottleneck.
  3. The distribution stays right-skewed and cannot be constrained. If the distributional distance between synthetic and real data keeps widening even with interventions, that gap cannot be closed by sampling strategy; it comes from beyond the generation model's capability boundary. At that point synthetic data can only be used to expand variants of a task form, not its content itself.

Ten: Bringing It Together — One Executable Spine

Compressed into a single thread, the whole article looks like this:

Use synthetic data to build a self-reinforcing loop; use the verification signal to guarantee that every iteration of that loop genuinely improves; use diversity constraints and curriculum orchestration to keep it from going off track; use real data as a coordinate system that never drifts; use strict stopping conditions to make sure it stops in time when there is no return.

If you can only remember six things, ordered by importance:

  1. The legitimacy of gold labels comes from the decision signal, not from the generator. When budget is tight, invest in the verifier before the generator.
  2. Training value concentrates at the edge of capability. The 0.1 to 0.5 pass rate band is where synthetic data has the highest input-output ratio, and the prompt pool must be updated on a rolling basis as model capability moves.
  3. Correct is not the same as useful. A chain of reasoning that passes verification may harbor systematic errors in the middle, which a verdict-only verifier cannot see.
  4. Collapse is a distribution problem, not a correctness problem. You must monitor semantic coverage and task-form coverage; monitoring pass rate alone will miss it.
  5. The ratio is stage-dependent, and the three axes are tuned separately. Sampling proportion, sample weight, and cleaning retention rate are three different things.
  6. Stopping conditions must be defined by real held-out data and decided in advance. Diversity collapse typically appears 2 to 3 rounds before the metric drops.

A last practical reminder: a synthetic data pipeline is a stateful system, and its degradation is gradual, silent, and multi-round, while a team's attention is concentrated, fragmented, and anchored to the latest metric. That mismatch is the real reason the vast majority of synthetic data projects eventually fail — not because some step was done wrong, but because nobody kept watching the metrics that need to be watched continuously. So making the diagnostic layer a permanent component rather than something you assemble when something breaks is the thing most easily skipped on this pipeline, and the thing least deserving of being skipped.