Skip to content

Commit 70cc64a

Browse files
committed
* Added support for additional rounding modes
Signed-off-by: Gideon Kassa <gkassa@nvidia.com>
1 parent ab1b538 commit 70cc64a

9 files changed

Lines changed: 291 additions & 21 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added support for specifying IEEE rounding modes in float to float `astype` conversions.

experimental/cuda-lang/test/passes/test_flatten_cfg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_kernel(A):
2323
A[0] = 0
2424

2525
# BEFORE: $[[ITEM:[0-9]+]]: int32 = load_pointer
26-
# BEFORE: $[[ITEM_CASTED:[0-9]+]]: bool_ = tile_astype(x=$[[ITEM]])
26+
# BEFORE: $[[ITEM_CASTED:[0-9]+]]: bool_ = tile_astype(x=$[[ITEM]], rounding_mode=None)
2727
# BEFORE: if(cond=$[[ITEM_CASTED]])
2828
# BEFORE: then
2929
# BEFORE: store_pointer
@@ -37,7 +37,7 @@ def test_kernel(A):
3737

3838
# AFTER: ^entry({{.+}}):
3939
# AFTER: $[[ITEM:[0-9]+]]: int32 = load_pointer
40-
# AFTER: $[[ITEM_CASTED:[0-9]+]]: bool_ = tile_astype(x=$[[ITEM]])
40+
# AFTER: $[[ITEM_CASTED:[0-9]+]]: bool_ = tile_astype(x=$[[ITEM]], rounding_mode=None)
4141
# AFTER: cond_br $[[ITEM_CASTED]]: bool_ ^then() ^else()
4242
# AFTER: ^then():
4343
# AFTER: store_pointer
@@ -87,7 +87,7 @@ def test_kernel(cond1, cond2):
8787
def test_flatten_ifelse_phi_merge():
8888
def test_kernel(A):
8989
# CHECK: $[[ITEM:[0-9]+]]: int32 = load_pointer
90-
# CHECK: $[[ITEM_BOOL:[0-9]+]]: bool_ = tile_astype(x=$[[ITEM]])
90+
# CHECK: $[[ITEM_BOOL:[0-9]+]]: bool_ = tile_astype(x=$[[ITEM]], rounding_mode=None)
9191
# CHECK: cond_br $[[ITEM_BOOL]]: bool_ ^then() ^else()
9292
if A[0]:
9393
# CHECK: $[[ITEM_1:[0-9]+]]: int32 = load_pointer

src/cuda/tile/_ir/arithmetic_ops.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,24 +127,28 @@ def broadcast_to(x: Var[TensorLikeTy], shape: Sequence[int]) -> Var[TensorLikeTy
127127
@dataclass(eq=False)
128128
class TileAsType(Operation, opcode="tile_astype"):
129129
x: Var = operand()
130+
rounding_mode: RoundingMode | None = attribute(default=None)
130131

131132
@override
132133
def generate_bytecode(self, ctx: BytecodeContext) -> bc.Value:
133134
value = ctx.get_value(self.x)
134-
return convert_dtype(ctx, value, ctx.typeof(self.x), ctx.typeof(self.result_var))
135+
136+
return convert_dtype(ctx, value, ctx.typeof(self.x), ctx.typeof(self.result_var),
137+
rounding_mode=self.rounding_mode)
135138

136139

137-
def astype(x: Var[TensorLikeTy], dtype: DType) -> Var[TensorLikeTy]:
140+
def astype(x: Var[TensorLikeTy], dtype: DType, *,
141+
rounding_mode: RoundingMode | None = None) -> Var[TensorLikeTy]:
138142
x_ty = x.get_type()
139143
if x_ty.tensor_dtype() == dtype:
140144
return x
141145

142-
if x.is_constant():
146+
if x.is_constant() and rounding_mode in (None, RoundingMode.RN):
143147
val = numeric_dtype_category(dtype).pytype(x.get_constant())
144148
return strictly_typed_const(val, x.ctx.typing_hooks.get_tensor_like_type(dtype, ()))
145149

146150
result_ty = x.ctx.typing_hooks.get_tensor_like_type(dtype, x_ty.tensor_shape())
147-
return add_operation(TileAsType, result_ty, x=x)
151+
return add_operation(TileAsType, result_ty, x=x, rounding_mode=rounding_mode)
148152

149153

150154
def dtype_constructor(new_dtype: DType, x: Var) -> Var[TensorLikeTy]:

src/cuda/tile/_ir/ops.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
make_aggregate, MemoryEffect, attribute, operand,
2222
BlockRestriction, add_operation_variadic,
2323
)
24+
from cuda.tile._ir.ops_utils import get_ftof_rounding_min_version
2425
from .aggregate_support import unflatten_aggregates
2526
from .arithmetic_ops import reshape, broadcast_to, astype, compare_tensorlike, \
2627
binary_bitwise_tensorlike, bitwise_shift_tensorlike, binary_arithmetic_tensorlike, \
@@ -2810,10 +2811,29 @@ def assert_impl(cond: Var, message: Var) -> None:
28102811

