We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
utils.save_image fails when passing list of images
utils.save_image fails when passing in a list of images, as the code tries to call .cpu on the list. 
Passing in a list should be possible according to the function's documentation.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `torchvision/utils.py`
Content:
```
1 import torch
2 import math
3 irange = range
4 
5 
6 def make_grid(tensor, nrow=8, padding=2,
7               normalize=False, range=None, scale_each=False, pad_value=0):
8     """Make a grid of images.
9 
10     Args:
11         tensor (Tensor or list): 4D mini-batch Tensor of shape (B x C x H x W)
12             or a list of images all of the same size.
13         nrow (int, optional): Number of images displayed in each row of the grid.
14             The Final grid size is (B / nrow, nrow). Default is 8.
15         padding (int, optional): amount of padding. Default is 2.
16         normalize (bool, optional): If True, shift the image to the range (0, 1),
17             by subtracting the minimum and dividing by the maximum pixel value.
18         range (tuple, optional): tuple (min, max) where min and max are numbers,
19             then these numbers are used to normalize the image. By default, min and max
20             are computed from the tensor.
21         scale_each (bool, optional): If True, scale each image in the batch of
22             images separately rather than the (min, max) over all images.
23         pad_value (float, optional): Value for the padded pixels.
24 
25     Example:
26         See this notebook `here <https://gist.github.com/anonymous/bf16430f7750c023141c562f3e9f2a91>`_
27 
28     """
29     if not (torch.is_tensor(tensor) or
30             (isinstance(tensor, list) and all(torch.is_tensor(t) for t in tensor))):
31         raise TypeError('tensor or list of tensors expected, got {}'.format(type(tensor)))
32 
33     # if list of tensors, convert to a 4D mini-batch Tensor
34     if isinstance(tensor, list):
35         tensor = torch.stack(tensor, dim=0)
36 
37     if tensor.dim() == 2:  # single image H x W
38         tensor = tensor.view(1, tensor.size(0), tensor.size(1))
39     if tensor.dim() == 3:  # single image
40         if tensor.size(0) == 1:  # if single-channel, convert to 3-channel
41             tensor = torch.cat((tensor, tensor, tensor), 0)
42         return tensor
43     if tensor.dim() == 4 and tensor.size(1) == 1:  # single-channel images
44         tensor = torch.cat((tensor, tensor, tensor), 1)
45 
46     if normalize is True:
47         tensor = tensor.clone()  # avoid modifying tensor in-place
48         if range is not None:
49             assert isinstance(range, tuple), \
50                 "range has to be a tuple (min, max) if specified. min and max are numbers"
51 
52         def norm_ip(img, min, max):
53             img.clamp_(min=min, max=max)
54             img.add_(-min).div_(max - min)
55 
56         def norm_range(t, range):
57             if range is not None:
58                 norm_ip(t, range[0], range[1])
59             else:
60                 norm_ip(t, t.min(), t.max())
61 
62         if scale_each is True:
63             for t in tensor:  # loop over mini-batch dimension
64                 norm_range(t, range)
65         else:
66             norm_range(tensor, range)
67 
68     # make the mini-batch of images into a grid
69     nmaps = tensor.size(0)
70     xmaps = min(nrow, nmaps)
71     ymaps = int(math.ceil(float(nmaps) / xmaps))
72     height, width = int(tensor.size(2) + padding), int(tensor.size(3) + padding)
73     grid = tensor.new(3, height * ymaps + padding, width * xmaps + padding).fill_(pad_value)
74     k = 0
75     for y in irange(ymaps):
76         for x in irange(xmaps):
77             if k >= nmaps:
78                 break
79             grid.narrow(1, y * height + padding, height - padding)\
80                 .narrow(2, x * width + padding, width - padding)\
81                 .copy_(tensor[k])
82             k = k + 1
83     return grid
84 
85 
86 def save_image(tensor, filename, nrow=8, padding=2,
87                normalize=False, range=None, scale_each=False, pad_value=0):
88     """Save a given Tensor into an image file.
89 
90     Args:
91         tensor (Tensor or list): Image to be saved. If given a mini-batch tensor,
92             saves the tensor as a grid of images by calling ``make_grid``.
93         **kwargs: Other arguments are documented in ``make_grid``.
94     """
95     from PIL import Image
96     tensor = tensor.cpu()
97     grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value,
98                      normalize=normalize, range=range, scale_each=scale_each)
99     ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).numpy()
100     im = Image.fromarray(ndarr)
101     im.save(filename)
102
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/torchvision/utils.py b/torchvision/utils.py
--- a/torchvision/utils.py
+++ b/torchvision/utils.py
@@ -93,9 +93,8 @@
         **kwargs: Other arguments are documented in ``make_grid``.
     """
     from PIL import Image
-    tensor = tensor.cpu()
     grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value,
                      normalize=normalize, range=range, scale_each=scale_each)
