Repository: ultralytics/ultralytics

Issue:
Inference speed is None after transferring results to CPU
### Search before asking

- [X] I have searched the YOLOv8 [issues](https://github.com/ultralytics/ultralytics/issues) and found no similar bug report.


### YOLOv8 Component

Predict

### Bug

I use the `speed` attribute to check the inference time of the network. However, I noticed that the speeds are becoming `None` after transferring the detection results to the CPU.

### Environment

Ultralytics YOLOv8.2.5 🚀 Python-3.10.13 torch-2.1.1+cu121 CUDA:0 (NVIDIA GeForce RTX 3090, 24257MiB)
Setup complete ✅ (32 CPUs, 62.7 GB RAM, 345.5/915.3 GB disk)

OS                  Linux-6.5.0-28-generic-x86_64-with-glibc2.35
Environment         Linux
Python              3.10.13
Install             pip
RAM                 62.71 GB
CPU                 AMD Ryzen 9 5950X 16-Core Processor
CUDA                12.1

matplotlib          ✅ 3.8.2>=3.3.0
opencv-python       ✅ 4.8.1.78>=4.6.0
pillow              ✅ 10.1.0>=7.1.2
pyyaml              ✅ 6.0>=5.3.1
requests            ✅ 2.31.0>=2.23.0
scipy               ✅ 1.11.4>=1.4.1
torch               ✅ 2.1.1>=1.8.0
torchvision         ✅ 0.16.1>=0.9.0
tqdm                ✅ 4.66.1>=4.64.0
psutil              ✅ 5.9.1
py-cpuinfo          ✅ 9.0.0
thop                ✅ 0.1.1-2209072238>=0.1.1
pandas              ✅ 1.4.2>=1.1.4
seaborn             ✅ 0.13.0>=0.11.0

### Minimal Reproducible Example

```python
from ultralytics import YOLO

path_to_weights = ...
path_to_image = ...

model = YOLO(path_to_weights)

results = model.predict(path_to_image)[0]

print(results.speed)

results = results.cpu()

print(results.speed)
```

This results in:

```
{'preprocess': 21.38376235961914, 'inference': 49.85451698303223, 'postprocess': 200.82473754882812}
{'preprocess': None, 'inference': None, 'postprocess': None}
```

### Additional

_No response_

### Are you willing to submit a PR?

- [x] Yes I'd like to help by submitting a PR!


Discussion:
👋 Hello @Rick-v-E, thank you for your interest in Ultralytics YOLOv8 🚀! We recommend a visit to the [Docs](https://docs.ultralytics.com) for new users where you can find many [Python](https://docs.ultralytics.com/usage/python/) and [CLI](https://docs.ultralytics.com/usage/cli/) usage examples and where many of the most common questions may already be answered.

If this is a 🐛 Bug Report, please provide a [minimum reproducible example](https://docs.ultralytics.com/help/minimum_reproducible_example/) to help us debug it.

If this is a custom training ❓ Question, please provide as much information as possible, including dataset image examples and training logs, and verify you are following our [Tips for Best Training Results](https://docs.ultralytics.com/yolov5/tutorials/tips_for_best_training_results/).

Join the vibrant [Ultralytics Discord](https://ultralytics.com/discord) 🎧 community for real-time conversations and collaborations. This platform offers a perfect space to inquire, showcase your work, and connect with fellow Ultralytics users.

## Install

Pip install the `ultralytics` package including all [requirements](https://github.com/ultralytics/ultralytics/blob/main/pyproject.toml) in a [**Python>=3.8**](https://www.python.org/) environment with [**PyTorch>=1.8**](https://pytorch.org/get-started/locally/).

```bash
pip install ultralytics
```

## Environments

YOLOv8 may be run in any of the following up-to-date verified environments (with all dependencies including [CUDA](https://developer.nvidia.com/cuda)/[CUDNN](https://developer.nvidia.com/cudnn), [Python](https://www.python.org/) and [PyTorch](https://pytorch.org/) preinstalled):

- **Notebooks** with free GPU: <a href="https://console.paperspace.com/github/ultralytics/ultralytics"><img src="https://assets.paperspace.io/img/gradient-badge.svg" alt="Run on Gradient"/></a> <a href="https://colab.research.google.com/github/ultralytics/ultralytics/blob/main/examples/tutorial.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"></a> <a href="https://www.kaggle.com/ultralytics/yolov8"><img src="https://kaggle.com/static/images/open-in-kaggle.svg" alt="Open In Kaggle"></a>
- **Google Cloud** Deep Learning VM. See [GCP Quickstart Guide](https://docs.ultralytics.com/yolov5/environments/google_cloud_quickstart_tutorial/)
- **Amazon** Deep Learning AMI. See [AWS Quickstart Guide](https://docs.ultralytics.com/yolov5/environments/aws_quickstart_tutorial/)
- **Docker Image**. See [Docker Quickstart Guide](https://docs.ultralytics.com/yolov5/environments/docker_image_quickstart_tutorial/) <a href="https://hub.docker.com/r/ultralytics/ultralytics"><img src="https://img.shields.io/docker/pulls/ultralytics/ultralytics?logo=docker" alt="Docker Pulls"></a>

## Status

<a href="https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml?query=event%3Aschedule"><img src="https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml/badge.svg" alt="Ultralytics CI"></a>

If this badge is green, all [Ultralytics CI](https://github.com/ultralytics/ultralytics/actions/workflows/ci.yaml?query=event%3Aschedule) tests are currently passing. CI tests verify correct operation of all YOLOv8 [Modes](https://docs.ultralytics.com/modes/) and [Tasks](https://docs.ultralytics.com/tasks/) on macOS, Windows, and Ubuntu every 24 hours and on every commit.
Hi there! It looks like the issue you're encountering with the `speed` attribute becoming `None` after transferring results to the CPU is related to how the `cpu()` method is handling the speed tracking. The `.cpu()` method typically moves data from GPU memory to CPU memory but does not carry over the speed metrics, which explains why they are set to `None`.

To retain the speed information after moving your results to CPU, you can simply copy the speed dictionary before calling `.cpu()`. Here's how you can modify your script:

```python
from ultralytics import YOLO

path_to_weights = ...
path_to_image = ...

model = YOLO(path_to_weights)

results = model.predict(path_to_image)[0]

# Store speed data before moving to CPU
speed_data = results.speed.copy()

results = results.cpu()

# Restoring the speed data after the transfer
results.speed = speed_data

print(results.speed)
```

This modification ensures that the inference speed data is preserved and accessible after the results have been transferred to the CPU. Hope this helps! 😊
Hi, thanks for your answer! :) That' s indeed what I am doing now and works fine, however, I was a bit surprised that the `speed` attribute was missing after moving the GPU data to the CPU. 

I initially understood that calling the `.cpu()` function transfers all tensors to the CPU, however when looking at the code, it creates a new copy of the `Results` object. Every attribute is copied except for the `speed` attribute. Does it make sense to copy that attribute as well?
Hello! I'm glad to hear that the solution worked for you! 😊 Indeed, the `.cpu()` function transfers tensor data to the CPU by creating a new `Results` object and currently, it does not automatically include the `speed` attribute. Copying the `speed` attribute as well could be beneficial for maintaining a complete record of the inference performance even after the data transfer. This could potentially be a useful enhancement for the library!

If you think this feature would be beneficial, you may consider opening a feature request on GitHub or if you're inclined to contribute, submitting a pull request to include this functionality. The community contributions help in making the tool more robust and user-friendly! Thank you for your feedback and suggestions! 🚀

Resolving patch:
diff --git a/ultralytics/engine/results.py b/ultralytics/engine/results.py
--- a/ultralytics/engine/results.py
+++ b/ultralytics/engine/results.py
@@ -94,7 +94,9 @@ class Results(SimpleClass):
         tojson(normalize=False): Converts detection results to JSON format.
     """
 
-    def __init__(self, orig_img, path, names, boxes=None, masks=None, probs=None, keypoints=None, obb=None) -> None:
+    def __init__(
+        self, orig_img, path, names, boxes=None, masks=None, probs=None, keypoints=None, obb=None, speed=None
+    ) -> None:
         """
         Initialize the Results class.
 
@@ -115,7 +117,7 @@ def __init__(self, orig_img, path, names, boxes=None, masks=None, probs=None, ke
         self.probs = Probs(probs) if probs is not None else None
         self.keypoints = Keypoints(keypoints, self.orig_shape) if keypoints is not None else None
         self.obb = OBB(obb, self.orig_shape) if obb is not None else None
-        self.speed = {"preprocess": None, "inference": None, "postprocess": None}  # milliseconds per image
+        self.speed = speed if speed is not None else {"preprocess": None, "inference": None, "postprocess": None}
         self.names = names
         self.path = path
         self.save_dir = None
@@ -180,8 +182,8 @@ def to(self, *args, **kwargs):
         return self._apply("to", *args, **kwargs)
 
     def new(self):
-        """Return a new Results object with the same image, path, and names."""
-        return Results(orig_img=self.orig_img, path=self.path, names=self.names)
+        """Return a new Results object with the same image, path, names and speed."""
+        return Results(orig_img=self.orig_img, path=self.path, names=self.names, speed=self.speed)
 
     def plot(
         self,


Test patch: