gtcode
Aloha gang,
I’m working on a port of Sakana AI’s TRINITY, an evolved LLM coordinator:
TRINITY Paper
OpenReview
Downloadable Assets
I started by attempting to reconstruct the work itself, but that isn’t realistic for me, given skill/resource constraints. So I’ve instead pivoted to porting their Python mechanism that uses a base Qwen model to build their coordinator: Trinity Coordinator. (Right now it’s been deconstructed to use a local path-dep in mix.exs related to an inference library I’m building to generalize abstracting LLM providers, thus not “clone friendly” yet.)
Just seeing if this is of interest to anyone. Certainly open to input/feedback/ideas/critiques on approach. Please respond here or open an issue with your candid feedback. There must be someone out there with more knowledge/experience on such matters who can provide guidance?
I’ve created the safetensors file from the original python scripts, so nx can talk numbers properly. I’ve been working on a staged verification process so that the resulting coordinator based on Qwen will behave the same as the generated .pt file from their Python system. There are some nuances related to numpy -> nx and others that might prevent perfect alignment but I’m aiming for behavioral and functional parity.
One thing I see often these days is people creating amazing work and ideas in Elixir, but often hard coupled to providers and the like. One goal for trinity_coordinator is to have a working standalone system with built in routing to LLM’s, but also pluggable/modular for integration into any other codebase/framework/system.
ps: I wasn’t sure if this is the right forum category, but there was a note that said to use the nx forum if it’s nx related.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
gtcode
I put in some elbow grease yesterday. Seems to be pretty solid so far. Using a modified, tiny 0.6B Qwen SLM for robust routing might turn out to be quite useful!
Sakana is using their version of TRINITY (obviously, theirs is not served from Elixir/Bumblebee/etc) in their forthcoming commercial AI framework. So, this is pretty cutting edge. Glad to stand on the shoulders of giants (both in the research community, as well as the ML community in Elixir)
I’m running this on a 5060 Ti 16GB, but it should work on most any GPU since it’s an SLM.
Right now it’s just saying what model it would call, so it’s not complete framework yet, just a proving ground.
ausimian
Just for fun, I ported this to work on my 24GB M4 MBP using the Emily backend. In doing so, during the export, I ran into a limitation of the current native
mlxlibraries - theirsvdfunctions have no ‘thin’ mode and always materialise the full matrix. For the Qwen embedder that turned out to be ~92GB of memory.I updated Emily to support this mode (in specific cases) directly via the Gram matrix, could do the one-time export in ~2s and was able to run the qwen router example.
If you are interested, the changes I made are here
polvalente
Nx provides a default implementation that doesn’t have any explicit evals, so that might be a way to avoid those materializations.
gtcode
Ah, that makes sense. Thanks both.
@ausimian, the Emily result is extremely useful to know. The fact that native MLX SVD materialized the full matrix, but the Gram-matrix path made the one-time export work on a 24GB M4, is exactly the kind of backend detail I would not have known to check.
@polvalente, your point about Nx’s default implementation is really helpful. If the default implementation avoids explicit evals, then it may avoid the immediate materialization issue seen in the native MLX SVD path.
This helped me separate two concerns: what TRINITY needs from Nx today, and what backend-neutral code can safely assume. In TRINITY, the export path only needs the reduced SVD outputs for reconstruction. As an application author, though, it is tempting to read:
as a “thin SVD” guarantee in both output shape and execution/memory behavior. The safer cross-backend interpretation seems to be:
full_matrices?: falseis an output-shape contract.Execution strategy and memory behavior are backend-dependent.
If that is the intended boundary, I have two practical questions:
If a backend’s native SVD is known to materialize full matrices anyway, should the preferred backend behavior be to notice
full_matrices?: falseand fall back to Nx’s default implementation when that is likely to behave better?Would the Nx team be open to a small docs PR clarifying that
full_matrices?controls SVD output shapes but does not guarantee a reduced-memory execution profile?For the current TRINITY path, this does not force an immediate change: it is still fine to call
Nx.LinAlg.svd/2and let Nx/the selected backend choose native SVD, default Nx lowering, or a backend-specific workaround. But the next rework is to make backend/runtime selection a profile concern instead of something CUDA-shaped in the application code, so I want to make sure I am relying on the right Nx contract before generalizing it. I do not want application code branching on Emily vs EXLA vs Torchx unless there is no better option.Side note: I did go down a rabbit hole thinking about how production code could ask a backend about these paths before starting a large SVD. I wrote that up here: Concrete proposal: SVD execution capabilities in Nx. It is probably too much architecture for this immediate issue, but I am leaving it as background in case backend-specific memory behavior becomes a larger Nx API discussion later.
polvalente
@gtcode any backend-specific issues should be addressed in the specific backends. For instance, EMLX, which is the original MLX-based backend for Nx, does not fall back to the binary backend at all, and relies on these default implementations from Nx for the most part when things aren’t supported by the native backend.
I think it doesn’t really make sense to document this specific issue in Nx because this can happen with literally any option, as different backends are limited by different constraints. Maybe a general comment on this would be welcome.
Also, feel free to test this out with EMLX in the github main version, as I’m actively working on improvements. v0.3 should be out soon.
polvalente
I gave a very quick pass through the proposal, and I think there’s something being overlooked there. It focuses on Nx.block, but the same concerns are pertinent to all Nx callbacks.
Also keep in mind that blocks and block structs can be defined by Nx users, not just Nx itself.
Additionally, even Nx.dot might have different execution profiles depending on the backend.
Finally, lowered vs eval’d is also something that’s not a concern for the backend, but for the compiler.
Do feel welcome to open an issue for us to discuss there and hopefully achieve a great feature, but I suggest rethinking the approach to something more general that includes all Nx callbacks.
gtcode
@ausimian — this is great, thank you. The ~92 GB → ~2 s number on the embedder is very helpful – I would not have been able to diagnose that failure mode from the CUDA side, and your Emily Gram path is the right idea.
I went through your two commits and the Emily PR. The diagnosis and the backend-level fix both look mostly right. The one thing I’d note is that since you opened that PR, the
trinity_coordinatorhas moved to a runtime-profile architecture (mix … --runtime-profile <name>), and the prompt-eval suite is now 37 fixed cases with snapshot + determinism + margin-floor checks rather than the original 12. So, I don’t want to merge the PR verbatim.config :nx, default_backend: Emily.Backendglobally and making"emily"a validXLA_TARGETwould blast through a few things on our end. But, I do want to land an explicit:emilyruntime profile that takes the same intent: Emily as the default backend when the operator chooses that lane, untouched otherwise.The blocker for me is that I don’t have Apple hardware. Would you be up for one validation pass against current
mainso I can shape the upstream integration with confidence? I think it’s two commands:What I’d love to see in the output:
status=completewith no OOM,route_hash(likely backend-sensitive) or one of the decision-stable fields (agent_id/role_id/token_count/transcript_hash) — that distinction tells me whether Emily needs its own snapshot lane or whether something deeper drifted.If you’d rather not run the full thing, even just the export half is useful. Either way, thanks again for doing this — turning it into a real Apple lane upstream is now basically a paperwork exercise on my side because of the work you did.
polvalente
@gtcode How can I run this on EMLX as well?
I was able to adapt it locally. I hit the same 92GB allocation, so I’ll see if I can add a better upstream Nx implementation for Thin SVD so that EXLA can also take advantage of it.
I do want to encourage people to reach out on the Nx issues tracker when they hit these types of limitations.
polvalente
I got a working version of thin SVD working here: refactor: better memory footprint for thin svd by polvalente · Pull Request #1753 · elixir-nx/nx · GitHub
It uses a smaller memory footprint which enabled EMLX to run the export without OOMs and without having to rewrite anything. This also benefits EXLA, which uses the same Nx base code.
PR should be merged soon, I just gotta fix some test assertions and documentation.
With just this change and replacing all EXLA.Backend cuda config with the equivalent EMLX, device: :gpu setup, I got the outputs:
and
edit: note that I didn’t configure emlx_axon, so the EMLX.Fast rewrites were not used
ausimian