Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3f11448
Decouple construction and checking in Transforms
PierreQuinton Mar 28, 2025
5a97c18
add check_keys in test_stack.FakeGradientsTransform
PierreQuinton Mar 28, 2025
c804e5e
Fix tests checking Transforms
PierreQuinton Mar 28, 2025
ea5dfac
remove test_call_checks_keys
PierreQuinton Mar 28, 2025
8adad9d
Add changelog entry
ValerianRey Mar 28, 2025
ee3e099
Extract _create_transform from backward
PierreQuinton Mar 28, 2025
ac2b31b
Test _create_transform
PierreQuinton Mar 28, 2025
4979038
Fix mtl_backward test of create transform and check keys
PierreQuinton Mar 28, 2025
df6c200
Remove _compute, use __call__ instead
ValerianRey Mar 28, 2025
8d3d98d
Same as before for FakeTransforms
ValerianRey Mar 28, 2025
69b2f75
Rename check_keys to check_and_get_keys
ValerianRey Mar 28, 2025
f278a5c
Improve docstring of check_and_get_keys
ValerianRey Mar 28, 2025
316d3dd
Fix tests of check_and_get_keys
ValerianRey Mar 28, 2025
fcd1e3b
Move check and cast to set from __init__ to check_and_get_keys in Select
ValerianRey Mar 28, 2025
b518046
Change test_keys_check into test_check_and_get_keys in Select
ValerianRey Mar 28, 2025
f490089
Add missing tests of check_and_get_keys
ValerianRey Mar 28, 2025
b64665b
revert cast to set in Select
PierreQuinton Mar 28, 2025
ab912a1
Remove check_keys_are
ValerianRey Mar 28, 2025
b744af0
Remove useless casting to set in Select.check_and_get_keys
ValerianRey Mar 28, 2025
4f6e515
Add docstring to mtl_backward._create_transform
ValerianRey Mar 28, 2025
e2b3374
Rename _make_task_transform to _create_task_transform
ValerianRey Mar 28, 2025
ffb6d55
Uniformize test_check_create_transform for backward and mtl_backward …
ValerianRey Mar 28, 2025
e72d3ca
Explicitly give parameter names in calls to _create_transform
ValerianRey Mar 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions src/torchjd/autojac/_transform/_differentiate.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ def _differentiate(self, tensor_outputs: Sequence[Tensor]) -> tuple[Tensor, ...]
tensor_outputs should be.
"""

@property
def required_keys(self) -> set[Tensor]:
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
Comment thread
ValerianRey marked this conversation as resolved.
Outdated
# outputs in the forward direction become inputs in the backward direction, and vice-versa
return set(self.outputs)

@property
def output_keys(self) -> set[Tensor]:
# outputs in the forward direction become inputs in the backward direction, and vice-versa
return set(self.inputs)
return set(self.outputs), set(self.inputs)
9 changes: 2 additions & 7 deletions src/torchjd/autojac/_transform/accumulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,8 @@ def _compute(self, gradients: Gradients) -> EmptyTensorDict:

return EmptyTensorDict()

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys

@property
def output_keys(self) -> set[Tensor]:
return set()
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return self._required_keys, set()


def _check_expects_grad(tensor: Tensor) -> None:
Expand Down
37 changes: 9 additions & 28 deletions src/torchjd/autojac/_transform/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,8 @@ def __init__(self, aggregator: Aggregator, key_order: Iterable[Tensor]):
def _compute(self, input: Jacobians) -> Gradients:
return self.transform(input)

@property
def required_keys(self) -> set[Tensor]:
return self.transform.required_keys

@property
def output_keys(self) -> set[Tensor]:
return self.transform.output_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return self.transform.check_keys()


class _AggregateMatrices(Transform[JacobianMatrices, GradientVectors]):
Expand All @@ -53,13 +48,9 @@ def _compute(self, jacobian_matrices: JacobianMatrices) -> GradientVectors:
ordered_matrices = self._select_ordered_subdict(jacobian_matrices, self.key_order)
return self._aggregate_group(ordered_matrices, self.aggregator)

@property
def required_keys(self) -> set[Tensor]:
return set(self.key_order)

@property
def output_keys(self) -> set[Tensor]:
return set(self.key_order)
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
keys = set(self.key_order)
return keys, keys

@staticmethod
def _select_ordered_subdict(
Expand Down Expand Up @@ -126,13 +117,8 @@ def _compute(self, jacobians: Jacobians) -> JacobianMatrices:
}
return JacobianMatrices(jacobian_matrices)

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys

@property
def output_keys(self) -> set[Tensor]:
return self._required_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return self._required_keys, self._required_keys


class _Reshape(Transform[GradientVectors, Gradients]):
Expand All @@ -146,10 +132,5 @@ def _compute(self, gradient_vectors: GradientVectors) -> Gradients:
}
return Gradients(gradients)

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys

@property
def output_keys(self) -> set[Tensor]:
return self._required_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return self._required_keys, self._required_keys
66 changes: 26 additions & 40 deletions src/torchjd/autojac/_transform/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,21 @@ def _compute(self, input: _B) -> _C:
"""Applies the transform to the input."""

def __call__(self, input: _B) -> _C:
input.check_keys_are(self.required_keys)
return self._compute(input)

@property
@abstractmethod
def required_keys(self) -> set[Tensor]:
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
"""
Returns the set of keys that the transform requires to be present in its input TensorDicts.
Returns a pair containing (in order) the required keys and the output keys of the Transform.
Checks that the transform is valid.
"""

@property
@abstractmethod
def output_keys(self) -> set[Tensor]:
"""Returns the set of keys that will be present in the output of the transform."""

Comment thread
ValerianRey marked this conversation as resolved.
__lshift__ = compose
__or__ = conjunct


class Composition(Transform[_A, _C]):
def __init__(self, outer: Transform[_B, _C], inner: Transform[_A, _B]):
if outer.required_keys != inner.output_keys:
raise ValueError(
"The `output_keys` of `inner` must match with the `required_keys` of "
f"outer. Found {outer.required_keys} and {inner.output_keys}"
)
self.outer = outer
self.inner = inner

Expand All @@ -80,32 +69,21 @@ def _compute(self, input: _A) -> _C:
intermediate = self.inner(input)
return self.outer(intermediate)

@property
def required_keys(self) -> set[Tensor]:
return self.inner.required_keys

@property
def output_keys(self) -> set[Tensor]:
return self.outer.output_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
outer_required_keys, outer_output_keys = self.outer.check_keys()
inner_required_keys, inner_output_keys = self.inner.check_keys()
if outer_required_keys != inner_output_keys:
raise ValueError(
"The `output_keys` of `inner` must match with the `required_keys` of "
f"outer. Found {outer_required_keys} and {inner_output_keys}"
)
return inner_required_keys, outer_output_keys


class Conjunction(Transform[_A, _B]):
def __init__(self, transforms: Sequence[Transform[_A, _B]]):
self.transforms = transforms

self._required_keys = set(
key for transform in transforms for key in transform.required_keys
)
for transform in transforms:
if transform.required_keys != self.required_keys:
raise ValueError("All transforms should require the same set of keys.")

output_keys_with_duplicates = [key for t in transforms for key in t.output_keys]
self._output_keys = set(output_keys_with_duplicates)

if len(self._output_keys) != len(output_keys_with_duplicates):
raise ValueError("The sets of output keys of transforms should be disjoint.")

def __str__(self) -> str:
strings = []
for t in self.transforms:
Expand All @@ -120,10 +98,18 @@ def _compute(self, tensor_dict: _A) -> _B:
output = _union([transform(tensor_dict) for transform in self.transforms])
return output

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
keys_pairs = [transform.check_keys() for transform in self.transforms]

required_keys = set(key for required_keys, _ in keys_pairs for key in required_keys)
for transform_required_keys, _ in keys_pairs:
if transform_required_keys != required_keys:
raise ValueError("All transforms should require the same set of keys.")

output_keys_with_duplicates = [key for _, output_keys in keys_pairs for key in output_keys]
output_keys = set(output_keys_with_duplicates)

if len(output_keys) != len(output_keys_with_duplicates):
raise ValueError("The sets of output keys of transforms should be disjoint.")

@property
def output_keys(self) -> set[Tensor]:
return self._output_keys
return required_keys, output_keys
10 changes: 3 additions & 7 deletions src/torchjd/autojac/_transform/diagonalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ def _compute(self, tensors: Gradients) -> Jacobians:
}
return Jacobians(diagonalized_tensors)

@property
def required_keys(self) -> set[Tensor]:
return set(self.considered)

@property
def output_keys(self) -> set[Tensor]:
return set(self.considered)
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
keys = set(self.considered)
return keys, keys
9 changes: 2 additions & 7 deletions src/torchjd/autojac/_transform/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,5 @@ def _compute(self, input: EmptyTensorDict) -> Gradients:

return Gradients({value: torch.ones_like(value) for value in self.values})

@property
def required_keys(self) -> set[Tensor]:
return set()

@property
def output_keys(self) -> set[Tensor]:
return self.values
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return set(), self.values
9 changes: 2 additions & 7 deletions src/torchjd/autojac/_transform/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,5 @@ def _compute(self, tensor_dict: _A) -> _A:
output = {key: tensor_dict[key] for key in self.keys}
return type(tensor_dict)(output)

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys

@property
def output_keys(self) -> set[Tensor]:
return self.keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return self._required_keys, self.keys
23 changes: 10 additions & 13 deletions src/torchjd/autojac/_transform/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,22 @@ class Stack(Transform[_A, Jacobians]):
def __init__(self, transforms: Sequence[Transform[_A, Gradients]]):
self.transforms = transforms

self._required_keys = {key for transform in transforms for key in transform.required_keys}
self._output_keys = {key for transform in transforms for key in transform.output_keys}

for transform in transforms:
if transform.required_keys != self.required_keys:
raise ValueError("All transforms should require the same set of keys.")

def _compute(self, input: _A) -> Jacobians:
results = [transform(input) for transform in self.transforms]
result = _stack(results)
return result

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
keys_pairs = [transform.check_keys() for transform in self.transforms]

required_keys = set(key for required_keys, _ in keys_pairs for key in required_keys)
output_keys = set(key for _, output_keys in keys_pairs for key in output_keys)

for transform_required_keys, _ in keys_pairs:
if transform_required_keys != required_keys:
raise ValueError("All transforms should require the same set of keys.")

@property
def output_keys(self) -> set[Tensor]:
return self._output_keys
return required_keys, output_keys


def _stack(gradient_dicts: list[Gradients]) -> Jacobians:
Expand Down
25 changes: 10 additions & 15 deletions tests/unit/autojac/_transform/test_base.py
Comment thread
ValerianRey marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,8 @@ def _compute(self, input: _B) -> _C:
output_dict = {key: torch.empty(0) for key in self._output_keys}
return typing.cast(_C, output_dict)

@property
def required_keys(self) -> set[Tensor]:
return self._required_keys

@property
def output_keys(self) -> set[Tensor]:
return self._output_keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return self._required_keys, self._output_keys


def test_call_checks_keys():
Expand Down Expand Up @@ -69,10 +64,10 @@ def test_compose_checks_keys():
t1 = FakeTransform(required_keys={a1}, output_keys={a1, a2})
t2 = FakeTransform(required_keys={a2}, output_keys={a1})

t1 << t2
(t1 << t2).check_keys()

with raises(ValueError):
t2 << t1
(t2 << t1).check_keys()


def test_conjunct_checks_required_keys():
Expand All @@ -88,13 +83,13 @@ def test_conjunct_checks_required_keys():
t2 = FakeTransform(required_keys={a1}, output_keys=set())
t3 = FakeTransform(required_keys={a2}, output_keys=set())

t1 | t2
(t1 | t2).check_keys()

with raises(ValueError):
t2 | t3
(t2 | t3).check_keys()

with raises(ValueError):
t1 | t2 | t3
(t1 | t2 | t3).check_keys()


def test_conjunct_checks_output_keys():
Expand All @@ -110,13 +105,13 @@ def test_conjunct_checks_output_keys():
t2 = FakeTransform(required_keys=set(), output_keys={a1})
t3 = FakeTransform(required_keys=set(), output_keys={a2})

t2 | t3
(t2 | t3).check_keys()

with raises(ValueError):
t1 | t3
(t1 | t3).check_keys()

with raises(ValueError):
t1 | t2 | t3
(t1 | t2 | t3).check_keys()


def test_empty_conjunction():
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/autojac/_transform/test_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,4 @@ def test_stack_different_required_keys():
grad2 = Grad([y2], [a])

with raises(ValueError):
_ = Stack([grad1, grad2])
Stack([grad1, grad2]).check_keys()
9 changes: 2 additions & 7 deletions tests/unit/autojac/_transform/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@ def __init__(self, keys: Iterable[Tensor]):
def _compute(self, input: EmptyTensorDict) -> Gradients:
return Gradients({key: torch.ones_like(key) for key in self.keys})

@property
def required_keys(self) -> set[Tensor]:
return set()

@property
def output_keys(self) -> set[Tensor]:
return self.keys
def check_keys(self) -> tuple[set[Tensor], set[Tensor]]:
return set(), self.keys


def test_single_key():
Expand Down
Loading