From 1339cf70b494c203f70c63113dcab84d23f28c84 Mon Sep 17 00:00:00 2001
From: Yifan Zhao <yifanz16@illinois.edu>
Date: Thu, 8 Jul 2021 06:28:42 -0500
Subject: [PATCH] Finalizing miniera

---
 .../projects/torch2hpvm/torch2hpvm/compile.py |  15 ++++-----
 .../torch2hpvm/torch2hpvm/graph_ir.py         |   3 ++
 .../test/epoch_dnn/assets/miniera/miniera.pth | Bin 1911195 -> 1908903 bytes
 .../assets/miniera/scales/calib_NONE.txt      |   3 +-
 hpvm/test/epoch_dnn/main.py                   |  26 +++++++++------
 hpvm/test/epoch_dnn/torch_dnn/miniera.py      |   8 ++---
 hpvm/test/epoch_dnn/torch_dnn/quantizer.py    |  30 ++++++++++++------
 7 files changed, 52 insertions(+), 33 deletions(-)

diff --git a/hpvm/projects/torch2hpvm/torch2hpvm/compile.py b/hpvm/projects/torch2hpvm/torch2hpvm/compile.py
index 06c9a1c297..f76b801ef9 100644
--- a/hpvm/projects/torch2hpvm/torch2hpvm/compile.py
+++ b/hpvm/projects/torch2hpvm/torch2hpvm/compile.py
@@ -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):
diff --git a/hpvm/projects/torch2hpvm/torch2hpvm/graph_ir.py b/hpvm/projects/torch2hpvm/torch2hpvm/graph_ir.py
index 3ddcb18685..06907cd277 100644
--- a/hpvm/projects/torch2hpvm/torch2hpvm/graph_ir.py
+++ b/hpvm/projects/torch2hpvm/torch2hpvm/graph_ir.py
@@ -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):
diff --git a/hpvm/test/epoch_dnn/assets/miniera/miniera.pth b/hpvm/test/epoch_dnn/assets/miniera/miniera.pth
index f264a4ea67f88c361923e7c7f0208703001a622d..6796117d7821bc9574ffbb7194f21a03dc559e73 100644
GIT binary patch
delta 1000
zcmZ9LL2DC16vt<FH@n-kNvkG}Rf9PM>?tO@+s*F9P7$;kvwAZiC6yXXBxEzmMo=gX
z2v$6$ERurWJVfyz;z8*teg?mRH$Q*}C4z4{N$O_cx9t1B`Olkqv-|qHH~8l5b%mGV
z`smHI->FCM?yO|GLh+{v&s~aEOG^l$X3O>59k*V7TxqpyZi8Z&P>MjNUh}+qrCez|
zsNHW+;YBG7qW7d(DYt7+D{U(7Qfajm0<m3nTOJJ+gpvqRt>IC*AeAJxM%ZmUq)I{R
zbTS9zc~Z9x)6lbqt!uVP<>UD~%??$H+oE&u<5p}k!OG_igDzH;Ldx+sFo{nRhN<f&
zO;nX4N<025T%20gayDcxa?7t1i)Q5v&7hZ<Wz+F*2qzZH(k+8t1`Bcg_rggP%g*LC
znrD`t<Npm<G+1n%Teie2iOI$^+t#x5dR4iJh>RTnlX!KqX)SM?ra^DB<*?(c()u)G
z+F8q@cUdOn`1hoZ>2d6wp3`U<3Wc1{AEgf%u5ydrS`_+fkE?3jkAosFCj>>_1u-BR
zeA7};<Sh;b#kJfXYp)zs`86psqQzRI&Wfznb2H*`o5MkoAAEjB)U_ylF$<LpVqYAL
z0l~?bitsoZjxI<l+T_tQayTG|IHj_yU}K+*L_h*UfD9;r3d{jvU>;ZiB0v;a1eSnP
zz-izN5Ch`CGLQhy0xQ5d;Jk{`y!zXr+*4Eh9r>i+od$i9PIgr?P7Q{CMEqa$6=N0k
hcygZ{VL%v=Bax`%BcuK>4rktGe;JG~WI5T5`~!|*^0EK`

delta 3325
zcmbtXUuY9a7@y5<l3ulGw5i&(xfb=*gI>Gc&2AC|6C>orSX>41j1Onko~b?5^?JT;
zK(GovR2D4$;U3P1Aj0_|hl-*er=Tbr$@z5nARZ{(z1)eQr^tOXn@wl7Ngw96{Mb!*
zzTf<2zRd6YQvPM)Y;_`I<V6XgmKHSY3ZsQ+<k0To*x}K^JtGq%0|&>y&1FNVz+5Mf
zAMJ^&Lvlh@H8qjcbTjak2wb}les2bccZznuu(87(5Le<U-E1!g^L_RSArm40mt{rO
zhV-PlxELHp%kAgFup6LgDP2{}UJ{V?)dEyim1P--R^ov5_O!1a055boX%0d_#(ql$
zB(#(wn+Y5+We<xp0SR5xWiw3zeiGL#a-}Mo3`AcRgBwsLfNW7(=ct8cDw-Zonwv0}
zVDFI9GcOW2W*dItw=YR+-DPM>GCri6U&AE+%aZ@1@QslVwwySAe;^t_2%~uxx>1bD
z!eENov|P5!lMpI9<=@lMDGSf_a9i{lnu4?d=9(+fXf9QC@{r4_&F7+~WgmsY^oz&Q
z5|MFg^y%FasKDTm;iR2c#*qiT<p;e&A5n7q%+gh;Pe2%aO5l6OtEBw?NmL**ZoG=U
zL!bhKM+W&?^U9bMt5HCAS*-SCh@49o{+dJ?AHv|0gAWt0obu~RZC7^fn0oX-b_EV7
zoI_q2lhw2U@Bu`C1n>g_fFPg+@Cl$5Fb^;v&;|$r+5us}0>DB*2Ot9I1S|q91}p(|
zk<H%RF{vQb1<CK?*=)y#cvpG;*KUcZh@D$eMNk1SI8v@D*z>qmTn|QwidR=&{)i%=
z0>GsL8!Vp+t1!Ow&1xyM>ekJF{#+xS4&T~vCzHmedF<nz6*WzBidxp#G@Wvo%l6J~
zn!ZBe%F2#dsf}zz`0t;8E46_`0(drpR9L=^0HYqS)QtMx_O|{KqywY5jCQoHzVySR
zhrSy-us@eg&%Io>I5hZV71UO|`%iw@yA_-WVHkM<-|B4&T{^?~1#@|u7H>3N47-`g
zeNc)mI^0ujy-ajHW0@+aP<?A~^Hd{pOR1NLDn}ObFikgMa;l-1iLNLtk#SXMEXiq_
zTq4Wu1?ghIGF7fMV<Pp)WpasVJ7<ZEf5lmnEYeG=q%ZR_(U!?FRo>5La+*?747^0N
z>#;<J^`U2$CYK5NJ}(pPTr5+iap#5`NbH$jBG+v!Y&k5G(b=#E#7x-tyiBx*uta|}
zB_gdmA+lh4H*>TNuuPR@%_I>A@=Br)b31e7D$8exh==Ssu;0iB0{yj7pNe19$A@Z9
m?)@eHp#j&H&tl{baY6i$`rP@98!IF4heM4}2>$SRXY7A6s@<Ic

diff --git a/hpvm/test/epoch_dnn/assets/miniera/scales/calib_NONE.txt b/hpvm/test/epoch_dnn/assets/miniera/scales/calib_NONE.txt
index 9bffa5b8d0..f99b4434b6 100644
--- a/hpvm/test/epoch_dnn/assets/miniera/scales/calib_NONE.txt
+++ b/hpvm/test/epoch_dnn/assets/miniera/scales/calib_NONE.txt
@@ -1,4 +1,4 @@
-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
-
diff --git a/hpvm/test/epoch_dnn/main.py b/hpvm/test/epoch_dnn/main.py
index fb02a15f40..2a66aae200 100644
--- a/hpvm/test/epoch_dnn/main.py
+++ b/hpvm/test/epoch_dnn/main.py
@@ -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)")
diff --git a/hpvm/test/epoch_dnn/torch_dnn/miniera.py b/hpvm/test/epoch_dnn/torch_dnn/miniera.py
index 84c88ac8f8..3e20067318 100644
--- a/hpvm/test/epoch_dnn/torch_dnn/miniera.py
+++ b/hpvm/test/epoch_dnn/torch_dnn/miniera.py
@@ -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)
diff --git a/hpvm/test/epoch_dnn/torch_dnn/quantizer.py b/hpvm/test/epoch_dnn/torch_dnn/quantizer.py
index 8543f80603..7bbbbda45c 100644
--- a/hpvm/test/epoch_dnn/torch_dnn/quantizer.py
+++ b/hpvm/test/epoch_dnn/torch_dnn/quantizer.py
@@ -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
-- 
GitLab