-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathverify_ascend_format.py
More file actions
195 lines (155 loc) · 7.17 KB
/
Copy pathverify_ascend_format.py
File metadata and controls
195 lines (155 loc) · 7.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
"""
Verify the new Ascend / en-dtypes byte-compatible HiFloat8 path and the
NaN alignment fix in the original LUT-rank path.
Checks:
A) NaN round-trip: NaN → uint8 → NaN in both layouts.
B) Ascend encode produces the same uint8 buffer as en-dtypes.
C) Ascend decode produces the same float values as en-dtypes for all
256 byte patterns (NaN/Inf semantics included).
D) Both layouts agree on dequantised value for all 1.1M random samples
(i.e. both round-trip to the same HiFloat8 magnitude).
E) The legacy LUT-rank kernel still byte-encodes the same as before
for finite inputs (no regression for non-NaN encodings).
"""
from __future__ import annotations
import math
import sys
import numpy as np
import torch
import en_dtypes as en
sys.path.insert(0, "custom_ops")
import hifp8_cuda_uint8 as h
def to_cuda_f32(arr_f64: np.ndarray) -> torch.Tensor:
return torch.from_numpy(arr_f64.astype(np.float32)).cuda().contiguous()
def en_encode(arr_f64: np.ndarray) -> np.ndarray:
return arr_f64.astype(np.float32).astype(en.hifloat8).view(np.uint8)
def en_decode_bytes(bytes_arr: np.ndarray) -> np.ndarray:
return bytes_arr.view(en.hifloat8).astype(np.float64)
# ---------------------------------------------------------------------------
# A. NaN round-trip
# ---------------------------------------------------------------------------
def test_nan_roundtrip():
nan_in = np.array([math.nan, -math.nan, math.nan], dtype=np.float64)
x = to_cuda_f32(nan_in)
# LUT-rank path
enc_lr = h.hif8_encode_cuda(x).cpu().numpy()
dec_lr = h.hif8_decode_cuda(h.hif8_encode_cuda(x)).cpu().numpy()
assert np.all(enc_lr == 0x80), f"LUT-rank NaN encode: {enc_lr}"
assert np.all(np.isnan(dec_lr)), f"LUT-rank NaN decode: {dec_lr}"
# Ascend path
enc_as = h.hif8_encode_ascend_cuda(x).cpu().numpy()
dec_as = h.hif8_decode_ascend_cuda(h.hif8_encode_ascend_cuda(x)).cpu().numpy()
assert np.all(enc_as == 0x80), f"Ascend NaN encode: {enc_as}"
assert np.all(np.isnan(dec_as)), f"Ascend NaN decode: {dec_as}"
print("[A] NaN round-trip OK (both layouts emit 0x80 → NaN)")
# ---------------------------------------------------------------------------
# B. Ascend encode == en-dtypes encode (byte-for-byte)
# ---------------------------------------------------------------------------
def test_ascend_encode_matches_endtypes():
rng = np.random.default_rng(0)
samples = []
# All representable values (signed)
repo_vals = []
for M in range(1, 8):
repo_vals.append(2.0 ** (M - 23))
for E in range(-15, 16):
ae = abs(E)
mb = 3 if ae <= 3 else (2 if ae <= 7 else 1)
for m in range(2 ** mb):
v = (2.0 ** E) * (1.0 + m / (2.0 ** mb))
if v >= 1.5 * 2 ** 15:
continue
repo_vals.append(v)
repo_vals.append(0.0)
samples.append(np.array(repo_vals + [-v for v in repo_vals], dtype=np.float64))
# Random sweeps
for hi in (1.0, 100.0, 30000.0):
samples.append((rng.random(50_000) * 2 - 1) * hi)
e = rng.uniform(math.log(2 ** -25), math.log(2 ** 17), size=200_000)
s = rng.choice([-1.0, 1.0], size=200_000)
samples.append(s * np.exp(e))
# Specials (NaN handled in test A; here include +/- Inf and 0)
samples.append(np.array([0.0, -0.0, math.inf, -math.inf], dtype=np.float64))
total = 0
bad = 0
for arr in samples:
x = to_cuda_f32(arr)
cuda_bytes = h.hif8_encode_ascend_cuda(x).cpu().numpy()
en_bytes = en_encode(arr)
diff = cuda_bytes != en_bytes
total += arr.size
bad += int(diff.sum())
if diff.any():
idx = np.where(diff)[0][:5]
for i in idx:
print(f" MISMATCH x={float(arr[i])!r} "
f"cuda=0x{int(cuda_bytes[i]):02X} en=0x{int(en_bytes[i]):02X}")
msg = "OK" if bad == 0 else f"FAIL ({bad}/{total})"
print(f"[B] Ascend encode vs en {msg} (n={total})")
assert bad == 0
# ---------------------------------------------------------------------------
# C. Ascend decode == en-dtypes decode for all 256 byte patterns
# ---------------------------------------------------------------------------
def test_ascend_decode_matches_endtypes():
bytes_all = np.arange(256, dtype=np.uint8)
cuda_dec = h.hif8_decode_ascend_cuda(
torch.from_numpy(bytes_all).cuda().contiguous()
).cpu().numpy().astype(np.float64)
en_dec = en_decode_bytes(bytes_all)
bad = 0
for b in range(256):
a, e = cuda_dec[b], en_dec[b]
same = (np.isnan(a) and np.isnan(e)) or (a == e) or \
(np.isinf(a) and np.isinf(e) and np.sign(a) == np.sign(e))
if not same:
bad += 1
print(f" 0x{b:02X}: cuda={a!r} en={e!r}")
print(f"[C] Ascend decode vs en {'OK' if bad == 0 else f'FAIL ({bad}/256)'} (n=256)")
assert bad == 0
# ---------------------------------------------------------------------------
# D. Both layouts produce the same dequantised value
# ---------------------------------------------------------------------------
def test_layouts_value_equivalent():
rng = np.random.default_rng(1)
arr = rng.standard_normal(500_000).astype(np.float32) * 100.0
x = to_cuda_f32(arr.astype(np.float64))
dec_lr = h.hif8_decode_cuda(h.hif8_encode_cuda(x)).cpu().numpy()
dec_as = h.hif8_decode_ascend_cuda(h.hif8_encode_ascend_cuda(x)).cpu().numpy()
# NaN-safe equality
finite = np.isfinite(dec_lr) & np.isfinite(dec_as)
eq = (dec_lr == dec_as) & finite
nan_eq = np.isnan(dec_lr) & np.isnan(dec_as)
inf_eq = np.isinf(dec_lr) & np.isinf(dec_as) & (np.sign(dec_lr) == np.sign(dec_as))
bad = int((~(eq | nan_eq | inf_eq)).sum())
print(f"[D] LUT-rank == Ascend {'OK' if bad == 0 else f'FAIL ({bad})'} (n={arr.size})")
assert bad == 0
# ---------------------------------------------------------------------------
# E. LUT-rank backward compatibility on finite inputs
# (Repo bytes are unchanged for non-NaN inputs; only 0x80 is repurposed.)
# ---------------------------------------------------------------------------
def test_lut_rank_backward_compat():
rng = np.random.default_rng(2)
arr = rng.standard_normal(100_000).astype(np.float32) * 100.0
# Recompute the legacy expected bytes via the LUT directly:
# since the patch only added a NaN early-exit, finite inputs go through
# the unchanged code path.
x = to_cuda_f32(arr.astype(np.float64))
enc = h.hif8_encode_cuda(x).cpu().numpy()
# The check is implicit: we already validated value-level equality with
# en-dtypes in earlier verification scripts. Here we only assert that
# 0x80 never appears for finite inputs (the only behavioural change).
bad = int((enc == 0x80).sum())
print(f"[E] LUT-rank no 0x80 leak {'OK' if bad == 0 else f'FAIL ({bad})'} (finite-only)")
assert bad == 0
def main():
assert torch.cuda.is_available(), "CUDA required"
test_nan_roundtrip()
test_ascend_encode_matches_endtypes()
test_ascend_decode_matches_endtypes()
test_layouts_value_equivalent()
test_lut_rank_backward_compat()
print()
print("=" * 60)
print("ALL CHECKS PASSED")
if __name__ == "__main__":
main()