Virtual Metrology & Process Control

Deploying VM in Production

Handling model drift, building adaptive models, and estimating confidence for every prediction

Model Drift in Semiconductor Fabs

Model Drift in Semiconductor Fabs

Unlike many ML domains where data distributions shift gradually, semiconductor processes experience abrupt, predictable drift events:

Sources of Drift

  • Preventive Maintenance (PM): Chamber cleaning, part replacement. Sensor baselines shift overnight. This is the #1 cause of VM model degradation.
  • Consumable wear: Focus rings, showerheads, and electrostatic chuck coatings wear slowly, causing gradual drift between PMs.
  • Recipe changes: Process engineers tweak recipes for yield improvement. Even small changes can invalidate VM models.
  • Upstream variation: Changes in incoming wafer properties (e.g., different film thickness from a preceding step) alter the relationship between sensors and outcomes.

Analogy: The Recalibrated Scale

Imagine you built a model to predict someone's weight from their height. Then everyone starts wearing heavy boots (PM event). Your model's predictions are all off by 5 kg — not because the model is wrong, but because the "input distribution" shifted. You need to recalibrate.

import numpy as np
import pandas as pd
from sklearn.metrics import mean_absolute_error

def detect_vm_drift(predictions, actuals, timestamps,
                    window_size=50, threshold_multiplier=2.0):
    """Detect VM model drift using moving-window residual monitoring."""
    residuals = actuals - predictions

    # Baseline statistics from qualification period
    baseline_mae = np.mean(np.abs(residuals[:window_size]))
    baseline_bias = np.mean(residuals[:window_size])
    baseline_std = np.std(residuals[:window_size])

    # Moving window monitoring
    drift_alerts = []
    for i in range(window_size, len(residuals)):
        window = residuals[i-window_size:i]
        current_mae = np.mean(np.abs(window))
        current_bias = np.mean(window)

        # Check for drift
        mae_ratio = current_mae / baseline_mae
        bias_shift = abs(current_bias - baseline_bias) / baseline_std

        if mae_ratio > threshold_multiplier or bias_shift > 3.0:
            drift_alerts.append({
                'timestamp': timestamps.iloc[i],
                'mae_ratio': mae_ratio,
                'bias_shift_sigma': bias_shift,
                'type': 'accuracy' if mae_ratio > threshold_multiplier else 'bias'
            })

    return pd.DataFrame(drift_alerts)

Adaptive and Self-Updating Models

Adaptive and Self-Updating Models

Static models degrade. Production VM systems must adapt. Here are the major strategies:

1. Moving Window Retraining

Retrain the model periodically using only the most recent N wafers. Simple and effective, but requires a steady stream of physical metrology data.

2. Bias Correction (Global Bias + Local Bias)

Instead of retraining, adjust predictions by the recent average residual. This handles systematic shifts (like post-PM offset) cheaply.

3. Incremental / Online Learning

Update model weights with each new metrology measurement without full retraining. Works well for linear models; harder for tree ensembles.

import numpy as np
from collections import deque

class AdaptiveVMPredictor:
    """VM predictor with bias correction and drift detection."""

    def __init__(self, base_model, bias_window=20, confidence_window=50):
        self.base_model = base_model
        self.bias_window = bias_window
        self.recent_residuals = deque(maxlen=bias_window)
        self.confidence_residuals = deque(maxlen=confidence_window)
        self.global_bias = 0.0

    def predict_with_confidence(self, X):
        """Predict with bias correction and confidence interval."""
        raw_pred = self.base_model.predict(X.reshape(1, -1))[0]

        # Apply bias correction
        corrected_pred = raw_pred + self.global_bias

        # Confidence interval from recent residual distribution
        if len(self.confidence_residuals) >= 10:
            residual_std = np.std(list(self.confidence_residuals))
            ci_lower = corrected_pred - 1.96 * residual_std
            ci_upper = corrected_pred + 1.96 * residual_std
        else:
            ci_lower = ci_upper = None  # Not enough data

        return {
            'prediction': corrected_pred,
            'raw_prediction': raw_pred,
            'bias_correction': self.global_bias,
            'ci_lower': ci_lower,
            'ci_upper': ci_upper,
        }

    def update(self, actual_value, predicted_value):
        """Update bias correction when actual metrology arrives."""
        residual = actual_value - predicted_value
        self.recent_residuals.append(residual)
        self.confidence_residuals.append(residual)

        # Update global bias
        if len(self.recent_residuals) >= 5:
            self.global_bias = np.mean(list(self.recent_residuals))

Key Concept: PM-Aware Adaptation

Smart VM systems detect PM events (from equipment logs or MES) and take special action: widen confidence intervals immediately after PM, increase physical metrology sampling to rebuild the bias estimate, and optionally switch to a "post-PM" model variant trained specifically on post-PM data.

Confidence Estimation

Confidence Estimation

A VM prediction without a confidence estimate is dangerous. The fab needs to know when to trust a VM prediction and when to send the wafer for physical measurement.

Approaches to VM Confidence

  • Residual-based: Use the distribution of recent residuals to estimate prediction intervals. Simple and effective.
  • Novelty detection: Measure how "different" the current wafer's FDC data is from training data. High novelty → low confidence.
  • Ensemble disagreement: Train multiple models; large disagreement → low confidence.
  • Conformal prediction: A principled, distribution-free framework that guarantees coverage probability.
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.neighbors import LocalOutlierFactor
import numpy as np

class VMConfidenceEstimator:
    """Estimate VM prediction confidence using novelty + ensemble methods."""

    def __init__(self, n_models=5):
        self.models = [
            GradientBoostingRegressor(
                n_estimators=200, max_depth=4,
                subsample=0.8, random_state=i
            ) for i in range(n_models)
        ]
        self.novelty_detector = LocalOutlierFactor(
            n_neighbors=20, novelty=True
        )

    def fit(self, X_train, y_train):
        for model in self.models:
            # Bootstrap sampling for diversity
            idx = np.random.choice(len(X_train), len(X_train), replace=True)
            model.fit(X_train[idx], y_train[idx])
        self.novelty_detector.fit(X_train)

    def predict_with_confidence(self, X):
        predictions = np.array([m.predict(X) for m in self.models])
        mean_pred = predictions.mean(axis=0)
        std_pred = predictions.std(axis=0)  # Ensemble disagreement

        # Novelty score (negative = more outlier-like)
        novelty_scores = self.novelty_detector.decision_function(X)

        # Combined confidence: low std AND high novelty score = high confidence
        # Normalize to 0-1 range
        confidence = 1.0 / (1.0 + std_pred) * np.clip(
            (novelty_scores + 2) / 4, 0, 1
        )

        return mean_pred, confidence, std_pred

Did You Know?

Some advanced fabs use a "smart sampling" strategy driven by VM confidence: if the VM model is confident about a wafer's metrology, skip physical measurement. If confidence is low, route the wafer to a metrology tool. This dynamically adjusts sampling rate — measuring more when it matters and less when the model is sure.

Knowledge Check

Knowledge Check

1 / 3

What is the #1 cause of VM model degradation in production?