Benchmark Saturation › Code Implementation

Michael BrenndoerferMarch 6, 20268 min read
Language AI HandbookAll chapters
Part 54 of 67Benchmark Evaluation
Chapter 422Benchmark Saturation › Code Implementation
Reading section 8 of 13 · Code Implementation · 8 minSection 8 of 13 · 8 min

Book contents

Section 8 of 13 · 8 min read

Benchmark Saturation › Code ImplementationOne section · Sections in this chapter8 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

We'll analyze synthetic benchmark data representing model scores over time to demonstrate saturation detection techniques. The synthetic data follows logistic growth curves, which are characteristic of technology adoption and capability improvement: starting with slow progress, accelerating through a period of rapid improvement, and finally plateauing as capabilities approach physical or theoretical limits. This S-shaped trajectory describes the observed behavior of real benchmarks well, and will allow us to illustrate the statistical detection methods with controlled, reproducible data.

First, let's set up our analysis environment and generate representative data.

In[8]:
Code
import numpy as np
import pandas as pd

# Set random seed for reproducibility
np.random.seed(42)

# Generate synthetic benchmark saturation data
# Simulating GLUE-like trajectory from 2018-2024
years = np.arange(2018, 2025.25, 0.25)
n_timepoints = len(years)


# Logistic growth curve for accuracy: starts low, accelerates, plateaus
def logistic_growth(t, L, k, t0):
    """Logistic growth: L is max, k is growth rate, t0 is midpoint"""
    return L / (1 + np.exp(-k * (t - t0)))


# Parameters for different benchmarks
benchmarks = {
    "GLUE (Easy)": {"L": 0.95, "k": 2.5, "t0": 2019.0, "noise": 0.01},
    "SuperGLUE (Medium)": {"L": 0.92, "k": 2.0, "t0": 2020.5, "noise": 0.015},
    "MMLU (Hard)": {"L": 0.94, "k": 1.8, "t0": 2022.0, "noise": 0.02},
    "HumanEval (Very Hard)": {"L": 0.95, "k": 1.2, "t0": 2023.5, "noise": 0.03},
}

# Generate scores
data = {}
for name, params in benchmarks.items():
    base_score = logistic_growth(years, params["L"], params["k"], params["t0"])
    noise = np.random.normal(0, params["noise"], len(years))
    data[name] = np.clip(base_score + noise, 0, 1)

df = pd.DataFrame(data, index=years)
df.index.name = "Year"

The four benchmarks represent a spectrum of difficulty levels, each with different logistic growth parameters. GLUE has the steepest growth rate (k=2.5k = 2.5) and an early midpoint (2019), leading to rapid early saturation. HumanEval has the gentlest growth rate (k=1.2k = 1.2) and a late midpoint (2023.5), maintaining discriminative power longer. The added noise mimics the natural stochasticity in benchmark scores due to evaluation randomness and differences between model training runs.

Out[9]:
Visualization
(0.4, 1.0)
Line plot showing accuracy over time for four benchmarks, with GLUE plateauing earliest and HumanEval still rising in 2024.
Benchmark saturation curves showing logistic growth trajectories for four benchmarks of varying difficulty. Easier benchmarks (GLUE) saturate quickly and plateau well before 2021, while harder benchmarks (HumanEval) maintain a rising trajectory through 2024. The 90% saturation threshold line separates the informative region below from the saturated region above.

The visualization reveals distinct saturation phases. GLUE approaches its ceiling by 2020, while HumanEval continues to show growth through 2024. This demonstrates how benchmark difficulty extends useful lifespan. A benchmark's maximum score ceiling matters less than the rate at which it is approached; a benchmark with a 95% ceiling that takes a decade to reach is far more useful than one with a 90% ceiling that is reached in two years.

Next, let's calculate statistical indicators of saturation, specifically tracking the coefficient of variation over time to measure how much models are converging.

In[10]:
Code
# Calculate rolling coefficient of variation to detect convergence
def rolling_cv(series, window=4):
    """Calculate coefficient of variation over rolling window"""
    rolling_std = series.rolling(window=window).std()
    rolling_mean = series.rolling(window=window).mean()
    return rolling_std / rolling_mean


# Calculate CV for each benchmark
cv_data = {}
for col in df.columns:
    cv_data[col] = rolling_cv(df[col])

cv_df = pd.DataFrame(cv_data, index=df.index)
Out[11]:
Visualization
Line plot showing coefficient of variation declining over time at different rates for four benchmarks, with GLUE reaching near zero first.
Coefficient of variation over time for each benchmark, computed using a four-quarter rolling window. Declining CV indicates models converging toward similar performance levels, a quantitative hallmark of saturation. GLUE reaches near-zero CV by 2021, indicating that all top models perform identically and the benchmark has exhausted its discriminative capacity.

The coefficient of variation plot shows GLUE's CV dropping to near zero by 2021, indicating that all top models perform identically on this benchmark. In contrast, HumanEval maintains higher variance, suggesting it still discriminates between model capabilities. The difference in CV trajectories directly reflects the difference in benchmark difficulty and how long each benchmark retains diagnostic value.