-    ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).numpy()
+    ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).cpu().numpy()
     im = Image.fromarray(ndarr)
     im.save(filename)

{"golden_diff": "diff --git a/torchvision/utils.py b/torchvision/utils.py\n--- a/torchvision/utils.py\n+++ b/torchvision/utils.py\n@@ -93,9 +93,8 @@\n         **kwargs: Other arguments are documented in ``make_grid``.\n     \"\"\"\n     from PIL import Image\n-    tensor = tensor.cpu()\n     grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value,\n                      normalize=normalize, range=range, scale_each=scale_each)\n-    ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).numpy()\n+    ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).cpu().numpy()\n     im = Image.fromarray(ndarr)\n     im.save(filename)\n", "issue": "utils.save_image fails when passing list of images\nutils.save_image fails when passing in a list of images, as the code tries to call .cpu on the list. \r\nPassing in a list should be possible according to the function's documentation.\n", "before_files": [{"content": "import torch\nimport math\nirange = range\n\n\ndef make_grid(tensor, nrow=8, padding=2,\n              normalize=False, range=None, scale_each=False, pad_value=0):\n    \"\"\"Make a grid of images.\n\n    Args:\n        tensor (Tensor or list): 4D mini-batch Tensor of shape (B x C x H x W)\n            or a list of images all of the same size.\n        nrow (int, optional): Number of images displayed in each row of the grid.\n            The Final grid size is (B / nrow, nrow). Default is 8.\n        padding (int, optional): amount of padding. Default is 2.\n        normalize (bool, optional): If True, shift the image to the range (0, 1),\n            by subtracting the minimum and dividing by the maximum pixel value.\n        range (tuple, optional): tuple (min, max) where min and max are numbers,\n            then these numbers are used to normalize the image. By default, min and max\n            are computed from the tensor.\n        scale_each (bool, optional): If True, scale each image in the batch of\n            images separately rather than the (min, max) over all images.\n        pad_value (float, optional): Value for the padded pixels.\n\n    Example:\n        See this notebook `here <https://gist.github.com/anonymous/bf16430f7750c023141c562f3e9f2a91>`_\n\n    \"\"\"\n    if not (torch.is_tensor(tensor) or\n            (isinstance(tensor, list) and all(torch.is_tensor(t) for t in tensor))):\n        raise TypeError('tensor or list of tensors expected, got {}'.format(type(tensor)))\n\n    # if list of tensors, convert to a 4D mini-batch Tensor\n    if isinstance(tensor, list):\n        tensor = torch.stack(tensor, dim=0)\n\n    if tensor.dim() == 2:  # single image H x W\n        tensor = tensor.view(1, tensor.size(0), tensor.size(1))\n    if tensor.dim() == 3:  # single image\n        if tensor.size(0) == 1:  # if single-channel, convert to 3-channel\n            tensor = torch.cat((tensor, tensor, tensor), 0)\n        return tensor\n    if tensor.dim() == 4 and tensor.size(1) == 1:  # single-channel images\n        tensor = torch.cat((tensor, tensor, tensor), 1)\n\n    if normalize is True:\n        tensor = tensor.clone()  # avoid modifying tensor in-place\n        if range is not None:\n            assert isinstance(range, tuple), \\\n                \"range has to be a tuple (min, max) if specified. min and max are numbers\"\n\n        def norm_ip(img, min, max):\n            img.clamp_(min=min, max=max)\n            img.add_(-min).div_(max - min)\n\n        def norm_range(t, range):\n            if range is not None:\n                norm_ip(t, range[0], range[1])\n            else:\n                norm_ip(t, t.min(), t.max())\n\n        if scale_each is True:\n            for t in tensor:  # loop over mini-batch dimension\n                norm_range(t, range)\n        else:\n            norm_range(tensor, range)\n\n    # make the mini-batch of images into a grid\n    nmaps = tensor.size(0)\n    xmaps = min(nrow, nmaps)\n    ymaps = int(math.ceil(float(nmaps) / xmaps))\n    height, width = int(tensor.size(2) + padding), int(tensor.size(3) + padding)\n    grid = tensor.new(3, height * ymaps + padding, width * xmaps + padding).fill_(pad_value)\n    k = 0\n    for y in irange(ymaps):\n        for x in irange(xmaps):\n            if k >= nmaps:\n                break\n            grid.narrow(1, y * height + padding, height - padding)\\\n                .narrow(2, x * width + padding, width - padding)\\\n                .copy_(tensor[k])\n            k = k + 1\n    return grid\n\n\ndef save_image(tensor, filename, nrow=8, padding=2,\n               normalize=False, range=None, scale_each=False, pad_value=0):\n    \"\"\"Save a given Tensor into an image file.\n\n    Args:\n        tensor (Tensor or list): Image to be saved. If given a mini-batch tensor,\n            saves the tensor as a grid of images by calling ``make_grid``.\n        **kwargs: Other arguments are documented in ``make_grid``.\n    \"\"\"\n    from PIL import Image\n    tensor = tensor.cpu()\n    grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value,\n                     normalize=normalize, range=range, scale_each=scale_each)\n    ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).numpy()\n    im = Image.fromarray(ndarr)\n    im.save(filename)\n", "path": "torchvision/utils.py"}], "after_files": [{"content": "import torch\nimport math\nirange = range\n\n\ndef make_grid(tensor, nrow=8, padding=2,\n              normalize=False, range=None, scale_each=False, pad_value=0):\n    \"\"\"Make a grid of images.\n\n    Args:\n        tensor (Tensor or list): 4D mini-batch Tensor of shape (B x C x H x W)\n            or a list of images all of the same size.\n        nrow (int, optional): Number of images displayed in each row of the grid.\n            The Final grid size is (B / nrow, nrow). Default is 8.\n        padding (int, optional): amount of padding. Default is 2.\n        normalize (bool, optional): If True, shift the image to the range (0, 1),\n            by subtracting the minimum and dividing by the maximum pixel value.\n        range (tuple, optional): tuple (min, max) where min and max are numbers,\n            then these numbers are used to normalize the image. By default, min and max\n            are computed from the tensor.\n        scale_each (bool, optional): If True, scale each image in the batch of\n            images separately rather than the (min, max) over all images.\n        pad_value (float, optional): Value for the padded pixels.\n\n    Example:\n        See this notebook `here <https://gist.github.com/anonymous/bf16430f7750c023141c562f3e9f2a91>`_\n\n    \"\"\"\n    if not (torch.is_tensor(tensor) or\n            (isinstance(tensor, list) and all(torch.is_tensor(t) for t in tensor))):\n        raise TypeError('tensor or list of tensors expected, got {}'.format(type(tensor)))\n\n    # if list of tensors, convert to a 4D mini-batch Tensor\n    if isinstance(tensor, list):\n        tensor = torch.stack(tensor, dim=0)\n\n    if tensor.dim() == 2:  # single image H x W\n        tensor = tensor.view(1, tensor.size(0), tensor.size(1))\n    if tensor.dim() == 3:  # single image\n        if tensor.size(0) == 1:  # if single-channel, convert to 3-channel\n            tensor = torch.cat((tensor, tensor, tensor), 0)\n        return tensor\n    if tensor.dim() == 4 and tensor.size(1) == 1:  # single-channel images\n        tensor = torch.cat((tensor, tensor, tensor), 1)\n\n    if normalize is True:\n        tensor = tensor.clone()  # avoid modifying tensor in-place\n        if range is not None:\n            assert isinstance(range, tuple), \\\n                \"range has to be a tuple (min, max) if specified. min and max are numbers\"\n\n        def norm_ip(img, min, max):\n            img.clamp_(min=min, max=max)\n            img.add_(-min).div_(max - min)\n\n        def norm_range(t, range):\n            if range is not None:\n                norm_ip(t, range[0], range[1])\n            else:\n                norm_ip(t, t.min(), t.max())\n\n        if scale_each is True:\n            for t in tensor:  # loop over mini-batch dimension\n                norm_range(t, range)\n        else:\n            norm_range(tensor, range)\n\n    # make the mini-batch of images into a grid\n    nmaps = tensor.size(0)\n    xmaps = min(nrow, nmaps)\n    ymaps = int(math.ceil(float(nmaps) / xmaps))\n    height, width = int(tensor.size(2) + padding), int(tensor.size(3) + padding)\n    grid = tensor.new(3, height * ymaps + padding, width * xmaps + padding).fill_(pad_value)\n    k = 0\n    for y in irange(ymaps):\n        for x in irange(xmaps):\n            if k >= nmaps:\n                break\n            grid.narrow(1, y * height + padding, height - padding)\\\n                .narrow(2, x * width + padding, width - padding)\\\n                .copy_(tensor[k])\n            k = k + 1\n    return grid\n\n\ndef save_image(tensor, filename, nrow=8, padding=2,\n               normalize=False, range=None, scale_each=False, pad_value=0):\n    \"\"\"Save a given Tensor into an image file.\n\n    Args:\n        tensor (Tensor or list): Image to be saved. If given a mini-batch tensor,\n            saves the tensor as a grid of images by calling ``make_grid``.\n        **kwargs: Other arguments are documented in ``make_grid``.\n    \"\"\"\n    from PIL import Image\n    grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value,\n                     normalize=normalize, range=range, scale_each=scale_each)\n    ndarr = grid.mul(255).clamp(0, 255).byte().permute(1, 2, 0).cpu().numpy()\n    im = Image.fromarray(ndarr)\n    im.save(filename)\n", "path": "torchvision/utils.py"}]}