Skip to content

Commit a4dcd89

Browse files
Add support for torch FP8 dtypes (#445)
1 parent 7e23c5a commit a4dcd89

9 files changed

Lines changed: 95 additions & 24 deletions

File tree

thunder/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@
9292
"int32",
9393
"int64",
9494
"bfloat16",
95+
"float8_e5m2",
96+
"float8_e5m2fnuz",
97+
"float8_e4m3fn",
98+
"float8_e4m3fnuz",
9599
"float16",
96100
"float32",
97101
"float64",
@@ -129,6 +133,10 @@ def __version__():
129133
int32 = dtypes.int32
130134
int64 = dtypes.int64
131135
bfloat16 = dtypes.bfloat16
136+
float8_e5m2 = dtypes.float8_e5m2
137+
float8_e5m2fnuz = dtypes.float8_e5m2fnuz
138+
float8_e4m3fn = dtypes.float8_e4m3fn
139+
float8_e4m3fnuz = dtypes.float8_e4m3fnuz
132140
float16 = dtypes.float16
133141
float32 = dtypes.float32
134142
float64 = dtypes.float64

thunder/core/baseutils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ def indent(level):
304304
torch.int32: "torch.int32",
305305
torch.int64: "torch.int64",
306306
torch.bfloat16: "torch.bfloat16",
307+
torch.float8_e4m3fn: "torch.float8_e4m3fn",
308+
torch.float8_e4m3fnuz: "torch.float8_e4m3fnuz",
309+
torch.float8_e5m2: "torch.float8_e5m2",
310+
torch.float8_e5m2fnuz: "torch.float8_e5m2fnuz",
307311
torch.float16: "torch.float16",
308312
torch.float32: "torch.float32",
309313
torch.float64: "torch.float64",

thunder/core/dtypes.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ def __new__(cls, *args, **kwargs):
5959

6060
return object.__new__(cls)
6161

62-
def __init__(self, *, python_type, name, shortname, bytes, is_weak):
62+
def __init__(self, *, python_type, name, shortname, bytes, is_weak, variant=None):
6363
self._python_type = python_type
6464
self._name = name
65+
self._variant = variant
6566
self._shortname = shortname
6667
self._bytes = bytes
6768
self._is_weak = is_weak
@@ -80,23 +81,30 @@ def is_weak(self):
8081
return self._is_weak
8182

8283
def shortname(self):
83-
return f"{self._shortname}{8 * self._bytes}"
84+
return f"{self._shortname}{8 * self._bytes}{f'_{self._variant}' if self._variant else ''}"
8485

8586
# TODO Fix name printing
8687
def __repr__(self):
87-
return f"{self._name}{8 * self._bytes}{'_' if self._is_weak else ''}"
88+
return (
89+
f"{self._name}{8 * self._bytes}{f'_{self._variant}' if self._variant else ''}{'_' if self._is_weak else ''}"
90+
)
8891

8992
def __str__(self):
9093
return self.__repr__()
9194

9295
def __hash__(self) -> int:
93-
return hash((self._name, self._bytes, self._is_weak))
96+
return hash((self._name, self._bytes, self._is_weak, f"{self._variant if self._variant else ''}"))
9497

9598
def __eq__(self, other) -> bool:
9699
if not isinstance(other, dtype):
97100
return False
98101

99-
return self._name == other._name and self._bytes == other._bytes and self._is_weak == other._is_weak
102+
return (
103+
self._name == other._name
104+
and self._bytes == other._bytes
105+
and self._is_weak == other._is_weak
106+
and self._variant == other._variant
107+
)
100108

101109

102110
class exact(dtype):
@@ -152,14 +160,24 @@ class inexact(dtype):
152160

153161

154162
class floating(inexact):
155-
"""Base class for the floating dtypes: bfloat16, float16, float32, float64."""
163+
"""Base class for the floating dtypes: float8, bfloat16, float16, float32, float64."""
156164

157-
def __init__(self, name, shortname, *, bytes, is_weak):
158-
super().__init__(python_type=float, name=name, shortname=shortname, bytes=bytes, is_weak=is_weak)
165+
def __init__(self, name, shortname, *, bytes, is_weak, variant=None):
166+
super().__init__(
167+
python_type=float, name=name, shortname=shortname, bytes=bytes, is_weak=is_weak, variant=variant
168+
)
159169

160170

161171
bfloat16 = floating("bfloat", "bf", bytes=2, is_weak=False)
162172
bfloat16_ = floating("bfloat", "bf", bytes=2, is_weak=True)
173+
float8_e5m2 = floating("float", "f", bytes=1, is_weak=False, variant="e5m2")
174+
float8_e5m2_ = floating("float", "f", bytes=1, is_weak=True, variant="e5m2")
175+
float8_e5m2fnuz = floating("float", "f", bytes=1, is_weak=False, variant="e5m2fnuz")
176+
float8_e5m2fnuz_ = floating("float", "f", bytes=1, is_weak=True, variant="e5m2fnuz")
177+
float8_e4m3fn = floating("float", "f", bytes=1, is_weak=False, variant="e4m3fn")
178+
float8_e4m3fn_ = floating("float", "f", bytes=1, is_weak=True, variant="e4m3fn")
179+
float8_e4m3fnuz = floating("float", "f", bytes=1, is_weak=False, variant="e4m3fnuz")
180+
float8_e4m3fnuz_ = floating("float", "f", bytes=1, is_weak=True, variant="e4m3fnuz")
163181
float16 = floating("float", "f", bytes=2, is_weak=False)
164182
float16_ = floating("float", "f", bytes=2, is_weak=True)
165183
float32 = floating("float", "f", bytes=4, is_weak=False)
@@ -200,6 +218,14 @@ def __init__(self, name, shortname, *, bytes, is_weak):
200218
int64_,
201219
bfloat16,
202220
bfloat16_,
221+
float8_e5m2,
222+
float8_e5m2_,
223+
float8_e5m2fnuz,
224+
float8_e5m2fnuz_,
225+
float8_e4m3fn,
226+
float8_e4m3fn_,
227+
float8_e4m3fnuz,
228+
float8_e4m3fnuz_,
203229
float16,
204230
float16_,
205231
float32,
@@ -242,6 +268,10 @@ def __init__(self, name, shortname, *, bytes, is_weak):
242268

243269
float_dtypes = {d for d in all_dtypes if isinstance(d, floating)} | {float}
244270

271+
float_math_dtypes = {d for d in all_dtypes if isinstance(d, floating) and d.bytes >= 2}
272+
273+
float_8bit_dtypes = {d for d in all_dtypes if (isinstance(d, floating) and d.bytes == 1)}
274+
245275
complex_dtypes = {d for d in all_dtypes if isinstance(d, complexfloating)} | {complex}
246276

247277
inexact_dtypes = float_dtypes | complex_dtypes
@@ -306,11 +336,12 @@ def has_subdtype(x, cls):
306336

307337

308338
# Translates a sequence of dtypes and dtype classes into a concrete set of corresponding (strong) dtypes
309-
def resolve_dtypes(args):
339+
def resolve_dtypes(args: Iterable) -> set[dtype]:
310340
dtypes = set()
311341
for arg in args:
312342
if isinstance(arg, dtype):
313-
dtypes.add(arg)
343+
if not arg.is_weak:
344+
dtypes.add(arg)
314345
continue
315346

316347
if isinstance(arg, Iterable):
@@ -320,7 +351,8 @@ def resolve_dtypes(args):
320351
lambda: f"Iterables passed to resolve_dtypes must only contain dtypes, but found an Iterable with {a}",
321352
exception_type=NotImplementedError,
322353
)
323-
dtypes.add(a)
354+
if not a.is_weak:
355+
dtypes.add(a)
324356

