Repository: deepset-ai/haystack

Issue:
Dumping a Pipeline to yaml is broken for certain kwargs
**Describe the bug**
I would like to dump and load a pipeline that contains the `TransformersSimilarityRanker` with the following initialization
```python
        component = TransformersSimilarityRanker(
            model_name_or_path="my_model",
            device="cuda",
            token="my_token",
            top_k=5,
            model_kwargs={"torch_dtype": torch.float16},
        )
```
Specifically, I want to be able to use `torch.float16` in `model_kwargs` to load the underlying model in half precision. 

However, when dumping this to yaml using
```python
        pipeline = Pipeline()
        pipeline.add_component("Comp1", component)
        with open(tmp_path / "out.yaml", "w") as f:
            pipeline.dump(f)
``` 
I get the error message
```bash
        pipeline = Pipeline()
        pipeline.add_component("Comp1", component)
        with open(tmp_path / "out.yaml", "w") as f:
>           pipeline.dump(f)

test/components/rankers/test_transformers_similarity.py:81: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
haystack/pipeline.py:134: in dump
    fp.write(marshaller.marshal(self.to_dict()))
haystack/marshal/yaml.py:8: in marshal
    return yaml.dump(dict_)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/__init__.py:253: in dump
    return dump_all([data], stream, Dumper=Dumper, **kwds)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/__init__.py:241: in dump_all
    dumper.represent(data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:27: in represent
    node = self.represent_data(data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:48: in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:207: in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:118: in represent_mapping
    node_value = self.represent_data(item_value)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:48: in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:207: in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:118: in represent_mapping
    node_value = self.represent_data(item_value)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:48: in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:207: in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:118: in represent_mapping
    node_value = self.represent_data(item_value)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:48: in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:207: in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:118: in represent_mapping
    node_value = self.represent_data(item_value)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:48: in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:207: in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:118: in represent_mapping
    node_value = self.represent_data(item_value)
/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:52: in represent_data
    node = self.yaml_multi_representers[data_type](self, data)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <yaml.dumper.Dumper object at 0x13e19db40>, data = torch.float16

    def represent_object(self, data):
        # We use __reduce__ API to save the data. data.__reduce__ returns
        # a tuple of length 2-5:
        #   (function, args, state, listitems, dictitems)
    
        # For reconstructing, we calls function(*args), then set its state,
        # listitems, and dictitems if they are not None.
    
        # A special case is when function.__name__ == '__newobj__'. In this
        # case we create the object with args[0].__new__(*args).
    
        # Another special case is when __reduce__ returns a string - we don't
        # support it.
    
        # We produce a !!python/object, !!python/object/new or
        # !!python/object/apply node.
    
        cls = type(data)
        if cls in copyreg.dispatch_table:
            reduce = copyreg.dispatch_table[cls](data)
        elif hasattr(data, '__reduce_ex__'):
            reduce = data.__reduce_ex__(2)
        elif hasattr(data, '__reduce__'):
            reduce = data.__reduce__()
        else:
            raise RepresenterError("cannot represent an object", data)
        reduce = (list(reduce)+[None]*5)[:5]
        function, args, state, listitems, dictitems = reduce
        args = list(args)
        if state is None:
            state = {}
        if listitems is not None:
            listitems = list(listitems)
        if dictitems is not None:
>           dictitems = dict(dictitems)
E           ValueError: dictionary update sequence element #0 has length 1; 2 is required

/opt/homebrew/Caskroom/miniforge/base/envs/haystack/lib/python3.10/site-packages/yaml/representer.py:330: ValueError

```

**Expected behavior**
For dumping and loading a component from a yaml file using a torch dtype to work. 

