Repository: IBM/aihwkit (language: python)

Issue:
Loading a new model state dict for inference does not overwrite the noise model setting 
## Description

When using 
```python
model.load_state_dict(other_model.state_dict(), load_rpu_config=False)
```
the `noise_model` and `drift_compensation` fields are updated  from the `other_model` instead of remaining the same.  

## Expected behavior

In case of `load_rpu_config=False` the expected behaviour is that all `RPUConfig` related settings should remain the same as in the loading model `model`.


Loading a new model state dict for inference does not overwrite the noise model setting 
## Description

When using 
```python
model.load_state_dict(other_model.state_dict(), load_rpu_config=False)
```
the `noise_model` and `drift_compensation` fields are updated  from the `other_model` instead of remaining the same.  

## Expected behavior

In case of `load_rpu_config=False` the expected behaviour is that all `RPUConfig` related settings should remain the same as in the loading model `model`.




Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 805b2c78..6660d58e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -36,8 +36,7 @@ job_compile_common: &job_compile_common
   install:
     # Set the python executable, to force cmake picking the right one.
     - PYTHON_EXECUTABLE=~/virtualenv/python$TRAVIS_PYTHON_VERSION/bin/python$TRAVIS_PYTHON_VERSION
-    - $PYTHON_EXECUTABLE -m pip install -r requirements.txt pytest
-    - $PYTHON_EXECUTABLE -m pip install -r requirements-dev.txt
+    - $PYTHON_EXECUTABLE -m pip install -r requirements.txt
     # Install the package in editable mode.
     - VERBOSE=1 $PYTHON_EXECUTABLE -m pip install -v -e ".[visualization]"
   script:
@@ -97,8 +96,8 @@ jobs:
     install:
       # Set the python executable, to force cmake picking the right one.
       - PYTHON_EXECUTABLE=~/virtualenv/python$TRAVIS_PYTHON_VERSION/bin/python$TRAVIS_PYTHON_VERSION
-      - $PYTHON_EXECUTABLE -m pip install -r requirements.txt pytest
       - $PYTHON_EXECUTABLE -m pip install "torch==1.7.1"