Now let's implement a function to detect when a benchmark has statistically saturated based on slope analysis and variance thresholds.

In[12]:
Code
def detect_saturation(
    years, scores, saturation_threshold=0.90, slope_threshold=0.02, window=4
):
    """
    Detect saturation point based on:
    1. Exceeding accuracy threshold
    2. Slope falling below threshold (improvement slows)
    3. Low variance in recent window
    """
    # Calculate rolling slope (rate of improvement)
    slopes = np.gradient(scores, years)
    rolling_slope = pd.Series(slopes).rolling(window=window).mean().values

    # Calculate rolling CV
    rolling_std = pd.Series(scores).rolling(window=window).std().values
    rolling_mean = pd.Series(scores).rolling(window=window).mean().values
    rolling_cv = rolling_std / rolling_mean

    # Find saturation point: where accuracy > threshold and slope < threshold
    saturated_indices = []
    for i in range(window, len(scores)):
        if (
            scores[i] > saturation_threshold
            and abs(rolling_slope[i]) < slope_threshold
            and rolling_cv[i] < 0.05
        ):  # Less than 5% variation
            saturated_indices.append(i)

    if saturated_indices:
        first_saturation = saturated_indices[0]
        return {
            "saturated": True,
            "year": years[first_saturation],
            "score": scores[first_saturation],
            "indices": saturated_indices,
        }
    return {"saturated": False, "year": None, "score": None}


# Detect saturation for each benchmark
saturation_results = {}
for col in df.columns:
    result = detect_saturation(df.index.values, df[col].values)
    saturation_results[col] = result

The detect_saturation function applies three simultaneous criteria. First, the accuracy must exceed the saturation threshold, so we only flag benchmarks where performance is high. Second, the rolling slope must fall below the slope threshold, capturing the slowdown in improvement rate that characterizes a plateau. Third, the rolling CV must be small, confirming that scores are converging rather than fluctuating. Requiring all three conditions together prevents false positives from temporary slowdowns or noise spikes.

Out[13]:
Console
Saturation Detection Results
==================================================
GLUE (Easy): SATURATED at 2021.25 (score: 0.927)
SuperGLUE (Medium): SATURATED at 2023.25 (score: 0.921)
MMLU (Hard): SATURATED at 2024.75 (score: 0.923)
HumanEval (Very Hard): NOT YET SATURATED

The detection algorithm confirms that GLUE saturates earliest (around 2020.25), while HumanEval remains unsaturated through the observation period. This matches the empirical observation that code generation benchmarks maintain discriminative power longer than natural language understanding tasks, because code execution provides an unambiguous correctness signal that is harder to game and requires new problem-solving ability for each distinct challenge.

Finally, let's simulate the effect of adversarial filtering (as used in HellaSwag) to show how dynamic difficulty adjustment extends benchmark lifespan.

In[14]:
Code
# Simulate adversarial filtering extending benchmark lifespan
def simulate_adversarial_lifecycle(
    years, initial_difficulty=0.5, adaptation_rate=0.3
):
    """
    Simulate a benchmark that increases difficulty as models improve
    """
    scores = []
    difficulty = initial_difficulty

    for year in years:
        # Base capability improves over time (logistic)
        base_capability = logistic_growth(year, 0.95, 1.5, 2021)

        # Effective score is capability relative to current difficulty
        # If capability > difficulty, model solves it, triggering difficulty increase
        effective_score = min(base_capability / difficulty, 0.95)  # Cap at 0.95

        scores.append(effective_score)

        # Adversarial adaptation: if score > 0.85, increase difficulty
        if effective_score > 0.85:
            difficulty = min(difficulty * (1 + adaptation_rate), 0.95)

    return np.array(scores)


# Generate adversarial benchmark trajectory
adv_scores = simulate_adversarial_lifecycle(years)

# Compare to static benchmark
static_scores = logistic_growth(years, 0.95, 1.5, 2021) + np.random.normal(
    0, 0.01, len(years)
)
static_scores = np.clip(static_scores, 0, 1)
Out[15]:
Visualization
(0.4, 1.0)
Line plot comparing static benchmark saturation curve to dynamic adversarial benchmark maintaining steady performance in the 80-90% range.
Comparison of a static benchmark (which saturates by 2022) versus a dynamic adversarial benchmark that continuously adjusts difficulty when scores exceed 85%. The dynamic benchmark maintains a steady performance range of 80-90%, preserving discriminative power through 2024 and beyond. This controlled oscillation around the target performance level is the fundamental goal of adversarial benchmark design.

The adversarial benchmark maintains utility by continuously adjusting difficulty upward as models improve, preventing the plateau effect seen in static evaluations. The dynamic benchmark does not optimize for maximum accuracy; it optimizes for maintaining a target difficulty level, keeping scores in the informative 60-85% range where differences between models are statistically detectable.