chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from .utils import quantize_model_ # NOQA
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from .qact import ActivationQuantizer # NOQA
|
||||
from .qconv import IntConv2d # NOQA
|
||||
from .qemb import IntEmbedding # NOQA
|
||||
from .qlinear import IntLinear # NOQA
|
||||
@@ -0,0 +1,88 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class ActivationQuantizer:
|
||||
"""
|
||||
Fake scalar quantization of the activations using a forward hook.
|
||||
|
||||
Args:
|
||||
- module. a nn.Module for which we quantize the *post-activations*
|
||||
- p: proportion of activations to quantize, set by default to 1
|
||||
- update_step: to recompute quantization parameters
|
||||
- bits: number of bits for quantization
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- clamp_threshold: to prevent gradients overflow
|
||||
|
||||
Remarks:
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- For the list of quantization methods and number of bits, see ops.py
|
||||
- To remove the hook from the module, simply call self.handle.remove()
|
||||
- At test time, the activations are fully quantized
|
||||
- We use the straight-through estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick
|
||||
- The activations are hard-clamped in [-clamp_threshold, clamp_threshold]
|
||||
to prevent overflow during the backward pass
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module,
|
||||
p=1,
|
||||
update_step=1000,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
clamp_threshold=5,
|
||||
):
|
||||
self.module = module
|
||||
self.p = p
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.clamp_threshold = clamp_threshold
|
||||
self.handle = None
|
||||
self.register_hook()
|
||||
|
||||
def register_hook(self):
|
||||
# forward hook
|
||||
def quantize_hook(module, x, y):
|
||||
|
||||
# update parameters every 1000 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.module.training else 1
|
||||
|
||||
# quantize activations
|
||||
y_q, self.scale, self.zero_point = emulate_int(
|
||||
y.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(y)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (y_q - y).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
return torch.clamp(y, clamp_low.item(), clamp_high.item()) + noise.detach()
|
||||
|
||||
# register hook
|
||||
self.handle = self.module.register_forward_hook(quantize_hook)
|
||||
@@ -0,0 +1,149 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.nn.modules.conv import _ConvNd
|
||||
from torch.nn.modules.utils import _pair
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class IntConv2d(_ConvNd):
|
||||
"""
|
||||
Quantized counterpart of the nn.Conv2d module that applies QuantNoise during training.
|
||||
|
||||
Args:
|
||||
- standard nn.Conv2d parameters
|
||||
- p: amount of noise to inject (0 = no quantization, 1 = quantize all the weights)
|
||||
- bits: number of bits
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- update_step: recompute scale and zero_point every update_steps iterations
|
||||
|
||||
Remarks:
|
||||
- We use the straight-thgourh estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- At test time, the weights are fully quantized
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=0,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
bias=True,
|
||||
padding_mode="zeros",
|
||||
p=0,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
update_step=1000,
|
||||
):
|
||||
kernel_size = _pair(kernel_size)
|
||||
stride = _pair(stride)
|
||||
padding = _pair(padding)
|
||||
dilation = _pair(dilation)
|
||||
super(IntConv2d, self).__init__(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
False,
|
||||
_pair(0),
|
||||
groups,
|
||||
bias,
|
||||
padding_mode,
|
||||
)
|
||||
|
||||
# quantization parameters
|
||||
self.p = p
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
|
||||
def _conv_forward(self, input, weight):
|
||||
if self.padding_mode != "zeros":
|
||||
return F.conv2d(
|
||||
F.pad(input, self._padding_repeated_twice, mode=self.padding_mode),
|
||||
weight,
|
||||
self.bias,
|
||||
self.stride,
|
||||
_pair(0),
|
||||
self.dilation,
|
||||
self.groups,
|
||||
)
|
||||
return F.conv2d(
|
||||
input,
|
||||
weight,
|
||||
self.bias,
|
||||
self.stride,
|
||||
self.padding,
|
||||
self.dilation,
|
||||
self.groups,
|
||||
)
|
||||
|
||||
def forward(self, input):
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.training else 1
|
||||
|
||||
# update parameters every 100 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# quantize weight
|
||||
weight_quantized, self.scale, self.zero_point = emulate_int(
|
||||
self.weight.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(self.weight)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (weight_quantized - self.weight).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
weight = (
|
||||
torch.clamp(self.weight, clamp_low.item(), clamp_high.item())
|
||||
+ noise.detach()
|
||||
)
|
||||
|
||||
# return output
|
||||
output = self._conv_forward(input, weight)
|
||||
return output
|
||||
|
||||
def extra_repr(self):
|
||||
return (
|
||||
"in_channels={}, out_channels={}, kernel_size={}, stride={}, "
|
||||
"padding={}, dilation={}, groups={}, bias={}, quant_noise={}, "
|
||||
"bits={}, method={}".format(
|
||||
self.in_channels,
|
||||
self.out_channels,
|
||||
self.kernel_size,
|
||||
self.stride,
|
||||
self.padding,
|
||||
self.dilation,
|
||||
self.groups,
|
||||
self.bias is not None,
|
||||
self.p,
|
||||
self.bits,
|
||||
self.method,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,147 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class IntEmbedding(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of the nn.Embedding module that applies QuantNoise during training.
|
||||
|
||||
Args:
|
||||
- num_embeddings: number of tokens
|
||||
- embedding_dim: embedding dimension
|
||||
- p: amount of noise to inject (0 = no quantization, 1 = quantize all the weights)
|
||||
- bits: number of bits
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- update_step: recompute scale and zero_point every update_steps iterations
|
||||
|
||||
Remarks:
|
||||
- We use the straight-through estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- At test time, the weights are fully quantized
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_embeddings,
|
||||
embedding_dim,
|
||||
padding_idx=None,
|
||||
max_norm=None,
|
||||
norm_type=2.0,
|
||||
scale_grad_by_freq=False,
|
||||
sparse=False,
|
||||
_weight=None,
|
||||
p=0,
|
||||
update_step=1000,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
):
|
||||
super(IntEmbedding, self).__init__()
|
||||
self.num_embeddings = num_embeddings
|
||||
self.embedding_dim = embedding_dim
|
||||
if padding_idx is not None:
|
||||
if padding_idx > 0:
|
||||
assert (
|
||||
padding_idx < self.num_embeddings
|
||||
), "Padding_idx must be within num_embeddings"
|
||||
elif padding_idx < 0:
|
||||
assert (
|
||||
padding_idx >= -self.num_embeddings
|
||||
), "Padding_idx must be within num_embeddings"
|
||||
padding_idx = self.num_embeddings + padding_idx
|
||||
self.padding_idx = padding_idx
|
||||
self.max_norm = max_norm
|
||||
self.norm_type = norm_type
|
||||
self.scale_grad_by_freq = scale_grad_by_freq
|
||||
if _weight is None:
|
||||
self.weight = nn.Parameter(torch.Tensor(num_embeddings, embedding_dim))
|
||||
self.reset_parameters()
|
||||
else:
|
||||
assert list(_weight.shape) == [
|
||||
num_embeddings,
|
||||
embedding_dim,
|
||||
], "Shape of weight does not match num_embeddings and embedding_dim"
|
||||
self.weight = nn.Parameter(_weight)
|
||||
self.sparse = sparse
|
||||
|
||||
# quantization parameters
|
||||
self.p = p
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.normal_(self.weight)
|
||||
if self.padding_idx is not None:
|
||||
with torch.no_grad():
|
||||
self.weight[self.padding_idx].fill_(0)
|
||||
|
||||
def forward(self, input):
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.training else 1
|
||||
|
||||
# update parameters every 1000 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# quantize weight
|
||||
weight_quantized, self.scale, self.zero_point = emulate_int(
|
||||
self.weight.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(self.weight)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (weight_quantized - self.weight).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
weight = (
|
||||
torch.clamp(self.weight, clamp_low.item(), clamp_high.item())
|
||||
+ noise.detach()
|
||||
)
|
||||
|
||||
# return output
|
||||
output = F.embedding(
|
||||
input,
|
||||
weight,
|
||||
self.padding_idx,
|
||||
self.max_norm,
|
||||
self.norm_type,
|
||||
self.scale_grad_by_freq,
|
||||
self.sparse,
|
||||
)
|
||||
return output
|
||||
|
||||
def extra_repr(self):
|
||||
s = "{num_embeddings}, {embedding_dim}"
|
||||
if self.padding_idx is not None:
|
||||
s += ", padding_idx={padding_idx}"
|
||||
if self.max_norm is not None:
|
||||
s += ", max_norm={max_norm}"
|
||||
if self.norm_type != 2:
|
||||
s += ", norm_type={norm_type}"
|
||||
if self.scale_grad_by_freq is not False:
|
||||
s += ", scale_grad_by_freq={scale_grad_by_freq}"
|
||||
if self.sparse is not False:
|
||||
s += ", sparse=True"
|
||||
s += "quant_noise={p}, bits={bits}, method={method}"
|
||||
return s.format(**self.__dict__)
|
||||
@@ -0,0 +1,113 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class IntLinear(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of the nn.Linear module that applies QuantNoise during training.
|
||||
|
||||
Args:
|
||||
- in_features: input features
|
||||
- out_features: output features
|
||||
- bias: bias or not
|
||||
- p: amount of noise to inject (0 = no quantization, 1 = quantize all the weights)
|
||||
- bits: number of bits
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- update_step: recompute scale and zero_point every update_steps iterations
|
||||
|
||||
Remarks:
|
||||
- We use the straight-through estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick.
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- At test time, the weights are fully quantized
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_features,
|
||||
out_features,
|
||||
bias=True,
|
||||
p=0,
|
||||
update_step=3000,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
):
|
||||
super(IntLinear, self).__init__()
|
||||
self.in_features = int(in_features)
|
||||
self.out_features = int(out_features)
|
||||
self.weight = torch.nn.Parameter(torch.Tensor(out_features, in_features))
|
||||
self.chosen_bias = bias
|
||||
if self.chosen_bias:
|
||||
self.bias = torch.nn.Parameter(torch.Tensor(out_features))
|
||||
else:
|
||||
self.register_parameter("bias", None)
|
||||
self.reset_parameters()
|
||||
|
||||
# quantization parameters
|
||||
self.p = p
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_uniform_(self.weight)
|
||||
if self.chosen_bias:
|
||||
nn.init.constant_(self.bias, 0.0)
|
||||
return
|
||||
|
||||
def forward(self, input):
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.training else 1
|
||||
|
||||
# update parameters every 100 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# quantize weight
|
||||
weight_quantized, self.scale, self.zero_point = emulate_int(
|
||||
self.weight.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(self.weight)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (weight_quantized - self.weight).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
weight = (
|
||||
torch.clamp(self.weight, clamp_low.item(), clamp_high.item())
|
||||
+ noise.detach()
|
||||
)
|
||||
|
||||
# return output
|
||||
output = F.linear(input, weight, self.bias)
|
||||
return output
|
||||
|
||||
def extra_repr(self):
|
||||
return "in_features={}, out_features={}, bias={}, quant_noise={}, bits={}, method={}".format(
|
||||
self.in_features,
|
||||
self.out_features,
|
||||
self.bias is not None,
|
||||
self.p,
|
||||
self.bits,
|
||||
self.method,
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def emulate_int(w, bits, method, scale=None, zero_point=None):
|
||||
q = globals()[f"emulate_int{bits}_{method}"]
|
||||
return q(w, scale=scale, zero_point=zero_point)
|
||||
|
||||
|
||||
def quantize(w, scale, zero_point):
|
||||
return (
|
||||
torch.clamp(torch.round(w / scale + zero_point), 0, 255) - zero_point
|
||||
) * scale
|
||||
|
||||
|
||||
def emulate_int8_histogram(w, scale=None, zero_point=None):
|
||||
if scale is None:
|
||||
obs = torch.quantization.observer.HistogramObserver()
|
||||
_ = obs(w.float())
|
||||
scale, zero_point = obs.calculate_qparams()
|
||||
scale = scale.cuda().type_as(w)
|
||||
zero_point = zero_point.cuda().type_as(w)
|
||||
return quantize(w, scale, zero_point), scale, zero_point
|
||||
|
||||
|
||||
def emulate_int8_channel(w, scale=None, zero_point=None):
|
||||
if scale is None:
|
||||
obs = torch.quantization.observer.PerChannelMinMaxObserver(
|
||||
ch_axis=-1, qscheme=torch.per_channel_symmetric
|
||||
)
|
||||
_ = obs(w)
|
||||
scale, zero_point, ch_axis = obs.get_qparams()
|
||||
scale = scale.cuda().type_as(w)
|
||||
zero_point = zero_point.cuda().type_as(w)
|
||||
return quantize(w, scale, zero_point), scale, zero_point
|
||||
|
||||
|
||||
def emulate_int8_tensor(w, scale=None, zero_point=None):
|
||||
if scale is None:
|
||||
obs = torch.quantization.observer.MinMaxObserver()
|
||||
_ = obs(w)
|
||||
scale, zero_point = obs.calculate_qparams()
|
||||
scale = scale.cuda().type_as(w)
|
||||
zero_point = zero_point.cuda().type_as(w)
|
||||
return quantize(w, scale, zero_point), scale, zero_point
|
||||
@@ -0,0 +1,77 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
|
||||
import torch.distributed as dist
|
||||
import torch.nn as nn
|
||||
|
||||
from ..pq.utils import attrsetter, get_layers
|
||||
from .modules import ActivationQuantizer, IntConv2d, IntEmbedding, IntLinear
|
||||
|
||||
|
||||
MAPPING = {nn.Linear: IntLinear, nn.Embedding: IntEmbedding, nn.Conv2d: IntConv2d}
|
||||
|
||||
|
||||
def quantize_model_(model, p=0.2, bits=8, update_step=3000):
|
||||
"""
|
||||
Replaces all modules with their scalar quantized counterpart and
|
||||
registers hooks to quantize the post-ativations of those modules.
|
||||
|
||||
Args:
|
||||
- model: a nn.Module
|
||||
- p: amount of noise (0 for no noise, 1 to quantize all the weights/activations)
|
||||
- bits: number of bits
|
||||
- update_step: update quantization parameters every update_step steps
|
||||
"""
|
||||
|
||||
# quantize all layers
|
||||
quantized_layers = get_layers(model, "(.*?)")
|
||||
|
||||
for layer in quantized_layers:
|
||||
|
||||
# book-keeping
|
||||
is_master_process = (not dist.is_initialized()) or (
|
||||
dist.is_initialized() and dist.get_rank() == 0
|
||||
)
|
||||
|
||||
# recover module
|
||||
module = attrgetter(layer)(model)
|
||||
if is_master_process:
|
||||
logging.info(
|
||||
f"Quantizing layer {layer} with bits={bits} and QuantNoise={p}"
|
||||
)
|
||||
|
||||
# quantization params
|
||||
q_params = {
|
||||
"p": p,
|
||||
"update_step": update_step,
|
||||
"bits": bits,
|
||||
"method": "histogram",
|
||||
"counter": 0,
|
||||
}
|
||||
|
||||
# instantiate the quantized counterpart
|
||||
if isinstance(module, tuple(MAPPING.keys())):
|
||||
QuantizedModule = MAPPING[module.__class__]
|
||||
quantized_module = QuantizedModule.__new__(QuantizedModule)
|
||||
params = module.__dict__
|
||||
params.update(q_params)
|
||||
quantized_module.__dict__.update(params)
|
||||
|
||||
else:
|
||||
if is_master_process:
|
||||
logging.info(f"Module {module} not yet supported for quantization")
|
||||
continue
|
||||
|
||||
# activation quantization
|
||||
a_q = ActivationQuantizer(quantized_module, p=0, bits=bits, method="histogram")
|
||||
|
||||
# replace layer by its quantized counterpart
|
||||
attrsetter(layer)(model, quantized_module)
|
||||
|
||||
# return name of quantized layers
|
||||
return quantized_layers
|
||||
Reference in New Issue
Block a user