+      - $PYTHON_EXECUTABLE -m pip install -r requirements.txt
       # Install the package in editable mode.
       - VERBOSE=1 $PYTHON_EXECUTABLE -m pip install -v -e ".[visualization]"
     env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f56da10f..1d4c4ec5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,10 @@ The format is based on [Keep a Changelog], and this project adheres to
 * Example 22: 2 layer LSTM network trained on War and Peace dataset. (\#391)
 * Notebook for exploring analog sensitivities. (\#380)
 * Remapping functionality for ``InferenceRPUConfig``. (\#388)
+* Inference experiment and runners. (\#410)
+* Added ``analog_modules`` generator in ``AnalogSequential``. (\#410)
+* Added ``SKIP_CUDA_TESTS`` to manually switch off the CUDA tests
+* Enabling comparisons of ``RPUConfig`` instances. (\#410)
 
 ### Fixed
 * Analog_summary error when model is on cuda device. (\#392)
@@ -27,6 +31,9 @@ The format is based on [Keep a Changelog], and this project adheres to
 * Weights that were not contiguous could have set wrongly. (\#388)
 * Programming noise would not be applied if drift compensation was not
   used. (\#389)
+* Loading a new model state dict for inference does not overwrite the
+  noise model setting. (\#410)
+* Avoid ``AnalogContext`` copying of self pointers. (\#410)
 
 ### Changed
 * Weight noise visualization now shows the programming noise and drift
diff --git a/examples/02_multiple_layer.py b/examples/02_multiple_layer.py
index 12a44106..63c4c4ff 100644
--- a/examples/02_multiple_layer.py
+++ b/examples/02_multiple_layer.py
@@ -1,7 +1,7 @@
 
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/examples/21_fit_device_data.py b/examples/21_fit_device_data.py
index 629dcf2a..005f1d22 100644
--- a/examples/21_fit_device_data.py
+++ b/examples/21_fit_device_data.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/examples/22_war_and_peace_lstm.py b/examples/22_war_and_peace_lstm.py
index f4d420cb..587e1641 100644
--- a/examples/22_war_and_peace_lstm.py
+++ b/examples/22_war_and_peace_lstm.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/setup.py b/setup.py
index 7b941f4d..83f55e11 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@
     'scipy',
     'requests>=2.25,<3',
     'numpy>=1.18',
-    'protobuf>=3.13.0,<4'
+    'protobuf>=3.13.0,<4',
 ]
 
 def get_version() -> str:
diff --git a/src/aihwkit/cloud/client/entities.py b/src/aihwkit/cloud/client/entities.py
index d96873ce..5b410b83 100644
--- a/src/aihwkit/cloud/client/entities.py
+++ b/src/aihwkit/cloud/client/entities.py
@@ -38,6 +38,7 @@ class CloudExperimentCategory(Enum):
     """Category of a CloudExperiment."""
 
     BASIC_TRAINING = 1
+    BASIC_INFERENCE = 2
 
 
 @dataclass
diff --git a/src/aihwkit/cloud/client/session.py b/src/aihwkit/cloud/client/session.py
index 2cceb830..b422b8c5 100644
--- a/src/aihwkit/cloud/client/session.py
+++ b/src/aihwkit/cloud/client/session.py
@@ -81,7 +81,7 @@ def __init__(
         self.api_token = api_token
         self.verify = verify
         if not verify:
-            urllib3.disable_warnings(InsecureRequestWarning)
+            urllib3.disable_warnings(InsecureRequestWarning)  # type: ignore[no-untyped-call]
 
         self.jwt_token = None  # type: Optional[str]
 
diff --git a/src/aihwkit/cloud/client/v1/api_client.py b/src/aihwkit/cloud/client/v1/api_client.py
index fbb19ae8..6d92b0c9 100644
--- a/src/aihwkit/cloud/client/v1/api_client.py
+++ b/src/aihwkit/cloud/client/v1/api_client.py
@@ -105,22 +105,28 @@ def experiment_create(
             A ``CloudExperiment``.
         """
         # Prepare the API data.
+        # debug: print('input_: ', input_)
         payload = self.converter.to_proto(input_).SerializeToString()
+        # debug: print('payload after convert to proto and serialize to string: \n', payload)
 
         # Create the experiment.
         response = self.experiments.post({'name': name,
                                           'category': 'train'})
+        # debug: print('response from experiments.post: ', response)
         experiment = ExperimentParser.parse_experiment(response, self)
 
         # Create the input.
         response = self.inputs.post({'experiment': experiment.id_,
                                      'device': device})
         object_storage_url = response['url']
+        # debug: print ('url: \n', object_storage_url)
         _ = self.object_storage_session.put(url=object_storage_url, data=payload)
 
         # Create the job.
-        response = self.jobs.post({'experiment': experiment.id_})
+        response = self.jobs.post({'device': device, 'experiment': experiment.id_})
+        # debug: print('response from jobs.post: ', response)
         experiment.job = ExperimentParser.parse_job(response)
+        # debug: print('In experiment_create: experiment.job: \n', experiment.job)
 
         return experiment
 
diff --git a/src/aihwkit/cloud/client/v1/i_api_client.py b/src/aihwkit/cloud/client/v1/i_api_client.py
new file mode 100644
index 00000000..00fa7974
--- /dev/null
+++ b/src/aihwkit/cloud/client/v1/i_api_client.py
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""API client the AIHW Composer API."""
+
+from typing import List, Dict
+
+from aihwkit.cloud.client.entities import CloudExperiment, CloudJob
+from aihwkit.cloud.client.exceptions import ApiResponseError, CredentialsError
+from aihwkit.cloud.client.session import ObjectStorageSession, ApiSession
+from aihwkit.cloud.client.v1.parsers import ExperimentParser, GeneralParser
+from aihwkit.cloud.client.v1.stubs import (
+    ExperimentStub, InputStub, JobStub, LoginStub, OutputStub
+)
+from aihwkit.cloud.converter.v1.inferencing import BasicInferencingConverter
+from aihwkit.experiments.experiments.inferencing import BasicInferencing
+
+
+class InferenceApiClient:
+    """API client the AIHW Composer API.
+
+    Client for interfacing with the AIHW Composer API. Upon instantiation, the
+    client will attempt to login (using the details from the ``session``).
+
+    The functionality is provided by the stubs that are created during the
+    instantiation, providing an interface that which mimic the REST API
+    endpoints.
+    """
+
+    def __init__(self, session: ApiSession):
+        """Create a new ``ApiClient``.
+
+        Args:
+            session: the request session to be used in the client.
+        """
+        self.session = session
+        self.object_storage_session = ObjectStorageSession()
+
+        # Create the helpers.
+        self.converter = BasicInferencingConverter()
+
+        # Create the stubs.
+        self.experiments = ExperimentStub(self.session)
+        self.inputs = InputStub(self.session)
+        self.outputs = OutputStub(self.session)
+        self.jobs = JobStub(self.session)
+        self.login_ = LoginStub(self.session)
+
+        # Automatically login.
+        self.login()
+
+    def login(self) -> None:
+        """Login into the application.
+
+        Raises:
+            CredentialsError: if the credentials are not valid.
+            ApiResponseError: if the request was not successful.
+        """
+        try:
+            response = self.login_.post({'token': self.session.api_token})
+        except ApiResponseError as ex:
+            if ex.response.status_code == 400:
+                try:
+                    json_response = ex.response.json()
+                except Exception:  # pylint: disable=broad-except
+                    json_response = {}
+                raise CredentialsError('Error while trying to log in: {}'.format(
+                    json_response.get('message', 'unknown')
+                )) from ex
+            raise
+
+        jwt_token = GeneralParser.parse_login(response)
+        self.session.update_jwt_token(jwt_token)
+
+    def experiments_list(self) -> List[CloudExperiment]:
+        """Return a list of experiments."""
+        response = self.experiments.list()
+
+        return [ExperimentParser.parse_experiment(experiment, self)
+                for experiment in response]
+
+    def experiment_create(
+            self,
+            input_: BasicInferencing,
+            analog_info: Dict,
+            noise_model_info: Dict,
+            name: str,
+            device: str = 'gpu'
+    ) -> CloudExperiment:
+        """Create a new experiment, queuing its execution.
+
+        Args:
+            input_: the experiment to be executed.
+            analog_info: analog information.
+            noise_model_info: noise information.
+            name: the name of the experiment.
+            device: the desired device.
+
+        Returns:
+            A ``CloudExperiment``.
+        """
+        # Prepare the API data.
+        # debug: print('input_: ', input_)
+        payload = self.converter.to_proto(input_, analog_info, noise_model_info).SerializeToString()
+        # payload = self.converter.to_proto(input_)
+        # debug: print('payload after convert to proto and serialize to string: \n', payload)
+
+        # Create the experiment.
+        response = self.experiments.post({'name': name,
+                                          'category': 'inference'})
+        # debug: print('response from experiments.post: ', response)
+        experiment = ExperimentParser.parse_experiment(response, self)
+
+        # Create the input.
+        response = self.inputs.post({'experiment': experiment.id_,
+                                     'device': device})
+        object_storage_url = response['url']
+        # debug: print ('url: \n', object_storage_url)
+        _ = self.object_storage_session.put(url=object_storage_url, data=payload)
+
+        # Create the job.
+        response = self.jobs.post({'device': device, 'experiment': experiment.id_})
+        # debug: print('response from jobs.post: ', response)
+        experiment.job = ExperimentParser.parse_job(response)
+        # debug: print('In experiment_create: experiment.job: \n', experiment.job)
+
+        return experiment
+
+    def experiment_get(self, experiment_id: str) -> CloudExperiment:
+        """Get an existing job by id.
+
+        Args:
+            experiment_id: id of the experiment.
+
+        Returns:
+            A `CloudExperiment` with the specified id.
+        """
+        response = self.experiments.get(experiment_id)
+
+        return ExperimentParser.parse_experiment(response, self)
+
+    def job_get(self, job_id: str) -> CloudJob:
+        """Get an existing job by id.
+
+        Args:
+            job_id: id of the job.
+
+        Returns:
+            A `CloudJob` with the specified id.
+        """
+        response = self.jobs.get(job_id)
+
+        return ExperimentParser.parse_job(response)
+
+    def input_get(self, input_id: str) -> bytes:
+        """Get an existing input by id.
+
+        Args:
+            input_id: id of the input.
+
+        Returns:
+            The input with the specified id, in protobuf format.
+        """
+        response = self.inputs.get(input_id)
+        object_storage_url = response['url']
+
+        object_storage_response = self.object_storage_session.get(object_storage_url)
+
+        return object_storage_response.content
+
+    def output_get(self, output_id: str) -> bytes:
+        """Get an existing output by id.
+
+        Args:
+            output_id: id of the output.
+
+        Returns:
+            The output with the specified id, in protobuf format.
+        """
+        response = self.outputs.get(output_id)
+        object_storage_url = response['url']
+
+        object_storage_response = self.object_storage_session.get(object_storage_url)
+
+        return object_storage_response.content
diff --git a/src/aihwkit/cloud/client/v1/parsers.py b/src/aihwkit/cloud/client/v1/parsers.py
index dfa28f72..24ae28f4 100644
--- a/src/aihwkit/cloud/client/v1/parsers.py
+++ b/src/aihwkit/cloud/client/v1/parsers.py
@@ -46,8 +46,12 @@ def parse_experiment(api_response: Dict, api_client: Any) -> CloudExperiment:
             job=None
         )
 
-        if api_response.get('input', {}).get('id'):
-            experiment.input_id = api_response['input']['id']
+        # debug
+        # print('api_response: ', api_response)
+
+        if api_response.get('input'):
+            if api_response.get('input', {}).get('id'):
+                experiment.input_id = api_response['input']['id']
 
         if api_response.get('job', None):
             experiment.job = ExperimentParser.parse_job(api_response['job'])
@@ -122,6 +126,9 @@ def parse_experiment_category(api_response: Dict) -> CloudExperimentCategory:
         if job_category in ('train', 'trainweb'):
             return CloudExperimentCategory.BASIC_TRAINING
 
+        if job_category in ('inference', 'inferenceweb'):
+            return CloudExperimentCategory.BASIC_INFERENCE
+
         raise InvalidResponseFieldError(
             'Unsupported experiment category: {}'.format(job_category))
 
diff --git a/src/aihwkit/cloud/converter/definitions/i_common_pb2.py b/src/aihwkit/cloud/converter/definitions/i_common_pb2.py
new file mode 100644
index 00000000..ab7e88dc
--- /dev/null
+++ b/src/aihwkit/cloud/converter/definitions/i_common_pb2.py
@@ -0,0 +1,531 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: i_common.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import i_onnx_common_pb2 as i__onnx__common__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='i_common.proto',
+  package='aihwx.inferencing',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x0ei_common.proto\x12\x11\x61ihwx.inferencing\x1a\x13i_onnx_common.proto\"R\n\x0eOptimizerProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"R\n\x0ePrimitiveProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"R\n\x0eTransformProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"\x85\x01\n\nLayerProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\x12\x35\n\nstate_dict\x18\x03 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"[\n\x17\x41\x63tivationFunctionProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"\x9e\x01\n\x19LayerOrActivationFunction\x12I\n\x13\x61\x63tivation_function\x18\x01 \x01(\x0b\x32*.aihwx.inferencing.ActivationFunctionProtoH\x00\x12.\n\x05layer\x18\x02 \x01(\x0b\x32\x1d.aihwx.inferencing.LayerProtoH\x00\x42\x06\n\x04item\"R\n\x0eSchedulerProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"U\n\x11LossFunctionProto\x12\n\n\x02id\x18\x01 \x02(\t\x12\x34\n\targuments\x18\x02 \x03(\x0b\x32!.aihwx.inferencing.AttributeProto\"c\n\x07Network\x12\x1a\n\x12weight_template_id\x18\x01 \x02(\t\x12<\n\x06layers\x18\x02 \x03(\x0b\x32,.aihwx.inferencing.LayerOrActivationFunction\"(\n\x07Version\x12\x0e\n\x06schema\x18\x01 \x02(\x03\x12\r\n\x05opset\x18\x02 \x02(\x03'
+  ,
+  dependencies=[i__onnx__common__pb2.DESCRIPTOR,])
+
+
+
+
+_OPTIMIZERPROTO = _descriptor.Descriptor(
+  name='OptimizerProto',
+  full_name='aihwx.inferencing.OptimizerProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.OptimizerProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.OptimizerProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=58,
+  serialized_end=140,
+)
+
+
+_PRIMITIVEPROTO = _descriptor.Descriptor(
+  name='PrimitiveProto',
+  full_name='aihwx.inferencing.PrimitiveProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.PrimitiveProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.PrimitiveProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=142,
+  serialized_end=224,
+)
+
+
+_TRANSFORMPROTO = _descriptor.Descriptor(
+  name='TransformProto',
+  full_name='aihwx.inferencing.TransformProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.TransformProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.TransformProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=226,
+  serialized_end=308,
+)
+
+
+_LAYERPROTO = _descriptor.Descriptor(
+  name='LayerProto',
+  full_name='aihwx.inferencing.LayerProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.LayerProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.LayerProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state_dict', full_name='aihwx.inferencing.LayerProto.state_dict', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=311,
+  serialized_end=444,
+)
+
+
+_ACTIVATIONFUNCTIONPROTO = _descriptor.Descriptor(
+  name='ActivationFunctionProto',
+  full_name='aihwx.inferencing.ActivationFunctionProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.ActivationFunctionProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.ActivationFunctionProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=446,
+  serialized_end=537,
+)
+
+
+_LAYERORACTIVATIONFUNCTION = _descriptor.Descriptor(
+  name='LayerOrActivationFunction',
+  full_name='aihwx.inferencing.LayerOrActivationFunction',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='activation_function', full_name='aihwx.inferencing.LayerOrActivationFunction.activation_function', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='layer', full_name='aihwx.inferencing.LayerOrActivationFunction.layer', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='item', full_name='aihwx.inferencing.LayerOrActivationFunction.item',
+      index=0, containing_type=None,
+      create_key=_descriptor._internal_create_key,
+    fields=[]),
+  ],
+  serialized_start=540,
+  serialized_end=698,
+)
+
+
+_SCHEDULERPROTO = _descriptor.Descriptor(
+  name='SchedulerProto',
+  full_name='aihwx.inferencing.SchedulerProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.SchedulerProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.SchedulerProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=700,
+  serialized_end=782,
+)
+
+
+_LOSSFUNCTIONPROTO = _descriptor.Descriptor(
+  name='LossFunctionProto',
+  full_name='aihwx.inferencing.LossFunctionProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='aihwx.inferencing.LossFunctionProto.id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='arguments', full_name='aihwx.inferencing.LossFunctionProto.arguments', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=784,
+  serialized_end=869,
+)
+
+
+_NETWORK = _descriptor.Descriptor(
+  name='Network',
+  full_name='aihwx.inferencing.Network',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='weight_template_id', full_name='aihwx.inferencing.Network.weight_template_id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='layers', full_name='aihwx.inferencing.Network.layers', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=871,
+  serialized_end=970,
+)
+
+
+_VERSION = _descriptor.Descriptor(
+  name='Version',
+  full_name='aihwx.inferencing.Version',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='schema', full_name='aihwx.inferencing.Version.schema', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='opset', full_name='aihwx.inferencing.Version.opset', index=1,
+      number=2, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=972,
+  serialized_end=1012,
+)
+
+_OPTIMIZERPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_PRIMITIVEPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_TRANSFORMPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_LAYERPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_LAYERPROTO.fields_by_name['state_dict'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_ACTIVATIONFUNCTIONPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_LAYERORACTIVATIONFUNCTION.fields_by_name['activation_function'].message_type = _ACTIVATIONFUNCTIONPROTO
+_LAYERORACTIVATIONFUNCTION.fields_by_name['layer'].message_type = _LAYERPROTO
+_LAYERORACTIVATIONFUNCTION.oneofs_by_name['item'].fields.append(
+  _LAYERORACTIVATIONFUNCTION.fields_by_name['activation_function'])
+_LAYERORACTIVATIONFUNCTION.fields_by_name['activation_function'].containing_oneof = _LAYERORACTIVATIONFUNCTION.oneofs_by_name['item']
+_LAYERORACTIVATIONFUNCTION.oneofs_by_name['item'].fields.append(
+  _LAYERORACTIVATIONFUNCTION.fields_by_name['layer'])
+_LAYERORACTIVATIONFUNCTION.fields_by_name['layer'].containing_oneof = _LAYERORACTIVATIONFUNCTION.oneofs_by_name['item']
+_SCHEDULERPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_LOSSFUNCTIONPROTO.fields_by_name['arguments'].message_type = i__onnx__common__pb2._ATTRIBUTEPROTO
+_NETWORK.fields_by_name['layers'].message_type = _LAYERORACTIVATIONFUNCTION
+DESCRIPTOR.message_types_by_name['OptimizerProto'] = _OPTIMIZERPROTO
+DESCRIPTOR.message_types_by_name['PrimitiveProto'] = _PRIMITIVEPROTO
+DESCRIPTOR.message_types_by_name['TransformProto'] = _TRANSFORMPROTO
+DESCRIPTOR.message_types_by_name['LayerProto'] = _LAYERPROTO
+DESCRIPTOR.message_types_by_name['ActivationFunctionProto'] = _ACTIVATIONFUNCTIONPROTO
+DESCRIPTOR.message_types_by_name['LayerOrActivationFunction'] = _LAYERORACTIVATIONFUNCTION
+DESCRIPTOR.message_types_by_name['SchedulerProto'] = _SCHEDULERPROTO
+DESCRIPTOR.message_types_by_name['LossFunctionProto'] = _LOSSFUNCTIONPROTO
+DESCRIPTOR.message_types_by_name['Network'] = _NETWORK
+DESCRIPTOR.message_types_by_name['Version'] = _VERSION
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+OptimizerProto = _reflection.GeneratedProtocolMessageType('OptimizerProto', (_message.Message,), {
+  'DESCRIPTOR' : _OPTIMIZERPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.OptimizerProto)
+  })
+_sym_db.RegisterMessage(OptimizerProto)
+
+PrimitiveProto = _reflection.GeneratedProtocolMessageType('PrimitiveProto', (_message.Message,), {
+  'DESCRIPTOR' : _PRIMITIVEPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.PrimitiveProto)
+  })
+_sym_db.RegisterMessage(PrimitiveProto)
+
+TransformProto = _reflection.GeneratedProtocolMessageType('TransformProto', (_message.Message,), {
+  'DESCRIPTOR' : _TRANSFORMPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.TransformProto)
+  })
+_sym_db.RegisterMessage(TransformProto)
+
+LayerProto = _reflection.GeneratedProtocolMessageType('LayerProto', (_message.Message,), {
+  'DESCRIPTOR' : _LAYERPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.LayerProto)
+  })
+_sym_db.RegisterMessage(LayerProto)
+
+ActivationFunctionProto = _reflection.GeneratedProtocolMessageType('ActivationFunctionProto', (_message.Message,), {
+  'DESCRIPTOR' : _ACTIVATIONFUNCTIONPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.ActivationFunctionProto)
+  })
+_sym_db.RegisterMessage(ActivationFunctionProto)
+
+LayerOrActivationFunction = _reflection.GeneratedProtocolMessageType('LayerOrActivationFunction', (_message.Message,), {
+  'DESCRIPTOR' : _LAYERORACTIVATIONFUNCTION,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.LayerOrActivationFunction)
+  })
+_sym_db.RegisterMessage(LayerOrActivationFunction)
+
+SchedulerProto = _reflection.GeneratedProtocolMessageType('SchedulerProto', (_message.Message,), {
+  'DESCRIPTOR' : _SCHEDULERPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.SchedulerProto)
+  })
+_sym_db.RegisterMessage(SchedulerProto)
+
+LossFunctionProto = _reflection.GeneratedProtocolMessageType('LossFunctionProto', (_message.Message,), {
+  'DESCRIPTOR' : _LOSSFUNCTIONPROTO,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.LossFunctionProto)
+  })
+_sym_db.RegisterMessage(LossFunctionProto)
+
+Network = _reflection.GeneratedProtocolMessageType('Network', (_message.Message,), {
+  'DESCRIPTOR' : _NETWORK,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.Network)
+  })
+_sym_db.RegisterMessage(Network)
+
+Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), {
+  'DESCRIPTOR' : _VERSION,
+  '__module__' : 'i_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.Version)
+  })
+_sym_db.RegisterMessage(Version)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/aihwkit/cloud/converter/definitions/i_input_file_pb2.py b/src/aihwkit/cloud/converter/definitions/i_input_file_pb2.py
new file mode 100644
index 00000000..442b633d
--- /dev/null
+++ b/src/aihwkit/cloud/converter/definitions/i_input_file_pb2.py
@@ -0,0 +1,513 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: i_input_file.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import i_common_pb2 as i__common__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='i_input_file.proto',
+  package='aihwx.inferencing',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x12i_input_file.proto\x12\x11\x61ihwx.inferencing\x1a\x0ei_common.proto\"1\n\x07\x44\x61taset\x12\x12\n\ndataset_id\x18\x01 \x02(\t\x12\x12\n\nbatch_size\x18\x02 \x01(\x03\"F\n\x0b\x41nalogProto\x12\x1d\n\x15output_noise_strength\x18\x01 \x02(\x02\x12\x0b\n\x03\x61\x64\x63\x18\x02 \x01(\x03\x12\x0b\n\x03\x64\x61\x63\x18\x03 \x01(\x03\"\xe9\x01\n\x08PCMProto\x12\x16\n\tdevice_id\x18\x01 \x02(\t:\x03pcm\x12\x1f\n\x17programming_noise_scale\x18\x02 \x02(\x02\x12\x18\n\x10read_noise_scale\x18\x03 \x02(\x02\x12\x13\n\x0b\x64rift_scale\x18\x04 \x02(\x02\x12\x1a\n\x12\x64rift_compensation\x18\x05 \x02(\x08\x12\x1d\n\x15poly_first_order_coef\x18\x06 \x02(\x02\x12\x1e\n\x16poly_second_order_coef\x18\x07 \x02(\x02\x12\x1a\n\x12poly_constant_coef\x18\x08 \x02(\x02\"\x98\x02\n\x0cGenericProto\x12\x1a\n\tdevice_id\x18\x01 \x02(\t:\x07generic\x12\x1f\n\x17programming_noise_scale\x18\x02 \x02(\x02\x12\x18\n\x10read_noise_scale\x18\x03 \x02(\x02\x12\x13\n\x0b\x64rift_scale\x18\x04 \x02(\x02\x12\x1a\n\x12\x64rift_compensation\x18\x05 \x02(\x08\x12\x1d\n\x15poly_first_order_coef\x18\x06 \x02(\x02\x12\x1e\n\x16poly_second_order_coef\x18\x07 \x02(\x02\x12\x1a\n\x12poly_constant_coef\x18\x08 \x02(\x02\x12\x12\n\ndrift_mean\x18\t \x02(\x02\x12\x11\n\tdrift_std\x18\n \x02(\x02\"y\n\x0fNoiseModelProto\x12*\n\x03pcm\x18\x01 \x01(\x0b\x32\x1b.aihwx.inferencing.PCMProtoH\x00\x12\x32\n\x07generic\x18\x02 \x01(\x0b\x32\x1f.aihwx.inferencing.GenericProtoH\x00\x42\x06\n\x04item\"\xb3\x01\n\x0bInferencing\x12\x19\n\x11inference_repeats\x18\x01 \x02(\x03\x12\x16\n\x0einference_time\x18\x02 \x02(\x03\x12\x33\n\x0b\x61nalog_info\x18\x03 \x02(\x0b\x32\x1e.aihwx.inferencing.AnalogProto\x12<\n\x10noise_model_info\x18\x04 \x02(\x0b\x32\".aihwx.inferencing.NoiseModelProto\"\xcc\x01\n\x0eInferenceInput\x12+\n\x07version\x18\x01 \x02(\x0b\x32\x1a.aihwx.inferencing.Version\x12+\n\x07\x64\x61taset\x18\x02 \x02(\x0b\x32\x1a.aihwx.inferencing.Dataset\x12+\n\x07network\x18\x03 \x02(\x0b\x32\x1a.aihwx.inferencing.Network\x12\x33\n\x0binferencing\x18\x04 \x02(\x0b\x32\x1e.aihwx.inferencing.Inferencing'
+  ,
+  dependencies=[i__common__pb2.DESCRIPTOR,])
+
+
+
+
+_DATASET = _descriptor.Descriptor(
+  name='Dataset',
+  full_name='aihwx.inferencing.Dataset',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='dataset_id', full_name='aihwx.inferencing.Dataset.dataset_id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='batch_size', full_name='aihwx.inferencing.Dataset.batch_size', index=1,
+      number=2, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=57,
+  serialized_end=106,
+)
+
+
+_ANALOGPROTO = _descriptor.Descriptor(
+  name='AnalogProto',
+  full_name='aihwx.inferencing.AnalogProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='output_noise_strength', full_name='aihwx.inferencing.AnalogProto.output_noise_strength', index=0,
+      number=1, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='adc', full_name='aihwx.inferencing.AnalogProto.adc', index=1,
+      number=2, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='dac', full_name='aihwx.inferencing.AnalogProto.dac', index=2,
+      number=3, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=108,
+  serialized_end=178,
+)
+
+
+_PCMPROTO = _descriptor.Descriptor(
+  name='PCMProto',
+  full_name='aihwx.inferencing.PCMProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='device_id', full_name='aihwx.inferencing.PCMProto.device_id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=True, default_value=b"pcm".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='programming_noise_scale', full_name='aihwx.inferencing.PCMProto.programming_noise_scale', index=1,
+      number=2, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='read_noise_scale', full_name='aihwx.inferencing.PCMProto.read_noise_scale', index=2,
+      number=3, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='drift_scale', full_name='aihwx.inferencing.PCMProto.drift_scale', index=3,
+      number=4, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='drift_compensation', full_name='aihwx.inferencing.PCMProto.drift_compensation', index=4,
+      number=5, type=8, cpp_type=7, label=2,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='poly_first_order_coef', full_name='aihwx.inferencing.PCMProto.poly_first_order_coef', index=5,
+      number=6, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='poly_second_order_coef', full_name='aihwx.inferencing.PCMProto.poly_second_order_coef', index=6,
+      number=7, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='poly_constant_coef', full_name='aihwx.inferencing.PCMProto.poly_constant_coef', index=7,
+      number=8, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=181,
+  serialized_end=414,
+)
+
+
+_GENERICPROTO = _descriptor.Descriptor(
+  name='GenericProto',
+  full_name='aihwx.inferencing.GenericProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='device_id', full_name='aihwx.inferencing.GenericProto.device_id', index=0,
+      number=1, type=9, cpp_type=9, label=2,
+      has_default_value=True, default_value=b"generic".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='programming_noise_scale', full_name='aihwx.inferencing.GenericProto.programming_noise_scale', index=1,
+      number=2, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='read_noise_scale', full_name='aihwx.inferencing.GenericProto.read_noise_scale', index=2,
+      number=3, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='drift_scale', full_name='aihwx.inferencing.GenericProto.drift_scale', index=3,
+      number=4, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='drift_compensation', full_name='aihwx.inferencing.GenericProto.drift_compensation', index=4,
+      number=5, type=8, cpp_type=7, label=2,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='poly_first_order_coef', full_name='aihwx.inferencing.GenericProto.poly_first_order_coef', index=5,
+      number=6, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='poly_second_order_coef', full_name='aihwx.inferencing.GenericProto.poly_second_order_coef', index=6,
+      number=7, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='poly_constant_coef', full_name='aihwx.inferencing.GenericProto.poly_constant_coef', index=7,
+      number=8, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='drift_mean', full_name='aihwx.inferencing.GenericProto.drift_mean', index=8,
+      number=9, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='drift_std', full_name='aihwx.inferencing.GenericProto.drift_std', index=9,
+      number=10, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=417,
+  serialized_end=697,
+)
+
+
+_NOISEMODELPROTO = _descriptor.Descriptor(
+  name='NoiseModelProto',
+  full_name='aihwx.inferencing.NoiseModelProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='pcm', full_name='aihwx.inferencing.NoiseModelProto.pcm', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='generic', full_name='aihwx.inferencing.NoiseModelProto.generic', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='item', full_name='aihwx.inferencing.NoiseModelProto.item',
+      index=0, containing_type=None,
+      create_key=_descriptor._internal_create_key,
+    fields=[]),
+  ],
+  serialized_start=699,
+  serialized_end=820,
+)
+
+
+_INFERENCING = _descriptor.Descriptor(
+  name='Inferencing',
+  full_name='aihwx.inferencing.Inferencing',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='inference_repeats', full_name='aihwx.inferencing.Inferencing.inference_repeats', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='inference_time', full_name='aihwx.inferencing.Inferencing.inference_time', index=1,
+      number=2, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='analog_info', full_name='aihwx.inferencing.Inferencing.analog_info', index=2,
+      number=3, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='noise_model_info', full_name='aihwx.inferencing.Inferencing.noise_model_info', index=3,
+      number=4, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=823,
+  serialized_end=1002,
+)
+
+
+_INFERENCEINPUT = _descriptor.Descriptor(
+  name='InferenceInput',
+  full_name='aihwx.inferencing.InferenceInput',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='version', full_name='aihwx.inferencing.InferenceInput.version', index=0,
+      number=1, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='dataset', full_name='aihwx.inferencing.InferenceInput.dataset', index=1,
+      number=2, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='network', full_name='aihwx.inferencing.InferenceInput.network', index=2,
+      number=3, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='inferencing', full_name='aihwx.inferencing.InferenceInput.inferencing', index=3,
+      number=4, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1005,
+  serialized_end=1209,
+)
+
+_NOISEMODELPROTO.fields_by_name['pcm'].message_type = _PCMPROTO
+_NOISEMODELPROTO.fields_by_name['generic'].message_type = _GENERICPROTO
+_NOISEMODELPROTO.oneofs_by_name['item'].fields.append(
+  _NOISEMODELPROTO.fields_by_name['pcm'])
+_NOISEMODELPROTO.fields_by_name['pcm'].containing_oneof = _NOISEMODELPROTO.oneofs_by_name['item']
+_NOISEMODELPROTO.oneofs_by_name['item'].fields.append(
+  _NOISEMODELPROTO.fields_by_name['generic'])
+_NOISEMODELPROTO.fields_by_name['generic'].containing_oneof = _NOISEMODELPROTO.oneofs_by_name['item']
+_INFERENCING.fields_by_name['analog_info'].message_type = _ANALOGPROTO
+_INFERENCING.fields_by_name['noise_model_info'].message_type = _NOISEMODELPROTO
+_INFERENCEINPUT.fields_by_name['version'].message_type = i__common__pb2._VERSION
+_INFERENCEINPUT.fields_by_name['dataset'].message_type = _DATASET
+_INFERENCEINPUT.fields_by_name['network'].message_type = i__common__pb2._NETWORK
+_INFERENCEINPUT.fields_by_name['inferencing'].message_type = _INFERENCING
+DESCRIPTOR.message_types_by_name['Dataset'] = _DATASET
+DESCRIPTOR.message_types_by_name['AnalogProto'] = _ANALOGPROTO
+DESCRIPTOR.message_types_by_name['PCMProto'] = _PCMPROTO
+DESCRIPTOR.message_types_by_name['GenericProto'] = _GENERICPROTO
+DESCRIPTOR.message_types_by_name['NoiseModelProto'] = _NOISEMODELPROTO
+DESCRIPTOR.message_types_by_name['Inferencing'] = _INFERENCING
+DESCRIPTOR.message_types_by_name['InferenceInput'] = _INFERENCEINPUT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Dataset = _reflection.GeneratedProtocolMessageType('Dataset', (_message.Message,), {
+  'DESCRIPTOR' : _DATASET,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.Dataset)
+  })
+_sym_db.RegisterMessage(Dataset)
+
+AnalogProto = _reflection.GeneratedProtocolMessageType('AnalogProto', (_message.Message,), {
+  'DESCRIPTOR' : _ANALOGPROTO,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.AnalogProto)
+  })
+_sym_db.RegisterMessage(AnalogProto)
+
+PCMProto = _reflection.GeneratedProtocolMessageType('PCMProto', (_message.Message,), {
+  'DESCRIPTOR' : _PCMPROTO,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.PCMProto)
+  })
+_sym_db.RegisterMessage(PCMProto)
+
+GenericProto = _reflection.GeneratedProtocolMessageType('GenericProto', (_message.Message,), {
+  'DESCRIPTOR' : _GENERICPROTO,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.GenericProto)
+  })
+_sym_db.RegisterMessage(GenericProto)
+
+NoiseModelProto = _reflection.GeneratedProtocolMessageType('NoiseModelProto', (_message.Message,), {
+  'DESCRIPTOR' : _NOISEMODELPROTO,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.NoiseModelProto)
+  })
+_sym_db.RegisterMessage(NoiseModelProto)
+
+Inferencing = _reflection.GeneratedProtocolMessageType('Inferencing', (_message.Message,), {
+  'DESCRIPTOR' : _INFERENCING,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.Inferencing)
+  })
+_sym_db.RegisterMessage(Inferencing)
+
+InferenceInput = _reflection.GeneratedProtocolMessageType('InferenceInput', (_message.Message,), {
+  'DESCRIPTOR' : _INFERENCEINPUT,
+  '__module__' : 'i_input_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.InferenceInput)
+  })
+_sym_db.RegisterMessage(InferenceInput)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/aihwkit/cloud/converter/definitions/i_onnx_common_pb2.py b/src/aihwkit/cloud/converter/definitions/i_onnx_common_pb2.py
new file mode 100644
index 00000000..16016a5b
--- /dev/null
+++ b/src/aihwkit/cloud/converter/definitions/i_onnx_common_pb2.py
@@ -0,0 +1,473 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: i_onnx_common.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='i_onnx_common.proto',
+  package='aihwx.inferencing',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x13i_onnx_common.proto\x12\x11\x61ihwx.inferencing\"\xb5\x03\n\x0e\x41ttributeProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12=\n\x04type\x18\x14 \x01(\x0e\x32/.aihwx.inferencing.AttributeProto.AttributeType\x12\t\n\x01\x66\x18\x02 \x01(\x02\x12\t\n\x01i\x18\x03 \x01(\x03\x12\t\n\x01s\x18\x04 \x01(\x0c\x12)\n\x01t\x18\x05 \x01(\x0b\x32\x1e.aihwx.inferencing.TensorProto\x12\t\n\x01\x62\x18\x65 \x01(\x08\x12\x0e\n\x06\x66loats\x18\x07 \x03(\x02\x12\x0c\n\x04ints\x18\x08 \x03(\x03\x12\x0f\n\x07strings\x18\t \x03(\x0c\x12/\n\x07tensors\x18\n \x03(\x0b\x32\x1e.aihwx.inferencing.TensorProto\x12\r\n\x05\x62ools\x18\x66 \x03(\x08\"\x8f\x01\n\rAttributeType\x12\r\n\tUNDEFINED\x10\x00\x12\t\n\x05\x46LOAT\x10\x01\x12\x07\n\x03INT\x10\x02\x12\n\n\x06STRING\x10\x03\x12\n\n\x06TENSOR\x10\x04\x12\x08\n\x04\x42OOL\x10\x65\x12\n\n\x06\x46LOATS\x10\x06\x12\x08\n\x04INTS\x10\x07\x12\x0b\n\x07STRINGS\x10\x08\x12\x0b\n\x07TENSORS\x10\t\x12\t\n\x05\x42OOLS\x10\x66\"\x89\x02\n\x0bTensorProto\x12\x0c\n\x04\x64ims\x18\x01 \x03(\x03\x12\x11\n\tdata_type\x18\x02 \x01(\x05\x12\x16\n\nfloat_data\x18\x04 \x03(\x02\x42\x02\x10\x01\x12\x16\n\nint32_data\x18\x05 \x03(\x05\x42\x02\x10\x01\x12\x13\n\x0bstring_data\x18\x06 \x03(\x0c\x12\x16\n\nint64_data\x18\x07 \x03(\x03\x42\x02\x10\x01\"|\n\x08\x44\x61taType\x12\r\n\tUNDEFINED\x10\x00\x12\t\n\x05\x46LOAT\x10\x01\x12\t\n\x05UINT8\x10\x02\x12\x08\n\x04INT8\x10\x03\x12\n\n\x06UINT16\x10\x04\x12\t\n\x05INT16\x10\x05\x12\t\n\x05INT32\x10\x06\x12\t\n\x05INT64\x10\x07\x12\n\n\x06STRING\x10\x08\x12\x08\n\x04\x42OOL\x10\t\"\xa2\x01\n\x10TensorShapeProto\x12:\n\x03\x64im\x18\x01 \x03(\x0b\x32-.aihwx.inferencing.TensorShapeProto.Dimension\x1aR\n\tDimension\x12\x13\n\tdim_value\x18\x01 \x01(\x03H\x00\x12\x13\n\tdim_param\x18\x02 \x01(\tH\x00\x12\x12\n\ndenotation\x18\x03 \x01(\tB\x07\n\x05value'
+)
+
+
+
+_ATTRIBUTEPROTO_ATTRIBUTETYPE = _descriptor.EnumDescriptor(
+  name='AttributeType',
+  full_name='aihwx.inferencing.AttributeProto.AttributeType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNDEFINED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='FLOAT', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INT', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='STRING', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='TENSOR', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='BOOL', index=5, number=101,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='FLOATS', index=6, number=6,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INTS', index=7, number=7,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='STRINGS', index=8, number=8,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='TENSORS', index=9, number=9,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='BOOLS', index=10, number=102,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=337,
+  serialized_end=480,
+)
+_sym_db.RegisterEnumDescriptor(_ATTRIBUTEPROTO_ATTRIBUTETYPE)
+
+_TENSORPROTO_DATATYPE = _descriptor.EnumDescriptor(
+  name='DataType',
+  full_name='aihwx.inferencing.TensorProto.DataType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNDEFINED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='FLOAT', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='UINT8', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INT8', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='UINT16', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INT16', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INT32', index=6, number=6,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INT64', index=7, number=7,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='STRING', index=8, number=8,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='BOOL', index=9, number=9,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=624,
+  serialized_end=748,
+)
+_sym_db.RegisterEnumDescriptor(_TENSORPROTO_DATATYPE)
+
+
+_ATTRIBUTEPROTO = _descriptor.Descriptor(
+  name='AttributeProto',
+  full_name='aihwx.inferencing.AttributeProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='aihwx.inferencing.AttributeProto.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='aihwx.inferencing.AttributeProto.type', index=1,
+      number=20, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='f', full_name='aihwx.inferencing.AttributeProto.f', index=2,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='i', full_name='aihwx.inferencing.AttributeProto.i', index=3,
+      number=3, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='s', full_name='aihwx.inferencing.AttributeProto.s', index=4,
+      number=4, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='t', full_name='aihwx.inferencing.AttributeProto.t', index=5,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='b', full_name='aihwx.inferencing.AttributeProto.b', index=6,
+      number=101, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='floats', full_name='aihwx.inferencing.AttributeProto.floats', index=7,
+      number=7, type=2, cpp_type=6, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='ints', full_name='aihwx.inferencing.AttributeProto.ints', index=8,
+      number=8, type=3, cpp_type=2, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='strings', full_name='aihwx.inferencing.AttributeProto.strings', index=9,
+      number=9, type=12, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='tensors', full_name='aihwx.inferencing.AttributeProto.tensors', index=10,
+      number=10, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='bools', full_name='aihwx.inferencing.AttributeProto.bools', index=11,
+      number=102, type=8, cpp_type=7, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _ATTRIBUTEPROTO_ATTRIBUTETYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=43,
+  serialized_end=480,
+)
+
+
+_TENSORPROTO = _descriptor.Descriptor(
+  name='TensorProto',
+  full_name='aihwx.inferencing.TensorProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='dims', full_name='aihwx.inferencing.TensorProto.dims', index=0,
+      number=1, type=3, cpp_type=2, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_type', full_name='aihwx.inferencing.TensorProto.data_type', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='float_data', full_name='aihwx.inferencing.TensorProto.float_data', index=2,
+      number=4, type=2, cpp_type=6, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\020\001', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='int32_data', full_name='aihwx.inferencing.TensorProto.int32_data', index=3,
+      number=5, type=5, cpp_type=1, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\020\001', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='string_data', full_name='aihwx.inferencing.TensorProto.string_data', index=4,
+      number=6, type=12, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='int64_data', full_name='aihwx.inferencing.TensorProto.int64_data', index=5,
+      number=7, type=3, cpp_type=2, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\020\001', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _TENSORPROTO_DATATYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=483,
+  serialized_end=748,
+)
+
+
+_TENSORSHAPEPROTO_DIMENSION = _descriptor.Descriptor(
+  name='Dimension',
+  full_name='aihwx.inferencing.TensorShapeProto.Dimension',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='dim_value', full_name='aihwx.inferencing.TensorShapeProto.Dimension.dim_value', index=0,
+      number=1, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='dim_param', full_name='aihwx.inferencing.TensorShapeProto.Dimension.dim_param', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='denotation', full_name='aihwx.inferencing.TensorShapeProto.Dimension.denotation', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='value', full_name='aihwx.inferencing.TensorShapeProto.Dimension.value',
+      index=0, containing_type=None,
+      create_key=_descriptor._internal_create_key,
+    fields=[]),
+  ],
+  serialized_start=831,
+  serialized_end=913,
+)
+
+_TENSORSHAPEPROTO = _descriptor.Descriptor(
+  name='TensorShapeProto',
+  full_name='aihwx.inferencing.TensorShapeProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='dim', full_name='aihwx.inferencing.TensorShapeProto.dim', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_TENSORSHAPEPROTO_DIMENSION, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=751,
+  serialized_end=913,
+)
+
+_ATTRIBUTEPROTO.fields_by_name['type'].enum_type = _ATTRIBUTEPROTO_ATTRIBUTETYPE
+_ATTRIBUTEPROTO.fields_by_name['t'].message_type = _TENSORPROTO
+_ATTRIBUTEPROTO.fields_by_name['tensors'].message_type = _TENSORPROTO
+_ATTRIBUTEPROTO_ATTRIBUTETYPE.containing_type = _ATTRIBUTEPROTO
+_TENSORPROTO_DATATYPE.containing_type = _TENSORPROTO
+_TENSORSHAPEPROTO_DIMENSION.containing_type = _TENSORSHAPEPROTO
+_TENSORSHAPEPROTO_DIMENSION.oneofs_by_name['value'].fields.append(
+  _TENSORSHAPEPROTO_DIMENSION.fields_by_name['dim_value'])
+_TENSORSHAPEPROTO_DIMENSION.fields_by_name['dim_value'].containing_oneof = _TENSORSHAPEPROTO_DIMENSION.oneofs_by_name['value']
+_TENSORSHAPEPROTO_DIMENSION.oneofs_by_name['value'].fields.append(
+  _TENSORSHAPEPROTO_DIMENSION.fields_by_name['dim_param'])
+_TENSORSHAPEPROTO_DIMENSION.fields_by_name['dim_param'].containing_oneof = _TENSORSHAPEPROTO_DIMENSION.oneofs_by_name['value']
+_TENSORSHAPEPROTO.fields_by_name['dim'].message_type = _TENSORSHAPEPROTO_DIMENSION
+DESCRIPTOR.message_types_by_name['AttributeProto'] = _ATTRIBUTEPROTO
+DESCRIPTOR.message_types_by_name['TensorProto'] = _TENSORPROTO
+DESCRIPTOR.message_types_by_name['TensorShapeProto'] = _TENSORSHAPEPROTO
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+AttributeProto = _reflection.GeneratedProtocolMessageType('AttributeProto', (_message.Message,), {
+  'DESCRIPTOR' : _ATTRIBUTEPROTO,
+  '__module__' : 'i_onnx_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.AttributeProto)
+  })
+_sym_db.RegisterMessage(AttributeProto)
+
+TensorProto = _reflection.GeneratedProtocolMessageType('TensorProto', (_message.Message,), {
+  'DESCRIPTOR' : _TENSORPROTO,
+  '__module__' : 'i_onnx_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.TensorProto)
+  })
+_sym_db.RegisterMessage(TensorProto)
+
+TensorShapeProto = _reflection.GeneratedProtocolMessageType('TensorShapeProto', (_message.Message,), {
+
+  'Dimension' : _reflection.GeneratedProtocolMessageType('Dimension', (_message.Message,), {
+    'DESCRIPTOR' : _TENSORSHAPEPROTO_DIMENSION,
+    '__module__' : 'i_onnx_common_pb2'
+    # @@protoc_insertion_point(class_scope:aihwx.inferencing.TensorShapeProto.Dimension)
+    })
+  ,
+  'DESCRIPTOR' : _TENSORSHAPEPROTO,
+  '__module__' : 'i_onnx_common_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.TensorShapeProto)
+  })
+_sym_db.RegisterMessage(TensorShapeProto)
+_sym_db.RegisterMessage(TensorShapeProto.Dimension)
+
+
+_TENSORPROTO.fields_by_name['float_data']._options = None
+_TENSORPROTO.fields_by_name['int32_data']._options = None
+_TENSORPROTO.fields_by_name['int64_data']._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/src/aihwkit/cloud/converter/definitions/i_output_file_pb2.py b/src/aihwkit/cloud/converter/definitions/i_output_file_pb2.py
new file mode 100644
index 00000000..7d661d2b
--- /dev/null
+++ b/src/aihwkit/cloud/converter/definitions/i_output_file_pb2.py
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: i_output_file.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from . import i_common_pb2 as i__common__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='i_output_file.proto',
+  package='aihwx.inferencing',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x13i_output_file.proto\x12\x11\x61ihwx.inferencing\x1a\x0ei_common.proto\"}\n\x15InferenceResultsProto\x12\x13\n\x0bt_inference\x18\x01 \x02(\x02\x12\x14\n\x0c\x61vg_accuracy\x18\x02 \x02(\x02\x12\x14\n\x0cstd_accuracy\x18\x03 \x02(\x02\x12\x11\n\tavg_error\x18\x04 \x02(\x02\x12\x10\n\x08\x61vg_loss\x18\x05 \x02(\x02\"\x9d\x01\n\x12InferenceRunsProto\x12\x18\n\x10inference_repeat\x18\x01 \x02(\x03\x12\x12\n\nis_partial\x18\x02 \x02(\x08\x12\x14\n\x0ctime_elapsed\x18\x03 \x02(\x02\x12\x43\n\x11inference_results\x18\x04 \x03(\x0b\x32(.aihwx.inferencing.InferenceResultsProto\"\x7f\n\x11InferencingOutput\x12+\n\x07version\x18\x01 \x02(\x0b\x32\x1a.aihwx.inferencing.Version\x12=\n\x0einference_runs\x18\x02 \x02(\x0b\x32%.aihwx.inferencing.InferenceRunsProto'
+  ,
+  dependencies=[i__common__pb2.DESCRIPTOR,])
+
+
+
+
+_INFERENCERESULTSPROTO = _descriptor.Descriptor(
+  name='InferenceResultsProto',
+  full_name='aihwx.inferencing.InferenceResultsProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='t_inference', full_name='aihwx.inferencing.InferenceResultsProto.t_inference', index=0,
+      number=1, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_accuracy', full_name='aihwx.inferencing.InferenceResultsProto.avg_accuracy', index=1,
+      number=2, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='std_accuracy', full_name='aihwx.inferencing.InferenceResultsProto.std_accuracy', index=2,
+      number=3, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_error', full_name='aihwx.inferencing.InferenceResultsProto.avg_error', index=3,
+      number=4, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_loss', full_name='aihwx.inferencing.InferenceResultsProto.avg_loss', index=4,
+      number=5, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=58,
+  serialized_end=183,
+)
+
+
+_INFERENCERUNSPROTO = _descriptor.Descriptor(
+  name='InferenceRunsProto',
+  full_name='aihwx.inferencing.InferenceRunsProto',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='inference_repeat', full_name='aihwx.inferencing.InferenceRunsProto.inference_repeat', index=0,
+      number=1, type=3, cpp_type=2, label=2,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='is_partial', full_name='aihwx.inferencing.InferenceRunsProto.is_partial', index=1,
+      number=2, type=8, cpp_type=7, label=2,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='time_elapsed', full_name='aihwx.inferencing.InferenceRunsProto.time_elapsed', index=2,
+      number=3, type=2, cpp_type=6, label=2,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='inference_results', full_name='aihwx.inferencing.InferenceRunsProto.inference_results', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=186,
+  serialized_end=343,
+)
+
+
+_INFERENCINGOUTPUT = _descriptor.Descriptor(
+  name='InferencingOutput',
+  full_name='aihwx.inferencing.InferencingOutput',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='version', full_name='aihwx.inferencing.InferencingOutput.version', index=0,
+      number=1, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='inference_runs', full_name='aihwx.inferencing.InferencingOutput.inference_runs', index=1,
+      number=2, type=11, cpp_type=10, label=2,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=345,
+  serialized_end=472,
+)
+
+_INFERENCERUNSPROTO.fields_by_name['inference_results'].message_type = _INFERENCERESULTSPROTO
+_INFERENCINGOUTPUT.fields_by_name['version'].message_type = i__common__pb2._VERSION
+_INFERENCINGOUTPUT.fields_by_name['inference_runs'].message_type = _INFERENCERUNSPROTO
+DESCRIPTOR.message_types_by_name['InferenceResultsProto'] = _INFERENCERESULTSPROTO
+DESCRIPTOR.message_types_by_name['InferenceRunsProto'] = _INFERENCERUNSPROTO
+DESCRIPTOR.message_types_by_name['InferencingOutput'] = _INFERENCINGOUTPUT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+InferenceResultsProto = _reflection.GeneratedProtocolMessageType('InferenceResultsProto', (_message.Message,), {
+  'DESCRIPTOR' : _INFERENCERESULTSPROTO,
+  '__module__' : 'i_output_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.InferenceResultsProto)
+  })
+_sym_db.RegisterMessage(InferenceResultsProto)
+
+InferenceRunsProto = _reflection.GeneratedProtocolMessageType('InferenceRunsProto', (_message.Message,), {
+  'DESCRIPTOR' : _INFERENCERUNSPROTO,
+  '__module__' : 'i_output_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.InferenceRunsProto)
+  })
+_sym_db.RegisterMessage(InferenceRunsProto)
+
+InferencingOutput = _reflection.GeneratedProtocolMessageType('InferencingOutput', (_message.Message,), {
+  'DESCRIPTOR' : _INFERENCINGOUTPUT,
+  '__module__' : 'i_output_file_pb2'
+  # @@protoc_insertion_point(class_scope:aihwx.inferencing.InferencingOutput)
+  })
+_sym_db.RegisterMessage(InferencingOutput)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/src/aihwkit/cloud/converter/v1/analog_info.py b/src/aihwkit/cloud/converter/v1/analog_info.py
new file mode 100644
index 00000000..fe4e51a1
--- /dev/null
+++ b/src/aihwkit/cloud/converter/v1/analog_info.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Helper class for adding rpu_config to neural network model"""
+
+from aihwkit.cloud.converter.definitions.i_input_file_pb2 import (
+    AnalogProto
+)
+
+
+# pylint: disable=too-few-public-methods)
+class AnalogInfo:
+    """Data class for fields from protobuf AnalogProto message"""
+
+    def __init__(self, a_info: AnalogProto):  # type: ignore[valid-type]
+        """Constructor for this class"""
+
+        # all three fields are required
+        self.output_noise_strength = a_info.output_noise_strength  # type: ignore[attr-defined]
+        self.adc = a_info.adc  # type: ignore[attr-defined]
+        self.dac = a_info.dac  # type: ignore[attr-defined]
diff --git a/src/aihwkit/cloud/converter/v1/i_mappings.py b/src/aihwkit/cloud/converter/v1/i_mappings.py
new file mode 100644
index 00000000..e1463f34
--- /dev/null
+++ b/src/aihwkit/cloud/converter/v1/i_mappings.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Mappings for version 1 of the AIHW Composer format."""
+
+from collections import namedtuple
+from typing import Any, Dict
+
+from torch.nn import (
+    BCELoss, BatchNorm2d, Conv2d, ConvTranspose2d, CrossEntropyLoss, Flatten,
+    LeakyReLU, Linear, LogSigmoid, LogSoftmax, MSELoss, MaxPool2d, NLLLoss,
+    ReLU, Sigmoid, Softmax, Tanh
+)
+from torchvision.datasets import FashionMNIST, SVHN  # type: ignore[import]
+
+from aihwkit.simulator.configs import InferenceRPUConfig
+from aihwkit.simulator.presets.web import (
+    WebComposerInferenceRPUConfig, OldWebComposerInferenceRPUConfig
+)
+from aihwkit.cloud.converter.definitions.i_onnx_common_pb2 import AttributeProto
+from aihwkit.cloud.converter.exceptions import ConversionError
+from aihwkit.nn import AnalogConv2d, AnalogLinear
+from aihwkit.optim import AnalogSGD
+from aihwkit.cloud.converter.v1.rpu_config_info import RPUconfigInfo
+
+
+Type = namedtuple('Type', ['attribute_type', 'field', 'fn'])
+
+# pylint: disable=no-member
+TYPES = {
+    int: Type(AttributeProto.AttributeType.INT, 'i', lambda x: x),  # type: ignore
+    bool: Type(AttributeProto.AttributeType.BOOL, 'b', lambda x: x),  # type: ignore
+    str: Type(AttributeProto.AttributeType.STRING, 's',  # type: ignore
+              lambda x: x.encode('utf-8')),
+    float: Type(AttributeProto.AttributeType.FLOAT, 'f', lambda x: x)  # type: ignore
+}
+
+TYPES_LISTS = {
+    int: Type(AttributeProto.AttributeType.INTS, 'ints', lambda x: x),  # type: ignore
+    bool: Type(AttributeProto.AttributeType.BOOLS, 'bools', lambda x: x),  # type: ignore
+    str: Type(AttributeProto.AttributeType.STRINGS, 'strings',  # type: ignore
+              lambda x: [y.encode('utf-8') for y in x]),
+    float: Type(AttributeProto.AttributeType.FLOATS, 'floats', lambda x: x)  # type: ignore
+}
+# pylint: enable=no-member
+
+
+class Function:
+    """Mapping for a function-like entity."""
+
+    def __init__(
+            self,
+            id_: str,
+            args: Dict
+    ):
+        self.id_ = id_
+        self.args = args
+
+    def to_proto(self, source: object, proto_cls: type) -> object:
+        """Convert a source object into a destination object."""
+        instance = proto_cls(id=self.id_)
+
+        for name, type_ in self.args.items():
+            value = self.get_field_value_to_proto(source, name, None)
+
+            argument = AttributeProto(name=name)
+            if isinstance(type_, list):
+                proto_type = TYPES_LISTS[type_[0]]
+                final_value = proto_type.fn(value)
+
+                if isinstance(final_value, int):
+                    final_value = [final_value, final_value]
+
+                getattr(argument, proto_type.field).extend(final_value)
+            else:
+                proto_type = TYPES[type_]
+                setattr(argument, proto_type.field, proto_type.fn(value))
+            argument.type = proto_type.attribute_type
+            instance.arguments.append(argument)
+
+        # TODO:  add to_proto for state_dict
+
+        return instance
+
+    def from_proto(self, source: Any, cls: type, default: Any = None) -> object:
+        """Convert a proto object into a destination object."""
+        kwargs = {}
+
+        for argument in source.arguments:
+
+            if argument.name not in self.args:
+                continue
+
+            type_ = self.args[argument.name]
+
+            if isinstance(type_, list):
+                proto_type = TYPES_LISTS[type_[0]]
+            else:
+                proto_type = TYPES[type_]
+
+            default_ = None if default is None else default.get(argument.name, None)
+            new_argument = self.get_argument_from_proto(argument, proto_type.field, default_)
+            if isinstance(type_, list):
+                new_argument[argument.name] = list(new_argument[argument.name])
+            kwargs.update(new_argument)
+
+        # TODO:  add from_proto for state_dict
+
+        return cls(**kwargs)
+
+    # pylint: disable=no-self-use
+    def get_field_value_to_proto(self, source: Any, field: str, default: Any = None) -> Any:
+        """Get the value of a field."""
+        return getattr(source, field, default)
+
+    def get_argument_from_proto(self, source: Any, field: str, default: Any = None) -> Dict:
+        """Get the value of an argument."""
+        return {source.name: getattr(source, field, default)}
+    # pylint: enable=no-self-use
+
+
+class LayerFunction(Function):
+    """Mapping for a function-like entity (Layer)."""
+
+    def get_field_value_to_proto(self, source: Any, field: str, default: Any = None) -> Any:
+        """Get the value of a field.
+
+        Raises ConversionError
+        """
+        if field == 'bias':
+            return getattr(source, 'bias', None) is not None
+        if field == 'rpu_config':
+            preset_cls = type(source.analog_tile.rpu_config)
+            if preset_cls not in Mappings.presets:
+                raise ConversionError('Invalid rpu_config in layer: {} not '
+                                      'among the presets'.format(preset_cls))
+            return Mappings.presets[preset_cls]
+        return super().get_field_value_to_proto(source, field, default)
+
+    def get_argument_from_proto(self, source: Any, field: str, default: Any = None) -> Dict:
+        """Get the value of an argument.
+
+        Raises ConversionError
+        """
+        if source.name == 'rpu_config':
+
+            if not isinstance(default, RPUconfigInfo):
+                raise ConversionError('Expect an new RPUconfigInfo as '
+                                      'default for layer creation.')
+
+            return {'rpu_config': default.create_inference_rpu_config(self.id_)}
+
+        return super().get_argument_from_proto(source, field, default)
+
+
+class Mappings:
+    """Mappings between Python entities and AIHW format."""
+    # pylint: disable=too-few-public-methods
+
+    datasets = {
+        FashionMNIST: 'fashion_mnist',
+        SVHN: 'svhn'
+    }
+
+    layers = {
+        AnalogConv2d: LayerFunction('AnalogConv2d', {
+            'in_channels': int,
+            'out_channels': int,
+            'kernel_size': [int],
+            'stride': [int],
+            'padding': [int],
+            'dilation': [int],
+            'bias': bool,
+            'rpu_config': str,
+        }),
+        AnalogLinear: LayerFunction('AnalogLinear', {
+            'in_features': int,
+            'out_features': int,
+            'bias': bool,
+            'rpu_config': str,
+        }),
+        BatchNorm2d: LayerFunction('BatchNorm2d', {
+            'num_features': int
+        }),
+        Conv2d: LayerFunction('Conv2d', {
+            'in_channels': int,
+            'out_channels': int,
+            'kernel_size': [int],
+            'stride': [int],
+            'padding': [int],
+            'dilation': [int],
+            'bias': bool
+        }),
+        ConvTranspose2d: LayerFunction('ConvTranspose2d', {
+            'in_channels': int,
+            'out_channels': int,
+            'kernel_size': [int],
+            'stride': [int],
+            'padding': [int],
+            'output_padding': [int],
+            'dilation': [int],
+            'bias': bool
+        }),
+        Flatten: LayerFunction('Flatten', {}),
+        Linear: LayerFunction('Linear', {
+            'in_features': int,
+            'out_features': int,
+            'bias': bool
+        }),
+        MaxPool2d: LayerFunction('MaxPool2d', {
+            'kernel_size': int,
+            'stride': int,
+            'padding': int,
+            'dilation': int,
+            'ceil_mode': bool
+        })
+    }
+
+    activation_functions = {
+        LeakyReLU: Function('LeakyReLU', {
+            'negative_slope': float
+        }),
+        LogSigmoid: Function('LogSigmoid', {}),
+        LogSoftmax: Function('LogSoftmax', {
+            'dim': int
+        }),
+        ReLU: Function('ReLU', {}),
+        Sigmoid: Function('Sigmoid', {}),
+        Softmax: Function('Softmax', {
+            'dim': int
+        }),
+        Tanh: Function('Tanh', {})
+    }
+
+    loss_functions = {
+        BCELoss: Function('BCELoss', {}),
+        CrossEntropyLoss: Function('CrossEntropyLoss', {}),
+        MSELoss: Function('MSELoss', {}),
+        NLLLoss: Function('NLLLoss', {})
+    }
+
+    optimizers = {
+        AnalogSGD: Function('AnalogSGD', {
+            'lr': float
+        })
+    }
+
+    presets = {
+        InferenceRPUConfig: 'InferenceRPUConfig',
+        OldWebComposerInferenceRPUConfig: 'OldWebComposerInferenceRPUConfig',
+        WebComposerInferenceRPUConfig: 'WebComposerInferenceRPUConfig',
+    }
+
+
+def build_inverse_mapping(mapping: Dict) -> Dict:
+    """Create the inverse mapping between Python entities and AIHW Composer formats."""
+    return {value if not isinstance(value, Function) else value.id_: key
+            for key, value in mapping.items()}
+
+
+class InverseMappings:
+    """Mappings between AIHW Composer format and Python entities."""
+    # pylint: disable=too-few-public-methods
+
+    datasets = build_inverse_mapping(Mappings.datasets)
+    layers = build_inverse_mapping(Mappings.layers)
+    activation_functions = build_inverse_mapping(Mappings.activation_functions)
+    loss_functions = build_inverse_mapping(Mappings.loss_functions)
+    optimizers = build_inverse_mapping(Mappings.optimizers)
+    presets = build_inverse_mapping(Mappings.presets)
diff --git a/src/aihwkit/cloud/converter/v1/inferencing.py b/src/aihwkit/cloud/converter/v1/inferencing.py
new file mode 100644
index 00000000..6062386c
--- /dev/null
+++ b/src/aihwkit/cloud/converter/v1/inferencing.py
@@ -0,0 +1,354 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Converters for `BasicInferencing` Experiment."""
+
+from typing import Any, Dict, List
+# from collections import OrderedDict
+
+from torch.nn import Module, NLLLoss
+
+from aihwkit.simulator.configs import InferenceRPUConfig
+from aihwkit.cloud.converter.exceptions import ConversionError
+from aihwkit.cloud.converter.v1.i_mappings import (
+    InverseMappings, Mappings)
+
+# pylint: disable=no-name-in-module,import-error
+from aihwkit.experiments.experiments.inferencing import BasicInferencing  # type: ignore[import]
+from aihwkit.cloud.converter.definitions.i_input_file_pb2 import (
+    InferenceInput, Dataset, Inferencing, NoiseModelProto, PCMProto, GenericProto, AnalogProto
+)
+
+from aihwkit.cloud.converter.definitions.i_common_pb2 import (
+    LayerOrActivationFunction, Network, LayerProto,
+    ActivationFunctionProto, Version
+)
+from aihwkit.nn import AnalogSequential
+
+from aihwkit.cloud.converter.v1.analog_info import AnalogInfo
+from aihwkit.cloud.converter.v1.noise_model_info import NoiseModelInfo
+from aihwkit.cloud.converter.v1.rpu_config_info import RPUconfigInfo
+
+
+class BasicInferencingConverter:
+    """Converter for `BasicInferencing` Experiment."""
+
+    def to_proto(self, experiment: BasicInferencing,
+                 analog_info: Dict,
+                 noise_model_info: Dict) -> Any:
+        """Convert an `Experiment` to its protobuf representation."""
+
+        version = self._version_to_proto()
+        dataset = self._dataset_to_proto(experiment.dataset,
+                                         experiment.batch_size)
+        network = self._model_to_proto(experiment.model,
+                                       experiment.weight_template_id)
+        inferencing = self._inferencing_to_proto(experiment.inference_repeats,
+                                                 experiment.inference_time,
+                                                 analog_info,
+                                                 noise_model_info,
+                                                 )
+        return InferenceInput(
+            version=version,
+            dataset=dataset,
+            network=network,
+            inferencing=inferencing
+        )
+
+    def from_proto(self, protobuf: Any) -> BasicInferencing:
+        """Convert a protobuf representation to an `Experiment`."""
+
+        dataset = InverseMappings.datasets[protobuf.dataset.dataset_id]
+
+        # build RPUconfig_info to be used when it is instantiated dynamically
+        alog_info = AnalogInfo(protobuf.inferencing.analog_info)
+        nm_info = NoiseModelInfo(protobuf.inferencing.noise_model_info)
+        rc_info = RPUconfigInfo(nm_info, alog_info)
+
+        model = self._model_from_proto(protobuf.network, rc_info)
+
+        batch_size = protobuf.dataset.batch_size
+        inference_info = self._inference_info_from_proto(protobuf)
+        loss_function = NLLLoss
+        weight_template_id = inference_info['weight_template_id']
+        inference_repeats = inference_info['inference_repeats']
+        inference_time = inference_info['inference_time']
+
+        return BasicInferencing(
+            dataset=dataset,
+            model=model,
+            batch_size=batch_size,
+            loss_function=loss_function,
+            weight_template_id=weight_template_id,
+            inference_repeats=inference_repeats,
+            inference_time=inference_time
+        )
+
+    # Methods for converting to proto.
+
+    @staticmethod
+    def _version_to_proto() -> Any:
+        return Version(
+            schema=1,
+            opset=1
+        )
+
+    @staticmethod
+    def _dataset_to_proto(dataset: type, batch_size: int) -> Any:
+        if dataset not in Mappings.datasets.keys():
+            raise ConversionError('Unsupported dataset: {}'.format(dataset))
+
+        return Dataset(
+            dataset_id=Mappings.datasets[dataset],
+            batch_size=batch_size
+        )
+
+    @staticmethod
+    def _model_to_proto(model: Module, weight_template_id: str) -> Any:
+
+        if not isinstance(model, AnalogSequential):
+            raise ConversionError('Unsupported model: only AnalogSequential is supported')
+
+        children_types = {type(layer) for layer in model.children()}
+        valid_types = set(Mappings.layers.keys()) | set(Mappings.activation_functions.keys())
+        if children_types - valid_types:
+            raise ConversionError('Unsupported layers: {}'.format(children_types - valid_types))
+
+        # Create a new input_file pb Network object with weight_template_id
+        network = Network(weight_template_id=weight_template_id)
+
+        for child in model.children():
+            child_type = type(child)
+            if child_type in Mappings.layers:
+                item = LayerOrActivationFunction(
+                    layer=Mappings.layers[child_type].to_proto(child, LayerProto)
+                )
+            else:
+                item = LayerOrActivationFunction(
+                    activation_function=Mappings.activation_functions[child_type].to_proto(
+                        child, ActivationFunctionProto)
+                )
+            # pylint: disable=no-member),undefined-variable
+            network.layers.extend([item])
+
+        return network
+
+    @staticmethod
+    def _noise_model_to_proto(
+            noise_model_info: Dict) -> NoiseModelProto:  # type: ignore[valid-type]
+        """Creates a protobuf NoiseModelProto object from input dictionaries"""
+
+        model = None
+        device_id = noise_model_info['device_id']
+        if device_id == 'pcm':
+            model = NoiseModelProto(
+                pcm=PCMProto(
+                    device_id=device_id,
+                    programming_noise_scale=noise_model_info['programming_noise_scale'],
+                    read_noise_scale=noise_model_info['read_noise_scale'],
+                    drift_scale=noise_model_info['drift_scale'],
+                    drift_compensation=noise_model_info['drift_compensation'],
+                    poly_first_order_coef=noise_model_info['poly_first_order_coef'],
+                    poly_second_order_coef=noise_model_info['poly_second_order_coef'],
+                    poly_constant_coef=noise_model_info['poly_constant_coef']
+                    )
+                )
+        else:
+            model = NoiseModelProto(
+                generic=GenericProto(
+                    device_id=device_id,
+                    programming_noise_scale=noise_model_info['programming_noise_scale'],
+                    read_noise_scale=noise_model_info['read_noise_scale'],
+                    drift_scale=noise_model_info['drift_scale'],
+                    drift_compensation=noise_model_info['drift_compensation'],
+                    poly_first_order_coef=noise_model_info['poly_first_order_coef'],
+                    poly_second_order_coef=noise_model_info['poly_second_order_coef'],
+                    poly_constant_coef=noise_model_info['poly_constant_coef'],
+                    drift_mean=noise_model_info['drift_mean'],
+                    drift_std=noise_model_info['drift_std'],
+                    )
+            )
+        return model
+
+    @staticmethod
+    def rpu_config_info_from_info(analog_info: Dict,
+                                  noise_model_info: Dict
+                                  ) -> RPUconfigInfo:
+        """ Creates RPUconfigInfo """
+
+        # TODO: Remove the "Info" objects: NoiseModelInfo, AnalogInfo. Seems unecessary
+        nm_info = NoiseModelInfo(BasicInferencingConverter._noise_model_to_proto(
+            noise_model_info))  # type: ignore[name-defined]
+        a_info = AnalogInfo(AnalogProto(**analog_info))
+        return RPUconfigInfo(nm_info, a_info)
+
+    @staticmethod
+    def rpu_config_from_info(analog_info: Dict,
+                             noise_model_info: Dict,
+                             func_id: str = 'id-not-provided'
+                             ) -> InferenceRPUConfig:
+        """ Creates RPUConfig """
+        nm_info = NoiseModelInfo(BasicInferencingConverter._noise_model_to_proto(
+            noise_model_info))  # type: ignore[name-defined]
+        a_info = AnalogInfo(AnalogProto(**analog_info))
+        return RPUconfigInfo(nm_info, a_info).create_inference_rpu_config(func_id)
+
+    @staticmethod
+    def _inferencing_to_proto(
+            inference_repeats: int,
+            inference_time: float,
+            analog_info: Dict,
+            noise_model_info: Dict) -> Inferencing:  # type: ignore[valid-type]
+        """Creates protobuf Inferencing object"""
+
+        # Not sure why mypy and pylint cannot find following method, python3 can.
+        # pylint: disable=undefined-variable
+        nm_info = BasicInferencingConverter._noise_model_to_proto(
+            noise_model_info)  # type: ignore[name-defined]
+        a_info = AnalogProto(**analog_info)
+
+        return Inferencing(inference_repeats=inference_repeats,
+                           inference_time=inference_time,
+                           analog_info=a_info,
+                           noise_model_info=nm_info
+                           )
+
+    # Methods for converting from proto.
+
+    @staticmethod
+    def _inference_info_from_proto(
+            inference_pb: InferenceInput) -> Dict:  # type: ignore[valid-type]
+        """Converts inference_info from protobuf to a dictionary"""
+
+        inferencing = inference_pb.inferencing  # type: ignore[attr-defined]
+        network = inference_pb.network  # type: ignore[attr-defined]
+        return {
+                    'inference_repeats': inferencing.inference_repeats,
+                    'inference_time': inferencing.inference_time,
+                    'weight_template_id': network.weight_template_id
+                }
+
+    @staticmethod
+    def _model_from_proto(network: Network,  # type: ignore[valid-type]
+                          rc_info: RPUconfigInfo) -> Module:
+        layers = []
+        for layer_proto in network.layers:  # type: ignore[attr-defined]
+            if layer_proto.WhichOneof('item') == 'layer':
+                layer_cls = InverseMappings.layers[layer_proto.layer.id]
+                layer = Mappings.layers[layer_cls].from_proto(
+                    layer_proto.layer, layer_cls, {'rpu_config': rc_info})
+            else:
+                layer_cls = InverseMappings.activation_functions[layer_proto.activation_function.id]
+                layer = Mappings.activation_functions[layer_cls].from_proto(
+                    layer_proto.activation_function, layer_cls)
+
+            layers.append(layer)
+
+        return AnalogSequential(*layers)
+
+    @staticmethod
+    def _analog_info_from_proto(
+            analog_info: AnalogProto) -> Dict:  # type: ignore[valid-type]
+        """Converts from protobuf analog_info to a dictionary"""
+
+        return {
+                    'output_noise_strength': (
+                        analog_info.output_noise_strength  # type: ignore[attr-defined]
+                    ),
+                    'adc': analog_info.adc,  # type: ignore[attr-defined]
+                    'dac': analog_info.dac  # type: ignore[attr-defined]
+                }
+
+    @staticmethod
+    def _noise_model_from_proto(
+            noise_model_info: NoiseModelProto) -> Dict:  # type: ignore[valid-type]
+        """Converts from protobuf noise_model to a dictionary"""
+
+        extra = {}
+        typ = noise_model_info.WhichOneof('item')  # type: ignore[attr-defined]
+
+        if typ == 'pcm':
+            # pcm does not have 2 extra fields
+            info = noise_model_info.pcm  # type: ignore[attr-defined]
+
+        elif typ == 'generic':
+            info = noise_model_info.generic  # type: ignore[attr-defined]
+            # There are 2 extra fields in generic
+            extra = {
+                'drift_mean': info.drift_mean,
+                'drift_std': info.drift_std
+            }
+        else:
+            raise TypeError
+
+        base = {
+            'device_id': typ,
+            'programming_noise_scale': info.programming_noise_scale,
+            'read_noise_scale': info.read_noise_scale,
+            'drift_scale': info.drift_scale,
+            'drift_compensation': info.drift_compensation,
+            'poly_first_order_coef': info.poly_first_order_coef,
+            'poly_second_order_coef': info.poly_second_order_coef,
+            'poly_constant_coef': info.poly_constant_coef,
+        }
+
+        # add the extra fields if any in return value
+        return {**base, **extra}
+
+
+class BasicInferencingResultConverter:
+    """Converter for `BasicInverencing` results."""
+
+    # pylint: disable=too-few-public-methods
+
+    def from_proto(self, results: Any) -> Any:
+        """Convert a result to its json representation."""
+
+        return {
+            'version': {
+                'schema': 1,
+                'opset': 1
+            },
+            'epochs': self._epochs_from_proto(results)
+        }
+
+    @staticmethod
+    def to_json() -> Dict:
+        """Converts dictionary and converts it to a output_file compliant json dictionary"""
+
+        version = {
+            'version': {
+                'schema': 1,
+                'opset': 1
+            }
+        }
+
+        # will probably need to return {**base, **extra}
+        #   comes from:
+        #   aihw-experience-validator-python/aihwx_validator/converters/json/i_converter.py
+
+        return version
+
+    @staticmethod
+    def _epochs_from_proto(epochs_proto: Any) -> List[Dict]:
+        epochs = []
+        for epoch in epochs_proto.epochs:
+            epoch_dict = {
+                'epoch': epoch.epoch,
+                'metrics': {}
+            }
+            for metric in epoch.metrics:
+                epoch_dict['metrics'][metric.name] = metric.f
+
+            epochs.append(epoch_dict)
+
+        return epochs
diff --git a/src/aihwkit/cloud/converter/v1/noise_model_info.py b/src/aihwkit/cloud/converter/v1/noise_model_info.py
new file mode 100644
index 00000000..98837719
--- /dev/null
+++ b/src/aihwkit/cloud/converter/v1/noise_model_info.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Noise model info in rpu_config to neural network model"""
+
+from aihwkit.cloud.converter.definitions.i_input_file_pb2 import (
+    NoiseModelProto
+)
+
+
+# pylint: disable=too-many-instance-attributes
+class NoiseModelInfo:
+    """Data only class for fields from protobuf NoiseModelProto message"""
+
+    PCM = 'pcm'
+    GENERIC = 'generic'
+
+    def __init__(self, nm_proto: NoiseModelProto):  # type: ignore[valid-type]
+        """Constructor for this class"""
+
+        type_ = nm_proto.WhichOneof('item')  # type: ignore[attr-defined]
+
+        info = None
+        if type_ == 'pcm':
+            # pcm does NOT have 2 extra fields
+            info = nm_proto.pcm  # type: ignore[attr-defined]
+        else:
+            # generic HAS 2 extra fields
+            info = nm_proto.generic  # type: ignore[attr-defined]
+
+        self.device_id = info.device_id
+        self.programming_noise_scale = info.programming_noise_scale
+        self.read_noise_scale = info.read_noise_scale
+        self.drift_scale = info.drift_scale
+        self.drift_compensation = info.drift_compensation
+        self.poly_first_order_coef = info.poly_first_order_coef
+        self.poly_second_order_coef = info.poly_second_order_coef
+        self.poly_constant_coef = info.poly_constant_coef
+
+        self._drift_mean = -1.1
+        self._drift_std = -1.1
+
+        # Generic device has two extra field.
+        if info.device_id == self.GENERIC:
+            self._drift_mean = info.drift_mean
+            self._drift_std = info.drift_std
+
+    def _assert_generic(self) -> None:
+        """Check is device is generic"""
+
+        assert(self.device_id == self.GENERIC), \
+            "device_id does not have value '{}'".format(self.GENERIC)
+
+    @property
+    def drift_mean(self) -> float:
+        """Enforce access to drift_mean if this is a generic device"""
+
+        self._assert_generic()
+        return self._drift_mean
+
+    @property
+    def drift_std(self) -> float:
+        """Enforce access to drift_std if this is a generic device"""
+
+        self._assert_generic()
+        return self._drift_std
+# pylint: enable=too-many-instance-attributes
diff --git a/src/aihwkit/cloud/converter/v1/rpu_config_info.py b/src/aihwkit/cloud/converter/v1/rpu_config_info.py
new file mode 100644
index 00000000..e225ab58
--- /dev/null
+++ b/src/aihwkit/cloud/converter/v1/rpu_config_info.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Creates InferenceRPUConfig to add to nn model"""
+
+from collections import OrderedDict
+
+from aihwkit.simulator.configs.configs import InferenceRPUConfig
+from aihwkit.simulator.presets.web import OldWebComposerInferenceRPUConfig
+from aihwkit.inference.noise.pcm import PCMLikeNoiseModel
+from aihwkit.inference.noise.custom import StateIndependentNoiseModel
+from aihwkit.inference.compensation.drift import GlobalDriftCompensation
+from aihwkit.cloud.converter.v1.analog_info import AnalogInfo
+from aihwkit.cloud.converter.v1.noise_model_info import NoiseModelInfo
+
+# pylint: disable=too-few-public-methods
+
+
+class NoiseModelDeviceIDException(Exception):
+    """Exception raised if noise model device id is not correct"""
+
+
+class RPUconfigInfo:
+    """Data only class for RPUConfig fields"""
+
+    def __init__(self, nm_info: NoiseModelInfo, a_info: AnalogInfo):
+        """"Constructor for this class"""
+
+        self._noise_model_info = nm_info
+        self._analog_info = a_info
+        self._device_id = ''
+
+    def _print_rpu_config(
+            self,
+            rpu_config: InferenceRPUConfig,
+            func_id: str,
+            dev_id: str) -> None:
+        """Pretty-print rpu_config"""
+
+        # Create ordered dictionary with field name as key and value as field value
+        #   Include ADC and DAC in input/output resolution
+        print_order = OrderedDict(
+            {
+                ('forward.out_noise', rpu_config.forward.out_noise),
+                ('forward.out_res/adc', '{}/{}'.format(
+                    rpu_config.forward.out_res, self._analog_info.adc)),
+                ('forward.inp_res/dac', '{}/{}'.format(
+                    rpu_config.forward.inp_res, self._analog_info.dac)),
+                ('noise_model', rpu_config.noise_model),
+                ('noise_model.read_noise_scale',
+                    rpu_config.noise_model.read_noise_scale),  # type: ignore[attr-defined]
+                ('noise_model.prog_noise_scale',
+                    rpu_config.noise_model.prog_noise_scale),  # type: ignore[attr-defined]
+                ('drift_compensation',
+                    rpu_config.drift_compensation),  # type: ignore[attr-defined]
+                ('noise_model.drift_scale',
+                    rpu_config.noise_model.drift_scale),  # type: ignore[attr-defined]
+            }
+
+        )
+
+        # add extra fields for GENERIC device
+        if dev_id == NoiseModelInfo.GENERIC:
+            print_order['noise_model.drift_nu_mean'] = (
+                rpu_config.noise_model.drift_nu_mean)  # type: ignore[attr-defined]
+            print_order['noise_model.drift_nu_std'] = (
+                rpu_config.noise_model.drift_nu_std)  # type: ignore[attr-defined]
+
+        output = '  rpu_config: function_id={} device_id={}\n'.format(func_id, dev_id)
+        for key in print_order:
+            output += '    {:30}: {}\n'.format(key, print_order[key])
+            if key == 'noise_model' and dev_id == NoiseModelInfo.PCM:
+                # need to print out prog_coeff in PCMLikeNoiseModel object
+                for i in range(3):
+                    field_name = 'noise_model.prog_coeff[{}]'.format(i)
+                    output += '    {:30}: {}\n'.format(
+                        field_name,
+                        rpu_config.noise_model.prog_coeff[i]  # type: ignore[attr-defined]
+                    )
+
+        print('Here is the variable content of the rpu_config:\n{}'.format(output))
+
+    def create_inference_rpu_config(self, func_id: str,
+                                    verbose: bool = False) -> InferenceRPUConfig:
+        """Creates a InferenceRPUConfig class using noise and analog info"""
+
+        rpu_config = OldWebComposerInferenceRPUConfig()
+
+        # Assign values from AnalogProto
+        rpu_config.forward.out_noise = self._analog_info.output_noise_strength
+
+        # changed input/output res to use a formula. print out adc and dac
+        rpu_config.forward.out_res = 1.0 / (2**self._analog_info.adc - 2)
+        rpu_config.forward.inp_res = 1.0 / (2**self._analog_info.dac - 2)
+
+        # Assign values from NoiseModelProto (CM Noise model)
+        self._device_id = self._noise_model_info.device_id
+        if self._device_id == NoiseModelInfo.PCM:
+            rpu_config.noise_model = (
+                PCMLikeNoiseModel(
+                                g_max=25.0,
+                                prog_coeff=[
+                                    self._noise_model_info.poly_constant_coef,
+                                    self._noise_model_info.poly_first_order_coef,
+                                    self._noise_model_info.poly_second_order_coef,
+                                ]
+                )
+            )
+        elif self._device_id == NoiseModelInfo.GENERIC:
+            rpu_config.noise_model = StateIndependentNoiseModel(
+                                g_max=25.0,
+                                prog_coeff=[
+                                    self._noise_model_info.poly_constant_coef,
+                                    self._noise_model_info.poly_first_order_coef,
+                                    self._noise_model_info.poly_second_order_coef,
+                                ]
+            )
+
+            # These are unique to generic
+            rpu_config.noise_model.drift_nu_mean = (
+                self._noise_model_info.drift_mean  # type: ignore[attr-defined]
+            )
+            rpu_config.noise_model.drift_nu_std = (
+                self._noise_model_info.drift_std  # type: ignore[attr-defined]
+            )
+        else:
+            raise NoiseModelDeviceIDException(
+                'invalid noise model device id {}'.format(self._device_id))
+
+        # common fields
+        rpu_config.noise_model.prog_noise_scale = (  # type: ignore[attr-defined]
+            self._noise_model_info.programming_noise_scale
+        )
+        rpu_config.noise_model.read_noise_scale = (  # type: ignore[attr-defined]
+                self._noise_model_info.read_noise_scale
+        )
+
+        # Drift compensation in protobuf is boolean (bool)
+        rpu_config.drift_compensation = None  # type: ignore[assignment]
+        if self._noise_model_info.drift_compensation:
+            rpu_config.drift_compensation = GlobalDriftCompensation()
+
+        rpu_config.noise_model.drift_scale = (  # type: ignore[attr-defined]
+                self._noise_model_info.drift_scale
+        )
+
+        if verbose:
+            self._print_rpu_config(rpu_config, func_id, self._noise_model_info.device_id)
+        return rpu_config
diff --git a/src/aihwkit/experiments/__init__.py b/src/aihwkit/experiments/__init__.py
index cf37f7c8..1d2777b2 100644
--- a/src/aihwkit/experiments/__init__.py
+++ b/src/aihwkit/experiments/__init__.py
@@ -15,3 +15,4 @@
 # Convenience imports for easier access to the classes.
 
 from aihwkit.experiments.experiments.training import BasicTraining