**Additional context**
- Just entering `torch.float16` as a string in model kwargs (e.g. `model_kwargs={"torch_dtype": "torch.float16"}` does not work because the underlying model loading using HF transformers does not support specifying torch_dtype as a string.  
- If we replace `torch.float16` with `"auto"` then dumping works as expected.
- We handled this in Haystack v1 by always converting `torch_dtype` to a string when serializing and then using the function `resolve_torch_dtype` (link [here](https://github.com/deepset-ai/haystack/blob/ca812cb3dab420e2fa377c30869b869e95b8f210/haystack/utils/torch_utils.py#L57)) to convert the string back into a valid torch dtype. Although in general how should we handle types of variables that are not serializable? 

**FAQ Check**
- [x] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?

**System:**
 - OS: Mac OS
 - GPU/CPU: CPU
 - Haystack version (commit or version number): Haystack v2



Discussion:
Affected components:
- TransformersSimilarityRanker (`model_kwargs`)
- ExtractiveReader (`model_kwargs`)
- HuggingFaceLocalGenerator (`model_kwargs` in `huggingface_pipeline_kwargs`)

There will probably be more in the future.



Patch:
diff --git a/haystack/components/generators/hugging_face_local.py b/haystack/components/generators/hugging_face_local.py
index b72ad0c5f6..e9faac2b2f 100644
--- a/haystack/components/generators/hugging_face_local.py
+++ b/haystack/components/generators/hugging_face_local.py
@@ -1,8 +1,7 @@
 import logging
 from typing import Any, Dict, List, Literal, Optional, Union
-from copy import deepcopy
 
-from haystack import component, default_to_dict
+from haystack import component, default_to_dict, default_from_dict
 from haystack.lazy_imports import LazyImport
 
 logger = logging.getLogger(__name__)
@@ -195,19 +194,70 @@ def to_dict(self) -> Dict[str, Any]:
         """
         Serialize this component to a dictionary.
         """
-        pipeline_kwargs_to_serialize = deepcopy(self.huggingface_pipeline_kwargs)
-
-        # we don't want to serialize valid tokens
-        if isinstance(pipeline_kwargs_to_serialize["token"], str):
-            pipeline_kwargs_to_serialize["token"] = None
-
-        return default_to_dict(
+        serialization_dict = default_to_dict(
             self,
-            huggingface_pipeline_kwargs=pipeline_kwargs_to_serialize,
+            huggingface_pipeline_kwargs=self.huggingface_pipeline_kwargs,
             generation_kwargs=self.generation_kwargs,
             stop_words=self.stop_words,
         )
 
+        huggingface_pipeline_kwargs = serialization_dict["init_parameters"]["huggingface_pipeline_kwargs"]
+        # we don't want to serialize valid tokens
+        if isinstance(huggingface_pipeline_kwargs["token"], str):
+            serialization_dict["init_parameters"]["huggingface_pipeline_kwargs"].pop("token")
+        # convert torch.dtype to string for serialization
+        # 1. torch_dtype can be specified in huggingface_pipeline_kwargs
+        torch_dtype = huggingface_pipeline_kwargs.get("torch_dtype", None)
+        if isinstance(torch_dtype, torch.dtype):
+            serialization_dict["init_parameters"]["huggingface_pipeline_kwargs"]["torch_dtype"] = str(torch_dtype)
+        # 2. torch_dtype and bnb_4bit_compute_dtype can be specified in model_kwargs
+        model_kwargs = huggingface_pipeline_kwargs.get("model_kwargs", {})
+        for key, value in model_kwargs.items():
+            if key in ["torch_dtype", "bnb_4bit_compute_dtype"] and isinstance(value, torch.dtype):
+                serialization_dict["init_parameters"]["huggingface_pipeline_kwargs"]["model_kwargs"][key] = str(value)
+        # 3. bnb_4bit_compute_dtype can be specified in model_kwargs["quantization_config"]
+        quantization_config = model_kwargs.get("quantization_config", {})
+        bnb_4bit_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
+        if isinstance(bnb_4bit_compute_dtype, torch.dtype):
+            serialization_dict["init_parameters"]["huggingface_pipeline_kwargs"]["model_kwargs"]["quantization_config"][
+                "bnb_4bit_compute_dtype"
+            ] = str(bnb_4bit_compute_dtype)
+
+        return serialization_dict
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "HuggingFaceLocalGenerator":
+        """
+        Deserialize this component from a dictionary.
+        """
+        torch_and_transformers_import.check()
+        init_params = data.get("init_parameters", {})
+        huggingface_pipeline_kwargs = init_params.get("huggingface_pipeline_kwargs", {})
+        model_kwargs = huggingface_pipeline_kwargs.get("model_kwargs", {})
+
+        # convert string to torch.dtype
+        # 1. torch_dtype can be specified in huggingface_pipeline_kwargs
+        torch_dtype = huggingface_pipeline_kwargs.get("torch_dtype", None)
+        if torch_dtype and torch_dtype.startswith("torch."):
+            data["init_parameters"]["huggingface_pipeline_kwargs"]["torch_dtype"] = getattr(
+                torch, torch_dtype.strip("torch.")
+            )
+        # 2. torch_dtype and bnb_4bit_compute_dtype can be specified in model_kwargs
+        for key, value in model_kwargs.items():
+            if key in ["torch_dtype", "bnb_4bit_compute_dtype"] and value.startswith("torch."):
+                data["init_parameters"]["huggingface_pipeline_kwargs"]["model_kwargs"][key] = getattr(
+                    torch, value.strip("torch.")
+                )
+        # 3. bnb_4bit_compute_dtype can be specified in model_kwargs["quantization_config"]
+        quantization_config = model_kwargs.get("quantization_config", {})
+        bnb_4bit_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
+        if bnb_4bit_compute_dtype and bnb_4bit_compute_dtype.startswith("torch."):
+            data["init_parameters"]["huggingface_pipeline_kwargs"]["model_kwargs"]["quantization_config"][
+                "bnb_4bit_compute_dtype"
+            ] = getattr(torch, bnb_4bit_compute_dtype.strip("torch."))
+
+        return default_from_dict(cls, data)
+
     @component.output_types(replies=List[str])
     def run(self, prompt: str, generation_kwargs: Optional[Dict[str, Any]] = None):
         """
diff --git a/haystack/components/rankers/transformers_similarity.py b/haystack/components/rankers/transformers_similarity.py
index 6dc163f5a3..033c06aa57 100644
--- a/haystack/components/rankers/transformers_similarity.py
+++ b/haystack/components/rankers/transformers_similarity.py
@@ -2,7 +2,7 @@
 from pathlib import Path
 from typing import List, Union, Dict, Any, Optional
 
-from haystack import ComponentError, Document, component, default_to_dict
+from haystack import ComponentError, Document, component, default_to_dict, default_from_dict
 from haystack.lazy_imports import LazyImport
 from haystack.utils import get_device
 
@@ -113,7 +113,7 @@ def to_dict(self) -> Dict[str, Any]:
         """
         Serialize this component to a dictionary.
         """
-        return default_to_dict(
+        serialization_dict = default_to_dict(
             self,
             device=self.device,
             model_name_or_path=self.model_name_or_path,
@@ -127,6 +127,45 @@ def to_dict(self) -> Dict[str, Any]:
             model_kwargs=self.model_kwargs,
         )
 
+        # convert torch.dtype to string for serialization
+        # 1. torch_dtype and bnb_4bit_compute_dtype can be specified in model_kwargs
+        model_kwargs = serialization_dict["init_parameters"]["model_kwargs"]
+        for key, value in model_kwargs.items():
+            if key in ["torch_dtype", "bnb_4bit_compute_dtype"] and isinstance(value, torch.dtype):
+                serialization_dict["init_parameters"]["model_kwargs"][key] = str(value)
+        # 2. bnb_4bit_compute_dtype can be specified in model_kwargs["quantization_config"]
+        quantization_config = model_kwargs.get("quantization_config", {})
+        bnb_4bit_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
+        if isinstance(bnb_4bit_compute_dtype, torch.dtype):
+            serialization_dict["init_parameters"]["model_kwargs"]["quantization_config"][
+                "bnb_4bit_compute_dtype"
+            ] = str(bnb_4bit_compute_dtype)
+
+        return serialization_dict
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "TransformersSimilarityRanker":
+        """
+        Deserialize this component from a dictionary.
+        """
+        torch_and_transformers_import.check()
+        init_params = data.get("init_parameters", {})
+        model_kwargs = init_params.get("model_kwargs", {})
+        # convert string to torch.dtype
+        # 1. torch_dtype and bnb_4bit_compute_dtype can be specified in model_kwargs
+        for key, value in model_kwargs.items():
+            if key in ["torch_dtype", "bnb_4bit_compute_dtype"] and value.startswith("torch."):
+                data["init_parameters"]["model_kwargs"][key] = getattr(torch, value.strip("torch."))
+        # 2. bnb_4bit_compute_dtype can be specified in model_kwargs["quantization_config"]
+        quantization_config = model_kwargs.get("quantization_config", {})
+        bnb_4bit_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
+        if isinstance(bnb_4bit_compute_dtype, str) and bnb_4bit_compute_dtype.startswith("torch."):
+            data["init_parameters"]["model_kwargs"]["quantization_config"]["bnb_4bit_compute_dtype"] = getattr(
+                torch, bnb_4bit_compute_dtype.strip("torch.")
+            )
+
+        return default_from_dict(cls, data)
+
     @component.output_types(documents=List[Document])
     def run(
         self,
diff --git a/haystack/components/readers/extractive.py b/haystack/components/readers/extractive.py
index dea016f77b..a26f66e9c4 100644
--- a/haystack/components/readers/extractive.py
+++ b/haystack/components/readers/extractive.py
@@ -4,7 +4,7 @@
 import warnings
 import logging
 
-from haystack import component, default_to_dict, ComponentError, Document, ExtractedAnswer
+from haystack import component, default_to_dict, default_from_dict, ComponentError, Document, ExtractedAnswer
 from haystack.lazy_imports import LazyImport
 from haystack.utils import get_device
 
@@ -112,7 +112,7 @@ def to_dict(self) -> Dict[str, Any]:
         """
         Serialize this component to a dictionary.
         """
-        return default_to_dict(
+        serialization_dict = default_to_dict(
             self,
             model_name_or_path=self.model_name_or_path,
             device=self.device,
@@ -128,6 +128,45 @@ def to_dict(self) -> Dict[str, Any]:
             model_kwargs=self.model_kwargs,
         )
 
+        # convert torch.dtype to string for serialization
+        # 1. torch_dtype and bnb_4bit_compute_dtype can be specified in model_kwargs
+        model_kwargs = serialization_dict["init_parameters"]["model_kwargs"]
+        for key, value in model_kwargs.items():
+            if key in ["torch_dtype", "bnb_4bit_compute_dtype"] and isinstance(value, torch.dtype):
+                serialization_dict["init_parameters"]["model_kwargs"][key] = str(value)
+        # 2. bnb_4bit_compute_dtype can be specified in model_kwargs["quantization_config"]
+        quantization_config = model_kwargs.get("quantization_config", {})
+        bnb_4bit_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
+        if isinstance(bnb_4bit_compute_dtype, torch.dtype):
+            serialization_dict["init_parameters"]["model_kwargs"]["quantization_config"][
+                "bnb_4bit_compute_dtype"
+            ] = str(bnb_4bit_compute_dtype)
+
+        return serialization_dict
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]) -> "ExtractiveReader":
+        """
+        Deserialize this component from a dictionary.
+        """
+        torch_and_transformers_import.check()
+        init_params = data.get("init_parameters", {})
+        model_kwargs = init_params.get("model_kwargs", {})
+        # convert string to torch.dtype
+        # 1. torch_dtype and bnb_4bit_compute_dtype can be specified in model_kwargs
+        for key, value in model_kwargs.items():
+            if key in ["torch_dtype", "bnb_4bit_compute_dtype"] and value.startswith("torch."):
+                data["init_parameters"]["model_kwargs"][key] = getattr(torch, value.strip("torch."))
+        # 2. bnb_4bit_compute_dtype can be specified in model_kwargs["quantization_config"]
+        quantization_config = model_kwargs.get("quantization_config", {})
+        bnb_4bit_compute_dtype = quantization_config.get("bnb_4bit_compute_dtype", None)
+        if isinstance(bnb_4bit_compute_dtype, str) and bnb_4bit_compute_dtype.startswith("torch."):
+            data["init_parameters"]["model_kwargs"]["quantization_config"]["bnb_4bit_compute_dtype"] = getattr(
+                torch, bnb_4bit_compute_dtype.strip("torch.")
+            )
+
+        return default_from_dict(cls, data)
+
     def warm_up(self):
         """
         Loads model and tokenizer
diff --git a/releasenotes/notes/serialize-torch-dtype-57c6c94f6114fc00.yaml b/releasenotes/notes/serialize-torch-dtype-57c6c94f6114fc00.yaml
new file mode 100644
index 0000000000..ba54d199dc
--- /dev/null
+++ b/releasenotes/notes/serialize-torch-dtype-57c6c94f6114fc00.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Correctly handle the serialization and deserialization of torch.dtype.
+    This concerns the following components:
+    ExtractiveReader, HuggingFaceLocalGenerator, and TransformersSimilarityRanker.


Test patch:
diff --git a/test/components/generators/test_hugging_face_local_generator.py b/test/components/generators/test_hugging_face_local_generator.py
index b83305c6b6..1e7bcf3061 100644
--- a/test/components/generators/test_hugging_face_local_generator.py
+++ b/test/components/generators/test_hugging_face_local_generator.py
@@ -153,6 +153,14 @@ def test_to_dict_with_parameters(self):
             token="test-token",
             generation_kwargs={"max_new_tokens": 100},
             stop_words=["coca", "cola"],
+            huggingface_pipeline_kwargs={
+                "model_kwargs": {
+                    "load_in_4bit": True,
+                    "bnb_4bit_use_double_quant": True,
+                    "bnb_4bit_quant_type": "nf4",
+                    "bnb_4bit_compute_dtype": torch.bfloat16,
+                }
+            },
         )
         data = component.to_dict()
 
@@ -162,14 +170,102 @@ def test_to_dict_with_parameters(self):
                 "huggingface_pipeline_kwargs": {
                     "model": "gpt2",
                     "task": "text-generation",
-                    "token": None,  # we don't want serialize valid tokens
                     "device": "cuda:0",
+                    "model_kwargs": {
+                        "load_in_4bit": True,
+                        "bnb_4bit_use_double_quant": True,
+                        "bnb_4bit_quant_type": "nf4",
+                        # dtype is correctly serialized
+                        "bnb_4bit_compute_dtype": "torch.bfloat16",
+                    },
                 },
                 "generation_kwargs": {"max_new_tokens": 100, "return_full_text": False},
                 "stop_words": ["coca", "cola"],
             },
         }
 
+    def test_to_dict_with_quantization_config(self):
+        component = HuggingFaceLocalGenerator(
+            model_name_or_path="gpt2",
+            task="text-generation",
+            device="cuda:0",
+            token="test-token",
+            generation_kwargs={"max_new_tokens": 100},
+            stop_words=["coca", "cola"],
+            huggingface_pipeline_kwargs={
+                "model_kwargs": {
+                    "quantization_config": {
+                        "load_in_4bit": True,
+                        "bnb_4bit_use_double_quant": True,
+                        "bnb_4bit_quant_type": "nf4",
+                        "bnb_4bit_compute_dtype": torch.bfloat16,
+                    }
+                }
+            },
+        )
+        data = component.to_dict()
+
+        assert data == {
+            "type": "haystack.components.generators.hugging_face_local.HuggingFaceLocalGenerator",
+            "init_parameters": {
+                "huggingface_pipeline_kwargs": {
+                    "model": "gpt2",
+                    "task": "text-generation",
+                    "device": "cuda:0",
+                    "model_kwargs": {
+                        "quantization_config": {
+                            "load_in_4bit": True,
+                            "bnb_4bit_use_double_quant": True,
+                            "bnb_4bit_quant_type": "nf4",
+                            # dtype is correctly serialized
+                            "bnb_4bit_compute_dtype": "torch.bfloat16",
+                        }
+                    },
+                },
+                "generation_kwargs": {"max_new_tokens": 100, "return_full_text": False},
+                "stop_words": ["coca", "cola"],
+            },
+        }
+
+    def test_from_dict(self):
+        data = {
+            "type": "haystack.components.generators.hugging_face_local.HuggingFaceLocalGenerator",
+            "init_parameters": {
+                "huggingface_pipeline_kwargs": {
+                    "model": "gpt2",
+                    "task": "text-generation",
+                    "device": "cuda:0",
+                    "model_kwargs": {
+                        "load_in_4bit": True,
+                        "bnb_4bit_use_double_quant": True,
+                        "bnb_4bit_quant_type": "nf4",
+                        # dtype is correctly serialized
+                        "bnb_4bit_compute_dtype": "torch.bfloat16",
+                    },
+                },
+                "generation_kwargs": {"max_new_tokens": 100, "return_full_text": False},
+                "stop_words": ["coca", "cola"],
+            },
+        }
+
+        component = HuggingFaceLocalGenerator.from_dict(data)
+
+        assert component.huggingface_pipeline_kwargs == {
+            "model": "gpt2",
+            "task": "text-generation",
+            "device": "cuda:0",
+            "token": None,
+            "model_kwargs": {
+                "load_in_4bit": True,
+                "bnb_4bit_use_double_quant": True,
+                "bnb_4bit_quant_type": "nf4",
+                # dtype is correctly deserialized
+                "bnb_4bit_compute_dtype": torch.bfloat16,
+            },
+        }
+        assert component.generation_kwargs == {"max_new_tokens": 100, "return_full_text": False}
+        assert component.stop_words == ["coca", "cola"]
+
     @patch("haystack.components.generators.hugging_face_local.pipeline")
     def test_warm_up(self, pipeline_mock):
         generator = HuggingFaceLocalGenerator(
diff --git a/test/components/rankers/test_transformers_similarity.py b/test/components/rankers/test_transformers_similarity.py
index 21566c4643..c81af7be53 100644
--- a/test/components/rankers/test_transformers_similarity.py
+++ b/test/components/rankers/test_transformers_similarity.py
@@ -36,7 +36,7 @@ def test_to_dict_with_custom_init_parameters(self):
             scale_score=False,
             calibration_factor=None,
             score_threshold=0.01,
-            model_kwargs={"torch_dtype": "auto"},
+            model_kwargs={"torch_dtype": torch.float16},
         )
         data = component.to_dict()
         assert data == {
@@ -51,10 +51,71 @@ def test_to_dict_with_custom_init_parameters(self):
                 "scale_score": False,
                 "calibration_factor": None,
                 "score_threshold": 0.01,
-                "model_kwargs": {"torch_dtype": "auto"},
+                "model_kwargs": {"torch_dtype": "torch.float16"},  # torch_dtype is correctly serialized
             },
         }
 
