diff --git a/hpvm/test/epoch_dnn/assets/miniera/miniera.pth b/hpvm/test/epoch_dnn/assets/miniera/miniera.ckpt similarity index 99% rename from hpvm/test/epoch_dnn/assets/miniera/miniera.pth rename to hpvm/test/epoch_dnn/assets/miniera/miniera.ckpt index 6796117d7821bc9574ffbb7194f21a03dc559e73..7bf0e767bd063e42c16aa10f6eca4649f553efc9 100644 Binary files a/hpvm/test/epoch_dnn/assets/miniera/miniera.pth and b/hpvm/test/epoch_dnn/assets/miniera/miniera.ckpt differ diff --git a/hpvm/test/epoch_dnn/miniera.py b/hpvm/test/epoch_dnn/miniera.py new file mode 100644 index 0000000000000000000000000000000000000000..398731d1ed4578bec7c3a2d9259db218f8310de1 --- /dev/null +++ b/hpvm/test/epoch_dnn/miniera.py @@ -0,0 +1,62 @@ +import site +import sys +from os import makedirs +from pathlib import Path + +import numpy as np +from torch2hpvm import ModelExporter + +self_folder = Path(__file__).parent.absolute() +site.addsitedir(self_folder.as_posix()) + +from torch_dnn import BUFFER_NAME, quantize, run_test_over_ssh, split_and_scp +from torch_dnn.miniera import MiniEraPL + +# Local configs +ASSET_DIR = self_folder / "assets/miniera" +QUANT_STRAT = "NONE" # Quantization method +WORKING_DIR = Path("/tmp/miniera") +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" +SCP_HOST = "root@espgate.cs.columbia.edu" +SCP_PWD = "openesp" +SCP_DST = "~/NV_NVDLA" + +if len(sys.argv) > 1: + WORKING_DIR = Path(sys.argv[1]) + +# Reproducibility +np.random.seed(42) + +# Create working directory +makedirs(WORKING_DIR, exist_ok=True) + +# Calculate quantization scales +ckpt = (ASSET_DIR / "miniera.ckpt").as_posix() +model: MiniEraPL = MiniEraPL.load_from_checkpoint(ckpt, dataset_path=ASSET_DIR) +scale_output = quantize(model, QUANT_STRAT, WORKING_DIR) + +# Code generation (into /tmp/miniera/hpvm-mod.nvdla) +nvdla_buffer = WORKING_DIR / BUFFER_NAME +print(f"Generating NVDLA buffer into {nvdla_buffer}") +# You may replace scale_output (below) with ASSET_DIR / "scales/calib_NONE.txt" +# to use precomputed quantization scale +dataset = model.test_dataloader().dataset +exporter = ModelExporter(model, 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 +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 +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, 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/__init__.py b/hpvm/test/epoch_dnn/torch_dnn/__init__.py index 89b65549bac66433f39965c5958c382786734637..af88db3e238fe29ac07128e9e195e59d0d5eec7c 100644 --- a/hpvm/test/epoch_dnn/torch_dnn/__init__.py +++ b/hpvm/test/epoch_dnn/torch_dnn/__init__.py @@ -1 +1,2 @@ from .quantizer import quantize +from ._utils import split_and_scp, run_test_over_ssh, BUFFER_NAME diff --git a/hpvm/test/epoch_dnn/torch_dnn/_utils.py b/hpvm/test/epoch_dnn/torch_dnn/_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..630100bb461c091d1ae1dd37c8e8b7f52472da47 --- /dev/null +++ b/hpvm/test/epoch_dnn/torch_dnn/_utils.py @@ -0,0 +1,47 @@ +from pathlib import Path +import pexpect + +# This is hardcoded in the NVDLA pass and will not change any time soon +BUFFER_NAME = "hpvm-mod.nvdla" + + +def split_and_scp( + local_srcs: list, host: str, remote_dst: str, password: str, options: str +): + print(f"Copying files to remote host {host}...") + args = options.split(" ") + local_srcs = [str(s) for s in local_srcs] + args += ["-r", *local_srcs, f"{host}:{remote_dst}"] + child = pexpect.spawn("scp", args, timeout=None) + child.expect(r"password:") + child.sendline(password) + # A rough approach to at least print something when scp is alive + for line in child: + print(line.decode(), end="") + + +def run_test_over_ssh( + host: str, password: str, working_dir: str, image_dir: Path, options: str +): + print(f"Running test on remote host {host}...") + args = options.split(" ") + [host] + child = pexpect.spawn("ssh", args, timeout=None) + child.expect(r"password:") + child.sendline(password) + child.expect("# ") # The bash prompt + child.sendline(f"cd {working_dir}") + child.expect("# ") + child.delimiter = "# " + for image in image_dir.glob("*"): + remote_path = f"{image_dir.name}/{image.name}" + print(f"Sending {image.name} to run") + child.sendline( + f"./nvdla_runtime --loadable {BUFFER_NAME} --image {remote_path} --rawdump" + ) + child.expect("# ") + child.sendline("cat output.dimg") + child.expect("# ") + result_lines = child.before.decode().splitlines() + # Should have 2 lines. First line is the command we keyed in. + output = [int(s) for s in result_lines[1].strip().split()] + yield image, output diff --git a/hpvm/test/epoch_dnn/torch_dnn/miniera/__init__.py b/hpvm/test/epoch_dnn/torch_dnn/miniera/__init__.py index d135d1bd401e89b8852e49d0d965b8ba47c87bf8..5ed605176e3f4be516cb00f81d8ee5a8bc323349 100644 --- a/hpvm/test/epoch_dnn/torch_dnn/miniera/__init__.py +++ b/hpvm/test/epoch_dnn/torch_dnn/miniera/__init__.py @@ -1,2 +1 @@ -from .dataset import CIFAR -from .model import MiniERA +from .model import MiniEraPL diff --git a/hpvm/test/epoch_dnn/torch_dnn/miniera/model.py b/hpvm/test/epoch_dnn/torch_dnn/miniera/model.py index 0f6fe455d8ebf56f68886d54749c5e9b29bdf9c8..b03df902de34380cc027e58e179c9ffadead1144 100644 --- a/hpvm/test/epoch_dnn/torch_dnn/miniera/model.py +++ b/hpvm/test/epoch_dnn/torch_dnn/miniera/model.py @@ -2,27 +2,33 @@ from pathlib import Path from typing import Union import numpy as np +import pytorch_lightning as pl import torch -from torch.nn import Conv2d, Linear, MaxPool2d, Module, ReLU, Sequential, Softmax +from torch import nn +from torch.utils.data import DataLoader +from .dataset import CIFAR -class MiniERA(Module): +PathLike = Union[Path, str] + + +class MiniERA(nn.Module): def __init__(self): super().__init__() - self.convs = Sequential( - Conv2d(3, 32, 3), - ReLU(), - Conv2d(32, 32, 3), - ReLU(), - MaxPool2d(2, 2), - Conv2d(32, 64, 3), - ReLU(), - Conv2d(64, 64, 3), - ReLU(), - MaxPool2d(2, 2), + self.convs = nn.Sequential( + nn.Conv2d(3, 32, 3), + nn.ReLU(), + nn.Conv2d(32, 32, 3), + nn.ReLU(), + nn.MaxPool2d(2, 2), + nn.Conv2d(32, 64, 3), + nn.ReLU(), + nn.Conv2d(64, 64, 3), + nn.ReLU(), + nn.MaxPool2d(2, 2), ) - self.fcs = Sequential(Linear(1600, 256), ReLU(), Linear(256, 5)) - self.softmax = Softmax(1) + self.fcs = nn.Sequential(nn.Linear(1600, 256), nn.ReLU(), nn.Linear(256, 5)) + self.softmax = nn.Softmax(1) def forward(self, input): outputs = self.convs(input) @@ -34,7 +40,7 @@ class MiniERA(Module): # Load in model convolution weights count = 0 for conv in self.convs: - if not isinstance(conv, Conv2d): + if not isinstance(conv, nn.Conv2d): continue weight_np = np.fromfile( prefix / f"conv2d_{count+1}_w.bin", dtype=np.float32 @@ -46,7 +52,7 @@ class MiniERA(Module): # Load in model fc weights count = 0 for linear in self.fcs: - if not isinstance(linear, Linear): + if not isinstance(linear, nn.Linear): continue 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) @@ -55,3 +61,47 @@ class MiniERA(Module): linear.bias.data = torch.tensor(bias_np).reshape(linear.bias.shape) count += 1 return self + + +class MiniEraPL(pl.LightningModule): + def __init__(self, dataset_path: PathLike): + super().__init__() + self.network = MiniERA() + self.dataset_path = Path(dataset_path) + + def forward(self, image): + prediction = self.network(image) + return prediction + + @staticmethod + def _get_loss(output, targets): + from torch.nn.functional import cross_entropy + + return cross_entropy(output, targets) + + @staticmethod + def _get_metric(output, targets): + predicted = torch.argmax(output, 1) + return (predicted == targets).sum().item() / len(targets) + + def validation_step(self, val_batch, batch_idx): + images, target = val_batch + prediction = self(images) + loss = self._get_loss(prediction, target) + accuracy = self._get_metric(prediction, target) + self.log("val_loss", loss) + self.log("val_acc", accuracy) + return accuracy + + def test_step(self, test_batch, batch_idx): + images, target = test_batch + prediction = self(images) + accuracy = self._get_metric(prediction, target) + self.log("test_acc", accuracy) + return accuracy + + def test_dataloader(self): + dataset = CIFAR.from_file( + self.dataset_path / "input.bin", self.dataset_path / "labels.bin" + ) + return DataLoader(dataset, batch_size=128) diff --git a/hpvm/test/epoch_dnn/yolo.py b/hpvm/test/epoch_dnn/yolo.py index aa1ac3421021effec094295322a89f8095b0ef7f..16c01fd28706afebb338a4e088bcc8950ddc3eb3 100644 --- a/hpvm/test/epoch_dnn/yolo.py +++ b/hpvm/test/epoch_dnn/yolo.py @@ -9,59 +9,9 @@ from torch2hpvm import ModelExporter self_folder = Path(__file__).parent.absolute() site.addsitedir(self_folder.as_posix()) -from torch_dnn import quantize +from torch_dnn import BUFFER_NAME, quantize, run_test_over_ssh, split_and_scp from torch_dnn.yolo import TinyYoloPL -# Consts (don't change) -BUFFER_NAME = "hpvm-mod.nvdla" - - -def split_and_scp( - local_srcs: list, host: str, remote_dst: str, password: str, options: str -): - import pexpect - - print(f"Copying files to remote host {host}...") - args = options.split(" ") - local_srcs = [str(s) for s in local_srcs] - args += ["-r", *local_srcs, f"{host}:{remote_dst}"] - child = pexpect.spawn("scp", args, timeout=None) - child.expect(r"password:") - child.sendline(password) - # A rough approach to at least print something when scp is alive - for line in child: - print(line.decode(), end="") - - -def run_test_over_ssh( - host: str, password: str, working_dir: str, image_dir: Path, options: str -): - import pexpect - - print(f"Running test on remote host {host}...") - args = options.split(" ") + [host] - child = pexpect.spawn("ssh", args, timeout=None) - child.expect(r"password:") - child.sendline(password) - child.expect("# ") # The bash prompt - child.sendline(f"cd {working_dir}") - child.expect("# ") - child.delimiter = "# " - for image in image_dir.glob("*"): - remote_path = f"{image_dir.name}/{image.name}" - print(f"Sending {image.name} to run") - child.sendline( - f"./nvdla_runtime --loadable {BUFFER_NAME} --image {remote_path} --rawdump" - ) - child.expect("# ") - child.sendline("cat output.dimg") - child.expect("# ") - result_lines = child.before.decode().splitlines() - # Should have 2 lines. First line is the command we keyed in. - output = [int(s) for s in result_lines[1].strip().split()] - yield image, output - - # Local configs ASSET_DIR = self_folder / "assets/yolo" QUANT_STRAT = "NONE" # Quantization method @@ -84,17 +34,16 @@ np.random.seed(42) makedirs(WORKING_DIR, exist_ok=True) # Calculate quantization scales -# model = TinyYoloPL(num_classes=12) ckpt = (ASSET_DIR / "yolo_atrv2.ckpt").as_posix() model = TinyYoloPL.load_from_checkpoint( ckpt, num_classes=12, dataset_path=ASSET_DIR / "atr_dataset.tar.gz" ) -dataset = model.test_dataloader().dataset scale_output = quantize(model, QUANT_STRAT, WORKING_DIR, gpus=1) # Code generation (into /tmp/miniera/hpvm-mod.nvdla) nvdla_buffer = WORKING_DIR / BUFFER_NAME print(f"Generating NVDLA buffer into {nvdla_buffer}") +dataset = model.test_dataloader().dataset exporter = ModelExporter(model, dataset, WORKING_DIR, scale_output) exporter.generate(n_images=N_IMAGES).compile(WORKING_DIR / "miniera", WORKING_DIR) @@ -103,14 +52,7 @@ 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 -# n_total, correct = 0, 0 -# image_path, output = next( -# run_test_over_ssh(SCP_HOST, SCP_PWD, SCP_DST, input_images, SSH_OPTS) -# ) -# for image_path, output in run_test_over_ssh(SCP_HOST, SCP_PWD, SCP_DST, input_images, SSH_OPTS): -# 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)") +n_total, correct = 0, 0 +image_path, output = next( + run_test_over_ssh(SCP_HOST, SCP_PWD, SCP_DST, input_images, SSH_OPTS) +)