Skip to content

Commit 8a0d812

Browse files
committed
Update on "Add formulas and basic tests"
RFC: pytorch/rfcs#11 This PR adds: - Codegen support to define forward grad formulas and few manual formulas - Codegen support to automatically generate formulas as well as few usage - Tests for basic forward grad components Codegen generated examples. For each of them, the only part that is changed is the if statement before the return checking for fw grad defined. - For manual entry: ```yaml - name: max(Tensor self) -> Tensor self: evenly_distribute_backward(grad, self, result) result: max_forward(self_fw_grad, self, result) ``` ```cpp Tensor max(const Tensor & self) { auto& self_ = unpack(self, "self", 0); auto _any_requires_grad = compute_requires_grad( self ); std::shared_ptr<MaxBackward1> grad_fn; if (_any_requires_grad) { grad_fn = std::shared_ptr<MaxBackward1>(new MaxBackward1(), deleteNode); grad_fn->set_next_edges(collect_next_edges( self )); grad_fn->self_ = SavedVariable(self, false); } #ifndef NDEBUG c10::optional<Storage> self__storage_saved = self_.has_storage() ? c10::optional<Storage>(self_.storage()) : c10::nullopt; c10::intrusive_ptr<TensorImpl> self__impl_saved; if (self_.defined()) self__impl_saved = self_.getIntrusivePtr(); #endif auto tmp = ([&]() { at::AutoNonVariableTypeMode non_var_type_mode(true); return at::max(self_); })(); auto result = std::move(tmp); #ifndef NDEBUG if (self__storage_saved.has_value()) AT_ASSERT(self__storage_saved.value().is_alias_of(self_.storage())); if (self__impl_saved) AT_ASSERT(self__impl_saved == self_.getIntrusivePtr()); #endif if (grad_fn) { set_history(flatten_tensor_args( result ), grad_fn); } throw_error_for_complex_autograd(result, "max"); if (isFwGradDefined(self)) { auto self_fw_grad = toLegacyFwGrad(self); auto self_primal = toLegacyPrimal(self); auto result_new_fw_grad = max_forward(self_fw_grad, self_primal, result); if (result_new_fw_grad.defined()) { result.set_fw_grad(result_new_fw_grad, /* level */ 0, /* is_inplace_op */ false); } } if (grad_fn) { grad_fn->result_ = SavedVariable(result, true); } return result; } ``` - For element wise entry: ```yaml - name: abs(Tensor self) -> Tensor self: grad * self.sgn() result: auto_element_wise ``` ```cpp Tensor abs(const Tensor & self) { auto& self_ = unpack(self, "self", 0); auto _any_requires_grad = compute_requires_grad( self ); std::shared_ptr<AbsBackward> grad_fn; if (_any_requires_grad) { grad_fn = std::shared_ptr<AbsBackward>(new AbsBackward(), deleteNode); grad_fn->set_next_edges(collect_next_edges( self )); grad_fn->self_ = SavedVariable(self, false); } #ifndef NDEBUG c10::optional<Storage> self__storage_saved = self_.has_storage() ? c10::optional<Storage>(self_.storage()) : c10::nullopt; c10::intrusive_ptr<TensorImpl> self__impl_saved; if (self_.defined()) self__impl_saved = self_.getIntrusivePtr(); #endif auto tmp = ([&]() { at::AutoNonVariableTypeMode non_var_type_mode(true); return at::abs(self_); })(); auto result = std::move(tmp); #ifndef NDEBUG if (self__storage_saved.has_value()) AT_ASSERT(self__storage_saved.value().is_alias_of(self_.storage())); if (self__impl_saved) AT_ASSERT(self__impl_saved == self_.getIntrusivePtr()); #endif if (grad_fn) { set_history(flatten_tensor_args( result ), grad_fn); } throw_error_for_complex_autograd(result, "abs"); if (isFwGradDefined(self)) { auto self_fw_grad = toLegacyFwGrad(self); auto self_primal = toLegacyPrimal(self); auto result_new_fw_grad = self_fw_grad * self_primal.sgn(); if (result_new_fw_grad.defined()) { result.set_fw_grad(result_new_fw_grad, /* level */ 0, /* is_inplace_op */ false); } } return result; } ``` - For linear entry: ```yaml - name: clone(Tensor self, *, MemoryFormat? memory_format=None) -> Tensor self: grad result: auto_linear ``` ```cpp Tensor clone(const Tensor & self, c10::optional<MemoryFormat> memory_format) { auto& self_ = unpack(self, "self", 0); auto _any_requires_grad = compute_requires_grad( self ); std::shared_ptr<CloneBackward> grad_fn; if (_any_requires_grad) { grad_fn = std::shared_ptr<CloneBackward>(new CloneBackward(), deleteNode); grad_fn->set_next_edges(collect_next_edges( self )); } #ifndef NDEBUG c10::optional<Storage> self__storage_saved = self_.has_storage() ? c10::optional<Storage>(self_.storage()) : c10::nullopt; c10::intrusive_ptr<TensorImpl> self__impl_saved; if (self_.defined()) self__impl_saved = self_.getIntrusivePtr(); #endif auto tmp = ([&]() { at::AutoNonVariableTypeMode non_var_type_mode(true); return at::clone(self_, memory_format); })(); auto result = std::move(tmp); #ifndef NDEBUG if (self__storage_saved.has_value()) AT_ASSERT(self__storage_saved.value().is_alias_of(self_.storage())); if (self__impl_saved) AT_ASSERT(self__impl_saved == self_.getIntrusivePtr()); #endif if (grad_fn) { set_history(flatten_tensor_args( result ), grad_fn); } if (isFwGradDefined(self)) { auto self_fw_grad = toLegacyFwGrad(self); auto result_new_fw_grad = at::clone(self_fw_grad, memory_format); if (result_new_fw_grad.defined()) { result.set_fw_grad(result_new_fw_grad, /* level */ 0, /* is_inplace_op */ false); } } return result; } ``` - For no entry: ```yaml - name: angle(Tensor self) -> Tensor self: angle_backward(grad, self) ``` ```cpp Tensor angle(const Tensor & self) { auto& self_ = unpack(self, "self", 0); auto _any_requires_grad = compute_requires_grad( self ); std::shared_ptr<AngleBackward> grad_fn; if (_any_requires_grad) { grad_fn = std::shared_ptr<AngleBackward>(new AngleBackward(), deleteNode); grad_fn->set_next_edges(collect_next_edges( self )); grad_fn->self_ = SavedVariable(self, false); } #ifndef NDEBUG c10::optional<Storage> self__storage_saved = self_.has_storage() ? c10::optional<Storage>(self_.storage()) : c10::nullopt; c10::intrusive_ptr<TensorImpl> self__impl_saved; if (self_.defined()) self__impl_saved = self_.getIntrusivePtr(); #endif auto tmp = ([&]() { at::AutoNonVariableTypeMode non_var_type_mode(true); return at::angle(self_); })(); auto result = std::move(tmp); #ifndef NDEBUG if (self__storage_saved.has_value()) AT_ASSERT(self__storage_saved.value().is_alias_of(self_.storage())); if (self__impl_saved) AT_ASSERT(self__impl_saved == self_.getIntrusivePtr()); #endif if (grad_fn) { set_history(flatten_tensor_args( result ), grad_fn); } throw_error_for_complex_autograd(result, "angle"); TORCH_CHECK(!(isFwGradDefined(self)), "Trying to use forward prop with angle that does not support it."); return result; } ``` [ghstack-poisoned]
2 parents cbfbd0a + 8eb6fe0 commit 8a0d812

