Backend
Understand backend engines, PJRT pluggable plugins, and execution environments.
Overview
GoMLX is a Machine Learning focused API, that “lowers” computations into a standard portable internal GO API defined in compute.Backend.
If you are simply using GoMLX, you don’t really need to worry about the backend API, you just need to know that you can select, based
on where you want to execute your models (CUDA, CPU, using pure Go). Simply create a backend at program startup with compute.New() and
reuse it throughout the application lifetime.
Configuring the Execution Environment
Most programs will simply use compute.New() to create a backend in a program, or testutil.BuildTestBackend() for tests.
Both default to the “best” backend available (out of the linked in backends).
You can configure which backend to use and specify options in two ways: programmatically using the
compute.NewWithConfig(config) function or setting the environment variable
GOMLX_BACKEND, which will be used by compute.New().
Example config (or GOMLX_BACKEND) values:
go: Forces the pure Go backend.xla: Uses the XLA backend. It will attempt to use one of TPU, CUDA (Nvidia GPU), or CPU in that order. You can also specify which XLA plugin to use explicitly: E.g.:xla:cpu,xla:cuda.mps: Uses the Apple Silicon GPU backend via Metal Performance Shaders Graph (MPSGraph) on macOS (included automatically bybackends/defaulton Darwin).mlx: Uses the Apple Silicon GPU backend via Apple’s MLX (mlx::core) on macOS (included automatically bybackends/defaulton Darwin; requiresbrew install mlx). You can also specifymlx:dynamicfor native dynamic shapes without recompilation.onnx: Uses the ONNX Runtime backend (included bybackends/defaultwhen built with-tags=onnx, or by importinggithub.com/gomlx/compute-onnx). You can also specify which accelerator to use explicitly: E.g.:onnx:cpu,onnx:cuda,onnx:migraphx.
See below for specific backend configurations.
Supported Backends
1. The XLA Backend ("xla")
This is the default and highest-performance backend. It calls XLA (the compiler powering JAX, TensorFlow, and PyTorch/XLA) to compile graphs to optimized machine code.
- Pros: Incredibly fast, supports GPU (CUDA) and TPU execution, performs operator fusion and memory optimizations automatically.
- Cons: Relies on CGO (which requires C/C++ dependencies) and currently only supports static shapes (compilation is tied to fixed input dimensions).
The xla plugin expects a config string in the format xla:<plugin>[,<option>=<value>].... Where <plugin> can be set to “cpu”, “cuda” or “tpu” or
the path for the PJRT plugin (see below) to use. The following options are supported:
Configuration options:
tf32(boolean, default=true): controls whether to use TF32 for DotGeneral operations that are using float32 (it can be faster in modern GPUs). It’s enabled by default.shared_buffer(boolean, default=true): controls whether to use shared buffers for the device buffer (where device=CPU). It’s enabled by default if the plugin is called “cpu”.preallocate(boolean, default=false): whether the CUDA PJRT preallocates a large portion of the memory.memory_fraction(float, default=0.75): how much memory to preallocate, if preallocate=true. CUDA only.allocator(string, default=“default”): which allocator to use. For CUDA the available ones are “default” (== “bfc”), “bfc” (“best-fit for coalescing”, avoids framementation), “cuda_async” (dynamic, no preallocation), “platform” (slow, good for debugging), “vmm”. CUDA only.visible_devices(list of integers, e.g., “0;1;2”): list IDs of the devices made visible to the backend.use_tfrt_gpu_client(boolean, default=false): uses the “TFRT” dispatcher for GPU.
Example:
GOMLX_BACKEND=xla:cuda,preallocate=true: Use XLA CUDA, and preallocate 75% (the default) for faster memory management for this session.
The PJRT plugins also read the XLA_FLAGS environment variable for additional lower-level configurations. Set XLA_FLAGS=--help and
it will return an error with the messages.
XLA’s Pluggable PJRT Plugin Architecture
XLA uses a “plugin” model, where it defines a standard C API called PJRT (“Pretty much Just another RunTime”) and a language (StableHLO) to express the computation and there are plugins (sometimes closed source) that implement them.
If you are only using GoMLX, you don’t need to know this, but if you are curious, this is how it looks:
graph TD
GoMLX["GoMLX App"] --> ComputeAPI["compute.Backend Interface"]
ComputeAPI --> XLA["xla Backend (CGO)"]
XLA --> PJRTLoader["PJRT Dynamic Linker"]
PJRTLoader -->|Load plugin.so| CPU["CPU PJRT Plugin"]
PJRTLoader -->|Load plugin.so| CUDA["CUDA (Nvidia) PJRT Plugin"]
PJRTLoader -->|Load plugin.so| TPU["TPU (Google Cloud) PJRT Plugin"]PJRT plugins are dynamically loaded libraries (.so on Linux, .dylib on macOS, .dll on Windows). There is typically one plugin per target hardware accelerator.
PJRT Auto-Installation
To simplify the developer experience, GoMLX includes an auto-installer. At startup, the xla package checks if a compatible PJRT plugin is installed. If not, it downloads and caches the required binaries locally in:
- Linux:
~/.local/lib/go-xla/ - macOS:
~/Library/Application Support/go-xla/ - Windows:
~\AppData\Local\go-xla\
Disabling Auto-Installation
For offline deployment or custom production builds (like Docker images), auto-installation can be disabled:
- Via Environment Variable: Set
GOMLX_NO_AUTO_INSTALL=1. - Programmatically: Call
xla.EnableAutoInstall(false)before initializing the backend.
2. The Go Backend ("go")
A pure Go implementation of the compute API. It does not use CGO or C++ libraries.
- Pros: 100% portable. It compiles easily to WebAssembly (WASM) and runs in the browser, making it possible to deploy models on client-side web apps. Also supports dynamic shapes.
- Cons: Slower than GPU/TPU accelerators or XLA for heavy model training.
- Performance Enhancements:
- SIMD & Hardware Matrix Acceleration (AMD64 & ARM64): When built with
GOEXPERIMENT=simd(Go 1.27+), utilizes Go’ssimdandsimd/archsimdpackages alongside handwritten assembly microkernels for:- AMD64:
AVX2andAVX-512(includingAVX-512 VNNI) microkernels forDotGeneral,FusedDense,FusedQuantizedDense,FusedScaledDotProductAttention(SDPA),FusedLayerNorm,FusedSoftmax, reductions, and activations. - ARM64 (
darwin/arm64&linux/arm64):NEON(FEAT_ASIMD) and Scalable Matrix Extension (FEAT_SME, e.g., on Apple M4/M5/M6) assembly microkernels acrossDotGeneral/FusedDense(Float32,Float16,BFloat16,Float64),FusedScaledDotProductAttention, reductions, trailing-axis binary broadcasts,Where, and hardwareFloat16/BFloat16conversions—with asymmetric CPU cluster probing and hybrid SME + NEON co-execution across Super, Performance, and Efficiency cores (reaching up to 2.51 TFlops/s on Apple M6).
- AMD64:
- Hierarchical 3-Level Parallel Scheduler: Prioritizes
Inter-Call>Intra-Op>Inter-Opparallelism using persistent workers, deadlock-free work-helping, dynamic graph execution, and Guided Self-Scheduling (GSS). - Fused Operations & Constant Weight Caching: Implements fused attention, layer normalization, softmax, dense/activation operations, and one-time GEMM constant weight panel pre-packing with automatic raw buffer eviction to minimize memory allocations and bandwidth.
- Quantization: Supports quantized operations (
FusedQuantizedDense,FusedQuantizedDenseVJP,QuantizedEmbeddingLookup) across all 12 GGML/GGUF block formats (Q4_0–Q8_0,IQ4_NL,Q2_K–Q8_K),QuantLinear(Int8,Uint8,Int4,Uint4,Int2,Uint2), andQuantNF4for faster inference and QLoRA backpropagation on smaller memory footprints.
- SIMD & Hardware Matrix Acceleration (AMD64 & ARM64): When built with
Configuration options (via GOMLX_BACKEND="go:<option1>,<option2>=<value>"):
parallelism=<N>: Sets the maximum number of worker goroutines in the intra-op scheduler pool (defaults toruntime.GOMAXPROCS(0)). Settingparallelism=1disables intra-op multi-threading.num_executors=<N>: Sets the maximum number of concurrent graph-node executor goroutines launched during parallel graph execution (defaults to8, capped at the effectiveparallelism).ops_sequential/ops_parallel: Forces independent graph operations to execute sequentially or in parallel where possible (by default, runs dynamically: parallel when a single execution is live, sequential when multiple concurrent calls are active).dependency_order/creation_order: Selects the topological node scheduling strategy for sequential execution:dependency_order(default): Critical-path topological order (schedules nodes with the longest dependency path to an output first, breaking ties by creation order).creation_order: Executes nodes in monotonically increasing creation order.
quantized_cache(orquantized_cache=<true|false>, defaultfalse): Enables constant weight caching forFusedQuantizedDense. By default (false), quantized weights stay solely in their compact native representation (e.g., 4-bit GGUF /QuantLinear/NF4) and are unpacked or dequantized on-the-fly in bounded pooled chunks (<= 2–4 MiB) to preserve minimal RAM usage. When enabled (GOMLX_BACKEND="go:quantized_cache"), constant quantized weights are pre-packed once on first execution (either into K4-interleaved integer GEMM strips whenquantized_matmul=true, or into pre-packed float32 GEMM panels for weights up to64 MiBwhenquantized_matmul=false/QuantNF4) and reused across calls—trading extra memory for higher throughput (especially at small batch sizes).no_ops: Debugging and benchmarking option that allocates output buffers and runs full graph scheduling without executing kernel math, useful for measuring pure framework and scheduler overhead.
(Tip: Passing an unknown option such as GOMLX_BACKEND="go:help" returns an error listing all valid configuration options.)
It also accepts the following environment variables for fine-grained tuning:
GOMLX_GO_SIMD_AVX512: Set to0orfalseto disable AVX-512 SIMD implementations (enabled by default when supported by the CPU).GOMLX_GO_SIMD_AVX2: Set to0orfalseto disable AVX2 SIMD implementations (enabled by default when supported by the CPU).GOMLX_GO_SIMD_NEON: Set to0orfalseto disable ARM64 NEON (FEAT_ASIMD) SIMD implementations (enabled by default when supported by the CPU).GOMLX_GO_SIMD_SVE: Set to0orfalseto disable ARM64 SVE SIMD implementations (enabled by default when supported by the CPU).GOMLX_GO_SIMD_SME: Set to0orfalseto disable ARM64 Scalable Matrix Extension (FEAT_SME) implementations (enabled by default when supported by the CPU).GOMLX_GO_AVX512_ASM,GOMLX_GO_AVX2_ASM,GOMLX_GO_NEON_ASM: Set to0orfalseto disable handwritten assembly GEMM microkernels and fall back to Go SIMD kernels.GOMLX_GO_AVX512_KC,GOMLX_GO_AVX512_MC,GOMLX_GO_AVX512_NC(and correspondingGOMLX_GO_AVX2_*,GOMLX_GO_NEON_*,GOMLX_GO_NOSIMD_*): Override cache blocking parameters (KC,MC,NC) for matrix multiplication.GOMLX_GO_DOT_MATMUL: Set to0orfalseto disable the tiledmatmulimplementation inDotGeneral(enabled by default).GOMLX_GO_FUSION: Set to0orfalseto disable fused operations (enabled by default).GOMLX_OP_PROFILE: Enables per-operator timing summaries in the Go backend. Set to1(ortrue) to print a per-op wall/CPU time breakdown every5timed runs (after1warm-up run), or set to<N>(N > 1) to print everyNruns (combine withGOMLX_BACKEND="go:ops_sequential"for isolated per-op wall and CPU times).
It’s relatively easy to add specialized fused operations, or SIMD versions for specific CPUs. Open an issue in the GoMLX repo, or reach out to us in our Slack channel for questions.
3. Darwin (Mac) Backends: "mps" and "mlx" (go-darwinml)
The github.com/gomlx/go-darwinml repository provides two Apple Silicon GPU backends for macOS (darwin with CGO enabled), both included automatically when importing github.com/gomlx/gomlx/backends/default on Darwin:
"mps"(github.com/gomlx/go-darwinml/compute/mps): Uses Apple’s system Metal and Metal Performance Shaders Graph (MPSGraph) frameworks (no external libraries required)."mlx"(github.com/gomlx/go-darwinml/compute/mlx): Uses Apple’s MLX (mlx::core) C++ framework (requiresbrew install mlx).
3.1 The "mps" Backend (Metal Performance Shaders Graph)
Executes GoMLX computation graphs on Apple Silicon GPUs using macOS’s built-in MPSGraph framework.
- Importing: Automatically included on macOS (
darwin) when importinggithub.com/gomlx/gomlx/backends/default(requires CGO enabled). You can exclude it at build time with-tags=nomps, or import it directly viaimport _ "github.com/gomlx/go-darwinml/compute/mps". - Selection & Configuration Options: Set
GOMLX_BACKEND=mpsor callcompute.NewWithConfig("mps")(no additional configuration options). - Environment Variables: None specific to
"mps"(note: when benchmarking against the"go"backend on Apple M4/M5/M6, AppleSMEin the"go"backend is controlled byGOMLX_GO_SIMD_SME=0). - Architecture & Features:
- Zero-Copy Unified Memory Shared Buffers: Allocates tensors directly as Metal
MTLBuffers (MTLResourceStorageModeShared), allowing CPU and GPU to read and write the same physical memory pages without staging copies, with automatic resource cleanup via Go’sruntime.AddCleanup. - Ping-Pong Buffer Pooling: Works around
MPSGraph’s lack of in-place input/output buffer aliasing by pooling donated inputMTLBuffers across consecutive executions (with cross-executable stealing to avoid memory duplication on multi-task or multi-shape models). - Fused Operations: Native support for
FusedSoftmax,FusedGelu,FusedLayerNorm,FusedDense,FusedScaledDotProductAttention(SDPA),FusedAttentionQKVProjection, andFusedActivation.
- Zero-Copy Unified Memory Shared Buffers: Allocates tensors directly as Metal
- Limitations:
- Requires macOS (
darwin) with CGO enabled and an Apple Silicon GPU. - Unsupported DTypes: Does not support
Float64,Complex64,Complex128, or sub-byte integer types (a hardware/Metal limitation on Apple GPUs). - Unsupported Operations: Complex & FFT operations (
Complex,Real,Imag,Conj,FFT), bit-level/bitwise-reduction operations (Bitcast,BitCount,Clz,ReduceBitwiseAnd/Or/Xor,ReduceLogicalXor),SelectAndScatterSum/Min, dynamic-shape operations, multi-device collective operations,OptimizationBarrier/SchedulingBarrier, and quantized fused ops (FusedQuantizedDense,QuantizedEmbeddingLookup). Operations with GoMLX decompositions (likeCumSumandCumMax) fall back automatically.
- Requires macOS (
3.2 The "mlx" Backend (Apple MLX)
Executes GoMLX computation graphs on Apple Silicon GPUs using Apple’s MLX framework (mlx::core).
- Prerequisites: Requires MLX installed on macOS:(See the MLX installation guide for alternative installation methods. Building Go programs succeeds even if
brew install mlxmlxis not installed, andlibmlx.dylibis loaded dynamically at runtime when the"mlx"backend is selected.) - Importing: Automatically included on macOS (
darwin) when importinggithub.com/gomlx/gomlx/backends/default(requires CGO enabled). You can exclude it at build time with-tags=nomlx, or import it directly viaimport _ "github.com/gomlx/go-darwinml/compute/mlx". - Selection & Configuration Options (via
GOMLX_BACKEND="mlx[:<options>]"orcompute.NewWithConfig(...)):mlx(default): Compiled static-shape mode (DynamicShapesNone). Compiles the recorded op tape withmlx::core::compile(tape_fn, /*shapeless=*/false)for automatic kernel fusion, and materializes outputs as row-major contiguous arrays (eager_contiguous).mlx:dynamic: Native dynamic-shape mode (DynamicShapesNative,DynamicAxes: true). Replays the lazy MLX op tape directly on runtime input arrays withoutmlx::core::compile, supporting variable batch sizes and sequence lengths (shapes.DynamicDim) with zero recompilation overhead.mlx:dynamic,lazy_contiguous: In dynamic mode, skips materializing row-major contiguity on view outputs (Transpose,BroadcastInDim,Slice) at the end ofExecute(), deferringmlx::core::contiguousuntil the buffer is accessed on the CPU viaBuffer.Data()orBuffer.ToFlatData().
- Environment Variables:
MLX_LIBRARY_PATH: Optional explicit path tolibmlx.dylib(if installed in a non-standard location outside/opt/homebrew/lib/libmlx.dylibor/usr/local/lib/libmlx.dylib).
- Architecture & Features:
- Zero-Copy Unified Memory Shared Buffers (
mlx::core::array): Allocates unified-memory buffers directly frommlx::core::allocator::malloc, exposing shared CPU/GPU pointers as typed Go slices with atomic handle management andruntime.AddCleanup. - Native In-Place Buffer Donation: Releases external
mlx::core::arrayreferences for donated inputs (donate[i] == true) beforemlx::core::eval(outputs)souse_count() == 1, enabling MLX’s native in-place buffer reuse (move_shared_buffer) with only a single copy of model variables in memory. - Broad Operation & DType Coverage: Supports
Complex64,Bitcast,BitCount,Clz, bitwise reductions (ReduceBitwiseAnd/Or/Xor,ReduceLogicalXor), cumulative ops (CumSum,CumProd,CumMax,CumMin), 1D/2D/3DConvGeneral, general N-DReduceWindowandSelectAndScatterMax/Min(viamlx::core::as_strided),BatchNormForInference/Training/Gradient,Call(via tape inlining), pattern-matchedSort, and fused ops (FusedSoftmax,FusedLayerNormviamlx::core::fast::layer_norm,FusedActivation,FusedDense,FusedAttentionQKVProjection, andFusedScaledDotProductAttention).
- Zero-Copy Unified Memory Shared Buffers (
- Limitations:
- Unsupported DTypes: Does not support
Float64,Complex128, or sub-byte integer types (Int2,Int4,Uint2,Uint4) on Apple Silicon Metal GPUs. - Unsupported Operations: Data-dependent control flow (
IfandWhile;Callis supported via tape inlining andSortis supported for standard ascending/descending comparators),SelectAndScatterSum,FFT, multi-device collective operations (AllReduce), and quantized fused ops (FusedQuantizedDense,QuantizedEmbeddingLookup).
- Unsupported DTypes: Does not support
Performance Note (
"go"vs"mps"/"mlx"): With the addition of ARM64NEONand AppleSME(FEAT_SME) SIMD assembly microkernels and the 3-level parallel scheduler in the"go"backend, the"go"backend is often faster than"mps"and"mlx"on Apple Silicon Macs—particularly on small-to-medium models or workloads where Metal command-buffer dispatch and graph compilation overhead dominate (you can test"go"with or without SME viaGOMLX_GO_SIMD_SME=0). We recommend benchmarkingGOMLX_BACKEND=go(withGOEXPERIMENT=simd),GOMLX_BACKEND=mlx, andGOMLX_BACKEND=mpsfor your specific model.
4. The ONNX Runtime Backend ("onnx")
Uses ONNX Runtime (ORT) to execute GoMLX computation graphs on Linux and Windows (amd64). Implemented in package github.com/gomlx/compute-onnx.
- Pros: Interoperability with the ONNX ecosystem, ability to execute ONNX models, and support for exporting trained GoMLX models to the standard
.onnxfile format. - Cons: Requires CGO/C++ dependencies for ONNX Runtime. Currently supports Linux/amd64 and Windows/amd64.
- Importing: Included in
github.com/gomlx/gomlx/backends/defaultwhen building with-tags=onnx. Alternatively, importgithub.com/gomlx/compute-onnxdirectly.
Configuration options (via GOMLX_BACKEND=onnx:<options>):
cpu: Forces CPU execution.cuda(orgpu): Forces CUDA GPU execution via the ONNX Runtime CUDA Execution Provider.migraphx(orrocm,amd) (Experimental): Forces AMD GPU execution via the ONNX Runtime MIGraphX Execution Provider. Requires ROCm and MIGraphX to be installed (sudo apt install migraphx migraphx-dev half). If no ORT library with the MIGraphX provider is found, one is automatically installed from AMD’s manylinux wheels matching the local ROCm version — it can also be installed manually withgo run github.com/gomlx/compute-onnx/cmd/onnxruntime_installer -migraphx. Notes / Limitations:- Only
float32,int32, andint64graphs are supported. - Models with scalar (0-dimensional) inputs fail (upstream issue).
- Buffers are currently transferred back to CPU (host), which makes training slower.
- Only
migraphx_cache_dir=<path>: Directory where the MIGraphX compiled-program (.mxr) for each model is cached, skipping expensive graph compilation on subsequent runs. Can also be set viaGOMLX_MIGRAPHX_CACHE_DIR.<path/to/libonnxruntime.so>: Explicit path to the ONNX Runtime shared library binary (.so,.dylib, or.dll), bypassingONNXRUNTIME_SHARED_LIBRARY_PATH.log=<level>: Sets internal logging severity level (0=Error, 1=Warning, 2=Info, 3=Verbose).intra_op_num_threads=<int>: Number of threads to parallelize execution within an operator (default: 0 = all CPU cores). When running multiple concurrent goroutines/workers, set this to1to avoid thread oversubscription.inter_op_num_threads=<int>: Number of threads to parallelize execution across independent operators (only effective withexecution_mode=parallel).execution_mode=<parallel|sequential>: Controls whether independent operators execute sequentially or in parallel. Can also be specified as standaloneparallelorsequential.cpu_mem_arena=<bool>: Enables/disables ONNX Runtime’s CPU memory arena allocator (default: true). Disabling (cpu_mem_arena=falseorno_cpu_mem_arena) avoids allocator lock contention across parallel workers.mem_pattern=<bool>: Enables/disables memory pattern optimization (default: true; sequential mode only).graph_optimization_level=<0|1|2|3|99>(ordisable_all,basic,extended,all): Controls the graph optimization level applied by ONNX Runtime.session_clones=<int>: Maximum number of concurrent ONNX Runtime session clones pooled per compiled Executable (default:8on CPU,1on GPU). Clones are allocated on-demand as concurrent goroutines execute, eliminating lock contention while avoiding memory overhead for single-threaded usage.- Default (empty): Auto-detects if an NVIDIA GPU is available and defaults to CUDA if present, then checks for a discrete AMD GPU (ROCm/MIGraphX), otherwise falling back to CPU.
Examples:
GOMLX_BACKEND="onnx:cuda,log=2"GOMLX_BACKEND=onnx:migraphxGOMLX_BACKEND="onnx:cpu,intra_op_num_threads=1,inter_op_num_threads=1,cpu_mem_arena=false,execution_mode=parallel"GOMLX_BACKEND="onnx:/path/to/libonnxruntime.so"
ONNX Runtime Auto-Installation
The ONNX Runtime backend includes an auto-installer. At startup, if the ONNX Runtime shared library (libonnxruntime.so on Linux, libonnxruntime.dylib on macOS, onnxruntime.dll on Windows) is not found, it automatically downloads and extracts the official ONNX Runtime binaries locally into:
- Linux:
~/.local/lib/onnxruntime/(or~/.local/lib/onnxruntime-migraphx/for MIGraphX) - macOS:
~/Library/Application Support/onnxruntime/ - Windows:
~\AppData\Local\onnxruntime\
Custom Library Path & Disabling Auto-Installation
- Custom Library Path: Specify an explicit shared library location by passing a path in the configuration string (e.g.
GOMLX_BACKEND=onnx:/path/to/libonnxruntime.so, which bypassesONNXRUNTIME_SHARED_LIBRARY_PATH), or by setting theONNXRUNTIME_SHARED_LIBRARY_PATHenvironment variable. - Disabling Auto-Installation: Set environment variable
GOMLX_NO_AUTO_INSTALL=1or callonnxbackend.EnableAutoInstall(false)programmatically before initializing the backend to prevent automatic downloads (ideal for offline environments or production Docker builds). - Standalone Installer Utility: You can pre-install libraries using the CLI tool in
github.com/gomlx/compute-onnx/cmd/onnxruntime_installer(pass-migraphxto install the ROCm/MIGraphX build).
AMD ROCm / MIGraphX Environment Variables (Experimental)
The MIGraphX execution provider relies on a local ROCm installation:
ROCM_PATH: Directory where ROCm is installed (defaults to/opt/rocm). Used to locaterocminfoand HIP/MIGraphX libraries when auto-detecting AMD GPUs and ROCm versions.GOMLX_MIGRAPHX_CACHE_DIR: Directory where MIGraphX compiled programs (.mxr) are cached, skipping expensive graph compilation on subsequent runs. Equivalent to themigraphx_cache_dirconfiguration key (e.g.GOMLX_BACKEND=onnx:migraphx,migraphx_cache_dir=/tmp/mxr); an empty value disables caching.
Debugging & Saving Models on Compilation Failure
- Save Model on Failure: If graph compilation or session creation fails, set environment variable
GOMLX_ONNX_SAVE_ON_FAILURE=/path/to/failed_model.onnx. When set, the backend will write the serialized ONNX model protobuf to that file path for inspection and print aklognotification.
Exporting / Saving Models to .onnx Format
With the onnx backend enabled, you can save trained GoMLX models to standard .onnx files. These files can then be loaded and executed with ONNX Runtime in GoMLX or deployed in other languages and inference engines.
- Package:
github.com/gomlx/gomlx/ml/model/onnx(protected by build tag//go:build onnx). - Key Functions:
onnx.SaveToFile(backend, exec, filePath, inputShapes, inputNames, outputNames): Exports the computation graph and model parameters to an.onnxfile.onnx.Save(backend, exec, writer, inputShapes, inputNames, outputNames): Exports the ONNX model to anio.Writer.onnx.LoadFromFile(backend, filePath)/onnx.Load(backend, reader): Loads an.onnxmodel into an executable for inference within GoMLX.onnx.IsONNX(backend): Returnstrueif the providedcompute.Backendis an ONNX backend instance (*onnxbackend.Backend).
- Dynamic Axes: Supports dynamic input dimensions (such as variable batch sizes) using
exec.WithDynamicAxes(...)andshapes.MakeDynamic(...). - Example: See the
save_onnx.godemo in the UCI-Adult example (build with-tags=onnx).
WebAssembly / Browser Execution (ONNX Runtime Web)
The onnx backend also supports compiling to WebAssembly (GOOS=js GOARCH=wasm) and executing inside web browsers via ONNX Runtime Web.
- Supported Execution Providers:
webgpu(orgpu): Hardware-accelerated GPU shader execution via WebGPU (best for large vision/transformer models and parallel batches).wasm(orcpu): High-speed CPU WebAssembly execution using SIMD instructions (best latency for small models and single-sample loops).webnn: Hardware NPU/GPU acceleration via the experimental Web Neural Network API in Chromium.
- Auto-Detection: Automatically detects WebGPU hardware availability at startup and defaults to
webgpuif present, otherwise falling back towasmCPU. - Zero-Config Script Loading: If
ort.min.jsis not embedded in the page, the backend automatically injects it from the official CDN at runtime.
Inspecting & Visualizing .onnx Files
- CLI Printer (
onnx_printer): To inspect the contents, shapes, initializers, and operations of a.onnxmodel file directly in the terminal, use theonnx_printerutility ingithub.com/gomlx/compute-onnx/cmd/onnx_printer:It formats tensor shapes using GoMLXgo run github.com/gomlx/compute-onnx/cmd/onnx_printer path/to/model.onnxshapes.Shape(including dynamic dimension names), prints operations on a single line per op, and truncates large constant tensors (use-max_itemsor-nto control element limit). - Graphical Visualization: For interactive graphical diagram visualization of ONNX computation graphs, open your
.onnxmodel file using Netron.
Devices and DeviceNum
A backend can be connected to multiple accelerator devices (for instance, a machine with multiple GPUs or a TPU pod). To address specific devices within a backend, GoMLX uses the compute.DeviceNum type (which is an integer wrapper).
- Single-Device Default: If you are not using multiple accelerators, you can always simply default this device number to
0. - Addressing Devices: For multi-device setups, device numbers range from
0tobackend.NumDevices() - 1. You specify this number when allocating buffers on specific devices (e.g., intensors.FromShapeForBackend), performing distributed computations, or pinning executions.
There is support for distributed execution, including distributed datasets and distributed training, see packages compute/distributed, gomlx/core/tensors/dtensor along with the standard packages to train models.
Backend Compliance Testing
For anyone wanting to develop a new backend.
To ensure different backend engines behave identically and yield mathematically correct results, GoMLX includes a compliance test suite in support/backendtest.
If you write a custom backend, you can run all compliance checks by referencing this package in your test file:
package mybackend_test
import (
"testing"
"github.com/gomlx/compute/support/backendtest"
)
func TestCompliance(t *testing.T) {
// Run all official compliance tests against your backend
backendtest.RunAll(t, myBackend)
}
Compliance tests automatically check backend capabilities. Any tests that require operations your backend does not yet implement are gracefully skipped.