diff --git a/hpvm/test/dnn_benchmarks/pytorch/.gitkeep b/hpvm/test/dnn_benchmarks/pytorch/.gitkeep
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/__init__.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d7deb9955a7e11adbd0e8aa3f23a5d83a591f48
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/__init__.py
@@ -0,0 +1,6 @@
+from .alexnet import AlexNet, AlexNet2, AlexNetImageNet
+from .datasets import CIFAR, MNIST, ImageNet
+from .lenet import LeNet
+from .vgg16 import VGG16Cifar10, VGG16Cifar100
+from .mobilenet import MobileNet
+from .resnet import ResNet18, ResNet50
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/_container.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/_container.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f38094d1542f9e9a2bbc95a4f466db697c0ef6b
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/_container.py
@@ -0,0 +1,37 @@
+from typing import Callable, Optional
+
+import torch
+from torch.nn import Conv2d, MaxPool2d, Module, Sequential, Softmax
+
+ActivT = Optional[Callable[[], Module]]
+
+
+def make_conv_pool_activ(
+    in_channels: int,
+    out_channels: int,
+    kernel_size: int,
+    activation: ActivT = None,
+    pool_size: Optional[int] = None,
+    pool_stride: Optional[int] = None,
+    **conv_kwargs
+):
+    layers = [Conv2d(in_channels, out_channels, kernel_size, **conv_kwargs)]
+    if pool_size is not None:
+        layers.append(MaxPool2d(pool_size, stride=pool_stride))
+    if activation:
+        layers.append(activation())
+    return layers
+
+
+class Classifier(Module):
+    def __init__(
+        self, convs: Sequential, linears: Sequential, use_softmax: bool = False
+    ):
+        super().__init__()
+        self.convs = convs
+        self.linears = linears
+        self.softmax = Softmax(1) if use_softmax else Sequential()
+
+    def forward(self, inputs: torch.Tensor) -> torch.Tensor:
+        outputs = self.convs(inputs)
+        return self.softmax(self.linears(outputs.view(outputs.shape[0], -1)))
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/alexnet.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/alexnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f0378cecc6fe3c156c4b7bc709168c41938528a
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/alexnet.py
@@ -0,0 +1,51 @@
+from torch.nn import Linear, ReLU, Sequential, Tanh
+
+from ._container import Classifier, make_conv_pool_activ
+
+
+class AlexNet(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            *make_conv_pool_activ(3, 64, 11, Tanh, pool_size=2, padding=5),
+            *make_conv_pool_activ(64, 192, 5, Tanh, pool_size=2, padding=2),
+            *make_conv_pool_activ(192, 384, 3, Tanh, padding=1),
+            *make_conv_pool_activ(384, 256, 3, Tanh, padding=1),
+            *make_conv_pool_activ(256, 256, 3, Tanh, pool_size=2, padding=1)
+        )
+        linears = Sequential(Linear(4096, 10))
+        super().__init__(convs, linears)
+
+
+class AlexNet2(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            *make_conv_pool_activ(3, 32, 3, Tanh, padding=1),
+            *make_conv_pool_activ(32, 32, 3, Tanh, pool_size=2, padding=1),
+            *make_conv_pool_activ(32, 64, 3, Tanh, padding=1),
+            *make_conv_pool_activ(64, 64, 3, Tanh, pool_size=2, padding=1),
+            *make_conv_pool_activ(64, 128, 3, Tanh, padding=1),
+            *make_conv_pool_activ(128, 128, 3, Tanh, pool_size=2, padding=1)
+        )
+        linears = Sequential(Linear(2048, 10))
+        super().__init__(convs, linears)
+
+
+class AlexNetImageNet(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            *make_conv_pool_activ(
+                3, 64, 11, ReLU, padding=2, stride=4, pool_size=3, pool_stride=2
+            ),
+            *make_conv_pool_activ(
+                64, 192, 5, ReLU, padding=2, pool_size=3, pool_stride=2
+            ),
+            *make_conv_pool_activ(192, 384, 3, ReLU, padding=1),
+            *make_conv_pool_activ(384, 256, 3, ReLU, padding=1),
+            *make_conv_pool_activ(
+                256, 256, 3, ReLU, padding=1, pool_size=3, pool_stride=2
+            )
+        )
+        linears = Sequential(
+            Linear(9216, 4096), ReLU(), Linear(4096, 4096), ReLU(), Linear(4096, 1000),
+        )
+        super().__init__(convs, linears)
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/datasets.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/datasets.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac519bfe26ba48429cf40d6f16fdc3587e606b37
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/datasets.py
@@ -0,0 +1,99 @@
+import logging
+from pathlib import Path
+from typing import Iterator, Tuple, Union
+
+import numpy as np
+import torch
+from torch.utils.data.dataset import Dataset
+
+RetT = Tuple[torch.Tensor, torch.Tensor]
+msg_logger = logging.getLogger(__name__)
+
+PathLike = Union[Path, str]
+
+
+class SingleFileDataset(Dataset):
+    def __init__(self, inputs: torch.Tensor, outputs: torch.Tensor):
+        self.inputs, self.outputs = inputs, outputs
+
+    @classmethod
+    def from_file(cls, *args, **kwargs):
+        pass
+
+    @property
+    def sample_input(self):
+        inputs, outputs = next(iter(self))
+        return inputs
+
+    def __len__(self) -> int:
+        return len(self.inputs)
+
+    def __getitem__(self, idx) -> RetT:
+        return self.inputs[idx], self.outputs[idx]
+
+    def __iter__(self) -> Iterator[RetT]:
+        for i in range(len(self)):
+            yield self[i]
+
+
+class DNNDataset(SingleFileDataset):
+    image_shape = None
+    label_ty = np.int32
+
+    @classmethod
+    def from_file(
+        cls,
+        input_file: PathLike,
+        labels_file: PathLike,
+        count: int = -1,
+        offset: int = 0,
+    ):
+        # NOTE: assuming (N, *) ordering of inputs (such as NCHW, NHWC)
+        channel_size = np.prod(np.array(cls.image_shape))
+        inputs_count_byte = -1 if count == -1 else count * channel_size
+        inputs = read_tensor_from_file(
+            input_file,
+            -1,
+            *cls.image_shape,
+            count=inputs_count_byte,
+            offset=offset * channel_size,
+        )
+        labels = read_tensor_from_file(
+            labels_file,
+            -1,
+            read_ty=cls.label_ty,
+            cast_ty=np.long,
+            count=count,
+            offset=offset,
+        )
+        if inputs.shape[0] != labels.shape[0]:
+            raise ValueError("Input and output have different number of data points")
+        msg_logger.info(f"%d entries loaded from dataset.", inputs.shape[0])
+        return cls(inputs, labels)
+
+
+class MNIST(DNNDataset):
+    image_shape = 1, 28, 28
+
+
+class CIFAR(DNNDataset):
+    image_shape = 3, 32, 32
+
+
+class ImageNet(DNNDataset):
+    image_shape = 3, 224, 224
+
+
+def read_tensor_from_file(
+    filename: Union[str, Path],
+    *shape: int,
+    read_ty=np.float32,
+    cast_ty=np.float32,
+    count: int = -1,
+    offset: int = 0,
+) -> torch.Tensor:
+    offset = offset * read_ty().itemsize
+    mmap = np.memmap(filename, dtype=read_ty, mode="r", offset=offset)
+    n_entries = min(mmap.shape[0], count) if count != -1 else mmap.shape[0]
+    np_array = mmap[:n_entries].reshape(shape).astype(cast_ty)
+    return torch.from_numpy(np_array).clone()
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/lenet.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/lenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9deb43fd6b604e09c2fe46cb748fd3dc245f3ad
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/lenet.py
@@ -0,0 +1,13 @@
+from torch.nn import Linear, Sequential, Tanh
+
+from ._container import Classifier, make_conv_pool_activ
+
+
+class LeNet(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            *make_conv_pool_activ(1, 32, 5, Tanh, 2, padding=2),
+            *make_conv_pool_activ(32, 64, 5, Tanh, 2, padding=2)
+        )
+        linears = Sequential(Linear(7 * 7 * 64, 1024), Tanh(), Linear(1024, 10), Tanh())
+        super().__init__(convs, linears)
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/mobilenet.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/mobilenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..34e4f794b7d2d4e04924db8e8879b1715e0c6ab5
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/mobilenet.py
@@ -0,0 +1,53 @@
+from torch.nn import AvgPool2d, BatchNorm2d, Conv2d, Linear, ReLU, Sequential
+
+from ._container import Classifier, make_conv_pool_activ
+
+
+def _make_seq(in_channels, out_channels, c_kernel_size, gc_stride, gc_kernel_size=3):
+    return Sequential(
+        *make_conv_pool_activ(
+            in_channels,
+            out_channels,
+            c_kernel_size,
+            bias=False,
+            padding=(c_kernel_size - 1) // 2,
+        ),
+        BatchNorm2d(out_channels, eps=0.001),
+        ReLU(),
+        Conv2d(
+            out_channels,
+            out_channels,
+            gc_kernel_size,
+            bias=False,
+            stride=gc_stride,
+            padding=(gc_kernel_size - 1) // 2,
+            groups=out_channels,
+        ),
+        BatchNorm2d(out_channels, eps=0.001),
+        ReLU()
+    )
+
+
+class MobileNet(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            _make_seq(3, 32, 3, 1),
+            _make_seq(32, 64, 1, 2),
+            _make_seq(64, 128, 1, 1),
+            _make_seq(128, 128, 1, 2),
+            _make_seq(128, 256, 1, 1),
+            _make_seq(256, 256, 1, 2),
+            _make_seq(256, 512, 1, 1),
+            _make_seq(512, 512, 1, 1),
+            _make_seq(512, 512, 1, 1),
+            _make_seq(512, 512, 1, 1),
+            _make_seq(512, 512, 1, 1),
+            _make_seq(512, 512, 1, 2),
+            _make_seq(512, 1024, 1, 1),
+            *make_conv_pool_activ(1024, 1024, 1, padding=0, bias=False),
+            BatchNorm2d(1024, eps=0.001),
+            ReLU(),
+            AvgPool2d(2)
+        )
+        linears = Sequential(Linear(1024, 10))
+        super().__init__(convs, linears)
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/resnet.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/resnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0273c42d6db36962174be02fe70d4970de0fc72
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/resnet.py
@@ -0,0 +1,100 @@
+from torch.nn import AvgPool2d, BatchNorm2d, Linear, Module, ReLU, Sequential
+
+from ._container import Classifier, make_conv_pool_activ
+
+
+class BasicBlock(Module):
+    def __init__(self, ins, outs, shortcut=False):
+        super().__init__()
+        stride = 2 if shortcut else 1
+        self.mainline = Sequential(
+            *make_conv_pool_activ(ins, outs, 3, ReLU, padding=1, stride=stride),
+            *make_conv_pool_activ(outs, outs, 3, padding=1)
+        )
+        self.relu1 = ReLU()
+        self.shortcut = (
+            Sequential(*make_conv_pool_activ(ins, outs, 1, stride=stride))
+            if shortcut
+            else Sequential()
+        )
+
+    def forward(self, input_):
+        return self.relu1(self.mainline(input_) + self.shortcut(input_))
+
+
+class ResNet18(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            *make_conv_pool_activ(3, 16, 3, ReLU, padding=1),
+            BasicBlock(16, 16),
+            BasicBlock(16, 16),
+            BasicBlock(16, 16),
+            BasicBlock(16, 32, True),
+            BasicBlock(32, 32),
+            BasicBlock(32, 32),
+            BasicBlock(32, 64, True),
+            BasicBlock(64, 64),
+            BasicBlock(64, 64),
+            AvgPool2d(8)
+        )
+        linears = Sequential(Linear(64, 10))
+        super().__init__(convs, linears)
+
+
+class Bottleneck(Module):
+    expansion = 4
+
+    def __init__(self, in_planes, planes, stride=1):
+        super(Bottleneck, self).__init__()
+        self.mainline = Sequential(
+            *make_conv_pool_activ(in_planes, planes, 1, stride=stride),
+            BatchNorm2d(planes, eps=0.001),
+            ReLU(),
+            *make_conv_pool_activ(planes, planes, 3, padding=1),
+            BatchNorm2d(planes, eps=0.001),
+            ReLU(),
+            *make_conv_pool_activ(planes, self.expansion * planes, 1),
+            BatchNorm2d(self.expansion * planes, eps=0.001)
+        )
+        self.relu1 = ReLU()
+        if stride != 1 or in_planes != self.expansion * planes:
+            self.shortcut = Sequential(
+                *make_conv_pool_activ(
+                    in_planes, self.expansion * planes, 1, stride=stride
+                ),
+                BatchNorm2d(self.expansion * planes, eps=0.001)
+            )
+        else:
+            self.shortcut = Sequential()
+
+    def forward(self, input_):
+        return self.relu1(self.mainline(input_) + self.shortcut(input_))
+
+
+class ResNet50(Classifier):
+    def __init__(self):
+        convs = Sequential(
+            *make_conv_pool_activ(
+                3, 64, 7, ReLU, pool_size=3, pool_stride=2, padding=3, stride=2
+            ),
+            BatchNorm2d(64, eps=0.001),
+            Bottleneck(64, 64),
+            Bottleneck(256, 64),
+            Bottleneck(256, 64),
+            Bottleneck(256, 128, stride=2),
+            Bottleneck(512, 128),
+            Bottleneck(512, 128),
+            Bottleneck(512, 128),
+            Bottleneck(512, 256, stride=2),
+            Bottleneck(1024, 256),
+            Bottleneck(1024, 256),
+            Bottleneck(1024, 256),
+            Bottleneck(1024, 256),
+            Bottleneck(1024, 256),
+            Bottleneck(1024, 512, stride=2),
+            Bottleneck(2048, 512),
+            Bottleneck(2048, 512),
+            AvgPool2d(7)
+        )
+        linears = Sequential(Linear(2048, 1000))
+        super().__init__(convs, linears)
diff --git a/hpvm/test/dnn_benchmarks/pytorch/dnn/vgg16.py b/hpvm/test/dnn_benchmarks/pytorch/dnn/vgg16.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c518fd511f4a95e1405d43211b390bbc8c4db19
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/dnn/vgg16.py
@@ -0,0 +1,41 @@
+from typing import Iterable
+
+from torch.nn import Linear, ReLU, Sequential
+
+from ._container import Classifier, make_conv_pool_activ
+
+
+class _VGG16(Classifier):
+    def __init__(self, linear_inouts: Iterable[int]):
+        convs = Sequential(
+            *make_conv_pool_activ(3, 64, 3, ReLU, padding=1),
+            *make_conv_pool_activ(64, 64, 3, ReLU, 2, padding=1),
+            *make_conv_pool_activ(64, 128, 3, ReLU, padding=1),
+            *make_conv_pool_activ(128, 128, 3, ReLU, 2, padding=1),
+            *make_conv_pool_activ(128, 256, 3, ReLU, padding=1),
+            *make_conv_pool_activ(256, 256, 3, ReLU, padding=1),
+            *make_conv_pool_activ(256, 256, 3, ReLU, 2, padding=1),
+            *make_conv_pool_activ(256, 512, 3, ReLU, padding=1),
+            *make_conv_pool_activ(512, 512, 3, ReLU, padding=1),
+            *make_conv_pool_activ(512, 512, 3, ReLU, 2, padding=1),
+            *make_conv_pool_activ(512, 512, 3, ReLU, padding=1),
+            *make_conv_pool_activ(512, 512, 3, ReLU, padding=1),
+            *make_conv_pool_activ(512, 512, 3, ReLU, 2, padding=1)
+        )
+        linear_layers = [
+            Linear(in_, out) for in_, out in zip(linear_inouts, linear_inouts[1:])
+        ]
+        linear_relus = [ReLU() for _ in range(2 * len(linear_layers) - 1)]
+        linear_relus[::2] = linear_layers
+        linears = Sequential(*linear_relus)
+        super().__init__(convs, linears)
+
+
+class VGG16Cifar10(_VGG16):
+    def __init__(self):
+        super().__init__([512, 512, 10])
+
+
+class VGG16Cifar100(_VGG16):
+    def __init__(self):
+        super().__init__([512, 512, 100])
diff --git a/hpvm/test/dnn_benchmarks/pytorch/test_frontend.py b/hpvm/test/dnn_benchmarks/pytorch/test_frontend.py
new file mode 100644
index 0000000000000000000000000000000000000000..8fe80f9abe580721ba1af6ffac0c328c4f5d4d0a
--- /dev/null
+++ b/hpvm/test/dnn_benchmarks/pytorch/test_frontend.py
@@ -0,0 +1,4 @@
+from torch2hpvm import compile_torch_module
+from dnn import AlexNet
+
+compile_torch_module(AlexNet(), (1, 3, 32, 32), "/tmp/alexnet", True)