// Sessions: one model file each, run on an NVIDIA GPU through CUDA or on the CPU, and running them on // a tensor. Shared by the modules that run models (OCR, Labels, Segment). // CUDA sessions are made, first run and released one at a time, process-wide: hold ort_gpu_lock // around it. Done on two threads at once (onnxruntime 1.30.0, CUDA 13, cuBLASLt 13.8, RTX 5080 // laptop), it crashed with a DEP access violation in cuBLASLt or near it, every time in a debug build; // one at a time, never. Running sessions on many threads at once is fine. A spin lock, needing no // setup, as engines can be made before anything else runs; it is held for a second or so at a time. ort_gpu_lock :: () { while atomic_swap(*ort_gpu_held, 1) != 0 sleep_milliseconds(2); } ort_gpu_unlock :: () { atomic_swap(*ort_gpu_held, 0); } #scope_file ort_gpu_held: s32; #scope_export Ort_Session :: struct { session: *OrtSession; input_name, output_name: *u8; // heap C strings input_shape: [4] s64; // as the model declares it: -1 for a size it leaves open input_rank: s64; } // How a CUDA session uses GPU memory. The defaults came from measuring OCR on the sample captures // (RTX 5080 laptop): with nearly every input a new size, onnxruntime's exact-size arena filled GPU // memory, and cuDNN's largest workspaces made it worse. Ort_Gpu_Settings :: struct { arena_extend_strategy := "kNextPowerOfTwo"; // reuses freed blocks for other sizes; "kSameAsRequested" grows by exactly what is asked cudnn_max_workspace := false; // let cuDNN take as much working memory as it finds best (onnxruntime's own default) } // A session for the model file at path; with CUDA when gpu is set. output names the output that // ort_run returns; "" for the first. missing_hint is added when the file is not there, to say how // to get it. On failure, the reason. ort_create_session :: (env: *OrtEnv, path: string, gpu: bool, intra_op_threads: s32, settings := Ort_Gpu_Settings.{}, output := "", missing_hint := "") -> Ort_Session, ok: bool, error: string { result: Ort_Session; api := ort.api; options: *OrtSessionOptions; ok, error := ort_check(api.CreateSessionOptions(*options)); if !ok return result, false, error; defer api.ReleaseSessionOptions(options); ort_check(api.SetSessionGraphOptimizationLevel(options, .ORT_ENABLE_ALL)); ort_check(api.SetIntraOpNumThreads(options, intra_op_threads)); ort_check(api.SetInterOpNumThreads(options, 1)); if gpu { cuda: *OrtCUDAProviderOptionsV2; ok, error = ort_check(api.CreateCUDAProviderOptions(*cuda)); if !ok return result, false, error; defer api.ReleaseCUDAProviderOptions(cuda); // Inputs come in many sizes: an exhaustive cuDNN search would be redone for each new shape. keys := (*u8).["device_id", "cudnn_conv_algo_search", "arena_extend_strategy", "cudnn_conv_use_max_workspace"]; values := (*u8).["0", "HEURISTIC", temp_c_string(settings.arena_extend_strategy), temp_c_string(ifx settings.cudnn_max_workspace then "1" else "0")]; ok, error = ort_check(api.UpdateCUDAProviderOptions(cuda, keys.data, values.data, xx keys.count)); if !ok return result, false, error; ok, error = ort_check(api.SessionOptionsAppendExecutionProvider_CUDA_V2(options, cuda)); if !ok return result, false, error; } if !file_exists(path) return result, false, tprint("% is not there%.", path, ifx missing_hint then tprint("; %", missing_hint) else ""); ok, error = ort_check(api.CreateSession(env, ort_path(path), options, *result.session)); if !ok return result, false, error; allocator: *OrtAllocator; ort_check(api.GetAllocatorWithDefaultOptions(*allocator)); name: *u8; ort_check(api.SessionGetInputName(result.session, 0, allocator, *name)); result.input_name = to_c_string(to_string(name),, context.default_allocator); ort_check(api.AllocatorFree(allocator, name)); // The output: the one named, else the first. output_count: u64; ort_check(api.SessionGetOutputCount(result.session, *output_count)); names: [..] string; names.allocator = temp; for i: 0..cast(s64) output_count - 1 { ort_check(api.SessionGetOutputName(result.session, xx i, allocator, *name)); array_add(*names, copy_string(to_string(name),, temp)); ort_check(api.AllocatorFree(allocator, name)); } chosen := ifx names.count then names[0] else ""; if output { found := array_find(names, output); if !found { ort_release_session(*result); return result, false, tprint("% has no output %; it has %.", path, output, names); } chosen = output; } result.output_name = to_c_string(chosen,, context.default_allocator); input_type: *OrtTypeInfo; if ort_check(api.SessionGetInputTypeInfo(result.session, 0, *input_type)) { tensor_info: *OrtTensorTypeAndShapeInfo; // belongs to input_type if ort_check(api.CastTypeInfoToTensorInfo(input_type, *tensor_info)) && tensor_info { rank: u64; ort_check(api.GetDimensionsCount(tensor_info, *rank)); result.input_rank = min(cast(s64) rank, result.input_shape.count); ort_check(api.GetDimensions(tensor_info, result.input_shape.data, xx result.input_rank)); } api.ReleaseTypeInfo(input_type); } return result, true, ""; } ort_release_session :: (s: *Ort_Session) { if s.session ort.api.ReleaseSession(s.session); free(s.input_name,, context.default_allocator); free(s.output_name,, context.default_allocator); s.* = .{}; } // A run's output: its values stay valid until ort_release_output. Ort_Output :: struct { value: *OrtValue; data: *float; // the values, for a float output (every model here has one) dims: [4] s64; dim_count: s64; } // Runs the session on one input tensor of shape, bytes long at data, with element type element. // A session may be run from several threads at once. ort_run :: (s: *Ort_Session, memory_info: *OrtMemoryInfo, data: *void, bytes: s64, element: ONNXTensorElementDataType, shape: [] s64) -> Ort_Output, ok: bool, error: string { out: Ort_Output; api := ort.api; input: *OrtValue; ok, error := ort_check(api.CreateTensorWithDataAsOrtValue(memory_info, data, xx bytes, shape.data, xx shape.count, element, *input)); if !ok return out, false, error; defer api.ReleaseValue(input); ok, error = ort_check(api.Run(s.session, null, *s.input_name, *input, 1, *s.output_name, 1, *out.value)); if !ok return out, false, error; raw: *void; ort_check(api.GetTensorMutableData(out.value, *raw)); out.data = cast(*float) raw; info: *OrtTensorTypeAndShapeInfo; ort_check(api.GetTensorTypeAndShape(out.value, *info)); count: u64; ort_check(api.GetDimensionsCount(info, *count)); out.dim_count = min(cast(s64) count, out.dims.count); ort_check(api.GetDimensions(info, out.dims.data, xx out.dim_count)); api.ReleaseTensorTypeAndShapeInfo(info); return out, true, ""; } ort_run_float :: (s: *Ort_Session, memory_info: *OrtMemoryInfo, data: [] float, shape: [] s64) -> Ort_Output, ok: bool, error: string { out, ok, error := ort_run(s, memory_info, data.data, data.count * size_of(float), .ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, shape); return out, ok, error; } // As ort_run_float, for a model with several outputs: those named, into outs (one each, in order), // each released with ort_release_output. On failure none is set. ort_run_float_outputs :: (s: *Ort_Session, memory_info: *OrtMemoryInfo, data: [] float, shape: [] s64, names: [] string, outs: [] Ort_Output) -> ok: bool, error: string { assert(names.count == outs.count); api := ort.api; input: *OrtValue; ok, error := ort_check(api.CreateTensorWithDataAsOrtValue(memory_info, data.data, xx (data.count * size_of(float)), shape.data, xx shape.count, .ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, *input)); if !ok return false, error; defer api.ReleaseValue(input); output_names := NewArray(names.count, *u8,, temp); for names output_names[it_index] = temp_c_string(it); values := NewArray(names.count, *OrtValue, initialized = true,, temp); ok, error = ort_check(api.Run(s.session, null, *s.input_name, *input, 1, output_names.data, xx names.count, values.data)); if !ok { for values if it api.ReleaseValue(it); return false, error; } for * out: outs { out.* = .{value = values[it_index]}; raw: *void; ort_check(api.GetTensorMutableData(out.value, *raw)); out.data = cast(*float) raw; info: *OrtTensorTypeAndShapeInfo; ort_check(api.GetTensorTypeAndShape(out.value, *info)); count: u64; ort_check(api.GetDimensionsCount(info, *count)); out.dim_count = min(cast(s64) count, out.dims.count); ort_check(api.GetDimensions(info, out.dims.data, xx out.dim_count)); api.ReleaseTensorTypeAndShapeInfo(info); } return true, ""; } ort_release_output :: (out: *Ort_Output) { if out.value ort.api.ReleaseValue(out.value); out.* = .{}; }