OCR filled the GPU: 9 GB for the mobile model on gpu-bench's 30 images, past 16 GB for the server model, which Windows then paged into shared memory (1.5 to 11 s a capture). onnxruntime's logged arena extensions showed why: the arena keeps every block it takes from CUDA and never joins or frees them, and OCR's inputs grow from run to run (recognizer batches went narrowest first; images in any order across calls), so each larger run left another block behind: 5.7 GB of recognizer blocks where the widest batch needs 0.9 GB. - Detection runs the largest images first, recognition the widest batches. - The detector's and recognizer's arenas shrink once each stage of an ocr_process call is done, by a run on a small input with memory.enable_memory_arena_shrinkage. Shrinking after every run cost 33 to 59% of the speed. - The worker threads are made with the engine and kept, not made for every stage of every call (onnxruntime keeps GPU state for each thread that runs a session, microsoft/onnxruntime#33037); 6% faster. - OCR's arenas grow by what is asked (kSameAsRequested). - ort.log_level sets what the engines' onnxruntime environments log; INFO shows every arena extension. Mobile OCR on gpu-bench (RTX 5080 laptop, 4 workers): peak 5.7 GB over the baseline, 2 GB between calls, 142 ms a capture, against 9.0 GB and 126 ms. The text read differs by a line in 2,986 (batches are grouped differently). The server model is not yet measured again.
116 lines
4.7 KiB
Plaintext
116 lines
4.7 KiB
Plaintext
// onnxruntime, loaded at run time. Nothing is linked: ort_load finds the library, takes its one
|
|
// export, OrtGetApiBase, and from it the OrtApi function table (bindings.jai, from generate.jai).
|
|
// A program that imports this module starts without onnxruntime and only fails in ort_load.
|
|
//
|
|
// Version: see fetch.jai (ORT_VERSION) and <os>/VERSION.txt. The bindings are for ORT_API_VERSION;
|
|
// an older library is refused, a newer one serves this version of the table.
|
|
|
|
ort: struct {
|
|
api: *OrtApi;
|
|
library_path: string; // the library that was loaded
|
|
version: string; // onnxruntime's own version string, such as 1.30.0
|
|
// What the engines' environments log, to the console: set before the first engine is made, as
|
|
// onnxruntime keeps one environment per process. INFO shows each GPU arena extension and its size.
|
|
log_level := OrtLoggingLevel.ORT_LOGGING_LEVEL_WARNING;
|
|
}
|
|
|
|
// Loads onnxruntime from the first folder that has it: beside the running executable, then each
|
|
// of folders. A full path is always used: Windows keeps an older onnxruntime.dll in System32 for
|
|
// its own use, which a bare name could find first.
|
|
ort_load :: (folders: [] string = .[]) -> ok: bool, error: string {
|
|
if ort.api return true, "";
|
|
|
|
candidates: [..] string;
|
|
candidates.allocator = temp;
|
|
array_add(*candidates, path_strip_filename(get_path_of_running_executable()));
|
|
for folders array_add(*candidates, it);
|
|
|
|
tried: [..] string;
|
|
tried.allocator = temp;
|
|
for folder: candidates {
|
|
path := tprint("%/%", trim_right(folder, "/\\"), ORT_LIBRARY_NAME);
|
|
if !file_exists(path) { array_add(*tried, path); continue; }
|
|
|
|
get_api_base, load_error := ort_open_library(path);
|
|
if !get_api_base return false, load_error;
|
|
|
|
base := get_api_base();
|
|
if !base return false, tprint("% returned no API.", path);
|
|
version := to_string(base.GetVersionString());
|
|
api := base.GetApi(ORT_API_VERSION);
|
|
if !api return false, tprint("% is onnxruntime %, which does not offer API version % (these bindings); use the version fetch.jai pins.", path, version, ORT_API_VERSION);
|
|
|
|
ort.api = api;
|
|
ort.library_path = copy_string(path,, context.default_allocator);
|
|
ort.version = copy_string(version,, context.default_allocator);
|
|
return true, "";
|
|
}
|
|
return false, tprint("% was not found. Looked for:\n %\nRun `jai fetch.jai` in modules/onnxruntime and rebuild, so the build copies it beside the executable.",
|
|
ORT_LIBRARY_NAME, join(..tried, "\n ",, temp));
|
|
}
|
|
|
|
// A failed status as its message, and the status released. ok and "" for success (a null status).
|
|
ort_check :: (status: OrtStatusPtr) -> ok: bool, message: string {
|
|
if !status return true, "";
|
|
message := tprint("%", to_string(ort.api.GetErrorMessage(status)));
|
|
ort.api.ReleaseStatus(status);
|
|
return false, message;
|
|
}
|
|
|
|
// A path as the C API takes it: UTF-16 on Windows, UTF-8 elsewhere. The bindings were generated on
|
|
// Windows, where ORTCHAR_T is a wchar_t, so elsewhere the pointer is cast to the type they expect. Temp.
|
|
ort_path :: (path: string) -> *ORTCHAR_T {
|
|
#if OS == .WINDOWS {
|
|
return utf8_to_wide(path,, temp);
|
|
} else {
|
|
return cast(*ORTCHAR_T) temp_c_string(path);
|
|
}
|
|
}
|
|
|
|
#load "bindings.jai";
|
|
#load "session.jai";
|
|
#load "cuda.jai";
|
|
|
|
#scope_module
|
|
|
|
Get_Api_Base :: #type () -> *OrtApiBase #c_call;
|
|
|
|
#if OS == .WINDOWS {
|
|
ORT_LIBRARY_NAME :: "onnxruntime.dll";
|
|
|
|
ort_open_library :: (path: string) -> Get_Api_Base, error: string {
|
|
// Altered search path: the libraries onnxruntime loads in turn (onnxruntime_providers_shared.dll,
|
|
// onnxruntime_providers_cuda.dll) are looked for in its folder.
|
|
windows_path := replace(path, "/", "\\",, temp);
|
|
module := LoadLibraryExW(utf8_to_wide(windows_path,, temp), null, LOAD_WITH_ALTERED_SEARCH_PATH);
|
|
if !module return null, tprint("Could not load % (Windows error %).", path, GetLastError());
|
|
get_api_base := cast(Get_Api_Base) GetProcAddress(module, "OrtGetApiBase");
|
|
if !get_api_base return null, tprint("% has no OrtGetApiBase; it is not onnxruntime.", path);
|
|
return get_api_base, "";
|
|
}
|
|
|
|
#import "Windows";
|
|
#import "Windows_Utf8";
|
|
} else {
|
|
#if OS == .MACOS ORT_LIBRARY_NAME :: "libonnxruntime.dylib";
|
|
else ORT_LIBRARY_NAME :: "libonnxruntime.so";
|
|
|
|
ort_open_library :: (path: string) -> Get_Api_Base, error: string {
|
|
handle := dlopen(temp_c_string(path), RTLD_NOW);
|
|
if !handle return null, tprint("Could not load %: %", path, to_string(dlerror()));
|
|
get_api_base := cast(Get_Api_Base) dlsym(handle, "OrtGetApiBase");
|
|
if !get_api_base return null, tprint("% has no OrtGetApiBase; it is not onnxruntime.", path);
|
|
return get_api_base, "";
|
|
}
|
|
|
|
#import "POSIX";
|
|
}
|
|
|
|
#import "Basic";
|
|
#import "String";
|
|
#import "File";
|
|
#import "File_Utilities";
|
|
#import "System";
|
|
#import "Sort";
|
|
#import "Atomics";
|