Repository: pytorch/ignite

Issue:
Backwards incompatibility between ignite 0.4.0.dev20200420 vs 0.2.1 for torch.utils.data.Dataset
## 🐛 Bug description

Hello,
When I run the script below with  ignite version 0.2.1. I get the expected output
```
tensor([1])
tensor([2])
tensor([3])
tensor([4])
tensor([5])
tensor([1])
tensor([2])
tensor([3])
tensor([4])
tensor([5])

```
However when I run same script with Ignite Version 0.4.0.dev20200420 I get the following error

```
Engine run is terminating due to exception: 'DataLoader' object has no attribute '_dataset_kind'.
Traceback (most recent call last):
  File "test_ignite.py", line 84, in <module>
    trainer.run(data_loader, max_epochs=2)
  File "/home/ec2-user/anaconda3/envs/pytorch_p36/lib/python3.6/site-packages/ignite/engine/engine.py", line 691, in run
    return self._internal_run()
  File "/home/ec2-user/anaconda3/envs/pytorch_p36/lib/python3.6/site-packages/ignite/engine/engine.py", line 803, in _internal_run
    self._handle_exception(e)
  File "/home/ec2-user/anaconda3/envs/pytorch_p36/lib/python3.6/site-packages/ignite/engine/engine.py", line 509, in _handle_exception
    raise e
  File "/home/ec2-user/anaconda3/envs/pytorch_p36/lib/python3.6/site-packages/ignite/engine/engine.py", line 786, in _internal_run
    self._setup_engine()
  File "/home/ec2-user/anaconda3/envs/pytorch_p36/lib/python3.6/site-packages/ignite/engine/engine.py", line 707, in _setup_engine
    _dataloader_kind = self.state.dataloader._dataset_kind
AttributeError: 'DataLoader' object has no attribute '_dataset_kind'
```

I need to use ignite 0.4.0.dev20200420 to use torch.utils.data.IterableDataset (https://github.com/pytorch/ignite/issues/932#event-3257231397). 
The script is below.
Thank you


```
import torch
from ignite.engine import Engine, Events
from ignite.contrib.handlers import ProgressBar
from ignite.handlers import EarlyStopping
from ignite.metrics import RunningAverage
import pdb


class DatasetUtils:
    def __init__(self, datapoints):
        self.datapoints = datapoints

    def load_data(self):
        for d in self.datapoints:
            return d

    def create_examples(self, data):
        for datapoint in data:
            return datapoint**2

    def __len__(self):
        return len(self.datapoints)

class MyDataset(torch.utils.data.Dataset):
    def __init__(self, datapoints):
        self.datapoints = datapoints

    def __getitem__(self, index):
        return datapoints[index]

    def __len__(self):
        return len(self.datapoints)

datapoints = [1, 2, 3, 4, 5]
helper = DatasetUtils(datapoints)

ds = MyDataset(datapoints)

data_loader = torch.utils.data.DataLoader(ds, num_workers=1, batch_size=1)

def update(engine, batch):
    print(batch)
    return 0

def inference(engine, batch):
    for i in data_loader:
        print(i)

trainer = Engine(update)
validator = Engine(inference)

trainer.run(data_loader, max_epochs=2)
```




## Environment

 - PyTorch Version: 1.2.0
 - Ignite Version 0.4.0.dev20200420':
 - OS: Linux
 - How you installed Ignite: pip
 - Python version: 3.6.5
 - Any other relevant information:




Discussion:
@bhedayat thanks for the report! Yes, this should be a bug mix between pytorch and ignite.

There are two solutions:
1) you can simply update ignite to a more late nigtly release (e.g. 0.4.0.dev20200507) where Engine is simplified and does not patch dataloader to be deterministic etc

2) If you need to use specifically ignite==0.4.0.dev20200420, then you need update pytorch to a higher version. It works for 1.5.0.


However, it is true that for pytorch 1.2.0 and the latest ignite using `DeterministicEngine` it fails in the same way you described:
```python
import torch
import ignite
print(torch.__version__, ignite.__version__)
> 1.2.0 0.4.0.dev20200507

import torch
from ignite.engine import Events, Engine
from ignite.engine.deterministic import DeterministicEngine
from ignite.contrib.handlers import ProgressBar
from ignite.handlers import EarlyStopping
from ignite.metrics import RunningAverage
import pdb


class DatasetUtils:
    def __init__(self, datapoints):
        self.datapoints = datapoints

    def load_data(self):
        for d in self.datapoints:
            return d

    def create_examples(self, data):
        for datapoint in data:
            return datapoint**2

    def __len__(self):
        return len(self.datapoints)

class MyDataset(torch.utils.data.Dataset):
    def __init__(self, datapoints):
        self.datapoints = datapoints

    def __getitem__(self, index):
        return datapoints[index]

    def __len__(self):
        return len(self.datapoints)

datapoints = [1, 2, 3, 4, 5]
helper = DatasetUtils(datapoints)

ds = MyDataset(datapoints)

data_loader = torch.utils.data.DataLoader(ds, num_workers=1, batch_size=1)

def update(engine, batch):
    print(batch)
    return 0

def inference(engine, batch):
    for i in data_loader:
        print(i)

trainer = DeterministicEngine(update)
validator = Engine(inference)

trainer.run(data_loader, max_epochs=2)
```