+    def test_to_dict_with_quantization_options(self):
+        component = TransformersSimilarityRanker(
+            model_kwargs={
+                "load_in_4bit": True,
+                "bnb_4bit_use_double_quant": True,
+                "bnb_4bit_quant_type": "nf4",
+                "bnb_4bit_compute_dtype": torch.bfloat16,
+            }
+        )
+        data = component.to_dict()
+        assert data == {
+            "type": "haystack.components.rankers.transformers_similarity.TransformersSimilarityRanker",
+            "init_parameters": {
+                "device": "cpu",
+                "top_k": 10,
+                "token": None,
+                "model_name_or_path": "cross-encoder/ms-marco-MiniLM-L-6-v2",
+                "meta_fields_to_embed": [],
+                "embedding_separator": "\n",
+                "scale_score": True,
+                "calibration_factor": 1.0,
+                "score_threshold": None,
+                "model_kwargs": {
+                    "load_in_4bit": True,
+                    "bnb_4bit_use_double_quant": True,
+                    "bnb_4bit_quant_type": "nf4",
+                    "bnb_4bit_compute_dtype": "torch.bfloat16",
+                },
+            },
+        }
+
+    def test_from_dict(self):
+        data = {
+            "type": "haystack.components.rankers.transformers_similarity.TransformersSimilarityRanker",
+            "init_parameters": {
+                "device": "cuda",
+                "model_name_or_path": "my_model",
+                "token": None,
+                "top_k": 5,
+                "meta_fields_to_embed": [],
+                "embedding_separator": "\n",
+                "scale_score": False,
+                "calibration_factor": None,
+                "score_threshold": 0.01,
+                "model_kwargs": {"torch_dtype": "torch.float16"},
+            },
+        }
+
+        component = TransformersSimilarityRanker.from_dict(data)
+        assert component.device == "cuda"
+        assert component.model_name_or_path == "my_model"
+        assert component.token is None
+        assert component.top_k == 5
+        assert component.meta_fields_to_embed == []
+        assert component.embedding_separator == "\n"
+        assert not component.scale_score
+        assert component.calibration_factor is None
+        assert component.score_threshold == 0.01
+        # torch_dtype is correctly deserialized
+        assert component.model_kwargs == {"torch_dtype": torch.float16}
+
     @patch("torch.sigmoid")
     @patch("torch.sort")
     def test_embed_meta(self, mocked_sort, mocked_sigmoid):
