Repository: dmlc/dgl
Pull request: [Example][Bugfix] Fix infograph example
## Description

To fix https://github.com/dmlc/dgl/issues/3975, the issue is due to this PR: https://github.com/dmlc/dgl/pull/3351

It crashed because of the checking condition `if len(F.shape(graph_feat)) == 1`, which indicates input feature is a 1-dim vector. 

However, 1-dim vector of `graph_feat` may be attributed to two scenarios  (1) when `batch_size = 1`, and then write the input `feat[0]` to `broadcast_nodes()` in a reduced-dimensionality way, e.g., like in our [doc](https://github.com/dmlc/dgl/blob/master/python/dgl/readout.py#:~:text=%3E%3E%3E%20dgl.broadcast_nodes(g1%2C%20feat%5B0%5D)); (2) each graph feature is a scalar (like graph id) and we are batching `graph_id` to constrcut `graph_feat` like [in this example](https://github.com/dmlc/dgl/blob/master/examples/pytorch/infograph/semisupervised.py#:~:text=graph_id%20%3D%20th.arange(n_graphs)).

**For scenario 1, we need to increase the dimensionality by calling `unsqueeze()`; however, for scenario 2, we should not add more dimension.** 

The proposed fix in here is changing the condition to `if len(F.shape(graph_feat)) == 1 and graph.batch_size == 1`. 

However, I think maybe a better idea is reverting the changes in this PR: https://github.com/dmlc/dgl/pull/3351; and update our Doc to avoid inputing reducd-dimensionality tensor `feat[0]`.

## Checklist
Please feel free to remove inapplicable items for your PR.
- [x] The PR title starts with [$CATEGORY] (such as [NN], [Model], [Doc], [Feature]])
- [x] Changes are complete (i.e. I finished coding on this PR)
- [x] All changes have test coverage
- [x] Code is well-documented
- [x] To the best of my knowledge, examples are either not affected by this change,
      or have been fixed to be compatible with this change
- [x] Related issue is referred in this PR
- [ ] If the PR is for a new model/paper, I've updated the example index [here](../examples/README.md).

## Changes
`if len(F.shape(graph_feat)) == 1`   --> `if len(F.shape(graph_feat)) == 1 and graph.batch_size == 1`  in readout.py



## Results
`python semisupervised.py --gpu 0 --target mu --epochs 1`
```
======== target = mu ========
Epoch: 0, Sup_Loss: 21.652762, Unsup_loss: 0.0466, Consis_loss: 0.0220
Epoch: 0, LR: 0.001, val_error: 2.0564, best_test_error: 2.0551

```

`python semisupervised.py --gpu 0 --target mu --epochs 1 --batch_size 1`
```
======== target = mu ========
Epoch: 0, Sup_Loss:  nan, Unsup_loss: nan, Consis_loss: nan
Epoch: 0, LR: 0.001, val_error: nan, best_test_error: inf

```



Problem statement:
InfoGraph example fails on GPU
## 🐛 Bug

Running the InfoGraph example on GPU fails.

```
   return th.repeat_interleave(input, repeats, dim) # PyTorch 1.1
RuntimeError: repeats must have the same size as input along dim
```

All I did is run:

```
 python infograph/semisupervised.py --gpu 0 --target mu
```

## To Reproduce

Steps to reproduce the behavior:

1. Go to DGL/examples folder
2. Run semisupervised eample


Traceback (most recent call last):
  File "semisupervised.py", line 217, in <module>
    for sup_data, unsup_data in zip(train_loader, unsup_loader):
  File "/home/neo/wellth-wrk/env/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 530, in __next__
    data = self._next_data()
  File "/home/neo/wellth-wrk/env/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 570, in _next_data
    data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
  File "/home/neo/wellth-wrk/env/lib/python3.8/site-packages/torch/utils/data/_utils/fetch.py", line 52, in fetch
    return self.collate_fn(data)
  File "semisupervised.py", line 116, in collate
    graph_id = dgl.broadcast_nodes(batched_graph, graph_id)
  File "/home/neo/wellth-wrk/env/lib/python3.8/site-packages/dgl/readout.py", line 418, in broadcast_nodes
    return F.repeat(graph_feat, graph.batch_num_nodes(ntype), dim=0)
  File "/home/neo/wellth-wrk/env/lib/python3.8/site-packages/dgl/backend/pytorch/tensor.py", line 189, in repeat
    return th.repeat_interleave(input, repeats, dim) # PyTorch 1.1
