LateNight: where? I use the local environment pytest to take you to play custom operators

Posted by jmarcv on Wed, 19 Jan 2022 03:10:03 +0100

Late at night: where? I use the local environment pytest to take you to play custom operators


Multi play Python debugging framework pytest

Beginner's introduction

Hello everyone, the python general testing framework is unittest+HTMLTestRunner. I have seen the pytest document during this period and found that this framework and rich plugins are very easy to use, so let's learn about pytest

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-djj2egar-1642483999727)( )]

pytest is a very mature and fully functional Python testing framework, which mainly has the following characteristics:

  • Simple, flexible and easy to use
  • Support parameterization
  • It can support simple unit testing and complex function testing. It can also be used for selenium/appnium and other automated testing and interface automated testing (pytest+requests)
  • Pytest has many third-party plug-ins and can customize extensions, such as pytest selenium (integrated selenium), pytest html (perfect html test report generation), pytest rerunfailures (repeated execution of failure case s), pytest xdist (multi CPU distribution), etc
  • skip and xfail processing of test cases
  • It can be well integrated with jenkins
  • report framework - allure also supports pytest

Install pytest:

pip install -U pytest

Verify installed version:

pytest --version

Examples in pytest documentation:

Example 1:

First, we find a directory, create a new folder Pytest, and then create a new, enter the following.

import pytest

# content of
def func(x):
    return x + 1
def test_answer():
    assert func(3) == 5

As shown in the figure above, enter cmd in the directory bar (you can also run it directly in the IDE):

Enter, open the command line and enter "pytest".

pytest returns an error report because func(3) does not return 5.

Example 2:
When we need to write multiple test samples, we can put them into a test class, create a new directory test, and create a new test under the directory_ class. Py, enter the following:

class TestClass:  
    def test_one(self):  
        x = "this"  
        assert 'h' in x  
    def test_two(self):  
        x = "hello"  
        assert hasattr(x, 'check') 

pytest -q


It can be seen from the test results that the test executed two test samples, one failed and the other succeeded. Similarly, we also see the details of the failure example and the intermediate results during execution- q is - quiet, which is used to reduce verbosity. Specifically, it no longer displays the version information of pytest.

How to write a pytest test sample

Through the above two examples, we find that writing pytest test test samples is very simple. We only need to follow the following rules:

  • Test file to test_ Start (or end with _test)
  • Test class starts with test and cannot have init method
  • Test function with test_ start
  • Assertions can be made using the basic assert

Operation mode

A variety of operation modes of pytest make testing and debugging more convenient. Here are five common modes. It should be noted that there are naming requirements for the files running the test, otherwise additional command line instructions are required. The pytest command will find all test *. In the current directory and its subdirectories Py or*_ format files and methods or class es starting with test, otherwise you will be prompted that you can't find a running case.

1. Generate test report (htmlReport) after running

To install pytest HTML:

pip install -U pytest-html

Operation mode:

pytest --html=report.html

We can see that html files are generated in the directory. Click to see:

Report effect:

We can see the error report with very neat layout. In the above report, we can clearly see the test results and error causes, and it is easy to locate problems.

2. Run the specified case

When we write more cases, it is undoubtedly a waste of time if we have to run them all every time. It is very convenient to run them by specifying cases.

Test case, create a new test2 directory, and create a new test under the directory_ se. Py file:

class TestClassOne(object):
    def test_one(self):
        x = "this"
        assert 't'in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, 'check')

class TestClassTwo(object):
    def test_one(self):
        x = "iphone"
        assert 'p'in x

    def test_two(self):
        x = "apple"
        assert hasattr(x, 'check')

Operation mode:

Mode 1: run test directly_ se. All cases in py file:


Mode 2: run test_ se. Two cases under the TestClassOne class in the. Py file:


Mode 3: run test_ se. TestClassTwo in the. Py file is the test under this class_ one:


Note: when defining a class, it needs to start with T, otherwise pytest will not run the class.

We can see that the time of the three is decreasing.

3. Multi process running cases

When there are a lot of cases, the running time will also become very long. If you want to shorten the running time of the script, you can run it with multiple processes.

To install pytest xdist:

pip install -U pytest-xdist

Operation mode:

pytest -n NUM

Where NUM is the number of concurrent processes.

