Experimental pure Go inference runtime for the PaddleOCR-VL 0.9B Hugging Face checkpoint.
This project is pure Go at runtime: no Python, no PaddlePaddle, no PyTorch, no Transformers, and no external inference subprocess.
Current scope:
- reads
config.json - reads Hugging Face
model.safetensorsand shardedmodel.safetensors.index.json - supports BF16/F16/F32 tensor conversion
- implements the ERNIE text decoder path in Go
- implements the PaddleOCR-VL image preprocessing path in Go
- implements the vision Transformer encoder path in Go
- projects visual features into text hidden states and replaces image tokens
- provides a token-id based greedy generation CLI
Not complete yet:
- full SentencePiece parity
- exact bicubic resize parity with Pillow
- GPU/SIMD kernels
Download the model files:
go run ./cmd/paddleocrvl-download D:\models\PaddleOCR-VLDownloader also supports -out, -base-url, -timeout, and -json for
mirrors and automated setup. JSON output includes per-file bytes and SHA256.
Convert safetensors to GGUF manually:
go run ./cmd/paddleocrvl-convert -model-dir D:\models\PaddleOCR-VLThe converter logs progress by default; use -progress=false for quiet runs
and -json for a machine-readable conversion summary. Conversion summaries
include output path, bytes, SHA256, source, F32, and quantized tensor counts.
Use -gomaxprocs and
-gc-percent during large conversions to tune CPU and GC behavior.
Custom -out parent directories are created automatically.
Build a quantized GGUF for faster startup and smaller text weights:
go run ./cmd/paddleocrvl-convert -model-dir D:\models\PaddleOCR-VL -quant q8
go run ./cmd/paddleocrvl-convert -model-dir D:\models\PaddleOCR-VL -quant q6
go run ./cmd/paddleocrvl-convert -model-dir D:\models\PaddleOCR-VL -quant q4All runtime loaders prefer GGUF. If it is missing and safetensors weights exist
as either model.safetensors or model.safetensors.index.json shards, the
loader converts to GGUF automatically before inference. With -quant q8,
-quant q6, or -quant q4, loaders prefer model-q8.gguf, model-q6.gguf,
or model-q4.gguf; if missing, the loader converts directly to a quantized
GGUF before inference.
CLI, server, and local benchmark loaders log auto-conversion and text-weight loading progress during first load.
Inspect model metadata without loading all weights:
go run ./cmd/paddleocrvl-inspect D:\models\PaddleOCR-VL
go run ./cmd/paddleocrvl-inspect -json D:\models\PaddleOCR-VLInspect output reports the active weight format, quantization, path, and file size directly. JSON and text output also include per-weight-file SHA256 hashes.
Then run token-id inference:
go run ./cmd/paddleocrvl-go `
-model-dir D:\models\PaddleOCR-VL `
-tokens 100273,1234,5678 `
-max-new-tokens 16Image run:
go run ./cmd/paddleocrvl-go `
-model-dir D:\models\PaddleOCR-VL `
-image D:\docs\page.png `
-tokens 100273,101305,100295,101306,1234 `
-max-new-tokens 64When -image is set, a single 100295 image placeholder is expanded to the
number of projected visual tokens for that image.
Official-style task prompt:
go run ./cmd/paddleocrvl-go `
-model-dir D:\models\PaddleOCR-VL `
-image D:\docs\page.png `
-task ocr `
-max-new-tokens 1024 `
-decode-generated-only `
-skip-special-task accepts ocr, table, formula, and chart.
HTTP inference service:
go run ./cmd/paddleocrvl-server -model-dir D:\models\PaddleOCR-VL -addr 127.0.0.1:8080Admin console and API documentation:
- open
http://127.0.0.1:8080/adminfor first-run admin initialization, login, model-path settings, API key issuance, quota management, and service overview. - open
http://127.0.0.1:8080/docfor human-readable API documentation. - open
http://127.0.0.1:8080/doc/openapi.jsonfor machine-readable OpenAPI 3.1, useful for other AI agents or HTTP clients. Operations include stableoperationIdvalues,status/inferencetags, and request examples for OCR, JSON generation, and batch calls, plus success/error response examples. - open
http://127.0.0.1:8080/doc/llms.txtfor a concise plaintext integration guide intended for AI agents.
The first admin initialization creates a default API key and shows the key only
once. Additional keys are generated by the server in the admin console. Keys can
be named or renamed, disabled, deleted, assigned a request quota, and reset for a new quota
period. Keys can also have a per-minute rate limit to protect local inference
capacity. Keys can be rotated when leaked; the old key is revoked immediately
and the new plaintext key is shown once. The console shows recent usage time and
client IP per key. A quota or rate limit of 0 means unlimited. Inference
endpoints require:
Authorization: Bearer <API_KEY>
The admin console keeps an in-memory recent-call audit log with API key, client IP, path, HTTP status, latency, authentication/quota errors, and JSON error messages returned by inference handlers. It stores request metadata only, not uploaded images or prompts. The recent audit log can be exported as CSV or cleared from the admin console before a focused integration test.
Admin configuration can be exported and restored from the Settings page. Backups
include admin password hashes, API key hashes/limits, model path settings, and
post-processing path settings, but never plaintext API keys or passwords.
Admin pages and admin JSON endpoints use no-store, frame-deny, MIME nosniff,
same-origin referrer, and a restrictive content security policy.
Admin login applies a short failed-attempt lockout per client IP.
Admin write endpoints reject cross-origin Origin/Referer headers.
Useful service flags:
-admin-config paddleocrvl-admin.jsonsets the admin console config path.-timeout 10msets a per-request timeout.-shutdown-timeout 30scontrols graceful shutdown after SIGINT/SIGTERM.-request-limit 134217728caps request body size.-multipart-memory 33554432caps memory used while parsing multipart forms.-max-new-limit 4096caps generated tokens per request.-max-input-tokens 0caps prompt/input tokens per request;0disables the cap.-max-batch-size 0caps/v1/batchitem count;0disables the cap.- generation options reject negative
max_new_tokens,temperature, andtop_k. -concurrency 1controls concurrent inference slots.-gomaxprocs 0controls Go CPU worker threads;0keeps the current value.-gc-percent 0controls Go GC target;0keeps current value and-1disables GC.-preload-visionloads vision weights during startup.-warmupruns one text-token warmup during startup.-quant q8enables row-wise int8 text-weight quantization.-quant q6enables row-wise int6 text-weight quantization.-quant q4enables row-wise int4 text-weight quantization.-quant autopicks an existingmodel-q4.gguf, thenmodel-q6.gguf, thenmodel-q8.gguf, thenmodel.gguf; if only safetensors exists, it buildsmodel-q6.gguf.
Windows service control:
paddleocrvl-server.exe service install -model-dir "C:\ProgramData\PaddleOCRVL\models" -admin-config "C:\ProgramData\PaddleOCRVL\paddleocrvl-admin.json" -addr 127.0.0.1:8080
paddleocrvl-server.exe service start
paddleocrvl-server.exe service stop
paddleocrvl-server.exe service uninstallGitHub Actions release workflow:
.github/workflows/release.ymlruns tests, then builds Windows NSIS, macOS PKG, and Linux AppImage artifacts.- Windows builds
amd64andarm64installers. - macOS builds one universal PKG containing x86_64 and arm64 binaries.
- Linux builds AppImage artifacts for
x86_64andaarch64. - Push a tag like
v1.0.0, or run the workflow manually with a version input.
Windows NSIS installer:
.\packaging\windows\build-nsis.ps1 -Version 1.0.0The installer packages paddleocrvl-client.exe and paddleocrvl-server.exe.
On install it registers PaddleOCRVLService as an automatic NT service and
starts it. On uninstall it stops and removes the service before deleting files.
The service uses C:\ProgramData\PaddleOCRVL\models as its default model
directory and C:\ProgramData\PaddleOCRVL\paddleocrvl-admin.json for admin
state. Put the model files in the default model directory before installing, or
the installer will fail when it verifies that the service can start.
macOS universal PKG installer:
VERSION=1.0.0 sh ./packaging/macos/build-pkg.shLinux AppImage:
VERSION=1.0.0 ARCH=x86_64 sh ./packaging/linux/build-appimage.sh
VERSION=1.0.0 ARCH=aarch64 sh ./packaging/linux/build-appimage.shThe Linux script uses linuxdeploy with the GTK plugin so the Wails/WebKitGTK
client gets a relocatable AppDir before AppImage output.
The PKG contains a universal Wails client app, a universal
paddleocrvl-server, and a LaunchDaemon named
com.znsoft.paddleocrvl.service. Post-install scripts load and kickstart the
service. The default model directory is
/Library/Application Support/PaddleOCRVL/models; it must contain model files
before installation so the LaunchDaemon can start successfully. The package
also installs /usr/local/paddleocrvl/uninstall.sh, which removes the
LaunchDaemon, server binary, and client app while keeping model/admin data.
-quant auto-fastprefers/builds Q4 for speed and size.-quant auto-qualityprefers/builds Q8 for quality.-backend cpu|auto|vulkanselects compute backend.vulkanis strict for loader/interface probing and fails startup if Vulkan is unavailable;autofalls back to CPU. The current pure-Go Vulkan layer exposes loader/device/driver status plus registered matvec/QKV/SwiGLU compute-kernel plans and model-shaped dispatch grids/summaries/stage execution graph in/stats. Kernel metadata includes descriptor bindings, push-constant ABI, and comparable pipeline cache keys. Command plans also expose per-dispatch resource bindings, pipeline layout plans, shader-module plans, descriptor-set layout/index plans, push-constant payloads, descriptor write plans, command-buffer recording plans, dispatch-batch bind-reuse plans, buffer barrier plans, buffer allocation plans, host/device transfer plans, byte ranges, and aligned storage-buffer allocation sizes plus descriptor-pool, command-pool, queue-submit, timeline, fence, pipeline-cache, pipeline-lifecycle, and validation plans for future descriptor-set writes./statsand-stats-only -jsonexposevulkan_command_plan_validandvulkan_command_plan_error. It reports CPU as the active tensor backend until GPU command submission is enabled.
Health and stats:
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/ready
curl http://127.0.0.1:8080/stats/ready reports whether the model, tokenizer, and inference slots are
initialized. /stats includes uptime, in-flight requests, request counters, failures,
cancel count, generated token count, average latency, average queue wait, last error, requested/effective
quantization, loaded weight_path, weight_sha256, Go memory stats, and model
dimensions. weight_source is existing_gguf when loading a GGUF file directly
and converted_safetensors when the loader converted original safetensors.
load_stats reports milliseconds spent in weight open/auto-convert, text preload,
runtime quantization, and total load. The cache section includes reusable task
prompts, tokenizer cache stats, and runtime vision position-table cache stats.
Batch JSON inference:
curl -X POST http://127.0.0.1:8080/v1/batch ^
-H "Authorization: Bearer <API_KEY>" ^
-H "Content-Type: application/json" ^
-d "{\"requests\":[{\"prompt\":\"<|begin_of_sentence|>hello\",\"max_new_tokens\":1},{\"task\":\"ocr\",\"image_path\":\"D:\\docs\\page.png\",\"decode\":true,\"decode_generated_only\":true,\"skip_special\":true}]}"Batch responses include items, aggregate generated_tokens, and per-request
responses. Batch items run concurrently up to the server -concurrency
slot limit while preserving response order.
HTTP benchmark:
go run ./cmd/paddleocrvl-bench `
-url http://127.0.0.1:8080/v1/generate `
-n 20 `
-c 1 `
-prompt "<|begin_of_sentence|>hello" `
-max-new-tokens 1 `
-temperature 0 `
-top-k 0 `
-batch-size 1Local runtime benchmark, bypassing HTTP:
go run ./cmd/paddleocrvl-bench `
-mode local `
-model-dir D:\models\PaddleOCR-VL `
-n 5 `
-c 1 `
-prompt "<|begin_of_sentence|>hello" `
-max-new-tokens 1Kernel microbenchmarks:
go test ./internal/tensor -bench "MatVec|Fused|Quantize" -run "^$"
go test ./internal/model -bench "Sample|TopK" -run "^$"
go test ./internal/tokenizer -bench Encode -run "^$"Set -batch-size above 1 to benchmark /v1/batch; the default /v1/generate
URL is automatically rewritten to /v1/batch. Benchmark output includes
request, item, and generated token throughput, plus last_error when failures
occur. Add -json for machine-readable benchmark results with CPU and memory
snapshots plus mode/backend/quantization/weight path/source context.
JSON request with an image path:
curl -X POST http://127.0.0.1:8080/v1/generate ^
-H "Content-Type: application/json" ^
-d "{\"task\":\"ocr\",\"image_path\":\"D:\\docs\\page.png\",\"max_new_tokens\":1024,\"decode\":true,\"decode_generated_only\":true,\"skip_special\":true}"JSON request with base64 image data:
curl -X POST http://127.0.0.1:8080/v1/generate ^
-H "Content-Type: application/json" ^
-d "{\"task\":\"ocr\",\"image_base64\":\"<base64-or-data-url>\",\"max_new_tokens\":1024,\"eos_token_ids\":[2],\"decode\":true,\"decode_generated_only\":true,\"skip_special\":true}"Base64 and multipart image requests are decoded in memory and passed directly
to the Go image preprocessing path. JSON generation responses include
tokens, prompt_tokens, and generated_tokens; text is included when
decoding is requested.
Multipart upload:
curl -X POST http://127.0.0.1:8080/v1/ocr ^
-F "task=ocr" ^
-F "image=@D:\docs\page.png"Desktop client:
cd cmd\paddleocrvl-client
wails devThe Wails client lets you set API URL and API Key, check /ready, open /doc,
choose and preview one or more images, manage the selected image queue with
per-image status, upload them to the multipart OCR endpoint, optionally continue a batch after per-image
errors, copy or save results, export batch results as JSON, set a request timeout, cancel in-flight
requests, inspect each batch result row, view the decoded text plus raw JSON, and reopen the last 10 local
runs from history. Client settings can be imported/exported as JSON. If the API
URL has no path, the client appends /v1/ocr. API Key is sent as both
Authorization: Bearer <key> and X-API-Key.
Prompt inference:
go run ./cmd/paddleocrvl-go `
-model-dir D:\models\PaddleOCR-VL `
-prompt "<|begin_of_sentence|>hello" `
-max-new-tokens 16 `
-decode-generated-onlyLoad, convert, quantize, and print memory stats without generating:
go run ./cmd/paddleocrvl-go -model-dir D:\models\PaddleOCR-VL -quant auto -stats-only
go run ./cmd/paddleocrvl-go -model-dir D:\models\PaddleOCR-VL -quant auto-fast -verify-only -verify-visionAdd -json to paddleocrvl-go for machine-readable stats, verification, or
generation output.
Stats and verification output include the loaded weight_path and
weight_source. Stats output also includes load_stats.
-stats-only also prints CPU features and backend details. On Linux, Vulkan
backend details include discovered ICD manifests and driver API versions; on
Windows, the loader reports the Vulkan instance API version when available.
CPU details include num_cpu and gomaxprocs for throughput tuning. Memory
details include heap, system allocation, object, and GC counters.
-verify-only exits after text weights load; add -verify-vision to force
vision weight loading too.
Tokenizer support is based on tokenizer.json: added special tokens, BPE merge
ranks, byte fallback, and the model's space replacement decoder are implemented.
The runtime uses Go CPU parallelism for large linear layers and batched vision
projections. Set GOMAXPROCS to control CPU worker count.
On Windows, if a default go build ./cmd/... output executable is locked by a
running process, build to an explicit path:
go build -o .\.gocache\bin\paddleocrvl-server.exe ./cmd/paddleocrvl-serverGeneration defaults to greedy decoding. Set -temperature above 0 to sample;
combine with -top-k and -seed for reproducible sampled runs.
Acceleration work in tree:
- row-wise int8 quantized text projection/MLP path (
-quant q8) - row-wise int6 quantized text projection/MLP path (
-quant q6) - row-wise int4 quantized text projection/MLP path (
-quant q4) - quantized GGUF conversion/loading path (
model.safetensors->model-q8.gguf/model-q6.gguf/model-q4.gguf) - row-streamed GGUF quantized conversion to reduce peak memory during first load
- reusable safetensors row buffers during GGUF quantized conversion to avoid per-tensor block reallocations
- GGUF quantized conversion reuses scale and quantized-row buffers across tensors to reduce multi-tensor conversion churn
- reusable GGUF F32 row buffers during runtime Q8/Q6/Q4 quantization from
model.gguf - lower-peak GGUF quantized tensor loading
- single-read GGUF Q8/Q6/Q4 tensor loading with shared scale/data backing
- existing GGUF load path opens candidate files directly and skips a separate pre-open stat call
- GGUF metadata open reuses shape backing storage and zero-copy string views to reduce first-load allocation count
- safetensors fallback probing is lazy and cached during weight selection
- row-streamed F32 GGUF -> Q8/Q6/Q4 runtime quantization when only
model.ggufis available - pre-sized runtime text/vision weight maps to reduce loader rehash churn
- release text-weight map entries after caching layer pointers to reduce runtime map scanning and retained references
- release vision-weight map entries after caching vision layer pointers for the same reason
- unrolled safetensors BF16/F16 decode paths for first-load conversion
- wider safetensors F16 decode loop for lookup-table conversion
- fused Q8/Q6/Q4 SwiGLU MLP path
- fused Q/K/V attention projection path for F32/Q8/Q6/Q4 weights
- fused residual-add + RMSNorm path in the text decoder, including next-layer pre-normalization after MLP residuals
- vision residual LayerNorm path uses the faster two-pass AddInPlace+LayerNorm sequence on Go CPU kernels
- per-token text RoPE table reuse across decoder layers to avoid repeated sin/cos table builds
head_dim=128andhead_dim=64attention-score dot-product fast paths- single-token
head_dim=128andhead_dim=64text attention-score fast paths - fused text KV-cache score + Softmax + value path for stable 2-token decoding
head_dim=128andhead_dim=64attention value aggregation fast paths for text KV cache and vision attention- short-context text KV and vision value aggregation fast paths for
head_dim=64 - unrolled RMSNorm hot path
- unrolled greedy Argmax path
- unrolled Softmax path
- specialized length-5/6/7/8 Softmax paths for early attention steps
- wider Q4/Q6 dot-product decode loops
- Q8/Q6/Q4 decode lookup tables for quantized dot products
- single-pass Q8 triplet dot product for fused Q/K/V projection
- wider Q8/Q6/Q4 row quantization loops for safetensors -> GGUF conversion
- wider F32/Q8 dot-product loops
- parallel GELU over large vision MLP row batches
- no-sort full-vocab sampling path and unsorted heap-backed top-k sampling
- full-vocab sampling max pass avoids per-logit temperature multiplication
- full-vocab
temperature=1sampling fast path skips per-logit temperature scaling - top-k sampling scan skips eight low-score logits per branch before heap work
- sampled-token weighted pick loop checks eight weights per iteration
- short EOS-id lists use direct comparisons in the generation loop
- greedy decoding skips RNG initialization when sampling is disabled
- zero-token text generation returns before allocating KV/scratch state
- zero-token image generation returns before image decode/vision encoding
- multimodal RoPE position construction tracks the current maximum position incrementally instead of rescanning previous tokens for every image block
- multimodal RoPE position buffers are reused from generation scratch during image generation to avoid per-request position slice allocation
- server greedy requests skip random seed generation when sampling is disabled
- CLI greedy requests skip random seed generation when sampling is disabled
- single-item batch requests avoid unused response/error slice allocation
- health, ready, and stats endpoints use fixed response structs instead of dynamic maps for lower monitoring overhead
- short server error responses use a stack buffer before falling back to heap
- read-only tokenizer encode cache path for server/CLI prompts to avoid cached slice copies
- tokenizer special-token matching keeps ordered token/id entries to avoid map lookups on prefix matches
- empty tokenizer encode/decode inputs return before cache locks or builders
- shared RGBA resize/preprocess bilinear-index backing to reduce hot-path allocations
- exact-size RGBA preprocessing skips bilinear resize and extracts patches directly in parallel
- base 27x27 vision position tables reuse a cached row view instead of interpolating or allocating
- vision position and RoPE cache hits use read locks so concurrent image requests do not serialize on monitoring/cache reads
- BF16/F16 safetensors -> quantized GGUF conversion reuses a raw decode buffer
- direct BF16/F16 safetensors row quantization during runtime load reuses raw decode storage across tensors
- safetensors model selection opens candidate files directly and preserves bad single-file errors instead of probing with extra stat calls
- incremental vision projection block indexing to reduce per-row integer division/modulo work
- pooled attention score buffers and lower-overhead KV cache appends
- packed per-layer text attention score buffers to reduce first-request and long-context allocation count
- generation scratch and KV cache getters have direct fallback allocation paths for pool-miss robustness without changing the pooled hot path
- pre-sized tokenizer decode buffers
- tokenizer byte-fallback decode fast paths for pure byte streams and single byte tokens
- mixed tokenizer byte-fallback decode uses one pass with lazy string builder
- tokenizer Unicode byte fallback encodes UTF-8 into a stack buffer instead of
allocating
[]byteper unknown rune - pooled vision projection scratch buffers and compact vision scratch row storage
- on-demand pooled vision embedding buffer in the image encoder to avoid a
separate large embedding allocation during
EncodeImage - vision embedding rows share the main vision scratch backing block to reduce first image encode allocation count
- cached vision RoPE tables by image grid and head dimension to avoid repeated per-image table allocations
- compact vision RoPE table storage with one backing block per axis table
- fused RGBA resize + patch extraction precompute tables to cut image preprocessing allocations
- current-goroutine first worker for RGBA resize and patch extraction to reduce scheduler allocation overhead
- adaptive vision projection worker limits to reduce scheduler overhead on small image grids while keeping full parallelism for large projections
- adaptive batched row-projection worker limits for small per-patch vision embeddings
- batch endpoint holds one inference slot across batch execution to reduce scheduler churn
- CPU parallel matrix-vector/matrix-row kernels with unrolled dot products
- CPU feature reporting in
/stats(arm64reports NEON-capable target) - Vulkan loader probing on Windows and Linux exposed in
/stats - Vulkan operator registry for f32/Q8/Q6/Q4 matvec and fused QKV/SwiGLU kernel plans with 256-thread reductions
- current-model Vulkan dispatch plans and expanded dispatch/weight-byte summaries exposed in CLI/server JSON stats
- current-model Vulkan execution graph groups text and vision stages for future command-buffer submission
- Vulkan kernel ABI metadata exposes descriptor bindings, push constants, and activation input/output byte estimates
- Vulkan pipeline cache keys and unique pipeline counts are exposed for future layout/pipeline reuse
- Vulkan pipeline precreation plans expose unique pipeline keys, stage, reference counts, and expanded dispatch counts
- Vulkan pipeline plans include layout indices built from descriptor signatures and push-constant sizes for future pipeline-layout reuse
- Vulkan pipeline plans include shader-module indices and shader module plans with entry point, source hash, local size, tile columns, specialization constants, and pipeline refs
- Vulkan command plans map model ops to pipeline slots with dispatch grids and repeat counts for future command-buffer recording
- Vulkan command plans include per-command input/weight/scale/output resources, descriptor binding indices, access modes, and byte sizes for future descriptor-set writes
- Vulkan command plans include per-command descriptor-set layout/index plans and concrete push-constant payloads for rows/columns
- Vulkan command plans include storage-buffer descriptor write records and resource/write counts for each model-shaped dispatch plan
- Vulkan command plans expose unique pipeline-layout plans with storage-buffer bindings and pipeline reference counts
- Vulkan command plans emit future command-buffer recording records for bind-pipeline, bind-descriptor-set, push-constants, and dispatch steps
- Vulkan command plans include dispatch-batch grouping for consecutive commands that can reuse pipeline binding while updating descriptor sets and push constants
- Vulkan command plans include buffer memory barrier plans for host-to-compute reads and compute-write-to-compute-read handoff around planned dispatches
- Vulkan command plans include per-resource buffer usage, memory property, and aligned total buffer-byte allocation plans for future device-memory binding
- Vulkan command plans include host-to-device upload and device-to-host readback transfer plans with byte totals for future staging-buffer execution
- Vulkan command plans include descriptor-pool sizing, compute command-pool sizing, and single-submit queue plans for future queue submission
- Vulkan command plans include timeline semaphore values and fence reset/wait plans for future queue completion tracking
- Vulkan command plans include compute pipeline cache keys, reuse counts, and pipeline create/destroy lifecycle plans for future cache-backed pipeline setup
- Vulkan command plans have a pure-Go validator for pipeline/layout/shader, descriptor/resource, dispatch, sync, and lifecycle consistency
- Vulkan command-plan validation status is exposed as
vulkan_command_plan_valid/vulkan_command_plan_errorin server and CLI JSON stats - Linux Vulkan ICD manifest discovery exposed in
/statsand-stats-only - Linux Vulkan ICD relative
library_pathentries are resolved against the manifest directory for clearer driver reporting - GGUF conversion/loading path (
model.safetensors->model.gguf)