RuntimeError: repeats must have the same size as input along dim

## Expected behavior

Code runs and finishes training.

## Environment

 - DGL Version (e.g., 1.0): 0.6.1
 - Backend Library & Version (e.g., PyTorch 0.4.1, MXNet/Gluon 1.3):1.11.0
 - OS (e.g., Linux): Ubuntu
 - How you installed DGL (`conda`, `pip`, source): PIP
 - Build command you used (if compiling from source):
 - Python version: 3.8
 - CUDA/cuDNN version (if applicable):  11.4
 - GPU models and configuration (e.g. V100): Titan RTX
 - Any other relevant information:

## Additional context

Hints from issue discussion:
I tried with `dgl-cu102=0.6.1, PIP, torch=1.10.1+cu102` on Ubuntu18.04 and it works well.

have you tried to train with CPU? does it work well on cpu?
It is actually the same issue on CPU as well. When trying other examples like gcn, ...etc. Both modes, CPU and GPU work.
I just find the same issue is hit in `dgl-cu102==0.8.1`. but it works well in `dgl-cu102==0.6.1`. could you double confirm the dgl version you're using via `print(dgl.__version__)`?
This is how my pip freeze looks like:

dgl==0.6.1
dgl-cu102==0.8.1
dgl-cu113==0.8.1
dglgo==0.0.1


I install the CUDA version this way:
pip3 install dgl-cu113 dglgo -f https://data.dgl.ai/wheels/repo.html

How do you specify 0.6.1 for the CUDA version?
Found out the problem!

I was installing using the instructions on the website:
```
pip install dgl-cu113 dglgo -f https://data.dgl.ai/wheels/repo.html
```

which results in 0.8.1 of the CUDA version being installed (it is grabbing the wheels files from that URL above).
If I install using just pip:
```
pip install dgl-cu111
```

Which would install DGL CUDA 0.6.1 instead (inline with the DGL 0.6.1).


`pip3 install dgl-cu102==0.6.1 -f https://data.dgl.ai/wheels/repo.html`

So this issue does not exist in `0.6.1`, but it exists in `0.8.1`. Let me keep an eye on it.
Yes, that is correct.
@Rhett-Ying I also reproduced the same error in 0.8.0post2 version and dgl 0.7.2.
@chang-l Do you plan to work on this?
> @chang-l Do you plan to work on this?

Sure. I will take a look.

Commit message: Update

Patch to review:
diff --git a/python/dgl/readout.py b/python/dgl/readout.py
index 5492afcdacd0..d52a73a273ec 100644
--- a/python/dgl/readout.py
+++ b/python/dgl/readout.py
@@ -1,7 +1,7 @@
 """Classes and functions for batching multiple graphs together."""
 from __future__ import absolute_import
 
-from .base import DGLError
+from .base import DGLError, dgl_warning
 from . import backend as F
 from .ops import segment
 
@@ -365,8 +365,9 @@ def broadcast_nodes(graph, graph_feat, *, ntype=None):
     graph : DGLGraph
         The graph.
     graph_feat : tensor
-        The feature to broadcast. Tensor shape is :math:`(*)` for single graph, and
-        :math:`(B, *)` for batched graph.
+        The feature to broadcast. Tensor shape is :math:`(B, *)` for batched graph,
+        where :math:`B` is the batch size.
+
     ntype : str, optional
         Node type. Can be omitted if there is only one node type.
 
@@ -403,9 +404,11 @@ def broadcast_nodes(graph, graph_feat, *, ntype=None):
             [0.2721, 0.4629, 0.7269, 0.0724, 0.1014],
             [0.2721, 0.4629, 0.7269, 0.0724, 0.1014]])
 
-    Broadcast feature to all nodes in the single graph.
+    Broadcast feature to all nodes in the single graph (the feature tensor shape
+    to broadcast should be :math:`(1, *)`).
 
-    >>> dgl.broadcast_nodes(g1, feat[0])
+    >>> feat0 = th.unsqueeze(feat[0], 0)
+    >>> dgl.broadcast_nodes(g1, feat0)
     tensor([[0.4325, 0.7710, 0.5541, 0.0544, 0.9368],
             [0.4325, 0.7710, 0.5541, 0.0544, 0.9368]])
 
