chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .compress import init_compression, redundancy_clean
|
||||
from .scheduler import compression_scheduler
|
||||
from .helper import convert_conv1d_to_linear
|
||||
@@ -0,0 +1,840 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
import math
|
||||
from torch import nn
|
||||
from torch.nn import init
|
||||
import deepspeed.comm as dist
|
||||
from .utils import TopKBinarizer, SymQuantizer, AsymQuantizer, TernaryQuantizer, BinaryQuantizer
|
||||
from deepspeed.utils import logger
|
||||
|
||||
g_mpu = None
|
||||
|
||||
|
||||
class QuantAct(nn.Module):
|
||||
"""
|
||||
Class to quantize given activations. Note that when using this function, the input activation quantization range will be fixed for all
|
||||
tokens/images for inference. This generally will affect some accuracy but achieve better latency performance.
|
||||
Parameters:
|
||||
----------
|
||||
act_range_momentum : float, default 0.95
|
||||
Momentum for updating the activation quantization range.
|
||||
quant_mode : str, default 'symmetric'
|
||||
"""
|
||||
|
||||
def __init__(self, act_range_momentum=0.95, quant_mode='symmetric'):
|
||||
super(QuantAct, self).__init__()
|
||||
|
||||
self.act_range_momentum = act_range_momentum
|
||||
self.quant_mode = quant_mode
|
||||
if quant_mode == 'symmetric':
|
||||
self.act_function = SymQuantizer.apply
|
||||
else:
|
||||
self.act_function = AsymQuantizer.apply
|
||||
|
||||
self.register_buffer('x_min_max', torch.zeros(2))
|
||||
|
||||
def forward(self, x, num_bits, *args):
|
||||
"""
|
||||
x: the activation that we need to quantize
|
||||
num_bits: the number of bits we need to quantize the activation to
|
||||
*args: some extra arguments that are useless but needed for align with the interface of other quantization functions
|
||||
"""
|
||||
|
||||
if self.training:
|
||||
x_min = x.data.min()
|
||||
x_max = x.data.max()
|
||||
|
||||
# Initialization
|
||||
if self.x_min_max[0] == self.x_min_max[1]:
|
||||
self.x_min_max[0] = x_min
|
||||
self.x_min_max[1] = x_max
|
||||
|
||||
# if do not need momentum, please set self.act_range_momentum = 0
|
||||
self.x_min_max[0] = self.x_min_max[0] * self.act_range_momentum + x_min * (1 - self.act_range_momentum)
|
||||
self.x_min_max[1] = self.x_min_max[1] * self.act_range_momentum + x_max * (1 - self.act_range_momentum)
|
||||
|
||||
x_q = self.act_function(x, num_bits, self.x_min_max[0], self.x_min_max[1])
|
||||
|
||||
return x_q
|
||||
|
||||
|
||||
class Embedding_Compress(nn.Embedding):
|
||||
|
||||
def __init__(self, *kargs):
|
||||
super(Embedding_Compress, self).__init__(*kargs)
|
||||
self.weight.start_bits = None
|
||||
self.weight.target_bits = None
|
||||
self.weight.q_period = None
|
||||
self.weight_quantization_enabled_in_forward = False
|
||||
self.weight_quantization_enabled = False
|
||||
|
||||
def extra_repr(self):
|
||||
return 'num_embeddings={}, embedding_dim={}, weight_quantization={}'.format(
|
||||
self.num_embeddings, self.embedding_dim, self.weight.target_bits)
|
||||
|
||||
def enable_weight_quantization(self, start_bits, target_bits, quantization_period,
|
||||
weight_quantization_enabled_in_forward, quantization_type, num_groups):
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = quantization_period
|
||||
self.weight_quantization_enabled_in_forward = weight_quantization_enabled_in_forward
|
||||
if self.weight_quantization_enabled_in_forward:
|
||||
logger.warning(
|
||||
"************ A lot of MoQ features are not supported in quantize_weight_in_forward mode, please consider to use DS-FP16 optimizer************"
|
||||
)
|
||||
if self.weight.target_bits >= 3:
|
||||
if quantization_type == 'symmetric':
|
||||
self.weight_quantizer = SymQuantizer.apply
|
||||
else:
|
||||
self.weight_quantizer = AsymQuantizer.apply
|
||||
elif self.weight.target_bits == 2:
|
||||
assert quantization_type == 'symmetric', 'Only symmetric quantization is supported for ternary weight quantization'
|
||||
self.weight_quantizer = TernaryQuantizer.apply
|
||||
elif self.weight.target_bits == 1:
|
||||
assert quantization_type == 'symmetric', 'Only symmetric quantization is supported for binary weight quantization'
|
||||
self.weight_quantizer = BinaryQuantizer.apply
|
||||
# for embedding, we always use token-wise quantization
|
||||
self.weight_quantize_num_groups = self.weight.size(0)
|
||||
|
||||
def fix_weight_quantization(self):
|
||||
self.weight.data = self.weight_quantizer(self.weight, self.weight.target_bits, None, None,
|
||||
self.weight_quantize_num_groups).data
|
||||
self.weight_quantization_enabled_in_forward = False
|
||||
return None
|
||||
|
||||
def forward(self, input):
|
||||
if self.weight_quantization_enabled_in_forward and self.weight_quantization_enabled:
|
||||
weight = self.weight_quantizer(self.weight, self.weight.target_bits, None, None,
|
||||
self.weight_quantize_num_groups)
|
||||
else:
|
||||
weight = self.weight
|
||||
|
||||
out = nn.functional.embedding(input, weight, self.padding_idx, self.max_norm, self.norm_type,
|
||||
self.scale_grad_by_freq, self.sparse)
|
||||
return out
|
||||
|
||||
|
||||
class LinearLayer_Compress(nn.Linear):
|
||||
"""
|
||||
Linear layer with compression.
|
||||
"""
|
||||
|
||||
def __init__(self, *kargs, bias=True):
|
||||
super(LinearLayer_Compress, self).__init__(*kargs, bias=bias)
|
||||
self.sparse_pruning_method = None
|
||||
self.row_pruning_method = None
|
||||
self.head_pruning_method = None
|
||||
self.activation_quantization_method = None
|
||||
self.weight.start_bits = None
|
||||
self.weight.target_bits = None
|
||||
self.weight.q_period = None
|
||||
self.weight_quantization_enabled_in_forward = False
|
||||
self.weight_quantization_enabled = False
|
||||
self.sparse_pruning_enabled = False
|
||||
self.row_pruning_enabled = False
|
||||
self.head_pruning_enabled = False
|
||||
self.activation_quantization_enabled = False
|
||||
|
||||
def extra_repr(self):
|
||||
return 'in_features={}, out_features={}, bias={}, sparse pruning={}, row pruning={}, head pruning={}, activation quantization={}, weight_quantization={}'.format(
|
||||
self.in_features, self.out_features, self.bias is not None, self.sparse_pruning_method is not None, \
|
||||
self.row_pruning_method is not None, self.head_pruning_method is not None, self.activation_quantization_method is not None, self.weight.target_bits)
|
||||
|
||||
def enable_sparse_pruning(self, ratio, method):
|
||||
# Here, we support two cases: L1 norm based pruning and topk based pruning
|
||||
self.sparse_pruning_ratio = ratio
|
||||
self.sparse_pruning_method = method
|
||||
if method == 'l1':
|
||||
weight_norm = torch.abs(self.weight.data)
|
||||
mask = TopKBinarizer.apply(weight_norm, self.sparse_pruning_ratio, False)
|
||||
mask = mask.view(self.weight.size())
|
||||
mask = mask.to(self.weight.device)
|
||||
elif method == 'topk':
|
||||
self.sparse_mask_scores = nn.Parameter(torch.Tensor(self.weight.size()))
|
||||
self.sparse_mask_scores.data = self.sparse_mask_scores.data.to(self.weight.device)
|
||||
init.kaiming_uniform_(self.sparse_mask_scores, a=math.sqrt(5))
|
||||
mask = None
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
self.register_buffer('sparse_pruning_mask', mask)
|
||||
|
||||
def enable_row_pruning(self, ratio, method):
|
||||
# Here, we support two cases: L1 norm based pruning and topk based pruning
|
||||
self.row_pruning_ratio = ratio
|
||||
self.row_pruning_method = method
|
||||
|
||||
if method == 'l1':
|
||||
# compute the l1 norm of each column
|
||||
weight_norm = torch.linalg.norm(self.weight.data, ord=1, dim=1)
|
||||
mask = TopKBinarizer.apply(weight_norm, self.row_pruning_ratio, False)
|
||||
mask = mask.view(-1, 1)
|
||||
mask = mask.to(self.weight.device)
|
||||
elif method == 'topk':
|
||||
self.row_mask_scores = nn.Parameter(torch.Tensor(self.weight.size(0), 1))
|
||||
self.row_mask_scores.data = self.row_mask_scores.data.to(self.weight.device)
|
||||
init.kaiming_uniform_(self.row_mask_scores, a=math.sqrt(5))
|
||||
mask = None
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
self.register_buffer('row_pruning_mask', mask)
|
||||
|
||||
def enable_head_pruning(self, ratio, method, num_heads):
|
||||
# Here, we support only topk based pruning
|
||||
self.num_heads = num_heads
|
||||
self.head_pruning_ratio = ratio
|
||||
self.head_pruning_method = method
|
||||
|
||||
if method not in ['topk']:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
self.head_pruning_ratio = ratio
|
||||
self.head_pruning_scores = nn.Parameter(torch.Tensor(1,
|
||||
self.num_heads)) # we apply the pruning to O matrix
|
||||
self.head_pruning_scores.data = self.head_pruning_scores.data.to(self.weight.device)
|
||||
init.kaiming_uniform_(self.head_pruning_scores, a=math.sqrt(5))
|
||||
|
||||
def fix_sparse_pruning_helper(self):
|
||||
mask = self.get_mask(pruning_type='sparse')
|
||||
self.weight.data = self.weight.data * mask
|
||||
del self.sparse_pruning_mask
|
||||
if self.sparse_pruning_method == 'topk':
|
||||
del self.sparse_mask_scores
|
||||
self.sparse_pruning_method = None
|
||||
self.sparse_pruning_enabled = False
|
||||
return None
|
||||
|
||||
def fix_row_col_pruning_helper(self, mask=None, dim_reduction=False):
|
||||
# This function is used for row/col pruning
|
||||
# particularly, if we have two back-to-back layers, F1 and F2; when
|
||||
# we remove rows from F1, we also need to remove columns from F2
|
||||
# However, if we only have one layer, F1, then we only need to mask pruned
|
||||
# rows as 0 in F1
|
||||
if mask is None:
|
||||
mask = self.get_mask(pruning_type='row').bool()
|
||||
if dim_reduction:
|
||||
start_bits = self.weight.start_bits
|
||||
target_bits = self.weight.target_bits
|
||||
q_period = self.weight.q_period
|
||||
self.weight = nn.Parameter(self.weight.data[mask.view(-1), :])
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = q_period
|
||||
if self.bias is not None:
|
||||
self.bias = nn.Parameter(self.bias.data[mask.view(-1)])
|
||||
self.out_features = self.weight.size(0)
|
||||
else:
|
||||
self.weight.data = self.weight.data * mask.view(-1, 1)
|
||||
if self.bias is not None:
|
||||
self.bias.data = self.bias.data * mask.view(-1)
|
||||
|
||||
del self.row_pruning_mask
|
||||
if self.row_pruning_method == 'topk':
|
||||
del self.row_mask_scores
|
||||
self.row_pruning_method = None
|
||||
else:
|
||||
# this is generally for column pruning
|
||||
start_bits = self.weight.start_bits
|
||||
target_bits = self.weight.target_bits
|
||||
q_period = self.weight.q_period
|
||||
self.weight = nn.Parameter(self.weight.data[:, mask.view(-1)])
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = q_period
|
||||
self.in_features = self.weight.size(1)
|
||||
mask = None
|
||||
self.row_pruning_enabled = False
|
||||
return mask
|
||||
|
||||
def fix_head_pruning_helper(self, mask=None, num_heads=None, dim_reduction=False):
|
||||
# similar as row/col pruning, head pruning also needs to prune QKV which is associated with O matrix
|
||||
num_heads = num_heads if num_heads else self.num_heads
|
||||
if mask is None:
|
||||
if self.head_pruning_method == 'topk':
|
||||
mask = self.get_mask(pruning_type='head').bool()
|
||||
if dim_reduction:
|
||||
shape = self.weight.size(0)
|
||||
start_bits = self.weight.start_bits
|
||||
target_bits = self.weight.target_bits
|
||||
q_period = self.weight.q_period
|
||||
self.weight = nn.Parameter(self.weight.data.t().reshape(num_heads,
|
||||
-1)[mask.view(-1), :].reshape(-1,
|
||||
shape).t())
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = q_period
|
||||
else:
|
||||
|
||||
shape = self.weight.size()
|
||||
self.weight.data = (self.weight.data.t().reshape(self.num_heads, -1) * mask.view(-1, 1)).reshape(
|
||||
shape[1], shape[0]).t()
|
||||
|
||||
if self.head_pruning_method == 'topk':
|
||||
del self.head_pruning_scores
|
||||
self.head_pruning_method = None
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
start_bits = self.weight.start_bits
|
||||
target_bits = self.weight.target_bits
|
||||
q_period = self.weight.q_period
|
||||
shape = self.weight.size(1)
|
||||
self.weight = nn.Parameter(self.weight.data.reshape(num_heads, -1)[mask.view(-1), :].reshape(-1, shape))
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = q_period
|
||||
if self.bias is not None:
|
||||
self.bias = nn.Parameter(self.bias.data.reshape(num_heads, -1)[mask.view(-1), :].reshape(-1))
|
||||
self.head_pruning_enabled = False
|
||||
return mask
|
||||
|
||||
def get_mask(self, pruning_type='row'):
|
||||
if pruning_type == 'sparse':
|
||||
if self.sparse_pruning_method == 'l1':
|
||||
return self.sparse_pruning_mask.to(self.weight.device)
|
||||
elif self.sparse_pruning_method == 'topk':
|
||||
return TopKBinarizer.apply(self.sparse_mask_scores, self.sparse_pruning_ratio, False)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
if pruning_type == 'row':
|
||||
if self.row_pruning_method == 'l1':
|
||||
return self.row_pruning_mask.to(self.weight.device)
|
||||
elif self.row_pruning_method == 'topk':
|
||||
return TopKBinarizer.apply(self.row_mask_scores, self.row_pruning_ratio, False)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif pruning_type == 'head':
|
||||
if self.head_pruning_method == 'topk':
|
||||
return TopKBinarizer.apply(self.head_pruning_scores, self.head_pruning_ratio, False)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def enable_weight_quantization(self, start_bits, target_bits, quantization_period,
|
||||
weight_quantization_enabled_in_forward, quantization_type, num_groups):
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = quantization_period
|
||||
self.weight_quantization_enabled_in_forward = weight_quantization_enabled_in_forward
|
||||
if self.weight_quantization_enabled_in_forward:
|
||||
logger.warning(
|
||||
"************ A lot of MoQ features are not supported in quantize_weight_in_forward mode, please consider to use DS-FP16 optimizer************"
|
||||
)
|
||||
if self.weight.target_bits >= 3:
|
||||
if quantization_type == 'symmetric':
|
||||
self.weight_quantizer = SymQuantizer.apply
|
||||
else:
|
||||
self.weight_quantizer = AsymQuantizer.apply
|
||||
elif self.weight.target_bits == 2:
|
||||
assert quantization_type == 'symmetric', 'Only symmetric quantization is supported for ternary weight quantization'
|
||||
self.weight_quantizer = TernaryQuantizer.apply
|
||||
elif self.weight.target_bits == 1:
|
||||
assert quantization_type == 'symmetric', 'Only symmetric quantization is supported for binary weight quantization'
|
||||
self.weight_quantizer = BinaryQuantizer.apply
|
||||
self.weight_quantize_num_groups = num_groups
|
||||
|
||||
def fix_weight_quantization(self):
|
||||
self.weight.data = self.weight_quantizer(self.weight, self.weight.target_bits, None, None,
|
||||
self.weight_quantize_num_groups).data
|
||||
self.weight_quantization_enabled_in_forward = False
|
||||
return None
|
||||
|
||||
def enable_activation_quantization(self, bits, quantization_type, range_calibration):
|
||||
assert bits in [4, 8], 'Only 4/8 bits activation quantization are supported for now'
|
||||
self.activation_quantization_bits = bits
|
||||
self.activation_quantization_method = f"{quantization_type}_{range_calibration}"
|
||||
if range_calibration == 'static':
|
||||
self.activation_quantizer = QuantAct(quant_mode=quantization_type)
|
||||
else:
|
||||
if quantization_type == 'symmetric':
|
||||
self.activation_quantizer = SymQuantizer.apply
|
||||
else:
|
||||
self.activation_quantizer = AsymQuantizer.apply
|
||||
|
||||
def head_pruning_reshape(self, w, mask):
|
||||
shape = w.shape
|
||||
return (w.t().reshape(self.num_heads, -1) * mask.view(-1, 1)).reshape(shape[1], shape[0]).t()
|
||||
|
||||
def forward(self, input, skip_bias_add=False):
|
||||
|
||||
if self.weight_quantization_enabled_in_forward and self.weight_quantization_enabled:
|
||||
weight = self.weight_quantizer(self.weight, self.weight.target_bits, None, None,
|
||||
self.weight_quantize_num_groups)
|
||||
bias = self.bias
|
||||
else:
|
||||
weight = self.weight
|
||||
bias = self.bias
|
||||
|
||||
if self.sparse_pruning_enabled and self.sparse_pruning_method:
|
||||
mask = self.get_mask(pruning_type='sparse')
|
||||
weight = weight * mask.view(self.weight.size())
|
||||
|
||||
if self.row_pruning_enabled and self.row_pruning_method:
|
||||
mask = self.get_mask(pruning_type='row')
|
||||
weight = weight * mask.view(-1, 1)
|
||||
if bias is not None:
|
||||
bias = bias * mask.view(-1)
|
||||
|
||||
if self.head_pruning_enabled and self.head_pruning_method:
|
||||
mask = self.get_mask(pruning_type='head')
|
||||
weight = self.head_pruning_reshape(weight, mask)
|
||||
|
||||
if self.activation_quantization_enabled:
|
||||
if 'dynamic' in self.activation_quantization_method:
|
||||
num_groups = input.numel() // input.size(-1)
|
||||
else:
|
||||
num_groups = 1
|
||||
input = self.activation_quantizer(input, self.activation_quantization_bits, None, None, num_groups)
|
||||
|
||||
if skip_bias_add:
|
||||
# used for mpu linear layers
|
||||
output = nn.functional.linear(input, weight, None)
|
||||
return output, bias
|
||||
else:
|
||||
output = nn.functional.linear(input, weight, bias)
|
||||
return output
|
||||
|
||||
|
||||
class Conv2dLayer_Compress(nn.Conv2d):
|
||||
"""
|
||||
Conv2D layer with compression.
|
||||
"""
|
||||
|
||||
def __init__(self, *kargs):
|
||||
super(Conv2dLayer_Compress, self).__init__(*kargs)
|
||||
self.sparse_pruning_method = None
|
||||
self.channel_pruning_method = None
|
||||
self.activation_quantization_method = None
|
||||
self.weight.start_bits = None
|
||||
self.weight.target_bits = None
|
||||
self.weight.q_period = None
|
||||
self.weight_quantization_enabled_in_forward = False
|
||||
self.sparse_pruning_enabled = False
|
||||
self.channel_pruning_enabled = False
|
||||
self.activation_quantization_enabled = False
|
||||
|
||||
def __repr__(self):
|
||||
s = ('{in_channels}, {out_channels}, kernel_size={kernel_size}'
|
||||
', stride={stride}')
|
||||
if self.padding != (0, ) * len(self.padding):
|
||||
s += ', padding={padding}'
|
||||
if self.dilation != (1, ) * len(self.dilation):
|
||||
s += ', dilation={dilation}'
|
||||
if self.output_padding != (0, ) * len(self.output_padding):
|
||||
s += ', output_padding={output_padding}'
|
||||
if self.groups != 1:
|
||||
s += ', groups={groups}'
|
||||
if self.bias is None:
|
||||
s += ', bias=False'
|
||||
if self.padding_mode != 'zeros':
|
||||
s += ', padding_mode={padding_mode}'
|
||||
output = s.format(**self.__dict__)
|
||||
|
||||
return output + ' sparse pruning={}, channel pruning={}, activation quantization={}, weight_quantization={}'.format(
|
||||
self.sparse_pruning_method is not None, self.channel_pruning_method is not None,
|
||||
self.activation_quantization_method is not None, self.weight.target_bits)
|
||||
|
||||
def enable_sparse_pruning(self, ratio, method):
|
||||
self.sparse_pruning_ratio = ratio
|
||||
self.sparse_pruning_method = method
|
||||
if method == 'l1':
|
||||
weight_norm = torch.abs(self.weight.data)
|
||||
mask = TopKBinarizer.apply(weight_norm, self.sparse_pruning_ratio, False)
|
||||
mask = mask.view(self.weight.size())
|
||||
mask = mask.to(self.weight.device)
|
||||
elif method == 'topk':
|
||||
self.sparse_mask_scores = nn.Parameter(torch.Tensor(self.weight.size()))
|
||||
self.sparse_mask_scores.data = self.sparse_mask_scores.data.to(self.weight.device)
|
||||
init.kaiming_uniform_(self.sparse_mask_scores, a=math.sqrt(5))
|
||||
mask = None
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
self.register_buffer('sparse_pruning_mask', mask)
|
||||
|
||||
def enable_channel_pruning(self, ratio, method):
|
||||
# Here, we support two cases: L1 norm based pruning and topk based pruning
|
||||
self.channel_pruning_ratio = ratio
|
||||
self.channel_pruning_method = method
|
||||
|
||||
if method == 'l1':
|
||||
# compute the l1 norm of each conv2d kernel (the last three dimension)
|
||||
weight_norm = torch.linalg.norm(self.weight.data, ord=1, dim=[1, 2, 3])
|
||||
mask = TopKBinarizer.apply(weight_norm, self.channel_pruning_ratio, False)
|
||||
mask = mask.view(-1, 1, 1, 1)
|
||||
mask = mask.to(self.weight.device)
|
||||
elif method == 'topk':
|
||||
self.channel_mask_scores = nn.Parameter(torch.Tensor(self.weight.size(0), 1, 1, 1))
|
||||
self.channel_mask_scores.data = self.channel_mask_scores.data.to(self.weight.device)
|
||||
init.kaiming_uniform_(self.channel_mask_scores, a=math.sqrt(5))
|
||||
mask = None
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
self.register_buffer('channel_pruning_mask', mask)
|
||||
|
||||
def fix_sparse_pruning_helper(self):
|
||||
mask = self.get_mask(pruning_type='sparse')
|
||||
self.weight.data = self.weight.data * mask
|
||||
del self.sparse_pruning_mask
|
||||
if self.sparse_pruning_method == 'topk':
|
||||
del self.sparse_mask_scores
|
||||
self.sparse_pruning_method = None
|
||||
self.sparse_pruning_enabled = False
|
||||
return None
|
||||
|
||||
def fix_channel_pruning_helper(self, mask=None, dim_reduction=False):
|
||||
if mask is None:
|
||||
if self.channel_pruning_method in ['l1', 'topk']:
|
||||
mask = self.get_mask(pruning_type='channel').bool()
|
||||
if dim_reduction:
|
||||
start_bits = self.weight.start_bits
|
||||
target_bits = self.weight.target_bits
|
||||
q_period = self.weight.q_period
|
||||
self.weight = nn.Parameter(self.weight.data[mask.view(-1), ...])
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = q_period
|
||||
if self.bias is not None:
|
||||
self.bias = nn.Parameter(self.bias.data[mask.view(-1)])
|
||||
else:
|
||||
self.weight.data = self.weight.data * mask.view(-1, 1, 1, 1)
|
||||
if self.bias is not None:
|
||||
self.bias.data = self.bias.data * mask.view(-1)
|
||||
del self.channel_pruning_mask
|
||||
if self.channel_pruning_method == 'topk':
|
||||
del self.channel_mask_scores
|
||||
self.channel_pruning_method = None
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
start_bits = self.weight.start_bits
|
||||
target_bits = self.weight.target_bits
|
||||
q_period = self.weight.q_period
|
||||
self.weight = nn.Parameter(self.weight.data[:, mask.view(-1), ...])
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = q_period
|
||||
mask = None
|
||||
self.channel_pruning_enabled = False
|
||||
return mask
|
||||
|
||||
def get_mask(self, pruning_type='sparse'):
|
||||
if pruning_type == 'sparse':
|
||||
if self.sparse_pruning_method == 'l1':
|
||||
return self.sparse_pruning_mask.to(self.weight.device)
|
||||
elif self.sparse_pruning_method == 'topk':
|
||||
return TopKBinarizer.apply(self.sparse_mask_scores, self.sparse_pruning_ratio, False)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif pruning_type == 'channel':
|
||||
if self.channel_pruning_method == 'l1':
|
||||
return self.channel_pruning_mask.to(self.weight.device)
|
||||
elif self.channel_pruning_method == 'topk':
|
||||
return TopKBinarizer.apply(self.channel_mask_scores, self.channel_pruning_ratio, False)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def fix_weight_quantization(self):
|
||||
self.weight.data = self.weight_quantizer(self.weight, self.weight.target_bits, None, None,
|
||||
self.weight_quantize_num_groups).data
|
||||
self.weight_quantization_enabled_in_forward = False
|
||||
return None
|
||||
|
||||
def enable_weight_quantization(self, start_bits, target_bits, quantization_period,
|
||||
weight_quantization_enabled_in_forward, quantization_type, num_groups):
|
||||
self.weight.start_bits = start_bits
|
||||
self.weight.target_bits = target_bits
|
||||
self.weight.q_period = quantization_period
|
||||
self.weight_quantization_enabled_in_forward = weight_quantization_enabled_in_forward
|
||||
if self.weight_quantization_enabled_in_forward:
|
||||
assert self.weight.target_bits >= 4, 'Only >=4 bits weight quantization are supported during forward pass for now'
|
||||
logger.warning(
|
||||
"************ A lot of MoQ features are not supported in quantize_weight_in_forward mode, please consider to use DS-FP16 optimizer************"
|
||||
)
|
||||
if quantization_type == 'symmetric':
|
||||
self.weight_quantizer = SymQuantizer.apply
|
||||
else:
|
||||
self.weight_quantizer = AsymQuantizer.apply
|
||||
self.weight_quantize_num_groups = num_groups
|
||||
|
||||
def enable_activation_quantization(self, bits, quantization_type, range_calibration):
|
||||
assert bits in [4, 8], 'Only 4/8 bits activation quantization are supported for now'
|
||||
self.activation_quantization_bits = bits
|
||||
self.activation_quantization_method = f"{quantization_type}_{range_calibration}"
|
||||
if range_calibration == 'static':
|
||||
self.activation_quantizer = QuantAct(quant_mode=quantization_type)
|
||||
else:
|
||||
if quantization_type == 'symmetric':
|
||||
self.activation_quantizer = SymQuantizer.apply
|
||||
else:
|
||||
self.activation_quantizer = AsymQuantizer.apply
|
||||
|
||||
def forward(self, input):
|
||||
|
||||
if self.weight_quantization_enabled_in_forward and self.weight_quantization_enabled:
|
||||
weight = self.weight_quantizer(self.weight, self.weight.target_bits, None, None,
|
||||
self.weight_quantize_num_groups)
|
||||
bias = self.bias
|
||||
else:
|
||||
weight = self.weight
|
||||
bias = self.bias
|
||||
|
||||
if self.sparse_pruning_enabled and self.sparse_pruning_method:
|
||||
mask = self.get_mask(pruning_type='sparse')
|
||||
weight = weight * mask.view(self.weight.size())
|
||||
|
||||
if self.channel_pruning_enabled:
|
||||
mask = self.get_mask(pruning_type='channel')
|
||||
weight = weight * mask.view(-1, 1, 1, 1)
|
||||
if bias is not None:
|
||||
bias = bias * mask.view(-1)
|
||||
|
||||
if self.activation_quantization_enabled:
|
||||
if 'dynamic' in self.activation_quantization_method:
|
||||
num_groups = input.numel() // input[0].numel()
|
||||
else:
|
||||
num_groups = 1
|
||||
input = self.activation_quantizer(input, self.activation_quantization_bits, None, None, num_groups)
|
||||
|
||||
return nn.functional.conv2d(input, weight, bias, self.stride, self.padding, self.dilation, self.groups)
|
||||
|
||||
|
||||
class BNLayer_Compress(nn.BatchNorm2d):
|
||||
|
||||
def fix_channel_pruning_helper(self, mask, dim_reduction=True):
|
||||
self.weight = nn.Parameter(self.weight.data[mask.view(-1)])
|
||||
self.bias = nn.Parameter(self.bias.data[mask.view(-1)])
|
||||
self.running_mean = self.running_mean[mask.view(-1)]
|
||||
self.running_var = self.running_var[mask.view(-1)]
|
||||
|
||||
|
||||
def _reduce(input_):
|
||||
"""All-reduce the input tensor across model parallel group."""
|
||||
group = g_mpu.get_model_parallel_group()
|
||||
|
||||
# Bypass the function if we are using only 1 GPU.
|
||||
if dist.get_world_size(group=group) == 1:
|
||||
return input_
|
||||
|
||||
# All-reduce.
|
||||
dist.all_reduce(input_, group=group)
|
||||
|
||||
return input_
|
||||
|
||||
|
||||
def split_tensor_along_last_dim(tensor, num_partitions, contiguous_split_chunks=False):
|
||||
"""Split a tensor along its last dimension.
|
||||
Arguments:
|
||||
tensor: input tensor.
|
||||
num_partitions: number of partitions to split the tensor
|
||||
contiguous_split_chunks: If True, make each chunk contiguous
|
||||
in memory.
|
||||
"""
|
||||
# Get the size and dimension.
|
||||
last_dim = tensor.dim() - 1
|
||||
assert tensor.size()[last_dim] % num_partitions == 0
|
||||
last_dim_size = tensor.size()[last_dim] // num_partitions
|
||||
# Split.
|
||||
tensor_list = torch.split(tensor, last_dim_size, dim=last_dim)
|
||||
# Note: torch.split does not create contiguous tensors by default.
|
||||
if contiguous_split_chunks:
|
||||
return tuple(chunk.contiguous() for chunk in tensor_list)
|
||||
|
||||
return tensor_list
|
||||
|
||||
|
||||
def _split(input_):
|
||||
"""Split the tensor along its last dimension and keep the
|
||||
corresponding slice."""
|
||||
group = g_mpu.get_model_parallel_group()
|
||||
|
||||
# Bypass the function if we are using only 1 GPU.
|
||||
if dist.get_world_size(group=group) == 1:
|
||||
return input_
|
||||
|
||||
# Split along last dimension.
|
||||
world_size = dist.get_world_size(group=group)
|
||||
input_list = split_tensor_along_last_dim(input_, world_size)
|
||||
|
||||
# Note: torch.split does not create contiguous tensors by default.
|
||||
rank = dist.get_rank(group=group)
|
||||
output = input_list[rank].contiguous()
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def _gather(input_):
|
||||
"""Gather tensors and concatenate along the last dimension."""
|
||||
group = g_mpu.get_model_parallel_group()
|
||||
|
||||
# Bypass the function if we are using only 1 GPU.
|
||||
if dist.get_world_size(group=group) == 1:
|
||||
return input_
|
||||
|
||||
# Size and dimension.
|
||||
last_dim = input_.dim() - 1
|
||||
rank = dist.get_rank(group=group)
|
||||
world_size = dist.get_world_size(group=group)
|
||||
|
||||
tensor_list = [torch.empty_like(input_) for _ in range(world_size)]
|
||||
tensor_list[rank] = input_
|
||||
dist.all_gather(tensor_list, input_, group=group)
|
||||
|
||||
# Note: torch.cat already creates a contiguous tensor.
|
||||
output = torch.cat(tensor_list, dim=last_dim).contiguous()
|
||||
|
||||
return output
|
||||
|
||||
|
||||
class _CopyToModelParallelRegion(torch.autograd.Function):
|
||||
"""Pass the input to the model parallel region."""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input_):
|
||||
return input_
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
return _reduce(grad_output)
|
||||
|
||||
|
||||
class _ReduceFromModelParallelRegion(torch.autograd.Function):
|
||||
"""All-reduce the input from the model parallel region."""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input_):
|
||||
return _reduce(input_)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
return grad_output
|
||||
|
||||
|
||||
class _ScatterToModelParallelRegion(torch.autograd.Function):
|
||||
"""Split the input and keep only the corresponding chuck to the rank."""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input_):
|
||||
return _split(input_)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
return _gather(grad_output)
|
||||
|
||||
|
||||
class _GatherFromModelParallelRegion(torch.autograd.Function):
|
||||
"""Gather the input from model parallel region and concatenate."""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input_):
|
||||
return _gather(input_)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
return _split(grad_output)
|
||||
|
||||
|
||||
# -----------------
|
||||
# Helper functions.
|
||||
# -----------------
|
||||
|
||||
|
||||
def copy_to_model_parallel_region(input_):
|
||||
return _CopyToModelParallelRegion.apply(input_)
|
||||
|
||||
|
||||
def reduce_from_model_parallel_region(input_):
|
||||
return _ReduceFromModelParallelRegion.apply(input_)
|
||||
|
||||
|
||||
def scatter_to_model_parallel_region(input_):
|
||||
return _ScatterToModelParallelRegion.apply(input_)
|
||||
|
||||
|
||||
def gather_from_model_parallel_region(input_):
|
||||
return _GatherFromModelParallelRegion.apply(input_)
|
||||
|
||||
|
||||
class ColumnParallelLinear_Compress(LinearLayer_Compress):
|
||||
|
||||
def __init__(self, mpu, input_size, output_size, bias=True, gather_output=True, skip_bias_add=False):
|
||||
# Keep input parameters
|
||||
global g_mpu
|
||||
g_mpu = mpu
|
||||
self.input_size = input_size
|
||||
self.output_size = output_size
|
||||
self.gather_output = gather_output
|
||||
self.skip_bias_add = skip_bias_add
|
||||
|
||||
# Divide the weight matrix along the last dimension.
|
||||
world_size = mpu.get_model_parallel_world_size()
|
||||
assert output_size % world_size == 0
|
||||
self.output_size_per_partition = output_size // world_size
|
||||
|
||||
super(ColumnParallelLinear_Compress, self).__init__(self.input_size, self.output_size_per_partition, bias=bias)
|
||||
|
||||
def forward(self, input_):
|
||||
# Set up backprop all-reduce.
|
||||
input_parallel = copy_to_model_parallel_region(input_)
|
||||
# Matrix multiply.
|
||||
if self.skip_bias_add:
|
||||
output_parallel, bias = super().forward(input_parallel, True)
|
||||
else:
|
||||
output_parallel = super().forward(input_parallel)
|
||||
bias = None
|
||||
if self.gather_output:
|
||||
# All-gather across the partitions.
|
||||
output = gather_from_model_parallel_region(output_parallel)
|
||||
else:
|
||||
output = output_parallel
|
||||
return output, bias
|
||||
|
||||
|
||||
class RowParallelLinear_Compress(LinearLayer_Compress):
|
||||
|
||||
def __init__(self, mpu, input_size, output_size, bias=True, input_is_parallel=False, skip_bias_add=False):
|
||||
# Keep input parameters
|
||||
global g_mpu
|
||||
g_mpu = mpu
|
||||
self.input_size = input_size
|
||||
self.output_size = output_size
|
||||
self.input_is_parallel = input_is_parallel
|
||||
self.skip_bias_add = skip_bias_add
|
||||
|
||||
# Divide the weight matrix along the last dimension.
|
||||
world_size = mpu.get_model_parallel_world_size()
|
||||
assert input_size % world_size == 0
|
||||
self.input_size_per_partition = input_size // world_size
|
||||
|
||||
super(RowParallelLinear_Compress, self).__init__(self.input_size_per_partition, self.output_size, bias=bias)
|
||||
|
||||
def forward(self, input_):
|
||||
# Set up backprop all-reduce.
|
||||
if self.input_is_parallel:
|
||||
input_parallel = input_
|
||||
else:
|
||||
input_parallel = scatter_to_model_parallel_region(input_)
|
||||
# Matrix multiply.
|
||||
output_parallel, bias = super().forward(input_parallel, True)
|
||||
|
||||
# All-reduce across all the partitions.
|
||||
output_ = reduce_from_model_parallel_region(output_parallel)
|
||||
if not self.skip_bias_add:
|
||||
if bias is not None:
|
||||
output = output_ + bias
|
||||
else:
|
||||
output = output_
|
||||
output_bias = None
|
||||
else:
|
||||
output = output_
|
||||
output_bias = bias
|
||||
return output, output_bias
|
||||
@@ -0,0 +1,239 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import re
|
||||
from .helper import compression_preparation, fix_compression, recursive_getattr, is_module_compressible
|
||||
from .config import get_compression_config
|
||||
from ..runtime.config_utils import dict_raise_error_on_duplicate_keys
|
||||
from .constants import *
|
||||
import os
|
||||
import json
|
||||
|
||||
try:
|
||||
import neural_compressor as nc
|
||||
except ImportError as e:
|
||||
nc = None
|
||||
|
||||
|
||||
def check_deepspeed_config(config):
|
||||
if isinstance(config, dict):
|
||||
return config
|
||||
elif os.path.exists(config):
|
||||
return json.load(open(config, "r"), object_pairs_hook=dict_raise_error_on_duplicate_keys)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Expected a string path to an existing deepspeed config, or a dictionary. Received: {config}")
|
||||
|
||||
|
||||
def get_module_name(group_name, model, key_word, exist_module_name, mpu=None, verbose=True):
|
||||
'''
|
||||
get the associated module name from the model based on the key_word provided by users
|
||||
'''
|
||||
return_module_name = []
|
||||
for name, module in model.named_modules():
|
||||
|
||||
module_check = is_module_compressible(module, mpu)
|
||||
|
||||
if re.search(key_word, name) is not None and module_check:
|
||||
if name in exist_module_name and verbose:
|
||||
# logger.warning
|
||||
raise ValueError(
|
||||
f"{name} is already added to compression, please check your config file for {group_name}.")
|
||||
if name not in exist_module_name:
|
||||
exist_module_name.add(name)
|
||||
return_module_name.append(name)
|
||||
return return_module_name, exist_module_name
|
||||
|
||||
|
||||
def get_compress_methods(model, compress_methods, mpu=None):
|
||||
# extract the compression module for each method in compress_methods
|
||||
layer_added_compress_methods = []
|
||||
for method, method_content in compress_methods.items():
|
||||
if LAYER_REDUCTION in method:
|
||||
continue
|
||||
# for loop different methods, i.e., weight quantization, activation quantization etc
|
||||
exist_module_name = set()
|
||||
shared_parameters = method_content[SHARED_PARAMETERS] # get all the shared parameters
|
||||
for group_name, method_parameters in method_content[DIFFERENT_GROUPS].items():
|
||||
# for loop different groups, i.e., weight quantization group 1, weight quantization group 2 etc
|
||||
module_name_list = []
|
||||
related_module_name_list = []
|
||||
if method_parameters[DIFFERENT_GROUPS_RELATED_MODULE_SCOPE]:
|
||||
# this is used for head/row/channel pruning, if users provide the related module scope, we can shrink the layer dim for them
|
||||
# otherwise we just mask those as zeros
|
||||
for key_word, related_key_words in zip(method_parameters[DIFFERENT_GROUPS_MODULE_SCOPE],
|
||||
method_parameters[DIFFERENT_GROUPS_RELATED_MODULE_SCOPE]):
|
||||
module_name, exist_module_name = get_module_name(group_name,
|
||||
model,
|
||||
key_word,
|
||||
exist_module_name,
|
||||
mpu=mpu)
|
||||
module_name_list.append(module_name)
|
||||
tmp_related_module_name_list = []
|
||||
for rkw in related_key_words:
|
||||
# related key word can be a list, for instance the QKV for O matrix in Attention
|
||||
module_name, _ = get_module_name(group_name, model, rkw, set(), mpu=mpu)
|
||||
tmp_related_module_name_list.append(module_name)
|
||||
related_module_name_list.append(tmp_related_module_name_list)
|
||||
else:
|
||||
for key_word in method_parameters[DIFFERENT_GROUPS_MODULE_SCOPE]:
|
||||
module_name, exist_module_name = get_module_name(group_name,
|
||||
model,
|
||||
key_word,
|
||||
exist_module_name,
|
||||
mpu=mpu)
|
||||
module_name_list.append(module_name)
|
||||
|
||||
if module_name_list:
|
||||
# combine shared parameters with each group
|
||||
combined_method_parameters = {
|
||||
**(method_parameters.copy().pop(DIFFERENT_GROUPS_PARAMETERS)),
|
||||
**shared_parameters
|
||||
}
|
||||
compression_item = [module_name_list, related_module_name_list, {method: combined_method_parameters}]
|
||||
layer_added_compress_methods.append(compression_item)
|
||||
return layer_added_compress_methods
|
||||
|
||||
|
||||
def init_compression(model, deepspeed_config, teacher_model=None, mpu=None):
|
||||
"""
|
||||
Compress a model: replace linear/conv2d layer with deepspeed compression-aware modules
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to compress.
|
||||
deepspeed_config (`DeepSpeedConfig`)
|
||||
The path of ds_config
|
||||
mpu
|
||||
The mpu module for Row/Column parallelism
|
||||
"""
|
||||
compress_methods = get_compression_config(check_deepspeed_config(deepspeed_config))
|
||||
if hasattr(model, 'module'):
|
||||
c_model = model.module
|
||||
else:
|
||||
c_model = model
|
||||
|
||||
# For layer reduction
|
||||
if compress_methods[LAYER_REDUCTION][LAYER_REDUCTION_ENABLED]:
|
||||
assert teacher_model is not None, "Teacher model is required for layer reduction"
|
||||
student_initialization(c_model, teacher_model, deepspeed_config)
|
||||
|
||||
layer_added_compress_methods = get_compress_methods(c_model, compress_methods, mpu=mpu)
|
||||
compression_preparation(c_model, layer_added_compress_methods, mpu)
|
||||
|
||||
# For sparse pruning snip_momentum method
|
||||
shared_parameters = compress_methods[SPARSE_PRUNING][SHARED_PARAMETERS]
|
||||
if shared_parameters[SPARSE_PRUNING_ENABLED] and \
|
||||
shared_parameters[SPARSE_PRUNING_METHOD] == SPARSE_PRUNING_METHOD_SNIP_MOMENTUM:
|
||||
|
||||
assert nc is not None, "please ensure the neural_compressor python package is installed by pip or conda if user wants to use snip_momentum sparse pruning"
|
||||
|
||||
from .helper import generate_pruners, register_on_step_begin
|
||||
from nc import WeightPruningConfig
|
||||
|
||||
config = WeightPruningConfig(target_sparsity=1 - shared_parameters[SPARSE_PRUNING_DENSE_RATIO],
|
||||
pattern=shared_parameters[SPARSE_PRUNING_BLOCK_PATTERN],
|
||||
pruning_frequency=shared_parameters[SPARSE_PRUNING_SCHEDULE_OFFSET_STRIDE],
|
||||
start_step=shared_parameters[SPARSE_PRUNING_SCHEDULE_OFFSET],
|
||||
end_step=shared_parameters[SPARSE_PRUNING_SCHEDULE_OFFSET_END],
|
||||
excluded_op_names=shared_parameters[SPARSE_PRUNING_EXCLUDED_MODULES])
|
||||
pruners = generate_pruners(config, c_model)
|
||||
c_model.pruners = pruners
|
||||
register_on_step_begin(c_model)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def redundancy_clean(model, deepspeed_config, mpu=None):
|
||||
"""
|
||||
Remove the redundancy of a model
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to compress.
|
||||
deepspeed_config (`DeepSpeedConfig`)
|
||||
The path of ds_config
|
||||
mpu
|
||||
The mpu module for Row/Column parallelism
|
||||
"""
|
||||
compress_methods = get_compression_config(check_deepspeed_config(deepspeed_config))
|
||||
if hasattr(model, 'module'):
|
||||
c_model = model.module
|
||||
else:
|
||||
c_model = model
|
||||
|
||||
layer_added_compress_methods_tmp = get_compress_methods(c_model, compress_methods, mpu=mpu)
|
||||
# sort methods
|
||||
order_list = [
|
||||
WEIGHT_QUANTIZATION, SPARSE_PRUNING, ROW_PRUNING, HEAD_PRUNING, CHANNEL_PRUNING, ACTIVATION_QUANTIZATION
|
||||
]
|
||||
layer_added_compress_methods = sorted(layer_added_compress_methods_tmp,
|
||||
key=lambda x: order_list.index(list(x[2].keys())[0]))
|
||||
|
||||
for module_name_lists, related_module_name_lists, compression_technique in layer_added_compress_methods:
|
||||
stored_mask = []
|
||||
need_mask = True if related_module_name_lists else False
|
||||
for i, mnl in enumerate(module_name_lists):
|
||||
for module_name in mnl:
|
||||
mask = fix_compression(c_model, module_name, compression_technique, dim_reduction=need_mask)
|
||||
if need_mask:
|
||||
stored_mask.append(mask)
|
||||
if need_mask:
|
||||
for rmnl in related_module_name_lists[i]:
|
||||
for j, module_name in enumerate(rmnl):
|
||||
mask = fix_compression(c_model,
|
||||
module_name,
|
||||
compression_technique,
|
||||
mask=stored_mask[j],
|
||||
dim_reduction=True)
|
||||
return model
|
||||
|
||||
|
||||
def student_initialization(student_model, teacher_model, deepspeed_config):
|
||||
'''
|
||||
Given a student model and a teacher model, select the
|
||||
Args:
|
||||
student_model (`torch.nn.Module`)
|
||||
The model we will update weight
|
||||
teacher_model (`torch.nn.Module`)
|
||||
The model guide the student to learn
|
||||
deepspeed_config (`DeepSpeedConfig`)
|
||||
The path of ds_config
|
||||
'''
|
||||
config = get_compression_config(check_deepspeed_config(deepspeed_config))
|
||||
compress_methods = config[LAYER_REDUCTION]
|
||||
|
||||
module_name_prefix = compress_methods[MODULE_NAME_PREFIX]
|
||||
teacher_layer = compress_methods[TEACHER_LAYER]
|
||||
student_layer = [i for i in range(len(teacher_layer))]
|
||||
other_module_name = compress_methods[OTHER_MODULE_NAME]
|
||||
'''
|
||||
name_prefix (`str`)
|
||||
The prefix name before the layer #.
|
||||
Example 1: bert.encoder.layer, for BERT_base model's prefix name
|
||||
Example 2: transformer.h, for GPT-2 hugging face prefix name
|
||||
teacher_layer (`list of integers`)
|
||||
The layer of teacher will be used for student's reinitialization
|
||||
Example 1: [1,3,5,7,9], means we want to matches the 2nd/4th/6th/8th/10th layer of teacher to the first 5 layers of student
|
||||
student_layer (`list` or None)
|
||||
The layer of student need to be re-initialized
|
||||
Example 1: None, means we want to reinitialize all the layers
|
||||
Example 1: [0,1,2,3,4], means we want to reinitialize the first 5 layers
|
||||
other_module_name (`list of string`)
|
||||
The modules will be used for student's reinitialization
|
||||
Example 1: ['bert.pooler', 'bert.embeddings', 'classifier'], means we want to apply the weight in teacher's embedding/pooler/classier module to the student
|
||||
Example 2: ['transformer.w', 'transformer.ln_f', 'lm_head'], means we want to apply the weight in teacher's embedding layers module to the student
|
||||
Note that teacher_layer should matches student layer
|
||||
'''
|
||||
assert len(student_layer) == len(teacher_layer)
|
||||
for s_name, t_name in zip(student_layer, teacher_layer):
|
||||
s_module = recursive_getattr(student_model, module_name_prefix + '.' + str(s_name))
|
||||
t_module = recursive_getattr(teacher_model, module_name_prefix + '.' + str(t_name))
|
||||
for s_param, t_param in zip(s_module.parameters(), t_module.parameters()):
|
||||
s_param.data.copy_(t_param.data)
|
||||
for name in other_module_name:
|
||||
s_module = recursive_getattr(student_model, name)
|
||||
t_module = recursive_getattr(teacher_model, name)
|
||||
print(name)
|
||||
for s_param, t_param in zip(s_module.parameters(), t_module.parameters()):
|
||||
s_param.data.copy_(t_param.data)
|
||||
@@ -0,0 +1,452 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .constants import *
|
||||
import copy
|
||||
from ..runtime.config_utils import get_scalar_param, get_list_param
|
||||
|
||||
|
||||
def get_compression_config(param_dict):
|
||||
#
|
||||
output = {}
|
||||
|
||||
if COMPRESSION_TRAINING not in param_dict.keys():
|
||||
param_dict[COMPRESSION_TRAINING] = {}
|
||||
sub_param_dict = param_dict[COMPRESSION_TRAINING]
|
||||
output[WEIGHT_QUANTIZATION] = get_weight_quantization(sub_param_dict)
|
||||
output[ACTIVATION_QUANTIZATION] = get_activation_quantization(sub_param_dict)
|
||||
output[SPARSE_PRUNING] = get_sparse_pruning(sub_param_dict)
|
||||
output[ROW_PRUNING] = get_row_pruning(sub_param_dict)
|
||||
output[HEAD_PRUNING] = get_head_pruning(sub_param_dict)
|
||||
output[CHANNEL_PRUNING] = get_channel_pruning(sub_param_dict)
|
||||
|
||||
output[LAYER_REDUCTION] = get_layer_reduction(sub_param_dict)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def get_layer_reduction(param_dict):
|
||||
output = {}
|
||||
output[LAYER_REDUCTION_ENABLED] = LAYER_REDUCTION_ENABLED_DEFAULT
|
||||
if get_layer_reduction_enabled(param_dict):
|
||||
output[LAYER_REDUCTION_ENABLED] = get_layer_reduction_enabled(param_dict)
|
||||
for key, val in get_layer_reduction_params(param_dict).items():
|
||||
output[key] = val
|
||||
return output
|
||||
|
||||
|
||||
def get_layer_reduction_enabled(param_dict):
|
||||
if LAYER_REDUCTION in param_dict.keys():
|
||||
return get_scalar_param(param_dict[LAYER_REDUCTION], LAYER_REDUCTION_ENABLED, LAYER_REDUCTION_ENABLED_DEFAULT)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_layer_reduction_params(param_dict):
|
||||
if LAYER_REDUCTION in param_dict.keys():
|
||||
layer_reduction_params = copy.copy(param_dict[LAYER_REDUCTION])
|
||||
layer_reduction_params.pop(LAYER_REDUCTION_ENABLED)
|
||||
return layer_reduction_params
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_quantize_enabled(param_dict):
|
||||
if COMPRESSION_TRAINING not in param_dict.keys():
|
||||
return False
|
||||
|
||||
sub_param_dict = param_dict[COMPRESSION_TRAINING]
|
||||
output = get_weight_quantization_shared_parameters(sub_param_dict)
|
||||
return output[WEIGHT_QUANTIZE_ENABLED]
|
||||
|
||||
|
||||
def get_weight_quantization(param_dict):
|
||||
output = {}
|
||||
if WEIGHT_QUANTIZATION not in param_dict.keys():
|
||||
param_dict[WEIGHT_QUANTIZATION] = {SHARED_PARAMETERS: {}, DIFFERENT_GROUPS: {}}
|
||||
sub_param_dict = param_dict[WEIGHT_QUANTIZATION]
|
||||
# shared parameters
|
||||
output[SHARED_PARAMETERS] = get_weight_quantization_shared_parameters(sub_param_dict)
|
||||
# each sub-groups
|
||||
if output[SHARED_PARAMETERS][WEIGHT_QUANTIZE_ENABLED]:
|
||||
assert DIFFERENT_GROUPS in sub_param_dict.keys(
|
||||
), f"Weigh Quantization is enabled, {DIFFERENT_GROUPS} must be specified"
|
||||
output[DIFFERENT_GROUPS] = get_weight_quantization_different_groups(sub_param_dict)
|
||||
return output
|
||||
|
||||
|
||||
def get_weight_quantization_shared_parameters(param_dict):
|
||||
output = {}
|
||||
if SHARED_PARAMETERS in param_dict.keys():
|
||||
sub_param_dict = param_dict[SHARED_PARAMETERS]
|
||||
output[WEIGHT_QUANTIZE_ENABLED] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_ENABLED,
|
||||
WEIGHT_QUANTIZE_ENABLED_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_KERNEL] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_KERNEL,
|
||||
WEIGHT_QUANTIZE_KERNEL_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_SCHEDULE_OFFSET] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_SCHEDULE_OFFSET,
|
||||
WEIGHT_QUANTIZE_SCHEDULE_OFFSET_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_GROUPS] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_GROUPS,
|
||||
WEIGHT_QUANTIZE_GROUPS_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_VERBOSE] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_VERBOSE,
|
||||
WEIGHT_QUANTIZE_VERBOSE_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_TYPE] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_TYPE,
|
||||
WEIGHT_QUANTIZE_TYPE_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_IN_FORWARD_ENABLED] = get_scalar_param(sub_param_dict,
|
||||
WEIGHT_QUANTIZE_IN_FORWARD_ENABLED,
|
||||
WEIGHT_QUANTIZE_IN_FORWARD_ENABLED_DEFAULT)
|
||||
assert output[WEIGHT_QUANTIZE_TYPE] in [
|
||||
WEIGHT_QUANTIZE_SYMMETRIC, WEIGHT_QUANTIZE_ASYMMETRIC
|
||||
], f"Invalid weight quantize type. Supported types: [{WEIGHT_QUANTIZE_SYMMETRIC}, {WEIGHT_QUANTIZE_ASYMMETRIC}]"
|
||||
output[WEIGHT_QUANTIZE_ROUNDING] = get_scalar_param(sub_param_dict, WEIGHT_QUANTIZE_ROUNDING,
|
||||
WEIGHT_QUANTIZE_ROUNDING_DEFAULT)
|
||||
assert output[WEIGHT_QUANTIZE_ROUNDING] in [
|
||||
WEIGHT_QUANTIZE_NEAREST_ROUNDING, WEIGHT_QUANTIZE_STOCHASTIC_ROUNDING
|
||||
], f"Invalid weight quantize rounding. Supported types: [{WEIGHT_QUANTIZE_NEAREST_ROUNDING}, {WEIGHT_QUANTIZE_STOCHASTIC_ROUNDING}]"
|
||||
if WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE in sub_param_dict.keys():
|
||||
output[WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE] = get_scalar_param(
|
||||
sub_param_dict[WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE], WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE_ENABLED,
|
||||
WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE_ENABLED_DEFAULT)
|
||||
output[WEIGHT_QUANTIZE_CHANGE_RATIO] = get_scalar_param(
|
||||
sub_param_dict[WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE], WEIGHT_QUANTIZE_CHANGE_RATIO,
|
||||
WEIGHT_QUANTIZE_CHANGE_RATIO_DEFAULT)
|
||||
else:
|
||||
output[WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE] = WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE_ENABLED_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_CHANGE_RATIO] = WEIGHT_QUANTIZE_CHANGE_RATIO_DEFAULT
|
||||
else:
|
||||
output[WEIGHT_QUANTIZE_ENABLED] = WEIGHT_QUANTIZE_ENABLED_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_KERNEL] = WEIGHT_QUANTIZE_KERNEL_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_SCHEDULE_OFFSET] = WEIGHT_QUANTIZE_SCHEDULE_OFFSET_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_GROUPS] = WEIGHT_QUANTIZE_GROUPS_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_VERBOSE] = WEIGHT_QUANTIZE_VERBOSE_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_TYPE] = WEIGHT_QUANTIZE_TYPE_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_ROUNDING] = WEIGHT_QUANTIZE_ROUNDING_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE] = WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE_ENABLED_DEFAULT
|
||||
output[WEIGHT_QUANTIZE_CHANGE_RATIO] = WEIGHT_QUANTIZE_CHANGE_RATIO_DEFAULT
|
||||
return output
|
||||
|
||||
|
||||
def get_weight_quantization_different_groups(param_dict):
|
||||
output = {}
|
||||
sub_param_dict = param_dict[DIFFERENT_GROUPS]
|
||||
|
||||
def get_params(name, group_dict):
|
||||
assert WEIGHT_QUANTIZE_START_BITS in group_dict.keys(
|
||||
), f"{WEIGHT_QUANTIZE_START_BITS} must be specified for weight quantization group {name}"
|
||||
assert WEIGHT_QUANTIZE_TARGET_BITS in group_dict.keys(
|
||||
), f"{WEIGHT_QUANTIZE_TARGET_BITS} must be specified for weight quantization group {name}"
|
||||
group_dict[WEIGHT_QUANTIZATION_PERIOD] = get_scalar_param(group_dict, WEIGHT_QUANTIZATION_PERIOD,
|
||||
WEIGHT_QUANTIZATION_PERIOD_DEFAULT)
|
||||
return group_dict
|
||||
|
||||
for k, v in sub_param_dict.items():
|
||||
output[k] = {}
|
||||
output[k][DIFFERENT_GROUPS_PARAMETERS] = get_params(k, sub_param_dict[k][DIFFERENT_GROUPS_PARAMETERS])
|
||||
output[k][DIFFERENT_GROUPS_MODULE_SCOPE] = get_scalar_param(sub_param_dict[k], DIFFERENT_GROUPS_MODULE_SCOPE,
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT)
|
||||
output[k][DIFFERENT_GROUPS_RELATED_MODULE_SCOPE] = get_scalar_param(
|
||||
sub_param_dict[k], DIFFERENT_GROUPS_RELATED_MODULE_SCOPE, DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def get_activation_quantization(param_dict):
|
||||
output = {}
|
||||
if ACTIVATION_QUANTIZATION not in param_dict.keys():
|
||||
param_dict[ACTIVATION_QUANTIZATION] = {SHARED_PARAMETERS: {}, DIFFERENT_GROUPS: {}}
|
||||
sub_param_dict = param_dict[ACTIVATION_QUANTIZATION]
|
||||
# shared parameters
|
||||
output[SHARED_PARAMETERS] = get_activation_quantization_shared_parameters(sub_param_dict)
|
||||
# each sub-groups
|
||||
if output[SHARED_PARAMETERS][ACTIVATION_QUANTIZATION_ENABLED]:
|
||||
assert DIFFERENT_GROUPS in sub_param_dict.keys(
|
||||
), f"Activation Quantization is enabled, {DIFFERENT_GROUPS} must be specified"
|
||||
output[DIFFERENT_GROUPS] = get_activation_quantization_different_groups(sub_param_dict)
|
||||
return output
|
||||
|
||||
|
||||
def get_activation_quantization_shared_parameters(param_dict):
|
||||
output = {}
|
||||
if SHARED_PARAMETERS in param_dict.keys():
|
||||
sub_param_dict = param_dict[SHARED_PARAMETERS]
|
||||
output[ACTIVATION_QUANTIZATION_ENABLED] = get_scalar_param(sub_param_dict, ACTIVATION_QUANTIZATION_ENABLED,
|
||||
ACTIVATION_QUANTIZATION_ENABLED_DEFAULT)
|
||||
output[ACTIVATION_QUANTIZE_TYPE] = get_scalar_param(sub_param_dict, ACTIVATION_QUANTIZE_TYPE,
|
||||
ACTIVATION_QUANTIZE_TYPE_DEFAULT)
|
||||
assert output[ACTIVATION_QUANTIZE_TYPE] in [
|
||||
ACTIVATION_QUANTIZE_SYMMETRIC, ACTIVATION_QUANTIZE_ASYMMETRIC
|
||||
], f"Invalid activation quantize type. Supported types: [{ACTIVATION_QUANTIZE_SYMMETRIC}, {ACTIVATION_QUANTIZE_ASYMMETRIC}]"
|
||||
output[ACTIVATION_QUANTIZE_RANGE] = get_scalar_param(sub_param_dict, ACTIVATION_QUANTIZE_RANGE,
|
||||
ACTIVATION_QUANTIZE_RANGE_DEFAULT)
|
||||
assert output[ACTIVATION_QUANTIZE_RANGE] in [
|
||||
ACTIVATION_QUANTIZE_RANGE_DYNAMIC, ACTIVATION_QUANTIZE_RANGE_STATIC
|
||||
], f"Invalid activation quantize range calibration. Supported types: [{ACTIVATION_QUANTIZE_RANGE_DYNAMIC}, {ACTIVATION_QUANTIZE_RANGE_STATIC}]"
|
||||
output[ACTIVATION_QUANTIZE_SCHEDULE_OFFSET] = get_scalar_param(sub_param_dict,
|
||||
ACTIVATION_QUANTIZE_SCHEDULE_OFFSET,
|
||||
ACTIVATION_QUANTIZE_SCHEDULE_OFFSET_DEFAULT)
|
||||
else:
|
||||
output[ACTIVATION_QUANTIZATION_ENABLED] = ACTIVATION_QUANTIZATION_ENABLED_DEFAULT
|
||||
output[ACTIVATION_QUANTIZE_TYPE] = ACTIVATION_QUANTIZE_TYPE_DEFAULT
|
||||
output[ACTIVATION_QUANTIZE_RANGE] = ACTIVATION_QUANTIZE_RANGE_DEFAULT
|
||||
output[ACTIVATION_QUANTIZE_SCHEDULE_OFFSET] = ACTIVATION_QUANTIZE_SCHEDULE_OFFSET_DEFAULT
|
||||
return output
|
||||
|
||||
|
||||
def get_activation_quantization_different_groups(param_dict):
|
||||
output = {}
|
||||
sub_param_dict = param_dict[DIFFERENT_GROUPS]
|
||||
|
||||
def get_params(name, group_dict):
|
||||
assert ACTIVATION_QUANTIZE_BITS in group_dict.keys(
|
||||
), f"{ACTIVATION_QUANTIZE_BITS} must be specified for activation quantization group {name}"
|
||||
return group_dict
|
||||
|
||||
for k, v in sub_param_dict.items():
|
||||
output[k] = {}
|
||||
output[k][DIFFERENT_GROUPS_PARAMETERS] = get_params(k, sub_param_dict[k][DIFFERENT_GROUPS_PARAMETERS])
|
||||
output[k][DIFFERENT_GROUPS_MODULE_SCOPE] = get_scalar_param(sub_param_dict[k], DIFFERENT_GROUPS_MODULE_SCOPE,
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT)
|
||||
output[k][DIFFERENT_GROUPS_RELATED_MODULE_SCOPE] = get_scalar_param(
|
||||
sub_param_dict[k], DIFFERENT_GROUPS_RELATED_MODULE_SCOPE, DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def get_sparse_pruning(param_dict):
|
||||
output = {}
|
||||
if SPARSE_PRUNING not in param_dict.keys():
|
||||
param_dict[SPARSE_PRUNING] = {SHARED_PARAMETERS: {}, DIFFERENT_GROUPS: {}}
|
||||
sub_param_dict = param_dict[SPARSE_PRUNING]
|
||||
# shared parameters
|
||||
output[SHARED_PARAMETERS] = get_sparse_pruning_shared_parameters(sub_param_dict)
|
||||
# each sub-groups
|
||||
if output[SHARED_PARAMETERS][SPARSE_PRUNING_ENABLED] and output[SHARED_PARAMETERS][
|
||||
SPARSE_PRUNING_METHOD] != SPARSE_PRUNING_METHOD_SNIP_MOMENTUM:
|
||||
assert DIFFERENT_GROUPS in sub_param_dict.keys(
|
||||
), f"Sparse Pruning is enabled and not snip_momentum method, {DIFFERENT_GROUPS} must be specified"
|
||||
output[DIFFERENT_GROUPS] = get_sparse_pruning_different_groups(sub_param_dict)
|
||||
return output
|
||||
|
||||
|
||||
def get_sparse_pruning_shared_parameters(param_dict):
|
||||
output = {}
|
||||
|
||||
if SHARED_PARAMETERS in param_dict.keys():
|
||||
sub_param_dict = param_dict[SHARED_PARAMETERS]
|
||||
output[SPARSE_PRUNING_ENABLED] = get_scalar_param(sub_param_dict, SPARSE_PRUNING_ENABLED,
|
||||
SPARSE_PRUNING_ENABLED_DEFAULT)
|
||||
output[SPARSE_PRUNING_METHOD] = get_scalar_param(sub_param_dict, SPARSE_PRUNING_METHOD,
|
||||
SPARSE_PRUNING_METHOD_DEFAULT)
|
||||
assert output[SPARSE_PRUNING_METHOD] in [
|
||||
SPARSE_PRUNING_METHOD_L1, SPARSE_PRUNING_METHOD_TOPK, SPARSE_PRUNING_METHOD_SNIP_MOMENTUM
|
||||
], f"Invalid sparse pruning method. Supported types: [{SPARSE_PRUNING_METHOD_L1}, {SPARSE_PRUNING_METHOD_TOPK}, {SPARSE_PRUNING_METHOD_SNIP_MOMENTUM}]"
|
||||
output[SPARSE_PRUNING_SCHEDULE_OFFSET] = get_scalar_param(sub_param_dict, SPARSE_PRUNING_SCHEDULE_OFFSET,
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_DEFAULT)
|
||||
if output[SPARSE_PRUNING_METHOD] == SPARSE_PRUNING_METHOD_SNIP_MOMENTUM:
|
||||
output[SPARSE_PRUNING_BLOCK_PATTERN] = get_scalar_param(sub_param_dict, SPARSE_PRUNING_BLOCK_PATTERN,
|
||||
SPARSE_PRUNING_BLOCK_PATTERN_DEFAULT)
|
||||
output[SPARSE_PRUNING_DENSE_RATIO] = get_scalar_param(sub_param_dict, SPARSE_PRUNING_DENSE_RATIO,
|
||||
SPARSE_PRUNING_DENSE_RATIO_DEFAULT)
|
||||
assert output[SPARSE_PRUNING_DENSE_RATIO] > 0 and output[
|
||||
SPARSE_PRUNING_DENSE_RATIO] < 1, "Invalid dense_ratio value. Must be less than 1"
|
||||
output[SPARSE_PRUNING_SCHEDULE_OFFSET_STRIDE] = get_scalar_param(
|
||||
sub_param_dict, SPARSE_PRUNING_SCHEDULE_OFFSET_STRIDE, SPARSE_PRUNING_SCHEDULE_OFFSET_STRIDE_DEFAULT)
|
||||
output[SPARSE_PRUNING_EXCLUDED_MODULES] = get_list_param(sub_param_dict, SPARSE_PRUNING_EXCLUDED_MODULES,
|
||||
SPARSE_PRUNING_EXCLUDED_MODULES_DEFAULT)
|
||||
output[SPARSE_PRUNING_SCHEDULE_OFFSET_END] = get_scalar_param(sub_param_dict,
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_END,
|
||||
output[SPARSE_PRUNING_SCHEDULE_OFFSET])
|
||||
assert output[SPARSE_PRUNING_SCHEDULE_OFFSET] <= output[
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_END], "Invalid schedule_offset and schedule_offset_end values"
|
||||
else:
|
||||
output[SPARSE_PRUNING_ENABLED] = SPARSE_PRUNING_ENABLED_DEFAULT
|
||||
output[SPARSE_PRUNING_METHOD] = SPARSE_PRUNING_METHOD_DEFAULT
|
||||
output[SPARSE_PRUNING_SCHEDULE_OFFSET] = SPARSE_PRUNING_SCHEDULE_OFFSET_DEFAULT
|
||||
return output
|
||||
|
||||
|
||||
def get_sparse_pruning_different_groups(param_dict):
|
||||
output = {}
|
||||
sub_param_dict = param_dict[DIFFERENT_GROUPS]
|
||||
|
||||
def get_params(name, group_dict):
|
||||
assert SPARSE_PRUNING_DENSE_RATIO in group_dict.keys(
|
||||
), f"{SPARSE_PRUNING_DENSE_RATIO} must be specified for sparse pruning group {name}"
|
||||
return group_dict
|
||||
|
||||
for k, v in sub_param_dict.items():
|
||||
output[k] = {}
|
||||
output[k][DIFFERENT_GROUPS_PARAMETERS] = get_params(k, sub_param_dict[k][DIFFERENT_GROUPS_PARAMETERS])
|
||||
output[k][DIFFERENT_GROUPS_MODULE_SCOPE] = get_scalar_param(sub_param_dict[k], DIFFERENT_GROUPS_MODULE_SCOPE,
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT)
|
||||
output[k][DIFFERENT_GROUPS_RELATED_MODULE_SCOPE] = get_scalar_param(
|
||||
sub_param_dict[k], DIFFERENT_GROUPS_RELATED_MODULE_SCOPE, DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def get_row_pruning(param_dict):
|
||||
output = {}
|
||||
if ROW_PRUNING not in param_dict.keys():
|
||||
param_dict[ROW_PRUNING] = {SHARED_PARAMETERS: {}, DIFFERENT_GROUPS: {}}
|
||||
sub_param_dict = param_dict[ROW_PRUNING]
|
||||
# shared parameters
|
||||
output[SHARED_PARAMETERS] = get_row_pruning_shared_parameters(sub_param_dict)
|
||||
# each sub-groups
|
||||
if output[SHARED_PARAMETERS][ROW_PRUNING_ENABLED]:
|
||||
assert DIFFERENT_GROUPS in sub_param_dict.keys(
|
||||
), f"Row Pruning is enabled, {DIFFERENT_GROUPS} must be specified"
|
||||
output[DIFFERENT_GROUPS] = get_row_pruning_different_groups(sub_param_dict)
|
||||
return output
|
||||
|
||||
|
||||
def get_row_pruning_shared_parameters(param_dict):
|
||||
output = {}
|
||||
if SHARED_PARAMETERS in param_dict.keys():
|
||||
sub_param_dict = param_dict[SHARED_PARAMETERS]
|
||||
output[ROW_PRUNING_ENABLED] = get_scalar_param(sub_param_dict, ROW_PRUNING_ENABLED,
|
||||
ROW_PRUNING_ENABLED_DEFAULT)
|
||||
output[ROW_PRUNING_METHOD] = get_scalar_param(sub_param_dict, ROW_PRUNING_METHOD, ROW_PRUNING_METHOD_DEFAULT)
|
||||
assert output[ROW_PRUNING_METHOD] in [
|
||||
ROW_PRUNING_METHOD_L1, ROW_PRUNING_METHOD_TOPK
|
||||
], f"Invalid row pruning method. Supported types: [{ROW_PRUNING_METHOD_L1}, {ROW_PRUNING_METHOD_TOPK}]"
|
||||
output[ROW_PRUNING_SCHEDULE_OFFSET] = get_scalar_param(sub_param_dict, ROW_PRUNING_SCHEDULE_OFFSET,
|
||||
ROW_PRUNING_SCHEDULE_OFFSET_DEFAULT)
|
||||
else:
|
||||
output[ROW_PRUNING_ENABLED] = ROW_PRUNING_ENABLED_DEFAULT
|
||||
output[ROW_PRUNING_METHOD] = ROW_PRUNING_METHOD_DEFAULT
|
||||
output[ROW_PRUNING_SCHEDULE_OFFSET] = ROW_PRUNING_SCHEDULE_OFFSET_DEFAULT
|
||||
return output
|
||||
|
||||
|
||||
def get_row_pruning_different_groups(param_dict):
|
||||
output = {}
|
||||
sub_param_dict = param_dict[DIFFERENT_GROUPS]
|
||||
|
||||
def get_params(name, group_dict):
|
||||
assert ROW_PRUNING_DENSE_RATIO in group_dict.keys(
|
||||
), f"{ROW_PRUNING_DENSE_RATIO} must be specified for row pruning group {name}"
|
||||
return group_dict
|
||||
|
||||
for k, v in sub_param_dict.items():
|
||||
output[k] = {}
|
||||
output[k][DIFFERENT_GROUPS_PARAMETERS] = get_params(k, sub_param_dict[k][DIFFERENT_GROUPS_PARAMETERS])
|
||||
output[k][DIFFERENT_GROUPS_MODULE_SCOPE] = get_scalar_param(sub_param_dict[k], DIFFERENT_GROUPS_MODULE_SCOPE,
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT)
|
||||
output[k][DIFFERENT_GROUPS_RELATED_MODULE_SCOPE] = get_scalar_param(
|
||||
sub_param_dict[k], DIFFERENT_GROUPS_RELATED_MODULE_SCOPE, DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT)
|
||||
return output
|
||||
|
||||
|
||||
def get_head_pruning(param_dict):
|
||||
output = {}
|
||||
if HEAD_PRUNING not in param_dict.keys():
|
||||
param_dict[HEAD_PRUNING] = {SHARED_PARAMETERS: {}, DIFFERENT_GROUPS: {}}
|
||||
sub_param_dict = param_dict[HEAD_PRUNING]
|
||||
# shared parameters
|
||||
output[SHARED_PARAMETERS] = get_head_pruning_shared_parameters(sub_param_dict)
|
||||
# each sub-groups
|
||||
if output[SHARED_PARAMETERS][HEAD_PRUNING_ENABLED]:
|
||||
assert DIFFERENT_GROUPS in sub_param_dict.keys(
|
||||
), f"Head Pruning is enabled, {DIFFERENT_GROUPS} must be specified"
|
||||
output[DIFFERENT_GROUPS] = get_head_pruning_different_groups(sub_param_dict)
|
||||
return output
|
||||
|
||||
|
||||
def get_head_pruning_shared_parameters(param_dict):
|
||||
output = {}
|
||||
if SHARED_PARAMETERS in param_dict.keys():
|
||||
sub_param_dict = param_dict[SHARED_PARAMETERS]
|
||||
output[HEAD_PRUNING_ENABLED] = get_scalar_param(sub_param_dict, HEAD_PRUNING_ENABLED,
|
||||
HEAD_PRUNING_ENABLED_DEFAULT)
|
||||
output[HEAD_PRUNING_METHOD] = get_scalar_param(sub_param_dict, HEAD_PRUNING_METHOD,
|
||||
HEAD_PRUNING_METHOD_DEFAULT)
|
||||
assert output[HEAD_PRUNING_METHOD] in [
|
||||
HEAD_PRUNING_METHOD_L1, HEAD_PRUNING_METHOD_TOPK
|
||||
], f"Invalid head pruning method. Supported types: [{HEAD_PRUNING_METHOD_L1}, {HEAD_PRUNING_METHOD_TOPK}]"
|
||||
output[HEAD_PRUNING_SCHEDULE_OFFSET] = get_scalar_param(sub_param_dict, HEAD_PRUNING_SCHEDULE_OFFSET,
|
||||
HEAD_PRUNING_SCHEDULE_OFFSET_DEFAULT)
|
||||
if output[HEAD_PRUNING_ENABLED]:
|
||||
assert HEAD_PRUNING_NUM_HEADS in sub_param_dict.keys(
|
||||
), f"{HEAD_PRUNING_NUM_HEADS} must be specified for head pruning"
|
||||
output[HEAD_PRUNING_NUM_HEADS] = sub_param_dict[HEAD_PRUNING_NUM_HEADS]
|
||||
else:
|
||||
output[HEAD_PRUNING_ENABLED] = HEAD_PRUNING_ENABLED_DEFAULT
|
||||
output[HEAD_PRUNING_METHOD] = HEAD_PRUNING_METHOD_DEFAULT
|
||||
output[HEAD_PRUNING_SCHEDULE_OFFSET] = HEAD_PRUNING_SCHEDULE_OFFSET_DEFAULT
|
||||
return output
|
||||
|
||||
|
||||
def get_head_pruning_different_groups(param_dict):
|
||||
output = {}
|
||||
sub_param_dict = param_dict[DIFFERENT_GROUPS]
|
||||
|
||||
def get_params(name, group_dict):
|
||||
assert HEAD_PRUNING_DENSE_RATIO in group_dict.keys(
|
||||
), f"dense_ratio must be specified for head pruning group {name}"
|
||||
return group_dict
|
||||
|
||||
for k, v in sub_param_dict.items():
|
||||
output[k] = {}
|
||||
output[k][DIFFERENT_GROUPS_PARAMETERS] = get_params(k, sub_param_dict[k][DIFFERENT_GROUPS_PARAMETERS])
|
||||
output[k][DIFFERENT_GROUPS_MODULE_SCOPE] = get_scalar_param(sub_param_dict[k], DIFFERENT_GROUPS_MODULE_SCOPE,
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT)
|
||||
output[k][DIFFERENT_GROUPS_RELATED_MODULE_SCOPE] = get_scalar_param(
|
||||
sub_param_dict[k], DIFFERENT_GROUPS_RELATED_MODULE_SCOPE, DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT)
|
||||
return output
|
||||
|
||||
|
||||
def get_channel_pruning(param_dict):
|
||||
output = {}
|
||||
if CHANNEL_PRUNING not in param_dict.keys():
|
||||
param_dict[CHANNEL_PRUNING] = {SHARED_PARAMETERS: {}, DIFFERENT_GROUPS: {}}
|
||||
sub_param_dict = param_dict[CHANNEL_PRUNING]
|
||||
# shared parameters
|
||||
output[SHARED_PARAMETERS] = get_channel_pruning_shared_parameters(sub_param_dict)
|
||||
# each sub-groups
|
||||
if output[SHARED_PARAMETERS][CHANNEL_PRUNING_ENABLED]:
|
||||
assert DIFFERENT_GROUPS in sub_param_dict.keys(
|
||||
), f"Sparse Pruning is enabled, {DIFFERENT_GROUPS} must be specified"
|
||||
output[DIFFERENT_GROUPS] = get_channel_pruning_different_groups(sub_param_dict)
|
||||
return output
|
||||
|
||||
|
||||
def get_channel_pruning_shared_parameters(param_dict):
|
||||
output = {}
|
||||
if SHARED_PARAMETERS in param_dict.keys():
|
||||
sub_param_dict = param_dict[SHARED_PARAMETERS]
|
||||
output[CHANNEL_PRUNING_ENABLED] = get_scalar_param(sub_param_dict, CHANNEL_PRUNING_ENABLED,
|
||||
CHANNEL_PRUNING_ENABLED_DEFAULT)
|
||||
output[CHANNEL_PRUNING_METHOD] = get_scalar_param(sub_param_dict, CHANNEL_PRUNING_METHOD,
|
||||
CHANNEL_PRUNING_METHOD_DEFAULT)
|
||||
assert output[CHANNEL_PRUNING_METHOD] in [
|
||||
CHANNEL_PRUNING_METHOD_L1, CHANNEL_PRUNING_METHOD_TOPK
|
||||
], f"Invalid channel pruning method. Supported types: [{CHANNEL_PRUNING_METHOD_L1}, {CHANNEL_PRUNING_METHOD_TOPK}]"
|
||||
output[CHANNEL_PRUNING_SCHEDULE_OFFSET] = get_scalar_param(sub_param_dict, CHANNEL_PRUNING_SCHEDULE_OFFSET,
|
||||
CHANNEL_PRUNING_SCHEDULE_OFFSET_DEFAULT)
|
||||
else:
|
||||
output[CHANNEL_PRUNING_ENABLED] = CHANNEL_PRUNING_ENABLED_DEFAULT
|
||||
output[CHANNEL_PRUNING_METHOD] = CHANNEL_PRUNING_METHOD_DEFAULT
|
||||
output[CHANNEL_PRUNING_SCHEDULE_OFFSET] = CHANNEL_PRUNING_SCHEDULE_OFFSET_DEFAULT
|
||||
return output
|
||||
|
||||
|
||||
def get_channel_pruning_different_groups(param_dict):
|
||||
output = {}
|
||||
sub_param_dict = param_dict[DIFFERENT_GROUPS]
|
||||
|
||||
def get_params(name, group_dict):
|
||||
assert CHANNEL_PRUNING_DENSE_RATIO in group_dict.keys(
|
||||
), f"{CHANNEL_PRUNING_DENSE_RATIO} must be specified for channel pruning group {name}"
|
||||
return group_dict
|
||||
|
||||
for k, v in sub_param_dict.items():
|
||||
output[k] = {}
|
||||
output[k][DIFFERENT_GROUPS_PARAMETERS] = get_params(k, sub_param_dict[k][DIFFERENT_GROUPS_PARAMETERS])
|
||||
output[k][DIFFERENT_GROUPS_MODULE_SCOPE] = get_scalar_param(sub_param_dict[k], DIFFERENT_GROUPS_MODULE_SCOPE,
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT)
|
||||
output[k][DIFFERENT_GROUPS_RELATED_MODULE_SCOPE] = get_scalar_param(
|
||||
sub_param_dict[k], DIFFERENT_GROUPS_RELATED_MODULE_SCOPE, DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT)
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,188 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
#########################################
|
||||
# Compression Methods
|
||||
# It has several sub-components
|
||||
# #########################################
|
||||
COMPRESSION_TRAINING = "compression_training"
|
||||
SHARED_PARAMETERS = "shared_parameters"
|
||||
DIFFERENT_GROUPS = "different_groups"
|
||||
TECHNIQUE_ENABLED = "enabled"
|
||||
TECHNIQUE_SCHEDULE_OFFSET = "schedule_offset"
|
||||
TECHNIQUE_SCHEDULE_OFFSET_END = "schedule_offset_end"
|
||||
DIFFERENT_GROUPS_PARAMETERS = "params"
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE = "modules"
|
||||
DIFFERENT_GROUPS_MODULE_SCOPE_DEFAULT = "*"
|
||||
DIFFERENT_GROUPS_RELATED_MODULE_SCOPE = "related_modules"
|
||||
DIFFERENT_GROUPS_RELATED_MODULE_SCOPE_DEFAULT = None
|
||||
# COMPRESSION_TRAINING_ENABLED = "enabled"
|
||||
# COMPRESSION_TRAINING_ENABLED_DEFAULT = False
|
||||
|
||||
####
|
||||
# Layer Reduction
|
||||
####
|
||||
LAYER_REDUCTION = "layer_reduction"
|
||||
LAYER_REDUCTION_ENABLED = "enabled"
|
||||
LAYER_REDUCTION_ENABLED_DEFAULT = False
|
||||
KEEP_NUMBER_LAYER = "keep_number_layer"
|
||||
MODULE_NAME_PREFIX = "module_name_prefix"
|
||||
TEACHER_LAYER = "teacher_layer"
|
||||
OTHER_MODULE_NAME = "other_module_name"
|
||||
|
||||
####
|
||||
# Weight Quantization
|
||||
####
|
||||
WEIGHT_QUANTIZATION = "weight_quantization"
|
||||
|
||||
WEIGHT_QUANTIZATION_PERIOD = "quantization_period"
|
||||
WEIGHT_QUANTIZATION_PERIOD_DEFAULT = 1
|
||||
|
||||
WEIGHT_QUANTIZE_IN_FORWARD_ENABLED = "quantize_weight_in_forward"
|
||||
WEIGHT_QUANTIZE_IN_FORWARD_ENABLED_DEFAULT = False
|
||||
|
||||
WEIGHT_QUANTIZE_ENABLED = TECHNIQUE_ENABLED
|
||||
WEIGHT_QUANTIZE_ENABLED_DEFAULT = False
|
||||
|
||||
WEIGHT_QUANTIZE_KERNEL = "quantizer_kernel"
|
||||
WEIGHT_QUANTIZE_KERNEL_DEFAULT = False
|
||||
|
||||
WEIGHT_QUANTIZE_SCHEDULE_OFFSET = TECHNIQUE_SCHEDULE_OFFSET
|
||||
WEIGHT_QUANTIZE_SCHEDULE_OFFSET_DEFAULT = 0
|
||||
|
||||
WEIGHT_QUANTIZE_GROUPS = "quantize_groups"
|
||||
WEIGHT_QUANTIZE_GROUPS_DEFAULT = 1
|
||||
|
||||
WEIGHT_QUANTIZE_VERBOSE = "quantize_verbose"
|
||||
WEIGHT_QUANTIZE_VERBOSE_DEFAULT = False
|
||||
|
||||
WEIGHT_QUANTIZE_TYPE = "quantization_type"
|
||||
WEIGHT_QUANTIZE_TYPE_DEFAULT = "symmetric"
|
||||
WEIGHT_QUANTIZE_SYMMETRIC = "symmetric"
|
||||
WEIGHT_QUANTIZE_ASYMMETRIC = "asymmetric"
|
||||
|
||||
WEIGHT_QUANTIZE_ROUNDING = "rounding"
|
||||
WEIGHT_QUANTIZE_ROUNDING_DEFAULT = "nearest"
|
||||
WEIGHT_QUANTIZE_STOCHASTIC_ROUNDING = "stochastic"
|
||||
WEIGHT_QUANTIZE_NEAREST_ROUNDING = "nearest"
|
||||
# maybe deleted for a cleaner version
|
||||
WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE = "fp16_mixed_quantize"
|
||||
|
||||
WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE_ENABLED = "enabled"
|
||||
WEIGHT_QUANTIZE_FP16_MIXED_QUANTIZE_ENABLED_DEFAULT = False
|
||||
|
||||
WEIGHT_QUANTIZE_CHANGE_RATIO = "quantize_change_ratio"
|
||||
WEIGHT_QUANTIZE_CHANGE_RATIO_DEFAULT = 0.001
|
||||
|
||||
WEIGHT_QUANTIZE_START_BITS = "start_bits"
|
||||
WEIGHT_QUANTIZE_TARGET_BITS = "target_bits"
|
||||
###
|
||||
# Activation Quantization
|
||||
###
|
||||
ACTIVATION_QUANTIZATION = "activation_quantization"
|
||||
|
||||
ACTIVATION_QUANTIZATION_ENABLED = TECHNIQUE_ENABLED
|
||||
ACTIVATION_QUANTIZATION_ENABLED_DEFAULT = False
|
||||
|
||||
ACTIVATION_QUANTIZE_SCHEDULE_OFFSET = TECHNIQUE_SCHEDULE_OFFSET
|
||||
ACTIVATION_QUANTIZE_SCHEDULE_OFFSET_DEFAULT = 1000
|
||||
|
||||
ACTIVATION_QUANTIZE_TYPE = "quantization_type"
|
||||
ACTIVATION_QUANTIZE_TYPE_DEFAULT = "symmetric"
|
||||
ACTIVATION_QUANTIZE_SYMMETRIC = "symmetric"
|
||||
ACTIVATION_QUANTIZE_ASYMMETRIC = "asymmetric"
|
||||
|
||||
ACTIVATION_QUANTIZE_RANGE = 'range_calibration'
|
||||
ACTIVATION_QUANTIZE_RANGE_DEFAULT = 'dynamic'
|
||||
ACTIVATION_QUANTIZE_RANGE_STATIC = 'static'
|
||||
ACTIVATION_QUANTIZE_RANGE_DYNAMIC = 'dynamic'
|
||||
|
||||
ACTIVATION_QUANTIZE_BITS = "bits"
|
||||
###
|
||||
# Sparse Pruning
|
||||
###
|
||||
SPARSE_PRUNING = "sparse_pruning"
|
||||
|
||||
SPARSE_PRUNING_ENABLED = TECHNIQUE_ENABLED
|
||||
SPARSE_PRUNING_ENABLED_DEFAULT = False
|
||||
|
||||
SPARSE_PRUNING_METHOD = "method"
|
||||
SPARSE_PRUNING_METHOD_DEFAULT = "l1"
|
||||
SPARSE_PRUNING_METHOD_L1 = "l1"
|
||||
SPARSE_PRUNING_METHOD_TOPK = "topk"
|
||||
SPARSE_PRUNING_METHOD_SNIP_MOMENTUM = "snip_momentum"
|
||||
|
||||
SPARSE_PRUNING_BLOCK_PATTERN = "block_pattern"
|
||||
SPARSE_PRUNING_BLOCK_PATTERN_DEFAULT = "4x1"
|
||||
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_STRIDE = "schedule_offset_stride"
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_STRIDE_DEFAULT = 1
|
||||
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET = TECHNIQUE_SCHEDULE_OFFSET
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_DEFAULT = 1000
|
||||
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_END = TECHNIQUE_SCHEDULE_OFFSET_END
|
||||
SPARSE_PRUNING_SCHEDULE_OFFSET_END_DEFAULT = SPARSE_PRUNING_SCHEDULE_OFFSET_DEFAULT
|
||||
|
||||
SPARSE_PRUNING_DENSE_RATIO = "dense_ratio"
|
||||
SPARSE_PRUNING_DENSE_RATIO_DEFAULT = 0.1
|
||||
|
||||
SPARSE_PRUNING_EXCLUDED_MODULES = "excluded_modules"
|
||||
SPARSE_PRUNING_EXCLUDED_MODULES_DEFAULT = []
|
||||
###
|
||||
# Row Pruning
|
||||
###
|
||||
ROW_PRUNING = "row_pruning"
|
||||
|
||||
ROW_PRUNING_ENABLED = TECHNIQUE_ENABLED
|
||||
ROW_PRUNING_ENABLED_DEFAULT = False
|
||||
|
||||
ROW_PRUNING_METHOD = "method"
|
||||
ROW_PRUNING_METHOD_DEFAULT = "l1"
|
||||
ROW_PRUNING_METHOD_L1 = "l1"
|
||||
ROW_PRUNING_METHOD_TOPK = "topk"
|
||||
|
||||
ROW_PRUNING_SCHEDULE_OFFSET = TECHNIQUE_SCHEDULE_OFFSET
|
||||
ROW_PRUNING_SCHEDULE_OFFSET_DEFAULT = 1000
|
||||
|
||||
ROW_PRUNING_DENSE_RATIO = "dense_ratio"
|
||||
|
||||
###
|
||||
# Head Pruning
|
||||
###
|
||||
HEAD_PRUNING = "head_pruning"
|
||||
|
||||
HEAD_PRUNING_ENABLED = TECHNIQUE_ENABLED
|
||||
HEAD_PRUNING_ENABLED_DEFAULT = False
|
||||
|
||||
HEAD_PRUNING_METHOD = "method"
|
||||
HEAD_PRUNING_METHOD_DEFAULT = "topk"
|
||||
HEAD_PRUNING_METHOD_L1 = "l1"
|
||||
HEAD_PRUNING_METHOD_TOPK = "topk"
|
||||
|
||||
HEAD_PRUNING_SCHEDULE_OFFSET = TECHNIQUE_SCHEDULE_OFFSET
|
||||
HEAD_PRUNING_SCHEDULE_OFFSET_DEFAULT = 1000
|
||||
|
||||
HEAD_PRUNING_NUM_HEADS = "num_heads"
|
||||
|
||||
HEAD_PRUNING_DENSE_RATIO = "dense_ratio"
|
||||
|
||||
###
|
||||
# Channel Pruning
|
||||
###
|
||||
CHANNEL_PRUNING = "channel_pruning"
|
||||
|
||||
CHANNEL_PRUNING_ENABLED = TECHNIQUE_ENABLED
|
||||
CHANNEL_PRUNING_ENABLED_DEFAULT = False
|
||||
|
||||
CHANNEL_PRUNING_METHOD = "method"
|
||||
CHANNEL_PRUNING_METHOD_DEFAULT = "l1"
|
||||
CHANNEL_PRUNING_METHOD_L1 = "l1"
|
||||
CHANNEL_PRUNING_METHOD_TOPK = "topk"
|
||||
|
||||
CHANNEL_PRUNING_SCHEDULE_OFFSET = TECHNIQUE_SCHEDULE_OFFSET
|
||||
CHANNEL_PRUNING_SCHEDULE_OFFSET_DEFAULT = 1000
|
||||
|
||||
CHANNEL_PRUNING_DENSE_RATIO = "dense_ratio"
|
||||
@@ -0,0 +1,322 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
from .basic_layer import Embedding_Compress, LinearLayer_Compress, Conv2dLayer_Compress, BNLayer_Compress, ColumnParallelLinear_Compress, RowParallelLinear_Compress
|
||||
from .constants import *
|
||||
from deepspeed.utils import logger
|
||||
|
||||
try:
|
||||
from neural_compressor.compression import pruner as nc_pruner
|
||||
except ImportError as e:
|
||||
nc_pruner = None
|
||||
|
||||
|
||||
def recursive_getattr(model, module_name):
|
||||
"""
|
||||
Recursively get the attribute of a module.
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to get the attribute from.
|
||||
module_name (`str`)
|
||||
The name of the module to get the attribute from.
|
||||
"""
|
||||
split_list = module_name.split('.')
|
||||
output = model
|
||||
for name in split_list:
|
||||
output = getattr(output, name)
|
||||
return output
|
||||
|
||||
|
||||
def recursive_setattr(model, module_name, module):
|
||||
"""
|
||||
Recursively set the attribute of a module.
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to set the attribute in.
|
||||
module_name (`str`)
|
||||
The name of the module to set the attribute in.
|
||||
module (`torch.nn.Module`)
|
||||
The module to set the attribute to.
|
||||
"""
|
||||
split_list = module_name.split('.')
|
||||
output = model
|
||||
for name in split_list[:-1]:
|
||||
output = getattr(output, name)
|
||||
output.__setattr__(split_list[-1], module)
|
||||
|
||||
|
||||
def module_replacement(model, module_name, compression_technique=None, mpu=None):
|
||||
"""
|
||||
Replace a module with a new module.
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to replace the module in.
|
||||
module_name (`str`)
|
||||
The name of the module to replace.
|
||||
compression_technique (`str`)
|
||||
The compression technique to use for the new module.
|
||||
"""
|
||||
|
||||
# Get the old module
|
||||
old_module = recursive_getattr(model, module_name)
|
||||
|
||||
need_bias = False
|
||||
if hasattr(old_module, 'bias') and old_module.bias is not None:
|
||||
need_bias = True
|
||||
|
||||
# Initialize the new module
|
||||
if isinstance(old_module, LinearLayer_Compress) or isinstance(old_module, torch.nn.Linear):
|
||||
if isinstance(old_module, LinearLayer_Compress):
|
||||
new_module = old_module
|
||||
else:
|
||||
new_module = LinearLayer_Compress(old_module.in_features, old_module.out_features,
|
||||
bias=need_bias).to(device=old_module.weight.device,
|
||||
dtype=old_module.weight.dtype)
|
||||
new_module.weight.data = old_module.weight.data
|
||||
if need_bias:
|
||||
new_module.bias.data = old_module.bias.data
|
||||
elif isinstance(old_module, Conv2dLayer_Compress) or isinstance(old_module, torch.nn.Conv2d):
|
||||
if isinstance(old_module, Conv2dLayer_Compress):
|
||||
new_module = old_module
|
||||
else:
|
||||
new_module = Conv2dLayer_Compress(old_module.in_channels, old_module.out_channels, old_module.kernel_size, old_module.stride, old_module.padding, \
|
||||
old_module.dilation, old_module.groups, need_bias, \
|
||||
old_module.padding_mode).to(device=old_module.weight.device, dtype=old_module.weight.dtype)
|
||||
new_module.weight.data = old_module.weight.data
|
||||
if need_bias:
|
||||
new_module.bias.data = old_module.bias.data
|
||||
elif isinstance(old_module, torch.nn.BatchNorm2d):
|
||||
new_module = BNLayer_Compress(old_module.num_features, old_module.eps, old_module.momentum, old_module.affine,
|
||||
old_module.track_running_stats).to(old_module.weight.device,
|
||||
old_module.weight.dtype)
|
||||
new_module.weight.data = old_module.weight.data
|
||||
if need_bias:
|
||||
new_module.bias.data = old_module.bias.data
|
||||
new_module.running_mean.data = old_module.running_mean.data
|
||||
new_module.running_var.data = old_module.running_var.data
|
||||
elif isinstance(old_module, Embedding_Compress) or isinstance(old_module, torch.nn.Embedding):
|
||||
if isinstance(old_module, Embedding_Compress):
|
||||
new_module = old_module
|
||||
else:
|
||||
new_module = Embedding_Compress(old_module.num_embeddings, old_module.embedding_dim, old_module.padding_idx, old_module.max_norm, old_module.norm_type, \
|
||||
old_module.scale_grad_by_freq, old_module.sparse).to(device=old_module.weight.device, dtype=old_module.weight.dtype)
|
||||
new_module.weight.data = old_module.weight.data
|
||||
elif mpu is not None and (isinstance(old_module, ColumnParallelLinear_Compress)
|
||||
or isinstance(old_module, mpu.ColumnParallelLinear)):
|
||||
if isinstance(old_module, ColumnParallelLinear_Compress):
|
||||
new_module = old_module
|
||||
else:
|
||||
new_module = ColumnParallelLinear_Compress(mpu,
|
||||
old_module.input_size,
|
||||
old_module.output_size,
|
||||
gather_output=old_module.gather_output,
|
||||
skip_bias_add=old_module.skip_bias_add,
|
||||
bias=need_bias).to(device=old_module.weight.device,
|
||||
dtype=old_module.weight.dtype)
|
||||
new_module.weight.data = old_module.weight.data
|
||||
if need_bias:
|
||||
new_module.bias.data = old_module.bias.data
|
||||
elif mpu is not None and (isinstance(old_module, RowParallelLinear_Compress)
|
||||
or isinstance(old_module, mpu.RowParallelLinear)):
|
||||
if isinstance(old_module, RowParallelLinear_Compress):
|
||||
new_module = old_module
|
||||
else:
|
||||
new_module = RowParallelLinear_Compress(mpu,
|
||||
old_module.input_size,
|
||||
old_module.output_size,
|
||||
input_is_parallel=old_module.input_is_parallel,
|
||||
skip_bias_add=old_module.skip_bias_add,
|
||||
bias=need_bias).to(device=old_module.weight.device,
|
||||
dtype=old_module.weight.dtype)
|
||||
new_module.weight.data = old_module.weight.data
|
||||
if need_bias:
|
||||
new_module.bias.data = old_module.bias.data
|
||||
else:
|
||||
new_module = None
|
||||
|
||||
if compression_technique is not None and new_module is not None:
|
||||
for k, v in compression_technique.items():
|
||||
if k == SPARSE_PRUNING:
|
||||
if v[SPARSE_PRUNING_ENABLED]:
|
||||
new_module.enable_sparse_pruning(v[SPARSE_PRUNING_DENSE_RATIO], v[SPARSE_PRUNING_METHOD])
|
||||
elif k == ROW_PRUNING:
|
||||
if v[ROW_PRUNING_ENABLED]:
|
||||
new_module.enable_row_pruning(v[ROW_PRUNING_DENSE_RATIO], v[ROW_PRUNING_METHOD])
|
||||
elif k == HEAD_PRUNING:
|
||||
if v[HEAD_PRUNING_ENABLED]:
|
||||
new_module.enable_head_pruning(v[HEAD_PRUNING_DENSE_RATIO], v[HEAD_PRUNING_METHOD],
|
||||
v[HEAD_PRUNING_NUM_HEADS])
|
||||
elif k == ACTIVATION_QUANTIZATION:
|
||||
if v[ACTIVATION_QUANTIZATION_ENABLED]:
|
||||
new_module.enable_activation_quantization(v[ACTIVATION_QUANTIZE_BITS], v[ACTIVATION_QUANTIZE_TYPE],
|
||||
v[ACTIVATION_QUANTIZE_RANGE])
|
||||
elif k == WEIGHT_QUANTIZATION:
|
||||
if v[WEIGHT_QUANTIZE_ENABLED]:
|
||||
new_module.enable_weight_quantization(v[WEIGHT_QUANTIZE_START_BITS],
|
||||
v[WEIGHT_QUANTIZE_TARGET_BITS],
|
||||
v[WEIGHT_QUANTIZATION_PERIOD],
|
||||
v[WEIGHT_QUANTIZE_IN_FORWARD_ENABLED],
|
||||
v[WEIGHT_QUANTIZE_TYPE], v[WEIGHT_QUANTIZE_GROUPS])
|
||||
elif k == CHANNEL_PRUNING:
|
||||
if v[CHANNEL_PRUNING_ENABLED]:
|
||||
new_module.enable_channel_pruning(v[CHANNEL_PRUNING_DENSE_RATIO], v[CHANNEL_PRUNING_METHOD])
|
||||
else:
|
||||
raise NotImplementedError('Compression technique {} is not implemented'.format(k))
|
||||
|
||||
# Replace the old module with the new one
|
||||
recursive_setattr(model, module_name, new_module)
|
||||
|
||||
|
||||
def is_module_compressible(module, mpu=None):
|
||||
ret = isinstance(module, torch.nn.Linear) or \
|
||||
isinstance(module, torch.nn.Conv2d) or \
|
||||
isinstance(module, torch.nn.Embedding) or \
|
||||
isinstance(module, torch.nn.BatchNorm2d)
|
||||
|
||||
if mpu is not None:
|
||||
ret = ret or isinstance(module, mpu.RowParallelLinear) or isinstance(module, mpu.ColumnParallelLinear)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def compression_preparation(model, compression_technique_list, mpu):
|
||||
"""
|
||||
Prepare the compression techniques of a model.
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to prepare the compression techniques of.
|
||||
compression_technique_list (`list`)
|
||||
The list of compression techniques to prepare the model to.
|
||||
list[]
|
||||
"""
|
||||
# Here we first replace all module with our linear wrapper
|
||||
for module_name, module in model.named_modules():
|
||||
if is_module_compressible(module, mpu):
|
||||
module_replacement(model, module_name, mpu=mpu)
|
||||
for module_name_lists, _, compression_technique in compression_technique_list:
|
||||
for mnl in module_name_lists:
|
||||
for module_name in mnl:
|
||||
module_replacement(model, module_name, compression_technique)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def fix_compression(model, module_name, compression_technique, mask=None, dim_reduction=False):
|
||||
"""
|
||||
Fix the compression technique of a module.
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to fix the compression technique of.
|
||||
module_name (`str`)
|
||||
The name of the module to fix the compression technique of.
|
||||
compression_technique (`str`)
|
||||
The compression technique to fix the module to.
|
||||
"""
|
||||
# Here we can make things much simpler by just replacing the module
|
||||
module = recursive_getattr(model, module_name)
|
||||
for k, v in compression_technique.items():
|
||||
if k == WEIGHT_QUANTIZATION and v[WEIGHT_QUANTIZE_IN_FORWARD_ENABLED] and v[WEIGHT_QUANTIZE_ENABLED]:
|
||||
return module.fix_weight_quantization()
|
||||
elif k == SPARSE_PRUNING and v[SPARSE_PRUNING_ENABLED]:
|
||||
return module.fix_sparse_pruning_helper()
|
||||
elif k == ROW_PRUNING and (v[ROW_PRUNING_ENABLED] or mask is not None):
|
||||
return module.fix_row_col_pruning_helper(mask, dim_reduction=dim_reduction)
|
||||
elif k == HEAD_PRUNING and (v[HEAD_PRUNING_ENABLED] or mask is not None):
|
||||
return module.fix_head_pruning_helper(mask, v[HEAD_PRUNING_NUM_HEADS], dim_reduction=dim_reduction)
|
||||
elif k == CHANNEL_PRUNING and (v[CHANNEL_PRUNING_ENABLED] or mask is not None):
|
||||
return module.fix_channel_pruning_helper(mask, dim_reduction=dim_reduction)
|
||||
|
||||
|
||||
def convert_conv1d_to_linear(model, convert_type):
|
||||
'''
|
||||
This is a help function to convert conv1d to linear (e.g., convert GPT2 from HF)
|
||||
'''
|
||||
if hasattr(model, 'module'):
|
||||
c_model = model.module
|
||||
else:
|
||||
c_model = model
|
||||
|
||||
for name, module in c_model.named_modules():
|
||||
if isinstance(module, convert_type):
|
||||
old_module = recursive_getattr(c_model, name)
|
||||
new_module = torch.nn.Linear(old_module.weight.data.size(0),
|
||||
old_module.weight.data.size(1),
|
||||
bias=True if old_module.bias is not None else False)
|
||||
new_module.weight.data = old_module.weight.data.t().contiguous()
|
||||
if new_module.bias is not None:
|
||||
new_module.bias.data = old_module.bias.data.view(-1)
|
||||
|
||||
recursive_setattr(c_model, name, new_module)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def generate_pruners(config, model):
|
||||
"""Generate pruners.
|
||||
Args:
|
||||
config (`neural_compressor.WeightPruningConfig`)
|
||||
The object to the class WeightPruningConfig.
|
||||
model (`torch.nn.module`)
|
||||
The torch module object to be pruned.
|
||||
"""
|
||||
assert nc_pruner is not None, "please ensure the neural_compressor python package is installed by pip or conda if user wants to use snip_momentum sparse pruning"
|
||||
from nc_pruner.utils import process_config, parse_to_prune
|
||||
from nc_pruner.pruners import get_pruner
|
||||
assert isinstance(model, torch.nn.Module)
|
||||
pruners_info = process_config(config)
|
||||
pruners = []
|
||||
for info in pruners_info:
|
||||
modules = parse_to_prune(info, model)
|
||||
if modules == {}:
|
||||
logger.warning("one pruner hooks no layers, please have a check")
|
||||
|
||||
pruners.append(get_pruner(info, modules))
|
||||
info['modules'] = [key for key in modules.keys()]
|
||||
info['len_of_modules'] = len(info['modules'])
|
||||
logger.info(info)
|
||||
return pruners
|
||||
|
||||
|
||||
def register_on_step_begin(model):
|
||||
"""Mount on_step_begin to the model.
|
||||
Args:
|
||||
model (`torch.nn.module`)
|
||||
The torch module object to be pruned.
|
||||
"""
|
||||
|
||||
def hook(module, input):
|
||||
for pruner in module.pruners:
|
||||
pruner.on_step_begin(0)
|
||||
|
||||
hook_handle = model.register_forward_pre_hook(hook)
|
||||
return hook_handle
|
||||
|
||||
|
||||
def rewrite_optimizer_step(opt: torch.optim.Optimizer):
|
||||
"""Mount on_before/after_optimizer_step to the optimizer.
|
||||
Args:
|
||||
model (`torch.opt.Optimizer`)
|
||||
The torch optimizer object to be hooked.
|
||||
"""
|
||||
|
||||
def new_step(self, closure=None):
|
||||
if hasattr(self, "pruners"):
|
||||
for pruner in self.pruners:
|
||||
pruner.on_before_optimizer_step()
|
||||
|
||||
if closure is not None:
|
||||
res = self.orig_step(closure)
|
||||
else:
|
||||
res = self.orig_step()
|
||||
if hasattr(self, "pruners"):
|
||||
for pruner in self.pruners:
|
||||
pruner.on_after_optimizer_step()
|
||||
return res
|
||||
|
||||
opt.orig_step = opt.step
|
||||
import types
|
||||
opt.step = types.MethodType(new_step, opt)
|
||||
return opt
|
||||
@@ -0,0 +1,173 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .compress import get_module_name
|
||||
from .constants import *
|
||||
from .helper import recursive_getattr
|
||||
from deepspeed.utils import logger
|
||||
|
||||
|
||||
class compression_scheduler():
|
||||
'''
|
||||
Used to schedule different compression methods
|
||||
'''
|
||||
|
||||
def __init__(self, model, compression_config):
|
||||
self.model = model
|
||||
self.compression_config = compression_config
|
||||
self.make_init()
|
||||
self.training_steps = 0
|
||||
self.weight_quantization_enabled = False
|
||||
|
||||
self.verbose = {
|
||||
WEIGHT_QUANTIZATION: False,
|
||||
ACTIVATION_QUANTIZATION: False,
|
||||
SPARSE_PRUNING: False,
|
||||
HEAD_PRUNING: False,
|
||||
ROW_PRUNING: False,
|
||||
CHANNEL_PRUNING: False
|
||||
}
|
||||
|
||||
def make_init(self):
|
||||
self.different_compression_methods = {}
|
||||
for method, method_content in self.compression_config.items():
|
||||
if LAYER_REDUCTION in method:
|
||||
continue
|
||||
self.different_compression_methods[method] = {
|
||||
TECHNIQUE_ENABLED: False,
|
||||
SHARED_PARAMETERS: None,
|
||||
DIFFERENT_GROUPS: []
|
||||
}
|
||||
exist_module_name = set()
|
||||
shared_parameters = method_content[SHARED_PARAMETERS]
|
||||
self.different_compression_methods[method][TECHNIQUE_ENABLED] = shared_parameters[TECHNIQUE_ENABLED]
|
||||
self.different_compression_methods[method][SHARED_PARAMETERS] = shared_parameters
|
||||
|
||||
for group_name, method_parameters in method_content[DIFFERENT_GROUPS].items():
|
||||
module_name_list = []
|
||||
for key_word in method_parameters[DIFFERENT_GROUPS_MODULE_SCOPE]:
|
||||
module_name, exist_module_name = get_module_name(group_name,
|
||||
self.model,
|
||||
key_word,
|
||||
exist_module_name,
|
||||
verbose=False)
|
||||
module_name_list.extend(module_name)
|
||||
if module_name_list:
|
||||
self.different_compression_methods[method][DIFFERENT_GROUPS].append(
|
||||
[group_name, module_name_list,
|
||||
method_parameters.copy().pop('params')])
|
||||
|
||||
def check_weight_quantization(self):
|
||||
# check weight quantization
|
||||
wq = self.different_compression_methods[WEIGHT_QUANTIZATION]
|
||||
if not wq[TECHNIQUE_ENABLED]:
|
||||
return
|
||||
else:
|
||||
shared_parameters = wq[SHARED_PARAMETERS]
|
||||
if self.training_steps >= shared_parameters[TECHNIQUE_SCHEDULE_OFFSET]:
|
||||
for group_name, module_name_list, method_parameters in wq[DIFFERENT_GROUPS]:
|
||||
for module_name in module_name_list:
|
||||
module = recursive_getattr(self.model, module_name)
|
||||
module.weight_quantization_enabled = True
|
||||
|
||||
if not self.verbose[WEIGHT_QUANTIZATION]:
|
||||
logger.info(f'Weight quantization is enabled at step {self.training_steps}')
|
||||
self.weight_quantization_enabled = True
|
||||
self.verbose[WEIGHT_QUANTIZATION] = True
|
||||
|
||||
def check_activation_quantization(self):
|
||||
# check activation quantization
|
||||
aq = self.different_compression_methods[ACTIVATION_QUANTIZATION]
|
||||
if not aq[TECHNIQUE_ENABLED]:
|
||||
return
|
||||
else:
|
||||
shared_parameters = aq[SHARED_PARAMETERS]
|
||||
if self.training_steps >= shared_parameters[TECHNIQUE_SCHEDULE_OFFSET]:
|
||||
for group_name, module_name_list, method_parameters in aq[DIFFERENT_GROUPS]:
|
||||
for module_name in module_name_list:
|
||||
module = recursive_getattr(self.model, module_name)
|
||||
module.activation_quantization_enabled = True
|
||||
if not self.verbose[ACTIVATION_QUANTIZATION]:
|
||||
logger.info(f'Activation quantization is enabled at step {self.training_steps}')
|
||||
self.verbose[ACTIVATION_QUANTIZATION] = True
|
||||
|
||||
def check_sparse_pruning(self):
|
||||
# check sparse pruning
|
||||
sp = self.different_compression_methods[SPARSE_PRUNING]
|
||||
if not sp[TECHNIQUE_ENABLED]:
|
||||
return
|
||||
else:
|
||||
shared_parameters = sp[SHARED_PARAMETERS]
|
||||
if shared_parameters[TECHNIQUE_SCHEDULE_OFFSET] <= self.training_steps <= shared_parameters[
|
||||
TECHNIQUE_SCHEDULE_OFFSET_END]:
|
||||
for group_name, module_name_list, method_parameters in sp[DIFFERENT_GROUPS]:
|
||||
for module_name in module_name_list:
|
||||
module = recursive_getattr(self.model, module_name)
|
||||
module.sparse_pruning_enabled = True
|
||||
if not self.verbose[SPARSE_PRUNING]:
|
||||
logger.info(f'Sparse pruning is enabled at step {self.training_steps}')
|
||||
self.verbose[SPARSE_PRUNING] = True
|
||||
|
||||
def check_head_pruning(self):
|
||||
# check head pruning
|
||||
hp = self.different_compression_methods[HEAD_PRUNING]
|
||||
if not hp[TECHNIQUE_ENABLED]:
|
||||
return
|
||||
else:
|
||||
shared_parameters = hp[SHARED_PARAMETERS]
|
||||
if self.training_steps >= shared_parameters[TECHNIQUE_SCHEDULE_OFFSET]:
|
||||
for group_name, module_name_list, method_parameters in hp[DIFFERENT_GROUPS]:
|
||||
for module_name in module_name_list:
|
||||
module = recursive_getattr(self.model, module_name)
|
||||
module.head_pruning_enabled = True
|
||||
if not self.verbose[HEAD_PRUNING]:
|
||||
logger.info(f'Head pruning is enabled at step {self.training_steps}')
|
||||
self.verbose[HEAD_PRUNING] = True
|
||||
|
||||
def check_row_pruning(self):
|
||||
# check row pruning
|
||||
rp = self.different_compression_methods[ROW_PRUNING]
|
||||
if not rp[TECHNIQUE_ENABLED]:
|
||||
return
|
||||
else:
|
||||
shared_parameters = rp[SHARED_PARAMETERS]
|
||||
if self.training_steps >= shared_parameters[TECHNIQUE_SCHEDULE_OFFSET]:
|
||||
for group_name, module_name_list, method_parameters in rp[DIFFERENT_GROUPS]:
|
||||
for module_name in module_name_list:
|
||||
module = recursive_getattr(self.model, module_name)
|
||||
module.row_pruning_enabled = True
|
||||
if not self.verbose[ROW_PRUNING]:
|
||||
logger.info(f'Row pruning is enabled at step {self.training_steps}')
|
||||
self.verbose[ROW_PRUNING] = True
|
||||
|
||||
def check_channel_pruning(self):
|
||||
# check channel pruning
|
||||
cp = self.different_compression_methods[CHANNEL_PRUNING]
|
||||
if not cp[TECHNIQUE_ENABLED]:
|
||||
return
|
||||
else:
|
||||
shared_parameters = cp[SHARED_PARAMETERS]
|
||||
if self.training_steps >= shared_parameters[TECHNIQUE_SCHEDULE_OFFSET]:
|
||||
for group_name, module_name_list, method_parameters in cp[DIFFERENT_GROUPS]:
|
||||
for module_name in module_name_list:
|
||||
module = recursive_getattr(self.model, module_name)
|
||||
module.channel_pruning_enabled = True
|
||||
if not self.verbose[CHANNEL_PRUNING]:
|
||||
logger.info(f'Channel pruning is enabled at step {self.training_steps}')
|
||||
self.verbose[CHANNEL_PRUNING] = True
|
||||
|
||||
def check_all_modules(self):
|
||||
# check all different compression methods we have
|
||||
self.check_weight_quantization()
|
||||
self.check_activation_quantization()
|
||||
self.check_sparse_pruning()
|
||||
self.check_head_pruning()
|
||||
self.check_row_pruning()
|
||||
self.check_channel_pruning()
|
||||
|
||||
def step(self, step_zero_check=False):
|
||||
if not step_zero_check:
|
||||
self.training_steps += 1
|
||||
self.check_all_modules()
|
||||
@@ -0,0 +1,222 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
from torch import autograd
|
||||
import math
|
||||
|
||||
|
||||
class TopKBinarizer(autograd.Function):
|
||||
"""
|
||||
Top-k Binarizer.
|
||||
Computes a binary mask M from a real value matrix S such that `M_{i,j} = 1` if and only if `S_{i,j}`
|
||||
is among the k% highest values of S.
|
||||
Implementation is inspired from:
|
||||
https://github.com/yaozhewei/MLPruning
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, inputs: torch.tensor, threshold: float, sigmoid: bool):
|
||||
"""
|
||||
Args:
|
||||
inputs (`torch.FloatTensor`)
|
||||
The input matrix from which the binarizer computes the binary mask.
|
||||
threshold (`float`)
|
||||
The percentage of weights to keep (the rest is pruned).
|
||||
`threshold` is a float between 0 and 1.
|
||||
sigmoid (`bool`)
|
||||
Whether to apply a sigmoid on the threshold
|
||||
Returns:
|
||||
mask (`torch.FloatTensor`)
|
||||
Binary matrix of the same size as `inputs` acting as a mask (1 - the associated weight is
|
||||
retained, 0 - the associated weight is pruned).
|
||||
"""
|
||||
# Get the subnetwork by sorting the inputs and using the top threshold
|
||||
if sigmoid:
|
||||
threshold = torch.sigmoid(threshold).item()
|
||||
ctx.sigmoid = sigmoid
|
||||
mask = inputs.clone()
|
||||
|
||||
_, idx = inputs.flatten().sort(descending=True)
|
||||
j = math.ceil(threshold * inputs.numel())
|
||||
|
||||
# flat_out and mask access the same memory.
|
||||
flat_out = mask.flatten()
|
||||
flat_out[idx[j:]] = 0.
|
||||
flat_out[idx[:j]] = 1.
|
||||
ctx.save_for_backward(mask)
|
||||
|
||||
return mask
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, gradOutput):
|
||||
mask, = ctx.saved_tensors
|
||||
if ctx.sigmoid:
|
||||
return gradOutput.clone(), ((gradOutput * mask).sum()).view(-1), None
|
||||
else:
|
||||
return gradOutput.clone(), None, None
|
||||
|
||||
|
||||
class SymQuantizer(torch.autograd.Function):
|
||||
"""
|
||||
Symmetric quantization
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input, num_bits, min_value=None, max_value=None, num_groups=1):
|
||||
"""
|
||||
Args:
|
||||
inputs (`torch.FloatTensor`)
|
||||
The input which needs to be quantized
|
||||
num_bits (int, >=4)
|
||||
Number of bits to use for quantization
|
||||
min_value/max_value (torch.FloatTensor)
|
||||
Used for static activation quantization
|
||||
num_groups (int)
|
||||
How many groups to partition the quantization into
|
||||
Returns:
|
||||
quantized_input (`torch.FloatTensor`)
|
||||
Quantized input
|
||||
"""
|
||||
assert (min_value is None and max_value is None) or (min_value is not None and max_value is not None
|
||||
and num_groups == 1)
|
||||
q_range = 2**num_bits
|
||||
input_shape = input.shape
|
||||
if min_value is None:
|
||||
input = input.reshape(num_groups, -1)
|
||||
max_input = torch.amax(torch.abs(input), dim=-1).view(num_groups, -1)
|
||||
else:
|
||||
max_input = torch.max(min_value.abs(), max_value).view(-1)
|
||||
|
||||
scale = 2 * max_input / q_range
|
||||
output = (input / scale).round().clamp(-q_range // 2, q_range // 2 - 1) * scale
|
||||
output = output.reshape(input_shape).contiguous()
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
grad_input = grad_output.clone()
|
||||
return grad_input, None, None, None, None
|
||||
|
||||
|
||||
class AsymQuantizer(torch.autograd.Function):
|
||||
"""
|
||||
Asymmetric quantization
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input, num_bits, min_value=None, max_value=None, num_groups=1):
|
||||
"""
|
||||
Args:
|
||||
inputs (`torch.FloatTensor`)
|
||||
The input which needs to be quantized
|
||||
num_bits (int, >=4)
|
||||
Number of bits to use for quantization
|
||||
min_value/max_value (torch.FloatTensor)
|
||||
Used for static activation quantization
|
||||
num_groups (int)
|
||||
How many groups to partition the quantization into
|
||||
Returns:
|
||||
quantized_input (`torch.FloatTensor`)
|
||||
Quantized input
|
||||
"""
|
||||
|
||||
assert (min_value is None and max_value is None) or (min_value is not None and max_value is not None
|
||||
and num_groups == 1)
|
||||
q_range = 2**num_bits
|
||||
input_shape = input.shape
|
||||
if min_value is None:
|
||||
input = input.reshape(num_groups, -1)
|
||||
min_value = input.amin(dim=-1, keepdim=True)
|
||||
max_value = input.amax(dim=-1, keepdim=True)
|
||||
|
||||
scale = (max_value - min_value) / q_range
|
||||
zero_point = (min_value / scale).round() * scale
|
||||
|
||||
output = ((input - zero_point) / scale).round().clamp(0, q_range - 1) * scale + zero_point
|
||||
output = output.reshape(input_shape).contiguous()
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
grad_input = grad_output.clone()
|
||||
return grad_input, None, None, None, None
|
||||
|
||||
|
||||
class TernaryQuantizer(torch.autograd.Function):
|
||||
"""
|
||||
Ternary quantization
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input, num_bits, min_value=None, max_value=None, num_groups=1):
|
||||
"""
|
||||
Args:
|
||||
inputs (`torch.FloatTensor`)
|
||||
The input which needs to be quantized
|
||||
num_bits (int)
|
||||
Dummy variable
|
||||
min_value/max_value (torch.FloatTensor)
|
||||
Used for static activation quantization; for now they are dummy variable
|
||||
num_groups (int)
|
||||
How many groups to partition the quantization into
|
||||
Returns:
|
||||
quantized_input (`torch.FloatTensor`)
|
||||
Quantized input
|
||||
"""
|
||||
|
||||
assert (min_value is None and max_value is None)
|
||||
input_flat = input.reshape(num_groups, -1)
|
||||
n = input_flat.shape[1]
|
||||
m = input_flat.norm(p=1, dim=1).div(n)
|
||||
thres = (0.7 * m).view(-1, 1)
|
||||
pos = (input_flat > thres).type(input.type())
|
||||
neg = (input_flat < -thres).type(input.type())
|
||||
mask = (input_flat.abs() > thres).type(input.type())
|
||||
alpha = ((mask * input_flat).abs().sum(dim=1) / mask.sum(dim=1)).view(-1, 1)
|
||||
output = alpha * pos - alpha * neg
|
||||
output = output.reshape(input.shape).contiguous()
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
grad_input = grad_output.clone()
|
||||
return grad_input, None, None, None, None
|
||||
|
||||
|
||||
class BinaryQuantizer(torch.autograd.Function):
|
||||
"""
|
||||
Binary quantization
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input, num_bits, min_value=None, max_value=None, num_groups=1):
|
||||
"""
|
||||
Args:
|
||||
inputs (`torch.FloatTensor`)
|
||||
The input which needs to be quantized
|
||||
num_bits (int)
|
||||
Dummy variable
|
||||
min_value/max_value (torch.FloatTensor)
|
||||
Used for static activation quantization; for now they are dummy variable
|
||||
num_groups (int)
|
||||
How many groups to partition the quantization into
|
||||
Returns:
|
||||
quantized_input (`torch.FloatTensor`)
|
||||
Quantized input
|
||||
"""
|
||||
|
||||
assert (min_value is None and max_value is None)
|
||||
input_flat = input.reshape(num_groups, -1)
|
||||
n = input_flat.shape[1]
|
||||
m = input_flat.norm(p=1, dim=1, keepdim=True).div(n)
|
||||
output = input_flat.sign().mul(m)
|
||||
output = output.reshape(input.shape).contiguous()
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
grad_input = grad_output.clone()
|
||||
return grad_input, None, None, None, None
|
||||
Reference in New Issue
Block a user