Skip to content
This repository was archived by the owner on Apr 24, 2025. It is now read-only.

Commit af7a21d

Browse files
SarahByrneIntelSarahByrneIntel
andauthored
Add doc for implementing new operations (#79)
* Add doc for implementing new operations * Adding md file name * Update to new operation doc --------- Co-authored-by: SarahByrneIntel <[email protected]>
1 parent e1d9155 commit af7a21d

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

docs/source/adding_operations.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Adding New Operations in the Library
2+
3+
This document outlines the process for integrating a new operation into the existing code library. The integration process involves several key steps: defining the operation's interface, implementing the operation ensuring compatibility with the library's architecture, and providing testing to validate the operation.
4+
5+
An example of implementing new operations can be found here: [Implementing reduce operations](https://github.com/intel/intel-npu-acceleration-library/commit/4f17015a75c146fe8d569ac71a2e2a0a960fc652)
6+
7+
## Step 1: Defining the OpenVINO interface
8+
9+
The first step is defining the call to the OpenVino method of the new operation through the OpenVINO Runtime C++ API. This is done in the `nn_factory.h` header. In this file, a new operation is created by interfacing with the OpenVINO operation. This includes specifying input and output parameters, and data types of the operation's interface and then calling and returning the OpenVINO method. The interface should align with the library's existing design patterns and naming conventions.
10+
11+
A simple example of defining a new operation:
12+
```
13+
ov::op::Op* new_operation(ov::op::Op* input) {
14+
auto new_operation = std::make_shared<ov::opset1::NewOp>(input->output(0));
15+
operations.push_back(new_operation);
16+
return new_operation.get();
17+
}
18+
```
19+
## Step 2: Defining the C++ bindings
20+
21+
The next step is defining the C++ binding in the `binding.cpp` source file. This is the method that will be called in Python. This method has the operation's input node as a parameter and additional arguments of the operation are defined in the method.
22+
23+
An example of defining the binding:
24+
```
25+
intel_npu_acceleration_library_DLL_API ov::op::Op* new_operation(intel_npu_acceleration_library::ModelFactory* factory, ov::op::Op* input) {
26+
return factory->new_operation(input);
27+
}
28+
```
29+
30+
## Step 3: Adding new operation to list of supported operation
31+
32+
The new operation is added to the list of supported NPU operations in the `ops.py` script.
33+
The information of the new operation that must be provided is:
34+
- the operation name
35+
- the number of inputs
36+
- the optional parameters types
37+
38+
## Step 4: Adding extra functionality to the operation's function
39+
Ctypes is used to interface between C++ and Python. (Documentation is found here: [Python Ctypes](https://docs.python.org/3/library/ctypes.html))
40+
41+
If there is additional logic that you may want to add to the function, this can be done by defining a Python function that calls the C++ method in the `factory.py` file.
42+
Otherwise, if you directly call the functions to C++, then you do not need to define a Python function.
43+
44+
## Step 5: Adding PyTorch wrapper for the new operation
45+
Additionally, to define a wrapper to use PyTorch native functions, this can be implemented in the `functional.py` file. In this step, a function of the same name as the PyTorch equivalent is created, which is used instead of the PyTorch implementation of the operation.
46+
If there is additional logic that you may want to add to the function to interface with the new operation, it can also be added in this function.
47+
48+
It is common for the new operation to have the same name as the PyTorch equivalent operation, however this is not always the case and to show which operation we are referring to, we refer to the newly implemented operation as `new_operation` and the PyTorch operation and `operation`.
49+
50+
The basic structure of PyTorch wrapper for a PyTorch operation, referred to as `torch.operation`, which returns the output of the implemented `new_operation`:
51+
```
52+
@implements(torch.operation)
53+
def operation(x: Tensor) -> Tensor:
54+
"""Return the output tensor of the operation.
55+
56+
Args:
57+
x (Tensor): The input tensor.
58+
Returns:
59+
Tensor: Output tensor.
60+
"""
61+
return generate_op(x, "new_operation")
62+
```
63+
## Step 6: Building the library
64+
To update the library, run the command:
65+
```
66+
pip install .
67+
```
68+
69+
## Step 7: Adding tests for the new operation
70+
A test for the new operation can be added in the `test_op.py` script. The new operation should be compared with a reference to ensure correct implementation.
71+
72+
The following is a basic structure to use the new operation:
73+
```
74+
X = torch.rand((16, 128)).to(torch.float16) # defining the input tensor
75+
76+
model = NNFactory()
77+
input = model.parameter(X.shape) # creating the input node
78+
_ = model.new_operation(input) # _ = torch.operation(input) is equivalent if using the PyTorch wrapper
79+
model.compile()
80+
out = model.run(X.numpy())
81+
```
82+
83+
Using pytest to run all of the tests in the file:
84+
```
85+
pytest <name of the file>
86+
```
87+
88+
Using pytest to run a single test in the file:
89+
```
90+
pytest <name of the file>::<name of the test>
91+
```

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Site map
9191

9292
.. toctree::
9393
developer.md
94+
adding_operation.md
9495
:maxdepth: 1
9596
:caption: Developements guide:
9697

0 commit comments

Comments
 (0)