Note
Click here to download the full example code
(beta) Static Quantization with Eager Mode in PyTorch¶
Author: Raghuraman Krishnamoorthi
Edited by: Seth Weidman
This tutorial shows how to do post-training static quantization, as well as illustrating two more advanced techniques - per-channel quantization and quantization-aware training - to further improve the model’s accuracy. Note that quantization is currently only supported for CPUs, so we will not be utilizing GPUs / CUDA in this tutorial.
By the end of this tutorial, you will see how quantization in PyTorch can result in significant decreases in model size while increasing speed. Furthermore, you’ll see how to easily apply some advanced quantization techniques shown here so that your quantized models take much less of an accuracy hit than they would otherwise.
Warning: we use a lot of boilerplate code from other PyTorch repos to, for example,
define the MobileNetV2
model archtecture, define data loaders, and so on. We of course
encourage you to read it; but if you want to get to the quantization features, feel free
to skip to the “4. Post-training static quantization” section.
We’ll start by doing the necessary imports:
import numpy as np
import torch
import torch.nn as nn
import torchvision
from torch.utils.data import DataLoader
from torchvision import datasets
import torchvision.transforms as transforms
import os
import time
import sys
import torch.quantization
# # Setup warnings
import warnings
warnings.filterwarnings(
action='ignore',
category=DeprecationWarning,
module=r'.*'
)
warnings.filterwarnings(
action='default',
module=r'torch.quantization'
)
# Specify random seed for repeatable results
torch.manual_seed(191009)
1. Model architecture¶
We first define the MobileNetV2 model architecture, with several notable modifications to enable quantization:
Replacing addition with
nn.quantized.FloatFunctional
Insert
QuantStub
andDeQuantStub
at the beginning and end of the network.Replace ReLU6 with ReLU
Note: this code is taken from here.
from torch.quantization import QuantStub, DeQuantStub
def _make_divisible(v, divisor, min_value=None):
"""
This function is taken from the original tf repo.
It ensures that all layers have a channel number that is divisible by 8
It can be seen here:
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
:param v:
:param divisor:
:param min_value:
:return:
"""
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
class ConvBNReLU(nn.Sequential):
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
padding = (kernel_size - 1) // 2
super(ConvBNReLU, self).__init__(
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
nn.BatchNorm2d(out_planes, momentum=0.1),
# Replace with ReLU
nn.ReLU(inplace=False)
)
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, stride, expand_ratio):
super(InvertedResidual, self).__init__()
self.stride = stride
assert stride in [1, 2]
hidden_dim = int(round(inp * expand_ratio))
self.use_res_connect = self.stride == 1 and inp == oup
layers = []
if expand_ratio != 1:
# pw
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
layers.extend([
# dw
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup, momentum=0.1),
])
self.conv = nn.Sequential(*layers)
# Replace torch.add with floatfunctional
self.skip_add = nn.quantized.FloatFunctional()
def forward(self, x):
if self.use_res_connect:
return self.skip_add.add(x, self.conv(x))
else:
return self.conv(x)
class MobileNetV2(nn.Module):
def __init__(self, num_classes=1000, width_mult=1.0, inverted_residual_setting=None, round_nearest=8):
"""
MobileNet V2 main class
Args:
num_classes (int): Number of classes
width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
inverted_residual_setting: Network structure
round_nearest (int): Round the number of channels in each layer to be a multiple of this number
Set to 1 to turn off rounding
"""
super(MobileNetV2, self).__init__()
block = InvertedResidual
input_channel = 32
last_channel = 1280
if inverted_residual_setting is None:
inverted_residual_setting = [
# t, c, n, s
[1, 16, 1, 1],
[6, 24, 2, 2],
[6, 32, 3, 2],
[6, 64, 4, 2],
[6, 96, 3, 1],
[6, 160, 3, 2],
[6, 320, 1, 1],
]
# only check the first element, assuming user knows t,c,n,s are required
if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
raise ValueError("inverted_residual_setting should be non-empty "
"or a 4-element list, got {}".format(inverted_residual_setting))
# building first layer
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
features = [ConvBNReLU(3, input_channel, stride=2)]
# building inverted residual blocks
for t, c, n, s in inverted_residual_setting:
output_channel = _make_divisible(c * width_mult, round_nearest)
for i in range(n):
stride = s if i == 0 else 1
features.append(block(input_channel, output_channel, stride, expand_ratio=t))
input_channel = output_channel
# building last several layers
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1))
# make it nn.Sequential
self.features = nn.Sequential(*features)
self.quant = QuantStub()
self.dequant = DeQuantStub()
# building classifier
self.classifier = nn.Sequential(
nn.Dropout(0.2),
nn.Linear(self.last_channel, num_classes),
)
# weight initialization
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.BatchNorm2d):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.zeros_(m.bias)
def forward(self, x):
x = self.quant(x)
x = self.features(x)
x = x.mean([2, 3])
x = self.classifier(x)
x = self.dequant(x)
return x
# Fuse Conv+BN and Conv+BN+Relu modules prior to quantization
# This operation does not change the numerics
def fuse_model(self):
for m in self.modules():
if type(m) == ConvBNReLU:
torch.quantization.fuse_modules(m, ['0', '1', '2'], inplace=True)
if type(m) == InvertedResidual:
for idx in range(len(m.conv)):
if type(m.conv[idx]) == nn.Conv2d:
torch.quantization.fuse_modules(m.conv, [str(idx), str(idx + 1)], inplace=True)
2. Helper functions¶
We next define several helper functions to help with model evaluation. These mostly come from here.
class AverageMeter(object):
"""Computes and stores the average and current value"""
def __init__(self, name, fmt=':f'):
self.name = name
self.fmt = fmt
self.reset()
def reset(self):
self.val = 0
self.avg = 0
self.sum = 0
self.count = 0
def update(self, val, n=1):
self.val = val
self.sum += val * n
self.count += n
self.avg = self.sum / self.count
def __str__(self):
fmtstr = '{name} {val' + self.fmt + '} ({avg' + self.fmt + '})'
return fmtstr.format(**self.__dict__)
def accuracy(output, target, topk=(1,)):
"""Computes the accuracy over the k top predictions for the specified values of k"""
with torch.no_grad():
maxk = max(topk)
batch_size = target.size(0)
_, pred = output.topk(maxk, 1, True, True)
pred = pred.t()
correct = pred.eq(target.view(1, -1).expand_as(pred))
res = []
for k in topk:
correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)
res.append(correct_k.mul_(100.0 / batch_size))
return res
def evaluate(model, criterion, data_loader, neval_batches):
model.eval()
top1 = AverageMeter('Acc@1', ':6.2f')
top5 = AverageMeter('Acc@5', ':6.2f')
cnt = 0
with torch.no_grad():
for image, target in data_loader:
output = model(image)
loss = criterion(output, target)
cnt += 1
acc1, acc5 = accuracy(output, target, topk=(1, 5))
print('.', end = '')
top1.update(acc1[0], image.size(0))
top5.update(acc5[0], image.size(0))
if cnt >= neval_batches:
return top1, top5
return top1, top5
def load_model(model_file):
model = MobileNetV2()
state_dict = torch.load(model_file)
model.load_state_dict(state_dict)
model.to('cpu')
return model
def print_size_of_model(model):
torch.save(model.state_dict(), "temp.p")
print('Size (MB):', os.path.getsize("temp.p")/1e6)
os.remove('temp.p')
3. Define dataset and data loaders¶
As our last major setup step, we define our dataloaders for our training and testing set.
ImageNet Data¶
The specific dataset we’ve created for this tutorial contains just 1000 images from the ImageNet data, one from each class (this dataset, at just over 250 MB, is small enough that it can be downloaded relatively easily). The URL for this custom dataset is:
https://s3.amazonaws.com/pytorch-tutorial-assets/imagenet_1k.zip
To download this data locally using Python, you could use:
import requests
url = 'https://s3.amazonaws.com/pytorch-tutorial-assets/imagenet_1k.zip`
filename = '~/Downloads/imagenet_1k_data.zip'
r = requests.get(url)
with open(filename, 'wb') as f:
f.write(r.content)
For this tutorial to run, we download this data and move it to the right place using these lines from the Makefile.
To run the code in this tutorial using the entire ImageNet dataset, on the other hand, you could download
the data using torchvision
following
here. For example,
to download the training set and apply some standard transformations to it, you could use:
import torchvision
import torchvision.transforms as transforms
imagenet_dataset = torchvision.datasets.ImageNet(
'~/.data/imagenet',
split='train',
download=True,
transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
With the data downloaded, we show functions below that define dataloaders we’ll use to read in this data. These functions mostly come from here.
def prepare_data_loaders(data_path):
traindir = os.path.join(data_path, 'train')
valdir = os.path.join(data_path, 'val')
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
dataset = torchvision.datasets.ImageFolder(
traindir,
transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
normalize,
]))
dataset_test = torchvision.datasets.ImageFolder(
valdir,
transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize,
]))
train_sampler = torch.utils.data.RandomSampler(dataset)
test_sampler = torch.utils.data.SequentialSampler(dataset_test)
data_loader = torch.utils.data.DataLoader(
dataset, batch_size=train_batch_size,
sampler=train_sampler)
data_loader_test = torch.utils.data.DataLoader(
dataset_test, batch_size=eval_batch_size,
sampler=test_sampler)
return data_loader, data_loader_test
Next, we’ll load in the pre-trained MobileNetV2 model. We provide the URL to download the data from in torchvision
here.
data_path = 'data/imagenet_1k'
saved_model_dir = 'data/'
float_model_file = 'mobilenet_pretrained_float.pth'
scripted_float_model_file = 'mobilenet_quantization_scripted.pth'
scripted_quantized_model_file = 'mobilenet_quantization_scripted_quantized.pth'
train_batch_size = 30
eval_batch_size = 30
data_loader, data_loader_test = prepare_data_loaders(data_path)
criterion = nn.CrossEntropyLoss()
float_model = load_model(saved_model_dir + float_model_file).to('cpu')
Next, we’ll “fuse modules”; this can both make the model faster by saving on memory access while also improving numerical accuracy. While this can be used with any model, this is especially common with quantized models.
print('\n Inverted Residual Block: Before fusion \n\n', float_model.features[1].conv)
float_model.eval()
# Fuses modules
float_model.fuse_model()
# Note fusion of Conv+BN+Relu and Conv+Relu
print('\n Inverted Residual Block: After fusion\n\n',float_model.features[1].conv)
Out:
Inverted Residual Block: Before fusion
Sequential(
(0): ConvBNReLU(
(0): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=32, bias=False)
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Conv2d(32, 16, kernel_size=(1, 1), stride=(1, 1), bias=False)
(2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
Inverted Residual Block: After fusion
Sequential(
(0): ConvBNReLU(
(0): ConvReLU2d(
(0): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=32)
(1): ReLU()
)
(1): Identity()
(2): Identity()
)
(1): Conv2d(32, 16, kernel_size=(1, 1), stride=(1, 1))
(2): Identity()
)
Finally to get a “baseline” accuracy, let’s see the accuracy of our un-quantized model with fused modules
num_eval_batches = 10
print("Size of baseline model")
print_size_of_model(float_model)
top1, top5 = evaluate(float_model, criterion, data_loader_test, neval_batches=num_eval_batches)
print('Evaluation accuracy on %d images, %2.2f'%(num_eval_batches * eval_batch_size, top1.avg))
torch.jit.save(torch.jit.script(float_model), saved_model_dir + scripted_float_model_file)
Out:
Size of baseline model
Size (MB): 13.998602
..........Evaluation accuracy on 300 images, 78.00
We see 78% accuracy on 300 images, a solid baseline for ImageNet, especially considering our model is just 14.0 MB.
This will be our baseline to compare to. Next, let’s try different quantization methods
4. Post-training static quantization¶
Post-training static quantization involves not just converting the weights from float to int, as in dynamic quantization, but also performing the additional step of first feeding batches of data through the network and computing the resulting distributions of the different activations (specifically, this is done by inserting observer modules at different points that record this data). These distributions are then used to determine how the specifically the different activations should be quantized at inference time (a simple technique would be to simply divide the entire range of activations into 256 levels, but we support more sophisticated methods as well). Importantly, this additional step allows us to pass quantized values between operations instead of converting these values to floats - and then back to ints - between every operation, resulting in a significant speed-up.
num_calibration_batches = 10
myModel = load_model(saved_model_dir + float_model_file).to('cpu')
myModel.eval()
# Fuse Conv, bn and relu
myModel.fuse_model()
# Specify quantization configuration
# Start with simple min/max range estimation and per-tensor quantization of weights
myModel.qconfig = torch.quantization.default_qconfig
print(myModel.qconfig)
torch.quantization.prepare(myModel, inplace=True)
# Calibrate first
print('Post Training Quantization Prepare: Inserting Observers')
print('\n Inverted Residual Block:After observer insertion \n\n', myModel.features[1].conv)
# Calibrate with the training set
evaluate(myModel, criterion, data_loader, neval_batches=num_calibration_batches)
print('Post Training Quantization: Calibration done')
# Convert to quantized model
torch.quantization.convert(myModel, inplace=True)
print('Post Training Quantization: Convert done')
print('\n Inverted Residual Block: After fusion and quantization, note fused modules: \n\n',myModel.features[1].conv)
print("Size of model after quantization")
print_size_of_model(myModel)
top1, top5 = evaluate(myModel, criterion, data_loader_test, neval_batches=num_eval_batches)
print('Evaluation accuracy on %d images, %2.2f'%(num_eval_batches * eval_batch_size, top1.avg))
Out:
QConfig(activation=functools.partial(<class 'torch.quantization.observer.MinMaxObserver'>, reduce_range=True), weight=functools.partial(<class 'torch.quantization.observer.MinMaxObserver'>, dtype=torch.qint8, qscheme=torch.per_tensor_symmetric))
Post Training Quantization Prepare: Inserting Observers
Inverted Residual Block:After observer insertion
Sequential(
(0): ConvBNReLU(
(0): ConvReLU2d(
(0): Conv2d(
32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=32
(activation_post_process): MinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
(1): ReLU(
(activation_post_process): MinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
)
(1): Identity()
(2): Identity()
)
(1): Conv2d(
32, 16, kernel_size=(1, 1), stride=(1, 1)
(activation_post_process): MinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
(2): Identity()
)
..........Post Training Quantization: Calibration done
Post Training Quantization: Convert done
Inverted Residual Block: After fusion and quantization, note fused modules:
Sequential(
(0): ConvBNReLU(
(0): QuantizedConvReLU2d(32, 32, kernel_size=(3, 3), stride=(1, 1), scale=0.1875331997871399, zero_point=0, padding=(1, 1), groups=32)
(1): Identity()
(2): Identity()
)
(1): QuantizedConv2d(32, 16, kernel_size=(1, 1), stride=(1, 1), scale=0.23388975858688354, zero_point=77)
(2): Identity()
)
Size of model after quantization
Size (MB): 3.629311
..........Evaluation accuracy on 300 images, 55.33
For this quantized model, we see a significantly lower accuracy of just ~62% on these same 300 images. Nevertheless, we did reduce the size of our model down to just under 3.6 MB, almost a 4x decrease.
In addition, we can significantly improve on the accuracy simply by using a different quantization configuration. We repeat the same exercise with the recommended configuration for quantizing for x86 architectures. This configuration does the following:
Quantizes weights on a per-channel basis
Uses a histogram observer that collects a histogram of activations and then picks quantization parameters in an optimal manner.
per_channel_quantized_model = load_model(saved_model_dir + float_model_file)
per_channel_quantized_model.eval()
per_channel_quantized_model.fuse_model()
per_channel_quantized_model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
print(per_channel_quantized_model.qconfig)
torch.quantization.prepare(per_channel_quantized_model, inplace=True)
evaluate(per_channel_quantized_model,criterion, data_loader, num_calibration_batches)
torch.quantization.convert(per_channel_quantized_model, inplace=True)
top1, top5 = evaluate(per_channel_quantized_model, criterion, data_loader_test, neval_batches=num_eval_batches)
print('Evaluation accuracy on %d images, %2.2f'%(num_eval_batches * eval_batch_size, top1.avg))
torch.jit.save(torch.jit.script(per_channel_quantized_model), saved_model_dir + scripted_quantized_model_file)
Out:
QConfig(activation=functools.partial(<class 'torch.quantization.observer.HistogramObserver'>, reduce_range=True), weight=functools.partial(<class 'torch.quantization.observer.PerChannelMinMaxObserver'>, dtype=torch.qint8, qscheme=torch.per_channel_symmetric))
....................Evaluation accuracy on 300 images, 78.67
Changing just this quantization configuration method resulted in an increase of the accuracy to over 76%! Still, this is 1-2% worse than the baseline of 78% achieved above. So lets try quantization aware training.
5. Quantization-aware training¶
Quantization-aware training (QAT) is the quantization method that typically results in the highest accuracy. With QAT, all weights and activations are “fake quantized” during both the forward and backward passes of training: that is, float values are rounded to mimic int8 values, but all computations are still done with floating point numbers. Thus, all the weight adjustments during training are made while “aware” of the fact that the model will ultimately be quantized; after quantizing, therefore, this method will usually yield higher accuracy than either dynamic quantization or post-training static quantization.
The overall workflow for actually performing QAT is very similar to before:
We can use the same model as before: there is no additional preparation needed for quantization-aware training.
We need to use a
qconfig
specifying what kind of fake-quantization is to be inserted after weights and activations, instead of specifying observers
We first define a training function:
def train_one_epoch(model, criterion, optimizer, data_loader, device, ntrain_batches):
model.train()
top1 = AverageMeter('Acc@1', ':6.2f')
top5 = AverageMeter('Acc@5', ':6.2f')
avgloss = AverageMeter('Loss', '1.5f')
cnt = 0
for image, target in data_loader:
start_time = time.time()
print('.', end = '')
cnt += 1
image, target = image.to(device), target.to(device)
output = model(image)
loss = criterion(output, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
acc1, acc5 = accuracy(output, target, topk=(1, 5))
top1.update(acc1[0], image.size(0))
top5.update(acc5[0], image.size(0))
avgloss.update(loss, image.size(0))
if cnt >= ntrain_batches:
print('Loss', avgloss.avg)
print('Training: * Acc@1 {top1.avg:.3f} Acc@5 {top5.avg:.3f}'
.format(top1=top1, top5=top5))
return
print('Full imagenet train set: * Acc@1 {top1.global_avg:.3f} Acc@5 {top5.global_avg:.3f}'
.format(top1=top1, top5=top5))
return
We fuse modules as before
qat_model = load_model(saved_model_dir + float_model_file)
qat_model.fuse_model()
optimizer = torch.optim.SGD(qat_model.parameters(), lr = 0.0001)
qat_model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
Finally, prepare_qat
performs the “fake quantization”, preparing the model for quantization-aware
training
torch.quantization.prepare_qat(qat_model, inplace=True)
print('Inverted Residual Block: After preparation for QAT, note fake-quantization modules \n',qat_model.features[1].conv)
Out:
Inverted Residual Block: After preparation for QAT, note fake-quantization modules
Sequential(
(0): ConvBNReLU(
(0): ConvBnReLU2d(
32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=32, bias=False
(bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(activation_post_process): FakeQuantize(
fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), scale=tensor([1.]), zero_point=tensor([0])
(activation_post_process): MovingAverageMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
(weight_fake_quant): FakeQuantize(
fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), scale=tensor([1.]), zero_point=tensor([0])
(activation_post_process): MovingAveragePerChannelMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
)
(1): Identity()
(2): Identity()
)
(1): ConvBn2d(
32, 16, kernel_size=(1, 1), stride=(1, 1), bias=False
(bn): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(activation_post_process): FakeQuantize(
fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), scale=tensor([1.]), zero_point=tensor([0])
(activation_post_process): MovingAverageMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
(weight_fake_quant): FakeQuantize(
fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), scale=tensor([1.]), zero_point=tensor([0])
(activation_post_process): MovingAveragePerChannelMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
)
)
(2): Identity()
)
Training a quantized model with high accuracy requires accurate modeling of numerics at inference. For quantization aware training, therefore, we modify the training loop by:
Switch batch norm to use running mean and variance towards the end of training to better match inference numerics.
We also freeze the quantizer parameters (scale and zero-point) and fine tune the weights.
num_train_batches = 20
# Train and check accuracy after each epoch
for nepoch in range(8):
train_one_epoch(qat_model, criterion, optimizer, data_loader, torch.device('cpu'), num_train_batches)
if nepoch > 3:
# Freeze quantizer parameters
qat_model.apply(torch.quantization.disable_observer)
if nepoch > 2:
# Freeze batch norm mean and variance estimates
qat_model.apply(torch.nn.intrinsic.qat.freeze_bn_stats)
# Check the accuracy after each epoch
quantized_model = torch.quantization.convert(qat_model.eval(), inplace=False)
quantized_model.eval()
top1, top5 = evaluate(quantized_model,criterion, data_loader_test, neval_batches=num_eval_batches)
print('Epoch %d :Evaluation accuracy on %d images, %2.2f'%(nepoch, num_eval_batches * eval_batch_size, top1.avg))
Out:
....................Loss tensor(1.9970, grad_fn=<DivBackward0>)
Training: * Acc@1 55.667 Acc@5 78.500
..........Epoch 0 :Evaluation accuracy on 300 images, 74.67
....................Loss tensor(2.0479, grad_fn=<DivBackward0>)
Training: * Acc@1 54.000 Acc@5 77.167
..........Epoch 1 :Evaluation accuracy on 300 images, 77.00
....................Loss tensor(2.0638, grad_fn=<DivBackward0>)
Training: * Acc@1 54.833 Acc@5 77.333
..........Epoch 2 :Evaluation accuracy on 300 images, 75.67
....................Loss tensor(1.9934, grad_fn=<DivBackward0>)
Training: * Acc@1 54.000 Acc@5 78.833
..........Epoch 3 :Evaluation accuracy on 300 images, 76.67
....................Loss tensor(1.8909, grad_fn=<DivBackward0>)
Training: * Acc@1 56.167 Acc@5 81.167
..........Epoch 4 :Evaluation accuracy on 300 images, 77.00
....................Loss tensor(1.8382, grad_fn=<DivBackward0>)
Training: * Acc@1 58.167 Acc@5 80.833
..........Epoch 5 :Evaluation accuracy on 300 images, 76.33
....................Loss tensor(1.9286, grad_fn=<DivBackward0>)
Training: * Acc@1 54.500 Acc@5 80.000
..........Epoch 6 :Evaluation accuracy on 300 images, 74.67
....................Loss tensor(1.9360, grad_fn=<DivBackward0>)
Training: * Acc@1 58.000 Acc@5 79.833
..........Epoch 7 :Evaluation accuracy on 300 images, 74.00
Here, we just perform quantization-aware training for a small number of epochs. Nevertheless, quantization-aware training yields an accuracy of over 71% on the entire imagenet dataset, which is close to the floating point accuracy of 71.9%.
More on quantization-aware training:
QAT is a super-set of post training quant techniques that allows for more debugging. For example, we can analyze if the accuracy of the model is limited by weight or activation quantization.
We can also simulate the accuracy of a quantized model in floating point since we are using fake-quantization to model the numerics of actual quantized arithmetic.
We can mimic post training quantization easily too.
Speedup from quantization¶
Finally, let’s confirm something we alluded to above: do our quantized models actually perform inference faster? Let’s test:
def run_benchmark(model_file, img_loader):
elapsed = 0
model = torch.jit.load(model_file)
model.eval()
num_batches = 5
# Run the scripted model on a few batches of images
for i, (images, target) in enumerate(img_loader):
if i < num_batches:
start = time.time()
output = model(images)
end = time.time()
elapsed = elapsed + (end-start)
else:
break
num_images = images.size()[0] * num_batches
print('Elapsed time: %3.0f ms' % (elapsed/num_images*1000))
return elapsed
run_benchmark(saved_model_dir + scripted_float_model_file, data_loader_test)
run_benchmark(saved_model_dir + scripted_quantized_model_file, data_loader_test)
Out:
Elapsed time: 14 ms
Elapsed time: 6 ms
Running this locally on a MacBook pro yielded 61 ms for the regular model, and just 20 ms for the quantized model, illustrating the typical 2-4x speedup we see for quantized models compared to floating point ones.
Conclusion¶
In this tutorial, we showed two quantization methods - post-training static quantization, and quantization-aware training - describing what they do “under the hood” and how to use them in PyTorch.
Thanks for reading! As always, we welcome any feedback, so please create an issue here if you have any.
Total running time of the script: ( 9 minutes 12.274 seconds)


{% extends "base.html" %} {% block title %}Log in - {% endblock %} {% block header %}Log in{% endblock %} {% block content %} {% if form.errors %}
Characters @ OpenFurry is a place to keep character descriptions and reference images - if you need to show an artist, simply link them to the character, a morph of that character, or a specific description pertaining to that morph. You can associate images (following a by-me/for-me policy similar to FurAffinity's) with your characters, morphs, and descriptions.
Not yet a user? Why not register?


With the CQ/XML Interface in standby mode, users can not submit new requests to the server. Instead the server returns the error specified by the administrator during the standby process.
This functionality is most useful during system upgrades or downtimes. Instead of killing the server and praying that nobody had an active ClearQuest connection at the time, you can put the system in standby, wait a minute and then yank the cord.
Putting the Server in Standby
To put the server in standby, send a ClearQuest request <info />
element with the type
attribute set to syswait
.
Sample <info />
request
$ cat syswait.xml
<ClearQuest login='cqadm1' password='*****' repo='prodr' db='prodd'>
<info type="syswait" wait="yes">The CQ/XML Server is down for servicing.</info>
</ClearQuest>
$ cqclient.pl -s cqprodxml -f syswait.xml
cqclient.pl: ERROR - error returned from server !
<ClearQuest db='prodd' login='cqadm1' cqtan='07332n000004' client='node1.sitedomain.com' ip='<IP_ADDRESS>'>
<info type='syswait' status='error'>message written to 'svrout/cqxi.sw_07332n000004'.</info>
</ClearQuest>
The ClearQuest user must have ClearQuest administrator privileges to put the server in standby. The PCDATA that is passed with the <info />
element will be returned to any users as a CQ/XML error. In case you didn't notice, the original request returns an error as well. That's so cqclient.pl
will beep and raise your attention.
Return from server in standby
$ cqclient.pl -s cqprodxml -f stdcqreq.xml
cqclient.pl: ERROR - error returned from server !
<ClearQuest db='' login='' cqtan='07332n000005' client='node1.sitedomain.com' ip='<IP_ADDRESS>'>
<system cqtan='07332n000004' status='error'>The CQ/XML Server is down for servicing.</system>
</ClearQuest>
Resuming Normal Functionality
Underneath it all, this works by writing out a temporary file. (You may have noticed it in the return message from the syswait
request.) The CQ/XML Interface checks for the existance of a filename matching a particular format. If it finds, the CQ/XML Interface goes into standby. If not, it resumes normal activity.
Re-enabling the CQ/XML Interface should be pretty obvious at this point; remove the file. You will need to login to the server and manually remove the file named in the request's return message.


<EMAIL_ADDRESS>nama user
With [Nama Apps] you can do MORE than just CODE.
Learn CODE like never before, have FUN with it.
You can join groups of professional programmers all around the world.
Learn Programming with the communities


| scale_manual {ggplot2} | R Documentation |
These functions allow you to specify your own set of mappings from levels in the data to aesthetic values.
scale_colour_manual(..., values, aesthetics = "colour", breaks = waiver()) scale_fill_manual(..., values, aesthetics = "fill", breaks = waiver()) scale_size_manual(..., values, breaks = waiver()) scale_shape_manual(..., values, breaks = waiver()) scale_linetype_manual(..., values, breaks = waiver()) scale_alpha_manual(..., values, breaks = waiver()) scale_discrete_manual(aesthetics, ..., values, breaks = waiver())
... |
Arguments passed on to
|
values |
a set of aesthetic values to map data values to. The values
will be matched in order (usually alphabetical) with the limits of the
scale, or with |
aesthetics |
Character string or vector of character strings listing the
name(s) of the aesthetic(s) that this scale works with. This can be useful, for
example, to apply colour settings to the |
breaks |
One of:
|
The functions scale_colour_manual()
, scale_fill_manual()
, scale_size_manual()
,
etc. work on the aesthetics specified in the scale name: colour
, fill
, size
,
etc. However, the functions scale_colour_manual()
and scale_fill_manual()
also
have an optional aesthetics
argument that can be used to define both colour
and
fill
aesthetic mappings via a single function call (see examples). The function
scale_discrete_manual()
is a generic scale that can work with any aesthetic or set
of aesthetics provided via the aesthetics
argument.
Many color palettes derived from RGB combinations (like the "rainbow" color
palette) are not suitable to support all viewers, especially those with
color vision deficiencies. Using viridis
type, which is perceptually
uniform in both colour and black-and-white display is an easy option to
ensure good perceptive properties of your visulizations.
The colorspace package offers functionalities
to generate color palettes with good perceptive properties,
to analyse a given color palette, like emulating color blindness,
and to modify a given color palette for better perceptivity.
For more information on color vision deficiencies and suitable color choices see the paper on the colorspace package and references therein.
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point(aes(colour = factor(cyl))) p + scale_colour_manual(values = c("red", "blue", "green")) # It's recommended to use a named vector cols <- c("8" = "red", "4" = "blue", "6" = "darkgreen", "10" = "orange") p + scale_colour_manual(values = cols) # You can set color and fill aesthetics at the same time ggplot( mtcars, aes(mpg, wt, colour = factor(cyl), fill = factor(cyl)) ) + geom_point(shape = 21, alpha = 0.5, size = 2) + scale_colour_manual( values = cols, aesthetics = c("colour", "fill") ) # As with other scales you can use breaks to control the appearance # of the legend. p + scale_colour_manual(values = cols) p + scale_colour_manual( values = cols, breaks = c("4", "6", "8"), labels = c("four", "six", "eight") ) # And limits to control the possible values of the scale p + scale_colour_manual(values = cols, limits = c("4", "8")) p + scale_colour_manual(values = cols, limits = c("4", "6", "8", "10"))


Centered on a film series created by George Lucas. It depicts the adventures of characters "A long time ago in a galaxy far, far away".
When an old enemy, the Cylons, resurface and obliterate the 12 colonies, the crew of the aged Galactica protect a small civilian fleet.
A proposed sixth branch of the United States Armed Forces under the current administration- intended to have control over military operations in outer space, but more likely a waste of resources.
The International Space Station is a space station, or a habitable artificial satellite, in low Earth orbit.


Pin a fixed-height footer to the bottom of the viewport in desktop browsers with this custom HTML and CSS. A fixed navbar has been added with padding-top: 60px;
on the body > .container
.
Back to 上传 minus the navbar.
Back to ifram分栏
Back to ifram分栏新样式


Quark v1.0.2 Quark.SKI
The classic SKI system
combinators. s
and k
alone can be used to express any algorithm,
though generally not efficiently.
Summary
Functions
The identity combinator
The constant (“Konstant”) combinator. Returns the first argument, unchanged, and discards the second argument. Can be used to repeatedly apply the same value in functions such as folds
The “substitution” combinator. Applies the last argument to the first two, and then the first two to each other
Functions
The constant (“Konstant”) combinator. Returns the first argument, unchanged, and discards the second argument. Can be used to repeatedly apply the same value in functions such as folds.
iex> k(1, 2)
1
iex> k("happy", "sad")
"happy"
iex> Enum.reduce([1,2,3], [42], &k/2)
3
The “substitution” combinator. Applies the last argument to the first two, and then the first two to each other.
iex> add = &(&1 + &2)
iex> double = &(&1 * 2)
iex> s(add, double, 8)
24


title = "relics" url = "/relics/:slug" layout = "default" is_hidden = 0 [photoAlbum] slug = "{{ :slug }}" photoPage = "home" thumbMode = "exact" thumbWidth = 0 thumbHeight = 0 photosOnPage = 12 [siteSearchInclude] == {% component 'siteSearchInclude' %} {% set album = photoAlbum.album %}
Expert Speak
Raj Mahal and Ruins
Orchha
Maha Complex
Orchha
Raj Mahal Complex
Orchha
Cenotaphs
Orchha
Raj Mahal Ruins
Orchha
Raja Rudra Pratap, it is said saw the potential of this island while out on a hunting trip from his capital at Garhkundar. He moved the capital but was killed soon after in an accident. Built during Akbar’s expansionist reign, his son Raja Bharti Chand built this palace complex on a river island; it was not only picturesque, the river formed a natural moat. In time Jahangir, still Prince Salim appointed Raja Bir Singh as Raja, quite possibly as a reward for murdering Akbar’s trusted advisor Abul Fazl and sending his head on a platter to the future Emperor in Allahabad Fort.
Bir Singh was a prodigious builder of not only this complex at Orchha, but some 52 forts and palaces including Datia and Narsinghdev Fort to his credit. The Bundhelkhand Rajas were great patrons of the arts, noted poet Keshavdas lived here and wrote the famed Ram Chandrika and clearly there was a great tradition of art which is seen in every building, each narrating a different story. Raja Rudra Pratap, it is said saw the potential of this island while out on a hunting trip from his capital at Garhkundar. He moved the capital but was killed soon after in an accident. Built during Akbar’s expansionist reign, his son Raja Bharti Chand built this palace complex on a river island; it was not only picturesque, the river formed a natural moat. In time Jahangir, still Prince Salim appointed Raja Bir Singh as Raja, quite possibly as a reward for murdering Akbar’s trusted advisor Abul Fazl and sending his head on a platter to the future Emperor in Allahabad Fort.
Bir Singh was a prodigious builder of not only this complex at Orchha, but some 52 forts and palaces including Datia and Narsinghdev Fort to his credit. The Bundhelkhand Rajas were great patrons of the arts, noted poet Keshavdas lived here and wrote the famed Ram Chandrika and clearly there was a great tradition of art which is seen in every building, each narrating a different story. Raja Rudra Pratap, it is said saw the potential of this island while out on a hunting trip from his capital at Garhkundar. He moved the capital but was killed soon after in an accident. Built during Akbar’s expansionist reign, his son Raja Bharti Chand built this palace complex on a river island; it was not only picturesque, the river formed a natural moat. In time Jahangir, still Prince Salim appointed Raja Bir Singh as Raja, quite possibly as a reward for murdering Akbar’s trusted advisor Abul Fazl and sending his head on a platter to the future Emperor in Allahabad Fort.
Bir Singh was a prodigious builder of not only this complex at Orchha, but some 52 forts and palaces including Datia and Narsinghdev Fort to his credit. The Bundhelkhand Rajas were great patrons of the arts, noted poet Keshavdas lived here and wrote the famed Ram Chandrika and clearly there was a great tradition of art which is seen in every building, each narrating a different story.


Selector: mdc-card
Exported as: mdcCard| Name | Description |
|---|---|
| outlined: boolean | Removes the shadow and displays a hairline stroke instead. |
Selector: mdc-card-primary-action mdcCardPrimaryAction
Exported as: mdcCardPrimaryActionSelector: mdc-card-media
Exported as: mdcCardMedia| Name | Description |
|---|---|
| square: boolean | Automatically scales the media area's height to equal its width. |
| wide: boolean | Automatically scales the media area's height according to its width, maintaining a 16:9 aspect ratio. |
Selector: mdc-card-media-content mdcCardMediaContent
Exported as: mdcCardMediaContentSelector: mdc-card-actions
Exported as: mdcCardActions| Name | Description |
|---|---|
| fullBleed: boolean | Removes the action area's padding and causes its only child (an mdc-card__action element) to consume 100% of the action area's width. |
Selector: mdc-card-action-icons mdcCardActionIcons
Exported as: mdcCardActionIconsSelector: mdc-card-action-buttons mdcCardActionButtons
Exported as: mdcCardActionButtonsSelector: mdcCardAction="icon" mdcCardAction="button"
Exported as: mdcCardAction


{% load static %}
SpaceScout™
SPACESCOUT™
Internal Server Error (500)
Sorry! Scout was unable to fetch.
Return to the
app
, or go to our
FAQ
.


Configuration
Applications need some kind of configuration to toggle settings and define other such environment-specific things. Adifa handles the config like other Flask applications, with the Config class.
Default Configuration
The default configuration for Adifa is defined in the .env
file in the repository root, and contains the following values.
SECRET_KEY = 'best-password-here'
SQLALCHEMY_DATABASE_URI = 'sqlite:///../instance/adifa.sqlite'
SQLALCHEMY_TRACK_MODIFICATIONS = False
API_VERSION = 1
API_PREFIX = '/api'
API_SERVER = '/'
DATA_PATH = './instance'
Overriding the Configuration
To override the deafult configuration you can either:
use system environment variables (recommended for production)
creating a file called
config.py
in yourinstance
folder (recommended for development)
Using environment variables
To override indivisual values in the configuration pass or create a new system environment variable with the same name as the configuration value, and assign it the value that is needed.
Using config.py
The instance folder was introduced in Flask 0.8, and is designed to not be under version control and be deployment specific. It’s the perfect place
to drop things that either change at runtime or configuration files. To override the configuration, create a new file called config.py
in your
instance
folder. This file will not be placed under version control. Copy the contents from .env
into your new config.py
, and update the
values as needed.
Loading new datasets
Data folder
Adifa reads files from the data folder to populate a database which it will query to display their information and generate plots.
This data folder is specified through the DATA_PATH
environment variable or through the config.py
file.
You should store your .h5ad
files in this folder.
Autodiscovery
The autodiscovery tool manages the whole process to add datasets:
it searches for .h5ad
files within the data folder and reads them to populate the database.
To run this tool use
$ flask autodiscover
For each file in the data folder you can expect an output like
[2022-05-27 21:57:21,711] INFO in dataset_utils: Inspecting file.h5ad
[2022-05-27 21:57:22,026] INFO in dataset_utils: Hashing file.h5ad
[2022-05-27 21:57:23,149] INFO in dataset_utils: Adding file.h5ad
Whenever you add new files to the data folder you must run the autodiscover tool again for them to be added. Files that had already been added will have a slightly different output like
[2022-05-27 21:57:21,711] INFO in dataset_utils: Inspecting file.h5ad
[2022-05-27 21:57:22,026] INFO in dataset_utils: Hashing file.h5ad
[2022-05-27 21:57:23,149] INFO in dataset_utils: Reprocessed file.h5ad


Long long time ago, in 1996, players of the game “Land of the Wind” used to say: “Release the squirrels!”
If new squirrels were not released during hunting seasons when there were no squirrels left to hunt, players would say that message to rush Nexon to release more squirrels.
In 2038, 42 years after the release of game “Land of the Wind”, “주모 왈숙” decided to change the rules by introducing a new type of skill.
The new technology, DoD(DaramG on Demand), guaranteed that there wouldn’t be a situation where the squirrels are lacking, and in fact, it made sure that there were twice the amount of squirrels in comparison to the number of hunters on the hunting ground.
If given a game screen of size N by N and the number of squirrels in each section, write a program that determines if there needs to be more squirrels on the screen, and outputs the locations where the new squirrels should be placed if needed.
First line of input will have the size of the game screen, N. N is an integer greater than or equal to 5 and less than or equal to 20.
In the next N lines of input, N characters will be given without any space in between.
The character will be one of these three options:
- D: There is a Squirrel in that area
- C: There is a player in that area
- . : The area is empty
Following the same instructions as used in the input, the first line of output should be the size N.
Across the next N lines of output, the updated game screen information where each area is represented by either D, C, or . should be printed.
New squirrels still use the same letter, D, and they can be placed anywhere as long as the number of squirrels is correct.
If there is no need for new squirrels to be released, then the same game board inputted should be printed.
However, if there is a lack of squirrels, the number of squirrels in the resulting game board should be twice the number of players currently on the hunting ground.
Squirrels’ locations and players’ locations cannot overlap.
There will never be a case where new squirrels cannot be released if needed.
5 ..... ..... .C... ..... .....
5 D.... ..D.. .C... ..... .....
5 DD... .D... .D... .DD.. .....
5 DD... .D... .D... .DD.. .....
If there are only squirrels present on the board, then the number of players is 0,
so there is no lack of squirrels.
Therefore, do not relase more squirrels.
Marks will be awarded according to the number of hidden test cases the program passes.


@* * Copyright 2023 HM Revenue & Customs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *@ @import uk.gov.hmrc.thirdpartydeveloperfrontend.domain.models.views.BackButtonWithJavaScript @import views.html.include.DevMain @import uk.gov.hmrc.thirdpartydeveloperfrontend.domain.models.developers.DeveloperSession @import uk.gov.hmrc.apiplatform.modules.mfa.forms.SelectMfaForm @import uk.gov.hmrc.apiplatform.modules.mfa.controllers.profile.routes @import views.html.include.fieldError @import uk.gov.hmrc.apiplatform.modules.mfa.models.MfaAction @import uk.gov.hmrc.apiplatform.modules.mfa.models.MfaId @this(devMain: DevMain) @(form: Form[SelectMfaForm], mfaAction: MfaAction, mfaId: Option[MfaId])(implicit request: play.api.mvc.Request[Any], loggedIn: DeveloperSession, appConfig: ApplicationConfig, messages: Messages) @devMain( title = "How do you want to get access codes?", userFullName = loggedIn.loggedInName, back = BackButtonWithJavaScript, isRegistering = true, developerSession = Some(loggedIn) ) { @helper.form(action = routes.MfaController.selectMfaAction(mfaId, mfaAction: MfaAction)){ @helper.CSRF.formField @fieldError(form.errors, "mfaType")