Skip to content

refactor(back_end): retire 13 uncollected self-tests behind two CI gates - #113

Merged
yuleisui merged 11 commits into
SVF-tools:mainfrom
guanqin-123:TensorCE
Aug 31, 2026
Merged

refactor(back_end): retire 13 uncollected self-tests behind two CI gates#113
yuleisui merged 11 commits into
SVF-tools:mainfrom
guanqin-123:TensorCE

Conversation

@guanqin-123

Copy link
Copy Markdown
Contributor

Deleted self contained functional test cases.

codecov project target 74% -> 75%:

bab.py carried 14 self-tests behind a hand-rolled `__main__` runner and
onnx_converter.py one stray `test_onnx_conversion`. Per-test coverage
attribution (each test run alone, import-only baseline subtracted, then
differenced against a 27-case e2e union built by expanding every loop
axis in act-bab.yml) showed which of them still protect anything.

Deleted -- zero residual coverage, no assertion the e2e union lacks:
  test_imports, test_random_branching, test_random_bounding

Deleted -- assertion is vacuous or unreachable from production:
  _test_bab_kbatch_status_parity: k=1/2/4/8 all end at nodes=32,
    exhausted_budget_nodes=True. The statuses agree because the budget
    ran out, not because K-batching preserves results.
  test_split_subproblems: the fixture leaves incremental_alpha, eta,
    split_signs and parent_margins as None, so `_clone_dict_tensors`
    only ever takes its `is not None` short-circuit. Degrading the clone
    to an alias would not fail this test.
  test_config_yaml_roundtrip: BaBConfig.from_yaml/to_yaml have no
    production caller -- BaB config is built via BaBConfig(**merged) at
    config.py:606/790/861. The test was keeping dead code alive.
  test_random_branching_with_mask: nothing in production passes
    unstable_mask to compute_scores (bab.py:1952 passes bounds_dict and
    nu_per_layer, :1964 passes none; branching.py:91 is a docstring), so
    RandomBranching's neuron-split branch is unreachable.
  test_subproblem_batch, test_babnode_compat: accessor smoke tests.

Migrated to CI, then deleted:
  _test_bab_budget_exhaustion_returns_unknown -> "BaB soundness --
    budget exhaustion returns UNKNOWN". Same two assertions against a
    real torchlp solver on mlp_plain_3x8, which unlike bab_deep survives
    presolve and enters BaB. Every other step in this workflow asserts
    throughput; this is the first to assert the verifier does not claim
    more than it proved.
  _test_bab_oom_fails_loud -> an inline AST check in act-bab.yml. A
    real OOM cannot be raised deterministically in CI, so the invariant
    is now checked structurally: no try/except may wrap
    setup_and_solve_batch or solve_batch. This covers every call site rather than the single
    path the fixture reached, and guards against the
    `except (ValueError, RuntimeError, ...): return None` idiom already
    present at bab.py:289 spreading to the solve path. An ast-grep rule
    was rejected first -- its `$$$` form missed the case where the
    assignment is the last statement in the try block.

Not migrated: _test_bab_k_fluctuates asserts a performance property,
not a soundness one, and K-batch width is not exposed in metadata.
`_k_log` itself is kept: it is documented diagnostic API.

Kept: _test_check_violations_batched_per_kind and its scalar-params
sibling. Adding --bab to the range/margin/unsafe netfactory run absorbs
only 7 of their 33 residual lines; the other 26 are broadcast shapes
((1,width), (n_batch,width), batched d) that real nets never emit. Both
recompute the expected mask independently via argmax/einsum and compare
with torch.equal, so a broadcasting bug surfaces as a failed assert
rather than a silently wrong CERTIFIED.

codecov project target 74% -> 75%: deleting test_onnx_conversion drops
41 uncovered statements; the `__main__` blocks and pragma'd self-tests
were already outside the denominator, so nothing else moves.

Pure deletion in both modules (0 insertions), plus five test-only
imports (os, tempfile, BabNode, split_subproblems, RandomBranching)
dropped alongside their last callers. bab.py self-tests: 2 passed.
The inline gate was exercised by parsing the workflow and running the
extracted shell: green on bab.py and verifier.py, red on a positive
control that includes the case where the guarded call is the last
statement in the try block. lsp clean; act.back_end.bab exports unchanged.
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.49780% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.61%. Comparing base (f1bc0f1) to head (1becb64).

Files with missing lines Patch % Lines
act/pipeline/verification/act2torch.py 72.30% 36 Missing ⚠️
act/back_end/interval_tf/tf_attention.py 0.00% 2 Missing ⚠️
act/pipeline/verification/per_neuron_bounds.py 84.61% 2 Missing ⚠️
act/pipeline/verification/validate_verifier.py 66.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #113      +/-   ##
==========================================
- Coverage   74.97%   74.61%   -0.36%     
==========================================
  Files          91       91              
  Lines       22230    22323      +93     
