Ejemplo n.º 1
0
def gen_1input_1output_mat_operator(env: jinja2.environment.Environment, graph: onnx.GraphProto,
                                    node: onnx.NodeProto) -> GeneratedScriptPart:
    """
    Generates simple operators like 'y = -x' which have one input and one output.
    :param env:  Jinja environment to load the template files
    :param graph: the onnx-graph for which the script shall be generated
    :param node: the onnx-node for which the script shall be generated
    :return: The generated script part
    """
    template_for_operator = {
        "Neg": "neg.dml.jinja",
    }

    operator_template = env.get_template("operators/" + template_for_operator[node.op_type])

    if len(node.attribute) != 0:
        raise Exception("attributes not supported for operator")

    if len(list(node.input)) != 1 or len(list(node.output)) != 1:
        raise Exception("Operator needs 1 input and 1 output")

    node_render = operator_template.render(
        input=list(node.input)[0],
        output=list(node.output)[0],
        doc_string=node.doc_string
    )
    return GeneratedScriptPart(dml_script=node_render)
Ejemplo n.º 2
0
def setup_website_dir(env: jinja2.environment.Environment, path: str,
                      all_players: Iterable) -> None:
    """Create the website dir and add static content."""
    print("Writing HTML to %s" % path)

    _mkdir(path)
    _mkdir(os.path.join(path, "api"))
    _mkdir(os.path.join(path, "api", "1"))
    _mkdir(os.path.join(path, "api", "1", "player"))
    _mkdir(os.path.join(path, "api", "1", "player", "wins"))

    print("Copying static assets")
    src = os.path.join(os.path.dirname(__file__), "html_static")
    dst = os.path.join(path, "static")
    if sys.platform != "win32":
        subprocess.run(["rsync", "-a", src + "/", dst + "/"])
    else:
        rsync_replacement(src, dst)

    print("Generating player list")
    _write_file(
        path=os.path.join(dst, "js", "players.json"),
        data=json.dumps([p.name for p in all_players]),
    )

    print("Writing minified local JS")
    js_template = env.get_template("dcss-scoreboard.js")
    _write_file(
        path=os.path.join(WEBSITE_DIR, "static/js/dcss-scoreboard.js"),
        data=jsmin.jsmin(js_template.render()),
    )
Ejemplo n.º 3
0
def gen_2input_1output_operator(env: jinja2.environment.Environment, graph: onnx.GraphProto,
                                node: onnx.NodeProto) -> GeneratedScriptPart:
    """
    Generates simple operator calls like 'z = x + y' which have two inputs (left and right) and one output.
    :param env: Jinja environment to load the template files
    :param graph: the onnx-graph for which the script shall be generated
    :param node: the onnx-node for which the script shall be generated
    :return: The generated script part
    """
    operator = {
        "Add": "+",
        "Sub": "-",
        "MatMul": "%*%",
        "And": "&",
        "Or": "|"
    }
    operator_template = env.get_template("operators/2input_1output_operator.dml.jinja")

    if len(node.attribute) != 0:
        raise Exception("attributes not supported for operator")

    if len(list(node.input)) > 2 or len(list(node.output)) > 1:
        raise Exception("Operator needs 2 inputs and 1 output")

    node_render = operator_template.render(
        input_0=list(node.input)[0],
        input_1=list(node.input)[1],
        output=list(node.output)[0],
        operator=operator[node.op_type],
        doc_string=node.doc_string
    )
    return GeneratedScriptPart(node_render)
Ejemplo n.º 4
0
def setup_website_dir(env: jinja2.environment.Environment,
                      path: str,
                      all_players: Iterable) -> None:
    """Create the website dir and add static content."""
    print("Writing HTML to %s" % path)

    _mkdir(path)
    _mkdir(os.path.join(path, 'api'))
    _mkdir(os.path.join(path, 'api', '1'))
    _mkdir(os.path.join(path, 'api', '1', 'player'))
    _mkdir(os.path.join(path, 'api', '1', 'player', 'wins'))

    print("Copying static assets")
    src = os.path.join(os.path.dirname(__file__), 'html_static')
    dst = os.path.join(path, 'static')
    if sys.platform != 'win32':
        subprocess.run(['rsync', '-a', src + '/', dst + '/'])
    else:
        rsync_replacement(src, dst)

    print("Generating player list")
    _write_file(
        path=os.path.join(dst, 'js', 'players.json'),
        data=json.dumps([p.name for p in all_players]))

    print("Writing minified local JS")
    js_template = env.get_template('dcss-scoreboard.js')
    _write_file(
        path=os.path.join(WEBSITE_DIR, 'static/js/dcss-scoreboard.js'),
        data=jsmin.jsmin(js_template.render()))