Resolving patch:
diff --git a/ignite/engine/deterministic.py b/ignite/engine/deterministic.py
--- a/ignite/engine/deterministic.py
+++ b/ignite/engine/deterministic.py
@@ -5,6 +5,8 @@
 from typing import Callable, Generator, Iterator, Optional
 
 import torch
+from torch.utils.data import DataLoader
+from torch.utils.data.sampler import BatchSampler
 
 from ignite.engine.engine import Engine
 from ignite.engine.events import Events
@@ -13,9 +15,7 @@
 __all__ = ["update_dataloader", "keep_random_state", "ReproducibleBatchSampler", "DeterministicEngine"]
 
 
-def update_dataloader(
-    dataloader: torch.utils.data.DataLoader, new_batch_sampler: torch.utils.data.sampler.BatchSampler
-) -> torch.utils.data.DataLoader:
+def update_dataloader(dataloader: DataLoader, new_batch_sampler: BatchSampler) -> DataLoader:
     """Helper function to replace current batch sampler of the dataloader by a new batch sampler. Function returns new
     dataloader with new batch sampler.
 
@@ -24,7 +24,7 @@ def update_dataloader(
         new_batch_sampler (torch.utils.data.sampler.BatchSampler): new batch sampler to use
 
     Returns:
-        torch.utils.data.DataLoader
+        DataLoader
     """
     params_keys = [k for k in dataloader.__dict__.keys() if not k.startswith("_")]
     for k in ["batch_size", "sampler", "drop_last", "batch_sampler", "dataset_kind"]:
