UH-OH

It looks like you don’t have access to that feature yet

Contact sales to get upgraded to the full DevStudio experience.

UH-OH

It looks like you don't have access to that feature yet.

Introduction to the Chimera SDK
Chimera SDK Quick Start Guide
Chimera SDK Command Line Interface (CLI)
Tutorial: Using SDK as a Library
Chimera Graph Compiler (CGC)
Chimera LLVM C++ Compiler
Chimera SDK Licensing Policy Documentation
Glossary
Chimera Software User GuideChimera Graph Compiler (CGC)Preparing a Model for CompilationTutorial: Custom Operator Insertion for Operators without an ONNX Equivalent

Tutorial: Custom Operator Insertion for Operators without an ONNX Equivalent


NOTE: The Jupyter Notebook below is included in the Chimera SDK and can be run interactively by running the following CLI command:

$ quadric sdk notebook

From the Jupyter Notebook window in your browser, select the notebook named /quadric/sdk-cli/examples/custom_op/tutorial_custom_op_without_onnx_equivalent.ipynb.


Tutorial: Custom Operator Insertion for Operators without an ONNX Equivalent

In this tutorial, we'll review the Open Neural Network Exchange (ONNX) graph representation framework, why Quadric selected it as its primary ingestion format for the Chimera Graph Compiler (CGC), and some situations when converting your entire model to ONNX format may be an inefficient process for porting your model to the Chimera Processor Architecture.

Lastly, we'll walk-through the custom operator flow for one of these situations, a YOLOv4 model's object-detector head, to demonstrate how a developer might port their model to the Chimera Processor architecture in the most efficient way.

Open Neural Network Exchange (ONNX)

The Chimera Graph Compiler (CGC) requires that Deep Neural Network (DNN) models be represented in the Open Neural Network Exchange (ONNX) format to run compilation.

ONNX is an open-source format for representing DNN models and is designed to enable AI developers to more easily use, compile, and deploy models trained with different Deep Learning (DL) frameworks, e.g. PyTorch, TensorFlow, TensorFlow Lite, Caffe, etc. ONNX accomplishes this by defining a common set of operators - the building blocks of deep learning models - and a conversion paradigm for representing these operators in each of the aforementioned DL frameworks.

Why Quadric chose ONNX

Quadric has elected to use ONNX as its initial ingestion format for the CGC so that the toolchain is accessible to all developers, regardless of their preferred DL framework for training.

What are ONNX OpSets?

The Deep Learning community and its techniques are rapidly changing as more efficient and effective methods are discovered.

ONNX adapts to these changes by periodically releasing updates to their common set of operators that are used to build and represent DNNs. These updates are called ONNX OpSets. A list of all ONNX operators, past and present, and the OpSet with which they were introduced or edited can be found here.

When converting to ONNX is a bad idea

ONNX is not a popular DL framework for model development and training. Instead, it excels at conversion between frameworks, operator canonicalization, and deployment to runtime environments. Thus, ONNX is often if not always behind in supporting the latest operators developer for the newest model architectures. It cannot support conversion of an operator between different DL frameworks if it only exists in one framework.

This process does have its advantages. ONNX may not be a first-mover in the DL space; but when it does add support for an operator, it does so because the operator has demonstrated widespread value across numerous DNN architectures. Since ONNX is also used to quickly port models to a vareity of supported runtime environments, ONNX has a vested interest in keeping the number of operators that it supports in each of its OpSets lean.

Despite this context, it may still surprise some users to learn that some very popular model architectures have custom operators defined in their native DL frameworks that have not been adopted by ONNX. For example, none of the popular YOLO object detection layers have been supported natively by ONNX.

ONNX is not wrong for refusing to canonicalize these operators. They are the algorithms that have changed the most between each new version of the YOLO architecture and supporting them in ONNX might quickly

Example: YOLOv4 Object-Detector Head

For the remainder of this tutorial, we'll focus on one model - the YOLOv4 Object Detector - to demonstrate when writing a custom operator for an algorothm without an equivalent in ONNX is simpler than attempting to convert that model to ONNX.

We'll start by looking at the PyTorch implementation of the YOLOv4 adapted from this open-source GitHub repository: https://github.com/Tianxiaomo/pytorch-YOLOv4

NOTE: It's not necessary for you to understand all of the code in the block below. You may skip ahead to the tutorials steps and refer back as needed.

import sys
import numpy as np
import torch
from torch import nn
import torch.nn.functional as F

## PyTorch Modules used to Build YOLOv4 Model - copied from: https://github.com/Tianxiaomo/pytorch-YOLOv4/blob/master/models.py
## ----------------------------------------------------------------------------------------------------------------------------


class Mish(torch.nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, x):
        x = x * (torch.tanh(torch.nn.functional.softplus(x)))
        return x


class Upsample(nn.Module):
    def __init__(self):
        super(Upsample, self).__init__()

    def forward(self, x, target_size, inference=False):
        assert x.data.dim() == 4
        # _, _, tH, tW = target_size

        if inference:
            # B = x.data.size(0)
            # C = x.data.size(1)
            # H = x.data.size(2)
            # W = x.data.size(3)

            return (
                x.view(x.size(0), x.size(1), x.size(2), 1, x.size(3), 1)
                .expand(
                    x.size(0),
                    x.size(1),
                    x.size(2),
                    target_size[2] // x.size(2),
                    x.size(3),
                    target_size[3] // x.size(3),
                )
                .contiguous()
                .view(x.size(0), x.size(1), target_size[2], target_size[3])
            )
        else:
            return F.interpolate(x, size=(target_size[2], target_size[3]), mode="nearest")


class Conv_Bn_Activation(nn.Module):
    def __init__(
        self,
        in_channels,
        out_channels,
        kernel_size,
        stride,
        activation,
        bn=True,
        bias=False,
    ):
        super().__init__()
        pad = (kernel_size - 1) // 2

        self.conv = nn.ModuleList()
        if bias:
            self.conv.append(nn.Conv2d(in_channels, out_channels, kernel_size, stride, pad))
        else:
            self.conv.append(
                nn.Conv2d(in_channels, out_channels, kernel_size, stride, pad, bias=False)
            )
        if bn:
            self.conv.append(nn.BatchNorm2d(out_channels))
        if activation == "mish":
            self.conv.append(Mish())
        elif activation == "relu":
            self.conv.append(nn.ReLU(inplace=True))
        elif activation == "leaky":
            self.conv.append(nn.LeakyReLU(0.1, inplace=True))
        elif activation == "linear":
            pass
        else:
            print(
                "activate error !!! {} {} {}".format(
                    sys._getframe().f_code.co_filename,
                    sys._getframe().f_code.co_name,
                    sys._getframe().f_lineno,
                )
            )

    def forward(self, x):
        for l in self.conv:
            x = l(x)
        return x


