forked from LsNatan/NICE
-
Notifications
You must be signed in to change notification settings - Fork 2
/
actquant.py
205 lines (155 loc) · 6.88 KB
/
actquant.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import itertools
import os
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.parameter import Parameter
import quantize
class ActQuant(nn.Module):
layer_id = itertools.count()
def __init__(self, quatize_during_training=False, noise_during_training=False, quant=False,
bitwidth=32):
super(ActQuant, self).__init__()
self.quant = quant
assert (isinstance(bitwidth, int))
self.bitwidth = bitwidth
self.act_full_scale = 2 ** self.bitwidth - 1
self.quatize_during_training = quatize_during_training
self.noise_during_training = noise_during_training
self.layer_num = next(self.layer_id)
self.saved_stats = False
self.gather_stats = False
self.quant_error = []
# For gathering statistics before training
self.pre_training_statistics = False
self.momentum = 0.9
# For activation scaling
self.max_factor_of_act_step = 8 # the factor can be 1-8 in each level , will be represented in 3 bits in HW
def update_stage(self, quatize_during_training=False, noise_during_training=False):
self.quatize_during_training = quatize_during_training
self.noise_during_training = noise_during_training
def plot_statistic(self, x):
# plot histogram
gaussian_numbers = x.view(-1).cpu().detach().numpy()
plt.hist(gaussian_numbers, bins=256)
file_name = 'activation_value_' + str(self.layer_num)
if not os.path.isdir('./activation_stats'):
os.mkdir('./activation_stats')
file_name = os.path.join('./activation_stats', file_name + '.png')
plt.savefig(file_name)
plt.close()
def act_clamp(self, x, clamp_val):
x = F.relu(x) - F.relu(x - torch.abs(clamp_val))
return x
def forward(self, input):
if self.quant and (not self.training or (self.training and self.quatize_during_training)):
x = self.act_clamp(input, self.clamp_val)
x = act_quant.apply(x, self.clamp_val, self.bitwidth)
print('Activation layer {} has clamp value {}'.format(self.layer_num, self.clamp_val.item()))
else:
# x = F.relu(input)
# x = quantize.act_clamp(input)
x = self.act_clamp(input, self.clamp_val)
if not self.saved_stats and self.gather_stats:
self.plot_statistic(x)
self.saved_stats = True
return x
def act_quant(x, act_max_value, bitwidth):
act_scale = (2 ** bitwidth - 1) / act_max_value
q_x = Round.apply(x * act_scale) * 1 / act_scale
return q_x
class Round(torch.autograd.Function):
@staticmethod
def forward(self, x):
round = (x).round()
return round.to(x.device)
@staticmethod
def backward(self, grad_output):
grad_input = grad_output
return grad_input, None, None
class ClampQ(torch.autograd.Function):
@staticmethod
def forward(ctx, x, min, max, bin_num):
ctx.save_for_backward(x, min, max, bin_num)
# print(min, max)
return torch.clamp(x, min.item(), max.item())
@staticmethod
def backward(ctx, grad_output):
x, mn, mx, bin_num = ctx.saved_tensors
grad_x = grad_output.clone()
grad_x[x < mn] = 0
grad_x[x > mx] = 0
return grad_x, None, None, None
grad_min = (grad_output[x <mn]).sum()-1 / (4 * bin_num)
grad_max = (grad_output[x > mx]).sum()+1 / (4 * bin_num)
#print(grad_min, grad_max)
return grad_x, grad_min.expand_as(mn), grad_max.expand_as(mx), None
class ActQuantBuffers(ActQuant): # This class exist to allow multi-gpu run
def __init__(self, quatize_during_training=False, noise_during_training=False, quant=False,
bitwidth=32):
super(ActQuantBuffers, self).__init__(quatize_during_training=quatize_during_training,
noise_during_training=noise_during_training, quant=quant,
bitwidth=bitwidth)
self.register_buffer('running_mean', torch.zeros(1))
self.register_buffer('running_std', torch.zeros(1))
self.clamp_val = Parameter(torch.zeros(1), requires_grad=True)
def forward(self, input):
if self.pre_training_statistics:
self.running_mean.to(input.device).detach().mul_(self.momentum).add_(
input.mean() * (1 - self.momentum))
self.running_std.to(input.device).detach().mul_(self.momentum).add_(
input.std() * (1 - self.momentum))
x = F.relu(input)
elif self.quant and (not self.training or (self.training and self.quatize_during_training)):
c_x = self.act_clamp(input, self.clamp_val)
x = act_quant(c_x, self.clamp_val, self.bitwidth)
else:
if self.quant:
x = self.act_clamp(input, self.clamp_val)
else:
x = F.relu(input)
if not self.saved_stats and self.gather_stats:
self.plot_statistic(x)
self.saved_stats = True
if False:
self.print_clamp()
return x
def print_clamp(self):
print('Activation layer {} has clamp value {}'.format(self.layer_num, self.clamp_val.item()))
class ActQuantDeepIspPic(nn.Module):
def __init__(self, act_quant=False, act_bitwidth=8, act_clamp=4):
super(ActQuantDeepIspPic, self).__init__()
self.act_quant = act_quant
self.act_bitwidth = act_bitwidth
self.act_clamp = act_clamp
self.act_scale = 2 ** self.act_bitwidth - 1
self.quatize_during_training = False
self.noise_during_training = False
def forward(self, x):
if self.act_quant and (not self.training or (self.training and self.quatize_during_training)):
# x = torch.clamp(x, -self.act_clamp, self.act_clamp)
x = 1 / self.act_scale * Round.apply(x * self.act_scale)
return x
else:
if self.act_quant:
return torch.clamp(x, -self.act_clamp, self.act_clamp)
else:
return x
###WRPN to compare in deep isp
class ActQuantWRPN(nn.Module):
def __init__(self, act_quant=False, act_bitwidth=8, act_clamp=1):
super(ActQuantWRPN, self).__init__()
self.act_quant = act_quant
self.act_bitwidth = act_bitwidth
self.act_clamp = act_clamp
self.act_scale = 2 ** self.act_bitwidth - 1
self.quatize_during_training = False
self.noise_during_training = False
def forward(self, x):
if self.act_quant and (not self.training or (self.training and self.quatize_during_training)):
x = torch.clamp(x, 0, self.act_clamp)
x = 1 / self.act_scale * Round.apply(x * self.act_scale)
return x
else:
return F.relu(x)