Skip to content

Make pytest random seeds repeatable per test - #1601

Open
jmitrevs wants to merge 3 commits into
mainfrom
repeatable-seeds
Open

jmitrevs wants to merge 3 commits into
mainfrom
repeatable-seeds

Conversation

@jmitrevs

@jmitrevs jmitrevs commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

Makes the random state in the pytests repeatable per test, so that adding or removing a test no longer
changes the random weights other tests see. This was making synthesis resource numbers move by more than
10% for unrelated changes, and loosening the tolerance would hide real changes.

Two problems, one line each:

  1. CI turned off per-test reseeding. test/pytest/ci-template.yml passed --randomly-dont-reset-seed.
    With it, pytest-randomly seeds once at session start and the RNG stream runs on through every test in the
    job. CI jobs group several test files, so any test earlier in a job changed the state of all later ones.
    Without the flag, pytest-randomly reseeds random, numpy.random and all registered seeders before each
    test with seed + crc32(nodeid), which depends only on the session seed (42) and the test's own id.

  2. The TF/PyTorch seeder was never registered. Since the move to pyproject.toml (Lazy converter imports and migrate to pyproject.toml #1094) the entry
    point was written as entry-points.pytest_randomly.random_seeder = "hls4ml:reseed". TOML splits unquoted
    dotted keys, so this registered group pytest_randomly, name random_seeder, while pytest-randomly looks
    in group pytest_randomly.random_seeder. hls4ml.reseed was therefore never called and
    tensorflow.random.set_seed / torch.manual_seed never ran. Quoting the group restores what the original
    setup.cfg declared:

    entry-points."pytest_randomly.random_seeder".hls4ml = "hls4ml:reseed"

Expect synthesis resource baselines to shift once, since every test now gets a different seed than before;
they should be stable afterwards. Module- or session-scoped fixtures that build random models still take the
seed of the first test that uses them, so they can change if a test is added in front of them or -k is used.

Existing editable installs keep the old, broken entry point in their metadata until reinstalled
(pip install -e . --no-deps). CI does a fresh install and is not affected.

Type of change

  • Bug fix (non-breaking)
  • New feature (non-breaking)
  • Breaking change (existing configs, APIs or generated code behave differently)
  • New frontend / backend
  • New layer / operator support
  • New configuration option — a new io_type, Strategy, or config attribute
  • Research paper implementation
  • Documentation
  • Build, CI or tooling
  • Refactor / cleanup (no functional change)

Affected areas

Backends: n/a (test infrastructure only)

  • Backend-independent (core IR, optimizer, hls4ml.model)

Frontends:

  • Not related to a frontend

Components:

  • IR (layers + model graph) / optimizer passes
  • C++ templates / HLS sources under hls4ml/templates/
  • Profiling, reporting or the CLI
  • Packaging, build or CI

Configurations affected and exercised

Backend io_type Strategy Verified
n/a (test seeding) n/a n/a pytest (see Tests)

Impact on generated HLS

  • This PR does not change the generated HLS for existing models.
  • This PR changes the generated HLS. Numbers below.

Numerical behavior: no change to hls4ml itself. Tests that relied on unseeded random weights will see
different (but now repeatable) weights.

No synthesis was run for this PR.

Tests

Added test_reseed.py to the the reseeding, since it had been broken and unnoticed for a while.

Verified with throwaway tests (not committed) that hash newly initialized weights and numpy draws. Each was
run twice on its own and once after another random-consuming test, with
--randomly-seed=42 --randomly-dont-reorganize:

Environment Checked With this PR With --randomly-dont-reset-seed
Keras 2.14 / TF 2.14 tf.keras Dense, torch Linear, numpy identical in all runs differs
Keras 3.13 (TF and torch backends) Keras Dense, torch Linear, numpy identical in all runs differs
PyTorch 2.10 (no TF) torch Linear, numpy identical in all runs differs

Before the entry-point fix, torch weights differed even between two identical runs.

Existing tests still pass with the new flags:

  • pytest test_keras_api.py -k "Vivado and io_parallel" --randomly-seed=42 --randomly-dont-reorganize
    (Keras 2.14): 14 passed
  • pytest test_pytorch_api.py -k "Vivado and io_parallel" --randomly-seed=42 --randomly-dont-reorganize
    (PyTorch 2.10): 29 passed

Test configuration (OS, Python, ML framework version, HLS tool version): macOS (Darwin 25.6), Python 3.11
(Keras 2 env) and 3.12 (Keras 3 and PyTorch envs), TF 2.14 / Keras 2.14, Keras 3.13, PyTorch 2.9.1 and 2.10,
pytest-randomly 4.0.1 and 4.1.0. No HLS tools used.

AI assistance disclosure

  • None — no AI tool was used.
  • Assisted — completion, refactoring, docstrings, tests; design and code are mine.
  • Substantial — significant AI-generated portions, reviewed and edited by me.
  • Agentic — produced largely end-to-end by an AI agent from my prompts.

Tool(s) and model(s): Claude Code (Claude Opus 5.5)

Where it was used: diagnosis, both one-line changes (pyproject.toml, test/pytest/ci-template.yml), the
verification runs and this description.

If anything other than None is ticked, confirm all of the following:

  • I am the author of this contribution and take full responsibility for
    it.
    I have read and understood every line and can explain and defend it
    in review.
  • I verified the generated code against real hls4ml and HLS semantics —
    no invented APIs, config keys, pragmas or citations.
  • All numbers, logs and test results quoted in this PR come from runs I
    actually performed, not from agent output.
  • I have the right to submit this work under the project's licence, and to
    my knowledge it does not reproduce third-party code that would conflict
    with it.
  • No AI tool is credited as an author in any commit in this branch.

Checklist

Required:

  • I have read the contributing guidelines.
  • I installed and ran pre-commit on the files I edited.
  • I added tests under test/pytest covering this change (a bug fix should
    add a test that fails on main and passes here).
  • I self-reviewed the full diff and it contains no leftover debug code,
    commented-out blocks or unrelated changes.
  • The AI assistance disclosure above is complete and accurate.

If applicable:

  • Documentation under docs/ updated.
  • No new build or synthesis warnings.
  • Public API / config schema changes are documented.

Release note

Fixed pytest seeding so that each test gets a repeatable random seed, including for TensorFlow and PyTorch.

🤖 Generated with Claude Code

Remove --randomly-dont-reset-seed from the CI pytest invocation so that
pytest-randomly reseeds before each test from the session seed and the
test's node id. Previously the RNG state carried over between tests in a
CI job, so adding or removing a test changed the random weights of the
tests after it.

Fix the pytest_randomly.random_seeder entry point in pyproject.toml. The
unquoted dotted key registered the group as "pytest_randomly", so
hls4ml.reseed was never called and TensorFlow and PyTorch were not
seeded.
Comment thread pyproject.toml
urls.Homepage = "https://fastmachinelearning.org/hls4ml"
scripts.hls4ml = "hls4ml.cli:main"
entry-points.pytest_randomly.random_seeder = "hls4ml:reseed"
entry-points."pytest_randomly.random_seeder".hls4ml = "hls4ml:reseed"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the entry point fixed, hls4ml.reseed runs for the first time: once in the report header and again at setup, call and teardown of every test. That brings up two things in hls4ml/__init__.py:

  • It imports frameworks unconditionally and only catches ImportError. If import torch raises OSError (for example missing CUDA libraries), or import tensorflow fails with something else (a protobuf or numpy ABI mismatch), the whole pytest session errors out at the header. That includes jobs that never use either framework.
  • Every job now loads both TensorFlow and torch. The KERAS_BACKEND=torch/jax jobs (e.g. test_pquant_pytorch) import TF, and the TF-only jobs import torch. That costs startup time and memory, and it puts both frameworks in one process where they weren't before.

Both go away if reseed only seeds frameworks that are already imported, since there's nothing to seed in a framework nobody has loaded yet:

import sys

def reseed(newseed):
    if 'tensorflow' in sys.modules:
        sys.modules['tensorflow'].random.set_seed(newseed)
    if 'torch' in sys.modules:
        sys.modules['torch'].manual_seed(newseed)

One caveat: a framework imported for the first time inside a test (rather than at module level) would skip that test's setup reseed, but it would still be seeded from the next reseed point on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread pyproject.toml
urls.Homepage = "https://fastmachinelearning.org/hls4ml"
scripts.hls4ml = "hls4ml.cli:main"
entry-points.pytest_randomly.random_seeder = "hls4ml:reseed"
entry-points."pytest_randomly.random_seeder".hls4ml = "hls4ml:reseed"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a small test guard this registration? It has been silently broken since #1094, and a future edit or formatter rewrite of this key could break it again without any test failing. Something like:

from importlib.metadata import entry_points

def test_reseed_entry_point_registered():
    eps = entry_points(group='pytest_randomly.random_seeder')
    assert any(ep.value == 'hls4ml:reseed' for ep in eps)

Because this reads the installed metadata, it will also fail in an editable install made before this change, which is a useful hint to reinstall.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

hls4ml.reseed now runs at every pytest-randomly reseed point. Importing
TensorFlow and PyTorch from it loaded both frameworks into every test
job, and any import failure other than ImportError aborted the session.
Seed only the frameworks already in sys.modules instead.

Add a test that the pytest_randomly.random_seeder entry point is
registered, since its breakage in #1094 went unnoticed.
@jmitrevs

jmitrevs commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

PR #1604 should fix the sphinx error.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants