From cba7e1c4f6b82f552ca3eec300e4329145c3175a Mon Sep 17 00:00:00 2001 From: Anjok07 <68268275+Anjok07@users.noreply.github.com> Date: Tue, 26 Jul 2022 04:56:39 -0500 Subject: [PATCH] Delete diffq directory --- diffq/__init__.py | 29 --- diffq/base.py | 343 ------------------------------- diffq/bitpack.cp39-win_amd64.pyd | Bin 162816 -> 0 bytes diffq/diffq.py | 318 ---------------------------- diffq/lsq.py | 192 ----------------- diffq/torch_pack.py | 80 ------- diffq/ts_export.py | 209 ------------------- diffq/uniform.py | 135 ------------ diffq/utils.py | 58 ------ 9 files changed, 1364 deletions(-) delete mode 100644 diffq/__init__.py delete mode 100644 diffq/base.py delete mode 100644 diffq/bitpack.cp39-win_amd64.pyd delete mode 100644 diffq/diffq.py delete mode 100644 diffq/lsq.py delete mode 100644 diffq/torch_pack.py delete mode 100644 diffq/ts_export.py delete mode 100644 diffq/uniform.py delete mode 100644 diffq/utils.py diff --git a/diffq/__init__.py b/diffq/__init__.py deleted file mode 100644 index b67e784..0000000 --- a/diffq/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -# flake8: noqa -""" -This package implements different quantization strategies: - -- `diffq.uniform.UniformQuantizer`: classic uniform quantization over n bits. -- `diffq.diffq.DiffQuantizer`: differentiable quantizer based on scaled noise injection. -- `diffq.lsq.LSQ`: Learnt Step size Quantizer based on [Esser et al. 2019] https://arxiv.org/abs/1902.08153 -- `diffq.bitpack`: efficient CPU bit-packing for returning quantized states. -- `diffq.torch_pack`: torch based bit-packing compatible with torchscript. -- `diffq.ts_export`: support exporting DiffQ based models to torchscript. - - -Also, do check `diffq.base.BaseQuantizer` for the common methods of all Quantizers. -""" - -from .uniform import UniformQuantizer -from .diffq import DiffQuantizer -from .lsq import LSQ -from .base import restore_quantized_state -from . import ts_export - - -__version__ = "0.2.2" diff --git a/diffq/base.py b/diffq/base.py deleted file mode 100644 index acbded3..0000000 --- a/diffq/base.py +++ /dev/null @@ -1,343 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Base class for all quantizers.""" -from contextlib import contextmanager -from dataclasses import dataclass -from concurrent import futures -from fnmatch import fnmatch -from functools import partial -import io -import math -from multiprocessing import cpu_count -import pickle -import typing as tp -import zlib - -import torch - -from . import bitpack -from . import torch_pack as torch_pack_mod - - -class BaseQuantizer: - @dataclass - class _QuantizedParam: - name: str - param: torch.nn.Parameter - module: torch.nn.Module - # If a Parameter is used multiple times, `other` can be used - # to share state between the different Quantizers - other: tp.Optional[tp.Any] - - def __init__(self, model: torch.nn.Module, min_size: float = 0.01, float16: bool = False, - exclude: tp.Optional[tp.List[str]] = [], detect_bound: bool = True): - self.model = model - self.min_size = min_size - self.float16 = float16 - self.exclude = exclude - self.detect_bound = detect_bound - self._quantized = False - self._need_unquantize = None - self._pre_handle = self.model.register_forward_pre_hook(self._forward_pre_hook) - self._post_handle = self.model.register_forward_hook(self._forward_hook) - - self._qparams = [] - self._float16 = [] - self._others = [] - self._rnns = [] - - self._saved = [] - - self._find_params() - - def _find_params(self): - min_params = self.min_size * 2**20 // 4 - previous = {} - for module_name, module in self.model.named_modules(): - if isinstance(module, torch.nn.RNNBase): - self._rnns.append(module) - for name, param in list(module.named_parameters(recurse=False)): - full_name = f"{module_name}.{name}" - matched = False - for pattern in self.exclude: - if fnmatch(full_name, pattern) or fnmatch(name, pattern): - matched = True - break - - if param.numel() <= min_params or matched: - if id(param) in previous: - continue - if self.detect_bound: - previous[id(param)] = None - if self.float16: - self._float16.append(param) - else: - self._others.append(param) - else: - qparam = self._register_param(name, param, module, previous.get(id(param))) - if self.detect_bound: - previous[id(param)] = qparam - self._qparams.append(qparam) - - def _register_param(self, name, param, module, other): - return self.__class__._QuantizedParam(name, param, module, other) - - def _forward_pre_hook(self, module, input): - if self.model.training: - self._quantized_state = None - self.unquantize() - if self._pre_forward_train(): - self._fix_rnns() - else: - assert self._need_unquantize is None - self._need_unquantize = self.quantize() - - def _forward_hook(self, module, input, output): - if self.model.training: - if self._post_forward_train(): - self._fix_rnns(flatten=False) # Hacky, next forward will flatten - else: - if self._need_unquantize: - self._need_unquantize = None - self.unquantize() - - def quantize(self): - """ - Immediately apply quantization to the model parameters. - Model parameters are saved to later allow restoring the unquantized state. - - Note that you shouldn't need to call this for model evaluation, as long as - you properly call `model.train()` and `model.eval()`, but this can be - useful for weight inspection. - """ - if self._quantized: - return False - self._saved = [qp.param.data.to('cpu', copy=True) - for qp in self._qparams if qp.other is None] - self.restore_quantized_state(self.get_quantized_state(packed=False)) - self._quantized = True - self._fix_rnns() - return True - - @contextmanager - def enter_quantize(self): - """Context manager for entering quantized state.""" - self.quantize() - try: - yield - finally: - self.unquantize() - - def unquantize(self): - """ - Revert a previous call to `quantize()`. - """ - if not self._quantized: - return - if not self._saved: - raise RuntimeError("Nothing to restore. This shouldn't happen") - for qparam in self._qparams: - if qparam.other is None: - qparam.param.data[:] = self._saved.pop(0) - assert len(self._saved) == 0 - self._quantized = False - self._fix_rnns() - - def _pre_forward_train(self) -> bool: - """ - Called once before each forward for continuous quantization. - Should return True if parameters were changed. - """ - return False - - def _post_forward_train(self) -> bool: - """ - Called once after each forward (to restore state for instance). - Should return True if parameters were changed. - """ - return False - - def _fix_rnns(self, flatten=True): - """ - To be called after quantization happened to fix RNNs. - """ - for rnn in self._rnns: - rnn._flat_weights = [ - (lambda wn: getattr(rnn, wn) if hasattr(rnn, wn) else None)(wn) - for wn in rnn._flat_weights_names] - if flatten: - rnn.flatten_parameters() - - def _bit_pack_param(self, qparam: _QuantizedParam, quantized: tp.Any, - pack_fn: tp.Any) -> tp.Any: - """Further bitpack the quantized representation. - This is used to return the quantized state. Should be overriden. - """ - return quantized - - def _bit_unpack_param(self, qparam: _QuantizedParam, packed: tp.Any, - unpack_fn: tp.Any) -> tp.Any: - """Unpack bitpacked representation. Should be overriden - """ - return packed - - def _quantize_param(self, qparam: _QuantizedParam) -> tp.Any: - """ - To be overriden. - """ - raise NotImplementedError() - - def _unquantize_param(self, qparam: _QuantizedParam, quantized: tp.Any) -> torch.Tensor: - """ - To be overriden. - """ - raise NotImplementedError() - - def get_quantized_state(self, packed=True, torch_pack=False): - """ - Return a quantized representation fo the weights. If `packed` is True, - this will also perform bitpacking to ensure optimal store. - If `torck_pack` is true, the bitpacking from `torch_pack` will be used. - It is slower (except maybe on GPU), but is compatible with torchscript. - - You can restore a model from a quantized state either using - `BaseQuantizer.restore_quantized_state` or `diffq.restore_quantized_state` - if you do not have the original quantizer around anymore. - """ - float16_params = [] - for p in self._float16: - q = p.data.half() - float16_params.append(q) - - if torch_pack: - pack_fn = torch_pack_mod.pack - else: - pack_fn = bitpack.pack - - all_quantized = [] - for qparam in self._qparams: - if qparam.other is not None: - continue - quantized = self._quantize_param(qparam) - if packed: - quantized = self._bit_pack_param(qparam, quantized, pack_fn=pack_fn) - all_quantized.append(quantized) - - state = { - "quantized": all_quantized, - "float16": float16_params, - "others": [p.data.clone() for p in self._others], - } - - kwargs = dict(self._init_kwargs) - kwargs.pop("model") - state["meta"] = { - "init_kwargs": kwargs, - "klass": self.__class__, - "packed": packed, - "torch_pack": torch_pack - } - return state - - def restore_quantized_state(self, state) -> None: - """ - Restore the state of the model from the quantized state. - """ - for p, q in zip(self._float16, state["float16"]): - p.data[:] = q.to(p) - - for p, q in zip(self._others, state["others"]): - p.data[:] = q - - meta = state.get("meta", {}) - packed = meta.get("packed", False) - torch_pack = meta.get("torch_pack", False) - - if torch_pack: - unpack_fn = torch_pack_mod.unpack - else: - unpack_fn = bitpack.unpack - - remaining = list(state["quantized"]) - for qparam in self._qparams: - if qparam.other is not None: - # Only unquantize first appearance of nn.Parameter. - continue - quantized = remaining.pop(0) - if packed: - quantized = self._bit_unpack_param(qparam, quantized, unpack_fn) - qparam.param.data[:] = self._unquantize_param(qparam, quantized) - assert not remaining - self._fix_rnns() - - def detach(self) -> None: - """ - Detach from the model, removes hooks and anything else. - """ - self._pre_handle.remove() - self._post_handle.remove() - - def model_size(self) -> torch.Tensor: - """ - Returns an estimate of the quantized model size. - """ - total = torch.tensor(0.) - for p in self._float16: - total += 16 * p.numel() - for p in self._others: - total += 32 * p.numel() - return total / 2**20 / 8 # bits to MegaBytes - - def true_model_size(self) -> float: - """ - Return the true quantized model size, in MB, without extra - compression. - """ - return self.model_size().item() - - def packed_model_size(self) -> float: - """Return the packed model size, when stored with pickle. - This should be mostly equivalent to `true_model_size` up to some - slight overhead for storing metadata. - """ - state = self.get_quantized_state(packed=True) - return len(pickle.dumps(state)) / 2 ** 20 - - def compressed_model_size(self, compress_level=-1, num_workers=8) -> float: - """ - Return the compressed quantized model size, in MB. - - Args: - compress_level (int): compression level used with zlib, - see `zlib.compress` for details. - num_workers (int): will split the final big byte representation in that - many chunks processed in parallels. - """ - out = io.BytesIO() - torch.save(self.get_quantized_state(packed=False), out) - ms = _parallel_compress_len(out.getvalue(), compress_level, num_workers) - return ms / 2 ** 20 - - -def restore_quantized_state(model: torch.nn.Module, state: dict): - assert "meta" in state - quantizer = state["meta"]["klass"](model, **state["meta"]["init_kwargs"]) - quantizer.restore_quantized_state(state) - quantizer.detach() - - -def _compress_len(data, compress_level): - return len(zlib.compress(data, level=compress_level)) - - -def _parallel_compress_len(data, compress_level, num_workers): - num_workers = min(cpu_count(), num_workers) - chunk_size = int(math.ceil(len(data) / num_workers)) - chunks = [data[offset:offset + chunk_size] for offset in range(0, len(data), chunk_size)] - with futures.ThreadPoolExecutor(num_workers) as pool: - # thread pool is okay here, zlib calls an external C lib and GIL is released - # before the call. - return sum(pool.map(partial(_compress_len, compress_level=compress_level), chunks)) diff --git a/diffq/bitpack.cp39-win_amd64.pyd b/diffq/bitpack.cp39-win_amd64.pyd deleted file mode 100644 index a28d6e901e66731aff057a66a2ff4843cab7b418..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162816 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

