Repository: pytorch/functorch (language: python)

Issue:
Actually actionable list of batching rules to write
For each of the items here, we should make sure all compositions (vmap, vmap x vjp) have a batching rule. All of these items should be actionable (in that it is possible to write a batching rule and we are not blocked on functionalization, which is coming soon).

Note: you may need to write an OpInfo for the operator if it doesn't exist already or wait for one to be added. A lot of folks are adding OpInfos right now, so if the OpInfo doesn't exist please ask first to see if someone is working on it.

Note: if any of the operations decompose into in-place operations, then we need functionalization to handle them. I think I've already filtered out all of those, but please check me on that.

Parcel 1: top nn.functional.* and top torch.* foo
- [x] torch.nn.functional.interpolate (this involves adding batching rules for adaptive_avg_pool1d, but we might as well do `adaptive_avg_pool{1, 2, 3}d` as well as their backward variants while we're at it)
- [x] nn.functional.unfold. Involves writing a batching rule for im2col im2col_backward (https://github.com/pytorch/functorch/pull/262)
- [x] nn.functional.grid_sample. Involves writing a batching rule for the backward operator. @vfdev-5 
- [x] torch.pow. The backward needs batching rule for logical_and ; use this as an opportunity to write the batching rules for `logical_{and, or, xor}` if those don't exist yet. We may need to also add a change to PyTorch core to make the logical_* functions primitives w.r.t. autograd

Parcel 2: new_blah
- [x] new_empty, new_full, new_ones, new_zeros, empty_like, zeros_like, ones_like, full_like
- [x] `adaptive_max_pool{1, 2, 3}d` as well as the backward variants
- [x] diagonal_scatter, select_scatter, slice_scatter @kshitij12345 
- [ ] linalg.householder_product (forward pass only)
- [x] pixel_shuffle, pixel_unshuffle (these might just be OP_DECOMPOSE)
- [x] isinf, isfinite, isnan
- [x] _cdist_forward, _cdist_backward (try to write a batching rule if possible. If not possible, we may need to write a decomposition) @vfdev-5 

Parcel 3: linalg things
- [x] _lu_with_info (forward pass only) @vfdev-5 
- [x] linalg.eig (forward pass only) @vfdev-5 
- [x] torch.addr @vfdev-5 
- [x] cholesky_solve (forward pass only) @vfdev-5 

Parcel 4:
- [x] ~~index_select, index_copy, etc, all need a backward formula in pytorch/pytorch https://github.com/pytorch/functorch/issues/260~~


Discussion / hints:
@zou3519 `nn.functional.pad` with circular option requires to do a copy: `out[..., out_d0:out_d1] = input[..., in_d0:in_d1]` and thus there is the following error is raised:
```
>           out[..., out_d0:out_d1] = input[..., in_d0:in_d1]                                                                                                                               
E           RuntimeError: vmap: aten::copy_(self, *extra_args) is not possible because there exists a Tensor `other` in extra_args that has more elements than `self`. This happened due to `other` being vmapped over but `self` not being vmapped over at level 2. Please try to use out-of-place operators instead of aten::copy_. If said operator is being called inside the PyTorch framework, please file a bug report instead. 
```
Can we do something with that ?
Aha. Nope, we can't do anything about that until functionalization is in, good catch.
Hi @zou3519, the "forward pass only" ops above means that the vjp and related operators require the functionalization too?
> Hi @zou3519, the "forward pass only" ops above means that the vjp and related operators require the functionalization too?

Yes, "forward pass only" means we should only try to get the `vmap` tests passing and none of the vjp/grad/compositions of {vjp, grad} tests.
@kshitij12345 on which tasks from Parcel 2 you are working on and plan to work on ?


I can start working on `_cdist_forward`, `_cdist_backward`
@vfdev-5 I think I'll be picking `diagonal_scatter` next. Go ahead with `_cdist_forward, _cdist_backward`

Fix patch:
diff --git a/functorch/csrc/BatchRulesBinaryOps.cpp b/functorch/csrc/BatchRulesBinaryOps.cpp
index bb51fe174..7da69ea7a 100644
--- a/functorch/csrc/BatchRulesBinaryOps.cpp
+++ b/functorch/csrc/BatchRulesBinaryOps.cpp
@@ -22,11 +22,9 @@ static void handleScalarTypePromotion(Tensor& logical_scalar_tensor, Tensor& sec
   }
 }
 
-template <typename F, F Func, typename... ExtraArgs>
-std::tuple<Tensor,optional<int64_t>> _binary_pointwise_batch_rule(
+std::tuple<Tensor, Tensor> _binary_pointwise_helper(
     const Tensor& tensor, optional<int64_t> tensor_batch_dim,
-    const Tensor& other, optional<int64_t> other_batch_dim,
-    ExtraArgs... extra_args) {
+    const Tensor& other, optional<int64_t> other_batch_dim) {
   // compute max logical rank
   auto tensor_logical_rank = rankWithoutBatchDim(tensor, tensor_batch_dim);
   auto other_logical_rank = rankWithoutBatchDim(other, other_batch_dim);
@@ -52,8 +50,22 @@ std::tuple<Tensor,optional<int64_t>> _binary_pointwise_batch_rule(
   tensor_ = maybePadToLogicalRank(tensor_, tensor_batch_dim, max_logical_rank);
   other_ = maybePadToLogicalRank(other_, other_batch_dim, max_logical_rank);
 
+  return std::make_tuple(tensor_, other_);
+}
+
+template <typename F, F Func, typename... ExtraArgs>
+std::tuple<Tensor,optional<int64_t>> _binary_pointwise_batch_rule(
+    const Tensor& tensor, optional<int64_t> tensor_batch_dim,
+    const Tensor& other, optional<int64_t> other_batch_dim,
+    ExtraArgs... extra_args) {
+
+  auto tensor_other = _binary_pointwise_helper(
+      tensor, tensor_batch_dim, other, other_batch_dim);
+  auto tensor_ = std::get<0>(tensor_other);
+  auto other_ = std::get<1>(tensor_other);
+
   auto result = Func(tensor_, other_, std::forward<ExtraArgs>(extra_args)...);
-  return std::make_tuple( std::move(result), 0 );
+  return std::make_tuple(result, 0);
 }
 
 template <typename A, A a, typename C>
@@ -163,6 +175,52 @@ Tensor addr_decomposition(
   return self * beta + outer;
 }
 
+std::tuple<Tensor,optional<int64_t>> cdist_backward_batch_rule(
+    const Tensor& grad, optional<int64_t> grad_bdim,
+    const Tensor& x1, optional<int64_t> x1_bdim,
+    const Tensor& x2, optional<int64_t> x2_bdim,
+    const double p,
+    const Tensor& cdist, optional<int64_t> cdist_bdim) {
+
+  auto x1_ = x1;
+  if (cdist_bdim && !x1_bdim) {
+    // We need to make sure that x1 has batch dim if cdist has one
+    // otherwise, we get
+    // RuntimeError: Function CdistBackward0 returned an invalid gradient at index 1 - got [5]
+    // but expected shape compatible with [4, 5]
+    auto bs = cdist.size(*cdist_bdim);
+    x1_ = ensure_has_bdim(x1, false, bs);
+    x1_ = x1_.contiguous();
+    x1_bdim = 0;
+  }
+
+  // We need to apply the same preprocessing on x1 and x2 as in the forward pass
+  // _binary_pointwise_batch_rule
+  auto x12 = _binary_pointwise_helper(x1_, x1_bdim, x2, x2_bdim);
+  x1_ = std::get<0>(x12);
+  auto x2_ = std::get<1>(x12);
+
+  auto grad_ = moveBatchDimToFront(grad, grad_bdim);
+  if ((x1_bdim || x2_bdim) && !grad_bdim) {
+    // We need to make sure that grad has batch dim if x1 or x2 have one
+    // Probably, there is an assumption on the strides.
+    // Otherwise grad input contains thrash values, e.g. -7.0816e+29, 7.0816e+29
+    auto bs = get_bdim_size2(x1_, 0, x2_, 0);
+    grad_ = ensure_has_bdim(grad_, grad_bdim.has_value(), bs);
+    grad_ = grad_.contiguous();
+  }
+
+  auto out = at::_cdist_backward(grad_, x1_, x2_, p, cdist);
+
+  optional<int64_t> out_bdim = nullopt;
+  if (x1_bdim || x2_bdim) {
+    out_bdim = 0;
+  }
+
+  return std::make_tuple(out, out_bdim);
+}
+
+
 TORCH_LIBRARY_IMPL(aten, FT_BATCHED_KEY, m) {
 #define BINARY_POINTWISE2(op, overload) \
   VMAP_SUPPORT(#op"."#overload, BINARY_POINTWISE_BATCH_RULE(ATEN_FN2(op, overload)));
@@ -218,6 +276,12 @@ TORCH_LIBRARY_IMPL(aten, FT_BATCHED_KEY, m) {
   UNARY_POINTWISE(clamp_max);
   POINTWISE_BOXED(clamp_max_);
 
+  VARIADIC_BDIMS_BOXED(_euclidean_dist);
+  // Implementation note: _binary_pointwise_helper performs a dtype promotion if args are scalars,
+  // but cdist can't work with scalars, at least 2d tensors.
+  BINARY_POINTWISE(_cdist_forward);
+  VMAP_SUPPORT("_cdist_backward", cdist_backward_batch_rule);
+
   // Commented out so we have a test op
   // BINARY_SCALAR_2(copysign, Tensor, Scalar);
   BINARY_SCALAR_2(div, Tensor, Scalar);
diff --git a/functorch/csrc/BatchRulesDecompositions.cpp b/functorch/csrc/BatchRulesDecompositions.cpp
index 317358e03..a28c181f1 100644
--- a/functorch/csrc/BatchRulesDecompositions.cpp
+++ b/functorch/csrc/BatchRulesDecompositions.cpp
@@ -127,6 +127,7 @@ TORCH_LIBRARY_IMPL(aten, FT_BATCHED_KEY, m) {
   OP_DECOMPOSE2(bitwise_xor, Scalar);
   OP_DECOMPOSE(broadcast_tensors);
   OP_DECOMPOSE(broadcast_to);
+  OP_DECOMPOSE(cdist);
   OP_DECOMPOSE(clip);
   OP_DECOMPOSE2(clip, Tensor );
   OP_DECOMPOSE(concat);


Test patch:
diff --git a/test/test_ops.py b/test/test_ops.py
index 47db75044..c4ab7d046 100644
--- a/test/test_ops.py
+++ b/test/test_ops.py
@@ -745,7 +745,6 @@ def test_vmapjvpall(self, device, dtype, op):
     @skipOps('TestOperators', 'test_vmapvjp_has_batch_rule', vmapvjp_fail.union({
         xfail('view_as_complex'),
         xfail('__getitem__'),
-        xfail('cdist'),
         xfail('cholesky'),
         xfail('complex'),
         xfail('copysign'),
@@ -757,7 +756,6 @@ def test_vmapjvpall(self, device, dtype, op):
         xfail('fft.ihfft'),
         xfail('fft.rfft'),
         xfail('fft.rfftn'),
-        xfail('cdist'),
         xfail('fill_'),
         xfail('fmax'),
         xfail('fmin'),
diff --git a/test/test_vmap.py b/test/test_vmap.py
index ad566c535..c0e9ca42c 100644
--- a/test/test_vmap.py
+++ b/test/test_vmap.py
@@ -3170,7 +3170,6 @@ def test_vmap_exhaustive(self, device, dtype, op):
 
     @ops(functorch_lagging_op_db + additional_op_db, allowed_dtypes=(torch.float,))
     @skipOps('TestVmapOperatorsOpInfo', 'test_op_has_batch_rule', vmap_fail.union({
-        xfail('cdist'),
         xfail('complex'),
         xfail('copysign'),
         xfail('dsplit'),