Standalone C++ example: export an ONNX model to NNEF#

This page documents examples/export_nnef (view on GitHub), a self-contained CMake project that demonstrates how to export an ONNX ModelProto to the Khronos NNEF v1.0 representation from C++.

The NNEF exporter is not part of the onnx-light package itself: the example ships its own implementation under examples/export_nnef/nnef/ (tensor_io.{h,cc} for the NNEF 128-byte-header binary tensor reader / writer, and exporter.{h,cc} for the ONNX → NNEF graph translator with builtin op converters for Conv, BatchNormalization, MaxPool/AveragePool, the elementwise math/logical/comparison ops, Gemm, MatMul, Reshape, Flatten, Transpose, Concat, Identity and Clip). Only the proto layer of onnx-light (onnx_light::lib_onnx_proto) is linked, to load and walk the ONNX ModelProto.

Step 1 – Install the C++ library#

From the onnx-light repository root, build and install the static library and its public headers. The Python extension is not required:

cmake -S . -B build-install \
      -DCMAKE_BUILD_TYPE=Release \
      -DONNX_LIGHT_BUILD_PYTHON=OFF \
      -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build-install
cmake --install build-install

Step 2 – Build the example#

Point CMAKE_PREFIX_PATH at the install prefix chosen above:

cmake -S examples/export_nnef -B build-export-nnef \
      -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_PREFIX_PATH=/usr/local
cmake --build build-export-nnef

Step 3 – Run the example#

./build-export-nnef/export_nnef path/to/model.onnx path/to/out_nnef

The output directory is created if needed and is populated with a graph.nnef text file plus one <label>.dat file per initializer, encoded with the standard NNEF binary tensor format (128-byte header followed by raw data). An optional third argument overrides the graph_name written into graph.nnef.

CMakeLists.txt#

The example uses find_package and links against the exported onnx_light::lib_onnx_proto target. The NNEF sources are compiled in the same executable:

cmake_minimum_required(VERSION 3.15)
project(export_nnef LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(onnx_light REQUIRED)

add_executable(export_nnef
    main.cc
    nnef/exporter.cc
    nnef/tensor_io.cc
)
target_include_directories(export_nnef PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(export_nnef PRIVATE onnx_light::lib_onnx_proto)

main.cc#

The program loads an ONNX model with FileStream / ParseModelProtoFromStream, prints the graph.nnef text and writes the NNEF directory. nnef::NNEFExportError is thrown when an ONNX construct cannot be expressed in NNEF (for instance an op with no registered converter, or a Reshape with a non-constant shape input).

#include "nnef/exporter.h"
#include "onnx.h"
#include "onnx_helper.h"
#include "stream.h"

#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
  if (argc < 3 || argc > 4) {
    std::cerr << "Usage: " << argv[0] << " <model.onnx> <out_dir> [graph_name]\n";
    return 1;
  }

  const std::string model_path = argv[1];
  const std::string out_dir = argv[2];
  const std::string graph_name = (argc == 4) ? argv[3] : "";

  ONNX_LIGHT_NAMESPACE::ModelProto model;
  ONNX_LIGHT_NAMESPACE::utils::FileStream stream(model_path);
  ONNX_LIGHT_NAMESPACE::ParseOptions parse_opts;
  ONNX_LIGHT_NAMESPACE::ParseModelProtoFromStream(model, stream, parse_opts);

  try {
    std::cout << ONNX_LIGHT_NAMESPACE::nnef::ToNNEFText(model, graph_name);
    const std::string absolute_out_dir =
        ONNX_LIGHT_NAMESPACE::nnef::SaveNNEF(model, out_dir, graph_name, /*overwrite=*/true);
    std::cout << "Wrote NNEF directory: " << absolute_out_dir << "\n";
  } catch (const ONNX_LIGHT_NAMESPACE::nnef::NNEFExportError &e) {
    std::cerr << "NNEF export error: " << e.what() << "\n";
    return 2;
  }
  return 0;
}

Registering a custom op converter#

Operators without a builtin converter raise nnef::NNEFExportError. New converters can be plugged in by calling nnef::RegisterOpConverter before ToNNEFText / SaveNNEF:

#include "nnef/exporter.h"

using namespace ONNX_LIGHT_NAMESPACE::nnef;

RegisterOpConverter("MyOp",
    [](ExportContext &ctx, const ONNX_LIGHT_NAMESPACE::NodeProto &,
       const std::map<std::string, AttributeValue> &,
       const std::vector<std::string> &inputs,
       const std::vector<std::string> &outputs) {
      ctx.AddStatement(outputs[0] + " = my_op(" + inputs[0] + ");");
    });

See also#

  • checker.h – checker API reference (useful to validate a model before exporting it).