[Go] Add Go bindings for ONNX Runtime C API#29615
Conversation
Idiomatic Go wrapper for the ORT C API via CGO, matching the pattern of existing language bindings (rust/, java/, csharp/). Covers session management, typed/untyped tensor I/O, string tensors, IO binding, model metadata, run options, session options with CUDA and TensorRT execution providers, context cancellation, and concurrent inference. Tested with real Qwen3-Embedding-0.6B ONNX INT8 model.
|
@microsoft-github-policy-service agree |
Fix TOCTOU race in NewIOBinding (use-after-free on concurrent Close), empty outputNames panic in Run/RunWithOptions, and metadata use-after- close segfault. Add 15 tests covering all identified coverage gaps. Remove dead code. Bump minimum ORT version to 1.27.0 for getter APIs.
|
Updated with a second commit:
|
Try API version 27 first, fall back to 17 for ORT 1.17-1.26. GetExecutionMode and IsMemPatternEnabled return a clear error on older libraries instead of crashing. Minimum ORT is now 1.17.
|
Added API version fallback: bindings now try API version 27 first, fall back to 17 for older ORT libraries. This means ORT 1.17+ is supported (previously required 1.27+). The two getter functions added in 1.27 (GetExecutionMode, IsMemPatternEnabled) return a clear error on older libraries instead of crashing. |
Add GetVersion/APIVersion, cache CString names in Session for zero- alloc Run hot path, enable/disable telemetry, SetOptimizedModelFilePath, RegisterCustomOpsLibrary, Has/GetSessionConfigEntry, NewSequence, NewMap, NewMapFromGoMap. Fix API version probing to try 27 down to 17 for broader ORT compatibility. Fix IsSequence/IsMap enum values.
There was a problem hiding this comment.
Pull request overview
This PR introduces an official Go (cgo) binding layer for the ONNX Runtime C API, including runtime library loading, core session/tensor APIs, and a fairly comprehensive Go test suite with small ONNX model fixtures.
Changes:
- Added Go package
go/onnxruntimewith wrappers for environment lifecycle, sessions, tensors (typed + raw-bytes + string), session/run options, IO binding, and model metadata. - Added a C shim layer (
cshim.h/cshim.c) to call through theOrtApivtable (since cgo can’t call function pointers directly). - Added test models and Go unit/integration tests covering common workflows (including dynamic shapes, zero-length dims, concurrency, and optional Qwen3 integration).
Reviewed changes
Copilot reviewed 29 out of 34 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| go/testdata/gen_models.py | Generates minimal ONNX models used by the Go tests. |
| go/onnxruntime/types.go | Defines Go enums/types for tensor dtypes, graph optimization level, execution mode, and generic tensor element constraints. |
| go/onnxruntime/tensor.go | Implements typed tensors, raw-byte tensors, sequence/map wrappers, and tensor shape/type helpers. |
| go/onnxruntime/tensor_test.go | Unit tests for tensor creation, accessors, and sequence/map helpers. |
| go/onnxruntime/string_tensor.go | Implements string tensor creation and string extraction. |
| go/onnxruntime/string_tensor_test.go | Unit tests for string tensor creation and reading. |
| go/onnxruntime/session.go | Implements session creation, IO introspection, Run/RunWithOptions, profiling end, and lifecycle management. |
| go/onnxruntime/session_test.go | Session tests covering model IO, inference, dynamic/zero-len dims, concurrency, cancellation, and options. |
| go/onnxruntime/run_options.go | Run options wrapper (verbosity, severity, tag, terminate, config entries). |
| go/onnxruntime/run_options_test.go | Tests for run options and RunWithOptions behavior. |
| go/onnxruntime/qwen3_test.go | Optional integration test for a locally available Qwen3 embedding model. |
| go/onnxruntime/ort.go | Global initialization/shutdown, telemetry toggles, provider enumeration, and library path resolution. |
| go/onnxruntime/ort_test.go | Package TestMain + tests for init/idempotency, providers, telemetry, shutdown constraints, and OrtError typing. |
| go/onnxruntime/options.go | SessionOptions wrapper including EP configuration, profiling, free-dim overrides, getters (API-gated), and misc config. |
| go/onnxruntime/options_test.go | Tests for SessionOptions cloning, memory settings, execution mode, getters, profiling, EP errors, and config entries. |
| go/onnxruntime/onnxruntime_error_code.h | Error-code header copy for the shim/include surface. |
| go/onnxruntime/metadata.go | Model metadata wrapper (producer, graph, domain, description, version, custom KV). |
| go/onnxruntime/metadata_test.go | Tests for metadata access, double-close, and use-after-close behavior. |
| go/onnxruntime/load_windows.go | Windows dynamic loading via LoadDLL + FindProc. |
| go/onnxruntime/load_unix.go | Unix dynamic loading via dlopen/dlsym. |
| go/onnxruntime/iobinding.go | IO binding API (bind inputs/outputs, run, fetch bound outputs/names). |
| go/onnxruntime/iobinding_test.go | IO binding tests (basic run, output binding modes, output names/values, memory info, closed session). |
| go/onnxruntime/errors.go | Go error mapping for OrtStatus + wrapping helpers. |
| go/onnxruntime/doc.go | Package-level documentation for initialization and concurrency expectations. |
| go/onnxruntime/cshim.h | Declares the C shim surface used by cgo. |
| go/onnxruntime/cshim.c | Implements shim functions that forward to OrtApi function pointers. |
| go/go.mod | Declares Go module path and minimum Go version. |
| go/.golangci.yml | Lint configuration for the Go code. |
| go/.gitignore | Ignores local .ort-lib/ test library directory. |
Address the PR review findings plus a follow-up audit of the same defect classes elsewhere in the package. Crashes and memory safety: - runInner released the OrtRunOptions before joining the cancellation watcher. Defers run LIFO, so the freed block could be handed to a concurrent Run whose live options the stale watcher then terminated, aborting an unrelated inference. Release now happens after the join. - IOBinding methods checked only the binding handle, never whether the Session had been closed, passing a NULL OrtSession into ORT. Binding calls now hold the session read lock across the C call. - Run, Bind*, NewSequence, NewMap and AddInitializer accepted nil or closed tensors and passed a NULL OrtValue into ORT. Validation is centralized in Tensor.checkUsable. - NewIOBinding(nil) panicked instead of returning an error. - StringData passed a pointer to a Go slice header into C when a tensor held only empty strings, violating the cgo pointer-passing rules. - Guard element-count and byte-size overflow before slicing ORT memory. Bytes now errors on string tensors instead of returning an empty slice. Context cancellation: - RunWithOptions ignored cancellation whenever the caller supplied RunOptions, so deadlines were a silent no-op. The terminate watcher now runs for any cancellable context; caller-owned options are restored afterwards because the terminate flag is sticky. Resource leaks: - loadLibrary discarded the module handle, so every Init failure after a successful load leaked the mapping. Init now closes it on all failure paths, and IO binding allocations are freed only when non-nil. Windows: - The C shim declared path parameters as char*, but ORT uses ORTCHAR_T, which is wchar_t on Windows. Model paths were therefore passed as narrow strings and opened as mojibake. Shim signatures now use ORTCHAR_T and paths are converted per platform. Paths containing NUL are rejected rather than silently truncated. Concurrency: - initialized and shutdown are now atomic; checkInit no longer races with Init. Verified with go vet, golangci-lint and Semgrep, the race detector, cgocheck2, and a mingw Windows cross-build. 102 tests pass, 3 skip.
|
All 13 review comments are addressed in A follow-up audit of the same defect classes turned up several more serious issues, fixed in the same commit:
Windows path handling — please review, this changes the shim's C signaturesThe shim declared path parameters as Verified with a mingw cross-build under A note on three of the commentsThe two "don't free a nullptr through the allocator" findings and the Verification102 tests pass, 3 skip (two need ORT ≥ 1.27; one is a subprocess race child). Clean under |
Description
Official Go bindings for the ONNX Runtime C API via CGO, following
the pattern of existing language bindings (
rust/,java/,csharp/).Closes #9786
Motivation
Go is widely used for backend services that need ML inference
(embeddings, classification, NLP). There is strong community demand
(#9786, 19 comments) and no official Go support. This PR fills that gap.
What's included
inputs/outputs with dynamic dimension support
CreateTensor[T](Go 1.18+ generics),untyped
NewTensorFromBytes, string tensors, zero-length dimensionsmemory arena, profiling, free dimension overrides
key-value API for all other EPs
context.Contextsupport viaRunOptionsSetTerminate,honored by both
RunandRunWithOptionsSession.Runsafe for concurrent use (ORT guarantee)Design
Wraps the C API vtable (
OrtApi) through ~80 C shim functions incshim.h/cshim.c— CGO cannot call C function pointers directly.Library loaded at runtime via
dlopen(Linux/macOS) orLoadDLL(Windows). Singleton
OrtEnvper process viasync.Mutex.Zero-copy tensor inputs via
runtime.Pinner. Output tensors wrapORT-allocated memory with views valid until
Close().Filesystem paths are passed as
ORTCHAR_T, so they are UTF-16 onWindows and UTF-8 elsewhere; conversion is per-platform on the Go side
(
path_windows.go/path_unix.go). Paths containing NUL are rejectedrather than silently truncated.
Module path:
github.com/microsoft/onnxruntime/goPackage:
onnxruntime(import asort)Go 1.26 minimum. stdlib + CGO only — no third-party dependencies.
Correctness and platform hardening
The automated review and a follow-up audit of the same defect classes
produced the fixes in
10fe857. The substantive ones:before the terminate watcher was joined, so a stale watcher could
terminate a concurrent, uncancelled inference.
own
RunOptions.Sessionpassed a NULLOrtSessioninto ORT. Binding calls now hold the session read lock across the C call.
Run,Bind*,NewSequence,NewMapandAddInitializer, passing NULLOrtValueinto ORT.path parameters as
char*where ORT expectsORTCHAR_T(wchar_tonWindows), so paths were opened as mojibake. The four path-taking shim
functions now take
ORTCHAR_T. Note this changes those shim signatures.the library handle is no longer leaked on
Initfailure paths;initialized/shutdownare atomic.Tests
102 tests passing, 3 skipped (two require ORT ≥ 1.27; one is a subprocess
race child):
Verified clean under the race detector,
GOEXPERIMENT=cgocheck2, and amingw Windows cross-build with
-Werror=incompatible-pointer-types.Each regression test carries a negative control confirming it fails when
its fix is reverted.