At the beginning, we spent 0.93s on two processes, 0.83s on one process and more than ten seconds on one hundred processes. When we test a small amount of code, multi process has no advantage.

4. Retry running cases

During the interface test, we may encounter 503 or short-term network fluctuations, resulting in the failure of case operation, which is not the result we expect. At this time, we can solve it by running cases again.

Install pytest rerunfailures:

pip install -U pytest-rerunfailures

Operation mode:

pytest --reruns NUM

NUM fill in the number of retries.

5. Display print content

When running the test script, in order to debug or print some content, we will add some print content to the code, but these contents will not be displayed when running pytest. If you bring - s, you can display it.

Operation mode:

pytest -s

In addition, various running modes of pytest can be superimposed. For example, if you want to run four processes at the same time and want to print the content of print. You can use:

pytest -s -n 4


pytest takes you to play with mindspot custom operator

For pre knowledge of operators, please read the reference documents on the official website: Mindspire operator

When the built-in operators are not enough to meet the requirements when developing the network, you can easily and quickly expand the custom operators on the CPU side by using mindspire's Python API and C++ API.

The custom operator is divided into three steps

  • Operator primitive: defines the front-end interface prototype of the operator in the network, which is also the basic unit of the network model. It mainly includes operator name, attribute (optional), input / output name, output shape reasoning method, output dtype reasoning method and other information.
  • Operator implementation: use the C++ API provided by the framework to realize the internal calculation logic of the operator in combination with the specific characteristics of the operator.
  • Operator information: describes the basic information of CPU operators, such as operator name, supported input and output types, etc. It is the basis for back-end operator selection and mapping.

Let's take the user-defined transfer operator as an example to introduce the user-defined operator and test with pytest.

Registration operator primitive

The primitive of an operator is a subclass inherited from PrimitiveWithInfer, and its type name is the operator name.

The CPU operator primitive is defined in the mindspot / OPS / operations path. Select the appropriate file according to the operator type. The interface is defined as follows:

  • Properties are defined by constructors__ init__ Input parameter definition for. The operator of this case has no init attribute, so__ init__ No additional input parameters.
  • The name of the input and output is through init_prim_io_names function definition.
  • Output Tensor's shape and dtype (data type) objects. You can refer to my article article )Check in__ infer__ Function.

Taking the transfer operator primitive as an example, the following example code is given, where the decorator syntax is used.

The parameter "perm" in the transfer operator primitive is passed in as input, but the tuple type "perm" is actually considered as an attribute of the operator during parsing.

from mindspore.ops import PrimitiveWithInfer

class Transpose(PrimitiveWithInfer):
    The definition of the Transpose primitive.
    def __init__(self):
        """Initialize Transpose"""
        self.init_prim_io_names(inputs=['x', 'perm'], outputs=['output'])
    def __infer__(self, x, perm):
        x_shape = x['shape']
        p_value = perm['value']
        if len(x_shape) != len(p_value):
            raise ValueError('The dimension of x and perm must be equal.')
        out_shapes = []
        for i in p_value:
        out = {'shape': tuple(out_shapes),
               'dtype': x['dtype'],
               'value': None}
        return out

Implement CPU operator and registration operator information (here is for simple understanding only, see the official website for details)

Implement CPU operator

Usually, for the implementation of a CPU operator, you need to write a header file and a source file. The file path is mindstock / ccsrc / backend / kernel_ Compiler / CPU, if the logical implementation of the operator is by calling the third-party library MKL-DNN, it will be placed in the subdirectory mkldnn. For details, please refer to oneMKL and oneDNN .

The header file of the operator includes the registration information of the operator and the declaration of the class. The operator class inherits from the CPUKernel parent class and overloads the InitKernel and Launch member functions.

The source file of the operator is the implementation of the class, which mainly overloads the InitKernel and Launch functions. The functions in the AnfRuntimeAlgorithm class in InitKernel implement various operations on operator nodes, shape_ Represents the shape, axis of the first input of the operator_ Represents the attribute perm of the operator.

For details of the AnfRuntimeAlgorithm class, please refer to the mindspire source code mindspore/ccsrc/backend/session/anf_runtime_algorithm.h Statement under.

The Launch function in the source file first obtains the address of each input and output in turn, and then according to axis_ Transform the dimension and assign the value to the space pointed to by the output address.

