diff options
author | Stella Laurenzo <laurenzo@google.com> | 2022-07-26 19:02:37 -0700 |
---|---|---|
committer | Stella Laurenzo <stellaraccident@gmail.com> | 2022-10-04 17:18:17 -0700 |
commit | e28b15b572b58e5db4375da35745a4131858fc4c (patch) | |
tree | 81ae3512844dd6b8d51d99da08b87d149890004a /llvm/lib/Support/APFloat.cpp | |
parent | be758cd4a34a2b648a12bb3ae52adf9e6c5472e4 (diff) | |
download | llvm-e28b15b572b58e5db4375da35745a4131858fc4c.zip llvm-e28b15b572b58e5db4375da35745a4131858fc4c.tar.gz llvm-e28b15b572b58e5db4375da35745a4131858fc4c.tar.bz2 |
Add APFloat and MLIR type support for fp8 (e5m2).
(Re-Apply with fixes to clang MicrosoftMangle.cpp)
This is a first step towards high level representation for fp8 types
that have been built in to hardware with near term roadmaps. Like the
BFLOAT16 type, the family of fp8 types are inspired by IEEE-754 binary
floating point formats but, due to the size limits, have been tweaked in
various ways in order to maximally use the range/precision in various
scenarios. The list of variants is small/finite and bounded by real
hardware.
This patch introduces the E5M2 FP8 format as proposed by Nvidia, ARM,
and Intel in the paper: https://arxiv.org/pdf/2209.05433.pdf
As the more conformant of the two implemented datatypes, we are plumbing
it through LLVM's APFloat type and MLIR's type system first as a
template. It will be followed by the range optimized E4M3 FP8 format
described in the paper. Since that format deviates further from the
IEEE-754 norms, it may require more debate and implementation
complexity.
Given that we see two parts of the FP8 implementation space represented
by these cases, we are recommending naming of:
* `F8M<N>` : For FP8 types that can be conceived of as following the
same rules as FP16 but with a smaller number of mantissa/exponent
bits. Including the number of mantissa bits in the type name is enough
to fully specify the type. This naming scheme is used to represent
the E5M2 type described in the paper.
* `F8M<N>F` : For FP8 types such as E4M3 which only support finite
values.
The first of these (this patch) seems fairly non-controversial. The
second is previewed here to illustrate options for extending to the
other known variant (but can be discussed in detail in the patch
which implements it).
Many conversations about these types focus on the Machine-Learning
ecosystem where they are used to represent mixed-datatype computations
at a high level. At that level (which is why we also expose them in
MLIR), it is important to retain the actual type definition so that when
lowering to actual kernels or target specific code, the correct
promotions, casts and rescalings can be done as needed. We expect that
most LLVM backends will only experience these types as opaque `I8`
values that are applicable to some instructions.
MLIR does not make it particularly easy to add new floating point types
(i.e. the FloatType hierarchy is not open). Given the need to fully
model FloatTypes and make them interop with tooling, such types will
always be "heavy-weight" and it is not expected that a highly open type
system will be particularly helpful. There are also a bounded number of
floating point types in use for current and upcoming hardware, and we
can just implement them like this (perhaps looking for some cosmetic
ways to reduce the number of places that need to change). Creating a
more generic mechanism for extending floating point types seems like it
wouldn't be worth it and we should just deal with defining them one by
one on an as-needed basis when real hardware implements a new scheme.
Hopefully, with some additional production use and complete software
stacks, hardware makers will converge on a set of such types that is not
terribly divergent at the level that the compiler cares about.
(I cleaned up some old formatting and sorted some items for this case:
If we converge on landing this in some form, I will NFC commit format
only changes as a separate commit)
Differential Revision: https://reviews.llvm.org/D133823
Diffstat (limited to 'llvm/lib/Support/APFloat.cpp')
-rw-r--r-- | llvm/lib/Support/APFloat.cpp | 86 |
1 files changed, 74 insertions, 12 deletions
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp index 6f888e31..68063bb 100644 --- a/llvm/lib/Support/APFloat.cpp +++ b/llvm/lib/Support/APFloat.cpp @@ -80,6 +80,7 @@ namespace llvm { static const fltSemantics semIEEEsingle = {127, -126, 24, 32}; static const fltSemantics semIEEEdouble = {1023, -1022, 53, 64}; static const fltSemantics semIEEEquad = {16383, -16382, 113, 128}; + static const fltSemantics semFloat8E5M2 = {15, -14, 3, 8}; static const fltSemantics semX87DoubleExtended = {16383, -16382, 64, 80}; static const fltSemantics semBogus = {0, 0, 0, 0}; @@ -131,12 +132,14 @@ namespace llvm { return IEEEsingle(); case S_IEEEdouble: return IEEEdouble(); - case S_x87DoubleExtended: - return x87DoubleExtended(); case S_IEEEquad: return IEEEquad(); case S_PPCDoubleDouble: return PPCDoubleDouble(); + case S_Float8E5M2: + return Float8E5M2(); + case S_x87DoubleExtended: + return x87DoubleExtended(); } llvm_unreachable("Unrecognised floating semantics"); } @@ -151,12 +154,14 @@ namespace llvm { return S_IEEEsingle; else if (&Sem == &llvm::APFloat::IEEEdouble()) return S_IEEEdouble; - else if (&Sem == &llvm::APFloat::x87DoubleExtended()) - return S_x87DoubleExtended; else if (&Sem == &llvm::APFloat::IEEEquad()) return S_IEEEquad; else if (&Sem == &llvm::APFloat::PPCDoubleDouble()) return S_PPCDoubleDouble; + else if (&Sem == &llvm::APFloat::Float8E5M2()) + return S_Float8E5M2; + else if (&Sem == &llvm::APFloat::x87DoubleExtended()) + return S_x87DoubleExtended; else llvm_unreachable("Unknown floating semantics"); } @@ -173,18 +178,15 @@ namespace llvm { const fltSemantics &APFloatBase::IEEEdouble() { return semIEEEdouble; } - const fltSemantics &APFloatBase::IEEEquad() { - return semIEEEquad; + const fltSemantics &APFloatBase::IEEEquad() { return semIEEEquad; } + const fltSemantics &APFloatBase::PPCDoubleDouble() { + return semPPCDoubleDouble; } + const fltSemantics &APFloatBase::Float8E5M2() { return semFloat8E5M2; } const fltSemantics &APFloatBase::x87DoubleExtended() { return semX87DoubleExtended; } - const fltSemantics &APFloatBase::Bogus() { - return semBogus; - } - const fltSemantics &APFloatBase::PPCDoubleDouble() { - return semPPCDoubleDouble; - } + const fltSemantics &APFloatBase::Bogus() { return semBogus; } constexpr RoundingMode APFloatBase::rmNearestTiesToEven; constexpr RoundingMode APFloatBase::rmTowardPositive; @@ -3353,6 +3355,33 @@ APInt IEEEFloat::convertHalfAPFloatToAPInt() const { (mysignificand & 0x3ff))); } +APInt IEEEFloat::convertFloat8E5M2APFloatToAPInt() const { + assert(semantics == (const llvm::fltSemantics *)&semFloat8E5M2); + assert(partCount() == 1); + + uint32_t myexponent, mysignificand; + + if (isFiniteNonZero()) { + myexponent = exponent + 15; // bias + mysignificand = (uint32_t)*significandParts(); + if (myexponent == 1 && !(mysignificand & 0x4)) + myexponent = 0; // denormal + } else if (category == fcZero) { + myexponent = 0; + mysignificand = 0; + } else if (category == fcInfinity) { + myexponent = 0x1f; + mysignificand = 0; + } else { + assert(category == fcNaN && "Unknown category!"); + myexponent = 0x1f; + mysignificand = (uint32_t)*significandParts(); + } + + return APInt(8, (((sign & 1) << 7) | ((myexponent & 0x1f) << 2) | + (mysignificand & 0x3))); +} + // This function creates an APInt that is just a bit map of the floating // point constant as it would appear in memory. It is not a conversion, // and treating the result as a normal integer is unlikely to be useful. @@ -3376,6 +3405,9 @@ APInt IEEEFloat::bitcastToAPInt() const { if (semantics == (const llvm::fltSemantics *)&semPPCDoubleDoubleLegacy) return convertPPCDoubleDoubleAPFloatToAPInt(); + if (semantics == (const llvm::fltSemantics *)&semFloat8E5M2) + return convertFloat8E5M2APFloatToAPInt(); + assert(semantics == (const llvm::fltSemantics*)&semX87DoubleExtended && "unknown format!"); return convertF80LongDoubleAPFloatToAPInt(); @@ -3603,6 +3635,34 @@ void IEEEFloat::initFromHalfAPInt(const APInt &api) { } } +void IEEEFloat::initFromFloat8E5M2APInt(const APInt &api) { + uint32_t i = (uint32_t)*api.getRawData(); + uint32_t myexponent = (i >> 2) & 0x1f; + uint32_t mysignificand = i & 0x3; + + initialize(&semFloat8E5M2); + assert(partCount() == 1); + + sign = i >> 7; + if (myexponent == 0 && mysignificand == 0) { + makeZero(sign); + } else if (myexponent == 0x1f && mysignificand == 0) { + makeInf(sign); + } else if (myexponent == 0x1f && mysignificand != 0) { + category = fcNaN; + exponent = exponentNaN(); + *significandParts() = mysignificand; + } else { + category = fcNormal; + exponent = myexponent - 15; // bias + *significandParts() = mysignificand; + if (myexponent == 0) // denormal + exponent = -14; + else + *significandParts() |= 0x4; // integer bit + } +} + /// Treat api as containing the bits of a floating point number. Currently /// we infer the floating point type from the size of the APInt. The /// isIEEE argument distinguishes between PPC128 and IEEE128 (not meaningful @@ -3623,6 +3683,8 @@ void IEEEFloat::initFromAPInt(const fltSemantics *Sem, const APInt &api) { return initFromQuadrupleAPInt(api); if (Sem == &semPPCDoubleDoubleLegacy) return initFromPPCDoubleDoubleAPInt(api); + if (Sem == &semFloat8E5M2) + return initFromFloat8E5M2APInt(api); llvm_unreachable(nullptr); } |