N1UibuadH#<8Lv6Ip?8^P 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 X=`FF>< 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 O(= 0 - assert min_bits < init_bits < max_bits, \ - "init_bits must be between min_bits and max_bits excluded3" - - for name, _ in model.named_parameters(): - if name.endswith(suffix): - raise RuntimeError("The model already has some noise scales parameters, " - "maybe you used twice a DiffQuantizer on the same model?.") - - super().__init__(model, min_size, float16, exclude, detect_bound) - - def _get_bits(self, logit: torch.Tensor): - if self.param == "noise": - return torch.log2(1 + 1 / self._get_noise_scale(logit)) - else: - t = torch.sigmoid(logit) - return self.max_bits * t + (1 - t) * self.min_bits - - def _get_noise_scale(self, logit: torch.Tensor): - if self.param == "noise": - t = torch.sigmoid(logit) - return torch.exp(t * math.log(self._min_noise) + (1 - t) * math.log(self._max_noise)) - else: - return 1 / (2 ** self._get_bits(logit) - 1) - - def _register_param(self, name, param, module, other): - if other is not None: - return self.__class__._QuantizedParam( - name=name, param=param, module=module, logit=other.logit, other=other) - assert self.group_size == 0 or param.numel() % self.group_size == 0 - # we want the initial number of bits to be init_bits. - if self.param == "noise": - noise_scale = 1 / (2 ** self.init_bits - 1) - t = (math.log(noise_scale) - math.log(self._max_noise)) / ( - math.log(self._min_noise) - math.log(self._max_noise)) - else: - t = (self.init_bits - self.min_bits) / (self.max_bits - self.min_bits) - assert 0 < t < 1 - logit = torch.logit(torch.tensor(float(t))) - assert abs(self._get_bits(logit) - self.init_bits) < 1e-5 - if self.group_size > 0: - nparam = param.numel() // self.group_size - else: - nparam = 1 - logit = torch.nn.Parameter( - torch.full( - (nparam,), - logit, - device=param.device)) - module.register_parameter(name + self.suffix, logit) - return self.__class__._QuantizedParam( - name=name, param=param, module=module, logit=logit, other=None) - - def clear_optimizer(self, optimizer: torch.optim.Optimizer): - params = [qp.logit for qp in self._qparams] - - for group in optimizer.param_groups: - new_params = [] - for q in list(group["params"]): - matched = False - for p in params: - if p is q: - matched = True - if not matched: - new_params.append(q) - group["params"][:] = new_params - - def setup_optimizer(self, optimizer: torch.optim.Optimizer, - lr: float = 1e-3, **kwargs): - """ - Setup the optimizer to tune the number of bits. In particular, this will deactivate - weight decay for the bits parameters. - - Args: - optimizer (torch.Optimizer): optimizer to use. - lr (float): specific learning rate for the bits parameters. 1e-3 - is perfect for Adam.,w - kwargs (dict): overrides for other optimization parameters for the bits. - """ - assert not self._optimizer_setup - self._optimizer_setup = True - - params = [qp.logit for qp in self._qparams] - - for group in optimizer.param_groups: - for q in list(group["params"]): - for p in params: - if p is q: - raise RuntimeError("You should create the optimizer " - "before the quantizer!") - - group = {"params": params, "lr": lr, "weight_decay": 0} - group.update(kwargs) - optimizer.add_param_group(group) - - def no_optimizer(self): - """ - Call this if you do not want to use an optimizer. - """ - self._optimizer_setup = True - - def check_unused(self): - for qparam in self._qparams: - if qparam.other is not None: - continue - grad = qparam.param.grad - if grad is None or (grad == 0).all(): - if qparam.logit.grad is not None: - qparam.logit.grad.data.zero_() - - def model_size(self, exact=False): - """ - Differentiable estimate of the model size. - The size is returned in MB. - - If `exact` is True, then the output is no longer differentiable but - reflect exactly an achievable size, even without compression, - i.e.same as returned by `naive_model_size()`. - """ - total = super().model_size() - subtotal = 0 - for qparam in self._qparams: - # only count the first appearance of a Parameter - if qparam.other is not None: - continue - bits = self.extra_bits + self._get_bits(qparam.logit) - if exact: - bits = bits.round().clamp(1, 15) - if self.group_size == 0: - group_size = qparam.param.numel() - else: - group_size = self.group_size - subtotal += group_size * bits.sum() - subtotal += 2 * 32 # param scale - - # Number of bits to represent each number of bits - bits_bits = math.ceil(math.log2(1 + (bits.max().round().item() - self.min_bits))) - subtotal += 8 # 8 bits for bits_bits - subtotal += bits_bits * bits.numel() - - subtotal /= 2 ** 20 * 8 # bits -> MegaBytes - return total + subtotal - - def true_model_size(self): - """ - Naive model size without zlib compression. - """ - return self.model_size(exact=True).item() - - def _pre_forward_train(self): - if not self._optimizer_setup: - raise RuntimeError("You must call `setup_optimizer()` on your optimizer " - "before starting training.") - for qparam in self._qparams: - if qparam.other is not None: - noisy = qparam.other.module._parameters[qparam.other.name] - else: - bits = self._get_bits(qparam.logit)[:, None] - if self.group_size == 0: - p_flat = qparam.param.view(-1) - else: - p_flat = qparam.param.view(-1, self.group_size) - scale = p_flat.max() - p_flat.min() - unit = 1 / (2**bits - 1) - if self.noise == "uniform": - noise_source = (torch.rand_like(p_flat) - 0.5) - elif self.noise == "gaussian": - noise_source = torch.randn_like(p_flat) / 2 - noise = scale * unit * noise_source - noisy = p_flat + noise - # We bypass the checks by PyTorch on parameters being leafs - qparam.module._parameters[qparam.name] = noisy.view_as(qparam.param) - return True - - def _post_forward_train(self): - for qparam in self._qparams: - qparam.module._parameters[qparam.name] = qparam.param - return True - - def _quantize_param(self, qparam: _QuantizedParam) -> tp.Any: - bits = self.extra_bits + self._get_bits(qparam.logit) - bits = bits.round().clamp(1, 15)[:, None].byte() - if self.group_size == 0: - p = qparam.param.data.view(1, -1) - else: - p = qparam.param.data.view(-1, self.group_size) - levels, scales = uniform_quantize(p, bits) - return levels, scales, bits[:, 0] - - def _unquantize_param(self, qparam: _QuantizedParam, quantized: tp.Any) -> torch.Tensor: - levels, param_scale, bits = quantized - bits = bits[:, None] - return uniform_unquantize(levels, param_scale, bits).view_as(qparam.param.data) - - def _bit_pack_param(self, qparam, quantized, pack_fn): - levels, scales, bits = quantized - all_packed = [] - for bit in range(1, 15): - sub_levels = levels[bits == bit] - if not sub_levels.numel(): - all_packed.append(None) - else: - packed = pack_fn(sub_levels, bit) - all_packed.append(packed) - packed_bits = pack_fn(bits - self.min_bits) - return (all_packed, scales, packed_bits) - - def _bit_unpack_param(self, qparam, packed, unpack_fn): - """Unpack bitpacked representation. Should be overriden. - """ - packed_all_levels, scales, packed_bits = packed - bits = unpack_fn(packed_bits, qparam.logit.numel()) + self.min_bits - bits = bits.to(qparam.param.device) - levels = torch.empty(qparam.logit.numel(), self.group_size, - dtype=torch.short, device=qparam.param.device) - for idx, packed_levels in enumerate(packed_all_levels): - bit = idx + 1 - if packed_levels is None: - continue - sub_levels = levels[bits == bit] - levels[bits == bit] = unpack_fn( - packed_levels, sub_levels.numel()).view_as(sub_levels).to(sub_levels) - return (levels, scales, bits) - - def detach(self): - super().detach() - for qparam in self._qparams: - delattr(qparam.module, qparam.name + self.suffix) - - def __repr__(self): - return simple_repr(self) diff --git a/diffq/lsq.py b/diffq/lsq.py deleted file mode 100644 index 95238b0..0000000 --- a/diffq/lsq.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -""" -Learnt-Stepsize quantizer from [Esser et al. 2019] https://arxiv.org/abs/1902.08153. -""" -from dataclasses import dataclass -import typing as tp - -import torch - -from .base import BaseQuantizer -from .utils import capture_init, simple_repr - - -class LSQ(BaseQuantizer): - """Implements weight only quantization based on [Esser et al. 2019]. - https://arxiv.org/abs/1902.08153 - """ - @dataclass - class _QuantizedParam(BaseQuantizer._QuantizedParam): - scale: torch.nn.Parameter - - @capture_init - def __init__(self, model: torch.nn.Module, bits: int = 8, min_size: float = 0.01, - float16: bool = False, suffix: str = "_lsq", exclude=[], detect_bound=True): - assert 0 < bits <= 15 - self.suffix = suffix - self._optimizer_setup = False - self.bits = bits - - for name, _ in model.named_parameters(): - if name.endswith(suffix): - raise RuntimeError("The model already has some noise scales parameters, " - "maybe you used twice a LSQ on the same model?.") - - super().__init__(model, min_size, float16, exclude, detect_bound) - - def _register_param(self, name, param, module, other): - if other is not None: - return self.__class__._QuantizedParam( - name=name, param=param, module=module, scale=other.scale, other=other) - # we want the initial number of bits to be init_bits. - scale = 2 * param.data.abs().mean() / (2 ** (self.bits - 1))**0.5 - scale = torch.nn.Parameter(scale) - module.register_parameter(name + self.suffix, scale) - return self.__class__._QuantizedParam( - name=name, param=param, module=module, scale=scale, other=None) - - def clear_optimizer(self, optimizer: torch.optim.Optimizer): - params = [qp.scale for qp in self._qparams] - - for group in optimizer.param_groups: - new_params = [] - for q in list(group["params"]): - matched = False - for p in params: - if p is q: - matched = True - if not matched: - new_params.append(q) - group["params"][:] = new_params - - def setup_optimizer(self, optimizer: torch.optim.Optimizer, **kwargs): - """ - Setup the optimizer to tune the scale parameter. - Following [Esser et al. 2019], we use the same LR and weight decay - as the base optimizer, unless specified otherwise. - - Args: - optimizer (torch.Optimizer): optimizer to use. - kwargs (dict): overrides for optimization parameters - """ - assert not self._optimizer_setup - self._optimizer_setup = True - - params = [qp.scale for qp in self._qparams] - - for group in optimizer.param_groups: - for q in list(group["params"]): - for p in params: - if p is q: - raise RuntimeError("You should create the optimizer " - "before the quantizer!") - - group = {"params": params} - group.update(kwargs) - optimizer.add_param_group(group) - - def no_optimizer(self): - """ - Call this if you do not want to use an optimizer. - """ - self._optimizer_setup = True - - def model_size(self, exact=False): - """ - Differentiable estimate of the model size. - The size is returned in MB. - - If `exact` is True, then the output is no longer differentiable but - reflect exactly an achievable size, even without compression, - i.e.same as returned by `naive_model_size()`. - """ - total = super().model_size() - subtotal = 0 - for qparam in self._qparams: - # only count the first appearance of a Parameter - if qparam.other is not None: - continue - bits = qparam.param.numel() * self.bits - subtotal += bits - subtotal += 1 * 32 # param scale - - subtotal /= 2 ** 20 * 8 # bits -> MegaBytes - return total + subtotal - - def true_model_size(self): - """ - Naive model size without zlib compression. - """ - return self.model_size(exact=True).item() - - def _pre_forward_train(self): - if not self._optimizer_setup: - raise RuntimeError("You must call `setup_optimizer()` on your optimizer " - "before starting training.") - for qparam in self._qparams: - scale = qparam.scale - quant, _ = quantize(qparam.param, scale, self.bits) - # We bypass the checks by PyTorch on parameters being leafs - qparam.module._parameters[qparam.name] = quant - return True - - def _post_forward_train(self): - for qparam in self._qparams: - qparam.module._parameters[qparam.name] = qparam.param - return True - - def _quantize_param(self, qparam: _QuantizedParam) -> tp.Any: - _, index = quantize(qparam.param, qparam.scale, self.bits) - assert (index <= (2 ** (self.bits - 1) - 1)).all(), index.max() - assert (index >= (-2 ** (self.bits - 1))).all(), index.min() - return index.detach().short(), qparam.scale.detach() - - def _unquantize_param(self, qparam: _QuantizedParam, quantized: tp.Any) -> torch.Tensor: - index, scale = quantized - return index.float() * scale - - def _bit_pack_param(self, qparam, quantized, pack_fn): - levels, scale = quantized - packed = pack_fn(levels + 2 ** (self.bits - 1)) - return (packed, scale) - - def _bit_unpack_param(self, qparam, packed, unpack_fn): - """Unpack bitpacked representation. Should be overriden - """ - packed_levels, scale = packed - levels = unpack_fn( - packed_levels, qparam.param.numel()).to(qparam.param.device).view_as(qparam.param) - levels -= 2 ** (self.bits - 1) - return (levels, scale) - - def detach(self): - super().detach() - for qparam in self._qparams: - delattr(qparam.module, qparam.name + self.suffix) - - def __repr__(self): - return simple_repr(self) - - -def roundpass(x): - return (x.round() - x).detach() + x - - -def gradscale(x, scale): - return (x - x * scale).detach() + x * scale - - -def quantize(tensor, scale, bits): - low = - 2 ** (bits - 1) - high = 2 ** (bits - 1) - 1 - scale = gradscale(scale, 1 / (tensor.numel() * high)**0.5) - - index = tensor / scale - index = index.clamp(low, high) - index = roundpass(index) - return index * scale, index diff --git a/diffq/torch_pack.py b/diffq/torch_pack.py deleted file mode 100644 index 926af7d..0000000 --- a/diffq/torch_pack.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -"""Bit packing in pure PyTorch. -Slower than bitpack.pyx but compatible with torchscript. -""" -import math -import typing as tp -import torch -from torch.nn import functional as F - - -def as_rectangle(p: torch.Tensor, side: int): - """Reshape as rectangle, using padding when necessary so that out shape is [*, side]""" - p_flat = p.view(-1) - ideal_length = int(math.ceil(len(p_flat) / side) * side) - p_flat_pad = F.pad(p_flat, (0, ideal_length - len(p_flat))) - return p_flat_pad.view(side, -1) - - -def _storage_size(dtype: torch.dtype): - if dtype == torch.int64: - return 64 - elif dtype == torch.int32: - return 32 - elif dtype == torch.int16: - return 16 - elif dtype == torch.uint8: - return 8 - else: - raise ValueError("Invalid bitpacking storage type") - - -def pack(indexes, nbits: int = 0, storage_dtype: torch.dtype = torch.int16): - """You can think of indexes as a "Tensor" of bits of shape [L, nbits]. - Instead of concatenating naively as [L * nbits], we instead look at it transposed as - [nbits, L]. For L = 16 * G, we get [nbits, G, 16] which is trivial to store - efficiently on int16 integers. - There will be overhead if L is far from a multiple of 16 (e.g. 1) but for large - model layers this is acceptable. Storage type can be changed. - - `nbits` should be the number of bits on which the indexes are coded, and will - actually be determined automatically if set to 0. - """ - assert not indexes.dtype.is_floating_point - if indexes.numel() > 0: - assert indexes.max().item() < 2 ** 15 - assert indexes.min().item() >= 0 - if nbits == 0: - nbits = int(math.ceil(math.log2(1 + (indexes.max())))) - else: - assert indexes.max().item() < 2 ** nbits - - indexes = indexes.reshape(-1) - storage_size = _storage_size(storage_dtype) - rect = as_rectangle(indexes, storage_size) - out = torch.zeros(nbits, rect.shape[1], dtype=storage_dtype, device=indexes.device) - for in_bit in range(nbits): - for out_bit in range(storage_size): - d = ((rect[out_bit] >> in_bit) & 1).to(out.dtype) << out_bit - out[in_bit, :] |= d - return out - - -def unpack(packed: torch.Tensor, length: tp.Optional[int] = None): - """Opposite of `pack`. You might need to specify the original length.""" - storage_size = _storage_size(packed.dtype) - nbits, groups = packed.shape - out = torch.zeros(storage_size, groups, dtype=torch.int16, device=packed.device) - for in_bit in range(storage_size): - for out_bit in range(nbits): - bit_value = (packed[out_bit, :] >> in_bit) & 1 - out[in_bit, :] = out[in_bit, :] | (bit_value.to(out) << out_bit) - out = out.view(-1) - if length is not None: - out = out[:length] - return out diff --git a/diffq/ts_export.py b/diffq/ts_export.py deleted file mode 100644 index d67ab2b..0000000 --- a/diffq/ts_export.py +++ /dev/null @@ -1,209 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""TorchScript export support. -We have to do a lot of black magic for TorchScript to be happy -because we cannot dynamically allocate new weights when loading the model. - -Here is how it works: -- we generate code in a temporary python file for the given model that explicitely - override all the weights on the first forward from their packed version. - This is because TorchScript does not let us iterate over parameters in a generic manner. -- we zero out all the original weights. We cannot simply remove those weights - because TorchScript won't let us recreate them. -- A TorchScript file is just a zip file, but stored without compression. - In order to remove the cost of storing the zeroed out weights, we unzip the file, - and zip it again with compression. -""" -import importlib -import os -from pathlib import Path -import random -import sys -import typing as tp -import tempfile -import zipfile - -import torch -from torch import jit - -from .diffq import DiffQuantizer -from .uniform import uniform_unquantize -from .torch_pack import unpack - -_DiffQPacked = tp.Tuple[ - tp.List[tp.Optional[torch.Tensor]], tp.Tuple[float, float], - torch.Tensor, tp.List[int]] - -# This is the template for the generated class. -TEMPLATE = ''' -import typing as tp -import torch -from torch import jit - -from diffq.ts_export import _unpack_param, _DiffQPacked - -from {module} import {klass} - - -class DiffQTSModel(torch.nn.Module): - def __init__(self, model: {klass}, group_size: int, min_bits: int, - packed: tp.List[_DiffQPacked]): - super().__init__() - self.group_size = group_size - self.min_bits = min_bits - self.model = model - self._unpacked = False - self._packed = packed - - @jit.export - def unpack(self): - """ - Unpack the weights, automatically called on the first forward, - or explicitely.""" - if self._unpacked: - return -{unpack_assigns} - self._unpacked = True - - def forward(self, x: torch.Tensor): - self.unpack() - return self.model.forward(x) -''' - -# those are the assignments for each quantized weight. -UNPACK_ASSIGN = (' ' * 8) + ('self.model{full_name}.data[:] = ' - '_unpack_param(self._packed[{index}], ' - 'group_size=self.group_size, min_bits=self.min_bits)') -UNPACK_ASSIGN_SAME = (' ' * 8) + 'self.model{full_name} = self.model{other_name}' - - -def export(quantizer: DiffQuantizer, path: tp.Union[str, Path]): - """Export the given quantized model to the given path. - We must save the quantized model ourselves, as we need to recompress - the zip archive afterwards. - """ - packed: tp.List[_DiffQPacked] = [] - uniq_name = ''.join([random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(12)]) - with tempfile.TemporaryDirectory() as tmpdir: - sys.path.insert(0, tmpdir) - try: - code = _codegen(quantizer) - with open(Path(tmpdir) / f'{uniq_name}.py', 'w') as f: - f.write(code) - module = importlib.import_module(uniq_name) - ts_klass = module.DiffQTSModel - state = quantizer.get_quantized_state(packed=True, torch_pack=True) - quantized = state["quantized"] - for qparam in quantizer._qparams: - if qparam.other is None: - levels, scales, bits = quantized.pop(0) - size = qparam.param.size() - packed.append((levels, scales, bits, list(size))) - qparam.param.data.zero_() - quantizer.detach() - ts_premodel = ts_klass(quantizer.model, quantizer.group_size, - quantizer.min_bits, packed) - ts_model = jit.script(ts_premodel) - if path is not None: - jit.save(ts_model, path) - recompress(path) - finally: - sys.path.pop(0) - - return ts_model - - -def _unpack_param(packed: _DiffQPacked, group_size: int, min_bits: int) -> torch.Tensor: - """Function called from TorchScript on the first forward to decode the - packed weights to FP32. - """ - packed_all_levels, scales, packed_bits, shape = packed - numel = 1 - for dim in shape: - numel *= dim - bits = unpack(packed_bits, numel // group_size) + min_bits - levels = torch.empty(bits.numel(), group_size, dtype=torch.short) - for idx, packed_levels in enumerate(packed_all_levels): - bit = idx + 1 - if packed_levels is not None: - sub_levels = levels[bits == bit] - levels[bits == bit] = unpack(packed_levels, sub_levels.numel()).view_as(sub_levels) - bits = bits[:, None] - unquant = uniform_unquantize(levels, scales, bits) - if len(shape) == 4: - return unquant.view(shape[0], shape[1], shape[2], shape[3]) - elif len(shape) == 3: - return unquant.view(shape[0], shape[1], shape[2]) - elif len(shape) == 2: - return unquant.view(shape[0], shape[1]) - elif len(shape) == 1: - return unquant.view(shape[0]) - else: - raise RuntimeError("Invalid numbr of dim") - - -def recompress(path: tp.Union[str, Path]): - """After having saved the torchscript file, this will recompress it - to make sure all the zeroed out parameters don't actually take any space. - """ - with tempfile.TemporaryDirectory() as tmpdir: - with zipfile.ZipFile(path) as zipin: - zipin.extractall(tmpdir) - with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED, - compresslevel=1) as zipout: - for root, folders, files in os.walk(tmpdir): - for file in files: - fp = Path(root) / file - name = fp.relative_to(tmpdir) - zipout.write(fp, name) - - -def _get_full_name_access(full_name): - # When generating code, we need to handle attributes vs. indexing. - parts = [] - for part in full_name.split("."): - try: - index = int(part) - except ValueError: - parts.append("." + part) - else: - parts.append(f"[{index}]") - return "".join(parts) - - -def _codegen(quantizer: DiffQuantizer): - # Generates the code for the given quantizer - module = quantizer.model.__class__.__module__ - klass = quantizer.model.__class__.__name__ - model = quantizer.model - - assert not quantizer.float16 - names = {} - for mod_name, mod in model.named_modules(): - names[mod] = mod_name - unpack_assigns = [] - - index = 0 - for qparam in quantizer._qparams: - mod_name = names[qparam.module] - if mod_name == '': - full_name = qparam.name - else: - full_name = mod_name + '.' + qparam.name - full_name = _get_full_name_access(full_name) - if qparam.other is None: - unpack_assigns.append(UNPACK_ASSIGN.format(full_name=full_name, index=index)) - index += 1 - else: - other_name = names[(qparam.other.module, qparam.other.name)] - other_name = _get_full_name_access(other_name) - unpack_assigns.append( - UNPACK_ASSIGN_SAME.format(full_name=full_name, other_name=other_name)) - - return TEMPLATE.format( - module=module, - klass=klass, - unpack_assigns='\n'.join(unpack_assigns)) diff --git a/diffq/uniform.py b/diffq/uniform.py deleted file mode 100644 index 7d02a4b..0000000 --- a/diffq/uniform.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -""" -Classic uniform quantization over n bits. -""" -from typing import Tuple -import torch - -from .base import BaseQuantizer -from .utils import capture_init, simple_repr - - -def uniform_quantize(p: torch.Tensor, bits: torch.Tensor = torch.tensor(8.)): - """ - Quantize the given weights over `bits` bits. - - Returns: - - quantized levels - - (min, max) range. - - """ - assert (bits >= 1).all() and (bits <= 15).all() - num_levels = (2 ** bits.float()).long() - mn = p.min().item() - mx = p.max().item() - p = (p - mn) / (mx - mn) # put p in [0, 1] - unit = 1 / (num_levels - 1) # quantization unit - levels = (p / unit).round() - if (bits <= 8).all(): - levels = levels.byte() - else: - levels = levels.short() - return levels, (mn, mx) - - -def uniform_unquantize(levels: torch.Tensor, scales: Tuple[float, float], - bits: torch.Tensor = torch.tensor(8.)): - """ - Unquantize the weights from the levels and scale. Return a float32 tensor. - """ - mn, mx = scales - num_levels = 2 ** bits.float() - unit = 1 / (num_levels - 1) - levels = levels.float() - p = levels * unit # in [0, 1] - return p * (mx - mn) + mn - - -class UniformQuantizer(BaseQuantizer): - @capture_init - def __init__(self, model: torch.nn.Module, bits: float = 8., min_size: float = 0.01, - float16: bool = False, qat: bool = False, exclude=[], detect_bound=True): - """ - Args: - model (torch.nn.Module): model to quantize - bits (float): number of bits to quantize over. - min_size (float): minimum size in MB of a parameter to be quantized. - float16 (bool): if a layer is smaller than min_size, should we still do float16? - qat (bool): perform quantized aware training. - exclude (list[str]): list of patterns used to match parameters to exclude. - For instance `['bias']` to exclude all bias terms. - detect_bound (bool): if True, will detect bound parameters and reuse - the same quantized tensor for both. - """ - self.bits = float(bits) - self.qat = qat - - super().__init__(model, min_size, float16, exclude, detect_bound) - - def __repr__(self): - return simple_repr(self, ) - - def _pre_forward_train(self): - if self.qat: - for qparam in self._qparams: - if qparam.other is not None: - new_param = qparam.other.module._parameters[qparam.other.name] - else: - quantized = self._quantize_param(qparam) - qvalue = self._unquantize_param(qparam, quantized) - new_param = qparam.param + (qvalue - qparam.param).detach() - qparam.module._parameters[qparam.name] = new_param - return True - return False - - def _post_forward_train(self): - if self.qat: - for qparam in self._qparams: - qparam.module._parameters[qparam.name] = qparam.param - return True - return False - - def _quantize_param(self, qparam): - levels, scales = uniform_quantize(qparam.param.data, torch.tensor(self.bits)) - return (levels, scales) - - def _unquantize_param(self, qparam, quantized): - levels, scales = quantized - return uniform_unquantize(levels, scales, torch.tensor(self.bits)) - - def _bit_pack_param(self, qparam, quantized, pack_fn): - levels, scales = quantized - packed = pack_fn(levels, self.bits) - return (packed, scales) - - def _bit_unpack_param(self, qparam, packed, unpack_fn): - """Unpack bitpacked representation. Should be overriden - """ - packed_levels, scales = packed - levels = unpack_fn( - packed_levels, qparam.param.numel()).to(qparam.param.device).view_as(qparam.param) - return (levels, scales) - - def model_size(self): - """ - Non differentiable model size in MB. - """ - total = super().model_size() - subtotal = 0 - for qparam in self._qparams: - if qparam.other is None: # if parameter is bound, count only one copy. - subtotal += self.bits * qparam.param.numel() + 64 # 2 float for the overall scales - subtotal /= 2**20 * 8 # bits to MegaBytes - return total + subtotal - - def true_model_size(self): - """ - Return the true quantized model size, in MB, without extra - compression. - """ - return self.model_size().item() diff --git a/diffq/utils.py b/diffq/utils.py deleted file mode 100644 index 4cd810c..0000000 --- a/diffq/utils.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -import functools -import inspect -from typing import Optional, List - - -def simple_repr(obj, attrs: Optional[List[str]] = None, overrides={}): - """ - Return a simple representation string for `obj`. - If `attrs` is not None, it should be a list of attributes to include. - """ - params = inspect.signature(obj.__class__).parameters - attrs_repr = [] - if attrs is None: - attrs = params.keys() - for attr in attrs: - display = False - if attr in overrides: - value = overrides[attr] - elif hasattr(obj, attr): - value = getattr(obj, attr) - else: - continue - if attr in params: - param = params[attr] - if param.default is inspect._empty or value != param.default: - display = True - else: - display = True - - if display: - attrs_repr.append(f"{attr}={value}") - return f"{obj.__class__.__name__}({','.join(attrs_repr)})" - - -def capture_init(init): - """capture_init. - - Decorate `__init__` with this, and you can then - recover the *args and **kwargs passed to it in `self._init_args_kwargs` - """ - signature = inspect.signature(init) - - @functools.wraps(init) - def __init__(self, *args, **kwargs): - bound = signature.bind(self, *args, **kwargs) - actual_kwargs = dict(bound.arguments) - del actual_kwargs['self'] - actual_kwargs.update(bound.kwargs) - self._init_kwargs = actual_kwargs - init(self, *args, **kwargs) - - return __init__