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