28112812

28122813
@impl(ct.astype)
2813-
def astype_impl(x: Var, dtype: Var) -> Var:
2814-
require_tile_type(x)
2814+
def astype_impl(x: Var, dtype: Var, rounding_mode: Var) -> Var:
2815+
x_ty = require_tile_type(x)
28152816
dtype = require_dtype_spec(dtype)
2816-
return astype(x, dtype)
2817+
rounding_mode = require_optional_constant_enum(rounding_mode, RoundingMode)
2818+
2819+
is_ftof = datatype.is_float(x_ty.tensor_dtype()) and datatype.is_float(dtype)
2820+
if not is_ftof and rounding_mode is not None:
2821+
raise TileTypeError("rounding_mode is only valid for float to float conversions")
2822+
2823+
if x_ty.tensor_dtype() == dtype:
2824+
return x
2825+
2826+
if is_ftof:
2827+
rounding_mode, min_bc_version = get_ftof_rounding_min_version(x_ty.tensor_dtype(),
2828+
dtype, rounding_mode)
2829+
cur_bc_version = Builder.get_current().ir_ctx.tileiras_version
2830+
if min_bc_version is not None and cur_bc_version < min_bc_version:
2831+
raise TileUnsupportedFeatureError(
2832+
f"The requested conversion and rounding_mode require tileiras "
2833+
f"{min_bc_version.as_string()} or later. Current version is "
2834+
f"{cur_bc_version.as_string()}.")
2835+
2836+
return astype(x, dtype, rounding_mode=rounding_mode)
28172837

28182838

28192839
@dataclass(eq=False)

src/cuda/tile/_ir/ops_utils.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import itertools
55
import math
66
from contextlib import contextmanager
7+
from collections import defaultdict
78

89
from dataclasses import dataclass, field
910
from typing import Optional, Tuple, Dict, Any, Sequence, Literal
@@ -123,6 +124,7 @@ def get_default_rounding_mode(opname: Optional[str] = None):
123124
RoundingMode.RZ: bc.RoundingMode.ZERO,
124125
RoundingMode.RM: bc.RoundingMode.NEGATIVE_INF,
125126
RoundingMode.RP: bc.RoundingMode.POSITIVE_INF,
127+
RoundingMode.RA: bc.RoundingMode.NEAREST_AWAY,
126128
RoundingMode.FULL: bc.RoundingMode.FULL,
127129
RoundingMode.APPROX: bc.RoundingMode.APPROX,
128130
RoundingMode.RZI: bc.RoundingMode.NEAREST_INT_TO_ZERO
@@ -225,6 +227,79 @@ def check_shapes_eq(a: TileTy, b: TileTy,
225227
f"got {a.shape} and {b.shape}", loc)
226228

227229

