2. Elementwise Operator with Parameters

In this section, you will learn about how to convert custom keras layer which is elementwise operation with hyper parameters.

Complete sample code is included in /example/custom_operator/section2.

Example: Pow

In section 1, we created square operator. Next, we’ll create general power operator defined as follows:

class PowLayer(keras.layers.Layer):
    """
    calculate x^a elementwisely
    """
    def __init__(self, a=1, **kwargs):
        super(PowLayer, self).__init__(**kwargs)
        self.a = a

    def call(self, x):
        return keras.backend.pow(x, a)

Different from SquareLayer in section 1, PowLayer has a hyper parameter named a.

2.1. Define Custom IR Operator

Similar to section 1, we define PowOperator first. Operator.parameters is a dictionary which contains all hyper parameters of this operator.

from webdnn.graph.operators.elementwise import Elementwise

class PowOperator(Elementwise):
    def __init__(self, name, a):
        super(PowOperator, self).__init__(name)
        self.parameters["a"] = a

2.2. Define Converter Handler

Almost is same as section 1.

from webdnn.frontend.keras.converter import KerasConverter

@KerasConverter.register_handler("PowLayer")
def square_converter_handler(converter, keras_layer):
    keras_x = converter.get_input_tensor(keras_layer)[0]
    webdnn_x = converter.get_variable(keras_x)

    # Create operator instance with the hyper parameter
    webdnn_operator = PowOperator(None, a=keras_layer.a)
    webdnn_y, = webdnn_operator(webdnn_x)

    keras_y = converter.get_output_tensor(keras_layer)[0]
    converter.set_variable(keras_y, webdnn_y)

2.3. Define Generator Handler

register_elementwise_kernel(OperatorClass, code, [parameters]) can receive optional third argument. It’s dictionary with value of parameter name and key of lambda function. This lambda function is called with an operator instance and returns each parameter value.

from webdnn.backend.webassembly.kernels.elementwise import register_elementwise_kernel

register_elementwise_kernel(PowOperator,
                            "y = powf(x0, a);",
                            { "a": lambda op: op.parameters["a"] })

All parameters specified in the third argument can be used in kernel code.

For other backends, same feature is provided like follows:

from webdnn.backend.fallback.kernels.elementwise import register_elementwise_kernel as register_elementwise_kernel_fallback
from webdnn.backend.webassembly.kernels.elementwise import register_elementwise_kernel as register_elementwise_kernel_webassembly
from webdnn.backend.webgpu.kernels.elementwise import register_elementwise_kernel as register_elementwise_kernel_webgpu

register_elementwise_kernel_fallback(PowOperator,
                                     "y = Math.pow(x0, a);", # JavaScript
                                     { "a": lambda op: op.parameters["a"] })

register_elementwise_kernel_webassembly(PowOperator,
                                        "y = powf(x0, a);", # C++
                                        { "a": lambda op: op.parameters["a"] })

register_elementwise_kernel_webgpu(PowOperator,
                                   "y = pow(x0, a);",  # Metal (Almost same as C++)
                                   { "a": lambda op: op.parameters["a"] })

Please be careful that fallback backend kernel is written in JavaScript, webassembly backend kernel is written in C++, and webgpu backend kernel is written in Metal.

Test

Let’s test the implementation.

# test.py

import keras
import pow

from webdnn.backend.fallback.generator import FallbackDescriptorGenerator
from webdnn.backend.webassembly.generator import WebassemblyDescriptorGenerator
from webdnn.backend.webgpu.generator import WebGPUDescriptorGenerator
from webdnn.frontend.keras.converter import KerasConverter

# Define Keras model
x = keras.layers.Input((10,))
y = pow.PowLayer(a=3)(x)
model = keras.models.Model([x], [y])

# Convert Keras model into WebDNN graph IR
graph = KerasConverter(batch_size=1).convert(model)

# Generate graph descriptors
WebGPUDescriptorGenerator.generate(graph).save("./output")
WebassemblyDescriptorGenerator.generate(graph).save("./output")
FallbackDescriptorGenerator.generate(graph).save("./output")
<!--index.html-->

<button onclick="main()">RUN</button>
<script src="../../../dist/webdnn.js"></script>
<script type="application/javascript">
    async function main() {
        let runner = await WebDNN.load("./output");
        let x = runner.inputs[0];
        let y = runner.outputs[0];

        x.set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

        await runner.run();

        console.log(y.toActual());
    }
</script>

The result is like follows.

../../_images/section2_result.png