+from aihwkit.experiments.experiments.inferencing import BasicInferencing
diff --git a/src/aihwkit/experiments/experiments/base.py b/src/aihwkit/experiments/experiments/base.py
index 2644b3c9..1d80d210 100644
--- a/src/aihwkit/experiments/experiments/base.py
+++ b/src/aihwkit/experiments/experiments/base.py
@@ -31,6 +31,10 @@ class Signals(Enum):
     VALIDATION_EPOCH_END = 31
     VALIDATION_EPOCH_BATCH_START = 32
     VALIDATION_EPOCH_BATCH_END = 33
+    INFERENCE_START = 40
+    INFERENCE_END = 41
+    INFERENCE_REPEAT_START = 50
+    INFERENCE_REPEAT_END = 51
 
 
 class Experiment:
@@ -54,11 +58,16 @@ def __init__(self) -> None:
             Signals.VALIDATION_EPOCH_START: [],
             Signals.VALIDATION_EPOCH_END: [],
             Signals.VALIDATION_EPOCH_BATCH_START: [],
-            Signals.VALIDATION_EPOCH_BATCH_END: []
+            Signals.VALIDATION_EPOCH_BATCH_END: [],
+            Signals.INFERENCE_START: [],
+            Signals.INFERENCE_END: [],
+            Signals.INFERENCE_REPEAT_START: [],
+            Signals.INFERENCE_REPEAT_END: []
         }
 
         self.results: Optional[Any] = None
 