230+
F64 = datatype.float64
231+
F32 = datatype.float32
232+
TF32 = datatype.tfloat32
233+
F16 = datatype.float16
234+
BF16 = datatype.bfloat16
235+
F8E5M2 = datatype.float8_e5m2
236+
F8E8M0FNU = datatype.float8_e8m0fnu
237+
F8E4M3FN = datatype.float8_e4m3fn
238+
F4E2M1FN = datatype.float4_e2m1fn
239+
ALL = (F64, F32, TF32, F16, BF16, F8E5M2, F8E8M0FNU, F8E4M3FN, F4E2M1FN)
240+
B133 = BytecodeVersion.V_13_3
241+
B134 = BytecodeVersion.V_13_4
242+
243+
_FTOF_ROUNDING_ROWS = (
244+
{(i, F64): (RoundingMode.RN, None) for i in ALL},
245+
{(i, F32): (RoundingMode.RN, None) for i in ALL},
246+
{(i, TF32): (RoundingMode.RN, None) for i in ALL},
247+
{(i, BF16): (RoundingMode.RN, None) for i in ALL},
248+
{(i, F16): (RoundingMode.RN, None) for i in ALL},
249+
{(i, F8E4M3FN): (RoundingMode.RN, None) for i in ALL},
250+
{(i, F8E5M2): (RoundingMode.RN, None) for i in ALL},
251+
{(i, F4E2M1FN): (RoundingMode.RN, None) for i in ALL},
252+
253+
{(i, F64): (RoundingMode.RZ, B134) for i in ALL},
254+
{(i, F32): (RoundingMode.RZ, B134) for i in ALL},
255+
{(i, TF32): (RoundingMode.RZ, B134) for i in ALL},
256+
{(i, F16): (RoundingMode.RZ, B134) for i in ALL},
257+
{(i, BF16): (RoundingMode.RZ, B134) for i in ALL},
258+
{(i, F8E8M0FNU): (RoundingMode.RZ, B133) for i in ALL if i not in {F64, F8E5M2, F8E4M3FN}},
259+
{(i, F8E8M0FNU): (RoundingMode.RZ, B134) for i in (F64, F8E5M2, F8E4M3FN)},
260+
261+
{(i, F64): (RoundingMode.RM, B134) for i in ALL},
262+
{(i, F32): (RoundingMode.RM, B134) for i in ALL if i not in {F8E8M0FNU}},
263+
{(i, F16): (RoundingMode.RM, B134) for i in ALL if i not in {F64, F32, TF32, F8E8M0FNU}},
264+
{(i, F64): (RoundingMode.RP, B134) for i in ALL},
265+
{(i, F32): (RoundingMode.RP, B134) for i in ALL if i not in {F8E8M0FNU}},
266+
{(i, F16): (RoundingMode.RP, B134) for i in ALL if i not in {F64, F32, TF32, F8E8M0FNU}},
267+
{(i, F8E8M0FNU): (RoundingMode.RP, B133) for i in ALL if i not in {F64, F8E5M2, F8E4M3FN}},
268+
{(i, F8E8M0FNU): (RoundingMode.RP, B134) for i in (F64, F8E5M2, F8E4M3FN)},
269+
270+
{(i, F64): (RoundingMode.RA, B134) for i in ALL},
271+
{(i, F32): (RoundingMode.RA, B134) for i in ALL if i not in {F64, F8E8M0FNU}},
272+
{(i, TF32): (RoundingMode.RA, B134) for i in ALL if i not in {F64, F8E8M0FNU}},
273+
{(i, F16): (RoundingMode.RA, B134) for i in ALL if i not in {F64, F32, TF32, F8E8M0FNU}}
274+
)
275+
276+
# {(from, to): {RoundingMode_1: BC_Version, RoundingMode_2: BC_Version}}
277+
FTOF_ROUNDING_REGISTRY = defaultdict(dict)
278+
for row in _FTOF_ROUNDING_ROWS:
279+
for from_to, (mode, version) in row.items():
280+
FTOF_ROUNDING_REGISTRY[from_to][mode] = version
281+
282+
283+
def get_ftof_rounding_min_version(from_dtype: datatype.DType, to_dtype: datatype.DType,
284+
rounding_mode: RoundingMode | None
285+
) -> tuple[RoundingMode, BytecodeVersion | None]:
286+
287+
conversion_pair = (from_dtype, to_dtype)
288+
supported = FTOF_ROUNDING_REGISTRY.get(conversion_pair, None)
289+
if supported is None:
290+
raise TileTypeError(f"float conversion from {from_dtype} to {to_dtype} "
291+
"is not supported")
292+
293+
rounding_mode = RoundingMode.RN if rounding_mode is None else rounding_mode
294+
if rounding_mode not in supported:
295+
raise TileTypeError(
296+
f"rounding_mode={rounding_mode} is not supported "
297+
f"for conversion from {from_dtype} to {to_dtype}, "
298+
f"supported rounding modes for this conversion are {tuple(supported.keys())}")
299+
300+
return (rounding_mode, supported[rounding_mode])
301+
302+
228303
class CompareOrdering(Enum):
229304
ORDERED = "ordered"
230305
UNORDERED = "unordered"