Ejemplo n.º 5
0
def render_one_file(
    env: jinja2.environment.Environment,
    template_file_name: str,
    output_dir: str,
    variables: dict,
) -> None:
    """Merge variables for the current output dir and render

    Args:
        env: Jinja2 Environment object
        template_file_name: Jinja2 template file name
        output_dir: Directory for rendered files
        variables: Variables to be used for rendering

    Returns:
        None

    """
    try:
        tmpl = env.get_template(template_file_name)
    except jinja2.exceptions.TemplateSyntaxError as err:
        # expected token 'end of statement block', got '='
        raise RendererFailed(f'Bad template: {err}')
    try:
        rendered = tmpl.render(**variables)
    except jinja2.exceptions.UndefinedError as err:
        # 'dict object' has no attribute 'worker'
        raise RendererFailed(f'Missing variable: {err}')
    os.makedirs(output_dir, exist_ok=True)
    output_path = os.path.join(output_dir, template_file_name)
    with open(output_path, 'w') as file_handler:
        file_handler.write(rendered)
        # the jinja renderer seems to strip the end-of-file new-line
        file_handler.write('\n')
    return
Ejemplo n.º 6
0
def write_index(s: sqlalchemy.orm.session.Session,
                env: jinja2.environment.Environment) -> None:
    """Write the index page."""
    print("Writing index")
    template = env.get_template("index.html")
    data = render_index(s, template)
    _write_file(path=os.path.join(WEBSITE_DIR, "index.html"), data=data)
Ejemplo n.º 7
0
def gen_model_header(env: jinja2.environment.Environment,
                     model: onnx.ModelProto) -> str:
    """
    Generates the header of the script for the given `model`

    :param env: Jinja environment to load the template files
    :param model: the onnx model for which the header shall be generated
    :return: the generated header
    """
    header_template = env.get_template("model_header.dml.jinja")
    header_infos = dict()

    header_infos["ir_version"] = model.ir_version
    opset_import = list()
    for opset in model.opset_import:
        if len(opset.domain) == 0:
            opset.domain = "ONNX"
        opset_import.append(opset.domain + "/" + str(opset.version))
    header_infos["producer_name"] = model.producer_name
    header_infos["producer_version"] = model.producer_version
    header_infos["domain"] = model.domain
    header_infos["model_version"] = model.model_version
    header_infos["doc_string"] = model.doc_string
    metadata_props = [[prop.key, prop.vale] for prop in model.metadata_props]

    model_header_render = header_template.render(
        header_components=header_infos,
        opset_import=opset_import,
        metadata_props=metadata_props)
    return model_header_render
Ejemplo n.º 8
0
def setup_website_dir(
    env: jinja2.environment.Environment, path: str, all_players: Iterable
) -> None:
    """Create the website dir and add static content."""
    print("Writing HTML to %s" % path)

    _mkdir(path)
    _mkdir(os.path.join(path, "api"))
    _mkdir(os.path.join(path, "api", "1"))
    _mkdir(os.path.join(path, "api", "1", "player"))
    _mkdir(os.path.join(path, "api", "1", "player", "wins"))

    print("Copying static assets")
    src = os.path.join(os.path.dirname(__file__), "html_static")
    dst = os.path.join(path, "static")
    if sys.platform != "win32":
        subprocess.run(["rsync", "-a", src + "/", dst + "/"])
    else:
        rsync_replacement(src, dst)

    print("Generating player list")
    _write_file(
        path=os.path.join(dst, "js", "players.json"),
        data=json.dumps([p.name for p in all_players]),
    )

    print("Writing minified local JS")
    js_template = env.get_template("dcss-scoreboard.js")
    _write_file(
        path=os.path.join(WEBSITE_DIR, "static/js/dcss-scoreboard.js"),
        data=jsmin.jsmin(js_template.render()),
    )
