[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 |
Link to microsoft/onnxruntime#29615 — Danny's open PR adding official Go bindings for ONNX Runtime C API via CGO.
Tianlei Wu (tianleiwu)
left a comment
There was a problem hiding this comment.
The C-boundary ownership, cancellation, and teardown hardening are well done, and the local regression suite is broad. Before merging an official binding, please add a required CI path that actually loads ONNX Runtime; currently the suite can execute zero tests and still report success. I also left two non-blocking suggestions on configuration validation and inference-path allocations.
Run race tests against the shared library produced by the Linux x64 Release build. Reject negative thread counts and benchmark Session.Run allocations while avoiding eager error formatting.
Apply repository formatters and use consistent ONNX imports and lowercase locals without changing generated model contents.
|
Tianlei Wu (@tianleiwu) Thank you for the thoughtful review. I’ve addressed the three comments and re-requested your review. The new workflow runs are currently waiting for maintainer approval. When you have a chance, could you please approve the runs and take another look? Thanks! |
Danny On The Air (dannyota)
left a comment
There was a problem hiding this comment.
Tianlei Wu (@tianleiwu) All workflow runs have been approved and all 86 checks are green. Ready for another look when you have a chance.
|
Danny On The Air (@dannyota), can we move *_test.go to a separated folder? |
Tianlei Wu (tianleiwu)
left a comment
There was a problem hiding this comment.
Re-reviewed at f2f62ab. The previous requested changes are addressed: Linux x64 Release now runs the race-enabled Go suite against the shared library built from the same checkout, negative thread counts are rejected with boundary coverage, and the inference allocation cost is recorded after deferring error formatting to failure paths.
I also traced the latest sequence-buffer pinning through the native CreateValue and GetValue implementations; it matches their shallow-copy and deep-copy ownership behavior. No additional blocking findings remain.
Local exact-head vet, formatting, and diff checks passed. The required Go runtime CI job is still running; the current macOS failure is an Abseil download error, and the arm64 job terminated mid-build without a source diagnostic.
Picks up the ContribOperators.md regeneration (microsoft#31985), which fixes the Windows GPU Kernel Documentation Validation failure. That check was failing on main independently of this branch. The Go binding CI wiring in linux_ci.yml and reusable_linux_build.yml auto-merged cleanly and is unchanged.
|
Happy to move them if you'd prefer — though my instinct is to keep them in place. Go's convention is One catch either way: a few tests reach into unexported internals ( If the aim is exercising the public API the way a user would, there's a cheaper middle ground — the same files can declare Either works for me, just let me know which you'd like. Separately, I've merged current |
Description
Adds official Go bindings for the ONNX Runtime C API using CGO, following
the structure of the existing language bindings.
Closes #9786.
Motivation
Go is widely used for backend services that perform ML inference, including
embeddings, classification, and NLP workloads. This PR provides a supported
Go interface without requiring third-party runtime dependencies.
Highlights
inputs and outputs, including dynamic dimensions
tensors, scalar tensors, and zero-length dimensions
memory arena, profiling, and free-dimension overrides
configuration API for other EPs
workflows
through
context.ContextSession.Runcalls supported by the wrapperDesign
The bindings access the ONNX Runtime C API vtable through C shim functions
because CGO cannot directly call C function pointers.
The ONNX Runtime shared library is loaded at runtime with
dlopenonLinux/macOS and
LoadDLLon Windows. A singleOrtEnvis shared withinthe process.
Numeric input tensors use
runtime.Pinnerto avoid copying their backingstorage. Output tensors expose ORT-allocated memory that remains valid until
the value is closed.
Filesystem paths use
ORTCHAR_T: UTF-16 on Windows and UTF-8 elsewhere.Paths containing NUL bytes are rejected instead of being silently truncated.
github.com/microsoft/onnxruntime/goonnxruntimeCorrectness and platform hardening
The implementation includes safeguards for several lifecycle and C-boundary
conditions:
preventing stale cancellation from affecting later inference calls.
RunOptions.ORTCHAR_T.tests for
-1,0, and1.Linux CI coverage
The existing Linux x64 Release job now tests the Go bindings against the
libonnxruntime.sobuilt from the same checkout.The job:
ORT_LIB_PATHgo test -race -count=1 ./onnxruntime-count=1prevents a cached Go test result from hiding a problem with thenewly built library.
Tests
Local validation covers:
The complete binding suite passes under the race detector against:
Additional validation completed:
GOEXPERIMENT=cgocheck2go vetgolangci-lintlintrunneractionlintBenchmarkSessionRunwas added to track successful inference allocations.Avoiding eager validation-message formatting reduced the local benchmark
from 504 B and 17 allocations to 440 B and 13 allocations per run.