diff --git a/test/components/readers/test_extractive.py b/test/components/readers/test_extractive.py
index 3a62c33ad5..5d193db36d 100644
--- a/test/components/readers/test_extractive.py
+++ b/test/components/readers/test_extractive.py
@@ -88,7 +88,7 @@ def forward(self, input_ids, attention_mask, *args, **kwargs):
 
 
 def test_to_dict():
-    component = ExtractiveReader("my-model", token="secret-token", model_kwargs={"torch_dtype": "auto"})
+    component = ExtractiveReader("my-model", token="secret-token", model_kwargs={"torch_dtype": torch.float16})
     data = component.to_dict()
 
     assert data == {
@@ -105,7 +105,7 @@ def test_to_dict():
             "answers_per_seq": None,
             "no_answer": True,
             "calibration_factor": 0.1,
-            "model_kwargs": {"torch_dtype": "auto"},
+            "model_kwargs": {"torch_dtype": "torch.float16"},  # torch_dtype is correctly serialized
         },
     }
 
@@ -133,6 +133,40 @@ def test_to_dict_empty_model_kwargs():
     }
 
 
+def test_from_dict():
+    data = {
+        "type": "haystack.components.readers.extractive.ExtractiveReader",
+        "init_parameters": {
+            "model_name_or_path": "my-model",
+            "device": None,
+            "token": None,
+            "top_k": 20,
+            "score_threshold": None,
+            "max_seq_length": 384,
+            "stride": 128,
+            "max_batch_size": None,
+            "answers_per_seq": None,
+            "no_answer": True,
+            "calibration_factor": 0.1,
+            "model_kwargs": {"torch_dtype": "torch.float16"},
+        },
+    }
+
+    component = ExtractiveReader.from_dict(data)
+    assert component.model_name_or_path == "my-model"
+    assert component.token is None
+    assert component.top_k == 20
+    assert component.score_threshold is None
+    assert component.max_seq_length == 384
+    assert component.stride == 128
+    assert component.max_batch_size is None
+    assert component.answers_per_seq is None
+    assert component.no_answer
+    assert component.calibration_factor == 0.1
+    # torch_dtype is correctly deserialized
+    assert component.model_kwargs == {"torch_dtype": torch.float16}
+
+
 def test_output(mock_reader: ExtractiveReader):
     answers = mock_reader.run(example_queries[0], example_documents[0], top_k=3)[
         "answers"