# -*- 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 the phenomenological noise models for inference."""
from typing import List, Tuple
from torch import Tensor
from torch.autograd import no_grad
from aihwkit.inference.converter.base import BaseConductanceConverter
from aihwkit.inference.converter.conductance import SinglePairConductanceConverter
[docs]class BaseNoiseModel:
"""Base class for phenomenological noise models for inference."""
def __init__(
self,
g_converter: BaseConductanceConverter = None
):
self.g_converter = g_converter or SinglePairConductanceConverter()
[docs] @no_grad()
def apply_noise(self, weights: Tensor, t_inference: float) -> Tensor:
"""Apply the expected noise.
Applies the noise to a non-perturbed conductance matrix ``weights``
at time of inference ``t_inference`` (in seconds) where 0 sec
refers to the time when weight programming has finished.
Note:
The drift coefficients and intermediate noises etc. are
sampled for each application of this function anew from the
distributions, thus it samples the expected noise and drift
behavior at time ``t_inference`` but not a continual
trajectory of a given device instance over time (having
e.g. constant drift coefficients).
"""
target_conductances, params = self.g_converter.convert_to_conductances(weights)
noisy_conductances = []
for g_target in target_conductances:
g_prog = self.apply_programming_noise_to_conductance(g_target)
if t_inference > 0:
nu_drift = self.generate_drift_coefficients(g_target)
noisy_conductances.append(self.apply_drift_noise_to_conductance(
g_prog, nu_drift, t_inference))
noisy_weights = self.g_converter.convert_back_to_weights(noisy_conductances, params)
return noisy_weights
[docs] @no_grad()
def apply_programming_noise(self, weights: Tensor) -> Tuple[Tensor, List[Tensor]]:
"""Apply the expected programming noise to weights.
Uses the :meth:`~apply_programming_noise_to_conductances` on
each of the conductance slices.
Args:
weights: weights tensor
Returns:
weight tensor with programming noise applied, and tuple of
all drift coefficients (per conductances slice) that are
determined during programming.
"""
target_conductances, params = self.g_converter.convert_to_conductances(weights)
noisy_conductances = []
nu_drift_list = []
for g_target in target_conductances:
noisy_conductances.append(self.apply_programming_noise_to_conductance(g_target))
nu_drift_list.append(self.generate_drift_coefficients(g_target))
noisy_weights = self.g_converter.convert_back_to_weights(noisy_conductances, params)
return noisy_weights, nu_drift_list
[docs] @no_grad()
def apply_drift_noise(
self,
weights: Tensor,
nu_drift_list: List[Tensor],
t_inference: float
) -> Tensor:
"""Apply the expected drift noise to weights.
Uses the :meth:`~apply_drift_noise_to_conductances` on
each of the conductance slices.
Args:
weights: weights tensor (usually with programming noise already applied)
nu_drift_list: list of drift nu for each conductance slice
t_inference: assumed time of inference (in sec)
Returns:
weight tensor with drift noise applied
"""
target_conductances, params = self.g_converter.convert_to_conductances(weights)
noisy_conductances = []
for g_target, nu_drift in zip(target_conductances, nu_drift_list):
noisy_conductances.append(
self.apply_drift_noise_to_conductance(g_target, nu_drift, t_inference))
noisy_weights = self.g_converter.convert_back_to_weights(noisy_conductances, params)
return noisy_weights
[docs] @no_grad()
def generate_drift_coefficients(self, g_target: Tensor) -> Tensor:
"""Generate drift coefficients ``nu`` based on the target conductances."""
raise NotImplementedError
[docs] @no_grad()
def apply_programming_noise_to_conductance(self, g_target: Tensor) -> Tensor:
r"""Apply programming noise to a target conductance ``Tensor``.
Args:
g_target: Target conductances
Returns:
Tensor of sampled drift coefficients :math:`\nu`, one for each
target conductance value.
"""
raise NotImplementedError
[docs] @no_grad()
def apply_drift_noise_to_conductance(
self,
g_prog: Tensor,
nu_drift: Tensor,
t_inference: float
) -> Tensor:
r"""Apply the noise and drift up to the assumed inference time point.
Args:
g_prog: Tensor of conductance values after programming (in :math:`\muS`)
nu_drift: drift nu
t_inference: assumed time of inference (in sec)
Returns:
conductance Tensor with applied noise and drift
"""
raise NotImplementedError