Skip to content

Commit 1e17b43

Browse files
authored
Support torch.Tensor.view(dtype) (#2213)
1 parent 4e3f235 commit 1e17b43

7 files changed

Lines changed: 79 additions & 1 deletion

File tree

thunder/core/prims.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ class PrimIDs(Enum):
279279
# Memory access methods
280280
ITEM = auto()
281281
COPY_ = auto()
282+
BITCAST = auto()
282283
#
283284
SINK = auto()
284285

@@ -4334,6 +4335,27 @@ def copy__meta(
43344335
copy_ = make_prim(PrimIDs.COPY_, "copy_", meta=copy__meta, tags=(OpTags.DONT_DCE,))
43354336

43364337

4338+
def bitcast_meta(
4339+
src: TensorProxy,
4340+
dtype: dtypes.dtype,
4341+
) -> TensorProxy:
4342+
shape = list(src.shape)
4343+
src_itemsize = src.dtype.bytes
4344+
dst_itemsize = dtype.bytes
4345+
if src_itemsize != dst_itemsize:
4346+
factor = dst_itemsize / src_itemsize
4347+
if factor > 1:
4348+
utils.check(
4349+
shape[-1] > factor and shape[-1] % factor == 0,
4350+
lambda: f"{src.shape[-1]=} is not divisible by {factor=}. Viewing {src.dtype=} as {dtype=}",
4351+
)
4352+
shape[-1] = int(shape[-1] / factor)
4353+
return TensorProxy(shape=tuple(shape), device=src.device, dtype=dtype)
4354+
4355+
4356+
bitcast = make_prim(PrimIDs.BITCAST, "bitcast", meta=bitcast_meta)
4357+
4358+
43374359
def sink_meta(*args, **kwargs):
43384360
return
43394361

thunder/core/transforms.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,7 @@ def zeros_like(x):
16491649
prims.PrimIDs.FMOD: lambda x, y: (prims.fmod(x, y), (x, y)),
16501650
prims.PrimIDs.COPY_: lambda x, y, grad_enabled: (prims.copy_(x, y, grad_enabled=grad_enabled), tuple()),
16511651
prims.PrimIDs.CLONE: lambda x: (prims.clone(x), tuple()),
1652+
prims.PrimIDs.BITCAST: lambda x, dtype: (prims.bitcast(x, dtype), (x.dtype,)),
16521653
}
16531654

16541655

@@ -1679,6 +1680,7 @@ def zeros_like(x):
16791680
prims.PrimIDs.FMOD: lambda x, y, g: (g, -g * prims.trunc(x / y)),
16801681
prims.PrimIDs.COPY_: lambda g: (g, None),
16811682
prims.PrimIDs.CLONE: lambda g: g,
1683+
prims.PrimIDs.BITCAST: lambda x_dtype, g: (prims.bitcast(g, x_dtype), None),
16821684
}
16831685

16841686