src/cuda/tile/_ir2bytecode.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import cuda.tile._bytecode as bc
1919
from cuda.tile._compiler_options import CompilerOptions
2020
from cuda.tile._exception import TileInternalError, TileError, FunctionDesc
21+
from cuda.tile._numeric_semantics import RoundingMode
2122
from cuda.tile._ir.ir import Block, Loc, Var, IRContext
2223
from cuda.tile._ir.ops_utils import (
2324
padding_mode_to_bytecode, rounding_mode_to_bytecode,
@@ -130,7 +131,8 @@ def _flatten_bools(value) -> tuple[bool]:
130131
return sum((_flatten_bools(v) for v in value), start=())
131132

132133

133-
def _get_type_conversion_encoder(from_dtype: Type, to_dtype: Type):
134+
def _get_type_conversion_encoder(from_dtype: Type, to_dtype: Type, *,
135+
rounding_mode: RoundingMode | None = None):
134136

135137
def kind(t):
136138
if datatype.is_float(t):
@@ -140,11 +142,16 @@ def kind(t):
140142
raise TileInternalError(f'Unsupported dtype: {t}')
141143

142144
from_kind, to_kind = kind(from_dtype), kind(to_dtype)
145+
146+
if rounding_mode is not None:
147+
rounding_mode = rounding_mode_to_bytecode[rounding_mode]
148+
else:
149+
rounding_mode = bc.RoundingMode.NEAREST_EVEN
150+
143151
round_to_float = rounding_mode_to_bytecode[get_default_rounding_mode()]
144152
partial = functools.partial
145153
match from_kind, to_kind:
146-
case 'f', 'f': return partial(bc.encode_FToFOp,
147-
rounding_mode=bc.RoundingMode.NEAREST_EVEN)
154+
case 'f', 'f': return partial(bc.encode_FToFOp, rounding_mode=rounding_mode)
148155
case 'f', 'si': return partial(bc.encode_FToIOp,
149156
signedness=bc.Signedness.Signed,
150157
rounding_mode=bc.RoundingMode.NEAREST_INT_TO_ZERO,
@@ -171,8 +178,8 @@ def kind(t):
171178
raise NotImplementedError(f"Type coversion from {from_dtype} to {to_dtype} not implemented")
172179

173180

174-
def convert_dtype(ctx: "BytecodeContext", val: bc.Value,
175-
fromty: Type, toty: Type) -> bc.Value:
181+
def convert_dtype(ctx: "BytecodeContext", val: bc.Value, fromty: Type,
182+
toty: Type, *, rounding_mode: RoundingMode | None = None) -> bc.Value:
176183
from_dtype = fromty.dtype if isinstance(fromty, TileTy) else fromty
177184
to_dtype = toty.dtype if isinstance(toty, TileTy) else toty
178185
toty_id = typeid(ctx.type_table, toty)
@@ -188,7 +195,7 @@ def convert_dtype(ctx: "BytecodeContext", val: bc.Value,
188195
comparison_predicate=bc.ComparisonPredicate.NOT_EQUAL,
189196
signedness=datatype.get_signedness(from_dtype))
190197
else:
191-
encoder = _get_type_conversion_encoder(from_dtype, to_dtype)
198+
encoder = _get_type_conversion_encoder(from_dtype, to_dtype, rounding_mode=rounding_mode)
192199
return encoder(ctx.builder, toty_id, val)
193200

194201

src/cuda/tile/_numeric_semantics.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class RoundingMode(Enum):
1111
"""
1212

1313
RN = "nearest_even"
14-
"""Rounds the nearest (ties to even)."""
14+
"""Round to nearest (ties to even)."""
1515

1616
RZ = "zero"
1717
"""Round towards zero (truncate)."""
@@ -22,6 +22,9 @@ class RoundingMode(Enum):
2222
RP = "positive_inf"
2323
"""Round towards positive infinity."""
2424

25+
RA = "nearest_away"
26+
"""Round to nearest (ties away from zero)."""
27+
2528
FULL = "full"
2629
"""Full precision rounding mode."""
2730

src/cuda/tile/_stub.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,9 @@ def transpose(self, axis0=None, axis1=None) -> "Tile":
605605
"""See :py:func:`transpose`."""
606606
return transpose(self, axis0, axis1)
607607

608-
def astype(self, dtype) -> "Tile":
608+
def astype(self, dtype, *, rounding_mode: Optional[RoundingMode] = None) -> "Tile":
609609
"""See :py:func:`astype`."""
610-
return astype(self, dtype)
610+
return astype(self, dtype, rounding_mode=rounding_mode)
611611

612612
@function
613613
def __index__(self) -> int:
@@ -2559,12 +2559,15 @@ def transpose(x, /, axis0=None, axis1=None) -> Tile:
25592559

25602560

25612561
@stub
2562-
def astype(x, dtype, /) -> Tile:
2562+
def astype(x, dtype, /, *, rounding_mode: Optional[RoundingMode] = None) -> Tile:
25632563
"""Converts a tile to the specified data type.
25642564
25652565
Args:
25662566
x (Tile): input tile.
25672567
dtype (DType): target data type.
2568+
rounding_mode (RoundingMode): optional rounding mode for explicit float to
2569+
float conversions. Default is round to nearest
2570+
with ties to even.
25682571
25692572
Returns:
25702573
Tile:

0 commit comments

Comments
 (0)