Repository navigation
Conversation
Adds an Onnx provider that computes embeddings inside the worker with ONNX Runtime (CPU), from a BERT-style .onnx model and its Hugging Face tokenizer.json supplied as nsfile://, kestra:// or allowed file:// URIs. Embeddings only. Loaded models are cached per worker by sha256 of model + tokenizer + pooling mode: langchain4j never closes the ONNX Runtime session, so reloading on every task run leaked ~115 MB of native memory per run. shadowJar excludes onnxruntime debug symbols (*.pdb, *.dSYM), cutting the added size from ~115 MB to ~51 MB. close kestra-io#354
|
Hi @fdelbrayelle requesting your review here,
also wanted to discuss about things that may pop up in future.
|
jymaire
left a comment
There was a problem hiding this comment.
Thanks a lot for this contribution @akhil838, and for the detailed write-up on the JAR size!
I went through it against the plan agreed in #354, and the scope matches: embeddings only, no bundled model, MEAN/CLS pooling, and it plugs into the existing RAG tasks without any change to them. It also follows the conventions of the other providers (UnsupportedOperationException for unsupported model types, internalStorageURI properties read through URIFetcher, icon, and the doc entry).
What I checked locally on 159574e:
./gradlew test --tests 'io.kestra.plugin.ai.provider.OnnxTest': 2/2 passed.shadowJar: 270.4 MiB. The onnxruntime.pdb/.dSYMdebug symbols are gone, and the onnxruntime and DJL tokenizers native libraries are still there for linux-x64, linux-aarch64, osx-aarch64, osx-x64 and win-x64. So the +51 MB figure from the issue holds.
I have one point, about the model cache. See the inline comment.
| ) | ||
| public class Onnx extends ModelProvider { | ||
| // ONNX Runtime sessions hold native memory that is never released, so each distinct model is loaded once per worker. | ||
| private static final Map<String, EmbeddingModel> MODELS = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
[MEDIUM] This cache is static and never evicts anything. Each distinct combination of model bytes, tokenizer bytes and poolingMode keeps its ONNX Runtime session in native memory until the worker stops. In onnxruntime 1.20.0, OrtSession only frees that memory through an explicit close() (it has no cleaner or finalizer), and OnnxBertBiEncoder never calls it. So even dropping an entry from the map would not free anything.
Workers are often shared across namespaces (and tenants in EE), so any flow can keep adding models (a different model, a re-exported file, a different pooling mode) and grow the worker's native memory until it gets OOM-killed. That would take down every other execution on that worker.
Could you put a limit on how much this cache can hold? For example, a small LRU where the plugin creates and owns the OrtSession, so it can close the session when the entry is evicted. langchain4j has a public OnnxBertBiEncoder(OrtEnvironment, OrtSession, InputStream, PoolingMode) constructor, plus AbstractInProcessEmbeddingModel, for exactly this. You would need to make sure a session is never closed while another task is still using it. If you'd rather keep the current approach, it would be good to agree with the maintainers on the trade-off and say clearly in the docs that every distinct model stays in worker memory until restart.
There was a problem hiding this comment.
hey @jymaire thanks for the review,
i pushed the LRU caching to unload the model with 0 references/uses automatically.
…sions The provider now owns each OrtSession and keeps at most max-loaded-models (default 2) loaded per worker, unloading the least recently used idle model. A model is never unloaded while an embedding call uses it, and close() waits for segments still running.


What changes are being made and why?
closes #354
Adds an
Onnxprovider that runs a sentence-embedding model inside the worker with ONNX Runtime, so you can do RAG without an API key or a model server. It only does embeddings, so it works anywhere an embedding provider is used (IngestDocument,Search,rag.ChatCompletion,EmbeddingStoreRetriever).chatModelandimageModelthrowUnsupportedOperationException.modelUriandtokenizerUripoint to the.onnxfile and itstokenizer.json. They are read withURIFetcher, sonsfile://,kestra://and allowedfile://all work. No model is bundled in the plugin.poolingModedefaults toMEAN(all-MiniLM, E5). BGE models needCLS.langchain4j-embeddingstakes the shaded JAR from 233 MB to 348 MB, but 64 MB of that is onnxruntime debug symbols (.pdb/.dSYM). I excluded them, so the JAR ends up at 284 MB (+51 MB) with all the native libs still there.How the changes have been QAed?
OnnxTestuses the all-MiniLM files that are already a test dependency, so it needs no network or container:I also tested it manually on a local Kestra with the plugin built from this branch. This flow downloads all-MiniLM, ingests three short documents and searches them;
searchreturns the Kestra one:I also checked loading the model from namespace files in another namespace (
nsfile://company.models/...) and ingesting a file uploaded through aFILEinput.I didn't run the full test suite, since most of it needs containers or API keys.
spotlessJavaCheckalready fails onmain, so I only formatted the two new Java files.Contributor Checklist ✅