-
Notifications
You must be signed in to change notification settings - Fork 602
Expand file tree
/
Copy path_inductor_patch.py
More file actions
79 lines (65 loc) · 3.4 KB
/
Copy path_inductor_patch.py
File metadata and controls
79 lines (65 loc) · 3.4 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
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD 3-Clause license found in the
# LICENSE file in the root directory of this source tree.
"""
Workaround for a Triton miscompile that produces spurious NaN/Inf in compiled
float8 backward kernels (e.g. FSDP2 float8 training with torch.compile).
Root cause: PyTorch inductor PR pytorch/pytorch#186933 changed the Triton codegen
for ``minimum``/``maximum`` from the ``tl.where``-based ``triton_helpers.{minimum,
maximum}`` to ``tl.{minimum,maximum}(a, b, tl.PropagateNan.ALL)``. The two forms
are numerically identical (both propagate NaN), but the ``PropagateNan.ALL`` form
lowers to PTX ``min.NaN``/``max.NaN`` instructions that make Triton mis-lower a
neighboring transposed, vectorized 1-byte (fp8) store. The stored value is
correct, but the transposed store writes garbage bytes that decode as NaN/Inf in
fp8. See https://github.com/triton-lang/triton/issues/11111.
This monkeypatch reverts the inductor min/max codegen to the numerically-identical
``tl.where`` form, which does not emit the ``min.NaN``/``max.NaN`` instructions and
so avoids tripping the Triton store bug. It is applied from
``convert_to_float8_training`` so it only affects callers who actually use the
float8 training product, not every ``import torchao``. The ``tl.where`` form is
always correct, so this is a no-op on numerics even once Triton fixes the
underlying bug.
Note: we must emit the ``tl.where`` expression *inline* rather than delegate to
``triton_helpers.{minimum,maximum}``. #186933's follow-up also rewrote those helper
functions to ``tl.{minimum,maximum}(..., propagate_nan=tl.PropagateNan.ALL)``, so
routing through them would still emit ``min.NaN``/``max.NaN`` and fail to work
around the bug (confirmed against torch 2.14.0.dev / triton 3.8.0).
"""
import logging
logger = logging.getLogger(__name__)
_PATCHED = False
def _patch_inductor_min_max_codegen() -> None:
"""Revert inductor's Triton min/max codegen to the ``tl.where`` form.
Idempotent and defensive: if inductor internals have moved, this logs and
returns rather than breaking ``import torchao.float8``.
"""
global _PATCHED
if _PATCHED:
return
try:
from torch._inductor.codegen.triton import TritonOverrides
except Exception as e: # pragma: no cover - inductor internals moved
logger.debug("float8: could not import TritonOverrides to patch: %s", e)
return
# Numerically-identical replacements for the post-pytorch/pytorch#186933
# `tl.{minimum,maximum}(a, b, tl.PropagateNan.ALL)` codegen. `(a != a)`
# propagates NaN from `a`; NaN in `b` is preserved because the comparison is
# false, so `b` is selected. `a` and `b` are already-CSE'd operands, so
# referencing `a` twice is free. This matches the pre-#186933 codegen
# semantics without routing through the (now also NaN-propagating)
# `triton_helpers.{minimum,maximum}` helpers.
@staticmethod
def minimum(a, b):
return f"tl.where(({a} < {b}) | ({a} != {a}), {a}, {b})"
@staticmethod
def maximum(a, b):
return f"tl.where(({a} > {b}) | ({a} != {a}), {a}, {b})"
TritonOverrides.minimum = minimum
TritonOverrides.maximum = maximum
_PATCHED = True
logger.debug(
"float8: patched inductor Triton min/max codegen to work around "
"https://github.com/triton-lang/triton/issues/11111"
)