Skip to content

Commit 6e94a89

Browse files
committed
Add initial implementation of view_default
1 parent 51a579a commit 6e94a89

1 file changed

Lines changed: 42 additions & 53 deletions

File tree

  • src/torchjd/sparse/_aten_function_overrides

src/torchjd/sparse/_aten_function_overrides/shape.py

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import operator
2+
from itertools import accumulate
13
from math import prod
24
from typing import cast
35

@@ -16,66 +18,53 @@
1618

1719
@impl(aten.view.default)
1820
def view_default(t: StructuredSparseTensor, shape: list[int]) -> Tensor:
21+
"""
22+
The main condition that we want to respect is that the indexing in the flattened virtual
23+
tensor should remain the same before and after the reshape, i.e.
24+
25+
c.T S = c'.T S' (1)
26+
where:
27+
* c is the reversed vector of cumulative physical shape before the reshape, i.e.
28+
c.T = [prod(t.shape[1:]), prod(t.shape[2:]), ..., t.shape[-1], 1]
29+
* c' is the same thing but after the reshape, i.e.
30+
c'.T = [prod(shape[1:]), prod(shape[2:]), ..., shape[-1], 1]
31+
* S is the original matrix of strides (t.strides)
32+
* S' is the matrix of strides after reshaping.
33+
34+
For u, v in Z^m and c in Z, say that u ≡ v (mod c) if u_i ≡ v_i (mod c) for all i.
35+
Note that c'.T S' ≡ S'[-1] (mod shape[-1])
36+
So if we set S'[-1] = c.T S % shape[-1], we have c.T S ≡ c'.T S' (mod shape[-1])
37+
38+
(c'.T S' - S'[-1]) // shape[-1] ≡ S'[-1] (mod shape[-1])
39+
...
40+
"""
41+
1942
assert isinstance(t, StructuredSparseTensor)
2043

2144
shape = infer_shape(shape, t.numel())
2245

2346
if prod(shape) != t.numel():
2447
raise ValueError(f"shape '{shape}' is invalid for input of size {t.numel()}")
2548

26-
new_v_to_ps = []
27-
idx = 0
28-
flat_v_to_ps = [dim for dims in t.v_to_ps for dim in dims]
29-
new_physical = t.physical
30-
for s in shape:
31-
group = []
32-
current_size = 1
33-
34-
while current_size < s:
35-
if idx >= len(flat_v_to_ps):
36-
# TODO: I don't think this can happen, need to review and remove if I'm right.
37-
raise ValueError()
38-
39-
pdim = flat_v_to_ps[idx]
40-
pdim_size = new_physical.shape[pdim]
41-
42-
if current_size * pdim_size > s:
43-
# Need to split physical dimension
44-
if s % current_size != 0:
45-
raise ValueError("Can't split physical dimension")
46-
47-
new_pdim_first_dim_size = s // current_size
48-
49-
if pdim_size % new_pdim_first_dim_size != 0:
50-
raise ValueError("Can't split physical dimension")
51-
52-
new_pdim_shape = [new_pdim_first_dim_size, pdim_size // new_pdim_first_dim_size]
53-
new_physical, new_encoding = unsquash_pdim(new_physical, pdim, new_pdim_shape)
54-
55-
new_v_to_ps = [
56-
[new_d for d in dims for new_d in new_encoding[d]] for dims in new_v_to_ps
57-
]
58-
# A bit of a weird trick here. We want to re-encode flat_v_to_ps according to
59-
# new_encoding. However, re-encoding elements before idx would potentially change
60-
# the length of the list before idx, so idx would not have the right value anymore.
61-
# Since we don't need the elements of flat_v_to_ps that are before idx anyway, we
62-
# just get rid of them and re-encode flat_v_to_ps[idx:] instead, and reset idx to 0
63-
# to say that we're back at the beginning of this new list.
64-
flat_v_to_ps = [new_d for d in flat_v_to_ps[idx:] for new_d in new_encoding[d]]
65-
idx = 0
66-
67-
group.append(pdim)
68-
current_size *= new_physical.shape[pdim]
69-
idx += 1
70-
71-
new_v_to_ps.append(group)
72-
73-
if idx != len(flat_v_to_ps):
74-
raise ValueError(f"idx != len(flat_v_to_ps). {idx}; {flat_v_to_ps}; {shape}; {t.v_to_ps}")
75-
76-
# The above code does not handle physical dimension squashing, so the physical is not
77-
# necessarily maximally squashed at this point, so we need the safe constructor.
78-
return to_most_efficient_tensor(new_physical, new_v_to_ps)
49+
S = t.strides
50+
vshape = list(t.shape)
51+
c = _reverse_cumulative_product(vshape)
52+
remaining_cT_S = c @ S
53+
54+
stride_rows = list[Tensor]()
55+
for modulo in shape[::-1]:
56+
stride_row = remaining_cT_S % modulo
57+
stride_rows.append(stride_row)
58+
remaining_cT_S = (remaining_cT_S - stride_row) // modulo
59+
# I think we could skip the - stride_row because the floor div will handle it for us, but it
60+
# will make code harder to understand.
61+
62+
new_strides = torch.stack(stride_rows, dim=0)
63+
return to_most_efficient_tensor(t.physical, new_strides)
64+
65+
66+
def _reverse_cumulative_product(values: list[int]) -> Tensor:
67+
return tensor(list(accumulate((values[1:] + [1])[::-1], operator.mul))[::-1])
7968

8069

8170
def infer_shape(shape: list[int], numel: int) -> list[int]:

0 commit comments

Comments
 (0)