Skip to content
Snippets Groups Projects
Commit 1339cf70 authored by Yifan Zhao's avatar Yifan Zhao
Browse files

Finalizing miniera

parent 34c63e9c
No related branches found
No related tags found
No related merge requests found
......@@ -62,18 +62,17 @@ class ModelExporter:
def export_datasets(self, n_images: Optional[int]):
from PIL import Image
from math import log10, ceil
labels = []
n_images = self.dataset_size if n_images is None else n_images
n_digits = int(ceil(log10(n_images)))
for i in range(n_images):
if n_images:
indices = torch.randperm(self.dataset_size)[:n_images].numpy()
else:
indices = range(self.dataset_size)
n_digits = int(ceil(log10(self.dataset_size)))
for i in indices:
image, label = self.dataset[i]
image = (image - image.min()) / (image.max() - image.min()) * 255
image = image.transpose((1, 2, 0)).astype(np.uint8)
name = str(i).zfill(n_digits)
Image.fromarray(image).save(self.dataset_dir / f"{name}.jpg")
labels.append(label)
np.array(labels).tofile(self.output_dir / self.label_name)
Image.fromarray(image).save(self.dataset_dir / f"{name}_{label}.jpg")
return self
def compile(self, output_binary: PathLike, working_dir: Optional[PathLike] = None):
......
......@@ -105,6 +105,9 @@ class WeightTensor(TensorNode):
def dump_weight(self, file_name: PathLike, to_fp16: bool = False):
data = self.input_data.astype(np.float16) if to_fp16 else self.input_data
if len(data.shape) == 2:
# weight tensor needs to be transposed
data = data.T
data.tofile(file_name)
def transpose_(self):
......
No preview for this file type
nput: 0.007874015748031496
input: 0.007874015748031496
conv1: 0.041152522047244094
add1: 0.041152522047244094
relu1: 0.03673215196850394
......@@ -19,4 +19,3 @@ relu5: 0.20020370078740157
gemm2: 0.6007037007874017
add6: 0.6007037007874017
softmax1: 0.007874015748031496
......@@ -9,7 +9,7 @@ from torch2hpvm import BinDataset, ModelExporter
self_folder = Path(__file__).parent.absolute()
site.addsitedir(self_folder.as_posix())
from torch_dnn import MiniERA, quantize
from torch_dnn import MiniERA, quantize, CIFAR
# Consts (don't change)
......@@ -38,7 +38,7 @@ def run_test_over_ssh(host: str, password: str, working_dir: str, image_dir: Pat
print(f"Running test on remote host {host}...")
args = options.split(" ") + [host]
child = pexpect.spawn("ssh", args)
child = pexpect.spawn("ssh", args, timeout=60)
child.expect(r"password:")
child.sendline(password)
child.expect("# ") # The bash prompt
......@@ -62,7 +62,7 @@ def run_test_over_ssh(host: str, password: str, working_dir: str, image_dir: Pat
ASSET_DIR = self_folder / "assets/miniera"
QUANT_STRAT = "NONE" # Quantization method
WORKING_DIR = Path("/tmp/miniera")
N_IMAGES = 100
N_IMAGES = 50
# Remote configs
SCP_OPTS = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 5506"
SSH_OPTS = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 5506"
......@@ -70,14 +70,17 @@ SCP_HOST = "root@espgate.cs.columbia.edu"
SCP_PWD = "openesp"
SCP_DST = "~/NV_NVDLA"
# Reproducibility
np.random.seed(42)
makedirs(WORKING_DIR, exist_ok=True)
makedirs(WORKING_DIR, exist_ok=False)
# Calculate quantization scales
ckpt = (ASSET_DIR / "miniera.pth").as_posix()
model = MiniERA()
model.load_state_dict(torch.load(ckpt))
scale_output = quantize(model, ASSET_DIR, QUANT_STRAT, WORKING_DIR)
dataset = CIFAR.from_file(ASSET_DIR / "input.bin", ASSET_DIR / "labels.bin")
scale_output = quantize(model, dataset, QUANT_STRAT, WORKING_DIR)
# Code generation (into /tmp/miniera/hpvm-mod.nvdla)
nvdla_buffer = WORKING_DIR / BUFFER_NAME
......@@ -85,7 +88,7 @@ print(f"Generating NVDLA buffer into {nvdla_buffer}")
bin_dataset = BinDataset(
ASSET_DIR / "input.bin", ASSET_DIR / "labels.bin", (5000, 3, 32, 32)
)
exporter = ModelExporter(model, bin_dataset, WORKING_DIR, scale_output)
exporter = ModelExporter(model, bin_dataset, WORKING_DIR, ASSET_DIR / "scales/calib_NONE.txt")
exporter.generate(n_images=N_IMAGES).compile(WORKING_DIR / "miniera", WORKING_DIR)
# SCP essential files to remote device
......@@ -93,8 +96,11 @@ input_images = exporter.dataset_dir
split_and_scp([nvdla_buffer, input_images], SCP_HOST, SCP_DST, SCP_PWD, SCP_OPTS)
# SSH to run test remotely
labels_file = WORKING_DIR / exporter.label_name
labels = np.fromfile(labels_file, dtype=np.int32)
n_total, correct = 0, 0
for image_path, output in run_test_over_ssh(SCP_HOST, SCP_PWD, SCP_DST, input_images, SSH_OPTS):
idx = int(image_path.stem)
print(idx, np.array(output).argmax(), labels[idx])
idx, label = [int(s) for s in image_path.stem.split("_")]
output = np.array(output)
print(idx, output.argmax(), label, output)
n_total += 1
correct += int(output.argmax() == label)
print(f"Accuracy: {correct / n_total * 100}% ({n_total} images)")
......@@ -36,8 +36,8 @@ class MiniERA(Module):
for conv in self.convs:
if not isinstance(conv, Conv2d):
continue
weight_np = np.fromfile(prefix / "conv2d_{count+1}_w.bin", dtype=np.float32)
bias_np = np.fromfile(prefix / "conv2d_{count+1}_b.bin", dtype=np.float32)
weight_np = np.fromfile(prefix / f"conv2d_{count+1}_w.bin", dtype=np.float32)
bias_np = np.fromfile(prefix / f"conv2d_{count+1}_b.bin", dtype=np.float32)
conv.weight.data = torch.tensor(weight_np).reshape(conv.weight.shape)
conv.bias.data = torch.tensor(bias_np).reshape(conv.bias.shape)
count += 1
......@@ -46,8 +46,8 @@ class MiniERA(Module):
for linear in self.fcs:
if not isinstance(linear, Linear):
continue
weight_np = np.fromfile(prefix / "dense_{count+1}_w.bin", dtype=np.float32)
bias_np = np.fromfile(prefix / "dense_{count+1}_b.bin", dtype=np.float32)
weight_np = np.fromfile(prefix / f"dense_{count+1}_w.bin", dtype=np.float32)
bias_np = np.fromfile(prefix / f"dense_{count+1}_b.bin", dtype=np.float32)
cout, cin = linear.weight.shape
linear.weight.data = torch.tensor(weight_np).reshape(cin, cout).T
linear.bias.data = torch.tensor(bias_np).reshape(linear.bias.shape)
......
......@@ -6,6 +6,7 @@ from shutil import move
import distiller
import torch
from torch.utils.data.dataset import Dataset
import yaml
from distiller.data_loggers import collect_quant_stats
from distiller.quantization import PostTrainLinearQuantizer
......@@ -13,7 +14,6 @@ from torch import nn
from torch.utils.data import DataLoader
from .datasets import CIFAR
from .miniera import MiniERA
PathLike = Union[str, Path]
STATS_FILENAME = "acts_quantization_stats.yaml"
......@@ -38,15 +38,13 @@ LAYER_DISTILLER_NAME = {
def quantize(
model: nn.Module,
dataset_path: PathLike,
dataset: Dataset,
strat: str = "NONE",
working_dir: PathLike = ".",
output_name: str = "calib.txt",
):
# possible quant strats ['NONE', 'AVG', 'N_STD', 'GAUSS', 'LAPLACE']
print("Quantizing...")
dataset_path = Path(dataset_path)
dataset = CIFAR.from_file(dataset_path / "input.bin", dataset_path / "labels.bin")
dataloader = DataLoader(dataset, batch_size=1)
# Collect Pre Quantization Stats
......@@ -57,12 +55,13 @@ def quantize(
if not os.path.isfile(stats_file):
# generates `stats_file`
collect_quant_stats(
model, lambda model: evaluate(model, dataloader), save_dir=working_dir
model, lambda model: get_loss(model, dataloader), save_dir=working_dir
)
# Generate Quantized Scales
new_model = deepcopy(model)
quantizer = PostTrainLinearQuantizer(
deepcopy(model),
new_model,
model_activation_stats=stats_file,
mode="SYMMETRIC",
bits_activations=8,
......@@ -77,7 +76,7 @@ def quantize(
# We don't need QUANT_AFTER_FILENAME, remove it
Path(QUANT_AFTER_FILENAME).unlink()
print("Quantization process finished.")
print(f"Quantization process finished; accuracy {evaluate(new_model, dataloader)}%")
# converts .yaml file stats to hpvm standard
generate_calib_file(model, working_dir, working_dir / output_name)
return working_dir / output_name
......@@ -96,7 +95,6 @@ def generate_calib_file(model: nn.Module, working_dir: Path, output_file: Path):
input_scale = max(abs(input_min_max["min"]), abs(input_min_max["max"])) / 127
lines.append(f"input:\t{input_scale}\n")
# because of definition of miniera
layer_count = {
nn.ReLU: 0,
nn.Linear: 0,
......@@ -131,7 +129,7 @@ def generate_calib_file(model: nn.Module, working_dir: Path, output_file: Path):
@torch.no_grad()
def evaluate(model: MiniERA, dataloader: DataLoader):
def get_loss(model: nn.Module, dataloader: DataLoader):
from torch.nn import functional as F
# Turn on evaluation mode which disables dropout.
......@@ -142,3 +140,17 @@ def evaluate(model: MiniERA, dataloader: DataLoader):
output = model(data)
total_loss += len(data) * F.cross_entropy(output, targets)
return total_loss / len(dataloader)
@torch.no_grad()
def evaluate(model: nn.Module, dataloader: DataLoader):
model.eval()
correct = 0
total = 0
for data in dataloader:
images, labels = data[0], data[1]
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
return 100 * correct / total
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment