createOrtSession function

OrtSessionObjects createOrtSession(
  1. String modelPath, {
  2. Map<String, String> sessionConfigEntries = const {},
})

You MUST call calloc.free on the returned pointer when you are done with it.

It is reasonable to never free it in an app where you would like the model to be loaded for the lifetime of the app. Creates an ORT session from modelPath.

sessionConfigEntries is a narrow escape hatch for documented ORT runtime workarounds. Entries are copied into the session options before creation.

Implementation

OrtSessionObjects createOrtSession(
  String modelPath, {
  Map<String, String> sessionConfigEntries = const {},
}) {
  // Normal builds resolve the hook's code asset through @Native. The explicit
  // lookup exists only for diagnostic hosts that intentionally override ORT.
  final answer = switch (fonnxOrtDylibPathOverride) {
    final path? =>
      DynamicLibrary.open(path).lookupFunction<
        Pointer<OrtApiBase> Function(),
        Pointer<OrtApiBase> Function()
      >('OrtGetApiBase')(),
    null => OrtGetApiBase(),
  };
  final baseApi = answer.ref;
  final api = baseApi.GetApi.asFunction<Pointer<OrtApi> Function(int)>();
  final ortApiPointer = api(ORT_API_VERSION);
  if (ortApiPointer == nullptr) {
    throw StateError('ONNX Runtime does not support API $ORT_API_VERSION');
  }
  final ortApi = ortApiPointer.ref;
  final envPtr = calloc<Pointer<OrtEnv>>();
  final sessionOptionsPtr = calloc<Pointer<OrtSessionOptions>>();
  final sessionPtr = calloc<Pointer<OrtSession>>();
  var optionsPointerFreed = false;
  var ownershipTransferred = false;

  try {
    final status = ortApi.createEnv(envPtr);
    if (status.isError) {
      final error =
          'Code: ${ortApi.getErrorCodeMessage(status)}\n'
          'Message: ${ortApi.consumeErrorMessage(status)}';
      throw Exception(error);
    }

    ortApi.createSessionOptions(sessionOptionsPtr);
    for (final entry in sessionConfigEntries.entries) {
      ortApi.addSessionConfigEntry(
        sessionOptionsPtr.value,
        key: entry.key,
        value: entry.value,
      );
    }
    // Avoiding explicit inter/intra-op thread counts currently performs best.
    // CoreML was also measured around 10x slower than CPU on an M2 Max for the
    // package's embedding graphs, so provider selection remains ORT's default.
    final sessionStatus = ortApi.createSession(
      env: envPtr.value,
      modelPath: modelPath,
      sessionOptions: sessionOptionsPtr.value,
      session: sessionPtr,
    );
    if (sessionStatus.isError) {
      final error =
          'Code: ${ortApi.getErrorCodeMessage(sessionStatus)}\n'
          'Message: ${ortApi.consumeErrorMessage(sessionStatus)}';
      throw Exception(error);
    }

    ortApi.releaseSessionOptions(sessionOptionsPtr.value);
    sessionOptionsPtr.value = nullptr;
    calloc.free(sessionOptionsPtr);
    optionsPointerFreed = true;

    final finalizerContext = calloc<_OrtSessionFinalizerContext>();
    finalizerContext.ref
      ..session = sessionPtr
      ..env = envPtr
      ..releaseSession = ortApi.ReleaseSession
      ..releaseEnv = ortApi.ReleaseEnv;
    ownershipTransferred = true;
    return OrtSessionObjects._(
      sessionPtr: sessionPtr,
      envPtr: envPtr,
      apiBase: baseApi,
      api: ortApi,
      finalizerContext: finalizerContext,
    );
  } catch (_) {
    if (sessionPtr.value != nullptr) {
      ortApi.releaseSession(sessionPtr.value);
      sessionPtr.value = nullptr;
    }
    if (!optionsPointerFreed && sessionOptionsPtr.value != nullptr) {
      ortApi.releaseSessionOptions(sessionOptionsPtr.value);
      sessionOptionsPtr.value = nullptr;
    }
    if (envPtr.value != nullptr) {
      ortApi.releaseEnv(envPtr.value);
      envPtr.value = nullptr;
    }
    rethrow;
  } finally {
    if (!ownershipTransferred) {
      calloc.free(sessionPtr);
      if (!optionsPointerFreed) calloc.free(sessionOptionsPtr);
      calloc.free(envPtr);
    }
  }
}