Source code for seu_injection.core.stochastic_seu_injector

"""Stochastic SEU Injector Module.

This module provides the `StochasticSEUInjector` class, which performs random bit flips in model parameters to evaluate
statistical robustness under fault injection scenarios.
"""

from typing import Any, Union

import numpy as np
import torch
from tqdm import tqdm

from ..bitops import bitflip_float32_optimized
from .base_injector import BaseInjector


[docs] class StochasticSEUInjector(BaseInjector): """Stochastic SEU injector for PyTorch models. Randomly flips bits in float32 weights across all layers (or a specified layer), evaluating model performance after each injection. Notes: - Use for statistical fault analysis in large models. - Injection probability p controls sample size and efficiency. - All injections are reversible; model is restored after each run. Example: >>> injector = StochasticSEUInjector(model, criterion, x=data, y=labels) >>> results = injector.run_injector(bit_i=15, p=0.01) >>> print(len(results['criterion_score'])) """ def _run_injector_impl(self, bit_i: int, layer_name: Union[str, None] = None, **kwargs) -> dict[str, list[Any]]: """Randomly inject faults into model parameters using probability p. Args: bit_i (int): Bit position to flip (0-31). layer_name (Optional[str]): Layer to target (None for all). p (float, via kwargs): Probability of injection for each parameter (0.0-1.0). run_at_least_one_injection (bool, via kwargs): If True, ensure at least one injection per layer even if p is very small. Default is True to prevent empty results in smoke tests. Returns: dict[str, list[Any]]: Results including tensor locations, scores, layer names, values before/after. Raises: ValueError: If p is not in [0.0, 1.0] or bit_i is not in [0, 32]. RuntimeError: If model evaluation fails. Notes: - Efficient for large models and statistical analysis. - All injections are reversible; model is restored after each run. """ p = kwargs.get("p", 0.0) run_at_least_one_injection = kwargs.get("run_at_least_one_injection", True) if not (0.0 <= p <= 1.0): raise ValueError(f"Probability p must be in [0, 1], got {p}") results: dict[str, list[Any]] = { "tensor_location": [], "criterion_score": [], "layer_name": [], "value_before": [], "value_after": [], } with torch.no_grad(): # Disable gradient tracking for efficiency # Iterate through each layer of the neural network for current_layer_name, tensor in self.model.named_parameters(): # Skip layer if specific layer requested and this isn't it if layer_name and layer_name != current_layer_name: continue print(f"Testing Layer: {current_layer_name}") # Store original tensor values for restoration original_tensor = tensor.data.clone() tensor_cpu = original_tensor.cpu().numpy() # ✅ PERFORMANCE: Now uses optimized bitflip function (major improvement) # IMPROVEMENT: Stochastic sampling now uses bitflip_float32_optimized() # PERFORMANCE GAIN: ~30x faster per operation (100μs → 3μs per bitflip) # NEW: Mask-based approach for better performance and cleaner logic # Build a boolean mask for stochastic selection injection_mask = np.random.random(tensor_cpu.shape) < p # Check if at least one injection will occur if run_at_least_one_injection and not injection_mask.any() and tensor_cpu.size > 0: # If no injections selected and we need at least one, pick one randomly random_idx = tuple(np.random.randint(0, dim) for dim in tensor_cpu.shape) injection_mask[random_idx] = True # Get indices where injections should occur injection_indices = np.argwhere(injection_mask) # Perform injections for selected indices for idx_array in tqdm( injection_indices, desc=f"Stochastic injection into {current_layer_name}", ): idx = tuple(idx_array) original_val = tensor_cpu[idx] seu_val = bitflip_float32_optimized(original_val, bit_i, inplace=False) # Inject fault, evaluate, restore tensor.data[idx] = torch.tensor(seu_val, device=self.device, dtype=tensor.dtype) criterion_score = self._get_criterion_score() tensor.data[idx] = original_tensor[idx] # Restore original value # Record results results["tensor_location"].append(idx) results["criterion_score"].append(criterion_score) results["layer_name"].append(current_layer_name) results["value_before"].append(original_val) results["value_after"].append(seu_val) return results