Skip to content

Commit 23b27ad

Browse files
authored
Fix 32-bit regressions in ndarray encode/decode and stochastic rounding tests (#60)
1 parent e45b455 commit 23b27ad

7 files changed

Lines changed: 31 additions & 75 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
7373
- name: Run full unit tests on linux/386
7474
run: |
75-
bash etc/test-linux-386.sh run
75+
bash etc/test-linux-386.sh run -vv test
7676
7777
- name: Save linux/386 docker image to cache path
7878
if: always()

etc/test-linux-386.sh

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# Copyright (c) 2024 Graphcore Ltd. All rights reserved.
44

5+
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
6+
57
set -euo pipefail
68

79
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -10,14 +12,23 @@ REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
1012
IMAGE="gfloat-linux-386:py310-uv"
1113
PLATFORM="linux/386"
1214
DOCKERFILE="etc/linux-386.Dockerfile"
13-
MODE="${1:-all}"
15+
MODE="all"
16+
17+
if [[ "${1:-}" == "all" || "${1:-}" == "load" || "${1:-}" == "build" || "${1:-}" == "run" ]]; then
18+
MODE="$1"
19+
shift
20+
fi
1421

1522
usage() {
16-
echo "Usage: bash etc/test-linux-386.sh [load|build|run]"
23+
echo "Usage: bash etc/test-linux-386.sh [all|load|build|run] [pytest args...]"
1724
echo " load Build image only if missing"
1825
echo " build Force rebuild image"
1926
echo " run Run tests (image must already exist)"
2027
echo " (no arg) Build image if missing, then run tests"
28+
echo ""
29+
echo "Examples:"
30+
echo " bash etc/test-linux-386.sh run test/test_encode.py::test_encode[binary32]"
31+
echo " bash etc/test-linux-386.sh run -k stochastic -q"
2132
}
2233

2334
build_image() {
@@ -39,16 +50,16 @@ run_tests() {
3950
bash -lc '
4051
set -euo pipefail
4152
export PYTHONPATH="/work/src${PYTHONPATH:+:${PYTHONPATH}}"
42-
python -m pytest -vv test
43-
'
53+
python -m pytest "$@"
54+
' _ "$@"
4455
}
4556

4657
if [[ "${MODE}" != "all" && "${MODE}" != "load" && "${MODE}" != "build" && "${MODE}" != "run" ]]; then
4758
usage
4859
exit 2
4960
fi
5061

51-
echo "Running full unit tests in Docker (${PLATFORM})"
62+
echo "Running unit tests in Docker (${PLATFORM})"
5263

5364
if ! docker info >/dev/null 2>&1; then
5465
echo "Docker daemon is not running; start Docker and rerun this script." >&2
@@ -73,5 +84,5 @@ if [[ "${MODE}" == "run" || "${MODE}" == "all" ]]; then
7384
exit 1
7485
fi
7586

76-
run_tests
87+
run_tests "$@"
7788
fi

src/gfloat/decode_ndarray.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ def decode_ndarray(
7878

7979
issubnormal = (exp == 0) & (significand != 0) & fi.has_subnormals
8080
expval = np.where(issubnormal, 1 - bias, exp - bias)
81-
fsignificand = np.where(issubnormal, 0.0, 1.0) + np.ldexp(significand, -t)
81+
fsignificand = np.where(issubnormal, 0.0, 1.0) + np.ldexp(
82+
significand.astype(np.float64), np.int32(-t)
83+
)
8284

8385
# Normal/Subnormal/Zero case, other values will be overwritten
84-
expval_safe = np.where(isspecial | iszero, 0, expval)
86+
expval_safe = np.where(isspecial | iszero, 0, expval).astype(np.int32)
8587
fval_finite_safe = sign * np.ldexp(fsignificand, expval_safe)
8688
fval = np.where(~(iszero | isspecial), fval_finite_safe, fval)
8789

src/gfloat/encode_ndarray.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ def encode_ndarray(fi: FormatInfo, v: npt.NDArray) -> npt.NDArray:
6666
biased_exp = exp.astype(np.int64) + (fi.bias - 1)
6767
subnormal_mask = (biased_exp < 1) & fi.has_subnormals
6868

69-
biased_exp_safe = np.where(subnormal_mask, biased_exp, 0)
69+
biased_exp_safe = np.where(subnormal_mask, biased_exp, 0).astype(np.int32)
7070
tsig = np.where(subnormal_mask, np.ldexp(sig, biased_exp_safe), sig * 2 - 1.0)
7171
biased_exp[subnormal_mask] = 0
7272

73-
isig = np.floor(np.ldexp(tsig, t)).astype(np.int64)
73+
isig = np.floor(np.ldexp(tsig, np.int32(t))).astype(np.int64)
7474

7575
zero_mask = fi.has_zero & (isig == 0) & (biased_exp == 0)
7676
if not fi.has_nz:
@@ -80,8 +80,9 @@ def encode_ndarray(fi: FormatInfo, v: npt.NDArray) -> npt.NDArray:
8080
if fi.is_twos_complement:
8181
isig[finite_sign] = (1 << t) - isig[finite_sign]
8282

83-
code[finite_mask] = (
84-
(finite_sign.astype(int) << (k - 1)) | (biased_exp << t) | (isig << 0)
85-
)
83+
sign_field = np.left_shift(finite_sign.astype(np.uint64), np.uint64(k - 1))
84+
exp_field = np.left_shift(biased_exp.astype(np.uint64), np.uint64(t))
85+
sig_field = isig.astype(np.uint64)
86+
code[finite_mask] = sign_field | exp_field | sig_field
8687

8788
return code

test/conftest.py

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1 @@
11
# Copyright (c) 2024 Graphcore Ltd. All rights reserved.
2-
3-
import struct
4-
5-
import pytest
6-
7-
8-
def _is_32bit_python() -> bool:
9-
return struct.calcsize("P") * 8 == 32
10-
11-
12-
def pytest_collection_modifyitems(
13-
config: pytest.Config, items: list[pytest.Item]
14-
) -> None:
15-
if not _is_32bit_python():
16-
return
17-
18-
mark = pytest.mark.xfail(
19-
reason="Known 32-bit regressions (issue #57)",
20-
strict=False,
21-
)
22-
23-
for item in items:
24-
nodeid = item.nodeid
25-
26-
if nodeid.startswith("test/test_array_api.py::"):
27-
item.add_marker(mark)
28-
continue
29-
30-
if nodeid.startswith(
31-
"test/test_round.py::test_stochastic_rounding_scalar_eq_array"
32-
):
33-
item.add_marker(mark)
34-
continue
35-
36-
if nodeid.startswith("test/test_encode.py::test_encode["):
37-
item.add_marker(mark)
38-
continue
39-
40-
if nodeid.startswith("test/test_encode.py::test_encode_edges[encode_ndarray-"):
41-
item.add_marker(mark)
42-
continue
43-
44-
if (
45-
nodeid.startswith("test/test_decode.py::test_spot_check_")
46-
and "[array]" in nodeid
47-
):
48-
item.add_marker(mark)
49-
continue
50-
51-
if (
52-
nodeid.startswith("test/test_decode.py::test_specials_decode[")
53-
and "[array-" in nodeid
54-
):
55-
item.add_marker(mark)
56-
continue
57-
58-
if nodeid.startswith("test/test_decode.py::test_consistent_decodes_all_values["):
59-
item.add_marker(mark)

test/test_array_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_array_api(fi: FormatInfo, rnd: RoundMode, sat: bool) -> None:
2525
a = xp.asarray(a0)
2626

2727
srnumbits = 32
28-
srbits0 = np.random.randint(0, 2**srnumbits, a.shape)
28+
srbits0 = np.random.randint(0, 2**srnumbits, a.shape, dtype=np.int64)
2929
srbits = xp.asarray(srbits0)
3030

3131
round_ndarray(fi, a, rnd, sat, srbits=srbits, srnumbits=srnumbits) # type: ignore

test/test_round.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ def test_stochastic_rounding(
537537
n = 10_000
538538
expected_up_count = expected_up * n
539539

540-
srbits = np.random.randint(0, 2**srnumbits, size=(n,))
540+
srbits = np.random.randint(0, 2**srnumbits, size=(n,), dtype=np.int64)
541541
if impl == "scalar":
542542
count_v1 = 0
543543
for k in range(n):
@@ -591,7 +591,7 @@ def test_stochastic_rounding_scalar_eq_array(
591591
for alpha in (0, 0.3, 0.5, 0.6, 0.7, 0.9, 1.25):
592592
v = _linterp(v0, v1, alpha)
593593
assert np.isfinite(v).all()
594-
srbits = np.random.randint(0, 2**srnumbits, v.shape)
594+
srbits = np.random.randint(0, 2**srnumbits, v.shape, dtype=np.int64)
595595

596596
val_array = round_ndarray(
597597
fi,

0 commit comments

Comments
 (0)