@@ -35,7 +35,7 @@ def update_dataloader(
     return type(dataloader)(**params)
 
 
-class ReproducibleBatchSampler(torch.utils.data.sampler.BatchSampler):
+class ReproducibleBatchSampler(BatchSampler):
     """Reproducible batch sampler. This class internally iterates and stores indices of the input batch sampler.
     This helps to start providing data batches from an iteration in a deterministic way.
 
@@ -57,8 +57,8 @@ class ReproducibleBatchSampler(torch.utils.data.sampler.BatchSampler):
         start_iteration (int, optional): optional start iteration
     """
 
-    def __init__(self, batch_sampler: torch.utils.data.sampler.BatchSampler, start_iteration: Optional[int] = None):
-        if not isinstance(batch_sampler, torch.utils.data.sampler.BatchSampler):
+    def __init__(self, batch_sampler: BatchSampler, start_iteration: Optional[int] = None):
+        if not isinstance(batch_sampler, BatchSampler):
             raise TypeError("Argument batch_sampler should be torch.utils.data.sampler.BatchSampler")
 
         self.batch_indices = None
@@ -146,7 +146,7 @@ class DeterministicEngine(Engine):
                 setup_saved_rng_states()
             do_single_epoch_iterations(dataloader)
 
-    If input data provider is `torch.utils.data.DataLoader`, its batch sampler is replaced by
+    If input data provider is `DataLoader`, its batch sampler is replaced by
     :class:`~ignite.engine.deterministic.ReproducibleBatchSampler`.
 
     .. code-block:: python
@@ -203,9 +203,15 @@ def _setup_engine(self) -> None:
 
         # if input data is torch dataloader we replace batch sampler by a batch sampler
         # such that its random sampling indices are reproducible by prefetching them before data iteration
-        if isinstance(self.state.dataloader, torch.utils.data.DataLoader):
-            _dataloader_kind = self.state.dataloader._dataset_kind
-            if _dataloader_kind == torch.utils.data.dataloader._DatasetKind.Map:
+        if isinstance(self.state.dataloader, DataLoader):
+            # attribute _dataset_kind is introduced since 1.3.0 => before 1.3.0 all datasets are map-like
+            can_patch_dataloader = True
+            if hasattr(self.state.dataloader, "_dataset_kind"):
+                from torch.utils.data.dataloader import _DatasetKind
+
+                _dataloader_kind = self.state.dataloader._dataset_kind
+                can_patch_dataloader = _dataloader_kind == _DatasetKind.Map
+            if can_patch_dataloader:
                 if (self._dataloader_len is not None) and hasattr(self.state.dataloader.sampler, "epoch"):
                     if self._dataloader_len != self.state.epoch_length:
                         warnings.warn(
@@ -235,7 +241,7 @@ def _setup_engine(self) -> None:
 
     def _from_iteration(self, iteration: int) -> Iterator:
         data = self.state.dataloader
-        if isinstance(data, torch.utils.data.DataLoader):
+        if isinstance(data, DataLoader):
             try:
                 # following is unsafe for IterableDatasets
                 iteration %= len(data.batch_sampler)


Test patch:
diff --git a/tests/ignite/contrib/handlers/test_trains_logger.py b/tests/ignite/contrib/handlers/test_trains_logger.py
--- a/tests/ignite/contrib/handlers/test_trains_logger.py
+++ b/tests/ignite/contrib/handlers/test_trains_logger.py
@@ -3,8 +3,8 @@
 
 import pytest
 import torch
-
 import trains
+
 from ignite.contrib.handlers.trains_logger import *
 from ignite.engine import Engine, Events, State
 from ignite.handlers import Checkpoint
diff --git a/tests/ignite/engine/test_deterministic.py b/tests/ignite/engine/test_deterministic.py
--- a/tests/ignite/engine/test_deterministic.py
+++ b/tests/ignite/engine/test_deterministic.py
@@ -6,6 +6,7 @@
 import pytest
 import torch
 import torch.nn as nn
+from torch.utils.data import DataLoader
 
 from ignite.engine import Events
 from ignite.engine.deterministic import (
@@ -27,7 +28,7 @@ def _test(sampler_type=None):
         num_workers = 4
 
         sampler = setup_sampler(sampler_type, num_iters, batch_size)
-        dataloader = torch.utils.data.DataLoader(
+        dataloader = DataLoader(
             data,
             batch_size=batch_size,
             num_workers=num_workers,
@@ -48,7 +49,7 @@ def _test(sampler_type=None):
             seen_batches.append(t)
 
         sampler = setup_sampler(sampler_type, num_iters, batch_size)
-        dataloader = torch.utils.data.DataLoader(
+        dataloader = DataLoader(
             data,
             batch_size=batch_size,
             num_workers=num_workers,
@@ -251,7 +252,7 @@ def _test(epoch_length=None):
 
             for num_workers in [0, 4]:
                 sampler = _setup_sampler(sampler_type, num_iters, batch_size)
-                orig_dataloader = torch.utils.data.DataLoader(
+                orig_dataloader = DataLoader(
                     data,
                     batch_size=batch_size,
                     num_workers=num_workers,
@@ -283,7 +284,7 @@ def _(engine):
                 batch_checker = BatchChecker(seen_batchs, init_counter=resume_epoch * epoch_length)
 
                 sampler = _setup_sampler(sampler_type, num_iters, batch_size)
-                resume_dataloader = torch.utils.data.DataLoader(
+                resume_dataloader = DataLoader(
                     data,
                     batch_size=batch_size,
                     num_workers=num_workers,
@@ -358,7 +359,7 @@ def _test(epoch_length=None):
             for num_workers in [0, 4]:
 
                 sampler = _setup_sampler(sampler_type, num_iters, batch_size)
-                orig_dataloader = torch.utils.data.DataLoader(
+                orig_dataloader = DataLoader(
                     data,
                     batch_size=batch_size,
                     num_workers=num_workers,
@@ -389,7 +390,7 @@ def _(engine):
                 batch_checker = BatchChecker(seen_batchs, init_counter=resume_iteration)
 
                 sampler = _setup_sampler(sampler_type, num_iters, batch_size)
-                resume_dataloader = torch.utils.data.DataLoader(
+                resume_dataloader = DataLoader(
                     data,
                     batch_size=batch_size,
                     num_workers=num_workers,
@@ -813,7 +814,7 @@ def test_engine_with_dataloader_no_auto_batching():
     from torch.utils.data import DataLoader, BatchSampler, RandomSampler
 
     data = torch.rand(64, 4, 10)
-    data_loader = torch.utils.data.DataLoader(
+    data_loader = DataLoader(
         data, batch_size=None, sampler=BatchSampler(RandomSampler(data), batch_size=8, drop_last=True)
     )
 
@@ -850,3 +851,28 @@ def restart_iter():
 
     assert engine.state.epoch == 5
     assert engine.state.iteration == unknown_size * 5
+
+
+class OldDataLoader(DataLoader):
+    def __init__(self, dl, *args, **kwargs):
+        self.dl = dl
+        self.sampler = self.dl.sampler
+        self.batch_sampler = self.dl.batch_sampler
+
+    def __len__(self):
+        return len(self.dl)
+
+    def __iter__(self):
+        return iter(self.dl)
+
+
+def test_dataloader_no_dataset_kind():
+    # tests issue : https://github.com/pytorch/ignite/issues/1022
+
+    engine = DeterministicEngine(lambda e, b: None)
+
+    data = torch.randint(0, 1000, size=(100 * 4,))
+    dataloader = DataLoader(data, batch_size=4)
+    dataloader = OldDataLoader(dataloader)
+
+    engine.run(dataloader)
diff --git a/tests/ignite/metrics/test_dill.py b/tests/ignite/metrics/test_dill.py
--- a/tests/ignite/metrics/test_dill.py
+++ b/tests/ignite/metrics/test_dill.py
@@ -1,4 +1,5 @@
 import dill
+
 from ignite.metrics import Metric