Skip to content

Commit d007f45

Browse files
committed
DEBUG: isolate Windows free-threaded special-value failures
Temporary branch: build only cp313 (GIL) + cp313t (free-threaded) on Windows and run ci_debug/ft_repro.py under each to diff [GIL] vs [FT] output. Not for merge.
1 parent 8b305c9 commit d007f45

2 files changed

Lines changed: 151 additions & 0 deletions

File tree

.github/workflows/debug_win_ft.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# TEMPORARY debug workflow (branch debug/win-ft-isolation).
2+
#
3+
# Isolates the Windows free-threaded (cp313t) special-value failures seen on
4+
# PR #108's CI: build ONLY cp313 (GIL) and cp313t (free-threaded) on Windows and
5+
# run ci_debug/ft_repro.py under each, so we can diff the [GIL] vs [FT] output
6+
# and see exactly which quad operation diverges. Not for merge.
7+
name: Debug Windows FT
8+
9+
on:
10+
push:
11+
branches:
12+
- "debug/win-ft-isolation"
13+
workflow_dispatch:
14+
15+
jobs:
16+
debug_windows_ft:
17+
name: Debug Windows FT (cp313 vs cp313t)
18+
runs-on: windows-latest
19+
strategy:
20+
matrix:
21+
architecture: [x64]
22+
23+
steps:
24+
- uses: actions/checkout@v6
25+
26+
- name: Setup MSVC
27+
uses: ilammy/msvc-dev-cmd@v1
28+
with:
29+
arch: ${{ matrix.architecture }}
30+
31+
- name: Set up Python
32+
uses: actions/setup-python@v6
33+
with:
34+
python-version: ">=3.11.0"
35+
architecture: ${{ matrix.architecture }}
36+
37+
- name: Install CMake
38+
uses: lukka/get-cmake@latest
39+
40+
- name: Install build dependencies
41+
shell: bash -l {0}
42+
run: |
43+
pip install -U pip
44+
pip install cibuildwheel==3.1.4 ninja meson meson-python numpy delvewheel pytest
45+
46+
- name: Build wheels and run FT probe (cp313, cp313t)
47+
env:
48+
# Only the two interpreters we need to compare -> much faster than the
49+
# full matrix.
50+
CIBW_BUILD: "cp313-* cp313t-*"
51+
CIBW_ENABLE: cpython-prerelease cpython-freethreading
52+
CIBW_ARCHS_WINDOWS: ${{ matrix.architecture == 'x86' && 'x86' || 'AMD64' }}
53+
CIBW_BUILD_VERBOSITY: "3"
54+
DISTUTILS_USE_SDK: "1"
55+
MSSdk: "1"
56+
CIBW_REPAIR_WHEEL_COMMAND: 'delvewheel repair -w {dest_dir} {wheel} --add-path C:\sleef\bin'
57+
# Replace the usual pytest run with the targeted diagnostic. It prints
58+
# the actual values and always exits 0, so BOTH wheels' output lands in
59+
# the log for a direct [GIL] vs [FT] comparison.
60+
CIBW_TEST_COMMAND_WINDOWS: pip install numpy && pip install --no-deps {wheel} && python {project}/ci_debug/ft_repro.py
61+
CIBW_TEST_EXTRAS: test
62+
shell: pwsh
63+
run: |
64+
python -m cibuildwheel --output-dir wheelhouse
65+
if (-not (Test-Path wheelhouse/*.whl)) { throw "Wheel was not created" }

ci_debug/ft_repro.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""Windows free-threaded isolation probe for numpy-quaddtype.
2+
3+
Prints the ACTUAL computed values for the operations that fail on the Windows
4+
cp313t (free-threaded) wheel but pass on cp313 (GIL) and on Linux/macOS. Run the
5+
SAME script under both interpreters and diff the ``[GIL]`` vs ``[FT]`` blocks to
6+
see exactly what diverges.
7+
8+
This is a diagnostic, not a gate: it never raises to the shell (always exits 0),
9+
so both wheels' output is captured in the CI log.
10+
"""
11+
import sys
12+
13+
import numpy as np
14+
import numpy_quaddtype as nq
15+
from numpy_quaddtype import QuadPrecision as Q
16+
17+
18+
def gil_enabled():
19+
f = getattr(sys, "_is_gil_enabled", None)
20+
return bool(f()) if f is not None else True
21+
22+
23+
def fval(x):
24+
try:
25+
return float(x)
26+
except Exception as e: # noqa: BLE001
27+
return "<err %s>" % type(e).__name__
28+
29+
30+
def bits(q):
31+
try:
32+
return q.__reduce__()[1][0].hex()
33+
except Exception: # noqa: BLE001
34+
return "?"
35+
36+
37+
def run(label, fn):
38+
try:
39+
print(" %-46s -> %s" % (label, fn()))
40+
except Exception as e: # noqa: BLE001
41+
print(" %-46s -> ERROR %s: %s" % (label, type(e).__name__, e))
42+
43+
44+
TAG = "FT" if not gil_enabled() else "GIL"
45+
46+
print("\n########## quaddtype win-ft probe [%s] ##########" % TAG)
47+
print("python : %s gil_enabled=%s" % (sys.version.split()[0], gil_enabled()))
48+
print("numpy : %s" % np.__version__)
49+
print("quaddtype : %s" % getattr(nq, "__version__", "?"))
50+
run("is_longdouble_128", nq.is_longdouble_128)
51+
52+
print("[%s] string -> quad (special values + a couple normals):" % TAG)
53+
for s in ["inf", "-inf", "nan", "-nan", "Infinity", "1.5", "0.1"]:
54+
run("Q(%r)" % s, lambda s=s: "float=%s bits=%s" % (fval(Q(s)), bits(Q(s))))
55+
56+
print("[%s] float() cast of constructed specials:" % TAG)
57+
run("float(Q('inf'))", lambda: fval(Q("inf")))
58+
run("np.isinf(float(Q('inf')))", lambda: np.isinf(fval(Q("inf"))))
59+
run("np.isnan(float(Q('nan')))", lambda: np.isnan(fval(Q("nan"))))
60+
61+
print("[%s] frexp (quad mantissa/exp vs numpy float64):" % TAG)
62+
for v in ["0.1", "0.9", "1.5", "inf", "nan"]:
63+
def _fx(v=v):
64+
q = Q(v)
65+
qm, qe = np.frexp(q)
66+
fm, fe = np.frexp(np.float64(fval(q)))
67+
return "quad=(%s, %s) np=(%s, %s)" % (fval(qm), qe, fm, fe)
68+
run("frexp(Q(%s))" % v, _fx)
69+
70+
print("[%s] ldexp overflow (expect inf):" % TAG)
71+
for base, e in [("1.5", 16384), ("2.0", 20000)]:
72+
run("ldexp(Q(%s), %d)" % (base, e),
73+
lambda base=base, e=e: fval(np.ldexp(Q(base), e)))
74+
75+
print("[%s] modf:" % TAG)
76+
run("modf(Q('-0.001'))",
77+
lambda: tuple(fval(x) for x in np.modf(Q("-0.001"))))
78+
79+
print("[%s] matmul special (Windows uses the naive kernel; QBLAS disabled):" % TAG)
80+
def _mm():
81+
A = np.array([[Q("inf"), Q("1")], [Q("2"), Q("3")]])
82+
Im = np.array([[Q("1"), Q("0")], [Q("0"), Q("1")]])
83+
return fval(np.matmul(A, Im)[0, 0])
84+
run("matmul(inf-matrix, I)[0,0]", _mm)
85+
86+
print("########## end probe [%s] ##########\n" % TAG)

0 commit comments

Comments
 (0)