Skip to content

Commit 5547ff6

Browse files
committed
Use strides-based representation instead of v_to_ps-based
* still need to update einsum and view, update tests, debug, and maybe delete some unused functions.
1 parent 1c73416 commit 5547ff6

5 files changed

Lines changed: 118 additions & 320 deletions

File tree

src/torchjd/sparse/_aten_function_overrides/backward.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def threshold_backward_default(
1010
) -> StructuredSparseTensor:
1111
new_physical = aten.threshold_backward.default(grad_output.physical, self, threshold)
1212

13-
return StructuredSparseTensor(new_physical, grad_output.v_to_ps)
13+
return StructuredSparseTensor(new_physical, grad_output.strides)
1414

1515

1616
@impl(aten.hardtanh_backward.default)
@@ -24,7 +24,7 @@ def hardtanh_backward_default(
2424
raise NotImplementedError()
2525

2626
new_physical = aten.hardtanh_backward.default(grad_output.physical, self, min_val, max_val)
27-
return StructuredSparseTensor(new_physical, grad_output.v_to_ps)
27+
return StructuredSparseTensor(new_physical, grad_output.strides)
2828

2929

3030
@impl(aten.hardswish_backward.default)
@@ -33,4 +33,4 @@ def hardswish_backward_default(grad_output: StructuredSparseTensor, self: Tensor
3333
raise NotImplementedError()
3434

3535
new_physical = aten.hardswish_backward.default(grad_output.physical, self)
36-
return StructuredSparseTensor(new_physical, grad_output.v_to_ps)
36+
return StructuredSparseTensor(new_physical, grad_output.strides)

src/torchjd/sparse/_aten_function_overrides/einsum.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from torchjd.sparse._structured_sparse_tensor import (
66
StructuredSparseTensor,
77
impl,
8-
p_to_vs_from_v_to_ps,
98
to_most_efficient_tensor,
109
to_structured_sparse_tensor,
1110
)
@@ -84,7 +83,7 @@ def group_indices(indices: list[tuple[int, int]]) -> None:
8483
)
8584
else:
8685
indices_to_n_pdims[index] = len(pdims)
87-
p_to_vs = p_to_vs_from_v_to_ps(t.v_to_ps)
86+
p_to_vs = ... # p_to_vs_from_v_to_ps(t.v_to_ps)
8887
for indices_ in p_to_vs:
8988
# elements in indices[indices_] map to the same dimension, they should be clustered
9089
# together
@@ -165,7 +164,7 @@ def mul_Tensor(t1: Tensor | int | float, t2: Tensor | int | float) -> Tensor:
165164
@impl(aten.div.Tensor)
166165
def div_Tensor(t1: Tensor | int | float, t2: Tensor | int | float) -> Tensor:
167166
t1_, t2_ = prepare_for_elementwise_op(t1, t2)
168-
t2_ = StructuredSparseTensor(1.0 / t2_.physical, t2_.v_to_ps)
167+
t2_ = StructuredSparseTensor(1.0 / t2_.physical, t2_.strides)
169168
all_dims = list(range(t1_.ndim))
170169
return einsum((t1_, all_dims), (t2_, all_dims), output=all_dims)
171170

@@ -177,7 +176,7 @@ def mul_Scalar(t: StructuredSparseTensor, scalar) -> StructuredSparseTensor:
177176

178177
assert isinstance(t, StructuredSparseTensor)
179178
new_physical = aten.mul.Scalar(t.physical, scalar)
180-
return StructuredSparseTensor(new_physical, t.v_to_ps)
179+
return StructuredSparseTensor(new_physical, t.strides)
181180

182181

183182
@impl(aten.add.Tensor)
@@ -186,9 +185,9 @@ def add_Tensor(
186185
) -> StructuredSparseTensor:
187186
t1_, t2_ = prepare_for_elementwise_op(t1, t2)
188187

189-
if t1_.v_to_ps == t2_.v_to_ps:
188+
if torch.equal(t1_.strides, t2_.strides):
190189
new_physical = t1_.physical + t2_.physical * alpha
191-
return StructuredSparseTensor(new_physical, t1_.v_to_ps)
190+
return StructuredSparseTensor(new_physical, t1_.strides)
192191
else:
193192
raise NotImplementedError()
194193

src/torchjd/sparse/_aten_function_overrides/pointwise.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def _override_pointwise(op):
7171
@impl(op)
7272
def func_(t: StructuredSparseTensor) -> StructuredSparseTensor:
7373
assert isinstance(t, StructuredSparseTensor)
74-
return StructuredSparseTensor(op(t.physical), t.v_to_ps)
74+
return StructuredSparseTensor(op(t.physical), t.strides)
7575

7676
return func_
7777

@@ -100,7 +100,7 @@ def pow_Tensor_Scalar(t: StructuredSparseTensor, exponent: float) -> StructuredS
100100
return aten.pow.Tensor_Scalar(t.to_dense(), exponent)
101101

102102
new_physical = aten.pow.Tensor_Scalar(t.physical, exponent)
103-
return StructuredSparseTensor(new_physical, t.v_to_ps)
103+
return StructuredSparseTensor(new_physical, t.strides)
104104

105105

106106
# Somehow there's no pow_.Tensor_Scalar and pow_.Scalar takes tensor and scalar.
@@ -122,4 +122,4 @@ def div_Scalar(t: StructuredSparseTensor, divisor: float) -> StructuredSparseTen
122122
assert isinstance(t, StructuredSparseTensor)
123123

124124
new_physical = aten.div.Scalar(t.physical, divisor)
125-
return StructuredSparseTensor(new_physical, t.v_to_ps)
125+
return StructuredSparseTensor(new_physical, t.strides)

src/torchjd/sparse/_aten_function_overrides/shape.py

Lines changed: 35 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
from typing import cast
33

44
import torch
5-
from torch import Tensor, tensor
5+
from torch import Tensor, arange, tensor
66
from torch.ops import aten # type: ignore
77

88
from torchjd.sparse._structured_sparse_tensor import (
99
StructuredSparseTensor,
10-
encode_v_to_ps,
11-
fix_dim_encoding,
1210
impl,
1311
print_fallback,
1412
to_most_efficient_tensor,
@@ -136,10 +134,10 @@ def unsqueeze_default(t: StructuredSparseTensor, dim: int) -> StructuredSparseTe
136134
if dim < 0:
137135
dim = t.ndim + dim + 1
138136

139-
new_v_to_ps = [p for p in t.v_to_ps] # Deepcopy the list to not modify the original v_to_ps
140-
new_v_to_ps.insert(dim, [])
141-
142-
return StructuredSparseTensor(t.physical, new_v_to_ps)
137+
new_strides = torch.concatenate(
138+
[t.strides[:dim], torch.zeros(1, t.strides.shape[1], dtype=torch.int64), t.strides[dim:]]
139+
)
140+
return StructuredSparseTensor(t.physical, new_strides)
143141

144142

145143
@impl(aten.squeeze.dims)
@@ -153,17 +151,15 @@ def squeeze_dims(t: StructuredSparseTensor, dims: list[int] | int | None) -> Ten
153151
else:
154152
excluded = set(dims)
155153

156-
new_v_to_ps = [pdims for i, pdims in enumerate(t.v_to_ps) if i not in excluded]
157-
158-
return to_most_efficient_tensor(t.physical, new_v_to_ps)
154+
is_row_kept = [i not in excluded for i in range(t.ndim)]
155+
new_strides = t.strides[is_row_kept]
156+
return to_most_efficient_tensor(t.physical, new_strides)
159157

160158

161159
@impl(aten.permute.default)
162160
def permute_default(t: StructuredSparseTensor, dims: list[int]) -> StructuredSparseTensor:
163-
new_v_to_ps = [t.v_to_ps[d] for d in dims]
164-
165-
new_physical, new_v_to_ps = fix_dim_encoding(t.physical, new_v_to_ps)
166-
return StructuredSparseTensor(new_physical, new_v_to_ps)
161+
new_strides = t.strides[torch.tensor(dims)]
162+
return StructuredSparseTensor(t.physical, new_strides)
167163

168164

169165
@impl(aten.cat.default)
@@ -197,25 +193,20 @@ def cat_default(tensors: list[Tensor], dim: int) -> Tensor:
197193
# Add a physical dimension pdim on which we can concatenate the physicals such that this
198194
# translates into a concatenation of the virtuals on virtual dimension dim.
199195

200-
# Stride-based representation:
201-
# new_stride_column = torch.zeros(ref_tensor.ndim, dtype=torch.int)
202-
# new_stride_column[dim] = ref_virtual_dim_size
203-
204196
pdim = ref_tensor.physical.ndim
205-
new_v_to_ps = [[d for d in pdims] for pdims in ref_tensor.v_to_ps]
206-
new_v_to_ps[dim] = [pdim] + new_v_to_ps[dim]
207-
new_v_to_ps, destination = encode_v_to_ps(new_v_to_ps)
208-
source = list(range(len(destination)))
209-
physicals = [t.physical.unsqueeze(-1).movedim(source, destination) for t in tensors_]
197+
physicals = [t.physical.unsqueeze(-1) for t in tensors_]
198+
new_stride_column = torch.zeros(ref_tensor.ndim, 1, dtype=torch.int64)
199+
new_stride_column[dim, 0] = ref_virtual_dim_size
200+
new_strides = torch.concatenate([ref_tensor.strides, new_stride_column], dim=1)
210201
else:
211202
# Such a physical dimension already exists. Note that an alternative implementation would be
212203
# to simply always add the physical dimension, and squash it if it ends up being not needed.
213204
physicals = [t.physical for t in tensors_]
214205
pdim = indices[0][0]
215-
new_v_to_ps = ref_tensor.v_to_ps
206+
new_strides = ref_tensor.strides
216207

217208
new_physical = aten.cat.default(physicals, dim=pdim)
218-
return StructuredSparseTensor(new_physical, new_v_to_ps)
209+
return StructuredSparseTensor(new_physical, new_strides)
219210

220211

221212
@impl(aten.expand.default)
@@ -225,32 +216,32 @@ def expand_default(t: StructuredSparseTensor, sizes: list[int]) -> StructuredSpa
225216
assert isinstance(sizes, list)
226217
assert len(sizes) >= t.ndim
227218

219+
# Add as many dimensions as needed at the beginning of the tensor (as torch.expand works)
228220
for _ in range(len(sizes) - t.ndim):
229221
t = t.unsqueeze(0)
230222

231-
assert len(sizes) == t.ndim
232-
223+
# Try to expand each dimension to its new size
233224
new_physical = t.physical
234-
new_v_to_ps = t.v_to_ps
235-
n_added_physical_dims = 0
236-
for dim, (ps, orig_size, new_size) in enumerate(zip(t.v_to_ps, t.shape, sizes, strict=True)):
237-
if len(ps) > 0 and orig_size != new_size and new_size != -1:
225+
new_strides = t.strides
226+
for d, (vstride, orig_size, new_size) in enumerate(zip(t.strides, t.shape, sizes, strict=True)):
227+
if vstride.sum() > 0 and orig_size != new_size and new_size != -1:
238228
raise ValueError(
239-
f"Cannot expand dim {dim} of size != 1. Found size {orig_size} and target size "
229+
f"Cannot expand dim {d} of size != 1. Found size {orig_size} and target size "
240230
f"{new_size}."
241231
)
242232

243-
if len(ps) == 0 and new_size != 1 and new_size != -1:
233+
if vstride.sum() == 0 and new_size != 1 and new_size != -1:
244234
# Add a dimension of size new_size at the end of the physical tensor.
245235
new_physical_shape = list(new_physical.shape) + [new_size]
246236
new_physical = new_physical.unsqueeze(-1).expand(new_physical_shape)
247-
new_v_to_ps[dim] = [t.physical.ndim + n_added_physical_dims]
248-
n_added_physical_dims += 1
249237

250-
new_v_to_ps, destination = encode_v_to_ps(new_v_to_ps)
251-
new_physical = new_physical.movedim(list(range(len(destination))), destination)
238+
# Make this new physical dimension have a stride of 1 at virtual dimension d and 0 at
239+
# every other virtual dimension
240+
new_stride_column = torch.zeros(t.ndim, 1, dtype=torch.int64)
241+
new_stride_column[d, 0] = 1
242+
new_strides = torch.cat([new_strides, new_stride_column], dim=1)
252243

253-
return StructuredSparseTensor(new_physical, new_v_to_ps)
244+
return StructuredSparseTensor(new_physical, new_strides)
254245

255246

256247
@impl(aten.broadcast_tensors.default)
@@ -280,49 +271,14 @@ def broadcast_tensors_default(tensors: list[Tensor]) -> tuple[Tensor, Tensor]:
280271
return aten.expand.default(t1, new_shape), aten.expand.default(t2, new_shape)
281272

282273

283-
@impl(aten.slice.Tensor)
284-
def slice_Tensor(
285-
t: StructuredSparseTensor, dim: int, start: int | None, end: int | None, step: int = 1
286-
) -> StructuredSparseTensor:
287-
assert isinstance(t, StructuredSparseTensor)
288-
289-
physical_dims = t.v_to_ps[dim]
290-
291-
if len(physical_dims) > 1:
292-
raise ValueError(
293-
"Cannot yet slice virtual dim corresponding to several physical dims.\n"
294-
f"{t.debug_info()}\n"
295-
f"dim={dim}, start={start}, end={end}, step={step}."
296-
)
297-
elif len(physical_dims) == 0:
298-
# Trying to slice a virtual dim of size 1.
299-
# Either
300-
# - the element of this dim is included in the slice: keep it as it is
301-
# - it's not included in the slice (e.g. end<=start): we would end up with a size of 0 on
302-
# that dimension, so we'd need to add a dimension of size 0 to the physical. This is not
303-
# implemented yet.
304-
start_ = start if start is not None else 0
305-
end_ = end if end is not None else 1
306-
if end_ <= start_: # TODO: the condition might be a bit more complex if step != 1
307-
raise NotImplementedError(
308-
"Slicing of dimension of size 1 leading to dimension of size 0 not implemented yet."
309-
)
310-
else:
311-
new_physical = t.physical
312-
else:
313-
physical_dim = physical_dims[0]
314-
new_physical = aten.slice.Tensor(t.physical, physical_dim, start, end, step)
315-
316-
return StructuredSparseTensor(new_physical, t.v_to_ps)
317-
318-
319274
@impl(aten.transpose.int)
320275
def transpose_int(t: StructuredSparseTensor, dim0: int, dim1: int) -> StructuredSparseTensor:
321276
assert isinstance(t, StructuredSparseTensor)
277+
return StructuredSparseTensor(t.physical, _swap_rows(t.strides, dim0, dim1))
322278

323-
new_v_to_ps = [dims for dims in t.v_to_ps]
324-
new_v_to_ps[dim0] = t.v_to_ps[dim1]
325-
new_v_to_ps[dim1] = t.v_to_ps[dim0]
326279

327-
new_physical, new_v_to_ps = fix_dim_encoding(t.physical, new_v_to_ps)
328-
return StructuredSparseTensor(new_physical, new_v_to_ps)
280+
def _swap_rows(matrix: Tensor, c0: int, c1: int) -> Tensor:
281+
index = arange(matrix.shape[0])
282+
index[c0] = c1
283+
index[c1] = c0
284+
return matrix[index]

0 commit comments

Comments
 (0)