Ejemplo n.º 9
0
def write_highscores(s: sqlalchemy.orm.session.Session,
                     env: jinja2.environment.Environment) -> None:
    """Write the highscores page."""
    print("Writing highscores")
    template = env.get_template("highscores.html")
    data = render_highscores(s, template)
    _write_file(path=os.path.join(WEBSITE_DIR, "highscores.html"), data=data)
Ejemplo n.º 10
0
def write_player_pages(
    s: sqlalchemy.orm.session.Session,
    env: jinja2.environment.Environment,
    players: Sequence,
) -> None:
    """Write all player pages."""
    print("Writing %s player pages... " % len(players))
    start2 = time.time()
    player_html_path = os.path.join(WEBSITE_DIR, "players")
    if not os.path.exists(player_html_path):
        os.mkdir(player_html_path)
    global_records = model.get_gobal_records(s)
    template = env.get_template("player.html")

    n = 0
    for player in players:
        data = render_player_page(s, template, player, global_records)
        write_player_page(player_html_path, player.url_name, data)
        model.updated_player_page(s, player)
        n += 1
        if not n % 100:
            print(n)
    s.commit()
    end = time.time()
    print("Wrote player pages in %s seconds" % round(end - start2, 2))
Ejemplo n.º 11
0
def write_player_pages(
    s: sqlalchemy.orm.session.Session,
    env: jinja2.environment.Environment,
    players: Sequence,
) -> None:
    """Write all player pages."""
    print("Writing %s player pages... " % len(players))
    start2 = time.time()
    player_html_path = os.path.join(WEBSITE_DIR, "players")
    if not os.path.exists(player_html_path):
        os.mkdir(player_html_path)
    global_records = model.get_gobal_records(s)
    template = env.get_template("player.html")

    n = 0
    for player in players:
        data = render_player_page(s, template, player, global_records)
        write_player_page(player_html_path, player.url_name, data)
        model.updated_player_page(s, player)
        n += 1
        if not n % 100:
            print(n)
    s.commit()
    end = time.time()
    print("Wrote player pages in %s seconds" % round(end - start2, 2))
Ejemplo n.º 12
0
def gen_simple_function_call(env: jinja2.environment.Environment, graph: onnx.GraphProto,
                             node: onnx.NodeProto) -> GeneratedScriptPart:
    """
    Generates a simple function call by directly providing the node inputs as arguments
    and node outputs as outputs to a function call. Additionally adds the required imports.

    :param env: Jinja environment to load the template files
    :param graph: the onnx-graph for which the script shall be generated
    :param node: the onnx-node for which the script shall be generated
    :return: The generated script part
    """
    operator_template = env.get_template("operators/" + "function_call.dml.jinja")
    import_template = env.get_template("module_import.dml.jinja")

    if len(list(node.output)) != 1:
        raise Exception("Function call needs output")

    if len(node.attribute) != 0:
        raise Exception("Attributes not supported for this generator")

    required_import = {
        "Relu": {"path": "/nn/layers/relu.dml", "import_name": "relu_layer", "function_name": "forward"},
        "Tanh": {"path": "/nn/layers/tanh.dml", "import_name": "tanh_layer", "function_name": "forward"},
        "Sigmoid": {"path": "/nn/layers/sigmoid.dml", "import_name": "sigmoid_layer", "function_name": "forward"},
        "Softmax": {"path": "/nn/layers/softmax.dml", "import_name": "softmax_layer", "function_name": "forward"}
    }

    import_render = ""
    function_name = node.op_type
    function_namespace = ""
    if node.op_type in required_import.keys():
        module_import = required_import[node.op_type]
        import_render = import_template.render(
            path=module_import["path"],
            name=module_import["import_name"]
        )
        function_name = module_import["function_name"]
        function_namespace = module_import["import_name"]

    node_render = operator_template.render(
        function_namespace=function_namespace,
        function=function_name,
        arguments=list(node.input),
        outputs=list(node.output),
        doc_string=node.doc_string
    )
    return GeneratedScriptPart(imports=[import_render], dml_script=node_render)
