Repository: Lightning-AI/pytorch-lightning

Issue:
Error in transfer_batch_to_device when None type is in the batch
<!-- 
### Common bugs:
1. Tensorboard not showing in Jupyter-notebook see [issue 79](https://github.com/PyTorchLightning/pytorch-lightning/issues/79).    
2. PyTorch 1.1.0 vs 1.2.0 support [see FAQ](https://github.com/PyTorchLightning/pytorch-lightning#faq)    
-->

## 🐛 Bug

<!-- A clear and concise description of what the bug is. -->

### To Reproduce

Steps to reproduce the behavior:

1. There should be no `torchtext` pre-installed
2. Run the sample code

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->


#### Code sample
```import torch
from torch.utils.data import DataLoader
import pytorch_lightning as pl


def collate_fn(batch):
    return batch


class MyDataModule(pl.LightningDataModule):
    def __init__(self):
        super().__init__()

    def prepare_data(self):
        pass

    def setup(self, stage):
        self.train = [{"input": torch.randn(1,2), "output": None}]

    def train_dataloader(self):
        return DataLoader(self.train, batch_size=1, collate_fn=collate_fn)


class MyModel(pl.LightningModule):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(2, 1)

    def forward(self, x):
        return self.linear(x)

    def configure_optimizers(self):
        return torch.optim.Adam(self.parameters())

    def training_step(self, batch, batch_idx):
        x = batch[0]["input"]
        y = batch[0]["output"]
        loss = self(x)
        result = pl.TrainResult(loss)
        result.log('train_loss', loss, on_epoch=True)
        return result


def main():
    # Dataset
    data_module = MyDataModule()

    # Model
    model = MyModel()

    # Train
    trainer = pl.Trainer(max_steps=1)
    trainer.fit(model, datamodule=data_module)


if __name__ == "__main__":
    main()
```
<!-- Ideally attach a minimal code sample to reproduce the decried issue. 
Minimal means having the shortest code but still preserving the bug. -->

### Expected behavior
The above code runs fine if the package `torchtext` is installed. However, the code raises following error if `torchtext` is not available and I believe this inconsistency is a bug.

```
 File "python3.6/site-packages/pytorch_lightning/utilities/apply_func.py", line 122, in batch_to
    return data.to(device, **kwargs)
AttributeError: 'NoneType' object has no attribute 'to'
python-BaseException
```

I think this line of the [code](https://github.com/PyTorchLightning/pytorch-lightning/blob/f46318ebfeb785a659c49091a6871584ccde3ee1/pytorch_lightning/utilities/apply_func.py#L24) is the cause of the problem 

<!-- A clear and concise description of what you expected to happen. -->

### Environment
* CUDA:
	- GPU:
		- TITAN Xp
		- TITAN Xp
		- TITAN Xp
		- TITAN Xp
		- TITAN Xp
		- TITAN Xp
		- TITAN Xp
		- TITAN Xp
	- available:         True
	- version:           10.2
* Packages:
	- numpy:             1.18.4
	- pyTorch_debug:     False
	- pyTorch_version:   1.6.0
	- pytorch-lightning: 0.9.0
	- tensorboard:       2.2.1
	- tqdm:              4.46.0
* System:
	- OS:                Linux
	- architecture:
		- 64bit
		- ELF
	- processor:         x86_64
	- python:            3.6.8
	- version:           #81~16.04.1-Ubuntu SMP Tue Nov 26 16:34:21 UTC 2019



Discussion:
Hi! thanks for your contribution!, great first issue!
Thanks! Mind sending us a PR with the fix?
@awaelchli mind taking a look?

Resolving patch:
diff --git a/pytorch_lightning/utilities/apply_func.py b/pytorch_lightning/utilities/apply_func.py
--- a/pytorch_lightning/utilities/apply_func.py
+++ b/pytorch_lightning/utilities/apply_func.py
@@ -121,4 +121,5 @@ def batch_to(data):
         kwargs = dict(non_blocking=True) if isinstance(data, torch.Tensor) else {}
         return data.to(device, **kwargs)
 
-    return apply_to_collection(batch, dtype=(TransferableDataType, Batch), function=batch_to)
+    dtype = (TransferableDataType, Batch) if TORCHTEXT_AVAILABLE else TransferableDataType
+    return apply_to_collection(batch, dtype=dtype, function=batch_to)


Test patch:
diff --git a/tests/models/test_gpu.py b/tests/models/test_gpu.py
--- a/tests/models/test_gpu.py
+++ b/tests/models/test_gpu.py
@@ -341,6 +341,12 @@ def test_single_gpu_batch_parse():
     trainer = Trainer(gpus=1)
     trainer.accelerator_backend = GPUBackend(trainer)
 
+    # non-transferrable types
+    primitive_objects = [None, {}, [], 1.0, "x", [None, 2], {"x": (1, 2), "y": None}]
+    for batch in primitive_objects:
+        data = trainer.accelerator_backend.batch_to_device(batch, torch.device('cuda:0'))
+        assert data == batch
+
     # batch is just a tensor
     batch = torch.rand(2, 3)
     batch = trainer.accelerator_backend.batch_to_device(batch, torch.device('cuda:0'))