thunder/executors/nvfuserex_impl.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,25 @@ def convert_element_type(
970970

971971
register_supported(PrimIDs.CONVERT_ELEMENT_TYPE, convert_element_type, _convert_element_type_check)
972972

973+
974+
def _bitcast_check(src: TensorProxy, dtype: dtypes.dtype) -> bool:
975+
return (
976+
nvfuser_version() > LooseVersion("0.29.0")
977+
and _convert_element_type_check(src, dtype)
978+
and src.dtype.bytes == dtype.bytes
979+
)
980+
981+
982+
# TODO: Expose bitcast in nvfuser to Python.
983+
# def bitcast(src: TensorProxy, dtype: dtypes.dtype, *, fd: FusionDefinition, lc_to_nv_map: dict):
984+
# nva = getnv(src, fd, lc_to_nv_map)
985+
# nvdtype = lcdtype_to_nvdtype(dtype)
986+
#
987+
# return fd.ops.bitcast(nva, nvdtype)
988+
#
989+
#
990+
# register_supported(PrimIDs.BITCAST, bitcast, _bitcast_check)
991+
973992
#
974993
# Tensor creation operations
975994
#

thunder/executors/torchex.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,14 @@ def _shape_impl(t):
23762376
_register_implementation(prims.shape, shape, checker=_always_executable)
23772377

23782378

2379+
def _bitcast_impl(src, dtype):
2380+
return src.view(dtypes.to_torch_dtype(dtype))
2381+
2382+
2383+
bitcast = ex.register_operator("bitcast", meta=prims.bitcast, fn=_bitcast_impl)
2384+
_register_implementation(prims.bitcast, bitcast, checker=_always_executable)
2385+
2386+
23792387
shallow_copy = ex.register_operator("shallow_copy", meta=prims.shallow_copy, fn=lambda x: x)
23802388
_register_implementation(prims.shallow_copy, shallow_copy, checker=_always_executable)
23812389

thunder/tests/opinfos.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,6 +3513,21 @@ def to_sample_generator(op, device, dtype, requires_grad, **kwargs):
35133513
data_movement_ops.append(to_opinfo)
35143514

35153515

3516+
def view_with_dtype_sample_generator(op, device, dtype, requires_grad, **kwargs):
3517+
make = partial(make_tensor, device=device, dtype=dtype, requires_grad=requires_grad)
3518+
3519+
for dst_dtype in {torch.float32, torch.bfloat16, torch.float64} - {dtype}:
3520+
yield SampleInput(make((8, 8)), dtype)
3521+
3522+
3523+
view_with_dtype_opinfo = OpInfo(
3524+
ltorch.view,
3525+
sample_input_generator=view_with_dtype_sample_generator,
3526+
torch_reference=torch.Tensor.view,
3527+
)
3528+
data_movement_ops.append(view_with_dtype_opinfo)
3529+
3530+
35163531
def cuda_sample_generator(op, device, dtype, requires_grad, **kwargs):
35173532
make = partial(make_tensor, device=device, dtype=dtype, requires_grad=requires_grad)
35183533

thunder/tests/test_grad.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,11 @@ def test_vjp_correctness(op, device, dtype, executor, comp):
408408
# sample.thunder() line below attempts to approximate those conversions
409409
# for non-differentiable arguments like dtypes so that the test will
410410
# execute properly.
411-
sample = sample.thunder() # converts torch.dtype to thunder.dtype
411+
# NOTE: While `convert_element_type` is skipeed as of https://github.com/Lightning-AI/lightning-thunder/pull/2213
412+
# as in https://github.com/Lightning-AI/lightning-thunder/blob/dbf6bad3/thunder/tests/opinfos.py#L3324-L3346,
413+
# `torch.Tensor.view(dtype)` seems to require `torch.dtype` to be kept as is, opposite to `convert_element_type`.
414+
if op.name != "view":
415+
sample = sample.thunder() # converts torch.dtype to thunder.dtype
412416
sample = sample.remove_singularities(op, eps)
413417

414418
flat_op, flat_args, spec = flatten_func(op.op, sample.args, sample.kwargs)

thunder/torch/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,9 +1495,12 @@ def unsqueeze(a: TensorLike, /, dim: int) -> TensorLike:
14951495

14961496

14971497
# TODO Add type annotations
1498+
# TODO(crcrpar): see [Return value of view creation ops]
14981499
@torchsymbol(torch.Tensor.view, is_method=True)
14991500
def view(a: TensorLike, /, *shape) -> TensorLike:
15001501
shape = utils.extract_shape_from_varargs(shape)
1502+
if len(shape) == 1 and isinstance(shape[0], (torch.dtype, dtypes.dtype)):
1503+
return prims.bitcast(a, dtype=to_dtype(shape[0]))
15011504
return reshape(a, shape)
15021505

15031506

@@ -6861,6 +6864,11 @@ def check_overlap_ops():
68616864
_torch_to_thunder_function_map[torch.Tensor.reshape_as],
68626865
}
68636866

6867+
# TODO(crcrpar): [Return value of view creation ops]
6868+
# Review what's more appropriate return value from the ops below.
6869+
# Currently they return a new tensor, which obscures the nature of these ops, i.e.,
6870+
# outputs share underlying storage with inputs. For more stable and improved in-place support
6871+
# it'd be necessary to think about e.g. extending TensorProxy and/or DCE.
68646872
_syms_returning_views: set[Symbol] = {
68656873
diagonal,
68666874
expand,

0 commit comments

Comments
 (0)