Re-check FTZ/DAZ before every optimizer step (2026-10-20)
Browse filespin_fp_mode ran once at start; a library imported mid-run can switch flush-to-zero /
denormals-are-zero back on. fit now checks before each opt.step, re-pins, and names the
rank and step. Verified: final parameter hashes unchanged vs 63780b7 on cuda+cpu and cpu+cpu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- daisychain/cluster.py +24 -0
- test_cluster_reductions.py +10 -0
daisychain/cluster.py
CHANGED
|
@@ -42,6 +42,29 @@ def pin_fp_mode() -> None:
|
|
| 42 |
torch.set_flush_denormal(False)
|
| 43 |
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
def _cuda_usable() -> bool:
|
| 46 |
"""is_available() alone is not enough: with CUDA_VISIBLE_DEVICES set but EMPTY (a common way
|
| 47 |
to say "CPU only") torch reports available=True and device_count()=0, and the node crashed
|
|
@@ -314,6 +337,7 @@ class DaisyCluster:
|
|
| 314 |
loss.backward()
|
| 315 |
capacity_weighted_allreduce_grads(model, w)
|
| 316 |
check_finite_grads(model, s)
|
|
|
|
| 317 |
if master is not None:
|
| 318 |
with torch.no_grad():
|
| 319 |
for mp, p in zip(master, params):
|
|
|
|
| 42 |
torch.set_flush_denormal(False)
|
| 43 |
|
| 44 |
|
| 45 |
+
def fp_mode_ok() -> bool:
|
| 46 |
+
"""True if this thread keeps subnormals: FTZ off (a normal*normal product may be subnormal)
|
| 47 |
+
and DAZ off (a subnormal input is read as itself)."""
|
| 48 |
+
ftz = torch.tensor([1e-20], dtype=torch.float32) * torch.tensor([1e-20], dtype=torch.float32)
|
| 49 |
+
daz = torch.tensor([1e-39], dtype=torch.float32) * 1.0
|
| 50 |
+
return float(ftz) != 0.0 and float(daz) != 0.0
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def repin_fp_mode(step: int, rank: int) -> bool:
|
| 54 |
+
"""Re-check the FP mode before an optimizer step; re-pin and say so if it was flipped.
|
| 55 |
+
|
| 56 |
+
pin_fp_mode runs once at start, but a library imported mid-run (a dataloader worker's
|
| 57 |
+
dependency, a fast-math extension) can switch FTZ/DAZ on afterwards. The emulator restores
|
| 58 |
+
MXCSR around EVERY guest call for this reason; here the replica hash would catch the fork
|
| 59 |
+
later with nothing naming the cause. Returns True if the mode had been flipped."""
|
| 60 |
+
if fp_mode_ok():
|
| 61 |
+
return False
|
| 62 |
+
pin_fp_mode()
|
| 63 |
+
print(" [fp] rank %d step %d: flush-to-zero/denormals-are-zero was switched ON since the "
|
| 64 |
+
"last step (a library changed MXCSR); re-pinned subnormals ON" % (rank, step), flush=True)
|
| 65 |
+
return True
|
| 66 |
+
|
| 67 |
+
|
| 68 |
def _cuda_usable() -> bool:
|
| 69 |
"""is_available() alone is not enough: with CUDA_VISIBLE_DEVICES set but EMPTY (a common way
|
| 70 |
to say "CPU only") torch reports available=True and device_count()=0, and the node crashed
|
|
|
|
| 337 |
loss.backward()
|
| 338 |
capacity_weighted_allreduce_grads(model, w)
|
| 339 |
check_finite_grads(model, s)
|
| 340 |
+
repin_fp_mode(s, self.rank) # before the step: the update is where FTZ forks
|
| 341 |
if master is not None:
|
| 342 |
with torch.no_grad():
|
| 343 |
for mp, p in zip(master, params):
|
test_cluster_reductions.py
CHANGED
|
@@ -112,6 +112,16 @@ def main():
|
|
| 112 |
caps, unit = plan_capacities([{"cores": 8, "capacity": 8.0}, {"cores": 8, "capacity": 150.0}])
|
| 113 |
ck("old-format surveys (no unit) are classified, and mixed -> cores", caps == [8.0, 8.0], caps)
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
print()
|
| 116 |
if FAILURES:
|
| 117 |
print("FAILED: %d" % len(FAILURES))
|
|
|
|
| 112 |
caps, unit = plan_capacities([{"cores": 8, "capacity": 8.0}, {"cores": 8, "capacity": 150.0}])
|
| 113 |
ck("old-format surveys (no unit) are classified, and mixed -> cores", caps == [8.0, 8.0], caps)
|
| 114 |
|
| 115 |
+
print("repin_fp_mode -- FTZ switched on mid-run is noticed and undone (2026-10-20)")
|
| 116 |
+
from daisychain.cluster import fp_mode_ok, pin_fp_mode, repin_fp_mode
|
| 117 |
+
pin_fp_mode()
|
| 118 |
+
ck("subnormals kept after pin_fp_mode", fp_mode_ok())
|
| 119 |
+
ck("no flip: repin reports nothing", repin_fp_mode(0, 0) is False)
|
| 120 |
+
torch.set_flush_denormal(True) # what a fast-math library does on load
|
| 121 |
+
ck("fp_mode_ok sees the flip", not fp_mode_ok())
|
| 122 |
+
ck("repin reports the flip", repin_fp_mode(7, 0) is True)
|
| 123 |
+
ck("...and subnormals are back", fp_mode_ok())
|
| 124 |
+
|
| 125 |
print()
|
| 126 |
if FAILURES:
|
| 127 |
print("FAILED: %d" % len(FAILURES))
|