The operator prototype describes the input, output, and attributes of the operator as well as the implementation information of the operator on the AI Processor, and associates the operator with the function such as tiling implementation. The operator prototype is carried by a custom operator class that is inherited from the OpDef class. After defining the operator prototype, call the OP_ADD API to transfer the operator type (class name of the custom operator class) and register the operator prototype. The following is an example of defining and registering the Add operator prototype.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
namespace ops { class AddCustom : public OpDef { public: AddCustom(const char* name) : OpDef(name) { this->Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this->Input("y") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("z") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); // The following shape/datatype inference functions are used only when operators are integrated into a graph. this->SetInferShape(ge::InferShape); this->SetInferDataType(ge::InferDataType); this->AICore() .SetTiling(optiling::TilingFunc); // Replace it with the actual Ascend AI Processor model. this->AICore().AddConfig("ascendxxx"); } }; OP_ADD(AddCustom); } // namespace ops |
The operator prototype definition describes the input, output, and attributes of the operator. The number of data types and formats supported by the input and output must be the same.
The following code snippet shows the description of input x of the Add operator.
1 2 3 4 |
this->Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); |
|
Prototype Definition |
Parameter |
Description |
|---|---|---|
|
Input/Output |
ParamType |
Parameter type. The options are OPTIONAL, REQUIRED, and DYNAMIC.
|
|
DataType |
Data type supported by the operator input and output. For details about the value of datatype, see DataType. |
|
|
Format |
Format supported by the operator input and output. For details about the value of format, see Format. |
According to the preceding prototype definition, all combinations of data types and formats are listed and correspond to each other. The following APIs can be used to simplify the code logic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// List all corresponding combinations. class XxxCustom : public OpDef { public: XxxCustom(const char* name) : OpDef(name) { this->Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT16, ge::DT_FLOAT16}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this->Input("y") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("z") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); ... } }; // Use DataTypeList and FormatList to express the data type and format. class XxxCustom : public OpDef { public: XxxCustom(const char* name) : OpDef(name) { this->Input("x") .ParamType(REQUIRED) .DataTypeList({ge::DT_FLOAT16}) .FormatList({ge::FORMAT_ND}); this->Input("y") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("z") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32}) .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); ... } }; |
1 2 3 4 5 6 7 8 9 10 11 12 |
this->Input("x1") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); this->Input("x2") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("y1") .ParamType(REQUIRED) .Follow("x1") .OutputShapeDependOnCompute(); |
The prototype definition also contains operator attributes. The following code snippet shows the description of the reduceDim and isKeepDim attributes of the ReduceMax operator.
1 2 3 4 5 6 |
this->Attr("reduceDim") .AttrType(REQUIRED) .Int(); this->Attr("isKeepDim") .AttrType(OPTIONAL) .Int(1); |
The following table describes the parameters.
|
Prototype Definition |
Registration Method |
Description |
|---|---|---|
|
Attr |
AttrType |
Sets the operator attribute type. The value can be OPTIONAL or REQUIRED. |
|
Bool/Float/Int... |
Sets the operator attribute data type to Bool, Float, or Int. For details, see OpAttrDef. |
Register the AI Processor models supported by the operator and related configuration by calling AddConfig. The prototype of the AddConfig API is as follows: The soc parameter indicates the AI Processor model, and the aicore_config parameter indicates other configuration.
1 2 |
void AddConfig(const char *soc); void AddConfig(const char *soc, OpAICoreConfig &aicore_config); |
The following is an example of registering the AI processor model through this API. For details about how to set ascendxxx, see the ASCEND_COMPUTE_UNIT field in the build configuration file CMakePresets.json in the operator project directory. The value of this field is automatically generated when msOpGen is used to create a project.
1
|
this->AICore().AddConfig("ascendxxx"); |
For details about how to configure other AI Core configuration, see OpAICoreConfig.
Call SetInferShape, SetInferDataType, and SetTiling to register functions such as tiling implementation and shape inference. The following is an example. The registered functions such as tiling implementation are called by the framework, and the corresponding context is passed during the calling for you to use. For details about how to implement the tiling function, see Tiling Implementation on the Host. For details about how to implement the functions such as shape inference related to operator integration into a graph, see Integrating Operators into a GE Graph.
1 2 3 4 5 |
// The following shape/datatype inference functions are used only when operators are integrated into a graph. this->SetInferShape(ge::InferShape); this->SetInferDataType(ge::InferDataType); this->AICore() .SetTiling(optiling::TilingFunc); |
The operator class inherits the base class OpDef and uses Input, Output, and Attr to register the operator prototype information. If the hardware platform supports the same operator prototype, use AICore().AddConfig to add the supported AI processor model. Different hardware forms have different operator prototype definitions. You can add OpAICoreConfig to register differentiated operator prototypes for different AI processor models.
Rules for differentiated operator prototypes to take effect:
In the following example, ascendxxx1 and ascendxxx2 (AI processor models) use the same operator prototype. The operator class inherits the base class OpDef and uses Input, Output, and Attr to register the operator prototype information, and call AICore().AddConfig to add supported AI processor models. The operator prototype supported by ascendxxx3 needs to be customized. The DT_BF16 type is added and registered by adding OpAICoreConfig. The definitions of x, y, and z overwrite the prototype information defined in the operator class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
namespace ops { class MyAdd : public OpDef { public: MyAdd(const char* name) : OpDef(name) { // ascendxxx1 ascendxxx2 AI processor model prototype definition this->Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16}) .Format({ge::FORMAT_ND}); this->Input("y") .ParamType(OPTIONAL) .DataType({ge::DT_INT64}) .Format({ge::FORMAT_ND}); this->Output("z") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16}) .Format({ge::FORMAT_ND}); this->AICore() .SetTiling(optiling::TilingFunc); this->AICore().AddConfig("ascendxxx1"); this->AICore().AddConfig("ascendxxx2"); // The OpAICoreConfig variable is defined for the ascendxxx3 AI processor to customize the prototype. OpAICoreConfig config; config.Input("x") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_BF16}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); config.Input("y") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_BF16}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); config.Output("z") .ParamType(REQUIRED) .DataType({ge::DT_FLOAT16, ge::DT_BF16}) .Format({ge::FORMAT_ND, ge::FORMAT_ND}); this->AICore().AddConfig("ascendxxx3", config); } }; OP_ADD(MyAdd); } |
In the following example, only several parameter prototype information is inconsistent on different hardware platforms. You can use OpAICoreConfig to customize some operator prototype information and reuse other operator prototype information defined by OpDef to customize the hardware platform of some prototype information.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class AddCustom : public OpDef { public: AddCustom(const char* name) : OpDef(name) { this->Input("x").DataType({ ge::DT_FLOAT16 }).ParamType(OPTIONAL); this->Output("y").DataType({ ge::DT_FLOAT16 }); OpAICoreConfig aicConfig1; OpAICoreConfig aicConfig2; aicConfig1.Input("x") .ParamType(OPTIONAL) .DataType({ ge::DT_FLOAT }) .Format({ ge::FORMAT_ND }); aicConfig2.Input("x") .ParamType(REQUIRED) .DataType({ ge::DT_INT32 }) .Format({ ge::FORMAT_ND }); this->AICore().AddConfig("ascendxxx1", aicConfig1); this->AICore().AddConfig("ascendxxx2", aicConfig2); } }; |