@@ -413,7 +416,10 @@ def broadcast_nodes(graph, graph_feat, *, ntype=None):
     --------
     broadcast_edges
     """
-    if len(F.shape(graph_feat)) == 1:
+    if (F.shape(graph_feat)[0] != graph.batch_size and graph.batch_size == 1):
+        dgl_warning(':func:`broadcast_nodes` directly taking one node feature tensor as `feat[i]`'
+                    ' to broadcast to all nodes in a single graph will be deprecated in future,'
+                    ' please make sure the feature tensor shape is :math:`(1, *).`')
         graph_feat = F.unsqueeze(graph_feat, dim=0)
     return F.repeat(graph_feat, graph.batch_num_nodes(ntype), dim=0)
 
@@ -434,8 +440,8 @@ def broadcast_edges(graph, graph_feat, *, etype=None):
     graph : DGLGraph
         The graph.
     graph_feat : tensor
-        The feature to broadcast. Tensor shape is :math:`(*)` for single graph, and
-        :math:`(B, *)` for batched graph.
+        The feature to broadcast. Tensor shape is :math:`(B, *)` for batched graph,
+        where :math:`B` is the batch size.
     etype : str, typle of str, optional
         Edge type. Can be omitted if there is only one edge type in the graph.
 
@@ -470,9 +476,11 @@ def broadcast_edges(graph, graph_feat, *, etype=None):
             [0.2721, 0.4629, 0.7269, 0.0724, 0.1014],
             [0.2721, 0.4629, 0.7269, 0.0724, 0.1014]])
 
-    Broadcast feature to all edges in the single graph.
+    Broadcast feature to all edges in the single graph (the feature tensor shape
+    to broadcast should be :math:`(1, *)`).
 
-    >>> dgl.broadcast_edges(g2, feat[1])
+    >>> feat1 = th.unsqueeze(feat[1], 0)
+    >>> dgl.broadcast_edges(g2, feat1)
     tensor([[0.2721, 0.4629, 0.7269, 0.0724, 0.1014],
             [0.2721, 0.4629, 0.7269, 0.0724, 0.1014]])
 
@@ -480,7 +488,10 @@ def broadcast_edges(graph, graph_feat, *, etype=None):
     --------
     broadcast_nodes
     """
-    if len(F.shape(graph_feat)) == 1:
+    if (F.shape(graph_feat)[0] != graph.batch_size and graph.batch_size == 1):
+        dgl_warning(':func:`broadcast_edges` directly taking one edge feature tensor as `feat[i]`'
+                    ' to broadcast to all edges in a single graph will be deprecated in future,'
+                    ' please make sure the feature tensor shape is :math:`(1, *).`')
         graph_feat = F.unsqueeze(graph_feat, dim=0)
     return F.repeat(graph_feat, graph.batch_num_edges(etype), dim=0)
 


Code review comments:
diff_hunk: @@ -417,8 +417,9 @@ def broadcast_nodes(graph, graph_feat, *, ntype=None):
     broadcast_edges
     """
     if (F.shape(graph_feat)[0] != graph.batch_size and graph.batch_size == 1):
-        dgl_warning('If single graph, please make sure the batching dimension is added'
-                    ' and the tensor shape of graph_feat is (1, *).')
+        dgl_warning(':func:`broadcast_nodes` directly taking one node feature tensor as `feat[i]`'
path: python/dgl/readout.py
text: @user1:
No need to have `:func:` and `:math:`. These only help for rendering the documentation with sphinx.

@author:
I see.. Thanks.

diff_hunk: @@ -417,8 +417,9 @@ def broadcast_nodes(graph, graph_feat, *, ntype=None):
     broadcast_edges
     """
     if (F.shape(graph_feat)[0] != graph.batch_size and graph.batch_size == 1):
-        dgl_warning('If single graph, please make sure the batching dimension is added'
-                    ' and the tensor shape of graph_feat is (1, *).')
+        dgl_warning(':func:`broadcast_nodes` directly taking one node feature tensor as `feat[i]`'
+                    ' to broadcast to all nodes in a single graph will be deprecated in future,'
+                    ' please make sure the feature tensor shape is :math:`(1, *).`')
path: python/dgl/readout.py
text: @user1:
Perhaps you can say

```
For a single graph, use a tensor of shape (1, *) for graph_feat. The support of shape (*) will be deprecated.
```

Same for `broadcast_edges`

@author:
Thank you for you suggestion. It's more concise.