Skip to content

Model Attributes

operators:
  # Matches all operators
  - type: "*"
    attributes:
      code_placement: ITCM
      scratch_placement: DTCM
  # Matches all operators of type CONV_2D
  - type: "CONV_2D"
    attributes:
      code_placement: MRAM
      scratch_placement: SRAM
  # Match operators with IDs: 1, 2
  - id:
      - "1"
      - "2"
    attributes:
      code_placement: MRAM
      scratch_placement: MRAM
Attribute Precedence

For each operator in the model, the convert routine will select all attributes that match the operator type and ID. The attributes with higher specificity (i.e., more specific operator IDs) will take precedence over more general ones. The attributes will be consolidated into a single set of attributes for each operator. To verify the attributes applied to each operator, you can set the verbose level to 2 during conversion. This will provide detailed information about the attributes assigned to each operator in the generated module.

RSQRT LUT mode via operator attributes

INT16 RSQRT uses the standard operators attribute ruleset instead of a dedicated top-level flag.

Supported values are:

  • per_op: default; each RSQRT operator gets its own LUT
  • universal: matching RSQRT operators share one universal LUT

Example:

model:
  path: ./model.tflite
module:
  path: ./out
  name: helia_aot_nn
operators:
  - type: RSQRT
    attributes:
      lut_mode: universal

You can also override specific operator IDs after setting a broader default:

operators:
  - type: RSQRT
    attributes:
      lut_mode: universal
  - type: RSQRT
    id: ["23", "56"]
    attributes:
      lut_mode: per_op

Example: Customizing Memory Placement

Let's say we have a model (simple.tflite) with the following architecture:

%%{ init: {
    "flowchart": {
      "nodeSpacing": 20,
      "rankSpacing": 30
    }
  }
}%%
flowchart TB;
    A[Input] ==> B["CONV_2D (ID:1)"]
    B ==> C["CONV_2D (ID:2)"]
    C ==> D["MAX_POOL_2D (ID:3)"]
    D ==> E["FULLY_CONNECTED (ID:4)"]
    E ==> F["SOFTMAX (ID:5)"]

Let's define an initial YAML configuration file (simple.yaml) for the conversion:

model:
  path: simple.tflite
module:
  prefix: simple
  type: zephyr
  path: simple.zip
memory:
  planner: greedy
verbose: 1

By default, all operator wrappers are typically placed in ITCM. However, ITCM is extremely limited in size. In addition, CONV_2D, MAX_POOL_2D, and SOFTMAX are least sensitive to code placement. Only FULLY_CONNECTED is sensitive. Therefore, we can set the default behavior for these operators. We will by default map all operator wrappers to MRAM with scratch in SRAM, and then route FULLY_CONNECTED to ITCM/DTCM. The new YAML configuration file (simple.yaml) will look like this:

model:
  path: simple.tflite
module:
  prefix: simple
  type: zephyr
  path: simple.zip
memory:
  planner: greedy
verbose: 1
operators:
  # Default placement for all operators
  - type: "*"
    attributes:
      code_placement: MRAM
      scratch_placement: SRAM
  # All Fully Connected operators get the fast TCM path
  - type: FULLY_CONNECTED
    attributes:
      code_placement: ITCM
      scratch_placement: DTCM

After experimenting, let's say we find the CONV_2D operator w/ ID:1 is too slow when placed in MRAM. We can override the placement for this operator. The final YAML configuration file (simple.yaml) will be the following:

model:
  path: simple.tflite
module:
  prefix: simple
  type: zephyr
  path: simple.zip
memory:
  planner: greedy
verbose: 1
operators:
  # Default placement for all operators
  - type: "*"
    attributes:
      code_placement: MRAM
      scratch_placement: SRAM
  # Place CONV_2D operator w/ ID:1 wrapper in ITCM
  - id:
      - "1"
    attributes:
      code_placement: ITCM
      scratch_placement: SRAM
  # All Fully Connected operators get the fast TCM path
  - type: FULLY_CONNECTED
    attributes:
      code_placement: ITCM
      scratch_placement: DTCM