Ejemplo n.º 13
0
def write_highscores(
    s: sqlalchemy.orm.session.Session, env: jinja2.environment.Environment
) -> None:
    """Write the highscores page."""
    print("Writing highscores")
    template = env.get_template("highscores.html")
    data = render_highscores(s, template)
    _write_file(path=os.path.join(WEBSITE_DIR, "highscores.html"), data=data)
Ejemplo n.º 14
0
def write_index(
    s: sqlalchemy.orm.session.Session, env: jinja2.environment.Environment
) -> None:
    """Write the index page."""
    print("Writing index")
    template = env.get_template("index.html")
    data = render_index(s, template)
    _write_file(path=os.path.join(WEBSITE_DIR, "index.html"), data=data)
Ejemplo n.º 15
0
def write_streaks(s: sqlalchemy.orm.session.Session,
                  env: jinja2.environment.Environment) -> None:
    """Write the streak page."""
    print("Writing streaks")
    template = env.get_template('streaks.html')
    active_streaks = model.get_streaks(s, active=True, max_age=365)
    best_streaks = model.get_streaks(s, limit=10)
    _write_file(path=os.path.join(WEBSITE_DIR, 'streaks.html'),
                data=template.render(active_streaks=active_streaks,
                                     best_streaks=best_streaks))
Ejemplo n.º 16
0
def write_streaks(
    s: sqlalchemy.orm.session.Session, env: jinja2.environment.Environment
) -> None:
    """Write the streak page."""
    print("Writing streaks")
    template = env.get_template("streaks.html")
    active_streaks = model.get_streaks(s, active=True, max_age=365)
    best_streaks = model.get_streaks(s, limit=10)
    _write_file(
        path=os.path.join(WEBSITE_DIR, "streaks.html"),
        data=template.render(active_streaks=active_streaks, best_streaks=best_streaks),
    )
Ejemplo n.º 17
0
def gen_dropout_call(env: jinja2.environment.Environment, graph: onnx.GraphProto,
                     node: onnx.NodeProto) -> GeneratedScriptPart:
    operator_template = env.get_template("operators/" + "function_call.dml.jinja")
    import_template = env.get_template("module_import.dml.jinja")

    function_namespace = "dropout_layer"
    function_name = "forward"
    path = "/nn/layers/dropout.dml"

    #  * Inputs:
    #    *  - X: Inputs, of shape (any, any).
    #    *  - p: Probability of keeping a neuron output.
    #    *  - seed: [Optional: -1] Random number generator seed to allow for
    #    *      deterministic evaluation.  Set to -1 for a random seed.
    # * Outputs:
    #    *  - out: Outputs, of same shape as `X`.
    #    *  - mask: Dropout mask used to compute the output.

    X = list(node.input)[0]
    p = 0.5
    seed = -1
    if len(list(node.attribute)) > 0:
        attributes = list(node.attribute)
        if attributes[0].name != "ratio" or len(attributes) > 1:
            raise Exception("Error in generating dropout call invalid attributes" + str(attributes))
        p = attributes[0].f

    import_render = import_template.render(
        path=path,
        name=function_namespace
    )

    node_render = operator_template.render(
        function_namespace=function_namespace,
        function=function_name,
        arguments=[X, p, seed],
        outputs=list(node.output),
        doc_string=node.doc_string
    )
    return GeneratedScriptPart(imports=[import_render], dml_script=node_render)
