Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Antisource/heronaisec/llms.txt

Use this file to discover all available pages before exploring further.

The utils module provides the infrastructure glue that holds experiment scripts together. It contains only infrastructure code — no attack logic, training logic, or evaluation logic belongs here. The functions in this module are used at the outermost layer of an experiment: loading the YAML configuration, seeding all random number generators for reproducibility, selecting the compute device, and creating a uniquely timestamped output directory tree before any training begins.

Functions

load_config()

Load an experiment configuration from a YAML file using yaml.safe_load(). Accepts either a str or a pathlib.Path as the config path. The file is opened with UTF-8 encoding and parsed with yaml.safe_load(), which prevents arbitrary Python object deserialisation. The returned dictionary maps directly to the structure of the YAML file and is typically passed to create_run_directory() and unpacked into training hyper-parameters.
config_path
str | Path
required
Path to the YAML configuration file. Both absolute and relative paths are accepted.
return
dict
A plain Python dictionary representing the parsed YAML contents.
Example config file (configs/baseline.yaml):
experiment:
  name: baseline
  output_dir: results

model:
  name: distilbert-base-uncased
  num_labels: 2

attack:
  type: badnet
  poison_rate: 0.10
  trigger: cf
  target_label: 1
  seed: 42

training:
  epochs: 3
  learning_rate: 2.0e-5
  batch_size: 32
  weight_decay: 0.01
Example:
from src.utils import load_config

config = load_config("configs/baseline.yaml")
print(config["model"]["name"])
# "distilbert-base-uncased"
print(config["attack"]["poison_rate"])
# 0.1

seed_everything()

Seed all supported random number generators for reproducible experiments. Calls accelerate.utils.set_seed(seed), which internally seeds Python’s built-in random module, NumPy, and PyTorch (both CPU and all CUDA devices). Call this function at the very start of an experiment script, before any dataset shuffling, model initialisation, or training.
seed
int
required
The integer seed value. Use the same seed across all experiment runs you intend to compare.
return
None
Returns None.
seed_everything() uses accelerate.utils.set_seed() rather than setting each RNG manually, which ensures compatibility with distributed training scenarios where per-process seeds need to be offset consistently.
Example:
from src.utils import seed_everything

seed_everything(42)
# Python random, NumPy, and PyTorch are now all deterministically seeded.

get_device()

Select the best available compute device. Returns torch.device("cuda") if CUDA is available, otherwise torch.device("cpu"). Move models and tensors to the returned device to ensure GPU acceleration is used when available.
return
torch.device
torch.device("cuda") if torch.cuda.is_available() is True, otherwise torch.device("cpu").
Example:
from src.utils import get_device
from src.models import build_model

device = get_device()
model = build_model("distilbert-base-uncased", num_labels=2).to(device)
print(device)
# device(type='cuda')  — on a GPU machine
# device(type='cpu')   — on a CPU-only machine

ensure_dir()

Create a directory (and any missing parents) if it does not already exist. Equivalent to mkdir -p. Safe to call multiple times on the same path — if the directory already exists, the call is a no-op. Returns the resolved Path object for convenient chaining.
path
str | Path
required
The directory path to create. Intermediate parent directories are created automatically.
return
Path
The input path coerced to a pathlib.Path object. The directory is guaranteed to exist after this call.
Example:
from src.utils import ensure_dir

log_dir = ensure_dir("results/run_01/logs")
print(log_dir)
# PosixPath('results/run_01/logs')

timestamp()

Return a filesystem-safe timestamp string representing the current local time. Formats the current datetime as "%Y%m%d_%H%M%S" (e.g. "20260719_113522"). Used by create_run_directory() to make each experiment run’s output directory unique. Calling this function twice in the same second returns the same string, so it should only be called once per run at startup.
return
str
A string of the form "YYYYMMDD_HHMMSS".
Example:
from src.utils import timestamp

ts = timestamp()
print(ts)
# "20260719_113522"

create_run_directory()

Create a uniquely timestamped output directory tree for a single experiment run. Constructs the path {output_dir}/{name}/{timestamp}/ from the "experiment" section of the config dictionary, then creates three fixed subdirectories inside it:
  • checkpoints/ — model checkpoint files
  • figures/ — saved plots
  • logs/ — training logs
All directories (including parents) are created with mkdir(parents=True, exist_ok=True). The function returns the run-level directory path so that downstream code can build sub-paths relative to it.
config
dict
required
The parsed experiment configuration dictionary. Must contain a nested "experiment" key with the following fields:
  • config["experiment"]["output_dir"] — root results directory (e.g. "results")
  • config["experiment"]["name"] — experiment name used as a subdirectory (e.g. "baseline")
return
Path
The path to the newly created run directory, e.g. Path("results/baseline/20260719_113522").
Store the returned run_dir in your experiment script and derive all output paths from it. For example: run_dir / "figures" / "backdoor_persistence.png" and run_dir / "checkpoints" / "final".
Example — complete experiment setup:
from src.utils import load_config, seed_everything, get_device, create_run_directory

# 1. Load config
config = load_config("configs/baseline.yaml")

# 2. Set seed for reproducibility
seed_everything(config["attack"]["seed"])

# 3. Select device
device = get_device()

# 4. Create run directory
run_dir = create_run_directory(config)
print(run_dir)
# PosixPath('results/baseline/20260719_113522')

checkpoints_dir = run_dir / "checkpoints"
figures_dir     = run_dir / "figures"
logs_dir        = run_dir / "logs"

# All three subdirectories already exist:
print(checkpoints_dir.exists())  # True
print(figures_dir.exists())      # True
print(logs_dir.exists())         # True
Expected directory structure after create_run_directory():
results/
└── baseline/
    └── 20260719_113522/
        ├── checkpoints/
        ├── figures/
        └── logs/

Build docs developers (and LLMs) love