==========================================
- Hits        16666    16657       -9     
- Misses       5564     5666     +102     
Flag Coverage Δ
bab 43.96% <47.57%> (-0.23%) ⬇️
backend-float32 49.95% <81.05%> (+2.44%) ⬆️
backend-float64 50.44% <81.05%> (+1.31%) ⬆️
frontend 31.81% <75.77%> (+0.50%) ⬆️
pipeline-fuzz 20.51% <14.09%> (+0.05%) ⬆️
pipeline-verify 41.76% <25.11%> (+0.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
act/back_end/bab/bab.py 70.64% <ø> (-3.01%) ⬇️
act/back_end/cons_exportor.py 56.79% <ø> (+23.84%) ⬆️
act/back_end/net_factory.py 87.82% <100.00%> (+1.32%) ⬆️
act/back_end/verifier.py 76.65% <ø> (-7.48%) ⬇️
act/front_end/vnnlib_loader/onnx_converter.py 45.55% <ø> (+1.73%) ⬆️
act/back_end/interval_tf/tf_attention.py 63.55% <0.00%> (-22.75%) ⬇️
act/pipeline/verification/per_neuron_bounds.py 79.83% <84.61%> (+1.68%) ⬆️
act/pipeline/verification/validate_verifier.py 82.12% <66.66%> (-0.30%) ⬇️
act/pipeline/verification/act2torch.py 72.33% <72.30%> (-1.75%) ⬇️

... and 14 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f1bc0f1...1becb64. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Two embedded self-test batteries are removed from production modules.
cons_exportor's 18 are replaced by a torchlp sweep over the 59
layer_testing nets. verifier.py's 16 are dropped outright; that file was
63% test scaffolding (1363 of 2153 lines) and is now production code
only. Neither dtype job runs a self-test step any more.

Retiring them exposed that the gate meant to cover those paths was not
actually a gate. per_neuron_bounds' check_hookable_alignment() treated
hookable_layers == 0 as "aligned" rather than "nothing to verify", so a
net whose kinds were all absent from _ACT_KIND_TO_MODULE ran with no
hooks, checked no neurons, and printed "All 0 checks passed". 120 of 222
validation runs were green ticks over zero work.

Zero-check runs are now SKIPPED and excluded from the pass tally, and
the smooth activations plus the 1:1 transformer ops are mapped and
emitted as real modules so their bounds are actually checked. Pre-
versus post-activation is decided from the ACT layer kind rather than
the traced module name, matching DualSolver's compute_forward_bounds(
post_activation=False) contract: nonlinear relaxations store the
incoming box, affine, shape, pooling and bilinear handlers store the
outgoing one. Module names are a tracing detail and never defined which
tensor a bound represented; deriving it from them compared cos(x)
against the box for x and produced 87 false violations in float32.

Verified by shrinking tf_sin's interval 10% toward its midpoint: the
gate previously exited 0 with 0 violations, and now reports the SIN
neuron violation and exits 1. Both dtypes report 0 violations across
132 checked runs with 0 zero-check runs.

The solve-path exception gate is also simplified to a comprehension
over Try nodes, verified to still flag both guarded call sites.
@yuleisui

Copy link
Copy Markdown
Collaborator

Need to fix CI coverage

Measured +164 repo-wide executed lines in 3 seconds over the 9,085-line combined baseline of the existing pipeline-verify commands.
Conflict was in act-pipeline-verify.yml: upstream folded a hybridz combo
into the vit_2023 loop, which supersedes the standalone hybridz ViT step
added here. Upstream's form is kept because it also runs
--validate-soundness; the standalone step is dropped as a duplicate run.
No generated network contained ATT_SCORES or ATT_MIX, and dual_tf's
forward_attention/backward_attention handle only those two kinds, so the
backend jobs' existing dual sweep could never reach them. MATMUL already
had cover via layer_testing_matmul; the attention cores had none.

layer_testing_attention lowers DENSE Q/K/V into ATT_SCORES -> CONCAT ->
SOFTMAX -> ATT_MIX -> CONCAT -> LAYERNORM -> GELU. Q/K/V come from DENSE
rather than an MHA_SPLIT decomposition because the dual path stubs
MHA split/join and consumes the bilinear cores directly.

dual_tf/tf_transformer.py goes from 72.07% to 90.4% (36 missing, was
69). Both dtypes report 0 violations and the tf_sin unsoundness probe is
still caught. Picked up by the existing sweep with no workflow change.
Commit the generated attention network and manifest entry omitted from b3d3d9d. Dual-transformer coverage moved from 271/376 to 340/376 executable lines, a +69-line delta, and both dtypes complete with zero soundness violations.
@guanqin-123

Copy link
Copy Markdown
Contributor Author

Need to fix CI coverage