325357
baseutils.check(
326358
arg in (dtype, exact, signedinteger, unsignedinteger, bool_, inexact, floating, complexfloating),
@@ -373,6 +405,10 @@ def corresponding_complex_dtype(dtype):
373405
int32: int32_,
374406
int64: int64_,
375407
bfloat16: bfloat16_,
408+
float8_e5m2: float8_e5m2_,
409+
float8_e5m2fnuz: float8_e5m2fnuz_,
410+
float8_e4m3fn: float8_e4m3fn_,
411+
float8_e4m3fnuz: float8_e4m3fnuz_,
376412
float16: float16_,
377413
float32: float32_,
378414
float64: float64_,
@@ -520,6 +556,14 @@ def are_same_dtypes(a, b, *, weak_and_strong_are_equivalent=True):
520556
int64: torch.int64,
521557
bfloat16_: torch.bfloat16,
522558
bfloat16: torch.bfloat16,
559+
float8_e5m2: torch.float8_e5m2,
560+
float8_e5m2_: torch.float8_e5m2,
561+
float8_e5m2fnuz: torch.float8_e5m2fnuz,
562+
float8_e5m2fnuz_: torch.float8_e5m2fnuz,
563+
float8_e4m3fn: torch.float8_e4m3fn,
564+
float8_e4m3fn_: torch.float8_e4m3fn,
565+
float8_e4m3fnuz: torch.float8_e4m3fnuz,
566+
float8_e4m3fnuz_: torch.float8_e4m3fnuz,
523567
float16_: torch.float16,
524568
float16: torch.float16,
525569
float32_: torch.float32,
@@ -551,7 +595,7 @@ def to_torch_dtype(x: None | torch.dtype | dtype) -> None | torch.dtype:
551595

552596
# Converts NumPy dtypes to and from thunder dtypes
553597

554-
# NOTE NumPy does not support the bfloat16 or complexhalf (complex32) datatypes
598+
# NOTE NumPy does not support the bfloat16, complexhalf (complex32) or float8 datatypes
555599
_thunder_to_numpy_dtype_map = {
556600
bool: np.bool_,
557601
int: np.int_,

thunder/tests/framework.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class nvFuserTestExecutor(TestExecutor):
172172
name = "nvfuser"
173173
supported_devicetypes = (devices.DeviceType.CUDA,)
174174
supported_dtypes = (
175-
datatypes.floating,
175+
*datatypes.float_math_dtypes,
176176
datatypes.bool8,
177177
datatypes.int32,
178178
datatypes.int64,
@@ -347,7 +347,9 @@ def __init__(
347347
self.supported_devicetypes = set(filter_ci_devicetypes(self.supported_devicetypes))
348348

349349
self.supported_dtypes = (
350-
datatypes.resolve_dtypes(supported_dtypes) if supported_dtypes is not None else datatypes.all_dtypes
350+
datatypes.resolve_dtypes(supported_dtypes)
351+
if supported_dtypes is not None
352+
else datatypes.all_dtypes - datatypes.float_8bit_dtypes
351353
)
352354

353355
if supported_dtypes == NOTHING:

thunder/tests/make_tensor.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,15 @@ def clamp(a, l, h):
117117
shape = cast(tuple[int, ...], tuple(shape))
118118

119119
_integral_types = [torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64]
120+
_floating_8bit_types = [torch.float8_e4m3fn, torch.float8_e4m3fnuz, torch.float8_e5m2, torch.float8_e5m2fnuz]
120121
_floating_types = [torch.float16, torch.bfloat16, torch.float32, torch.float64]
121122
_complex_types = [torch.complex32, torch.complex64, torch.complex128]
122-
if requires_grad and dtype not in _floating_types and dtype not in _complex_types:
123+
if (
124+
requires_grad
125+
and dtype not in _floating_types
126+
and dtype not in _floating_8bit_types
127+
and dtype not in _complex_types
128+
):
123129
raise ValueError("make_tensor: requires_grad must be False for integral dtype")
124130

125131
if dtype is torch.bool:
@@ -145,10 +151,10 @@ def clamp(a, l, h):
145151
if low == high:
146152
return torch.full(shape, low, device=device, dtype=dtype)
147153
result = torch.randint(low, high, shape, device=device, dtype=dtype) # type: ignore[call-overload]
148-
elif dtype in _floating_types:
154+
elif dtype in _floating_types + _floating_8bit_types:
149155
ranges_floats = (torch.finfo(dtype).min, torch.finfo(dtype).max)
150156
m_low, m_high = _modify_low_high(low, high, ranges_floats[0], ranges_floats[1], -9, 9, dtype)
151-
result = torch.empty(shape, device=device, dtype=dtype)
157+
result = torch.empty(shape, device=device, dtype=dtype if dtype not in _floating_8bit_types else torch.float32)
152158
_uniform_random(result, m_low, m_high)
153159
elif dtype in _complex_types:
154160
float_dtype = complex_to_corresponding_float_type_map[dtype]
@@ -175,6 +181,8 @@ def clamp(a, l, h):
175181
replace_with = torch.tensor(1, device=device, dtype=dtype)
176182
elif dtype in _floating_types:
177183
replace_with = torch.tensor(torch.finfo(dtype).tiny, device=device, dtype=dtype)
184+
elif dtype in _floating_8bit_types:
185+
replace_with = torch.tensor(torch.finfo(dtype).tiny, device=device, dtype=torch.float32)
178186
else: # dtype in _complex_types:
179187
float_dtype = complex_to_corresponding_float_type_map[dtype]
180188
float_eps = torch.tensor(torch.finfo(float_dtype).tiny, device=device, dtype=float_dtype)
@@ -184,6 +192,11 @@ def clamp(a, l, h):
184192
if dtype in _floating_types + _complex_types:
185193
result.requires_grad = requires_grad
186194

195+
# NOTE This is a workaround. There are so many not supported operations that,
196+
# even creating the test tensors is hard.
197+
if dtype in _floating_8bit_types:
198+
result = result.to(dtype)
199+
187200
return result
188201

189202

thunder/tests/test_autocast.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# TODO This test currently ignores the "should_autocast" argument enumerated in it
1616
@instantiate(
17-
dtypes=dtypes.float_dtypes - {float},
17+
dtypes=dtypes.float_math_dtypes,
1818
)
1919
def test_thunder_autocast_transform(executor, device, dtype):
2020
from thunder.core.transforms import autocast
@@ -65,7 +65,7 @@ def h(a, b, c):
6565

6666
@instantiate(
6767
executors=[TorchExecutor],
68-
dtypes=dtypes.float_dtypes - {float},
68+
dtypes=dtypes.float_math_dtypes,
6969
)
7070
def test_no_autocast(executor, device, dtype):
7171
from thunder.core.symbol import Symbol
@@ -112,7 +112,7 @@ def func():
112112

113113

114114
@instantiate(
115-
dtypes=dtypes.float_dtypes - {float},
115+
dtypes=dtypes.float_math_dtypes,
116116
decorators=(pytest.mark.skipif(not is_inductor_supported(), reason="inductor unsupported"),),
117117
)
118118
def test_compile_autocast(executor, device, dtype):

thunder/tests/test_grad.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ def upcast_tensors(x: Any) -> Any:
12691269

12701270
@ops(
12711271
tuple(op for op in opinfos if op.supports_grad and op.torch_reference is not None),
1272-
supported_dtypes=(dtypes.floating,),
1272+
supported_dtypes=dtypes.float_math_dtypes,
12731273
)
12741274
def test_phantom_grad_vs_torch_consistency(op, device: str, dtype: dtypes.dtype, executor, comp):
12751275
if dtypes.is_complex_dtype(dtype):

thunder/tests/test_inplace_copy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from thunder.tests.framework import instantiate, nvFuserExecutor
1111

1212

13-
@instantiate()
13+
@instantiate(dtypes=datatypes.all_dtypes - datatypes.float_8bit_dtypes)
1414
def test_prim_inplace_copy_fwd(executor, device, dtype):
1515
def torch_foo(x, y):
1616
z = x + y
@@ -37,7 +37,7 @@ def foo(x, y):
3737
assert_close(a, a1)
3838

3939

40-
@instantiate(dtypes=(datatypes.floating,))
40+
@instantiate(dtypes=datatypes.float_math_dtypes)
4141
def test_prim_inplace_copy_bwd(executor, device, dtype):
4242
def torch_foo(x, y):
4343
z = x * y

thunder/tests/test_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def snippet_errors(op, sample, ex_type, err_msg_match=None):
2323

2424

2525
@ops(tuple(op for op in opinfos if op.error_input_generator is not None))
26-
def test_errors(op, device, _, executor, comp):
26+
def test_errors(op, device, dtype, executor, comp):
2727
for sample, ex_type, err_msg in op.error_inputs(device):
2828
result = run_snippet(snippet_errors, op, device, None, executor.make_callable(op.op), sample, ex_type, err_msg)
2929
if result is not None:

0 commit comments

Comments
 (0)