+    # add the specified routine to call with the specified hook key to the experiment.
     def add_hook(self, key: Signals, hook: Callable) -> None:
         """Register a hook for the experiment.
 
@@ -76,6 +85,7 @@ def clear_hooks(self) -> None:
         for key in self.hooks:
             self.hooks[key] = []
 
+    # call the routine that is associated with the specified hook key.
     def _call_hook(self, key: Signals, *args: Any, **kwargs: Any) -> Dict:
         """Invoke the hooks for a specific key."""
         ret = {}
diff --git a/src/aihwkit/experiments/experiments/inferencing.py b/src/aihwkit/experiments/experiments/inferencing.py
new file mode 100644
index 00000000..f3503e6a
--- /dev/null
+++ b/src/aihwkit/experiments/experiments/inferencing.py
@@ -0,0 +1,400 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Basic inferencing Experiment."""
+
+# pylint: disable=too-many-locals
+
+from typing import Any, Dict, Tuple, Type, Optional
+from os import path, mkdir
+from copy import deepcopy
+from requests import get as requests_get
+from numpy import ndarray, array, logspace, log10, zeros, concatenate
+
+
+from torch import device as torch_device, max as torch_max, Tensor
+from torch import load
+from torch.nn import Module, CrossEntropyLoss
+from torch.nn.modules.loss import _Loss
+from torch.utils.data import DataLoader, Dataset, Subset
+from torchvision.datasets import FashionMNIST, SVHN
+from torchvision.transforms import Compose, Normalize, ToTensor
+
+from aihwkit.experiments.experiments.base import Experiment, Signals
+from aihwkit.nn.modules.base import AnalogModuleBase
+
+
+WEIGHT_TEMPLATE_URL = "https://github.com/IBM-AI-Hardware-Center/Composer/raw/main/"
+
+
+def download(url: str, destination: str) -> None:
+    """Helper for downloading a file from url"""
+    response = requests_get(url)
+    with open(destination, "wb") as file_:
+        file_.write(response.content)
+
+
+class BasicInferencing(Experiment):
+    """Experiment for inferencing a neural network.
+
+    ``Experiment`` that represents inferencing a neural network using a basic
+    inferencing loop.
+
+    This class contains:
+
+    * the data needed for an experiment. The recommended way of setting this
+      data is via the arguments of the constructor. Additionally, some of the
+      items have getters that are used by the ``Workers`` that execute the
+      experiments and by the inferencing loop.
+    * the inferencing algorithm, with the main entry point being ``train()``.
+
+    Note:
+        When executing a ``BasicInferencing`` in the cloud, additional constraints
+        are applied to the data. For example, the model is restricted to
+        sequential layers of specific types; the dataset choices are limited,
+        etc. Please check the ``CloudRunner`` documentation.
+    """
+
+    def __init__(
+            self,
+            dataset: Type[Dataset],
+            model: Module,
+            batch_size: int = 10,
+            loss_function: type = CrossEntropyLoss,
+            weight_template_id: str = "",
+            inference_repeats: int = 2,
+            inference_time: int = 86400,
+            remap_weights: bool = True
+    ):
+        """Create a new ``BasicInferencing``.
+
+        Args:
+            dataset: the dataset class to be used.
+            model: the neural network to use for inferencing.
+            batch_size: the batch size used for inferencing.
+            loss_function: the loss function used in the neural network.
+            weight_template_id: weights and biases of the trained neural network.
+            inference_repeats: the number of times running the inference.
+            inference_time: the time span between programming the chip and performing the inference.
+            remap_weights:  whether to remap the weights
+        """
+        self.dataset = dataset
+        self.model = model
+        self.batch_size = batch_size
+        self.loss_function = loss_function
+        self.inference_repeats = inference_repeats
+        self.inference_time = inference_time
+        self.weight_template_id = weight_template_id
+        self.remap_weights = remap_weights
+
+        super().__init__()
+
+    def get_dataset_arguments(self, dataset: type) -> Tuple[Dict, Dict]:
+        """Return the dataset constructor arguments for specifying subset."""
+        if dataset in (SVHN, ):
+            return {'split': 'train'}, {'split': 'test'}
+        return {'train': True}, {'train': False}
+
+    def get_dataset_transform(self, dataset: type) -> Any:
+        """Return the dataset transform."""
+        # Normalize supported datasets.
+        if dataset == FashionMNIST:
+            # mean = Tensor([0.2860])
+            # std_dev = Tensor([0.3205])
+            # transform = Compose([ToTensor(), Normalize(mean, std_dev)])
+            # Note: I removed the normalize step to match up with
+            # the steps that were used by Fabio to generate the weight file.
+            transform = Compose([ToTensor()])
+        elif dataset == SVHN:
+            mean = Tensor([0.4377, 0.4438, 0.4728])
+            std_dev = Tensor([0.1980, 0.2010, 0.1970])
+            transform = Compose([ToTensor(), Normalize(mean, std_dev)])
+        else:
+            transform = Compose([ToTensor()])
+
+        return transform
+
+    def get_data_loader(
+            self,
+            dataset: type,
+            batch_size: int,
+            max_elements: int = 0,
+            dataset_root: str = '/tmp/datasets'
+    ) -> DataLoader:
+        """Return `DataLoaders` for the selected dataset.
+
+        Args:
+            dataset: the dataset class to be used.
+            batch_size: the batch size used for inferencing.
+            max_elements: the maximum number of elements of the dataset
+                to be used. If ``0``, the full dataset is used.
+            dataset_root: the path to the folder where the files from the
+                dataset are stored.
+
+        Returns:
+            A tuple with the inferencing and validation loaders.
+        """
+        # Create the sets and the loaders.
+        _, test_args = self.get_dataset_arguments(dataset)
+        transform = self.get_dataset_transform(dataset)
+
+        # Create the validation set
+        validation_set = dataset(dataset_root, transform=transform, **test_args)
+
+        if max_elements > 0:
+            validation_set = Subset(validation_set, range(max_elements))
+
+        validation_loader = DataLoader(validation_set, batch_size=batch_size, shuffle=False)
+
+        return validation_loader
+
+    def get_model(
+            self,
+            weight_template_id: str,
+            device: torch_device
+    ) -> Module:
+        """Get a copy of the set-up model (with load the weights and biases)
+        from the original experiment model.
+
+        Args:
+            weight_template_id: location/index for the file
+                that contains the state_dicts for the model.
+            device: the torch device used for the model.
+
+        Returns:
+            a copied model with loaded weights and biases
+
+        """
+        model = deepcopy(self.model)
+
+        if weight_template_id != "":
+            if weight_template_id[0:1] == "." or weight_template_id[0:1] == '/':
+                # This is the case where it is a local file
+                template_path = weight_template_id
+            else:
+                template_dir = "/tmp/weight_templates"
+                if weight_template_id.startswith('http'):
+                    template_url = weight_template_id
+                else:
+                    print('weights_template_id: ', weight_template_id)
+                    template_path = template_dir + "/" + weight_template_id + ".pth"
+                    template_url = WEIGHT_TEMPLATE_URL + weight_template_id + ".pth"
+                # check if the file exists
+                if not path.exists(template_dir):
+                    mkdir(template_dir)
+                if not path.exists(template_path):
+                    download(template_url, template_path)
+
+            print('template_path: ', template_path)
+            if path.exists(template_path):
+                model.load_state_dict(load(template_path, map_location=device),
+                                      load_rpu_config=False)
+            else:
+                print('Checkpoint file: ', template_path, ' does not exist.')
+
+            if self.remap_weights:
+                for module in model.analog_modules():
+                    module.remap_weights(1.0)
+
+        return model.to(device)
+
+    def inferencing_step(
+            self,
+            validation_loader: DataLoader,
+            model: Module,
+            loss_function: _Loss,
+            t_inference_list: list,
+            device: torch_device
+    ) -> Tuple[ndarray, ndarray, ndarray]:
+        """Run a single inferencing.
+
+        Args:
+            validation_loader: the data loader for the inferencing data.
+            model: the neural network to be trained.
+            loss_function: the loss function used for inferencing.
+            t_inference_list: list of t_inferences.
+            device: the torch device used for the model.
+
+        Return:
+            Tuple of ndarray of inference accuracy, error and loss.
+        """
+
+        # Set the mode mode to eval mode.
+        model.eval()
+        # Reset the program analog weights.
+        model.program_analog_weights()
+
+        n_inference = len(t_inference_list)
+        infer_accuracy = zeros(n_inference)
+        infer_error = zeros(n_inference)
+        infer_loss = zeros(n_inference)
+
+        # Simulation of inference pass at different times after training.
+        # Go through the generated list
+        for idx, t_inference in enumerate(t_inference_list):
+            # Set the drift_analog_weights
+            model.drift_analog_weights(t_inference)
+
+            # Initialize variables as needed.
+            predicted_ok = 0
+            total_images = 0
+            total_loss = 0
+
+            # Go through the images in the validation dataset
+            for images, labels in validation_loader:
+                # Load the images and labels into the memory of the device
+                images = images.to(device)
+                labels = labels.to(device)
+                # Do prediction for the images using the model.
+                predict = model(images)
+                # Calculate the loss
+                loss = loss_function(predict, labels)
+
+                # Cummulate the loss to total_loss
+                n_images = images.size(0)
+                total_images += n_images
+                total_loss += loss.item() * n_images
+
+                _, predicted = torch_max(predict.data, 1)
+                predicted_ok += (predicted == labels).sum().item()
+
+            # Save the information in the np arrays and return
+            accuracy_post = predicted_ok / total_images * 100.
+            infer_accuracy[idx] = accuracy_post
+            infer_error[idx] = 100. - accuracy_post
+            infer_loss[idx] = total_loss / total_images
+
+        return infer_accuracy, infer_error, infer_loss
+
+    def inference(
+            self,
+            validation_loader: DataLoader,
+            model: Module,
+            loss_function: _Loss,
+            inference_repeats: int,
+            inference_time: int,
+            device: torch_device,
+            n_inference_times: int = 10,
+    ) -> Dict:
+        """Run the inferencing loop.
+
+        Args:
+            validation_loader: the data loader for the validation data.
+            model: the neural network to be trained.
+            loss_function: the loss function used for inferencing.
+            inference_repeats: the number of times to repeat the process
+                zof programming and drifting.
+            inference_time: the time span between programming the chip and performing the inference.
+            device: the torch device used for the model.
+            n_inference_times: how many inference times (log-spaced)
+
+        Returns:
+            A list of the metrics for each epoch.
+        """
+
+        # Move the model to the device if needed.
+        if device:
+            model = model.to(device)
+
+        # Create the t_inference_list using inference_time.
+        # Generate the 9 values between 0 and the inference time using log10
+        t_inference_list = [0.0] + logspace(0, log10(float(inference_time)),
+                                            n_inference_times - 1).tolist()
+        repeat_results = {}
+        accuracy_array = array([])
+        error_array = array([])
+        loss_array = array([])
+
+        for repeat in range(inference_repeats):
+            self._call_hook(Signals.INFERENCE_REPEAT_START, repeat)
+            infer_accuracy, infer_error, infer_loss = self.inferencing_step(
+                validation_loader, model, loss_function, t_inference_list, device)
+
+            # Save the info
+            accuracy_array = concatenate([accuracy_array, infer_accuracy])  # type: ignore
+            error_array = concatenate([error_array, infer_error])  # type: ignore
+            loss_array = concatenate([loss_array, infer_loss])  # type: ignore
+
+            # call the metric hook function with the average information
+            # to write out the partial result to standard out.
+            shape = (repeat + 1, n_inference_times)
+            repeat_results = self._call_hook(Signals.INFERENCE_REPEAT_END,
+                                             array(t_inference_list),
+                                             accuracy_array.reshape(shape).mean(axis=0),
+                                             accuracy_array.reshape(shape).std(axis=0),
+                                             error_array.reshape(shape).mean(axis=0),
+                                             loss_array.reshape(shape).mean(axis=0),
+                                             self.inference_repeats)
+        return deepcopy(repeat_results)
+
+    def _print_rpu_fields(self, model: Module) -> None:
+        """Print the Inference RPU Config fields"""
+
+        print('\n>>> inferenceworker.py: STARTING _print_rpu_fields() ')
+
+        for name, module in model.named_modules():
+            if not isinstance(module, AnalogModuleBase):
+                continue
+
+            print(f'RPUConfig of module {name}:')
+            tile = next(module.analog_tiles())
+            print(tile.rpu_config)
+            print(tile.tile)
+            print('-------------')
+
+        print('\n>>> inferenceworker.py: ENDING _print_rpu_fields() ')
+
+    def run(self, max_elements: int = 0,
+            dataset_root: str = '/tmp/data',
+            device: Optional[torch_device] = None,
+            ) -> Dict:
+        """ Sets up the internal model and runs the inference.
+
+        Results are returned and the internal model is updated.
+        """
+
+        # Build the objects needed for inferencing.
+        # Get valication dataset
+        validation_loader = self.get_data_loader(
+            self.dataset, self.batch_size,
+            max_elements=max_elements,
+            dataset_root=dataset_root
+        )
+
+        # Load the weights and biases to the model.
+        # Assumption: the model already includes the customer-specified InferenceRPUConfig.
+        model = self.get_model(self.weight_template_id, device)
+
+        self._print_rpu_fields(model)
+
+        # Invoke the inference step
+        result = self.inference(validation_loader,
+                                model,
+                                self.loss_function(),
+                                self.inference_repeats,
+                                self.inference_time,
+                                device)
+        self.model = model  # update the stored model with the trained one
+        return result
+
+    def __str__(self) -> str:
+        """Return a string representation of a BasicInferencing experiment."""
+        return ('{}(dataset={}, batch_size={}, loss_function={}, inference_repeats={}, '
+                'inference_time={}, model={})'.format(
+                    self.__class__.__name__,
+                    getattr(self.dataset, '__name__', self.dataset),
+                    self.batch_size,
+                    getattr(self.loss_function, '__name__', self.loss_function),
+                    self.inference_repeats,
+                    self.inference_time,
+                    self.model
+                ))
diff --git a/src/aihwkit/experiments/experiments/training.py b/src/aihwkit/experiments/experiments/training.py
index 743abcc2..a6151b9c 100644
--- a/src/aihwkit/experiments/experiments/training.py
+++ b/src/aihwkit/experiments/experiments/training.py
@@ -12,7 +12,7 @@
 
 """Basic training Experiment."""
 