class ResBlock(nn.Module):
    """
    Sequential residual blocks each of which consists of \
    two convolution layers.
    Args:
        ch (int): number of input and output channels.
        nblocks (int): number of residual blocks.
        shortcut (bool): if True, residual tensor addition is enabled.
    """

    def __init__(self, ch, nblocks=1, shortcut=True):
        super().__init__()
        self.shortcut = shortcut
        self.module_list = nn.ModuleList()
        for i in range(nblocks):
            resblock_one = nn.ModuleList()
            resblock_one.append(Conv_Bn_Activation(ch, ch, 1, 1, "mish"))
            resblock_one.append(Conv_Bn_Activation(ch, ch, 3, 1, "mish"))
            self.module_list.append(resblock_one)

    def forward(self, x):
        for module in self.module_list:
            h = x
            for res in module:
                h = res(h)
            x = x + h if self.shortcut else h
        return x


class DownSample1(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = Conv_Bn_Activation(3, 32, 3, 1, "mish")

        self.conv2 = Conv_Bn_Activation(32, 64, 3, 2, "mish")
        self.conv3 = Conv_Bn_Activation(64, 64, 1, 1, "mish")
        # [route]
        # layers = -2
        self.conv4 = Conv_Bn_Activation(64, 64, 1, 1, "mish")

        self.conv5 = Conv_Bn_Activation(64, 32, 1, 1, "mish")
        self.conv6 = Conv_Bn_Activation(32, 64, 3, 1, "mish")
        # [shortcut]
        # from=-3
        # activation = linear

        self.conv7 = Conv_Bn_Activation(64, 64, 1, 1, "mish")
        # [route]
        # layers = -1, -7
        self.conv8 = Conv_Bn_Activation(128, 64, 1, 1, "mish")

    def forward(self, input):
        x1 = self.conv1(input)
        x2 = self.conv2(x1)
        x3 = self.conv3(x2)
        # route -2
        x4 = self.conv4(x2)
        x5 = self.conv5(x4)
        x6 = self.conv6(x5)
        # shortcut -3
        x6 = x6 + x4

        x7 = self.conv7(x6)
        # [route]
        # layers = -1, -7
        x7 = torch.cat([x7, x3], dim=1)
        x8 = self.conv8(x7)
        return x8


class DownSample2(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = Conv_Bn_Activation(64, 128, 3, 2, "mish")
        self.conv2 = Conv_Bn_Activation(128, 64, 1, 1, "mish")
        # r -2
        self.conv3 = Conv_Bn_Activation(128, 64, 1, 1, "mish")

        self.resblock = ResBlock(ch=64, nblocks=2)

        # s -3
        self.conv4 = Conv_Bn_Activation(64, 64, 1, 1, "mish")
        # r -1 -10
        self.conv5 = Conv_Bn_Activation(128, 128, 1, 1, "mish")

    def forward(self, input):
        x1 = self.conv1(input)
        x2 = self.conv2(x1)
        x3 = self.conv3(x1)

        r = self.resblock(x3)
        x4 = self.conv4(r)

        x4 = torch.cat([x4, x2], dim=1)
        x5 = self.conv5(x4)
        return x5


class DownSample3(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = Conv_Bn_Activation(128, 256, 3, 2, "mish")
        self.conv2 = Conv_Bn_Activation(256, 128, 1, 1, "mish")
        self.conv3 = Conv_Bn_Activation(256, 128, 1, 1, "mish")

        self.resblock = ResBlock(ch=128, nblocks=8)
        self.conv4 = Conv_Bn_Activation(128, 128, 1, 1, "mish")
        self.conv5 = Conv_Bn_Activation(256, 256, 1, 1, "mish")

    def forward(self, input):
        x1 = self.conv1(input)
        x2 = self.conv2(x1)
        x3 = self.conv3(x1)

        r = self.resblock(x3)
        x4 = self.conv4(r)

        x4 = torch.cat([x4, x2], dim=1)
        x5 = self.conv5(x4)
        return x5


class DownSample4(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = Conv_Bn_Activation(256, 512, 3, 2, "mish")
        self.conv2 = Conv_Bn_Activation(512, 256, 1, 1, "mish")
        self.conv3 = Conv_Bn_Activation(512, 256, 1, 1, "mish")

        self.resblock = ResBlock(ch=256, nblocks=8)
        self.conv4 = Conv_Bn_Activation(256, 256, 1, 1, "mish")
        self.conv5 = Conv_Bn_Activation(512, 512, 1, 1, "mish")

    def forward(self, input):
        x1 = self.conv1(input)
        x2 = self.conv2(x1)
        x3 = self.conv3(x1)

        r = self.resblock(x3)
        x4 = self.conv4(r)

        x4 = torch.cat([x4, x2], dim=1)
        x5 = self.conv5(x4)
        return x5


class DownSample5(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = Conv_Bn_Activation(512, 1024, 3, 2, "mish")
        self.conv2 = Conv_Bn_Activation(1024, 512, 1, 1, "mish")
        self.conv3 = Conv_Bn_Activation(1024, 512, 1, 1, "mish")

        self.resblock = ResBlock(ch=512, nblocks=4)
        self.conv4 = Conv_Bn_Activation(512, 512, 1, 1, "mish")
        self.conv5 = Conv_Bn_Activation(1024, 1024, 1, 1, "mish")

    def forward(self, input):
        x1 = self.conv1(input)
        x2 = self.conv2(x1)
        x3 = self.conv3(x1)

        r = self.resblock(x3)
        x4 = self.conv4(r)

        x4 = torch.cat([x4, x2], dim=1)
        x5 = self.conv5(x4)
        return x5


class Neck(nn.Module):
    def __init__(self, inference=False):
        super().__init__()
        self.inference = inference

        self.conv1 = Conv_Bn_Activation(1024, 512, 1, 1, "leaky")
        self.conv2 = Conv_Bn_Activation(512, 1024, 3, 1, "leaky")
        self.conv3 = Conv_Bn_Activation(1024, 512, 1, 1, "leaky")
        # SPP
        self.maxpool1 = nn.MaxPool2d(kernel_size=5, stride=1, padding=5 // 2)
        self.maxpool2 = nn.MaxPool2d(kernel_size=9, stride=1, padding=9 // 2)
        self.maxpool3 = nn.MaxPool2d(kernel_size=13, stride=1, padding=13 // 2)

        # R -1 -3 -5 -6
        # SPP
        self.conv4 = Conv_Bn_Activation(2048, 512, 1, 1, "leaky")
        self.conv5 = Conv_Bn_Activation(512, 1024, 3, 1, "leaky")
        self.conv6 = Conv_Bn_Activation(1024, 512, 1, 1, "leaky")
        self.conv7 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        # UP
        self.upsample1 = Upsample()
        # R 85
        self.conv8 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        # R -1 -3
        self.conv9 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        self.conv10 = Conv_Bn_Activation(256, 512, 3, 1, "leaky")
        self.conv11 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        self.conv12 = Conv_Bn_Activation(256, 512, 3, 1, "leaky")
        self.conv13 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        self.conv14 = Conv_Bn_Activation(256, 128, 1, 1, "leaky")
        # UP
        self.upsample2 = Upsample()
        # R 54
        self.conv15 = Conv_Bn_Activation(256, 128, 1, 1, "leaky")
        # R -1 -3
        self.conv16 = Conv_Bn_Activation(256, 128, 1, 1, "leaky")
        self.conv17 = Conv_Bn_Activation(128, 256, 3, 1, "leaky")
        self.conv18 = Conv_Bn_Activation(256, 128, 1, 1, "leaky")
        self.conv19 = Conv_Bn_Activation(128, 256, 3, 1, "leaky")
        self.conv20 = Conv_Bn_Activation(256, 128, 1, 1, "leaky")

    def forward(self, input, downsample4, downsample3, inference=False):
        x1 = self.conv1(input)
        x2 = self.conv2(x1)
        x3 = self.conv3(x2)
        # SPP
        m1 = self.maxpool1(x3)
        m2 = self.maxpool2(x3)
        m3 = self.maxpool3(x3)
        spp = torch.cat([m3, m2, m1, x3], dim=1)
        # SPP end
        x4 = self.conv4(spp)
        x5 = self.conv5(x4)
        x6 = self.conv6(x5)
        x7 = self.conv7(x6)
        # UP
        up = self.upsample1(x7, downsample4.size(), self.inference)
        # R 85
        x8 = self.conv8(downsample4)
        # R -1 -3
        x8 = torch.cat([x8, up], dim=1)

        x9 = self.conv9(x8)
        x10 = self.conv10(x9)
        x11 = self.conv11(x10)
        x12 = self.conv12(x11)
        x13 = self.conv13(x12)
        x14 = self.conv14(x13)

        # UP
        up = self.upsample2(x14, downsample3.size(), self.inference)
        # R 54
        x15 = self.conv15(downsample3)
        # R -1 -3
        x15 = torch.cat([x15, up], dim=1)

        x16 = self.conv16(x15)
        x17 = self.conv17(x16)
        x18 = self.conv18(x17)
        x19 = self.conv19(x18)
        x20 = self.conv20(x19)
        return x20, x13, x6


class YoloLayer(nn.Module):
    """Yolo layer
    model_out: while inference,is post-processing inside or outside the model
        true:outside
    """

    def __init__(
        self,
        anchor_mask=[],
        num_classes=0,
        anchors=[],
        num_anchors=1,
        stride=32,
        model_out=False,
    ):
        super(YoloLayer, self).__init__()
        self.anchor_mask = anchor_mask
        self.num_classes = num_classes
        self.anchors = anchors
        self.num_anchors = num_anchors
        self.anchor_step = len(anchors) // num_anchors
        self.coord_scale = 1
        self.noobject_scale = 1
        self.object_scale = 5
        self.class_scale = 1
        self.thresh = 0.6
        self.stride = stride
        self.seen = 0
        self.scale_x_y = 1

        self.model_out = model_out

    def yolo_forward_dynamic(
        self,
        output,
        conf_thresh,
        num_classes,
        anchors,
        num_anchors,
        only_objectness=1,
        validation=False,
    ):
        # Output would be invalid if it does not satisfy this assert
        # assert (output.size(1) == (5 + num_classes) * num_anchors)

        # print(output.size())

        # Slice the second dimension (channel) of output into:
        # [ 2, 2, 1, num_classes, 2, 2, 1, num_classes, 2, 2, 1, num_classes ]
        # And then into
        # bxy = [ 6 ] bwh = [ 6 ] det_conf = [ 3 ] cls_conf = [ num_classes * 3 ]
        # batch = output.size(0)
        # H = output.size(2)
        # W = output.size(3)

        bxy_list = []
        bwh_list = []
        det_confs_list = []
        cls_confs_list = []

        for i in range(num_anchors):
            begin = i * (5 + num_classes)
            end = (i + 1) * (5 + num_classes)

            bxy_list.append(output[:, begin : begin + 2])
            bwh_list.append(output[:, begin + 2 : begin + 4])
            det_confs_list.append(output[:, begin + 4 : begin + 5])
            cls_confs_list.append(output[:, begin + 5 : end])

        # Shape: [batch, num_anchors * 2, H, W]
        bxy = torch.cat(bxy_list, dim=1)
        # Shape: [batch, num_anchors * 2, H, W]
        bwh = torch.cat(bwh_list, dim=1)

        # Shape: [batch, num_anchors, H, W]
        det_confs = torch.cat(det_confs_list, dim=1)
        # Shape: [batch, num_anchors * H * W]
        det_confs = det_confs.view(output.size(0), num_anchors * output.size(2) * output.size(3))

        # Shape: [batch, num_anchors * num_classes, H, W]
        cls_confs = torch.cat(cls_confs_list, dim=1)
        # Shape: [batch, num_anchors, num_classes, H * W]
        cls_confs = cls_confs.view(
            output.size(0), num_anchors, num_classes, output.size(2) * output.size(3)
        )
        # Shape: [batch, num_anchors, num_classes, H * W] --> [batch, num_anchors * H * W, num_classes]
        cls_confs = cls_confs.permute(0, 1, 3, 2).reshape(
            output.size(0), num_anchors * output.size(2) * output.size(3), num_classes
        )

        # Apply sigmoid(), exp() and softmax() to slices
        #
        bxy = torch.sigmoid(bxy) * self.scale_x_y - 0.5 * (self.scale_x_y - 1)
        bwh = torch.exp(bwh)
        det_confs = torch.sigmoid(det_confs)
        cls_confs = torch.sigmoid(cls_confs)

        # Prepare C-x, C-y, P-w, P-h (None of them are torch related)
        grid_x = np.expand_dims(
            np.expand_dims(
                np.expand_dims(np.linspace(0, output.size(3) - 1, output.size(3)), axis=0).repeat(
                    output.size(2), 0
                ),
                axis=0,
            ),
            axis=0,
        )
        grid_y = np.expand_dims(
            np.expand_dims(
                np.expand_dims(np.linspace(0, output.size(2) - 1, output.size(2)), axis=1).repeat(
                    output.size(3), 1
                ),
                axis=0,
            ),
            axis=0,
        )
        # grid_x = torch.linspace(0, W - 1, W).reshape(1, 1, 1, W).repeat(1, 1, H, 1)
        # grid_y = torch.linspace(0, H - 1, H).reshape(1, 1, H, 1).repeat(1, 1, 1, W)

        anchor_w = []
        anchor_h = []
        for i in range(num_anchors):
            anchor_w.append(anchors[i * 2])
            anchor_h.append(anchors[i * 2 + 1])

        device = None
        cuda_check = output.is_cuda
        if cuda_check:
            device = output.get_device()

        bx_list = []
        by_list = []
        bw_list = []
        bh_list = []

        # Apply C-x, C-y, P-w, P-h
        for i in range(num_anchors):
            ii = i * 2
            # Shape: [batch, 1, H, W]
            bx = bxy[:, ii : ii + 1] + torch.tensor(
                grid_x, device=device, dtype=torch.float32
            )  # grid_x.to(device=device, dtype=torch.float32)
            # Shape: [batch, 1, H, W]
            by = bxy[:, ii + 1 : ii + 2] + torch.tensor(
                grid_y, device=device, dtype=torch.float32
            )  # grid_y.to(device=device, dtype=torch.float32)
            # Shape: [batch, 1, H, W]
            bw = bwh[:, ii : ii + 1] * anchor_w[i]
            # Shape: [batch, 1, H, W]
            bh = bwh[:, ii + 1 : ii + 2] * anchor_h[i]

            bx_list.append(bx)
            by_list.append(by)
            bw_list.append(bw)
            bh_list.append(bh)

        ########################################
        #   Figure out bboxes from slices     #
        ########################################

        # Shape: [batch, num_anchors, H, W]
        bx = torch.cat(bx_list, dim=1)
        # Shape: [batch, num_anchors, H, W]
        by = torch.cat(by_list, dim=1)
        # Shape: [batch, num_anchors, H, W]
        bw = torch.cat(bw_list, dim=1)
        # Shape: [batch, num_anchors, H, W]
        bh = torch.cat(bh_list, dim=1)

        # Shape: [batch, 2 * num_anchors, H, W]
        bx_bw = torch.cat((bx, bw), dim=1)
        # Shape: [batch, 2 * num_anchors, H, W]
        by_bh = torch.cat((by, bh), dim=1)

        # normalize coordinates to [0, 1]
        bx_bw /= output.size(3)
        by_bh /= output.size(2)

        # Shape: [batch, num_anchors * H * W, 1]
        bx = bx_bw[:, :num_anchors].view(
            output.size(0), num_anchors * output.size(2) * output.size(3), 1
        )
        by = by_bh[:, :num_anchors].view(
            output.size(0), num_anchors * output.size(2) * output.size(3), 1
        )
        bw = bx_bw[:, num_anchors:].view(
            output.size(0), num_anchors * output.size(2) * output.size(3), 1
        )
        bh = by_bh[:, num_anchors:].view(
            output.size(0), num_anchors * output.size(2) * output.size(3), 1
        )

        bx1 = bx - bw * 0.5
        by1 = by - bh * 0.5
        bx2 = bx1 + bw
        by2 = by1 + bh

        # Shape: [batch, num_anchors * h * w, 4] -> [batch, num_anchors * h * w, 1, 4]
        boxes = torch.cat((bx1, by1, bx2, by2), dim=2).view(
            output.size(0), num_anchors * output.size(2) * output.size(3), 1, 4
        )
        # boxes = boxes.repeat(1, 1, num_classes, 1)

        # boxes:     [batch, num_anchors * H * W, 1, 4]
        # cls_confs: [batch, num_anchors * H * W, num_classes]
        # det_confs: [batch, num_anchors * H * W]

        det_confs = det_confs.view(output.size(0), num_anchors * output.size(2) * output.size(3), 1)
        confs = cls_confs * det_confs

        # boxes: [batch, num_anchors * H * W, 1, 4]
        # confs: [batch, num_anchors * H * W, num_classes]

        return boxes, confs

    def forward(self, output, target=None):
        if self.training:
            return output
        masked_anchors = []
        for m in self.anchor_mask:
            masked_anchors += self.anchors[m * self.anchor_step : (m + 1) * self.anchor_step]
        masked_anchors = [anchor / self.stride for anchor in masked_anchors]

        return self.yolo_forward_dynamic(
            output, self.thresh, self.num_classes, masked_anchors, len(self.anchor_mask)
        )


class Yolov4Head(nn.Module):
    def __init__(self, output_ch, n_classes, inference=False):
        super().__init__()
        self.inference = inference

        self.conv1 = Conv_Bn_Activation(128, 256, 3, 1, "leaky")
        self.conv2 = Conv_Bn_Activation(256, output_ch, 1, 1, "linear", bn=False, bias=True)

        self.yolo1 = YoloLayer(
            anchor_mask=[0, 1, 2],
            num_classes=n_classes,
            anchors=[
                12,
                16,
                19,
                36,
                40,
                28,
                36,
                75,
                76,
                55,
                72,
                146,
                142,
                110,
                192,
                243,
                459,
                401,
            ],
            num_anchors=9,
            stride=8,
        )

        # R -4
        self.conv3 = Conv_Bn_Activation(128, 256, 3, 2, "leaky")

        # R -1 -16
        self.conv4 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        self.conv5 = Conv_Bn_Activation(256, 512, 3, 1, "leaky")
        self.conv6 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        self.conv7 = Conv_Bn_Activation(256, 512, 3, 1, "leaky")
        self.conv8 = Conv_Bn_Activation(512, 256, 1, 1, "leaky")
        self.conv9 = Conv_Bn_Activation(256, 512, 3, 1, "leaky")
        self.conv10 = Conv_Bn_Activation(512, output_ch, 1, 1, "linear", bn=False, bias=True)

        self.yolo2 = YoloLayer(
            anchor_mask=[3, 4, 5],
            num_classes=n_classes,
            anchors=[
                12,
                16,
                19,
                36,
                40,
                28,
                36,
                75,
                76,
                55,
                72,
                146,
                142,
                110,
                192,
                243,
                459,
                401,
            ],
            num_anchors=9,
            stride=16,
        )

        # R -4
        self.conv11 = Conv_Bn_Activation(256, 512, 3, 2, "leaky")

        # R -1 -37
        self.conv12 = Conv_Bn_Activation(1024, 512, 1, 1, "leaky")
        self.conv13 = Conv_Bn_Activation(512, 1024, 3, 1, "leaky")
        self.conv14 = Conv_Bn_Activation(1024, 512, 1, 1, "leaky")
        self.conv15 = Conv_Bn_Activation(512, 1024, 3, 1, "leaky")
        self.conv16 = Conv_Bn_Activation(1024, 512, 1, 1, "leaky")
        self.conv17 = Conv_Bn_Activation(512, 1024, 3, 1, "leaky")
        self.conv18 = Conv_Bn_Activation(1024, output_ch, 1, 1, "linear", bn=False, bias=True)

        self.yolo3 = YoloLayer(
            anchor_mask=[6, 7, 8],
            num_classes=n_classes,
            anchors=[
                12,
                16,
                19,
                36,
                40,
                28,
                36,
                75,
                76,
                55,
                72,
                146,
                142,
                110,
                192,
                243,
                459,
                401,
            ],
            num_anchors=9,
            stride=32,
        )

    def forward(self, input1, input2, input3):
        x1 = self.conv1(input1)
        x2 = self.conv2(x1)

        x3 = self.conv3(input1)
        # R -1 -16
        x3 = torch.cat([x3, input2], dim=1)
        x4 = self.conv4(x3)
        x5 = self.conv5(x4)
        x6 = self.conv6(x5)
        x7 = self.conv7(x6)
        x8 = self.conv8(x7)
        x9 = self.conv9(x8)
        x10 = self.conv10(x9)

        # R -4
        x11 = self.conv11(x8)
        # R -1 -37
        x11 = torch.cat([x11, input3], dim=1)

        x12 = self.conv12(x11)
        x13 = self.conv13(x12)
        x14 = self.conv14(x13)
        x15 = self.conv15(x14)
        x16 = self.conv16(x15)
        x17 = self.conv17(x16)
        x18 = self.conv18(x17)

        if self.inference:
            y1 = self.yolo1(x2)
            y2 = self.yolo2(x10)
            y3 = self.yolo3(x18)

            return self.get_region_boxes([y1, y2, y3])

        else:
            return [x2, x10, x18]

    def get_region_boxes(self, boxes_and_confs):
        # print('Getting boxes from boxes and confs ...')

        boxes_list = []
        confs_list = []

        for item in boxes_and_confs:
            boxes_list.append(item[0])
            confs_list.append(item[1])

        # boxes: [batch, num1 + num2 + num3, 1, 4]
        # confs: [batch, num1 + num2 + num3, num_classes]
        boxes = torch.cat(boxes_list, dim=1)
        confs = torch.cat(confs_list, dim=1)

        return [boxes, confs]


class Yolov4(nn.Module):
    def __init__(self, yolov4conv137weight=None, n_classes=80, inference=False):
        super().__init__()

        output_ch = (4 + 1 + n_classes) * 3

        # backbone
        self.down1 = DownSample1()
        self.down2 = DownSample2()
        self.down3 = DownSample3()
        self.down4 = DownSample4()
        self.down5 = DownSample5()
        # neck
        self.neck = Neck(inference)
        # yolov4conv137
        if yolov4conv137weight:
            _model = nn.Sequential(
                self.down1, self.down2, self.down3, self.down4, self.down5, self.neck
            )
            pretrained_dict = torch.load(yolov4conv137weight)

            model_dict = _model.state_dict()
            # 1. filter out unnecessary keys
            pretrained_dict = {k1: v for (k, v), k1 in zip(pretrained_dict.items(), model_dict)}
            # 2. overwrite entries in the existing state dict
            model_dict.update(pretrained_dict)
            _model.load_state_dict(model_dict)

        # head
        self.head = Yolov4Head(output_ch, n_classes, inference)

    def forward(self, input):
        d1 = self.down1(input)
        d2 = self.down2(d1)
        d3 = self.down3(d2)
        d4 = self.down4(d3)
        d5 = self.down5(d4)

        x20, x13, x6 = self.neck(d5, d4, d3)

        output = self.head(x20, x13, x6)
        return output

1. Next, install the following PIP requirements from the pytorch-YOLOv4 repo in a Python environment outside of this notebook:

```sh
$ pip install numpy torch>=1.4.0 torchinfo netron

### 2. Optional: Follow the instructions in the `README.md` of the `pytorch-YOLOv4` repo for downloading pre-trained model weights from the author's Google Drive or Baidu Wangpan.

### 3. Change global variables, if needed:


```python
## Update this variable if you've downloaded pre-trained weights for the YOLOv4 Model
## E.g. 'yolov4.conv.137.pth'
PATH_TO_DOWNLOADED_WEIGHTS = None

## Below we set the number of classes to `80` because the COCO dataset that is often used to benchmark Object Detection models has 80 classes
## More on the COCO dataset: https://cocodataset.org
## NOTE: If the weights you download or train have a different number of classes, update this variable
NUM_MODEL_CLASSES = 80

## NOTE: Below defines the expected shape(s) of the YOLOv4 Model's Inputs
## E.g. (1, 3, 608, 608)
INPUT_BATCH_SIZE = 1
INPUT_IMAGE_CHANNELS = 3
INPUT_IMAGE_HEIGHT = 608
INPUT_IMAGE_WIDTH = 608

4. Print the YOLOv4 model summary:

from torchinfo import summary

model = Yolov4(n_classes=80, inference=True)
summary(model, input_size=(INPUT_BATCH_SIZE, 3, INPUT_IMAGE_HEIGHT, INPUT_IMAGE_WIDTH))
=========================================================================================================
Layer (type:depth-idx)                                  Output Shape              Param #
=========================================================================================================
Yolov4                                                  [1, 22743, 1, 4]          --
├─DownSample1: 1-1                                      [1, 64, 304, 304]         --
│    └─Conv_Bn_Activation: 2-1                          [1, 32, 608, 608]         --
│    │    └─ModuleList: 3-1                             --                        928
│    └─Conv_Bn_Activation: 2-2                          [1, 64, 304, 304]         --
│    │    └─ModuleList: 3-2                             --                        18,560
│    └─Conv_Bn_Activation: 2-3                          [1, 64, 304, 304]         --
│    │    └─ModuleList: 3-3                             --                        4,224
│    └─Conv_Bn_Activation: 2-4                          [1, 64, 304, 304]         --
│    │    └─ModuleList: 3-4                             --                        4,224
│    └─Conv_Bn_Activation: 2-5                          [1, 32, 304, 304]         --
│    │    └─ModuleList: 3-5                             --                        2,112
│    └─Conv_Bn_Activation: 2-6                          [1, 64, 304, 304]         --
│    │    └─ModuleList: 3-6                             --                        18,560
│    └─Conv_Bn_Activation: 2-7                          [1, 64, 304, 304]         --
│    │    └─ModuleList: 3-7                             --                        4,224
│    └─Conv_Bn_Activation: 2-8                          [1, 64, 304, 304]         --
│    │    └─ModuleList: 3-8                             --                        8,320
├─DownSample2: 1-2                                      [1, 128, 152, 152]        --
│    └─Conv_Bn_Activation: 2-9                          [1, 128, 152, 152]        --
│    │    └─ModuleList: 3-9                             --                        73,984
│    └─Conv_Bn_Activation: 2-10                         [1, 64, 152, 152]         --
│    │    └─ModuleList: 3-10                            --                        8,320
│    └─Conv_Bn_Activation: 2-11                         [1, 64, 152, 152]         --
│    │    └─ModuleList: 3-11                            --                        8,320
│    └─ResBlock: 2-12                                   [1, 64, 152, 152]         --
│    │    └─ModuleList: 3-12                            --                        82,432
│    └─Conv_Bn_Activation: 2-13                         [1, 64, 152, 152]         --
│    │    └─ModuleList: 3-13                            --                        4,224
│    └─Conv_Bn_Activation: 2-14                         [1, 128, 152, 152]        --
│    │    └─ModuleList: 3-14                            --                        16,640
├─DownSample3: 1-3                                      [1, 256, 76, 76]          --
│    └─Conv_Bn_Activation: 2-15                         [1, 256, 76, 76]          --
│    │    └─ModuleList: 3-15                            --                        295,424
│    └─Conv_Bn_Activation: 2-16                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-16                            --                        33,024
│    └─Conv_Bn_Activation: 2-17                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-17                            --                        33,024
│    └─ResBlock: 2-18                                   [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-18                            --                        1,314,816
│    └─Conv_Bn_Activation: 2-19                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-19                            --                        16,640
│    └─Conv_Bn_Activation: 2-20                         [1, 256, 76, 76]          --
│    │    └─ModuleList: 3-20                            --                        66,048
├─DownSample4: 1-4                                      [1, 512, 38, 38]          --
│    └─Conv_Bn_Activation: 2-21                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-21                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-22                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-22                            --                        131,584
│    └─Conv_Bn_Activation: 2-23                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-23                            --                        131,584
│    └─ResBlock: 2-24                                   [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-24                            --                        5,251,072
│    └─Conv_Bn_Activation: 2-25                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-25                            --                        66,048
│    └─Conv_Bn_Activation: 2-26                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-26                            --                        263,168
├─DownSample5: 1-5                                      [1, 1024, 19, 19]         --
│    └─Conv_Bn_Activation: 2-27                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-27                            --                        4,720,640
│    └─Conv_Bn_Activation: 2-28                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-28                            --                        525,312
│    └─Conv_Bn_Activation: 2-29                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-29                            --                        525,312
│    └─ResBlock: 2-30                                   [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-30                            --                        10,493,952
│    └─Conv_Bn_Activation: 2-31                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-31                            --                        263,168
│    └─Conv_Bn_Activation: 2-32                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-32                            --                        1,050,624
├─Neck: 1-6                                             [1, 128, 76, 76]          --
│    └─Conv_Bn_Activation: 2-33                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-33                            --                        525,312
│    └─Conv_Bn_Activation: 2-34                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-34                            --                        4,720,640
│    └─Conv_Bn_Activation: 2-35                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-35                            --                        525,312
│    └─MaxPool2d: 2-36                                  [1, 512, 19, 19]          --
│    └─MaxPool2d: 2-37                                  [1, 512, 19, 19]          --
│    └─MaxPool2d: 2-38                                  [1, 512, 19, 19]          --
│    └─Conv_Bn_Activation: 2-39                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-36                            --                        1,049,600
│    └─Conv_Bn_Activation: 2-40                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-37                            --                        4,720,640
│    └─Conv_Bn_Activation: 2-41                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-38                            --                        525,312
│    └─Conv_Bn_Activation: 2-42                         [1, 256, 19, 19]          --
│    │    └─ModuleList: 3-39                            --                        131,584
│    └─Upsample: 2-43                                   [1, 256, 38, 38]          --
│    └─Conv_Bn_Activation: 2-44                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-40                            --                        131,584
│    └─Conv_Bn_Activation: 2-45                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-41                            --                        131,584
│    └─Conv_Bn_Activation: 2-46                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-42                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-47                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-43                            --                        131,584
│    └─Conv_Bn_Activation: 2-48                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-44                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-49                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-45                            --                        131,584
│    └─Conv_Bn_Activation: 2-50                         [1, 128, 38, 38]          --
│    │    └─ModuleList: 3-46                            --                        33,024
│    └─Upsample: 2-51                                   [1, 128, 76, 76]          --
│    └─Conv_Bn_Activation: 2-52                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-47                            --                        33,024
│    └─Conv_Bn_Activation: 2-53                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-48                            --                        33,024
│    └─Conv_Bn_Activation: 2-54                         [1, 256, 76, 76]          --
│    │    └─ModuleList: 3-49                            --                        295,424
│    └─Conv_Bn_Activation: 2-55                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-50                            --                        33,024
│    └─Conv_Bn_Activation: 2-56                         [1, 256, 76, 76]          --
│    │    └─ModuleList: 3-51                            --                        295,424
│    └─Conv_Bn_Activation: 2-57                         [1, 128, 76, 76]          --
│    │    └─ModuleList: 3-52                            --                        33,024
├─Yolov4Head: 1-7                                       [1, 22743, 1, 4]          --
│    └─Conv_Bn_Activation: 2-58                         [1, 256, 76, 76]          --
│    │    └─ModuleList: 3-53                            --                        295,424
│    └─Conv_Bn_Activation: 2-59                         [1, 255, 76, 76]          --
│    │    └─ModuleList: 3-54                            --                        65,535
│    └─Conv_Bn_Activation: 2-60                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-55                            --                        295,424
│    └─Conv_Bn_Activation: 2-61                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-56                            --                        131,584
│    └─Conv_Bn_Activation: 2-62                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-57                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-63                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-58                            --                        131,584
│    └─Conv_Bn_Activation: 2-64                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-59                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-65                         [1, 256, 38, 38]          --
│    │    └─ModuleList: 3-60                            --                        131,584
│    └─Conv_Bn_Activation: 2-66                         [1, 512, 38, 38]          --
│    │    └─ModuleList: 3-61                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-67                         [1, 255, 38, 38]          --
│    │    └─ModuleList: 3-62                            --                        130,815
│    └─Conv_Bn_Activation: 2-68                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-63                            --                        1,180,672
│    └─Conv_Bn_Activation: 2-69                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-64                            --                        525,312
│    └─Conv_Bn_Activation: 2-70                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-65                            --                        4,720,640
│    └─Conv_Bn_Activation: 2-71                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-66                            --                        525,312
│    └─Conv_Bn_Activation: 2-72                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-67                            --                        4,720,640
│    └─Conv_Bn_Activation: 2-73                         [1, 512, 19, 19]          --
│    │    └─ModuleList: 3-68                            --                        525,312
│    └─Conv_Bn_Activation: 2-74                         [1, 1024, 19, 19]         --
│    │    └─ModuleList: 3-69                            --                        4,720,640
│    └─Conv_Bn_Activation: 2-75                         [1, 255, 19, 19]          --
│    │    └─ModuleList: 3-70                            --                        261,375
│    └─YoloLayer: 2-76                                  [1, 17328, 1, 4]          --
│    └─YoloLayer: 2-77                                  [1, 4332, 1, 4]           --
│    └─YoloLayer: 2-78                                  [1, 1083, 1, 4]           --
=========================================================================================================
Total params: 64,363,101
Trainable params: 64,363,101
Non-trainable params: 0
Total mult-adds (G): 64.20
=========================================================================================================
Input size (MB): 4.44
Forward/backward pass size (MB): 1814.99
Params size (MB): 257.45
Estimated Total Size (MB): 2076.88
=========================================================================================================

NOTE: The model output summary is long and may be truncated by your notebook with the default Jupyter notebook settings

Looking at the model summary and the model definition in PyTorch, it's important to note that the YoloLayer is an uncommon, custom nn.Module defined specifically for this model. This particular model architecture, has three YoloLayer layers at the very end of the model.

For the purposes of this tutorial, it's also important to examine the YoloLayer.forward function which calls the YoloLayer.yolo_forward_dynamic function. All torch.nn.Module functions must have a forward member function defined that overrides the virtual function in that parent class. This function is defines what the layer does during a forward-pass through the model, i.e. inference.

Below is a code snippet from the YoloLayer.yolo_forward_dynamic function:

bxy_list = []
bwh_list = []
det_confs_list = []
cls_confs_list = []

for i in range(num_anchors):
    begin = i * (5 + num_classes)
    end = (i + 1) * (5 + num_classes)
    
    bxy_list.append(output[:, begin : begin + 2])
    bwh_list.append(output[:, begin + 2 : begin + 4])
    det_confs_list.append(output[:, begin + 4 : begin + 5])
    cls_confs_list.append(output[:, begin + 5 : end])

## Shape: [batch, num_anchors * 2, H, W]
bxy = torch.cat(bxy_list, dim=1)
## Shape: [batch, num_anchors * 2, H, W]
bwh = torch.cat(bwh_list, dim=1)

## Shape: [batch, num_anchors, H, W]
det_confs = torch.cat(det_confs_list, dim=1)
## Shape: [batch, num_anchors * H * W]
det_confs = det_confs.view(output.size(0), num_anchors * output.size(2) * output.size(3))

## Shape: [batch, num_anchors * num_classes, H, W]
cls_confs = torch.cat(cls_confs_list, dim=1)
## Shape: [batch, num_anchors, num_classes, H * W]
cls_confs = cls_confs.view(output.size(0), num_anchors, num_classes, output.size(2) * output.size(3))
## Shape: [batch, num_anchors, num_classes, H * W] --> [batch, num_anchors * H * W, num_classes] 
cls_confs = cls_confs.permute(0, 1, 3, 2).reshape(output.size(0), num_anchors * output.size(2) * output.size(3), num_classes)

## Apply sigmoid(), exp() and softmax() to slices
#
bxy = torch.sigmoid(bxy) * self.scale_x_y - 0.5 * (self.scale_x_y - 1)
bwh = torch.exp(bwh)
det_confs = torch.sigmoid(det_confs)
cls_confs = torch.sigmoid(cls_confs)

## Prepare C-x, C-y, P-w, P-h (None of them are torch related)
grid_x = np.expand_dims(np.expand_dims(np.expand_dims(np.linspace(0, output.size(3) - 1, output.size(3)), axis=0).repeat(output.size(2), 0), axis=0), axis=0)
grid_y = np.expand_dims(np.expand_dims(np.expand_dims(np.linspace(0, output.size(2) - 1, output.size(2)), axis=1).repeat(output.size(3), 1), axis=0), axis=0)
## grid_x = torch.linspace(0, W - 1, W).reshape(1, 1, 1, W).repeat(1, 1, H, 1)
## grid_y = torch.linspace(0, H - 1, H).reshape(1, 1, H, 1).repeat(1, 1, 1, W)

anchor_w = []
anchor_h = []
for i in range(num_anchors):
    anchor_w.append(anchors[i * 2])
    anchor_h.append(anchors[i * 2 + 1])

bx_list = []
by_list = []
bw_list = []
bh_list = []

## Apply C-x, C-y, P-w, P-h
for i in range(num_anchors):
    ii = i * 2
    # Shape: [batch, 1, H, W]
    bx = bxy[:, ii : ii + 1] + torch.tensor(grid_x, device=device, dtype=torch.float32) # grid_x.to(device=device, dtype=torch.float32)
    # Shape: [batch, 1, H, W]
    by = bxy[:, ii + 1 : ii + 2] + torch.tensor(grid_y, device=device, dtype=torch.float32) # grid_y.to(device=device, dtype=torch.float32)
    # Shape: [batch, 1, H, W]
    bw = bwh[:, ii : ii + 1] * anchor_w[i]
    # Shape: [batch, 1, H, W]
    bh = bwh[:, ii + 1 : ii + 2] * anchor_h[i]

    bx_list.append(bx)
    by_list.append(by)
    bw_list.append(bw)
    bh_list.append(bh)


########################################
##   Figure out bboxes from slices     #
########################################

## Shape: [batch, num_anchors, H, W]
bx = torch.cat(bx_list, dim=1)
## Shape: [batch, num_anchors, H, W]
by = torch.cat(by_list, dim=1)
## Shape: [batch, num_anchors, H, W]
bw = torch.cat(bw_list, dim=1)
## Shape: [batch, num_anchors, H, W]
bh = torch.cat(bh_list, dim=1)

## Shape: [batch, 2 * num_anchors, H, W]
bx_bw = torch.cat((bx, bw), dim=1)
## Shape: [batch, 2 * num_anchors, H, W]
by_bh = torch.cat((by, bh), dim=1)

## normalize coordinates to [0, 1]
bx_bw /= output.size(3)
by_bh /= output.size(2)

## Shape: [batch, num_anchors * H * W, 1]
bx = bx_bw[:, :num_anchors].view(output.size(0), num_anchors * output.size(2) * output.size(3), 1)
by = by_bh[:, :num_anchors].view(output.size(0), num_anchors * output.size(2) * output.size(3), 1)
bw = bx_bw[:, num_anchors:].view(output.size(0), num_anchors * output.size(2) * output.size(3), 1)
bh = by_bh[:, num_anchors:].view(output.size(0), num_anchors * output.size(2) * output.size(3), 1)

bx1 = bx - bw * 0.5
by1 = by - bh * 0.5
bx2 = bx1 + bw
by2 = by1 + bh

## Shape: [batch, num_anchors * h * w, 4] -> [batch, num_anchors * h * w, 1, 4]
boxes = torch.cat((bx1, by1, bx2, by2), dim=2).view(output.size(0), num_anchors * output.size(2) * output.size(3), 1, 4)
## boxes = boxes.repeat(1, 1, num_classes, 1)

## boxes:     [batch, num_anchors * H * W, 1, 4]
## cls_confs: [batch, num_anchors * H * W, num_classes]
## det_confs: [batch, num_anchors * H * W]

det_confs = det_confs.view(output.size(0), num_anchors * output.size(2) * output.size(3), 1)
confs = cls_confs * det_confs

## boxes: [batch, num_anchors * H * W, 1, 4]
## confs: [batch, num_anchors * H * W, num_classes]

Without going into too much detail, this function slices the concatenated tensor output into components:

  • bxy_list, the (x, y) coordinate-pairs of object bounding boxes
  • bwh_list, the widths and heights of the object bounding boxes
  • det_confs_list, the 'objectiveness' scores for the objects found in the bounding boxes
  • cls_confs_list, the numerical class label of the object predicted to be in the bounding boxes

These components are the outputs of the YOLOv4 model's backbone and are object detections in the high-dimensional feature space. The feature space is a compressed grid of the original input image. The feature space makes a fixed-number of object detections for each grid space. This is why the model produces an 'objectiveness' score; the model must make a prediction even if it is confident that no object exists in the grid space.

This forward-pass function aggregates this component data, filters out the low-scoring predictions, and formats the high-scoring predictions into more easily understood bounding boxes and class confidence scores that can be rendered onto an image and visually verified by a human, like below:

This forward-pass function, as a developer, is easy enough to read, understand, and potentially port to a C++ representation. Let's continue to see how this algorithm looks after we've converted this model to ONNX format so that it can be compiled by the CGC.

5. Convert the model to ONNX

model = Yolov4(n_classes=80, inference=True)

if PATH_TO_DOWNLOADED_WEIGHTS:
    pretrained_dict = torch.load(PATH_TO_DOWNLOADED_WEIGHTS, map_location=torch.device("cpu"))
    model.load_state_dict(pretrained_dict)

input_names = ["input"]
output_names = ["boxes", "confs"]

x = torch.randn((INPUT_BATCH_SIZE, 3, INPUT_IMAGE_HEIGHT, INPUT_IMAGE_WIDTH), requires_grad=True)
onnx_file_name = "yolov4_{}_3_{}_{}_static.onnx".format(
    INPUT_BATCH_SIZE, INPUT_IMAGE_HEIGHT, INPUT_IMAGE_WIDTH
)

## Export the model
print("Export the onnx model ...")
torch.onnx.export(
    model,
    x,
    onnx_file_name,
    export_params=True,
    opset_version=14,
    do_constant_folding=True,
    input_names=input_names,
    output_names=output_names,
    dynamic_axes=None,
)

print("Onnx model exporting done")
Export the onnx model ...


/tmp/ipykernel_2686/2593715709.py:490: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.
  bx = bxy[:, ii : ii + 1] + torch.tensor(
/tmp/ipykernel_2686/2593715709.py:494: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.
  by = bxy[:, ii + 1 : ii + 2] + torch.tensor(


Onnx model exporting done

6. Visualize & Inspect the ONNX model using Netron

Netron is an open-source viewer for deep neural networks (DNN) models. Below we use Netron to visualize the YOLOv4 model we recently converted to ONNX format:

import IPython
import netron

netron.start(onnx_file_name, 6006, browse=False)
IPython.display.IFrame(f"http://localhost:6006", width=1000, height=1000)
Serving 'yolov4_1_3_608_608_static.onnx' at http://localhost:6006

<iframe width="1000" height="1000" src="http://localhost:6006" frameborder="0" allowfullscreen

</iframe>

Notice in the ONNX graph that the end of the network is particularly dense with operators:

If we zoom in a bit, we can see that these operators are mostly basic math operators, such as Exp, Mul, Div, Add, and Sub, alongside non-computational tensor-editing operators, such as Slice, Concat, and Reshape.

The reason that the tail-end of this ONNX representation of the YOLO model looks so chaotic is because ONNX has no better way of representing the custom YoloLayer defined by the authors in PyTorch. This custom layer only exists in the YOLOv4 model architecture and therefore will likely never be adopted or standardized by the ONNX community. Further, the ability to convert this more traditional algorithm into an ONNX representation using more basic operators, speaks to the robustness of ONNX as a conversion tool.

The main point of this tutorial is that, while ONNX is a robust tool and can represent more traditional algorithms in its graph representation framework if asked to do so, it is inherently counterintuitive to the value added by ONNX.

Similarly, these complex graph substructures of basic math and non-computational tensor-editing operators are challenging for the Chimera Graph Compiler. Simply put, it involves trying to reverse engineer the traditional algorithm from the mangled ONNX representation. As the CGC matures, we hope to be able to accomodate more of these complex substructures; however, in the near term you are likely to experience issues compiling a model with the CGC that contain traditional algorithms represented as ONNX graphs.

Quadric recommends that if you experience issues compiling your model, check the ONNX graph for structures like these. They will typically be found at the end of the network graphs. If you ecounter this situation, we recommend that you try to identify the traditional algorithm in the original model that resulted in the complext graph, such as the custom YoloLayer of the original PyTorch model in this example, and attempt to write it as a custom operator when porting your model to the Chimera platform.

To learn more about the basics of writing a custom operator, refer to the Tutorial: Custom Op Insertion into a Model.

Table of Contents
Introduction to the Chimera SDK
Chimera SDK Quick Start Guide
Chimera SDK Command Line Interface (CLI)
Tutorial: Using SDK as a Library
Chimera Graph Compiler (CGC)
Chimera LLVM C++ Compiler
Chimera SDK Licensing Policy Documentation
Glossary

Sign in to your account

Don't have an account? Create an Account
By signing in, you are agreeing to our Terms of Use and Privacy Policy.

Develop.

Simulate.

Profile.

Collaborate.