To date, we deleted self-contained functional test cases, (at least) change to netfactory-composed ACT net to test.

It was named a soundness gate but only ever flagged one syntactic shape:
try/except wrapped directly around setup_and_solve_batch or solve_batch.
An exception swallowed inside solve_batch's own body, or in a caller of
verify_once, is equally fatal and invisible to it. It never fired, and
the shape it guards is one review catches. Twenty-five lines of inline
YAML Python for that is not worth carrying.
_dot_product_degenerate is documented as the closed-form box product for
when there is no perturbation dimension, but both callers guarded it with
dim_in == 1 rather than dim_in == 0. Since dim_in is
embed_dim * perturbed_words, dim_in == 1 is one genuinely perturbed
coordinate, and the helper zeroes lw/uw and multiplies only the bias
terms, discarding the lw . x^r contribution entirely.

Reproduction: for z1 = 2x and z2 = 3x over x in [-1, 1], dot_product
returned [0, 0] while the true range of 6x^2 is [0, 6] -- the abstract
bound excluded most of the reachable set. Routing dim_in == 1 through
_dot_product_planes yields [-18, 6], looser but sound.

Not reachable from any current end-to-end path, since attn_mode is never
set in production, but it would have become a false-CERTIFIED source the
moment the dual-planar route was wired up.
c56b7e4 committed layer_testing_attention.json and the manifest into
act/back_end/examples/nets, which .gitignore excludes because every net
there is a build product of `act.back_end --generate`. The definition
already lives in act/config/gen_act_net.yaml.

Tracking a generated file also disables .gitignore for it, so a later
DSL edit would silently leave the committed copy stale while CI ran the
regenerated one.

Verified by wiping act/back_end/examples/nets entirely: --generate
rebuilds all 75 nets including the attention one, byte-identical, and
the dual sweep still picks it up with no --networks flag (0 violations).
32e8f0d replaced cons_exportor's self-tests with a torchlp sweep over
the layer_testing nets but only wired it into the float64 job, and the
verifier self-test step was later removed from both. float32 was left
with neither, so nothing there exercised the LP export path.

Measured locally at float32: 60 nets, 0 failures, 246s, comparable to
the float64 sweep.
Comment thread .github/workflows/act-bab.yml Outdated
Comment on lines +234 to +256
# ===================================================================
# Soundness gate: a BaB run that exhausts its node budget with unproven
# sub-boxes left in the pool MUST report UNKNOWN, never CERTIFIED. Every
# other step here asserts throughput (node counts, exit codes); this is
# the only one asserting the verifier does not claim more than it proved.
#
# layer_testing_bab_deep is certified by presolve and never branches, so
# it cannot exhaust anything -- mlp_plain_3x8 is the net that survives
# presolve and enters BaB. --verbose is load-bearing: backend_cli only
# prints result.metadata under it.
# ===================================================================
- name: BaB soundness — budget exhaustion returns UNKNOWN
run: |
cd ${{ github.workspace }}
out=$(coverage run -p -m act.back_end --verify \
--network "$ACT_NETS_DIR/mlp_plain_3x8_64x64_3962224133.json" \
--bab --bab-max-depth 10 --bab-max-subproblems 2 --bab-max-batch-size 1 \
--solver torchlp --device cpu --dtype float64 --verbose 2>&1)
echo "$out"
grep -q "Lane 0: VerifyStatus.UNKNOWN" <<<"$out"
grep -q "reason: budget_exhausted_with_unproven_subboxes" <<<"$out"
grep -q "exhausted_budget_nodes: True" <<<"$out"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this looks to be auto generated for debugging.

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.

yes. Now deleted.

Comment on lines -64 to -67
run: |
cd ${{ github.workspace }}
coverage run -p -m act.back_end.verifier

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

be consistent with float64.yml

@@ -0,0 +1,85 @@
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we remove this file as we have other json files as nets.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you merge this into gen_act_net.yaml where necessary.

@@ -0,0 +1,815 @@
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to be auto generated rather than manually added.

Refer to gen_act_net.yaml

bab.py is production code only now, matching verifier.py. The two
check_violations_batched tests, the _TESTS list and the module entry
point are gone, so the workflow step that ran the module goes with them,
as does the budget-exhaustion gate.

What this gives up, measured rather than assumed: stubbing
check_violations_batched to always report "no violation" degrades a
falsifiable net from FALSIFIED to UNKNOWN, and no remaining check
notices -- the netfactory counterexample validation passes because it
exercises verify_once, not BaB's counterexample path. Every net CI
currently runs through BaB already returns UNKNOWN, so the
violation-found branch has no end-to-end cover. Restoring it needs a
generated net that BaB actually falsifies, plus a step asserting
FALSIFIED.
@yuleisui
yuleisui merged commit 5772a00 into SVF-tools:main Aug 31, 2026
9 checks passed
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