Ejemplo n.º 18
0
def gen_if_call(env: jinja2.environment.Environment, graph: onnx.GraphProto, node: onnx.NodeProto) \
        -> GeneratedScriptPart:
    operator_template = env.get_template("operators/if_operator.dml.jinja")
    function_call_template = env.get_template("operators/function_call.dml.jinja")

    if len(node.input) != 1:
        raise Exception("Wrong number of inputs")
    if len(node.attribute) != 2:
        raise Exception("Wrong number of attributes")
    if node.attribute[0].name != "else_branch" or node.attribute[1].name != "then_branch":
        raise Exception("Wrong attributes")

    else_graph = node.attribute[0].g
    then_graph = node.attribute[1].g

    else_call = function_call_template.render(
        doc_string="",
        function_namespace="",
        function=util.generate_function_name(else_graph.name),
        arguments=[i.name for i in list(else_graph.input)],
        outputs=[o.name for o in list(else_graph.output)],
    )

    then_call = function_call_template.render(
        doc_string="",
        function_namespace="",
        function=util.generate_function_name(then_graph.name),
        arguments=[i.name for i in list(then_graph.input)],
        outputs=[o.name for o in list(then_graph.output)],
    )

    sub_graphs = [else_graph, then_graph]

    node_render = operator_template.render(
        cond=node.input[0],
        then_function_call=then_call,
        else_function_call=else_call
    )

    return GeneratedScriptPart(dml_script=node_render, sub_graphs=sub_graphs)
Ejemplo n.º 19
0
def generate_sample(sample,
                    env: jinja2.environment.Environment,
                    api_schema: api.API,
                    template_name: str = DEFAULT_TEMPLATE_NAME) -> str:
    """Generate a standalone, runnable sample.

    Rendering and writing the rendered output is left for the caller.

    Args:
        sample (Any): A definition for a single sample generated from parsed yaml.
        env (jinja2.environment.Environment): The jinja environment used to generate
                                              the filled template for the sample.
        api_schema (api.API): The schema that defines the API to which the sample belongs.
        template_name (str): An optional override for the name of the template
                             used to generate the sample.

    Returns:
        str: The rendered sample.
    """
    sample_template = env.get_template(template_name)

    service_name = sample["service"]
    service = api_schema.services.get(service_name)
    if not service:
        raise types.UnknownService("Unknown service: {}", service_name)

    rpc_name = sample["rpc"]
    rpc = service.methods.get(rpc_name)
    if not rpc:
        raise types.RpcMethodNotFound(
            "Could not find rpc in service {}: {}".format(
                service_name, rpc_name))

    calling_form = types.CallingForm.method_default(rpc)

    v = Validator(rpc)
    # Tweak some small aspects of the sample to set sane defaults for optional
    # fields, add fields that are required for the template, and so forth.
    v.preprocess_sample(sample, api_schema)
    sample["request"] = v.validate_and_transform_request(
        calling_form, sample["request"])
    v.validate_response(sample["response"])

    return sample_template.render(
        file_header=FILE_HEADER,
        sample=sample,
        imports=[],
        calling_form=calling_form,
        calling_form_enum=types.CallingForm,
    )
Ejemplo n.º 20
0
def render_function(env: jinja2.environment.Environment,
                    graph: onnx.GraphProto,
                    generated_node_scripts: [str]) -> str:
    """
    Generates the dml function for the given `graph` and inserts the 'generated_node_scripts' in
    the function-body.

    :param env: Jinja environment to load the template files
    :param graph: the graph for which the function shall be generated
    :param generated_node_scripts: the node scripts in correct order for the function-body
    :return: the generated function
    """
    function_template = env.get_template("graph_function.dml.jinja")

    inputs_with_initializers = onnx_helper.get_graph_inputs_with_initializers(
        graph)
    inputs_without_initializers = onnx_helper.get_graph_inputs_without_initializers(
        graph)
    outputs = list(graph.output)

    # prepare inputs/outputs
    function_inputs = [
        onnx_helper.PreparedValue(i) for i in inputs_without_initializers
    ]
    function_outputs = [onnx_helper.PreparedValue(o) for o in outputs]
    function_initializers = [
        onnx_helper.PreparedValue(info, init)
        for info, init in inputs_with_initializers
    ]

    # render function
    graph_function_render = function_template.render(
        function_inputs=function_inputs,
        function_outputs=function_outputs,
        function_start_initializers=function_initializers,
        graph_function_name=util.generate_function_name(graph.name),
        graph_function_description=graph.doc_string,
        node_scripts=generated_node_scripts)
    return graph_function_render
Ejemplo n.º 21
0
def write_404(env: jinja2.environment.Environment) -> None:
    """Write the 404 page."""
    print("Writing 404")
    template = env.get_template("404.html")
    _write_file(path=os.path.join(WEBSITE_DIR, "404.html"),
                data=template.render())