The implementation process is not described in detail here.

Registration operator information

Operator information is the key information to guide the implementation of back-end selection operator, Ms_ REG_ CPU_ The first parameter in kernel is the name of the registered operator, which is consistent with the operator name in the primitive. The second parameter successively indicates the type of each input and output, and the last parameter is the class name implemented by the operator. The registration code of the transfer operator is as follows:

MS_REG_CPU_KERNEL(Transpose, KernelAttr().AddInputAttr(kNumberTypeFloat32).AddOutputAttr(kNumberTypeFloat32),

The number and order of input and output information defined in operator information, the number and order of input and output information in operator implementation, and the number and order of input and output name list in operator primitive shall be completely consistent.

Compile mindspire

After writing the custom CPU operator, you need to recompile and install mindcore. Please refer to Installation documentation.

Use custom CPU operators and test with pytest

After compilation and installation, the custom CPU operator can be directly used by importing primitives. The following takes the single operator network test of transfer as an example.

In test_ transpose. The network is defined in the. Py file.

import numpy as np
import mindspore.nn as nn
import mindspore.context as context
from mindspore import Tensor
import mindspore.ops as ops

context.set_context(mode=context.GRAPH_MODE, device_target="CPU")

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.transpose = ops.Transpose()

    def construct(self, data):
        return self.transpose(data, (1, 0))

def test_net():
    x = np.arange(2 * 3).reshape(2, 3).astype(np.float32)
    transpose = Net()
    output = transpose(Tensor(x))
    print("output: ", output)

Execute the use case (here we can see that using pytest is to test a function in one of the classes, and all of them start with test_. You can go back to the usage of pytest above to review):

pytest -s

Execution results:

output: [[0, 3]
        [1, 4]
        [2, 5]]

We output the report as html:

pytest --html=report.html -s

You can View html reports.

Define operator back propagation function

If the operator wants to support automatic differentiation, its back propagation function (bprop) needs to be defined in its primitive. You need to describe in bprop the reverse computing logic that uses forward input, forward output, and output gradients to get input gradients. Reverse calculation logic can be composed of built-in operators or custom reverse operators.

The following points should be paid attention to when defining the operator back propagation function:

  • The input parameter order of bprop function is agreed as positive input, positive output and output gradient. If the operator is a multi output operator, the forward output and output gradient will be provided in the form of tuples.
  • The return value form of bprop function is agreed to be a tuple composed of input gradients, and the order of elements in the tuple is consistent with the order of forward input parameters. Even if there is only one input gradient, the return value must be in the form of tuples.

For example, the reverse primitive of transfer is:

import mindspore as ms
import mindspore.ops as ops
from mindspore.ops._grad.grad_base import bprop_getters
fill = ops.Fill()
invert_permutation = ops.InvertPermutation()
transpose = ops.Transpose()
def get_bprop_transpose(self):
    """Generate bprop for Transpose"""

    def bprop(x, perm, out, dout):
        return transpose(dout, invert_permutation(perm)), fill(ms.int32, (len(perm), ), 0)

    return bprop
  • The InvertPermutation operator is used in the reverse operator of transfer. Like the development of transfer operator, this operator needs complete processes such as operator primitive, registration and implementation.

In test_ transpose. Add the following content to the PY file to define the reverse use case.

import mindspore.ops as ops
class Grad(nn.Cell):
    def __init__(self, network):
        super(Grad, self).__init__()
        self.grad = ops.GradOperation(sens_param=True) = network

    def construct(self, input_data, sens):
        gout = self.grad(, sens)
        return gout

def test_grad_net():
    x = np.arange(2 * 3).reshape(2, 3).astype(np.float32)
    sens = np.arange(2 * 3).reshape(3, 2).astype(np.float32)
    grad = Grad(Net())
    dx = grad(Tensor(x), Tensor(sens))
    print("dx: ", dx.asnumpy())

Execution case:

pytest -s

Execution results:

dx:  [[0. 2. 4.]
     [1. 3. 5.]]

open source

qmckw/CPUcustom_opByPytest (

reference material

pytest documentation
Easy to use Pytest unit test framework (51 test world 49 (2) - 44)
Pytest study notes
pytest unit test framework


Topics: Python unit testing pytest MindSpore