Skip to content

Commit 1f10f31

Browse files
committed
fp-stability: drop dd line/sym bisection; keep cancellation + move fypp flag onto it
The dd delta-debug stack (bisection + confirmation positive-control + sensitivity ranking) tried to pinpoint and rank the single most precision-sensitive source line, but fypp #:for/#:def expansion collapses many generated computations onto one .fpp line, so that attribution is instance-ambiguous by construction — the fragile part. Removed it (~900 lines). The cancellation pass stays and now carries the fypp instance-ambiguity flag: each cancellation origin is checked with _macro_context and, if its .fpp line sits inside a #:for/#:def, marked 'may represent multiple instances' in console, annotations, and summary. file:line attribution (cancellation origins, ranked by digits lost) is preserved; only the false-precision line-pinpointing is gone. Verified end-to-end: 27 cancellation sites, 23 flagged fypp-ambiguous.
1 parent 37b7a21 commit 1f10f31

6 files changed

Lines changed: 69 additions & 975 deletions

File tree

toolchain/mfc/cli/commands.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -916,8 +916,6 @@
916916
"Analysis passes (skip with --no-* flags):\n"
917917
" float proxy One run with --rounding-mode=float (single-precision sensitivity)\n"
918918
" vprec sweep Runs at mantissa bits [52, 23, 16, 10] (precision floor curve)\n"
919-
" dd_sym/dd_line verrou_dd bisection to responsible functions/lines, then a\n"
920-
" --source positive control confirms + ranks them by sensitivity\n"
921919
" cancellation --check-cancellation origins, ranked by significant digits lost\n"
922920
" mca-sigbits Monte Carlo Arithmetic (mcaquad) significant-bits lower bound\n"
923921
" float-max --check-max-float detection of double→float overflow sites\n"
@@ -972,20 +970,6 @@
972970
default=False,
973971
dest="no_vprec",
974972
),
975-
Argument(
976-
name="no-dd-sym",
977-
help="Skip verrou_dd_sym function-level delta-debug on failure.",
978-
action=ArgAction.STORE_TRUE,
979-
default=False,
980-
dest="no_dd_sym",
981-
),
982-
Argument(
983-
name="no-dd-line",
984-
help="Skip verrou_dd_line source-line delta-debug on failure.",
985-
action=ArgAction.STORE_TRUE,
986-
default=False,
987-
dest="no_dd_line",
988-
),
989973
Argument(
990974
name="no-cancellation",
991975
help="Skip --check-cancellation catastrophic-cancellation detection.",
@@ -1016,7 +1000,7 @@
10161000
"Specify simulation binary explicitly",
10171001
),
10181002
Example("./mfc.sh fp-stability -N 10", "Run 10 random-rounding samples per case"),
1019-
Example("./mfc.sh fp-stability --no-vprec --no-dd-line", "Skip VPREC sweep and line debug"),
1003+
Example("./mfc.sh fp-stability --no-vprec --no-cancellation", "Skip VPREC sweep and cancellation detection"),
10201004
Example("./mfc.sh fp-stability --no-cancellation --no-mca --no-float-max", "Skip new analysis passes"),
10211005
],
10221006
key_options=[
@@ -1026,8 +1010,6 @@
10261010
("-N, --samples N", "Random-rounding samples per case (default: 5)"),
10271011
("--no-float-proxy", "Skip float-rounding proxy run"),
10281012
("--no-vprec", "Skip VPREC mantissa-bit sweep"),
1029-
("--no-dd-sym", "Skip verrou_dd_sym on failure"),
1030-
("--no-dd-line", "Skip verrou_dd_line on failure"),
10311013
("--no-cancellation", "Skip cancellation detection"),
10321014
("--no-mca", "Skip MCA significant-bits estimate"),
10331015
("--no-float-max", "Skip float32 overflow detection"),

toolchain/mfc/fp_stability.py

Lines changed: 19 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,18 @@
1515
One run per mantissa-bit level [52,23,16,10] with
1616
--backend=vprec --vprec-mode=full; shows where each case breaks.
1717
18-
D. verrou_dd_sym on failure (--no-dd-sym to skip)
19-
Delta-debug bisection isolates the minimal set of *functions* causing
20-
instability.
21-
22-
E. verrou_dd_line on failure, after dd_sym (--no-dd-line to skip)
23-
Further bisects to exact *source lines* within the responsible functions.
24-
Each reported line is then *confirmed* by a positive control: --gen-source
25-
captures the symbol-correct executed lines, those are filtered to the suspect
26-
set, and a float-mode run with --source restricted to just them must
27-
reproduce the instability. If perturbing the suspect set does not reproduce
28-
it, the case's hotspots are reported as unconfirmed (downgraded from
29-
::warning:: to ::notice::) — this is a single set-level verdict, not per line.
30-
Each line is then perturbed alone and ranked by the share of the single-
31-
precision deviation it reproduces. NOTE: that share is a *sensitivity*
32-
measure — where reduced precision most moves the output — typically dominated
33-
by the time integrator / final accumulation, NOT by where cancellation
34-
originates. Stage F is the cancellation-origin view; the two usually differ.
35-
Hotspots are cross-referenced against the stage-F cancellation sites and
36-
flagged as instance-ambiguous when the .fpp line sits inside a #:for/#:def
37-
expansion.
38-
39-
F. Cancellation detection (--no-cancellation to skip)
18+
D. Cancellation detection (--no-cancellation to skip)
4019
One run with --check-cancellation=yes; reports MFC source lines that
4120
produce catastrophic cancellation (subtraction of nearly-equal doubles).
42-
Uses --cc-gen-file for structured per-line output.
21+
Uses --cc-gen-file for structured per-line output. A cancellation site whose
22+
.fpp line sits inside a #:for/#:def expansion is flagged as instance-ambiguous
23+
(the line maps to multiple generated instances).
4324
44-
G. MCA significant-bits estimate (--no-mca to skip)
25+
E. MCA significant-bits estimate (--no-mca to skip)
4526
N runs with --backend=mcaquad; max deviation vs nearest-rounding
4627
reference gives a lower bound on significant bits: s = -log2(dev/scale).
4728
48-
H. Float-max overflow detection (--no-float-max to skip)
29+
F. Float-max overflow detection (--no-float-max to skip)
4930
One run with --check-max-float=yes; reports locations where a
5031
double→float conversion would overflow to ±Inf.
5132
@@ -62,7 +43,7 @@
6243
Usage:
6344
./mfc.sh fp-stability # built-in 1-D suite
6445
./mfc.sh fp-stability my_case.py # your own case (small/short, serial, CPU)
65-
./mfc.sh fp-stability --no-vprec --no-dd-line
46+
./mfc.sh fp-stability --no-vprec --no-cancellation
6647
./mfc.sh fp-stability --sim-binary PATH --pre-binary PATH
6748
6849
A user case .py is run as a single serial CPU process under Verrou, so it must be
@@ -84,7 +65,7 @@
8465
MIN_SIG_BITS,
8566
_autodetect_compare,
8667
_cancellation_severity,
87-
_mark_cancellation,
68+
_macro_context,
8869
_max_abs_np,
8970
_max_diff_np,
9071
_sig_bits,
@@ -97,9 +78,6 @@
9778
_find_binary,
9879
_find_verrou,
9980
_run_cancellation_check,
100-
_run_confirmation,
101-
_run_dd_line,
102-
_run_dd_sym,
10381
_run_float_max_check,
10482
_run_float_proxy,
10583
_run_mca_samples,
@@ -391,12 +369,9 @@ def _blank_result(name: str) -> dict:
391369
"sig_bits": None,
392370
"float_proxy": None,
393371
"vprec": [],
394-
"dd_sym_syms": [],
395-
"dd_line_locs": [],
396-
"dd_line_confirmed": None,
397-
"dd_line_confirm_dev": None,
398372
"cancellation_locs": [],
399373
"cancellation_bits": {},
374+
"cancellation_macro": {},
400375
"mca_dev": None,
401376
"mca_sigbits": None,
402377
"float_max_locs": [],
@@ -409,11 +384,8 @@ def _run_case(
409384
sim_bin: str,
410385
pp_bin: str,
411386
n_samples: int,
412-
log_dir: str,
413387
run_float: bool,
414388
run_vprec: bool,
415-
run_dd_sym: bool,
416-
run_dd_line: bool,
417389
run_cancellation: bool,
418390
run_mca: bool,
419391
run_float_max: bool,
@@ -493,62 +465,7 @@ def _run_case(
493465
marker = " [red]FAIL[/red]"
494466
cons.print(f" {bits:2d} bits{label_str}: dev={dev:.3e}{marker}")
495467

496-
# --- D/E: delta-debug with float mode to find FP hotspots.
497-
# dd_run.sh uses --rounding-mode=float (deterministic single-precision),
498-
# so each bisection step is consistent and --nruns=1 suffices. Threshold
499-
# = float_proxy/10: the full instrumented set produces ~float_proxy
500-
# deviation; excluding the responsible function drops it to near zero;
501-
# any subset missing the responsible function gives SAME.
502-
# Skip when float_proxy is unavailable or too small to localize.
503-
float_proxy = result.get("float_proxy")
504-
_DD_FLOAT_MIN = 1e-6
505-
dd_threshold = float_proxy / 10.0 if float_proxy and float_proxy >= _DD_FLOAT_MIN else 0.0
506-
if dd_threshold > 0 and (run_dd_sym or run_dd_line):
507-
cons.print(f" [dim]dd threshold: {dd_threshold:.1e} (float_proxy={float_proxy:.1e})[/dim]")
508-
elif run_dd_sym or run_dd_line:
509-
cons.print(f" [dim]skipping dd: float_proxy={float_proxy} < {_DD_FLOAT_MIN:.0e}[/dim]")
510-
if dd_threshold > 0 and run_dd_sym:
511-
try:
512-
result["dd_sym_syms"] = _run_dd_sym(case, verrou_bin, sim_bin, work_dir, log_dir, threshold=dd_threshold)
513-
except Exception as exc:
514-
cons.print(f" [bold yellow]dd_sym error[/bold yellow]: {exc}")
515-
if dd_threshold > 0 and run_dd_line:
516-
try:
517-
result["dd_line_locs"] = _run_dd_line(
518-
case,
519-
verrou_bin,
520-
sim_bin,
521-
work_dir,
522-
log_dir,
523-
threshold=dd_threshold,
524-
)
525-
macro_n = sum(1 for loc in result["dd_line_locs"] if loc["macro"])
526-
if macro_n:
527-
cons.print(f" [dim]dd_line: {macro_n} hotspot(s) inside fypp-expanded code (instance-ambiguous)[/dim]")
528-
except Exception as exc:
529-
cons.print(f" [bold yellow]dd_line error[/bold yellow]: {exc}")
530-
531-
# --- E2: confirm dd_line hotspots and rank each by its individual share ---
532-
if dd_threshold > 0 and run_dd_line and result["dd_line_locs"]:
533-
cons.print(" [dim]confirming + ranking dd_line hotspots (per-line perturbation)...[/dim]")
534-
try:
535-
confirmed, cdev, ranked = _run_confirmation(case, verrou_bin, sim_bin, work_dir, ref_dir, result["dd_line_locs"], dd_threshold, float_proxy)
536-
result["dd_line_locs"] = ranked
537-
result["dd_line_confirmed"] = confirmed
538-
result["dd_line_confirm_dev"] = cdev
539-
if confirmed is True:
540-
cons.print(f" [bold green]dd_line confirmed[/bold green]: suspect-only dev={cdev:.3e} >= {dd_threshold:.1e}")
541-
elif confirmed is False:
542-
cons.print(f" [bold yellow]dd_line UNCONFIRMED[/bold yellow]: suspect-only dev={cdev:.3e} < {dd_threshold:.1e} (attribution suspect)")
543-
top = ranked[0] if ranked else None
544-
if top and top.get("share") is not None:
545-
cons.print(f" highest single-precision sensitivity: {top['path']}:{top['start']} ({top['share'] * 100:.0f}% of float-proxy)")
546-
cons.print(" [dim](sensitivity = where reduced precision most moves the output, often the time")
547-
cons.print(" [dim] integrator; not necessarily where cancellation originates — see cancellation sites)[/dim]")
548-
except Exception as exc:
549-
cons.print(f" [bold yellow]dd_line confirmation error[/bold yellow]: {exc}")
550-
551-
# --- F: cancellation detection ---
468+
# --- D: cancellation detection ---
552469
if run_cancellation:
553470
cons.print(" [dim]cancellation detection...[/dim]")
554471
try:
@@ -562,21 +479,22 @@ def _run_case(
562479
bits = _cancellation_severity([(lvl, s) for lvl, s in level_sites if s is not None])
563480
result["cancellation_locs"] = locs
564481
result["cancellation_bits"] = bits
482+
# flag cancellation sites whose .fpp line is inside a #:for/#:def
483+
# expansion: the line maps to multiple generated instances, so the
484+
# report cannot pin it to a unique runtime instance.
485+
result["cancellation_macro"] = {(path, line): macro for (path, line) in locs if (macro := _macro_context(path, line))}
565486
if locs:
566487
worst = max(bits.values()) if bits else 0
567488
cons.print(f" cancellation: {len(locs)} site(s), worst loses ≥ {worst / math.log2(10):.0f} of ~16 digits")
489+
n_macro = len(result["cancellation_macro"])
490+
if n_macro:
491+
cons.print(f" [dim]{n_macro} inside fypp expansions — line maps to multiple instances[/dim]")
568492
else:
569493
cons.print(" cancellation: none detected")
570-
# cross-reference: label dd_line hotspots that sit on a cancellation site
571-
if result["dd_line_locs"] and locs:
572-
_mark_cancellation(result["dd_line_locs"], locs)
573-
n_xref = sum(1 for loc in result["dd_line_locs"] if loc.get("cancellation"))
574-
if n_xref:
575-
cons.print(f" {n_xref} hotspot(s) coincide with a catastrophic-cancellation site")
576494
except Exception as exc:
577495
cons.print(f" [bold yellow]cancellation check error[/bold yellow]: {exc}")
578496

579-
# --- G: MCA significant-bits estimate ---
497+
# --- E: MCA significant-bits estimate ---
580498
if run_mca:
581499
cons.print(f" [dim]MCA significant-bits estimate (N={n_samples})...[/dim]")
582500
try:
@@ -591,7 +509,7 @@ def _run_case(
591509
except Exception as exc:
592510
cons.print(f" [bold yellow]MCA error[/bold yellow]: {exc}")
593511

594-
# --- H: float-max overflow detection ---
512+
# --- F: float-max overflow detection ---
595513
if run_float_max:
596514
cons.print(" [dim]float-max overflow check...[/dim]")
597515
try:
@@ -691,8 +609,6 @@ def fp_stability():
691609
n_samples = ARG("samples")
692610
run_float = not ARG("no_float_proxy")
693611
run_vprec = not ARG("no_vprec")
694-
run_dd_sym = not ARG("no_dd_sym")
695-
run_dd_line = not ARG("no_dd_line")
696612
run_cancellation = not ARG("no_cancellation")
697613
run_mca = not ARG("no_mca")
698614
run_float_max = not ARG("no_float_max")
@@ -715,10 +631,6 @@ def fp_stability():
715631
features.append("float-proxy")
716632
if run_vprec:
717633
features.append("vprec-sweep")
718-
if run_dd_sym:
719-
features.append("dd_sym")
720-
if run_dd_line:
721-
features.append("dd_line")
722634
if run_cancellation:
723635
features.append("cancellation")
724636
if run_mca:
@@ -739,11 +651,8 @@ def fp_stability():
739651
sim_bin,
740652
pp_bin,
741653
n_samples,
742-
log_dir,
743654
run_float,
744655
run_vprec,
745-
run_dd_sym,
746-
run_dd_line,
747656
run_cancellation,
748657
run_mca,
749658
run_float_max,
@@ -762,9 +671,6 @@ def fp_stability():
762671
mark = "[green]✓[/green]" if r["passed"] else "[red]✗[/red]"
763672
cons.print(f" {mark} {r['name']}")
764673

765-
if n_fail > 0:
766-
cons.print(f"\n dd_sym/dd_line logs in: {log_dir}")
767-
768674
_emit_github_summary(results, n_samples)
769675
_emit_github_annotations(results)
770676

0 commit comments

Comments
 (0)