File tree

10 files changed

+21
-60
lines changed

10 files changed

+21
-60
lines changed

aten/src/ATen/native/native_functions.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@
111111
dispatch:
112112
DefaultBackend: _fw_primal
113113

114+
- func: make_dual(Tensor(a) primal, Tensor tangent, int level) -> Tensor(a)
115+
use_c10_dispatcher: full
116+
variants: function
117+
118+
- func: unpack_dual(Tensor(a) dual, int level) -> (Tensor(a) primal, Tensor tangent)
119+
use_c10_dispatcher: full
120+
variants: function
121+
114122
- func: rename_(Tensor(a!) self, Dimname[]? names) -> Tensor(a!)
115123
variants: method
116124

aten/src/ATen/templates/TensorBody.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ class CAFFE2_API Tensor {
609609

610610
/// This function can be used to set the value of the forward grad.
611611
/// Note that the given value might not be used directly if it is a view of another Tensor.
612-
void set_fw_grad(Tensor& new_grad, uint64_t level, bool is_inplace_op) {
612+
void set_fw_grad(const Tensor& new_grad, uint64_t level, bool is_inplace_op) {
613613
impl_->set_fw_grad(new_grad, *this, level, is_inplace_op);
614614
}
615615

c10/core/TensorImpl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const at::Tensor& TensorImpl::fw_grad(uint64_t level, const at::Tensor& self) co
5050
return autograd_meta_->fw_grad(level, self);
5151
}
5252

53-
void TensorImpl::set_fw_grad(at::Tensor& new_grad, const at::Tensor& self, uint64_t level, bool is_inplace_op) {
53+
void TensorImpl::set_fw_grad(const at::Tensor& new_grad, const at::Tensor& self, uint64_t level, bool is_inplace_op) {
5454
if (!autograd_meta_) autograd_meta_ = impl::GetAutogradMetaFactory()->make();
5555
autograd_meta_->set_fw_grad(new_grad, self, level, is_inplace_op);
5656
}

c10/core/TensorImpl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ struct C10_API AutogradMetaInterface {
137137
virtual at::Tensor& mutable_grad() = 0;
138138
virtual const at::Tensor& grad() const = 0;
139139
virtual const at::Tensor& fw_grad(uint64_t level, const at::Tensor& self) const = 0;
140-
virtual void set_fw_grad(at::Tensor& new_grad, const at::Tensor& self, uint64_t level, bool is_inplace_op) = 0;
140+
virtual void set_fw_grad(const at::Tensor& new_grad, const at::Tensor& self, uint64_t level, bool is_inplace_op) = 0;
141141
virtual ~AutogradMetaInterface();
142142
};
143143

@@ -634,7 +634,7 @@ struct C10_API TensorImpl : public c10::intrusive_ptr_target {
634634
* - "is_inplace_op" is a boolean flag that tells if this gradient was generated
635635
* by an inplace operation or an out of place one. This allows better error checking.
636636
*/
637-
void set_fw_grad(at::Tensor& new_grad, const at::Tensor& self, uint64_t level, bool is_inplace_op);
637+
void set_fw_grad(const at::Tensor& new_grad, const at::Tensor& self, uint64_t level, bool is_inplace_op);
638638

639639
/**
640640
* Return a typed data pointer to the actual data which this tensor refers to.

torch/autograd/forward_ad.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def make_dual(tensor, tangent, *, level=None):
6565
raise RuntimeError("Trying to create a dual Tensor for forward AD but no level "
6666
"exists, make sure to enter_dual_level() first.")
6767

68-
return torch._C._make_dual(tensor, tangent, level=level)
68+
return torch.make_dual(tensor, tangent, level=level)
6969

7070
def unpack_dual(tensor, *, level=None):
7171
r"""Function that unpacks a "dual object" to recover two plain tensors, one representing
@@ -80,7 +80,7 @@ def unpack_dual(tensor, *, level=None):
8080
if level < 0:
8181
return tensor, None
8282

83-
return torch._C._unpack_dual(tensor, level=level)
83+
return torch.unpack_dual(tensor, level=level)
8484

8585
class dual_level(_DecoratorContextManager):
8686
r"""Context-manager that controls the current forward ad level. It

torch/csrc/autograd/autograd.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,19 +166,6 @@ void exit_dual_level(uint64_t level) {
166166
ForwardADLevel::release_idx(level);
167167
}
168168

169-
at::Tensor make_dual(const at::Tensor& primal, at::Tensor tangent, uint64_t level) {
170-
TORCH_CHECK(!primal.fw_grad(level).defined(), "Making a dual Tensor based on a Tensor that "
171-
"already has a forward gradient at the same level ", level, " is not supported.");
172-
173-
auto dual_tensor = primal.view(primal.sizes());
174-
dual_tensor.set_fw_grad(tangent, level, /* is_inplace_op */ false);
175-
return dual_tensor;
176-
}
177-
178-
std::pair<at::Tensor, at::Tensor> unpack_dual(const at::Tensor& tensor, uint64_t level) {
179-
return {tensor._fw_primal(level), tensor.fw_grad(level)};
180-
}
181-
182169
} // namespace forward_ad
183170