-from typing import Any, Dict, List, Tuple, Type
+from typing import Any, Dict, List, Tuple, Type, Optional
 
 from torch import device as torch_device, max as torch_max, Tensor
 from torch.nn import Module, NLLLoss
@@ -255,10 +255,6 @@ def train(
         """
         results = []
 
-        # Move the model to the device if needed.
-        if device:
-            model = model.to(device)
-
         for epoch_number in range(epochs):
             self._call_hook(Signals.EPOCH_START, epoch_number)
             self._call_hook(Signals.TRAIN_EPOCH_START, epoch_number)
@@ -282,6 +278,38 @@ def train(
 
         return results
 
+    def run(self, max_elements: int = 0,
+            dataset_root: str = '/tmp/data',
+            device: Optional[torch_device] = None) -> List[Dict]:
+        """ Sets up and runs the training.
+
+        Results are returned and the internal model is updated.
+        """
+
+        # Build the objects needed for training.
+        training_loader, validation_loader = self.get_data_loaders(
+            self.dataset, self.batch_size,
+            max_elements_train=max_elements,
+            dataset_root=dataset_root
+        )
+
+        optimizer = self.get_optimizer(self.learning_rate, self.model)
+
+        # Move the model to the device if needed.
+        model = self.model
+        if device:
+            model = model.to(device)
+
+        results = self.train(training_loader,
+                             validation_loader,
+                             model,
+                             optimizer,
+                             self.loss_function(),
+                             self.epochs,
+                             device)
+        self.model = model  # update the stored model with the trained one
+        return results
+
     def __str__(self) -> str:
         """Return a string representation of a BasicTraining experiment."""
         return ('{}(dataset={}, batch_size={}, loss_function={}, epochs={}, '
diff --git a/src/aihwkit/experiments/runners/__init__.py b/src/aihwkit/experiments/runners/__init__.py
index 91f6ba7f..63e02a23 100644
--- a/src/aihwkit/experiments/runners/__init__.py
+++ b/src/aihwkit/experiments/runners/__init__.py
@@ -15,4 +15,8 @@
 # Convenience imports for easier access to the classes.
 
 from aihwkit.experiments.runners.cloud import CloudRunner
+from aihwkit.experiments.runners.i_cloud import InferenceCloudRunner
 from aihwkit.experiments.runners.local import LocalRunner
+from aihwkit.experiments.runners.i_local import InferenceLocalRunner
+from aihwkit.experiments.runners.metrics import LocalMetric
+from aihwkit.experiments.runners.i_metrics import InferenceLocalMetric
diff --git a/src/aihwkit/experiments/runners/i_cloud.py b/src/aihwkit/experiments/runners/i_cloud.py
new file mode 100644
index 00000000..bcaaaa19
--- /dev/null
+++ b/src/aihwkit/experiments/runners/i_cloud.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Base class for an Experiment Runner."""
+
+from typing import Any, Dict, List, Optional
+
+from aihwkit.cloud.client.entities import CloudExperiment
+from aihwkit.cloud.client.exceptions import CredentialsError
+from aihwkit.cloud.client.session import ApiSession
+from aihwkit.cloud.client.utils import ClientConfiguration
+from aihwkit.cloud.client.v1.i_api_client import InferenceApiClient
+from aihwkit.experiments import BasicInferencing
+from aihwkit.experiments.runners.base import Runner
+
+
+class InferenceCloudRunner(Runner):
+    """Runner that executes Experiments in the AIHW Composer cloud.
+
+    Class that allows executing Experiments in the cloud.
+    """
+    # pylint: disable=too-few-public-methods
+
+    def __init__(self,
+                 api_url: Optional[str] = None,
+                 api_token: Optional[str] = None,
+                 verify: bool = False):
+        """Create a new ``InferenceCloudRunner``.
+
+        Note:
+            If no ``api_token`` or ``api_url`` is provided, this class will
+            attempt to read them from the local configuration file (by default,
+            at ``~/.config/aihwkit/aihwkit.conf`` or  environment variables
+            (``AIHW_API_TOKEN``).
+
+        Args:
+            api_url: the URL of the AIHW Composer API.
+            api_token: the API token for authentication.
+            verify: if ``False``, disable the remote server TLS verification.
+
+        Raises:
+            CredentialsError: if no credentials could be found.
+        """
+        # Attempt to load credentials if not present.
+        if not api_url or not api_token:
+            config = ClientConfiguration()
+            api_url = api_url or config.url
+            api_token = api_token or config.token
+
+            if not api_url or not api_token:
+                raise CredentialsError('No credentials could be found')
+
+        self.api_url = api_url
+        self.api_token = api_token
+
+        # Authenticate.
+        self.session = ApiSession(self.api_url, self.api_token, verify)
+        self.api_client = InferenceApiClient(self.session)
+
+    def get_cloud_experiment(self, id_: str) -> CloudExperiment:
+        """Return a single cloud experiment by id.
+
+        Args:
+            id_: the identifier of the cloud experiment.
+
+        Returns:
+            A ``CloudExperiment``.
+        """
+        return self.api_client.experiment_get(id_)
+
+    def list_cloud_experiments(self) -> List[CloudExperiment]:
+        """Return a list of cloud experiments.
+
+        Returns:
+            A list of ``CloudExperiments``.
+        """
+        return self.api_client.experiments_list()
+
+    def run(  # type: ignore[override]
+            self,
+            experiment: BasicInferencing,
+            analog_info: Dict,
+            noise_model_info: Dict,
+            name: str = '',
+            device: str = 'gpu',
+            **_: Any
+    ) -> CloudExperiment:
+        """Run a single Experiment.
+
+        Starts the execution of an Experiment in the cloud. Upon successful
+        invocation, this method will return a ``CloudExperiment`` object that
+        can be used for inspecting the status of the remote execution.
+
+        Note:
+            Please be aware that the ``experiment`` is subjected to some
+            constraints compared to local running of experiments.
+
+        Args:
+            experiment: the experiment to be executed.
+            analog_info: analog information,
+            noise_model_info: noise model information,
+            name: an optional name for the experiment.
+            device: the desired device.
+            _: extra arguments for the runner.
+
+        Returns:
+            A ``CloudExperiment`` which represents the remote experiment.
+        """
+        # pylint: disable=arguments-differ
+        # Generate an experiment name if not given.
+        if not name:
+            name = 'aihwkit inference cloud experiment ({}, {} layers)'.format(
+                experiment.dataset.__name__,
+                len(experiment.model)
+            )
+
+        return self.api_client.experiment_create(experiment, analog_info,
+                                                 noise_model_info, name, device)
diff --git a/src/aihwkit/experiments/runners/i_local.py b/src/aihwkit/experiments/runners/i_local.py
new file mode 100644
index 00000000..3f0d9e6d
--- /dev/null
+++ b/src/aihwkit/experiments/runners/i_local.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Runner that executes Experiments locally."""
+
+from typing import Dict, Optional
+
+from torch import device as torch_device
+from torchvision.datasets import FashionMNIST, SVHN
+
+from aihwkit.experiments.experiments.base import Signals
+from aihwkit.experiments.runners.base import Runner
+from aihwkit.experiments.experiments.inferencing import BasicInferencing
+from aihwkit.experiments.runners.i_metrics import InferenceLocalMetric
+
+
+class InferenceLocalRunner(Runner):
+    """Runner that executes Experiments locally.
+
+    Class that allows executing Experiments locally.
+    """
+    # pylint: disable=too-few-public-methods
+
+    def __init__(self,
+                 device: Optional[torch_device] = None):
+        """Create a new ``InferenceLocalRunner``.
+
+        Args:
+            device: the device where the model will be running on.
+        """
+        self.device = device
+
+    def run(  # type: ignore[override]
+            self,
+            experiment: BasicInferencing,
+            max_elements: int = 0,
+            dataset_root: str = '/tmp/datasets',
+            stdout: bool = False,
+    ) -> Dict:
+        """Run a single Experiment.
+
+        Executes an experiment locally, in the device specified by
+        ``self.device``, optionally printing information to stdout.
+
+        Note:
+            If using a dataset different than ``FashionMNIST`` or ``SVHN``,
+            the runner assumes that the files for the dataset are downloaded at
+            ``dataset_root``. For those two datasets, the downloading will
+            take place automatically if the files are not present.
+
+        Args:
+            experiment: the experiment to be executed.
+            max_elements: limit on the amount of samples to use from
+                the dataset. If ``0``, no limit is applied.
+            dataset_root: path for the dataset files.
+            stdout: enable printing to stdout during the execution of the
+                experiment.
+
+        Returns:
+
+            A dictionary with the inference results.
+        """
+        # pylint: disable=arguments-differ
+
+        # Setup the metric helper for the experiment.
+        metric = InferenceLocalMetric(stdout=stdout)
+        experiment.clear_hooks()
+        experiment.add_hook(Signals.INFERENCE_REPEAT_START, metric.receive_repeat_start)
+        experiment.add_hook(Signals.INFERENCE_REPEAT_END, metric.receive_repeat_end)
+
+        # Download the FashionMNIST or SVHN dataset if needed.
+        if experiment.dataset == FashionMNIST:
+            _ = experiment.dataset(dataset_root, download=True)
+        elif experiment.dataset == SVHN:
+            _ = experiment.dataset(dataset_root, download=True, split='train')
+            _ = experiment.dataset(dataset_root, download=True, split='test')
+
+        # Invoke the inference step
+        return experiment.run(max_elements, dataset_root, self.device)
diff --git a/src/aihwkit/experiments/runners/i_metrics.py b/src/aihwkit/experiments/runners/i_metrics.py
new file mode 100644
index 00000000..fde4c3ae
--- /dev/null
+++ b/src/aihwkit/experiments/runners/i_metrics.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Helper for retrieving Metrics of an Experiment."""
+
+from datetime import datetime
+import json
+from typing import Dict
+
+
+class InferenceLocalMetric:
+    """Metric used by the InferenceWorker Runner."""
+
+    def __init__(self, stdout: bool = False) -> None:
+        self.current_repeat: Dict = {}
+        self.time_init = datetime.utcnow()
+        self.stdout = stdout
+
+    def receive_repeat_start(self, repeat: int) -> None:
+        """Hook for `INFERENCE_REPEAT_START`."""
+        self.current_repeat = {
+                "number": repeat,
+                "inference_results": []
+        }
+
+    def receive_repeat_end(self, t_inference_array: list, avg_acc_arr: list,
+                           std_acc_arr: list, avg_err_arr: list, avg_loss_arr: list,
+                           inference_repeats: int) -> Dict:
+        """Hook for `INFERENCE_REPEAT_END`."""
+
+        inf_results = []
+        n_inference = len(t_inference_array)
+
+        # The input are the arrays of avg accuracy, avg error and avg loss.
+        # Create the dict entry for the items in the arrays.
+        for i in range(n_inference):
+            new_dict = {
+                    "t_inference": t_inference_array[i],
+                    "avg_accuracy": avg_acc_arr[i],
+                    "std_accuracy": std_acc_arr[i],
+                    "avg_error": avg_err_arr[i],
+                    "avg_loss": avg_loss_arr[i]}
+            inf_results.append(new_dict)
+
+        repeat = self.current_repeat["number"] + 1
+        time_elapsed = (datetime.utcnow() - self.time_init).total_seconds()
+        is_partial = bool(repeat < inference_repeats)
+        partial = {
+            "inference_runs": {
+                    "inference_repeat": repeat,
+                    "is_partial": is_partial,
+                    "time_elapsed": time_elapsed,
+                    "inference_results":  inf_results
+            }
+        }
+
+        if self.stdout:
+            print("{}".format(json.dumps(partial)))
+
+        # Return the partial.
+        return partial
diff --git a/src/aihwkit/experiments/runners/local.py b/src/aihwkit/experiments/runners/local.py
index 36797a12..99305ee1 100644
--- a/src/aihwkit/experiments/runners/local.py
+++ b/src/aihwkit/experiments/runners/local.py
@@ -44,7 +44,7 @@ def run(  # type: ignore[override]
             experiment: BasicTraining,
             max_elements_train: int = 0,
             dataset_root: str = '/tmp/datasets',
-            stdout: bool = True
+            stdout: bool = False,
     ) -> List[Dict]:
         """Run a single Experiment.
 
@@ -66,7 +66,8 @@ def run(  # type: ignore[override]
                 experiment.
 
         Returns:
-            A list of dictionaries with information about each epoch.
+            A list of dictionaries with information about each
+            epoch.
         """
         # pylint: disable=arguments-differ
 
@@ -88,24 +89,4 @@ def run(  # type: ignore[override]
             _ = experiment.dataset(dataset_root, download=True, split='train')
             _ = experiment.dataset(dataset_root, download=True, split='test')
 
-        # Move the model to the device.
-        if self.device:
-            if self.device.type == 'cuda':
-                experiment.model = experiment.model.to(self.device)
-
-        # Build the objects needed for training.
-        training_loader, validation_loader = experiment.get_data_loaders(
-            experiment.dataset, experiment.batch_size,
-            max_elements_train=max_elements_train,
-            dataset_root=dataset_root
-        )
-        optimizer = experiment.get_optimizer(experiment.learning_rate, experiment.model)
-
-        experiment.results = experiment.train(training_loader,
-                                              validation_loader,
-                                              experiment.model,
-                                              optimizer,
-                                              experiment.loss_function(),
-                                              experiment.epochs,
-                                              self.device)
-        return experiment.results
+        return experiment.run(max_elements_train, dataset_root, self.device)
diff --git a/src/aihwkit/inference/compensation/base.py b/src/aihwkit/inference/compensation/base.py
index f41f9e46..a2464959 100644
--- a/src/aihwkit/inference/compensation/base.py
+++ b/src/aihwkit/inference/compensation/base.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/src/aihwkit/inference/converter/base.py b/src/aihwkit/inference/converter/base.py
index fbe5f382..02215086 100644
--- a/src/aihwkit/inference/converter/base.py
+++ b/src/aihwkit/inference/converter/base.py
@@ -52,3 +52,6 @@ def convert_back_to_weights(self, conductances: List[Tensor], params: Dict) -> T
             weight matrix
         """
         raise NotImplementedError
+
+    def __eq__(self, other: object) -> bool:
+        return self.__class__ == other.__class__ and self.__dict__ == other.__dict__
diff --git a/src/aihwkit/inference/noise/base.py b/src/aihwkit/inference/noise/base.py
index a3187cdf..be58a60e 100644
--- a/src/aihwkit/inference/noise/base.py
+++ b/src/aihwkit/inference/noise/base.py
@@ -154,3 +154,6 @@ def apply_drift_noise_to_conductance(
             conductance Tensor with applied noise and drift
         """
         raise NotImplementedError
+
+    def __eq__(self, other: object) -> bool:
+        return self.__class__ == other.__class__ and self.__dict__ == other.__dict__
diff --git a/src/aihwkit/nn/modules/base.py b/src/aihwkit/nn/modules/base.py
index 2ab4b4d6..907d7563 100644
--- a/src/aihwkit/nn/modules/base.py
+++ b/src/aihwkit/nn/modules/base.py
@@ -126,7 +126,6 @@ def register_analog_tile(self, tile: 'BaseTile', name: Optional[str] = None) ->
             par_name = self.ANALOG_SHARED_WEIGHT_PREFIX + str(self._analog_tile_counter)
             self.register_parameter(par_name, tile.shared_weights)
             self.register_helper(par_name)
-
         mapping = tile.rpu_config.mapping
         if mapping.learn_out_scaling_alpha:
             if tile.out_scaling_alpha is None:
@@ -453,7 +452,8 @@ def _load_from_state_dict(
                 analog_state = state_dict.pop(key).copy()
 
                 if not self._load_rpu_config:
-                    if analog_tile.rpu_config.__class__ != analog_state['rpu_config'].__class__:
+
+                    if not isinstance(analog_tile.rpu_config, type(analog_state['rpu_config'])):
                         raise ModuleError("RPU config mismatch during loading: "
                                           "Tried to replace "
                                           f"{analog_state['rpu_config'].__class__.__name__} "
diff --git a/src/aihwkit/nn/modules/container.py b/src/aihwkit/nn/modules/container.py
index b6530acf..f32e4117 100644
--- a/src/aihwkit/nn/modules/container.py
+++ b/src/aihwkit/nn/modules/container.py
@@ -12,7 +12,9 @@
 
 """Analog Modules that contain children Modules."""
 
-from typing import Callable, Optional, Union, Any, NamedTuple, TYPE_CHECKING
+from typing import (
+    Callable, Optional, Union, Any, NamedTuple, TYPE_CHECKING, Generator
+)
 from collections import OrderedDict
 
 from torch import device as torch_device
@@ -52,12 +54,17 @@ def _apply_to_analog(self, fn: Callable) -> 'AnalogSequential':
         Returns:
             This module after the function has been applied.
         """
-        for module in self.modules():
-            if isinstance(module, AnalogModuleBase):
-                fn(module)
+        for module in self.analog_modules():
+            fn(module)
 
         return self
 
+    def analog_modules(self) -> Generator[AnalogModuleBase, None, None]:
+        """Generator over analog modules only"""
+        for module in self.modules():
+            if isinstance(module, AnalogModuleBase):
+                yield module
+
     def cpu(
             self
     ) -> 'AnalogSequential':
diff --git a/src/aihwkit/optim/context.py b/src/aihwkit/optim/context.py
index 8de6318a..1aa65e36 100644
--- a/src/aihwkit/optim/context.py
+++ b/src/aihwkit/optim/context.py
@@ -57,6 +57,7 @@ def get_data(self) -> Tensor:
 
     def reset(self, analog_tile: Optional['BaseTile'] = None) -> None:
         """Reset the gradient trace and optionally sets the tile pointer."""
+
         if analog_tile is not None:
             self.analog_tile = analog_tile
             self.analog_tile.analog_ctx = self
@@ -68,6 +69,15 @@ def has_gradient(self) -> bool:
         """Return whether a gradient trace was stored."""
         return len(self.analog_input) > 0
 
+    def __copy__(self) -> Parameter:
+        """Turn off copying of the pointers. Context will be re-created
+        when tile is created"""
+        return Parameter(self.data)
+
+    def __deepcopy__(self, memo: Any) -> Parameter:
+        """ Turn off deep copying. Context will be re-created when tile is created """
+        return Parameter(self.data)
+
     def cuda(
             self,
             device: Optional[Union[torch_device, str, int]] = None
diff --git a/src/aihwkit/simulator/configs/devices.py b/src/aihwkit/simulator/configs/devices.py
index 1c34efe8..543f385c 100644
--- a/src/aihwkit/simulator/configs/devices.py
+++ b/src/aihwkit/simulator/configs/devices.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/src/aihwkit/simulator/configs/utils.py b/src/aihwkit/simulator/configs/utils.py
index 86f40505..d3d913e9 100644
--- a/src/aihwkit/simulator/configs/utils.py
+++ b/src/aihwkit/simulator/configs/utils.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/src/aihwkit/simulator/noise_models.py b/src/aihwkit/simulator/noise_models.py
index c8982ed8..5e3e831d 100644
--- a/src/aihwkit/simulator/noise_models.py
+++ b/src/aihwkit/simulator/noise_models.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/src/aihwkit/simulator/presets/configs.py b/src/aihwkit/simulator/presets/configs.py
index a15862f4..9d0f2e50 100644
--- a/src/aihwkit/simulator/presets/configs.py
+++ b/src/aihwkit/simulator/presets/configs.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/src/aihwkit/simulator/presets/utils.py b/src/aihwkit/simulator/presets/utils.py
index 4fbc47c2..9de3cf14 100644
--- a/src/aihwkit/simulator/presets/utils.py
+++ b/src/aihwkit/simulator/presets/utils.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
diff --git a/src/aihwkit/simulator/presets/web.py b/src/aihwkit/simulator/presets/web.py
new file mode 100644
index 00000000..a557fc89
--- /dev/null
+++ b/src/aihwkit/simulator/presets/web.py
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""RPU configurations presets used for the composer interface."""
+
+# pylint: disable=too-many-instance-attributes
+
+from dataclasses import dataclass, field
+from aihwkit.simulator.configs.configs import (
+    InferenceRPUConfig
+)
+
+from aihwkit.simulator.configs.utils import (
+    IOParameters, WeightClipParameter,  WeightModifierParameter, MappingParameter,
+    BoundManagementType, NoiseManagementType, WeightNoiseType, WeightModifierType,
+    WeightClipType
+)
+from aihwkit.inference import (
+    BaseDriftCompensation, BaseNoiseModel, GlobalDriftCompensation,
+    PCMLikeNoiseModel
+)
+
+
+@dataclass
+class WebComposerIOParameters(IOParameters):
+    r"""Preset for the forward and backward pass parameters.
+
+    The default values used for the web composer
+    """
+
+    bound_management: BoundManagementType = BoundManagementType.ITERATIVE
+    noise_management: NoiseManagementType = NoiseManagementType.ABS_MAX
+
+    inp_res: float = 1.0 / (2**7 - 2)  # 7 bit DAC.
+    inp_sto_round: bool = False
+
+    out_bound: float = 20.0
+    out_noise: float = 0.1
+    out_res: float = 1.0 / (2**9 - 2)  # 9 bit ADC.
+
+    w_noise: float = 0.0
+    w_noise_type: WeightNoiseType = WeightNoiseType.NONE
+
+
+@dataclass
+class WebComposerWeightModifierParameter(WeightModifierParameter):
+    r"""Preset for the WeightModifierParameter
+
+    The default values used for the web composer
+    """
+
+    type: WeightModifierType = WeightModifierType.ADD_NORMAL
+    std_dev: float = 0.1
+
+
+@dataclass
+class WebComposerWeightClipParameter(WeightClipParameter):
+    """Parameter that clip the weights during hardware-aware training.
+
+    The default values used for the web composer.
+    """
+
+    fixed_value: float = 1.0
+    type: WeightClipType = WeightClipType.FIXED_VALUE
+
+
+@dataclass
+class WebComposerMappingParameter(MappingParameter):
+    """Parameter related to hardware design and the mapping of logical
+    weight matrices to physical tiles.
+
+    The default values used for the web composer.
+    """
+
+    digital_bias: bool = True
+
+    weight_scaling_omega: float = 1.0
+    weight_scaling_omega_columnwise: bool = True
+    learn_out_scaling_alpha: bool = True
+
+    max_input_size: int = 512
+    max_output_size: int = 512
+
+
+@dataclass
+class WebComposerInferenceRPUConfig(InferenceRPUConfig):
+    """Preset configuration used as default for the Inference Composer
+    """
+
+    forward: IOParameters = field(default_factory=WebComposerIOParameters)
+    """Input-output parameter setting for the forward direction."""
+
+    noise_model: BaseNoiseModel = field(default_factory=PCMLikeNoiseModel)
+    """Statistical noise model to be used during (realistic) inference."""
+
+    drift_compensation: BaseDriftCompensation = field(default_factory=GlobalDriftCompensation)
+    """For compensating the drift during inference only."""
+
+    clip: WeightClipParameter = field(default_factory=WebComposerWeightClipParameter)
+    """Parameter for weight clip."""
+
+    modifier: WeightModifierParameter = field(default_factory=WebComposerWeightModifierParameter)
+    """Parameter for weight modifier."""
+
+    mapping: MappingParameter = field(default_factory=WebComposerMappingParameter)
+    """Parameter related to mapping weights to tiles for supporting modules."""
+
+
+@dataclass
+class OldWebComposerMappingParameter(WebComposerMappingParameter):
+    """Parameter related to hardware design and the mapping of logical
+    weight matrices to physical tiles.
+
+    The default values used for the web composer.
+    """
+
+    digital_bias: bool = False
+
+    weight_scaling_omega: float = 1.0
+    weight_scaling_omega_columnwise: bool = True
+    learn_out_scaling_alpha: bool = False
+
+
+@dataclass
+class OldWebComposerInferenceRPUConfig(WebComposerInferenceRPUConfig):
+    """Preset configuration used as default for the Inference Composer
+    """
+
+    mapping: MappingParameter = field(default_factory=OldWebComposerMappingParameter)
+    """Parameter related to mapping weights to tiles for supporting modules."""
diff --git a/src/aihwkit/simulator/tiles/base.py b/src/aihwkit/simulator/tiles/base.py
index 26d29843..6c23c913 100644
--- a/src/aihwkit/simulator/tiles/base.py
+++ b/src/aihwkit/simulator/tiles/base.py
@@ -182,6 +182,9 @@ def __setstate__(self, state: Dict) -> None:
         shared_weights = current_dict.pop('shared_weights')
         shared_weights_if = shared_weights is not None
 
+        current_dict.pop('noise_model', None)  # legacy
+        current_dict.pop('drift_compensation', None)  # legacy
+
         self.__dict__.update(current_dict)
 
         self.device = torch_device('cpu')
diff --git a/src/aihwkit/simulator/tiles/inference.py b/src/aihwkit/simulator/tiles/inference.py
index 339f144f..a75cfcf3 100644
--- a/src/aihwkit/simulator/tiles/inference.py
+++ b/src/aihwkit/simulator/tiles/inference.py
@@ -60,13 +60,11 @@ def __init__(
             from aihwkit.simulator.configs import InferenceRPUConfig
             rpu_config = InferenceRPUConfig()
 
-        # Noise model.
-        self.noise_model = deepcopy(rpu_config.noise_model)
+        # CAUTION: one cannot save parts of the RPUConfig as
+        # properties of the class! This is because those attributes
+        # would be restored even if the RPUConfig would be replaced
+        # during checkpoint loading
 
-        # Drift compensation.
-        self.drift_compensation = None
-        if rpu_config.drift_compensation:
-            self.drift_compensation = deepcopy(rpu_config.drift_compensation)
         self.drift_baseline = None
         self.drift_readout_tensor = None  # type: Optional[Tensor]
         self.alpha = ones((1,))
@@ -86,11 +84,12 @@ def __init__(
     @no_grad()
     def _forward_drift_readout_tensor(self) -> Optional[Tensor]:
         """Perform a forward pass using the drift read-out tensor."""
-        if self.drift_compensation is None:
+
+        if self.rpu_config.drift_compensation is None:
             return None
 
         if self.drift_readout_tensor is None:
-            self.drift_readout_tensor = self.drift_compensation.get_readout_tensor(
+            self.drift_readout_tensor = self.rpu_config.drift_compensation.get_readout_tensor(
                 self.tile.get_x_size()).detach().to(self.device)
             if self.in_trans:
                 self.drift_readout_tensor = self.drift_readout_tensor.tranpose(0, 1).clone()
@@ -111,17 +110,19 @@ def program_weights(self, from_reference: bool = True) -> None:
         Args:
             from_reference: Whether to use weights from reference
         """
+
         if not from_reference or self.reference_combined_weights is None:
             self.reference_combined_weights = Tensor(self.tile.get_weights())
 
-        self.programmed_weights, self.nu_drift_list = self.noise_model.apply_programming_noise(
-            self.reference_combined_weights)
+        self.programmed_weights, self.nu_drift_list = \
+            self.rpu_config.noise_model.apply_programming_noise(
+                self.reference_combined_weights)
 
         self.tile.set_weights(self.programmed_weights.numpy())
 
-        if self.drift_compensation is not None:
+        if self.rpu_config.drift_compensation is not None:
             forward_output = self._forward_drift_readout_tensor()
-            self.drift_baseline = self.drift_compensation.init_baseline(forward_output)
+            self.drift_baseline = self.rpu_config.drift_compensation.init_baseline(forward_output)
 
     @no_grad()
     def drift_weights(
@@ -146,14 +147,15 @@ def drift_weights(
         if self.programmed_weights is None:
             self.program_weights()
 
-        drifted_weights = self.noise_model.apply_drift_noise(
+        drifted_weights = self.rpu_config.noise_model.apply_drift_noise(
             self.programmed_weights, self.nu_drift_list, t_inference)
         self.tile.set_weights(drifted_weights.detach().cpu().numpy())
 
-        if self.drift_compensation is not None:
+        if self.rpu_config.drift_compensation is not None:
             forward_output = self._forward_drift_readout_tensor()
-            self.alpha = self.drift_compensation.apply(forward_output,
-                                                       self.drift_baseline).to(self.device)
+            self.alpha = self.rpu_config.drift_compensation.apply(
+                forward_output,
+                self.drift_baseline).to(self.device)
 
     def forward(self, x_input: Tensor, is_test: bool = False) -> Tensor:
         """Forward pass with drift compensation.
@@ -172,7 +174,7 @@ def forward(self, x_input: Tensor, is_test: bool = False) -> Tensor:
             weight_modify_params = parameters_to_bindings(self.rpu_config.modifier)
             self.tile.modify_weights(weight_modify_params)
 
-        if not is_test or self.drift_compensation is None:
+        if not is_test or self.rpu_config.drift_compensation is None:
             return super().forward(x_input, is_test)
 
         # only do drift compensation in eval mode
diff --git a/src/aihwkit/utils/visualization.py b/src/aihwkit/utils/visualization.py
index 59a83c94..452e8c40 100644
--- a/src/aihwkit/utils/visualization.py
+++ b/src/aihwkit/utils/visualization.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -163,20 +163,21 @@ def calc_mean_and_std(
         alpha = np.exp(-0.5*(node - w_values)**2/lam**2)
         beta = alpha.sum(axis=0)
         alpha[:, beta < 0.1] = np.nan
-        alpha /= np.expand_dims(beta, axis=0)
+        alpha /= np.expand_dims(beta, axis=0)  # type: ignore
         mean = np.sum(alpha*delta_w, axis=0)
-        std = np.sqrt(np.sum(alpha*(delta_w - np.expand_dims(mean, axis=0))**2, axis=0))
+        std = np.sqrt(np.sum(alpha*(delta_w - np.expand_dims(mean, axis=0))**2,  # type: ignore
+                             axis=0))
         return (mean, std)
 
     # dw statistics.
-    delta_w = np.diff(w_trace, axis=0)
+    delta_w = np.diff(w_trace, axis=0)  # type: ignore
     w_trace_s = w_trace[:-1, :, :]
     if up_direction:
-        msk = np.logical_and(direction[:-1] > 0, np.diff(direction) == 0)
+        msk = np.logical_and(direction[:-1] > 0, np.diff(direction) == 0)  # type: ignore
         w_values = w_trace_s[msk, :, :]
         delta_w_values = delta_w[msk, :, :]
     else:
-        msk = np.logical_and(direction[:-1] < 0, np.diff(direction) == 0)
+        msk = np.logical_and(direction[:-1] < 0, np.diff(direction) == 0)  # type: ignore
         w_values = w_trace_s[msk, :, :]
         delta_w_values = delta_w[msk, :, :]
 
@@ -340,7 +341,7 @@ def estimate_n_steps(rpu_config: Union[SingleRPUConfig, UnitCellRPUConfig]) -> i
     w_min = device_binding.w_min
     w_max = device_binding.w_max
 
-    n_steps = int(np.round((w_max - w_min) / weight_granularity))
+    n_steps = int(np.round((w_max - w_min) / weight_granularity))  # type: ignore
     return n_steps
 
 
@@ -539,8 +540,8 @@ def get_rpu_config(device: Union[PulsedDevice, UnitCell], io_pars: IOParameters)
     axis_right.set_ylim(-limit, limit)
 
     # Set xlim's.
-    limit = np.maximum(np.nanmax(np.abs(dw_mean_down)),
-                       np.nanmax(np.abs(dw_mean_up))) * 1.2
+    limit = np.maximum(np.nanmax(np.abs(dw_mean_down)),  # type: ignore
+                       np.nanmax(np.abs(dw_mean_up))) * 1.2  # type: ignore
     axis_left.set_xlim(0.0, limit)
     axis_right.set_xlim(0.0, limit)
 
@@ -624,7 +625,7 @@ def plot_weight_drift(noise_model: BaseNoiseModel = None,
 
     weights = w_inits.flatten()
     weights.sort()
-    weights = np.tile(weights, [n_repeats, 1])
+    weights = np.tile(weights, [n_repeats, 1])  # type: ignore
 
     analog_tile = InferenceTile(weights.shape[0], weights.shape[1], rpu_config)
     analog_tile.set_weights(from_numpy(weights))


Test patch:
diff --git a/tests/helpers/infer_experiments.py b/tests/helpers/infer_experiments.py
new file mode 100644
index 00000000..549e21db
--- /dev/null
+++ b/tests/helpers/infer_experiments.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+# pylint: disable=missing-function-docstring,too-few-public-methods
+
+"""Models helpers for aihwkit tests."""
+
+from typing import Any
+
+from torch.nn import (
+    BatchNorm2d, Conv2d, Flatten, Linear, LogSoftmax, MaxPool2d, Module,
+    ReLU, Tanh, NLLLoss
+)
+from torchvision.datasets import FashionMNIST, SVHN
+
+from aihwkit.nn import AnalogConv2d, AnalogLinear, AnalogSequential
+from aihwkit.simulator.presets.utils import PresetIOParameters
+from aihwkit.simulator.configs import InferenceRPUConfig
+from aihwkit.simulator.configs.utils import WeightNoiseType, WeightClipType
+from aihwkit.inference import PCMLikeNoiseModel
+from aihwkit.experiments.experiments.inferencing import BasicInferencing
+
+
+class HwTrainedLenet5:
+    """Hardware-aware LeNet5; with FashionMNIST."""
+
+    def get_reference_accuracy(self):
+        """ Reference for 100 samples without noise """
+        return 86.0
+
+    def get_experiment(
+            self,
+            real: bool = False,
+            rpu_config: Any = InferenceRPUConfig()
+    ):
+        """Return a BasicInference experiment."""
+
+        rpu_config = InferenceRPUConfig(forward=PresetIOParameters())
+        rpu_config.forward.w_noise_type = WeightNoiseType.ADDITIVE_CONSTANT
+        rpu_config.clip.type = WeightClipType.FIXED_VALUE
+        rpu_config.clip.fixed_value = 1.0
+        rpu_config.forward.w_noise = 0.0175
+
+        argv = {
+            'dataset': FashionMNIST,
+            'model': self.get_model(rpu_config),
+            'batch_size': 8,
+            'loss_function': NLLLoss,
+            'weight_template_id': 'hw-trained-lenet5',
+            'inference_repeats': 10,
+            'inference_time': 3600
+        }
+
+        if not real:
+            argv['inference_repeats'] = 2
+
+        return BasicInferencing(**argv)
+
+    def get_model(self, rpu_config: Any = InferenceRPUConfig) -> Module:
+        # set the InferenceRPUConfig
+        channel = [16, 32, 512, 128]
+        return AnalogSequential(
+           AnalogConv2d(in_channels=1, out_channels=channel[0], kernel_size=5, stride=1,
+                        rpu_config=rpu_config),
+           Tanh(),
+           MaxPool2d(kernel_size=2),
+           AnalogConv2d(in_channels=channel[0], out_channels=channel[1], kernel_size=5, stride=1,
+                        rpu_config=rpu_config),
+           Tanh(),
+           MaxPool2d(kernel_size=2),
+           Tanh(),
+           Flatten(),
+           AnalogLinear(in_features=channel[2], out_features=channel[3], rpu_config=rpu_config),
+           Tanh(),
+           AnalogLinear(in_features=channel[3], out_features=10, rpu_config=rpu_config),
+           LogSoftmax(dim=1)
+        )
+
+
+class DigitalTrainedLenet5:
+    """Hardware-aware LeNet5; with FashionMNIST."""
+
+    def get_reference_accuracy(self):
+        """ Reference for 100 samples without noise """
+        return 88.0
+
+    def get_experiment(
+            self,
+            real: bool = False,
+            rpu_config: Any = InferenceRPUConfig()
+    ):
+        """Return a BasicInference experiment."""
+
+        rpu_config = InferenceRPUConfig(forward=PresetIOParameters())
+        rpu_config.forward.w_noise_type = WeightNoiseType.ADDITIVE_CONSTANT
+        rpu_config.clip.type = WeightClipType.FIXED_VALUE
+        rpu_config.clip.fixed_value = 1.0
+        rpu_config.forward.w_noise = 0.0175
+
+        argv = {
+            'dataset': FashionMNIST,
+            'model': self.get_model(rpu_config),
+            'batch_size': 8,
+            'loss_function': NLLLoss,
+            'weight_template_id': 'digital-trained-lenet5',
+            'inference_repeats': 10,
+            'inference_time': 3600
+        }
+
+        if not real:
+            argv['inference_repeats'] = 2
+
+        return BasicInferencing(**argv)
+
+    def get_model(self, rpu_config: Any = InferenceRPUConfig) -> Module:
+        # set the InferenceRPUConfig
+        channel = [16, 32, 512, 128]
+        return AnalogSequential(
+           AnalogConv2d(in_channels=1, out_channels=channel[0], kernel_size=5, stride=1,
+                        rpu_config=rpu_config),
+           Tanh(),
+           MaxPool2d(kernel_size=2),
+           AnalogConv2d(in_channels=channel[0], out_channels=channel[1], kernel_size=5, stride=1,
+                        rpu_config=rpu_config),
+           Tanh(),
+           MaxPool2d(kernel_size=2),
+           Tanh(),
+           Flatten(),
+           AnalogLinear(in_features=channel[2], out_features=channel[3], rpu_config=rpu_config),
+           Tanh(),
+           AnalogLinear(in_features=channel[3], out_features=10, rpu_config=rpu_config),
+           LogSoftmax(dim=1)
+        )
+
+
+class HwTrainedVgg8:
+    """Vgg8; with SVHN."""
+
+    def get_reference_accuracy(self):
+        """ Reference for 100 samples without noise """
+        return 93.0
+
+    def get_experiment(
+            self,
+            real: bool = False,
+            rpu_config: Any = InferenceRPUConfig()
+    ):
+        """Return a BasicInference experiment."""
+        output_noise_strength = 0.03999999910593033
+        adc = 9
+        dac = 7
+        programming_noise_scale = 1.0
+        read_noise_scale = 1.0
+        drift_scale = 1.0
+        poly_first_order_coef = 1.965000033378601
+        poly_second_order_coef = -1.1730999946594238
+        poly_constant_coef = 0.26350000500679016
+        rpu_config.forward.out_noise = output_noise_strength
+        rpu_config.forward.out_res = 1/(2**adc - 2)
+        rpu_config.forward.inp_res = 1/(2**dac - 2)
+        rpu_config.noise_model.prog_noise_scale = programming_noise_scale
+        rpu_config.noise_model.read_noise_scale = read_noise_scale
+        rpu_config.noise_model.drift_scale = drift_scale
+        rpu_config.noise_model = PCMLikeNoiseModel(g_max=25.0,
+                                                   prog_coeff=[poly_constant_coef,
+                                                               poly_first_order_coef,
+                                                               poly_second_order_coef])
+
+        argv = {
+            'dataset': SVHN,
+            'model': self.get_model(rpu_config),
+            'batch_size': 128,
+            'loss_function': NLLLoss,
+            'weight_template_id': 'hw-trained-vgg8',
+            'inference_repeats': 10,
+            'inference_time': 3600
+
+        }
+
+        if not real:
+            argv['inference_repeats'] = 2
+
+        return BasicInferencing(**argv)
+
+    def get_model(self, rpu_config: Any = InferenceRPUConfig) -> Module:
+        return AnalogSequential(
+            Conv2d(in_channels=3, out_channels=48,
+                   kernel_size=3, stride=1, padding=1),
+            ReLU(),
+            AnalogConv2d(in_channels=48, out_channels=48,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            BatchNorm2d(48),
+            ReLU(),
+            MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1),
+            AnalogConv2d(in_channels=48, out_channels=96,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            ReLU(),
+            AnalogConv2d(in_channels=96, out_channels=96,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            BatchNorm2d(96),
+            ReLU(),
+            MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1),
+            AnalogConv2d(in_channels=96, out_channels=144,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            ReLU(),
+            AnalogConv2d(in_channels=144, out_channels=144,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            BatchNorm2d(144),
+            ReLU(),
+            MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1),
+            Flatten(),
+            AnalogLinear(in_features=16 * 144, out_features=384,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            ReLU(),
+            Linear(in_features=384, out_features=10),
+            LogSoftmax(dim=1)
+        )
+
+
+class DigitalTrainedVgg8:
+    """Vgg8; with SVHN."""
+
+    def get_reference_accuracy(self):
+        """ Reference for 100 samples without noise """
+        return 6.0
+
+    def get_experiment(
+            self,
+            real: bool = False,
+            rpu_config: Any = InferenceRPUConfig()
+    ):
+        """Return a BasicInference experiment."""
+
+        output_noise_strength = 0.03999999910593033
+        adc = 9
+        dac = 7
+        programming_noise_scale = 1.0
+        read_noise_scale = 1.0
+        drift_scale = 1.0
+        poly_first_order_coef = 1.965000033378601
+        poly_second_order_coef = -1.1730999946594238
+        poly_constant_coef = 0.26350000500679016
+        rpu_config.forward.out_noise = output_noise_strength
+        rpu_config.forward.out_res = 1/(2**adc - 2)
+        rpu_config.forward.inp_res = 1/(2**dac - 2)
+        rpu_config.noise_model.prog_noise_scale = programming_noise_scale
+        rpu_config.noise_model.read_noise_scale = read_noise_scale
+        rpu_config.noise_model.drift_scale = drift_scale
+        rpu_config.noise_model = PCMLikeNoiseModel(g_max=25.0,
+                                                   prog_coeff=[poly_constant_coef,
+                                                               poly_first_order_coef,
+                                                               poly_second_order_coef])
+
+        argv = {
+            'dataset': SVHN,
+            'model': self.get_model(rpu_config),
+            'batch_size': 128,
+            'loss_function': NLLLoss,
+            'weight_template_id': 'digital-trained-vgg8',
+            'inference_repeats': 10,
+            'inference_time': 3600
+
+        }
+
+        if not real:
+            argv['inference_repeats'] = 2
+
+        return BasicInferencing(**argv)
+
+    def get_model(self, rpu_config: Any = InferenceRPUConfig) -> Module:
+        return AnalogSequential(
+            Conv2d(in_channels=3, out_channels=48,
+                   kernel_size=3, stride=1, padding=1),
+            ReLU(),
+            AnalogConv2d(in_channels=48, out_channels=48,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            BatchNorm2d(48),
+            ReLU(),
+            MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1),
+            AnalogConv2d(in_channels=48, out_channels=96,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            ReLU(),
+            AnalogConv2d(in_channels=96, out_channels=96,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            BatchNorm2d(96),
+            ReLU(),
+            MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1),
+            AnalogConv2d(in_channels=96, out_channels=144,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            ReLU(),
+            AnalogConv2d(in_channels=144, out_channels=144,
+                         kernel_size=3, stride=1, padding=1,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            BatchNorm2d(144),
+            ReLU(),
+            MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1),
+            Flatten(),
+            AnalogLinear(in_features=16 * 144, out_features=384,
+                         rpu_config=rpu_config, weight_scaling_omega=0.8),
+            ReLU(),
+            Linear(in_features=384, out_features=10),
+            LogSoftmax(dim=1)
+        )
diff --git a/tests/helpers/testcases.py b/tests/helpers/testcases.py
index 0be01b3f..796c3cc1 100644
--- a/tests/helpers/testcases.py
+++ b/tests/helpers/testcases.py
@@ -12,13 +12,15 @@
 
 """TestCases for aihwkit tests."""
 
+import os
 from typing import Type
 from unittest import SkipTest, TestCase
 
 from numpy.testing import assert_array_almost_equal, assert_raises
-
 from aihwkit.simulator.rpu_base import cuda
 
+SKIP_CUDA_TESTS = os.getenv('SKIP_CUDA_TESTS') or not cuda.is_compiled()
+
 
 class AihwkitTestCase(TestCase):
     """Test case that contains common asserts and functions for aihwkit."""
@@ -67,7 +69,7 @@ class ParametrizedTestCase(AihwkitTestCase):
     """Friendly name of the parameters used in this test."""
 
     def setUp(self) -> None:
-        if self.use_cuda and not cuda.is_compiled():
+        if self.use_cuda and SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
 
         super().setUp()
diff --git a/tests/test_bindings_tiles.py b/tests/test_bindings_tiles.py
index 72944f45..320d4759 100644
--- a/tests/test_bindings_tiles.py
+++ b/tests/test_bindings_tiles.py
@@ -21,18 +21,18 @@
 from torch import Tensor, from_numpy
 from torch.cuda import init
 
-from aihwkit.simulator.rpu_base import tiles, cuda
+from aihwkit.simulator.rpu_base import tiles
 
 from aihwkit.simulator.configs import FloatingPointRPUConfig, SingleRPUConfig
 from aihwkit.simulator.configs.devices import FloatingPointDevice, ConstantStepDevice, IdealDevice
 from aihwkit.simulator.configs.utils import IOParameters, DriftParameter
 
 from .helpers.decorators import parametrize_over_tiles
-from .helpers.testcases import ParametrizedTestCase
+from .helpers.testcases import ParametrizedTestCase, SKIP_CUDA_TESTS
 from .helpers.tiles import (FloatingPoint, ConstantStep,
                             FloatingPointCuda, ConstantStepCuda)
 
-if cuda.is_compiled():
+if not SKIP_CUDA_TESTS:
     init()
 
 
@@ -183,7 +183,7 @@ def test_mimic_rpu_mac(self):
 
     def test_cuda_instantiation(self):
         """Test whether cuda weights are copied correctly."""
-        if not self.use_cuda and not cuda.is_compiled():
+        if not self.use_cuda or SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
 
         python_tile = self.get_tile(10, 12)
diff --git a/tests/test_experiment_runners.py b/tests/test_experiment_runners.py
index 49e535f8..bf912c2a 100644
--- a/tests/test_experiment_runners.py
+++ b/tests/test_experiment_runners.py
@@ -19,7 +19,6 @@
 
 from torch import device as torch_device
 from aihwkit.experiments.runners.local import LocalRunner
-from aihwkit.simulator.rpu_base import cuda
 
 from .helpers.decorators import parametrize_over_experiments
 from .helpers.experiments import (
@@ -27,7 +26,7 @@
     LeNet5FashionMNIST,
     Vgg8SVHN, Vgg8SVHNTikiTaka
 )
-from .helpers.testcases import AihwkitTestCase
+from .helpers.testcases import AihwkitTestCase, SKIP_CUDA_TESTS
 
 
 @parametrize_over_experiments([
@@ -46,10 +45,9 @@ def test_run_example_cpu(self):
         """Test running the example using a local runner."""
         training_experiment = self.get_experiment()
         local_runner = LocalRunner(device=torch_device('cpu'))
-
         with patch('sys.stdout', new=StringIO()) as captured_stdout:
-            result = local_runner.run(training_experiment, max_elements_train=10)
-
+            result = local_runner.run(training_experiment, max_elements_train=10,
+                                      stdout=True)
         # Asserts over stdout.
         self.assertIn('Epoch: ', captured_stdout.getvalue())
 
@@ -59,14 +57,15 @@ def test_run_example_cpu(self):
         self.assertIn('train_loss', result[0])
         self.assertIn('accuracy', result[0])
 
-    @skipIf(not cuda.is_compiled(), 'not compiled with CUDA support')
+    @skipIf(SKIP_CUDA_TESTS, 'not compiled with CUDA support')
     def test_run_example_gpu(self):
         """Test running the example using a local runner."""
         training_experiment = self.get_experiment()
         local_runner = LocalRunner(device=torch_device('cuda'))
 
         with patch('sys.stdout', new=StringIO()) as captured_stdout:
-            result = local_runner.run(training_experiment, max_elements_train=10)
+            result = local_runner.run(training_experiment, max_elements_train=10,
+                                      stdout=True)
 
         # Asserts over stdout.
         self.assertIn('Epoch: ', captured_stdout.getvalue())
diff --git a/tests/test_layers.py b/tests/test_layers.py
index bfd60be3..d37e0603 100644
--- a/tests/test_layers.py
+++ b/tests/test_layers.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# (C) Copyright 2020, 2021 IBM. All Rights Reserved.
+# (C) Copyright 2020, 2021, 2022 IBM. All Rights Reserved.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -21,7 +21,7 @@
 
 from aihwkit.nn import AnalogSequential
 from aihwkit.simulator.configs import SingleRPUConfig
-from aihwkit.simulator.rpu_base import cuda, tiles
+from aihwkit.simulator.rpu_base import tiles
 from aihwkit.simulator.tiles import AnalogTile
 
 from .helpers.decorators import parametrize_over_layers
@@ -31,6 +31,7 @@
 )
 from .helpers.testcases import ParametrizedTestCase
 from .helpers.tiles import ConstantStep, Inference
+from .helpers.testcases import SKIP_CUDA_TESTS
 
 
 @parametrize_over_layers(
@@ -133,7 +134,7 @@ class AnalogLayerMoveTest(ParametrizedTestCase):
 
     def test_sequential_move_to_cuda(self):
         """Test moving AnalogSequential to cuda (from CPU)."""
-        if not cuda.is_compiled():
+        if SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
 
         # Map the original tile classes to the expected ones after `cuda()`.
@@ -165,7 +166,7 @@ def test_sequential_move_to_cuda(self):
 
     def test_sequential_move_to_cuda_via_to(self):
         """Test moving AnalogSequential to cuda (from CPU), using ``.to()``."""
-        if not cuda.is_compiled():
+        if SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
 
         # Map the original tile classes to the expected ones after `cuda()`.
@@ -197,7 +198,7 @@ def test_sequential_move_to_cuda_via_to(self):
 
     def test_sequential_move_to_cuda_via_to_multiple_gpus(self):
         """Test moving AnalogSequential to cuda (from CPU), using ``.to()``."""
-        if not cuda.is_compiled():
+        if SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
         if device_count() < 2:
             raise SkipTest('Need at least two devices for this test')
@@ -238,7 +239,7 @@ def test_sequential_move_to_cuda_via_to_multiple_gpus(self):
 
     def test_sequential_move_to_cuda_multiple_gpus(self):
         """Test moving AnalogSequential to cuda (from CPU), using ``.to()``."""
-        if not cuda.is_compiled():
+        if SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
         if device_count() < 2:
             raise SkipTest('Need at least two devices for this test')
@@ -279,7 +280,7 @@ def test_sequential_move_to_cuda_multiple_gpus(self):
 
     def test_save_with_cuda(self):
         """Whether model is correctly reconstructed after saving"""
-        if not cuda.is_compiled():
+        if SKIP_CUDA_TESTS:
             raise SkipTest('not compiled with CUDA support')
 
         # Map the original tile classes to the expected ones after `cuda()`.
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 43e9bc8f..10154b43 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -34,7 +34,6 @@
 from aihwkit.simulator.configs import SingleRPUConfig, InferenceRPUConfig, FloatingPointRPUConfig
 from aihwkit.simulator.configs.devices import ConstantStepDevice, LinearStepDevice
 from aihwkit.simulator.configs.utils import IOParameters, UpdateParameters, MappingParameter
-from aihwkit.simulator.rpu_base import cuda
 from aihwkit.exceptions import TileError, ModuleError
 from aihwkit.nn.conversion import convert_to_analog
 
@@ -43,7 +42,7 @@
     Conv2d, Conv2dCuda, Linear, LinearCuda, LinearMapped,
     LinearMappedCuda, Conv2dMapped, Conv2dMappedCuda
 )
-from .helpers.testcases import ParametrizedTestCase
+from .helpers.testcases import ParametrizedTestCase, SKIP_CUDA_TESTS
 from .helpers.tiles import FloatingPoint, ConstantStep, Inference
 
 
@@ -321,7 +320,7 @@ def test_save_load_model(self):
     def test_save_load_model_cross_device(self):
         """Test saving and loading a model directly."""
 
-        if not cuda.is_compiled():
+        if SKIP_CUDA_TESTS:
             raise SkipTest('CUDA not available.')
 
         model = self.get_layer()