Ejemplo n.º 22
0
def write_404(env: jinja2.environment.Environment) -> None:
    """Write the 404 page."""
    print("Writing 404")
    template = env.get_template("404.html")
    _write_file(path=os.path.join(WEBSITE_DIR, "404.html"), data=template.render())
Ejemplo n.º 23
0
def gen_maxpool_call(env: jinja2.environment.Environment, graph: onnx.GraphProto,
                     node: onnx.NodeProto) -> GeneratedScriptPart:
    operator_template = env.get_template("operators/" + "function_call.dml.jinja")
    import_template = env.get_template("module_import.dml.jinja")

    function_namespace = "maxpool_layer"
    function_name = "forward"
    path = "/nn/layers/max_pool2d.dml"

    #    * Inputs:
    #    *  - X: Inputs, of shape (N, C*Hin*Win).
    #    *  - C: Number of input channels (dimensionality of input depth).
    #    *  - Hin: Input height.
    #    *  - Win: Input width.
    #    *  - Hf: Filter height.
    #    *  - Wf: Filter width.
    #    *  - strideh: Stride over height.
    #    *  - stridew: Stride over width.
    #    *  - padh: Padding for top and bottom sides.
    #    *      A typical value is 0.
    #    *  - padw: Padding for left and right sides.
    #    *      A typical value is 0.
    #    *
    #    * Outputs:
    #    *  - out: Outputs, of shape (N, C*Hout*Wout).
    #    *  - Hout: Output height.
    #    *  - Wout: Output width.

    if len(node.input) != 1:
        raise Exception("Invalid number of inputs")
    if len(node.output) < 1 or len(node.output) > 2:
        raise Exception("Invalid number of outputs")

    # Inputs
    x = onnx_helper.get_value_info(graph, node.input[0])
    # dimensions are (N x C x H x W), where N is the batch size, C is the number of channels,
    # and H and W are the height and the width
    x_shape = onnx_helper.get_valueinfo_dimensions(x)
    if len(x_shape) > 4:
        raise NotImplementedError("Currently only MaxPool-2D supported")

    batch_size = x_shape[0]  # TODO: currently not used
    C = x_shape[1]
    Hin = x_shape[2]
    Win = x_shape[3]

    # Attributes
    auto_pad = "NOTSET"
    ceil_mode = 0
    dilations = [1, 1]
    kernel_shape = None
    pads = [0, 0, 0, 0]
    storage_order = 0
    strides = [1, 1]
    for attribute in node.attribute:
        if attribute.name == "auto_pad":
            auto_pad = attribute.strings[0]
        elif attribute.name == "ceil_mode":
            ceil_mode = attribute.ints[0]
            raise NotImplementedError("Currently no support for ceil_mode")
        elif attribute.name == "dilations":
            raise NotImplementedError
        elif attribute.name == "kernel_shape":
            kernel_shape = attribute.ints
        elif attribute.name == "pads":
            pads = attribute.ints
        elif attribute.name == "storage_order":
            raise NotImplementedError("Currently no support for storage_order")
        elif attribute.name == "strides":
            strides = attribute.ints
        else:
            raise Exception("Invalid Attribute")

    if kernel_shape is None:
        raise Exception("kernel_shape attribute is required")

    Hf = kernel_shape[0]
    Wf = kernel_shape[1]
    strideh = strides[0]
    stridew = strides[1]
    padh, padw = __compute_pad(auto_pad, Hf, Wf, strides, pads, Hin, Win)

    # Create render
    node_render = operator_template.render(
        function_namespace=function_namespace,
        function=function_name,
        arguments=[x.name, C, Hin, Win, Hf, Wf, strideh, stridew, padh, padw],
        outputs=list(node.output),
        doc_string=node.doc_string
    )

    import_render = import_template.render(
        path=path,
        name=function_namespace
    )

    return GeneratedScriptPart(imports=[import_render], dml_script=node_render)