184171
} // namespace autograd

torch/csrc/autograd/autograd.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ TORCH_API uint64_t enter_dual_level();
8989
/// reverse order compared to the entering that was done with the function above.
9090
TORCH_API void exit_dual_level(uint64_t level);
9191

92-
/// This function can be used to create a dual Tensor that holds a tangent to compute forward mode gradients.
93-
/// Note that the dual Tensor's primal is a view of the given primal and the given tangent is used as-is.
94-
/// This function is backward differentiable.
95-
TORCH_API at::Tensor make_dual(const at::Tensor& primal, at::Tensor tangent, uint64_t level);
96-
/// This function can be used to unpack a given dual Tensor to get its primal and tangent. The returned primal
97-
/// is a view of the dual and the tangent is returned as is.
98-
/// This function is backward differentiable.
99-
TORCH_API std::pair<at::Tensor, at::Tensor> unpack_dual(const at::Tensor& tensor, uint64_t level);
100-
10192
} // namespace forward_ad
10293
} // namespace autograd
10394
} // namespace torch

torch/csrc/autograd/init.cpp

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -317,32 +317,6 @@ static PyObject * python_exit_dual_level(PyObject* _unused, PyObject* args, PyOb
317317
END_HANDLE_TH_ERRORS
318318
}
319319

