main
py 354 lines 12.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import functools
5 import struct
6 from dataclasses import dataclass
7 from enum import Enum
8
9 _SCALAR_TYPES_ID_MAP = {}
10
11
12 # Mirrors enum in `core/scalar_type.hpp`
13 class NanRepr(Enum):
14 NONE = 0 # nans are not supported
15 IEEE_754 = 1 # nans are: Exp all 1s, mantissa not all 0s
16 EXTD_RANGE_MAX_MIN = 2 # nans are: Exp all 1s, mantissa all 1s
17
18
19 # This ScalarType class is a parallel implementation of the C++ ScalarType
20 # class found in csrc/core/scalar_type.hpp. These two classes should be kept
21 # in sync until the inductor fully supports custom C++ classes.
22 @dataclass(frozen=True)
23 class ScalarType:
24 """ScalarType can represent a wide range of floating point and integer
25 types, in particular it can be used to represent sub-byte data types
26 (something that torch.dtype currently does not support). It is also
27 capable of representing types with a bias, i.e.:
28 `stored_value = value + bias`,
29 this is useful for quantized types (e.g. standard GPTQ 4bit uses a bias
30 of 8). The implementation for this class can be found in
31 csrc/core/scalar_type.hpp, these type signatures should be kept in sync
32 with that file.
33 """
34
35 exponent: int
36 """
37 Number of bits in the exponent if this is a floating point type
38 (zero if this an integer type)
39 """
40
41 mantissa: int
42 """
43 Number of bits in the mantissa if this is a floating point type,
44 or the number bits representing an integer excluding the sign bit if
45 this an integer type.
46 """
47
48 signed: bool
49 "If the type is signed (i.e. has a sign bit)"
50
51 bias: int
52 """
53 bias used to encode the values in this scalar type
54 (value = stored_value - bias, default 0) for example if we store the
55 type as an unsigned integer with a bias of 128 then the value 0 will be
56 stored as 128 and -1 will be stored as 127 and 1 will be stored as 129.
57 """
58
59 _finite_values_only: bool = False
60 """
61 Private: if infs are supported, used `has_infs()` instead.
62 """
63
64 nan_repr: NanRepr = NanRepr.IEEE_754
65 """
66 How NaNs are represent in this scalar type, returns NanRepr value.
67 (not applicable for integer types)
68 """
69
70 def _floating_point_max_int(self) -> int:
71 assert self.mantissa <= 52 and self.exponent <= 11, (
72 f"Cannot represent max/min as a double for type {self.__str__()}"
73 )
74
75 max_mantissa = (1 << self.mantissa) - 1
76 if self.nan_repr == NanRepr.EXTD_RANGE_MAX_MIN:
77 max_mantissa = max_mantissa - 1
78
79 max_exponent = (1 << self.exponent) - 2
80 if self.nan_repr == NanRepr.EXTD_RANGE_MAX_MIN or self.nan_repr == NanRepr.NONE:
81 assert self.exponent < 11, (
82 f"Cannot represent max/min as a double for type {self.__str__()}"
83 )
84 max_exponent = max_exponent + 1
85
86 # adjust the exponent to match that of a double
87 # for now we assume the exponent bias is the standard 2^(e-1) -1, (where
88 # e is the exponent bits), there is some precedent for non-standard
89 # biases, example `float8_e4m3b11fnuz` here:
90 # https://github.com/jax-ml/ml_dtypes but to avoid premature over
91 # complication we are just assuming the standard exponent bias until
92 # there is a need to support non-standard biases
93 exponent_bias = (1 << (self.exponent - 1)) - 1
94 exponent_bias_double = (1 << 10) - 1 # double e = 11
95
96 max_exponent_double = max_exponent - exponent_bias + exponent_bias_double
97
98 # shift the mantissa and exponent into the proper positions for an
99 # IEEE double and bitwise-or them together.
100 return (max_mantissa << (52 - self.mantissa)) | (max_exponent_double << 52)
101
102 def _floating_point_max(self) -> float:
103 double_raw = self._floating_point_max_int()
104 return struct.unpack("!d", struct.pack("!Q", double_raw))[0]
105
106 def _raw_max(self) -> int | float:
107 if self.is_floating_point():
108 return self._floating_point_max()
109 else:
110 assert self.size_bits < 64 or self.size_bits == 64 and self.is_signed(), (
111 "Cannot represent max as an int"
112 )
113 return (1 << self.mantissa) - 1
114
115 def _raw_min(self) -> int | float:
116 if self.is_floating_point():
117 assert self.is_signed(), (
118 "We currently assume all floating point types are signed"
119 )
120 sign_bit_double = 1 << 63
121
122 max_raw = self._floating_point_max_int()
123 min_raw = max_raw | sign_bit_double
124 return struct.unpack("!d", struct.pack("!Q", min_raw))[0]
125 else:
126 assert not self.is_signed() or self.size_bits <= 64, (
127 "Cannot represent min as a int64_t"
128 )
129
130 if self.is_signed():
131 return -(1 << (self.size_bits - 1))
132 else:
133 return 0
134
135 @functools.cached_property
136 def id(self) -> int:
137 """Convert the ScalarType to an int which can be passed to pytorch custom
138 ops. This layout of the int must be kept in sync with the C++
139 ScalarType's from_id method.
140 """
141 val = 0
142 offset = 0
143
144 def or_and_advance(member, bit_width):
145 nonlocal val
146 nonlocal offset
147 bit_mask = (1 << bit_width) - 1
148 val = val | (int(member) & bit_mask) << offset
149 offset = offset + bit_width
150
151 or_and_advance(self.exponent, 8)
152 or_and_advance(self.mantissa, 8)
153 or_and_advance(self.signed, 1)
154 or_and_advance(self.bias, 32)
155 or_and_advance(self._finite_values_only, 1)
156 or_and_advance(self.nan_repr.value, 8)
157
158 assert offset <= 64, f"ScalarType fields too big {offset} to fit into an int64"
159
160 _SCALAR_TYPES_ID_MAP[val] = self
161
162 return val
163
164 @property
165 def size_bits(self) -> int:
166 return self.exponent + self.mantissa + int(self.signed)
167
168 def min(self) -> int | float:
169 """Min representable value for this scalar type.
170 (accounting for bias if there is one)
171 """
172 return self._raw_min() - self.bias
173
174 def max(self) -> int | float:
175 """Max representable value for this scalar type.
176 (accounting for bias if there is one)
177 """
178 return self._raw_max() - self.bias
179
180 def is_signed(self) -> bool:
181 """If the type is signed (i.e. has a sign bit), same as `signed`
182 added for consistency with:
183 https://pytorch.org/docs/stable/generated/torch.Tensor.is_signed.html
184 """
185 return self.signed
186
187 def is_floating_point(self) -> bool:
188 """If the type is a floating point type."""
189 return self.exponent != 0
190
191 def is_integer(self) -> bool:
192 """If the type is an integer type."""
193 return self.exponent == 0
194
195 def has_bias(self) -> bool:
196 """If the type has a non-zero bias."""
197 return self.bias != 0
198
199 def has_infs(self) -> bool:
200 """If the type is floating point and supports infinity."""
201 return not self._finite_values_only
202
203 def has_nans(self) -> bool:
204 return self.nan_repr != NanRepr.NONE.value
205
206 def is_ieee_754(self) -> bool:
207 """If the type is a floating point type that follows IEEE 754
208 conventions
209 """
210 return self.nan_repr == NanRepr.IEEE_754.value and not self._finite_values_only
211
212 def __str__(self) -> str:
213 """Naming generally follows: https://github.com/jax-ml/ml_dtypes
214 for floating point types (leading f) the scheme is:
215 `float<size_bits>_e<exponent_bits>m<mantissa_bits>[flags]`
216 flags:
217 - no-flags: means it follows IEEE 754 conventions
218 - f: means finite values only (no infinities)
219 - n: means nans are supported (non-standard encoding)
220 for integer types the scheme is:
221 `[u]int<size_bits>[b<bias>]`
222 - if bias is not present it means its zero
223 """
224 if self.is_floating_point():
225 ret = (
226 "float"
227 + str(self.size_bits)
228 + "_e"
229 + str(self.exponent)
230 + "m"
231 + str(self.mantissa)
232 )
233
234 if not self.is_ieee_754():
235 if self._finite_values_only:
236 ret = ret + "f"
237 if self.nan_repr != NanRepr.NONE:
238 ret = ret + "n"
239
240 return ret
241 else:
242 ret = ("int" if self.is_signed() else "uint") + str(self.size_bits)
243 if self.has_bias():
244 ret = ret + "b" + str(self.bias)
245 return ret
246
247 def __repr__(self) -> str:
248 return "ScalarType." + self.__str__()
249
250 # __len__ needs to be defined (and has to throw TypeError) for pytorch's
251 # opcheck to work.
252 def __len__(self) -> int:
253 raise TypeError
254
255 #
256 # Convenience Constructors
257 #
258
259 @classmethod
260 def int_(cls, size_bits: int, bias: int | None) -> "ScalarType":
261 """Create a signed integer scalar type (size_bits includes sign-bit)."""
262 ret = cls(0, size_bits - 1, True, bias if bias else 0)
263 ret.id # noqa B018: make sure the id is cached
264 return ret
265
266 @classmethod
267 def uint(cls, size_bits: int, bias: int | None) -> "ScalarType":
268 """Create an unsigned integer scalar type."""
269 ret = cls(0, size_bits, False, bias if bias else 0)
270 ret.id # noqa B018: make sure the id is cached
271 return ret
272
273 @classmethod
274 def float_IEEE754(cls, exponent: int, mantissa: int) -> "ScalarType":
275 """Create a standard floating point type
276 (i.e. follows IEEE 754 conventions).
277 """
278 assert mantissa > 0 and exponent > 0
279 ret = cls(exponent, mantissa, True, 0)
280 ret.id # noqa B018: make sure the id is cached
281 return ret
282
283 @classmethod
284 def float_(
285 cls, exponent: int, mantissa: int, finite_values_only: bool, nan_repr: NanRepr
286 ) -> "ScalarType":
287 """Create a non-standard floating point type
288 (i.e. does not follow IEEE 754 conventions).
289 """
290 assert mantissa > 0 and exponent > 0
291 assert nan_repr != NanRepr.IEEE_754, (
292 "use `float_IEEE754` constructor for floating point types that "
293 "follow IEEE 754 conventions"
294 )
295 ret = cls(exponent, mantissa, True, 0, finite_values_only, nan_repr)
296 ret.id # noqa B018: make sure the id is cached
297 return ret
298
299 @classmethod
300 def from_id(cls, scalar_type_id: int):
301 if scalar_type_id not in _SCALAR_TYPES_ID_MAP:
302 raise ValueError(f"scalar_type_id {scalar_type_id} doesn't exists.")
303 return _SCALAR_TYPES_ID_MAP[scalar_type_id]
304
305
306 # naming generally follows: https://github.com/jax-ml/ml_dtypes
307 # for floating point types (leading f) the scheme is:
308 # `float<size_bits>_e<exponent_bits>m<mantissa_bits>[flags]`
309 # flags:
310 # - no-flags: means it follows IEEE 754 conventions
311 # - f: means finite values only (no infinities)
312 # - n: means nans are supported (non-standard encoding)
313 # for integer types the scheme is:
314 # `[u]int<size_bits>[b<bias>]`
315 # - if bias is not present it means its zero
316
317
318 class scalar_types:
319 uint2 = ScalarType.uint(2, None)
320 uint3 = ScalarType.uint(3, None)
321 int4 = ScalarType.int_(4, None)
322 uint4 = ScalarType.uint(4, None)
323 uint5 = ScalarType.uint(5, None)
324 uint6 = ScalarType.uint(6, None)
325 uint7 = ScalarType.uint(7, None)
326 int8 = ScalarType.int_(8, None)
327 uint8 = ScalarType.uint(8, None)
328 float8_e4m3fn = ScalarType.float_(4, 3, True, NanRepr.EXTD_RANGE_MAX_MIN)
329 float8_e5m2 = ScalarType.float_IEEE754(5, 2)
330 float8_e8m0fnu = ScalarType(8, 0, False, 0, True, NanRepr.EXTD_RANGE_MAX_MIN)
331 float16_e8m7 = ScalarType.float_IEEE754(8, 7)
332 float16_e5m10 = ScalarType.float_IEEE754(5, 10)
333
334 # fp6, https://github.com/usyd-fsalab/fp6_llm/tree/main
335 # and https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
336 float6_e3m2f = ScalarType.float_(3, 2, True, NanRepr.NONE)
337
338 float6_e2m3f = ScalarType.float_(2, 3, True, NanRepr.NONE)
339
340 # fp4, https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
341 float4_e2m1f = ScalarType.float_(2, 1, True, NanRepr.NONE)
342
343 # "gptq" types
344 uint2b2 = ScalarType.uint(2, 2)
345 uint3b4 = ScalarType.uint(3, 4)
346 uint4b8 = ScalarType.uint(4, 8)
347 uint5b16 = ScalarType.uint(5, 16)
348 uint6b32 = ScalarType.uint(6, 32)
349 uint7b64 = ScalarType.uint(7, 64)
350 uint8b128 = ScalarType.uint(8, 128)
351
352 # colloquial names
353 bfloat16 = float16_e8m7
354 float16 = float16_e5m10