Ejemplo n.º 24
0
def gen_conv_call(env: jinja2.environment.Environment, graph: onnx.GraphProto, node: onnx.NodeProto) \
        -> GeneratedScriptPart:
    operator_template = env.get_template("operators/" + "function_call.dml.jinja")
    import_template = env.get_template("module_import.dml.jinja")

    function_namespace = "conv_layer"
    function_name = "forward"
    path = "/nn/layers/conv2d.dml"

    #    * Inputs:
    #    *  - X: Inputs, of shape (N, C*Hin*Win).
    #    *  - W: Weights, of shape (F, C*Hf*Wf).
    #    *  - b: Biases, of shape (F, 1).
    #    *  - C: Number of input channels (dimensionality of input depth).
    #    *  - Hin: Input height.
    #    *  - Win: Input width.
    #    *  - Hf: Filter height.
    #    *  - Wf: Filter width.
    #    *  - strideh: Stride over height.
    #    *  - stridew: Stride over width.
    #    *  - padh: Padding for top and bottom sides.
    #    *  - padw: Padding for left and right sides.
    #    *
    #    * Outputs:
    #    *  - out: Outputs, of shape (N, F*Hout*Wout).
    #    *  - Hout: Output height.
    #    *  - Wout: Output width.

    if len(node.input) < 2 or len(node.input) > 3:
        raise Exception("Invalid number of inputs")

    if len(node.output) > 1:
        raise Exception("Invalid number of outputs")

    # Inputs
    x = onnx_helper.get_value_info(graph, node.input[0])
    # size (N x C x H x W), where N is the batch size, C is the number of channels, and H and W are the height and width
    x_shape = onnx_helper.get_valueinfo_dimensions(x)
    if len(x_shape) > 4:
        raise NotImplementedError("Currently only Conv-2D supported")
    batch_size = x_shape[0]  # TODO: Batch size unused?
    C = x_shape[1]
    Hin = x_shape[2]
    Win = x_shape[3]

    w = onnx_helper.get_value_info(graph, node.input[1])
    W_shape = onnx_helper.get_valueinfo_dimensions(w)
    M = W_shape[0]
    C_group = W_shape[1]  # TODO Channels/group unused?
    Hf = W_shape[2]
    Wf = W_shape[3]

    bias = None
    bias_initializer_render = ""
    if len(node.input) == 2:
        # Generate 0-bias if no bias given
        generated_bias_identifier = "gen_bias"
        while onnx_helper.get_value_info(graph, generated_bias_identifier) is not None:
            # add random number to create unique identifier if already exists
            generated_bias_identifier += str(randint())

        bias_init_template = env.get_template("matrix_initialize.dml.jinja")
        bias_initializer_render = bias_init_template.render(
            identifier_name=generated_bias_identifier,
            initializer_values=[0] * M,
            rows=M,
            cols=1
        )
        bias = generated_bias_identifier
    elif len(node.input) == 3:
        bias = node.input[3]

    # Attributes
    auto_pad = "NOTSET"
    dilations = [1, 1]
    group = 1
    pads = [0, 0, 0, 0]
    strides = [1, 1]
    for attribute in node.attribute:
        if attribute.name == "auto_pad":
            auto_pad = attribute.strings[0]
        elif attribute.name == "dilations":
            raise NotImplementedError
        elif attribute.name == "group":
            group = attribute.ints[0]
        elif attribute.name == "kernel_shape":
            kernel_shape = attribute.ints
            if kernel_shape[0] != Hf or kernel_shape[1] != Wf:
                raise Exception("Invalid kernel shape")
        elif attribute.name == "pads":
            pads = attribute.ints
        elif attribute.name == "strides":
            strides = attribute.ints
        else:
            raise Exception("Invalid Attribute")

    strideh = strides[0]
    stridew = strides[1]
    padh, padw = __compute_pad(auto_pad, Hf, Wf, strides, pads, Hin, Win)

    node_render = operator_template.render(
        function_namespace=function_namespace,
        function=function_name,
        arguments=[x.name, w.name, bias, C, Hin, Win, Hf, Wf, strideh, stridew, padh, padw],
        outputs=list(node.output),
        doc_string=node.doc_string
    )

    import_render = import_template.render(
        path=path,
        name=function_namespace
    )

    return GeneratedScriptPart(imports=[import_render], dml_script=bias_initializer_render + "\n" + node_render)