build(agent): jabba#56a767 iteration
This commit is contained in:
parent
08901363ff
commit
14fef58a96
|
|
@ -0,0 +1,106 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
"""SEU fuzzing harness: deterministic bitflip injection for tensors.
|
||||||
|
|
||||||
|
This small utility injects single-event-upset-style bit flips into
|
||||||
|
numpy arrays (viewed as raw bytes). It is deterministic given a seed
|
||||||
|
and returns a small bundle describing the flips for reproducible
|
||||||
|
counterexample bundles used in CI and forensics.
|
||||||
|
|
||||||
|
Functions:
|
||||||
|
- inject_bitflips(arr, rate=1e-3, seed=None): returns (flipped, bundle)
|
||||||
|
- detect_corruption(arr, bundle): quick checksum-based detector
|
||||||
|
|
||||||
|
The implementation is intentionally small and pure-Python to keep tests
|
||||||
|
fast and dependency-free (only numpy used, which the project already
|
||||||
|
depends on).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
import zlib
|
||||||
|
import hashlib
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SEUBundle:
|
||||||
|
seed: Optional[int]
|
||||||
|
rate: float
|
||||||
|
flips: List[int]
|
||||||
|
original_crc32: int
|
||||||
|
|
||||||
|
|
||||||
|
def _crc32_bytes(b: bytes) -> int:
|
||||||
|
return zlib.crc32(b) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def inject_bitflips(
|
||||||
|
arr: np.ndarray,
|
||||||
|
rate: float = 1e-3,
|
||||||
|
seed: Optional[int] = None,
|
||||||
|
max_flips: Optional[int] = None,
|
||||||
|
) -> Tuple[np.ndarray, SEUBundle]:
|
||||||
|
"""Inject deterministic bit flips into a numpy array's bytes.
|
||||||
|
|
||||||
|
- arr: input numpy array (will not be modified; a copy is returned)
|
||||||
|
- rate: probability of flipping each bit (0..1). Small values recommended.
|
||||||
|
- seed: optional RNG seed for determinism. If None, non-deterministic.
|
||||||
|
- max_flips: optional cap on number of flips applied.
|
||||||
|
|
||||||
|
Returns (flipped_array, bundle) where bundle records seed, rate,
|
||||||
|
list of flipped bit indices (as bit offsets into the raw buffer),
|
||||||
|
and the CRC32 of the original bytes for detection.
|
||||||
|
"""
|
||||||
|
if rate < 0.0 or rate > 1.0:
|
||||||
|
raise ValueError("rate must be in [0,1]")
|
||||||
|
|
||||||
|
rnd = np.random.RandomState(seed)
|
||||||
|
|
||||||
|
orig_bytes = arr.tobytes()
|
||||||
|
orig_crc = _crc32_bytes(orig_bytes)
|
||||||
|
|
||||||
|
# Represent data as a mutable bytearray so we can flip bits
|
||||||
|
b = bytearray(orig_bytes)
|
||||||
|
n_bits = len(b) * 8
|
||||||
|
|
||||||
|
# decide which bits to flip
|
||||||
|
# draw bernoulli per-bit; to remain efficient for small arrays we sample indices
|
||||||
|
flip_mask = rnd.rand(n_bits) < rate
|
||||||
|
idxs = np.nonzero(flip_mask)[0].tolist()
|
||||||
|
|
||||||
|
if max_flips is not None and len(idxs) > max_flips:
|
||||||
|
idxs = idxs[:max_flips]
|
||||||
|
|
||||||
|
# apply flips: for each bit index, flip the corresponding bit in the bytearray
|
||||||
|
for bit_idx in idxs:
|
||||||
|
byte_idx = bit_idx // 8
|
||||||
|
bit_in_byte = bit_idx % 8
|
||||||
|
b[byte_idx] ^= 1 << bit_in_byte
|
||||||
|
|
||||||
|
flipped = np.frombuffer(bytes(b), dtype=np.uint8).view(arr.dtype).reshape(arr.shape).copy()
|
||||||
|
|
||||||
|
bundle = SEUBundle(seed=seed, rate=rate, flips=idxs, original_crc32=orig_crc)
|
||||||
|
return flipped, bundle
|
||||||
|
|
||||||
|
|
||||||
|
def detect_corruption(arr: np.ndarray, bundle: SEUBundle) -> bool:
|
||||||
|
"""Return True if the array's bytes differ from the bundle's recorded CRC32."""
|
||||||
|
current_crc = _crc32_bytes(arr.tobytes())
|
||||||
|
return current_crc != bundle.original_crc32
|
||||||
|
|
||||||
|
|
||||||
|
def reproduce_flips(arr: np.ndarray, bundle: SEUBundle) -> np.ndarray:
|
||||||
|
"""Reproduce the flips recorded in bundle on a copy of arr.
|
||||||
|
|
||||||
|
This is useful for deterministic replay: it applies the listed bit
|
||||||
|
offsets to a fresh copy of the original array.
|
||||||
|
"""
|
||||||
|
b = bytearray(arr.tobytes())
|
||||||
|
for bit_idx in bundle.flips:
|
||||||
|
byte_idx = bit_idx // 8
|
||||||
|
bit_in_byte = bit_idx % 8
|
||||||
|
b[byte_idx] ^= 1 << bit_in_byte
|
||||||
|
out = np.frombuffer(bytes(b), dtype=np.uint8).view(arr.dtype).reshape(arr.shape).copy()
|
||||||
|
return out
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import numpy as np
|
||||||
|
from nebulaforge.robustness import inject_bitflips, detect_corruption, reproduce_flips
|
||||||
|
|
||||||
|
|
||||||
|
def test_inject_deterministic_and_detect():
|
||||||
|
a = np.arange(16, dtype=np.float32)
|
||||||
|
# use a noticeable rate so we flip some bits
|
||||||
|
flipped1, bundle = inject_bitflips(a, rate=0.01, seed=42)
|
||||||
|
|
||||||
|
# detector should say corruption relative to original
|
||||||
|
assert detect_corruption(flipped1, bundle)
|
||||||
|
|
||||||
|
# reproducing flips on original should yield identical bytes
|
||||||
|
reproduced = reproduce_flips(a, bundle)
|
||||||
|
assert reproduced.tobytes() == flipped1.tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_reproducible_with_same_seed():
|
||||||
|
a = np.linspace(-1.0, 1.0, num=64, dtype=np.float32)
|
||||||
|
f1, b1 = inject_bitflips(a, rate=0.005, seed=123)
|
||||||
|
f2, b2 = inject_bitflips(a, rate=0.005, seed=123)
|
||||||
|
|
||||||
|
# bundles should be identical for same seed
|
||||||
|
assert b1.seed == b2.seed
|
||||||
|
assert b1.flips == b2.flips
|
||||||
|
# flipped arrays equal
|
||||||
|
assert f1.tobytes() == f2.tobytes()
|
||||||
Loading…
Reference in New Issue