320-
static PyObject * python_make_dual(PyObject* _unused, PyObject* args, PyObject* kwargs) {
321-
HANDLE_TH_ERRORS
322-
static PythonArgParser parser({
323-
"make_dual(Tensor tensor, Tensor tangent, *, int64_t level)"
324-
});
325-
326-
ParsedArgs<3> parsed_args;
327-
auto _r = parser.parse(args, kwargs, parsed_args);
328-
329-
return utils::wrap(forward_ad::make_dual(_r.tensor(0), _r.tensor(1), _r.toInt64(2)));
330-
END_HANDLE_TH_ERRORS
331-
}
332-
333-
static PyObject * python_unpack_dual(PyObject* _unused, PyObject* args, PyObject* kwargs) {
334-
HANDLE_TH_ERRORS
335-
static PythonArgParser parser({
336-
"unpack_dual(Tensor tensor, *, int64_t level)"
337-
});
338-
339-
ParsedArgs<2> parsed_args;
340-
auto _r = parser.parse(args, kwargs, parsed_args);
341-
342-
return utils::wrap(forward_ad::unpack_dual(_r.tensor(0), _r.toInt64(1)));
343-
END_HANDLE_TH_ERRORS
344-
}
345-
346320
// autograd methods on torch._C
347321
static PyMethodDef methods[] = { // NOLINT
348322
{"_set_grad_enabled", set_grad_enabled, METH_O, nullptr},
@@ -356,8 +330,6 @@ static PyMethodDef methods[] = { // NOLINT
356330
{"autocast_decrement_nesting", autocast_decrement_nesting, METH_NOARGS, nullptr},
357331
{"set_anomaly_enabled", set_anomaly_mode_enabled, METH_O, nullptr},
358332
{"is_anomaly_enabled", is_anomaly_mode_enabled, METH_NOARGS, nullptr},
359-
{"_make_dual", castPyCFunctionWithKeywords(python_make_dual), METH_VARARGS | METH_KEYWORDS, nullptr},
360-
{"_unpack_dual", castPyCFunctionWithKeywords(python_unpack_dual), METH_VARARGS | METH_KEYWORDS, nullptr},
361333
{"_enter_dual_level", python_enter_dual_level, METH_NOARGS, nullptr},
362334
{"_exit_dual_level", castPyCFunctionWithKeywords(python_exit_dual_level), METH_VARARGS | METH_KEYWORDS, nullptr},
363335
{nullptr, nullptr, 0, nullptr}

torch/csrc/autograd/variable.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -594,23 +594,26 @@ namespace {
594594

595595
// This function is will ensure that the fw_grad_ is properly a view of the base for inplace ops on
596596
// Tensors that do not have forward grad originally.
597-
void AutogradMeta::set_fw_grad(Variable& new_grad, const Variable& self, uint64_t level, bool is_inplace_op) {
597+
void AutogradMeta::set_fw_grad(const Variable& new_grad_, const Variable& self, uint64_t level, bool is_inplace_op) {
598598
if (!fw_grad_) {
599599
// Lazy initialization
600600
fw_grad_ = std::make_shared<ForwardGrad>();
601601
}
602602
if (fw_grad_->contains(level)) {
603603
// Setting the forward grad again is only allowed if it is a no-op.
604604
// We do allow this case to simplify writing codegen for inplace ops.
605-
TORCH_INTERNAL_ASSERT(new_grad.defined(), "Cannot set a forward grad that is an undefined Tensor. Use "
605+
TORCH_INTERNAL_ASSERT(new_grad_.defined(), "Cannot set a forward grad that is an undefined Tensor. Use "
606606
"_fw_primal(level) to get a new Tensor with this forward grad unset.");
607607

608608
TORCH_INTERNAL_ASSERT(is_inplace_op, "Only inplace operations can re-set the forward grad of a Tensor that "
609609
"already has one.");
610610

611-
TORCH_INTERNAL_ASSERT(fw_grad_->value(level).is_same(new_grad), "Cannot set a value of a forward grad if it "
611+
TORCH_INTERNAL_ASSERT(fw_grad_->value(level).is_same(new_grad_), "Cannot set a value of a forward grad if it "
612612
"already exists. Inplace operations should modify it inplace.");
613613
} else {
614+
// TODO(alband) remove this spurious version counter bump
615+
auto new_grad = new_grad_;
616+
614617
// For inplace ops on a Tensor that does not already have a forward grad and is a view, we propagate
615618
// the tangent to the base and ensure that the new_grad is a view of that base's tangent.
616619
if (is_inplace_op && is_view_) {

torch/csrc/autograd/variable.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ struct TORCH_API AutogradMeta : public c10::AutogradMetaInterface {
244244

245245
const Variable& fw_grad(uint64_t level, const Variable& self) const override;
246246

247-
void set_fw_grad(Variable& new_grad, const Variable& self, uint64_t level, bool is_inplace_op) override;
247+
void set_fw_grad(const Variable& new_grad, const Variable& self, uint64_t level, bool is_inplace_op) override;
248248

249249
AutogradMeta(at::TensorImpl* self_impl = nullptr, bool requires_grad = false, Edge gradient_edge = Edge() ) {
250250
grad_fn_ = std::move(gradient_edge.function);

0 commit comments

Comments
 (0)