<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Javier Cañete Arroyo</title>
    <description>The latest articles on DEV Community by Javier Cañete Arroyo (@javiercanete).</description>
    <link>https://dev.to/javiercanete</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4182754%2Ff6f2471d-2a27-473a-b881-bad6a6f42edb.jpg</url>
      <title>DEV Community: Javier Cañete Arroyo</title>
      <link>https://dev.to/javiercanete</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/javiercanete"/>
    <language>en</language>
    <item>
      <title>REA: a reverse engineering MCP for coding agents, tested with Ghidra and Electron</title>
      <dc:creator>Javier Cañete Arroyo</dc:creator>
      <pubDate>Wed, 21 Oct 2026 09:11:38 +0000</pubDate>
      <link>https://dev.to/javiercanete/rea-a-reverse-engineering-mcp-for-coding-agents-tested-with-ghidra-and-electron-36bf</link>
      <guid>https://dev.to/javiercanete/rea-a-reverse-engineering-mcp-for-coding-agents-tested-with-ghidra-and-electron-36bf</guid>
      <description>&lt;p&gt;&lt;strong&gt;REA (Reverse Engineer Anything) is a Model Context Protocol (MCP) server and command-line interface (CLI) that gives your coding agent reverse engineering tools: decompile binaries with Ghidra, Hopper or IDA, rebuild Electron apps, read .NET assemblies and observe websites.&lt;/strong&gt; It installs from the &lt;code&gt;rea-agents&lt;/code&gt; npm package, is MIT-licensed and is maintained by the &lt;code&gt;morluto&lt;/code&gt; GitHub account. I tested it on 18 October 2026 in an arm64 Docker container with &lt;code&gt;rea-agents&lt;/code&gt; 6.7.0, Node.js 24.21.0 and Ghidra 12.1.4. Here is what worked, what it weighs and what to check before you let it touch your agents’ configuration. The &lt;a href="https://jacar.es/rea-ingenieria-inversa-agentes-mcp/" rel="noopener noreferrer"&gt;Spanish version&lt;/a&gt; is also available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rea-agents&lt;/code&gt; 6.7.0 exposes 207 MCP tools and 6 prompts, and its CLI has 153 commands. The tool list is 3,158,352 bytes of JSON; the compact profile cuts it to 219,211.&lt;/li&gt;
&lt;li&gt;Static analysis of the Hyper 3.4.1 Electron app (a 14&amp;nbsp;MB &lt;code&gt;app.asar&lt;/code&gt;) walked 2236 files, found 18 inter-process communication (IPC) channels and 6 native add-ons, and produced a 253&amp;nbsp;MB JSON evidence file.&lt;/li&gt;
&lt;li&gt;With Ghidra 12.1.4 it decompiled a stripped C binary and recovered the validation logic with its constants: 31, 9973 and 4242.&lt;/li&gt;
&lt;li&gt;On Linux arm64, the official Ghidra ZIP ships no native decompiler: you build it with &lt;code&gt;gradlew buildNatives&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rea setup&lt;/code&gt; rewrites your agents’ configuration with backups, and &lt;code&gt;rea uninstall&lt;/code&gt; reverts it. Try it against a fake &lt;code&gt;HOME&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;The npm package is published from GitHub Actions with SLSA provenance, and &lt;code&gt;npm audit signatures&lt;/code&gt; verifies it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What REA is and what it analyzes
&lt;/h2&gt;

&lt;p&gt;REA is a tool layer, not a model. Its &lt;code&gt;setup&lt;/code&gt; can configure 24 clients, among them Claude Code, Codex, Cursor and Gemini CLI. Your agent calls REA over MCP, and REA runs the analysis on your machine and returns results with their evidence and limits. If the protocol is new to you, start with the &lt;a href="https://jacar.es/en/mcp-model-context-protocol-in-2026-the-complete-guide-for-engineering-teams/" rel="noopener noreferrer"&gt;MCP guide for 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://github.com/morluto/rea" rel="noopener noreferrer"&gt;project README&lt;/a&gt;, it covers these target types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native binaries&lt;/strong&gt;: pseudocode, assembly, strings, symbols and references, with Hopper, Ghidra or IDA as the engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript and Electron&lt;/strong&gt;: modules, imports, source maps, IPC channels and native add-ons, with no external engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET assemblies&lt;/strong&gt;: metadata and Common Intermediate Language (CIL) instructions, without loading the assembly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Websites&lt;/strong&gt;: structure, scripts and observed traffic, with a Chrome-family browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android APKs&lt;/strong&gt;: classes and methods decompiled with JADX, plus 15 other families (Go, Rust, WASM, SQLite, firmware, network captures)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static JavaScript and .NET analysis reads the files without running them. Runtime capture does run the target with your permissions, and the documentation flags it in each guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who maintains it and how much traction it has
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;morluto/rea&lt;/code&gt; repository was created on 14 April 2026 and belongs to a personal GitHub account. The &lt;code&gt;rea-agents&lt;/code&gt; package appeared on npm on 12 July 2026 and has 33 versions; 6.7.0 shipped on 18 October at 08:12 UTC. It was earlier published as &lt;code&gt;@morluto/rea&lt;/code&gt;, whose version 0.2.1 described itself as automation "for Hopper Disassembler".&lt;/p&gt;

&lt;p&gt;The popularity figures are unusual. The GitHub API returned 314,700 stars, 79,773 forks and 1213 open issues on 18 October 2026 at 11:28 UTC. The npm downloads API reported 307,234 downloads of &lt;code&gt;rea-agents&lt;/code&gt; for the week of 10 to 16 October and 346,782 for the previous 30 days. I am not interpreting them: they are what the two APIs returned that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install it without touching your real configuration
&lt;/h2&gt;

&lt;p&gt;Install it inside a container with its own &lt;code&gt;HOME&lt;/code&gt;, because &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; rewrite agent configuration files. You need Node.js 22.19 or later, 24.11 or later, or 26. On Linux, REA also requires the procps &lt;code&gt;ps&lt;/code&gt; to check who owns each process it launches, and the &lt;code&gt;node:24-slim&lt;/code&gt; image lacks it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; rea-lab &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/lab &lt;span class="nt"&gt;-w&lt;/span&gt; /home/lab &lt;span class="se"&gt;\&lt;/span&gt;
  node:24-slim &lt;span class="nb"&gt;sleep &lt;/span&gt;infinity
docker &lt;span class="nb"&gt;exec &lt;/span&gt;rea-lab bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'apt-get update -qq &amp;amp;&amp;amp; \
  apt-get install -y -qq procps curl jq'&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;rea-lab npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; rea-agents@6.7.0
docker &lt;span class="nb"&gt;exec &lt;/span&gt;rea-lab rea &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command prints &lt;code&gt;6.7.0&lt;/code&gt;. The installed package takes 222&amp;nbsp;MB with its 158 dependencies, because it bundles Playwright, a terminal emulator and the MCP SDKs. &lt;code&gt;rea doctor&lt;/code&gt; lists what is missing: in a clean container every native engine (Hopper, Ghidra, IDA, JADX, Kuna) shows up as &lt;code&gt;missing_analysis_engine&lt;/code&gt;, and JavaScript analysis works regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to analyze an Electron app without running it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;analyze-javascript-application&lt;/code&gt; reads an &lt;code&gt;app.asar&lt;/code&gt; (the archive Electron packs the app’s code into) or an extracted folder and rebuilds its module graph. I tested it with &lt;a href="https://github.com/vercel/hyper" rel="noopener noreferrer"&gt;Hyper&lt;/a&gt; 3.4.1, Vercel’s MIT-licensed terminal, downloading its arm64 &lt;code&gt;.deb&lt;/code&gt; inside the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /home/lab &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;targets &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;targets
curl &lt;span class="nt"&gt;-sSLO&lt;/span&gt; https://github.com/vercel/hyper/releases/download/&lt;span class="se"&gt;\&lt;/span&gt;
v3.4.1/hyper_3.4.1_arm64.deb
dpkg-deb &lt;span class="nt"&gt;-x&lt;/span&gt; hyper_3.4.1_arm64.deb hyper
rea analyze-javascript-application &lt;span class="se"&gt;\&lt;/span&gt;
  /home/lab/targets/hyper/opt/Hyper/resources/app.asar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /home/lab/hyper.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command exited with code 0 after 4 minutes, with a system load average below 3 during the measurement. The result is a 253&amp;nbsp;MB JSON file: a graph with 10,552 nodes and 14,909 relationships, of which 4853 are imports and 15 are IPC sends. Among the findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;18 IPC channel nodes and 5 handlers, such as &lt;code&gt;electron-store-get-data&lt;/code&gt; in the main process&lt;/li&gt;
&lt;li&gt;14 IPC operations with a dynamic channel name that REA leaves unresolved instead of guessing&lt;/li&gt;
&lt;li&gt;6 native add-ons, including &lt;code&gt;node-pty/bin/linux-x64-107/node-pty.node&lt;/code&gt; and three &lt;code&gt;win32&lt;/code&gt; binaries of &lt;code&gt;native-reg&lt;/code&gt; inside a Linux arm64 package&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is also honest about its gaps. 1994 files, nearly all in &lt;code&gt;node_modules&lt;/code&gt;, carry a &lt;code&gt;resource_constraint&lt;/code&gt; mark because their expanded relationships did not fit the memory limit. The evidence states it instead of dropping them.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to summarize a 253 MB evidence file
&lt;/h3&gt;

&lt;p&gt;An agent cannot read 253&amp;nbsp;MB of context, so REA projects views over the saved evidence. The &lt;a href="https://github.com/morluto/rea/blob/main/docs/cli.md" rel="noopener noreferrer"&gt;CLI guide&lt;/a&gt; suggests wrapping it with &lt;code&gt;jq&lt;/code&gt; and asking for the &lt;code&gt;summary&lt;/code&gt; view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'{source: {kind: "inline", evidence: .},
        view: {kind: "summary"}}'&lt;/span&gt; hyper.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hyper-view.json
rea inspect-analysis-view hyper-view.json &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hyper-summary.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The summary is 22,843 bytes: one window, 22 IPC operations, 7 literal channels and no preload script. That is the piece you would hand to the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Electron fuses are enabled
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;inspect-electron-fuses&lt;/code&gt; reads the "fuses" Electron writes into the executable, without launching it. In the 147&amp;nbsp;MB &lt;code&gt;hyper&lt;/code&gt; binary, &lt;code&gt;RunAsNode&lt;/code&gt;, &lt;code&gt;EnableNodeOptionsEnvironmentVariable&lt;/code&gt; and &lt;code&gt;EnableNodeCliInspectArguments&lt;/code&gt; were enabled, and &lt;code&gt;EnableEmbeddedAsarIntegrityValidation&lt;/code&gt; and &lt;code&gt;OnlyLoadAppFromAsar&lt;/code&gt; were disabled. That is useful when auditing an app: with &lt;code&gt;RunAsNode&lt;/code&gt; on, the executable agrees to run as a plain Node.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to decompile a native binary with Ghidra on Linux arm64
&lt;/h2&gt;

&lt;p&gt;REA accepts Ghidra 12.1.x with a Java Development Kit (JDK) 21 or later. On arm64 there is a prior step the documentation warns about: the official Ghidra 12.1.4 ZIP (570&amp;nbsp;MB) ships the native decompiler for &lt;code&gt;linux_x86_64&lt;/code&gt; and &lt;code&gt;win_x86_64&lt;/code&gt;, but not for &lt;code&gt;linux_arm_64&lt;/code&gt;. I built it with the Gradle wrapper the distribution includes, after installing &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;g++&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;bison&lt;/code&gt; and &lt;code&gt;flex&lt;/code&gt; and a Temurin JDK 21:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/jdk21 &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/jdk21/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/ghidra_12.1.4_PUBLIC/support/gradle
./gradlew buildNatives
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GHIDRA_INSTALL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/ghidra_12.1.4_PUBLIC
rea doctor | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A3&lt;/span&gt; ghidra-native_decompiler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build ended with &lt;code&gt;BUILD SUCCESSFUL&lt;/code&gt; and &lt;code&gt;rea doctor&lt;/code&gt; turned green: "decompile reports elf arm64, which matches linux/arm64". As a target I compiled a 17-line C program that validates a key with a checksum, and stripped its symbols with &lt;code&gt;strip&lt;/code&gt;. The resulting binary is 67,616 bytes. Then I asked REA to find a string and follow its references:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rea trace licencia.stripped &lt;span class="s2"&gt;"clave valida"&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ghidra &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; json | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'.normalized_result.references[0]'&lt;/span&gt;
rea decompile licencia.stripped 0x1007d4 &lt;span class="nt"&gt;--provider&lt;/span&gt; ghidra &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;trace&lt;/code&gt; found the string at &lt;code&gt;0x1008c8&lt;/code&gt;, referenced from &lt;code&gt;0x100880&lt;/code&gt; inside the 192-byte function &lt;code&gt;FUN_001007d4&lt;/code&gt;. &lt;code&gt;decompile&lt;/code&gt; returned this pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;    &lt;span class="n"&gt;iVar4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;&lt;/span&gt; &lt;span class="n"&gt;__s&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pbVar1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;__s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;iVar4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;iVar4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mh"&gt;0x1f&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;__s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mh"&gt;0x26f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iVar4&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x1092&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clave valida"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;uVar3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constants match my source code: &lt;code&gt;0x1f&lt;/code&gt; is 31, &lt;code&gt;0x26f5&lt;/code&gt; is 9973 and &lt;code&gt;0x1092&lt;/code&gt; is 4242, with the helper function already inlined into &lt;code&gt;main&lt;/code&gt; by the compiler. Each CLI command is a new process that imports the binary into a temporary Ghidra project and deletes it on close. To chain queries, use &lt;code&gt;--snapshot&lt;/code&gt; or the MCP server, which keeps the session open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which tools the MCP server exposes
&lt;/h2&gt;

&lt;p&gt;You start the server with &lt;code&gt;rea mcp&lt;/code&gt; and it speaks MCP over standard input and output. I queried it with a 25-line Node.js client that sends &lt;code&gt;initialize&lt;/code&gt; and pages through &lt;code&gt;tools/list&lt;/code&gt;. It answered as &lt;code&gt;rea&lt;/code&gt; 6.7.0, protocol &lt;code&gt;2025-06-18&lt;/code&gt;, with 207 tools and 6 prompts: &lt;code&gt;investigate_feature&lt;/code&gt;, &lt;code&gt;compare_application_versions&lt;/code&gt;, &lt;code&gt;verify_reconstruction&lt;/code&gt;, &lt;code&gt;trace_crash&lt;/code&gt;, &lt;code&gt;audit_residual_unknowns&lt;/code&gt; and &lt;code&gt;prepare_bounded_process_capture&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The tools fall into families:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native session&lt;/strong&gt;: &lt;code&gt;open_binary&lt;/code&gt;, &lt;code&gt;close_binary&lt;/code&gt;, &lt;code&gt;procedure_pseudo_code&lt;/code&gt;, &lt;code&gt;procedure_callers&lt;/code&gt;, &lt;code&gt;xrefs&lt;/code&gt;, &lt;code&gt;search_strings&lt;/code&gt;, &lt;code&gt;batch_decompile&lt;/code&gt;, &lt;code&gt;get_call_graph&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level workflows&lt;/strong&gt;: &lt;code&gt;binary_overview&lt;/code&gt;, &lt;code&gt;analyze_function&lt;/code&gt;, &lt;code&gt;trace_feature&lt;/code&gt;, &lt;code&gt;trace_call_path&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript and Electron&lt;/strong&gt;: &lt;code&gt;analyze_javascript_application&lt;/code&gt;, &lt;code&gt;trace_application_feature&lt;/code&gt;, &lt;code&gt;inspect_electron_fuses&lt;/code&gt;, &lt;code&gt;capture_electron_scenario&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web&lt;/strong&gt;: &lt;code&gt;inspect_web_page&lt;/code&gt;, &lt;code&gt;analyze_web_bundle&lt;/code&gt;, &lt;code&gt;capture_browser_scenario&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android and debugging&lt;/strong&gt;: 26 &lt;code&gt;adb&lt;/code&gt; tools, plus Frida, GDB and Rizin sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catalog is heavy, and that costs context in every agent session. The MCP contracts documentation records that kimi-cli rejected requests with the full catalog and offers two variables: &lt;code&gt;REA_MCP_INPUT_SCHEMA_PROFILE=compact&lt;/code&gt; and &lt;code&gt;REA_MCP_OUTPUT_SCHEMA_PROFILE=off&lt;/code&gt;. With both, the same &lt;code&gt;tools/list&lt;/code&gt; went from 3.16&amp;nbsp;MB to 0.22&amp;nbsp;MB:&lt;/p&gt;

&lt;p&gt;Size of the REA 6.7.0 tools/list response bytes · lower is better&lt;/p&gt;

&lt;p&gt;Full (default)3158352Compact input, output off219211Show the data table&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Schema profile&lt;/th&gt;
&lt;th&gt;bytes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full (default)&lt;/td&gt;
&lt;td&gt;3158352&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact input, output off&lt;/td&gt;
&lt;td&gt;219211&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;207 tools in both cases. Measured on 18 October 2026 with rea-agents 6.7.0 in arm64 Docker, serializing the tools array to JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rea setup changes in your agents’ configuration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rea setup&lt;/code&gt; registers the MCP server in the agents you pick and installs an investigation skill. I ran it in the container against a fake &lt;code&gt;HOME&lt;/code&gt; with a minimal &lt;code&gt;~/.claude.json&lt;/code&gt; and &lt;code&gt;~/.codex/config.toml&lt;/code&gt;. First with &lt;code&gt;--dry-run&lt;/code&gt;, which only returns the plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/fake
rea setup &lt;span class="nt"&gt;--client&lt;/span&gt; claude_code &lt;span class="nt"&gt;--client&lt;/span&gt; codex &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json
rea setup &lt;span class="nt"&gt;--client&lt;/span&gt; claude_code &lt;span class="nt"&gt;--client&lt;/span&gt; codex &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan announced four actions, and the run did exactly that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;~/.claude.json&lt;/code&gt;&lt;/strong&gt;: adds &lt;code&gt;"rea": {"command": "/usr/local/bin/rea", "args": ["mcp"]}&lt;/code&gt; under &lt;code&gt;mcpServers&lt;/code&gt;, keeps the existing entry and reformats the file with indentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;&lt;/strong&gt;: adds a &lt;code&gt;[mcp_servers.rea]&lt;/code&gt; table with &lt;code&gt;startup_timeout_sec = 30&lt;/code&gt; and &lt;code&gt;tool_timeout_sec = 450&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups&lt;/strong&gt;: leaves &lt;code&gt;.claude.json.rea.backup&lt;/code&gt; and &lt;code&gt;config.toml.rea.backup&lt;/code&gt; next to each file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill&lt;/strong&gt;: copies &lt;code&gt;reverse-engineer-anything&lt;/code&gt; (88&amp;nbsp;KB, 7 files) into &lt;code&gt;~/.agents/skills/&lt;/code&gt; and &lt;code&gt;~/.claude/skills/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;rea uninstall&lt;/code&gt; with no options removed both registrations and the skill, and also left a &lt;code&gt;.recovery&lt;/code&gt; copy of the previous state. If you use Claude Code every day, first check which &lt;a href="https://jacar.es/en/how-to-install-a-local-mcp-server-for-your-editor/" rel="noopener noreferrer"&gt;MCP servers your editor already registers&lt;/a&gt;, because setup adds one with 207 tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify the package is the official one
&lt;/h2&gt;

&lt;p&gt;The README warns about copies: "A similar repository name or copied README does not establish that a download is official". There are three official sources: the &lt;code&gt;morluto/rea&lt;/code&gt; repository, the &lt;code&gt;rea.tools&lt;/code&gt; site and the &lt;code&gt;rea-agents&lt;/code&gt; npm package. The package is published from GitHub Actions with an OIDC trusted publisher and an SLSA (Supply-chain Levels for Software Artifacts) provenance attestation, and npm checks both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;verify &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;verify &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt; rea-agents@6.7.0
npm audit signatures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output was "174 packages have verified registry signatures" and "36 packages have verified attestations". The &lt;code&gt;rea-agents@6.7.0&lt;/code&gt; attestation points at the &lt;code&gt;.github/workflows/release.yml&lt;/code&gt; workflow of the &lt;code&gt;https://github.com/morluto/rea&lt;/code&gt; repository, branch &lt;code&gt;release/6.7.0&lt;/code&gt;. Also pin the exact version in your agent’s MCP registration instead of &lt;code&gt;@latest&lt;/code&gt;: it is the defence against &lt;a href="https://jacar.es/en/supply-chain-attacks-lessons-from-2023/" rel="noopener noreferrer"&gt;supply chain attacks&lt;/a&gt; that arrive with an update. The package declares no &lt;code&gt;postinstall&lt;/code&gt; scripts.&lt;/p&gt;

&lt;p&gt;The project itself states the limit of its isolation. Its &lt;a href="https://github.com/morluto/rea/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;security policy&lt;/a&gt; reads: "This is not a sandbox and does not protect against malicious processes already running as the same operating-system user." Opening an untrusted binary hands its analysis to Ghidra or Hopper with your permissions, so do it in a container or a virtual machine, as with any &lt;a href="https://jacar.es/en/llm-agent-security-the-new-class-of-threats/" rel="noopener noreferrer"&gt;agent that has tool access&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What EU law allows for reverse engineering
&lt;/h2&gt;

&lt;p&gt;Decompiling a program is not illegal in itself in the European Union, but it comes with conditions. &lt;a href="https://eur-lex.europa.eu/eli/dir/2009/24/oj/eng" rel="noopener noreferrer"&gt;Directive 2009/24/EC&lt;/a&gt; sets two exceptions that need no permission from the rightholder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Article 5(3)&lt;/strong&gt;: whoever has the right to use a copy may "observe, study or test the functioning of the program in order to determine the ideas and principles" while loading, running or storing it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article 6&lt;/strong&gt;: they may reproduce and translate the code when it is "indispensable to obtain the information necessary to achieve the interoperability of an independently created" program, limited to the parts needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Article 6(2) forbids using what you obtain for another purpose or to build a program that copies the original’s expression. Article 8 makes licence clauses contrary to those exceptions void. In Spain, the &lt;a href="https://www.boe.es/buscar/act.php?id=BOE-A-1996-8930" rel="noopener noreferrer"&gt;Intellectual Property Act&lt;/a&gt; carries them in Article 100, sections 3, 5 and 6. With an end-user licence agreement (EULA) that forbids decompiling, interoperability is still covered in the EU; every other use depends on what the licence says.&lt;/p&gt;

&lt;p&gt;REA takes that line in its &lt;code&gt;DISCLAIMER.md&lt;/code&gt;: the project will not help bypass copy protection (DRM), licence checks or anti-cheat systems, and it requires legitimate licences for IDA Pro, Hopper or JEB.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it compares with GhidraMCP, IDA Pro MCP and radare2-mcp
&lt;/h2&gt;

&lt;p&gt;REA is not the first bridge between a disassembler and an agent; what changes is the scope. The other three projects connect a single engine, while REA orchestrates six engines under a common evidence contract:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;th&gt;Licence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REA&lt;/td&gt;
&lt;td&gt;Ghidra, Hopper, IDA, Kuna, JADX, Rizin&lt;/td&gt;
&lt;td&gt;Native, Electron, .NET, Android, web, network captures&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/LaurieWired/GhidraMCP" rel="noopener noreferrer"&gt;GhidraMCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Ghidra with a plugin&lt;/td&gt;
&lt;td&gt;Native binaries inside Ghidra&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/mrexodia/ida-pro-mcp" rel="noopener noreferrer"&gt;IDA Pro MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;IDA Pro 8.3 or later&lt;/td&gt;
&lt;td&gt;Native binaries; its author now recommends the official Hex-Rays server&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/radareorg/radare2-mcp" rel="noopener noreferrer"&gt;radare2-mcp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;radare2, written in C&lt;/td&gt;
&lt;td&gt;Native binaries, with a read-only mode&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you only decompile binaries and already live in IDA or Ghidra, a single-engine bridge adds less surface. REA pays off when the question crosses layers, such as following a feature of an Electron app from the renderer down to a native add-on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does REA upload my binaries to a server?
&lt;/h3&gt;

&lt;p&gt;No. The analysis runs on your machine, according to the README. What does leave it is whatever REA returns to your agent, which sends it to its model provider under that provider’s own data policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need Hopper, Ghidra or IDA?
&lt;/h3&gt;

&lt;p&gt;Only for native binaries. JavaScript, Electron and .NET analysis worked in my test with no engine at all. For Ghidra on Linux arm64, plan on building the native decompiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it legal to use REA on a commercial app?
&lt;/h3&gt;

&lt;p&gt;It depends on the use and your jurisdiction. In the EU you may study how it works and decompile what interoperability needs even if the licence says otherwise; copying its code or bypassing a protection is not covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;REA delivers on the two parts I tested. It rebuilt the structure of a real Electron app without running it and, with Ghidra, returned the exact constants of a stripped binary. In exchange it weighs 222&amp;nbsp;MB, adds 207 tools to your agent’s context and ships a new version almost every day. If you want to try it, do it as here: in a container, with the version pinned, &lt;code&gt;npm audit signatures&lt;/code&gt; before installing and a &lt;code&gt;--dry-run&lt;/code&gt; before letting &lt;code&gt;setup&lt;/code&gt; touch your configuration.&lt;/p&gt;

&lt;p&gt;Sources: [1] &lt;a href="https://github.com/morluto/rea" rel="noopener noreferrer"&gt;morluto/rea repository on GitHub&lt;/a&gt;, [2] &lt;a href="https://github.com/morluto/rea/blob/main/docs/installation.md" rel="noopener noreferrer"&gt;REA installation and setup&lt;/a&gt;, [3] &lt;a href="https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md" rel="noopener noreferrer"&gt;REA MCP contracts&lt;/a&gt;, [4] &lt;a href="https://github.com/morluto/rea/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;REA security policy&lt;/a&gt;, [5] &lt;a href="https://www.npmjs.com/package/rea-agents" rel="noopener noreferrer"&gt;rea-agents on npm&lt;/a&gt;, [6] &lt;a href="https://rea.tools/showcase/" rel="noopener noreferrer"&gt;Case studies on rea.tools&lt;/a&gt;, [7] &lt;a href="https://eur-lex.europa.eu/eli/dir/2009/24/oj/eng" rel="noopener noreferrer"&gt;Directive 2009/24/EC on EUR-Lex&lt;/a&gt;, [8] &lt;a href="https://www.boe.es/buscar/act.php?id=BOE-A-1996-8930" rel="noopener noreferrer"&gt;Spanish Intellectual Property Act on BOE&lt;/a&gt;, [9] &lt;a href="https://github.com/NationalSecurityAgency/ghidra/releases/tag/Ghidra_12.1.4_build" rel="noopener noreferrer"&gt;Ghidra 12.1.4 on GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://jacar.es/en/rea-reverse-engineering-mcp-coding-agents/" rel="noopener noreferrer"&gt;jacar.es&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
    <item>
      <title>What Obscura is, the Rust headless browser for AI agents</title>
      <dc:creator>Javier Cañete Arroyo</dc:creator>
      <pubDate>Sun, 18 Oct 2026 20:52:39 +0000</pubDate>
      <link>https://dev.to/javiercanete/what-obscura-is-the-rust-headless-browser-for-ai-agents-49fc</link>
      <guid>https://dev.to/javiercanete/what-obscura-is-the-rust-headless-browser-for-ai-agents-49fc</guid>
      <description>&lt;p&gt;Obscura landed on GitHub in April 2026 and has collected 24,624 stars in five months. Cloudflare’s engineering team credits it as the starting point for their own agent browser, and the project already advertises a managed version. Here is what it actually does, what the numbers looked like when I ran it on my own machine, and where it genuinely replaces headless Chrome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Obscura is a headless browser engine written in Rust under an Apache-2.0 licence. It runs JavaScript on V8 and speaks the Chrome DevTools Protocol.&lt;/li&gt;
&lt;li&gt;Playwright and Puppeteer connect without a single code change: point &lt;code&gt;connectOverCDP&lt;/code&gt; at the port &lt;code&gt;obscura serve&lt;/code&gt; opens.&lt;/li&gt;
&lt;li&gt;Its reproducible advantage is memory. In my tests it used about 38 MB per session against 532 MB for headless Chromium on the same page.&lt;/li&gt;
&lt;li&gt;The speed advantage depends on the page: obvious on static HTML, absent on a real site where network latency dominates.&lt;/li&gt;
&lt;li&gt;Since version 0.2.0 (August 2026) it renders and captures without Chromium, but it does not match Chromium yet, and I hit that on the first page I gave it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Obscura is and what problem it solves
&lt;/h2&gt;

&lt;p&gt;Obscura is a browser engine with no graphical interface, written from scratch in Rust. It is not a Chromium fork or a WebKit patch. It embeds V8 to run the page’s JavaScript, builds its own DOM, and exposes the Chrome DevTools Protocol so existing automation tools treat it as if it were Chrome. The official documentation defines it this way: &lt;a href="https://docs.obscura.sh/" rel="noopener noreferrer"&gt;"Obscura is an open-source headless browser engine written in Rust. It runs JavaScript via V8, speaks the Chrome DevTools Protocol, and works as a drop-in replacement for headless Chrome with Puppeteer and Playwright"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The problem it attacks is cost, not capability. Headless Chrome automates a page perfectly well, but it was designed for one person looking at one tab.&lt;/p&gt;

&lt;p&gt;When a swarm of agents opens thousands of concurrent sessions, every process drags along a compositor, a rendering stack and dozens of subsystems no agent ever touches. You pay for that in gigabytes of memory and in seconds of startup. Obscura’s proposal is to keep only the part that matters for reading data: run the JavaScript, build the DOM, hand it back.&lt;/p&gt;

&lt;p&gt;The Cloudflare connection is the strongest signal that the idea holds up. In the Kitesurf announcement, describing their agent browser built on Workers, the team writes: &lt;a href="https://blog.cloudflare.com/kitesurf/" rel="noopener noreferrer"&gt;"We got the initial inspiration from obscura, a headless engine written in Rust for AI automation that has ‘no Chrome, no Node.js, no dependencies’"&lt;/a&gt;. Cloudflare ported Obscura to Workers as a first proof of concept in May 2026 and built the product from there. Kitesurf is still a Cloudflare service in free beta; Obscura is the engine you can download today.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works inside
&lt;/h2&gt;

&lt;p&gt;The binary bundles three pieces. The first is V8, the same JavaScript engine Chrome uses, embedded directly in the Rust process. The second is its own implementation of the DOM and the web APIs a modern page needs: &lt;code&gt;fetch&lt;/code&gt;, XHR, observers, Shadow DOM, custom elements, WebCrypto and the rest. The third is the CDP server, which translates Puppeteer or Playwright calls into operations on that DOM.&lt;/p&gt;

&lt;p&gt;Since version 0.2.0, released on 8 August 2026 with 449 accumulated commits, there is a fourth piece. It is a layout and paint engine also written in Rust, which allows screenshots, screencasting and PDF export without starting Chromium. Version 0.2.1, from 23 August, added dedicated V8 realms for child iframes.&lt;/p&gt;

&lt;p&gt;Installing it needs neither Node.js nor Chrome. The project publishes binaries for Linux, macOS and Windows, in variants with and without rendering and with and without stealth mode, plus an &lt;a href="https://hub.docker.com/r/h4ckf0r0day/obscura" rel="noopener noreferrer"&gt;official image on Docker Hub&lt;/a&gt; with 45,789 pulls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/h4ckf0r0day/obscura/releases/latest/download/obscura-aarch64-linux.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf obscura-aarch64-linux.tar.gz

./obscura fetch https://example.com &lt;span class="nt"&gt;--eval&lt;/span&gt; &lt;span class="s2"&gt;"document.title"&lt;/span&gt;
./obscura fetch https://example.com &lt;span class="nt"&gt;--dump&lt;/span&gt; markdown

./obscura serve &lt;span class="nt"&gt;--port&lt;/span&gt; 9222 &lt;span class="nt"&gt;--stealth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stealth mode is a separate build variant. According to the project README it randomises the GPU, screen, canvas, audio and battery fingerprint on every session, leaves &lt;code&gt;navigator.webdriver&lt;/code&gt; undefined and blocks 3,520 tracking domains. Those are the project’s own figures, not a third party’s.&lt;/p&gt;

&lt;p&gt;It also ships an embedded Model Context Protocol server with fourteen browser tools an assistant can call directly. If you have built your own servers before, the &lt;a href="https://jacar.es/en/mcp-model-context-protocol-in-2026-the-complete-guide-for-engineering-teams/" rel="noopener noreferrer"&gt;complete MCP guide&lt;/a&gt; has the context for that protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened when I ran it on my own machine
&lt;/h2&gt;

&lt;p&gt;I downloaded the &lt;code&gt;aarch64-linux&lt;/code&gt; variant of v0.2.1 and ran it in an ARM64 Linux container on 4 September 2026, measuring wall time and peak resident memory across the process tree. Facing it I put headless Chromium driven by Playwright, on the same URLs and the same network. Three passes per combination.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page&lt;/th&gt;
&lt;th&gt;Obscura v0.2.1&lt;/th&gt;
&lt;th&gt;Headless Chromium&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;example.com&lt;/td&gt;
&lt;td&gt;92 ms, 13 MB&lt;/td&gt;
&lt;td&gt;237 ms, 510 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;news.ycombinator.com&lt;/td&gt;
&lt;td&gt;1,550 ms, 38 MB&lt;/td&gt;
&lt;td&gt;1,430 ms, 532 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest reading of those numbers has two halves. The memory advantage is huge and it holds: roughly thirteen times less on the real page.&lt;/p&gt;

&lt;p&gt;The speed advantage evaporates as soon as the page stops being a static 500-byte file. On Hacker News, Chromium was even slightly faster, because what dominates the median is the network, not the engine. Dropping &lt;code&gt;--wait&lt;/code&gt; to zero changed nothing.&lt;/p&gt;

&lt;p&gt;The project’s own benchmark repository reaches the same conclusion with more data. Over a corpus of 98 real public pages, Obscura rendered 94 and headless Chrome rendered 85. Median memory was 64.2 MB against 201.2 MB, but median latency was 5.2 seconds against Chrome’s 2.1 seconds. That is how the cards actually fall.&lt;/p&gt;

&lt;p&gt;Playwright compatibility turned out to be literal. I started &lt;code&gt;obscura serve --port 9333&lt;/code&gt; and connected with &lt;code&gt;chromium.connectOverCDP('ws://127.0.0.1:9333')&lt;/code&gt; without touching anything else: navigation, &lt;code&gt;page.title()&lt;/code&gt; and &lt;code&gt;page.evaluate()&lt;/code&gt; all answered in 100 milliseconds.&lt;/p&gt;

&lt;p&gt;Then I tried the new rendering path on the front page of a Tailwind-based WordPress site. The capture took 587 milliseconds and produced a valid PNG, but with clear differences against Chromium.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkd6mbyoz39n805oh3so.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkd6mbyoz39n805oh3so.webp" alt="Side-by-side comparison of the same page rendered by Obscura and by headless Chromium, where Obscura paints tooltips visible that the CSS declares with zero opacity." width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most concrete difference: the theme declares its navigation-bar labels as &lt;code&gt;::after&lt;/code&gt; pseudo-elements with &lt;code&gt;content: attr(data-tooltip)&lt;/code&gt; and &lt;code&gt;opacity: 0&lt;/code&gt;, visible only on hover. Obscura painted the pseudo-element but ignored the opacity, so the header came out covered in overlapping labels. The search panel also rendered open and the cookie notice was missing. This is exactly the class of gap the project declares in its release notes, found on the first page I fed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it compares with headless Chrome and Lightpanda
&lt;/h2&gt;

&lt;p&gt;There are four families of solutions and it is worth keeping them apart, because they solve different things. GitHub figures from 4 September 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Licence&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;What it solves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Obscura&lt;/td&gt;
&lt;td&gt;Own, in Rust&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;24,624&lt;/td&gt;
&lt;td&gt;Memory cost per session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/lightpanda-io/browser" rel="noopener noreferrer"&gt;Lightpanda&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Own, in Zig&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;34,414&lt;/td&gt;
&lt;td&gt;The same, with more history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headless Chrome with Playwright&lt;/td&gt;
&lt;td&gt;Chromium&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;95,604&lt;/td&gt;
&lt;td&gt;Full rendering fidelity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steel Browser or Browserless&lt;/td&gt;
&lt;td&gt;Wrapped Chromium&lt;/td&gt;
&lt;td&gt;Apache-2.0 or custom&lt;/td&gt;
&lt;td&gt;7,591 and 13,658&lt;/td&gt;
&lt;td&gt;Running the infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lightpanda is the direct rival: same premise of a new engine, written in Zig, two years older and with more stars. The difference that ends up deciding it is legal rather than technical.&lt;/p&gt;

&lt;p&gt;Lightpanda uses AGPL-3.0, which propagates to any service that exposes it over the network. Obscura uses Apache-2.0, which does not. If you plan to embed it in a closed product, that line in the &lt;code&gt;LICENSE&lt;/code&gt; file weighs more than any benchmark.&lt;/p&gt;

&lt;p&gt;Steel Browser and Browserless do not compete with Obscura: they wrap Chromium with queues, sessions and an API. They solve operations, not cost per session. Browserless is also not open source, it is free only for non-commercial use.&lt;/p&gt;

&lt;p&gt;Then there is the agent ecosystem that already drives a browser. If what you want is an agent that browses for you, &lt;a href="https://jacar.es/en/browser-use-agents-that-browse/" rel="noopener noreferrer"&gt;Browser Use&lt;/a&gt; works one layer above; if what you want is clean web data, &lt;a href="https://jacar.es/en/firecrawl-web-data-for-agents/" rel="noopener noreferrer"&gt;Firecrawl&lt;/a&gt; does the extraction for you. Obscura lives underneath all of them, as an engine replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do well yet
&lt;/h2&gt;

&lt;p&gt;The rendering path is less than a month old and does not reach Chromium parity. The v0.2.0 notes say so plainly: long-tail CSS, some web APIs, native media playback and GPU compositor effects may still differ. My test confirmed it on the first try. The PDF output is also raster-backed, so the text cannot be selected.&lt;/p&gt;

&lt;p&gt;Conformance is not complete either. The benchmark repository publishes an 83.3% subtest pass rate on the Core tier of the Web Platform Tests (318,916 of 382,891) and 60.0% on the full suite. That 60% is not a hidden defect, it is the consequence of deliberately not implementing whole layers of the platform, and the project documents it.&lt;/p&gt;

&lt;p&gt;The README figures age. It advertises a 70 MB binary, but the ARM64 build with rendering that I downloaded takes 100 MB unpacked, plus another 92 MB for the worker process. The Docker image went from 53.2 MB in 0.1.11 to 87.1 MB in 0.2.1. None of this is serious, but measure on your own platform before quoting the table.&lt;/p&gt;

&lt;p&gt;The project is young and highly concentrated. It was created on 13 April 2026 and, of the twenty contributors the GitHub API lists, a single one accounts for 733 commits. Stealth mode reduces automation signals, but it does not prevent blocks based on IP reputation or behaviour patterns. And Obscura Cloud, the managed version, is still a waiting list rather than a product you can buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it is worth it and when it is not
&lt;/h2&gt;

&lt;p&gt;It is worth it when your bill is a memory bill. If you run hundreds or thousands of concurrent sessions extracting data, going from 500 MB to 38 MB per session changes machine sizing in a way no code optimisation can match. It is also worth it when you want to package a browser inside a small image with no Node.js and no Chrome, or when you need genuinely clean, reproducible sessions.&lt;/p&gt;

&lt;p&gt;It is not worth it if your workload is a few dozen pages and already works. The per-page latency gain on real sites is zero or negative, and you would be swapping a mature engine for a five-month-old one. It is also not worth it if you depend on visual fidelity: visual regression testing, client-facing captures, anything where the pixel matters, all of that remains Chromium territory. And if your problem is getting blocked, the engine is not the lever; proxies and request pacing are.&lt;/p&gt;

&lt;p&gt;My practical advice: run it alongside. Start &lt;code&gt;obscura serve&lt;/code&gt; next to your current headless Chrome, point a percentage of traffic at it with &lt;code&gt;connectOverCDP&lt;/code&gt; and compare results on your own target pages for a week. Migration costs one environment variable, so the experiment is cheap. If you are curious about the language underneath, &lt;a href="https://jacar.es/en/rust-for-backend-axum-and-tokio-in-real-services/" rel="noopener noreferrer"&gt;Rust for backend work with Axum and Tokio&lt;/a&gt; has the context for why this class of project gets written that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Obscura replace Playwright or Puppeteer?
&lt;/h3&gt;

&lt;p&gt;No, it complements them. Playwright and Puppeteer are the libraries you write the automation script in; Obscura is the browser that script drives. You keep the same Playwright code and only change the connection target, from a Chromium launched by the library to an &lt;code&gt;obscura serve&lt;/code&gt; listening over CDP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Obscura in a closed commercial product?
&lt;/h3&gt;

&lt;p&gt;Yes. The licence is Apache-2.0, which permits commercial use and integration into proprietary software with no obligation to publish your code. That is the most practical difference against Lightpanda, which uses AGPL-3.0 and requires releasing the source of any networked service that embeds it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Obscura good enough for reliable screenshots?
&lt;/h3&gt;

&lt;p&gt;Only with caution. Since v0.2.0 it does screenshots, screencasting and PDF without Chromium, and they work. But the paint engine is new and the project itself warns that it does not match Chromium: in my test it ignored an opacity rule on a pseudo-element and rendered the header wrong. For visual regression testing, stay on Chromium.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Obscura solves a specific problem and solves it well: the memory cost of giving every agent its own browser. Thirteen times less RAM per session is a figure that changes architecture decisions, and CDP compatibility makes trying it almost free. What you should not expect is the speed gain the home page advertises, because it does not appear on real pages. Nor rendering fidelity equal to Chromium, because the project itself warns that it is not there yet.&lt;/p&gt;

&lt;p&gt;That Cloudflare started from this code to build Kitesurf says a good deal about the quality of the idea. Five months of life says a good deal about the risk. Measure it against your own pages before moving anything.&lt;/p&gt;

&lt;p&gt;Sources: [1] &lt;a href="https://github.com/h4ckf0r0day/obscura" rel="noopener noreferrer"&gt;Obscura, official GitHub repository&lt;/a&gt;, [2] &lt;a href="https://github.com/h4ckf0r0day/obscura/releases" rel="noopener noreferrer"&gt;Obscura, published release notes&lt;/a&gt;, [3] &lt;a href="https://github.com/h4ckf0r0day/obscura-benchmark" rel="noopener noreferrer"&gt;obscura-benchmark, measured conformance and performance&lt;/a&gt;, [4] &lt;a href="https://docs.obscura.sh/" rel="noopener noreferrer"&gt;Official Obscura documentation&lt;/a&gt;, [5] &lt;a href="https://blog.cloudflare.com/kitesurf/" rel="noopener noreferrer"&gt;Cloudflare, introducing Kitesurf&lt;/a&gt;, [6] &lt;a href="https://hub.docker.com/r/h4ckf0r0day/obscura" rel="noopener noreferrer"&gt;Official Obscura image on Docker Hub&lt;/a&gt;, [7] &lt;a href="https://obscura.sh/" rel="noopener noreferrer"&gt;Official Obscura website&lt;/a&gt;, [8] &lt;a href="https://wpt.fyi/" rel="noopener noreferrer"&gt;wpt.fyi, Web Platform Test results by engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Spanish version: &lt;a href="https://jacar.es/obscura-navegador-headless-rust-agentes/" rel="noopener noreferrer"&gt;Qué es Obscura, el navegador headless en Rust para agentes de IA&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://jacar.es/en/obscura-rust-headless-browser-ai-agents/" rel="noopener noreferrer"&gt;jacar.es&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to install and tune oMLX on M5 Max 128 GB</title>
      <dc:creator>Javier Cañete Arroyo</dc:creator>
      <pubDate>Tue, 13 Oct 2026 18:35:56 +0000</pubDate>
      <link>https://dev.to/javiercanete/how-to-install-and-tune-omlx-on-m5-max-128-gb-3no1</link>
      <guid>https://dev.to/javiercanete/how-to-install-and-tune-omlx-on-m5-max-128-gb-3no1</guid>
      <description>&lt;p&gt;&lt;strong&gt;oMLX is an LLM inference server built on MLX, the framework Apple shipped in December 2023 for Apple Silicon. It adds continuous batching, two-tier KV cache (RAM + SSD), and an OpenAI- and Anthropic-compatible API.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On a Mac M5 Max with 128 GB of unified memory you can hold three or four large models at once with TurboQuant 3.5-bit on KV cache. That is enough to feed chat, agent and IDE in parallel. This guide collects the configuration tested in May 2026 to get the most out of that combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick answers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which oMLX version do I use and how do I install it?&lt;/strong&gt; Version 0.6.4 (released 29 August 2026, Apache 2.0). The direct route: download the &lt;code&gt;.dmg&lt;/code&gt; from &lt;a href="https://github.com/jundot/omlx/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;, open it and drag to Applications. Or install via &lt;code&gt;brew tap jundot/omlx https://github.com/jundot/omlx&lt;/code&gt; and &lt;code&gt;brew install jundot/omlx/omlx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first load of the panel at &lt;code&gt;http://localhost:8000/admin&lt;/code&gt; prompts for the API key. The full Homebrew route, including the service that starts on its own, is in &lt;a href="https://jacar.es/en/omlx-homebrew-install-update-uninstall/" rel="noopener noreferrer"&gt;installing, updating and uninstalling oMLX with Homebrew&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What TurboQuant setting makes sense on 128 GB?&lt;/strong&gt; 3.5-bit. &lt;a href="https://vllm.ai/blog/2026-05-11-turboquant" rel="noopener noreferrer"&gt;vLLM’s independent analysis published 11 May 2026&lt;/a&gt; shows 3.5-bit matches full-precision quality with about 4x less KV-cache memory. On M5 Max that turns 128k context from "blows past available RAM" into "fits alongside other loaded models."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much faster is it with the advanced 0.6.4 settings?&lt;/strong&gt; Between 1.19x and 1.88x when generating code, and between 1.07x and 1.47x with 15,000 tokens of context, measured through the API on 15 September 2026. The biggest gain came from Lightning MTP on Qwen3.8-27B oQ4e, from 21.1 to 39.7 tok/s, and it needs a build of the model that keeps the MTP head. The details are in &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/#advanced-settings-for-maximum-performance-on-0-6-4" rel="noopener noreferrer"&gt;the advanced settings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which models can I load concurrently on 128 GB?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Primary: &lt;code&gt;unsloth/Qwen3.6-35B-A3B-MLX-8bit&lt;/code&gt; (37.7 GB, MoE with 3B active). Fast helper: &lt;code&gt;Qwen3-14B-Instruct-mlx-4bit&lt;/code&gt; (8 GB). Vision: &lt;code&gt;Qwen2.5-VL-32B-mlx-4bit&lt;/code&gt; (18 GB).&lt;/p&gt;

&lt;p&gt;Embeddings: &lt;code&gt;BGE-M3-mlx&lt;/code&gt; (1.2 GB). Reranker: &lt;code&gt;ModernBERT-base-mlx&lt;/code&gt; (150 MB). Comfortable sum: ~65 GB with headroom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I point Claude Code at the local endpoint?&lt;/strong&gt; Since 0.5.0 the short way is &lt;code&gt;omlx launch claude&lt;/code&gt;, which exports the variables for you. By hand: export &lt;code&gt;ANTHROPIC_BASE_URL=http://127.0.0.1:8000&lt;/code&gt;, &lt;code&gt;ANTHROPIC_AUTH_TOKEN=&amp;lt;your_api_key&amp;gt;&lt;/code&gt; and the three &lt;code&gt;ANTHROPIC_DEFAULT_*_MODEL&lt;/code&gt; variables (Opus/Sonnet/Haiku).&lt;/p&gt;

&lt;p&gt;Launch with &lt;code&gt;claude --bare&lt;/code&gt; to drop the system prompt to ~1,795 tokens. The dashboard’s &lt;strong&gt;Claude Code with oMLX&lt;/strong&gt; section assembles the full command for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this replace Claude Opus 4.7?&lt;/strong&gt; Not one for one. Claude Code is tuned for Claude’s tool-use format; a non-Claude model behind the endpoint loses reliability in agentic loops. Use this for offline work, sensitive data that should not leave the Mac, or as a fallback when &lt;code&gt;api.anthropic.com&lt;/code&gt; is rate-limiting you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;The easiest path: download the &lt;a href="https://github.com/jundot/omlx/releases" rel="noopener noreferrer"&gt;.dmg from GitHub Releases&lt;/a&gt;, open it and drag the app to Applications. The server starts in the background with a menu-bar icon.&lt;/p&gt;

&lt;p&gt;Or install via Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap jundot/omlx https://github.com/jundot/omlx
brew &lt;span class="nb"&gt;install &lt;/span&gt;jundot/omlx/omlx
omlx start
omlx serve &lt;span class="nt"&gt;--model-dir&lt;/span&gt; ~/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first hit to &lt;code&gt;http://localhost:8000/admin&lt;/code&gt; prompts for the API key. By default oMLX ships with no API key set (empty field), which is fine for local testing. If the instance only listens on &lt;code&gt;127.0.0.1&lt;/code&gt; this is safe; the moment you expose it to the LAN or plan to share the endpoint, go to Settings → Auth &amp;amp; Info and set a long random string. The section accepts multiple keys at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw47iapatq6mg08j5bodj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw47iapatq6mg08j5bodj.webp" alt="Login screen of the oMLX admin panel at localhost:8000/admin, with the API Key field empty and a " width="780" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Server settings for 128 GB
&lt;/h2&gt;

&lt;p&gt;Settings → Global Settings holds the full server configuration. The decisions that matter on a 128 GB machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server → Host&lt;/strong&gt;: &lt;code&gt;Localhost only (127.0.0.1)&lt;/code&gt;. If you open it to the LAN, put real auth in front first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server → Port&lt;/strong&gt;: &lt;code&gt;8000&lt;/code&gt; by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Management → Memory Limit (Total)&lt;/strong&gt;: &lt;code&gt;Auto&lt;/code&gt;. oMLX subtracts what macOS reserves; on 128 GB you end up around 110-114 GB for inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Management → Memory Limit (Models Only)&lt;/strong&gt;: &lt;code&gt;Auto&lt;/code&gt;. Keeps a percentage for activations, KV cache and auxiliary processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Management → Hot Cache Limit&lt;/strong&gt;: &lt;code&gt;10%&lt;/code&gt;. Intermediate RAM-tier KV cache. With TurboQuant enabled on the models that support it (see below), 10% is the value people running this setup in practice land on for 128 GB. With TurboQuant off and a single model loaded you can drop to Off, but you stop gaining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Management → Cold Cache Limit (SSD Cache)&lt;/strong&gt;: &lt;code&gt;10%&lt;/code&gt;. Around 80 GB of SSD for cold tokens without saturating it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Management → Max Concurrent Requests&lt;/strong&gt;: &lt;code&gt;16&lt;/code&gt;. Comfortable for a single user with an agent, a chat session and an IDE pinging at once. Raise to 32 if you share with a small team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Management → Idle Timeout&lt;/strong&gt;: &lt;code&gt;None&lt;/code&gt;. Keeps models warm; the first token arrives in well under a second instead of seconds later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation Defaults → Max Context Window&lt;/strong&gt;: &lt;code&gt;256000&lt;/code&gt; as the global default. The Qwen3.6 family holds up at long context, and with TurboQuant the effective RAM stretches enough to actually use it. Each model can be capped lower in Model Settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation Defaults → Max Tokens&lt;/strong&gt;: &lt;code&gt;64000&lt;/code&gt;. Upper bound per response. Going higher only matters if you plan to generate full books in one shot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation Defaults → Temperature&lt;/strong&gt;: &lt;code&gt;1.0&lt;/code&gt; for general use, &lt;code&gt;0.2-0.5&lt;/code&gt; for code models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Settings → Experimental Features → TurboQuant KV Cache&lt;/strong&gt;: enable at &lt;code&gt;3.5-bit&lt;/code&gt; on the large dense models. &lt;a href="https://vllm.ai/blog/2026-05-11-turboquant" rel="noopener noreferrer"&gt;vLLM’s independent study published 11 May 2026&lt;/a&gt; of Google’s TurboQuant shows 3.5-bit matches full-precision quality with roughly 4x less KV-cache memory. On M5 Max that makes 128k context fit on models where FP16 does not. On 0.6.4 it cannot be combined with VLM MTP, and in these tests it did not speed up generation; if speed is what you are after, see &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/#advanced-settings-for-maximum-performance-on-0-6-4" rel="noopener noreferrer"&gt;the advanced settings&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkdpy4y3eyekat01iuyp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkdpy4y3eyekat01iuyp.webp" alt="oMLX Global Settings page: Host set to Localhost only (127.0.0.1), Port 8000, Memory Limit Total Auto (120 GB), Memory Limit Models Only Auto (108 GB), Hot Cache at 9% (12 GB), Cold Cache SSD at 10% (372 GB), Max Concurrent Requests 16, Idle Timeout None, Max Context Window 256000, Max Tokens 64000, Temperature 1.0, Top P 0.95" width="568" height="2560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Model stack for multi-LLM
&lt;/h2&gt;

&lt;p&gt;With 128 GB you have room to load one large primary model, a fast helper, a VLM, embeddings and a reranker concurrently. Download them from Models → Downloader by pasting the Hugging Face repo URL.&lt;/p&gt;

&lt;p&gt;The recommendation holding up in May 2026 comes from people actually running Claude Code on oMLX on M5 Max (see &lt;a href="https://gist.github.com/DiegoRBaquero/f53ab22ae978226c86158a60dad8199d" rel="noopener noreferrer"&gt;Diego R. Baquero’s gist&lt;/a&gt; for the source): Unsloth’s &lt;strong&gt;Qwen 3.6 35B-A3B&lt;/strong&gt; family in MoE with ~3B active parameters per token. On 128 GB the 8-bit fits without breaking a sweat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary (chat + code + reasoning)&lt;/strong&gt;: &lt;code&gt;unsloth/Qwen3.6-35B-A3B-MLX-8bit&lt;/code&gt; (~37.7 GB). MoE 35B with 3B active. The all-rounder: long chat, code, agents. On 128 GB it fits alongside everything else without squeezing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More compressed alternative&lt;/strong&gt;: &lt;code&gt;unsloth/Qwen3.6-35B-A3B-UD-MLX-4bit&lt;/code&gt; (~21.6 GB) if you want two primary models loaded at once. Loses a notch of quality versus 8-bit but leaves room to experiment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dense reasoning (when MoE falls short)&lt;/strong&gt;: &lt;code&gt;Mistral-Large-2-123B-Instruct-mlx-4bit&lt;/code&gt; (~70 GB) or &lt;code&gt;Llama-3.3-70B-Instruct-mlx-4bit&lt;/code&gt; (~40 GB). For deep reasoning in a single pass, where a dense architecture beats a small-activation MoE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast helper&lt;/strong&gt;: &lt;code&gt;Qwen3-14B-Instruct-mlx-4bit&lt;/code&gt; (~8 GB). For cheap tasks in agents, parsing and summaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision&lt;/strong&gt;: &lt;code&gt;Qwen2.5-VL-32B-Instruct-mlx-4bit&lt;/code&gt; (~18 GB) covers OCR, image description and multimodal reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings&lt;/strong&gt;: &lt;code&gt;BGE-M3-mlx&lt;/code&gt; (~1.2 GB), dense + sparse + multi-vector in a single model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reranker&lt;/strong&gt;: &lt;code&gt;ModernBERT-base-mlx&lt;/code&gt; (~150 MB) to close the loop on a decent RAG pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comfortable concurrent load with Qwen3.6 35B-A3B 8-bit as primary + 14B helper + VL-32B + embeddings + reranker: around 65-70 GB. Add Mistral Large 2 123B on top for dense reasoning and you hit ~135 GB nominal. The LRU policy moves idle models to SSD, so the cohabitation works in practice for sessions that do not pin everything at once. Pin the primary model from Model Manager so it never gets evicted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Point Claude Code at the local endpoint
&lt;/h2&gt;

&lt;p&gt;oMLX exposes an Anthropic-compatible API at &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;. In current versions the routes live under &lt;code&gt;/v1&lt;/code&gt;: &lt;code&gt;POST /v1/messages&lt;/code&gt; and &lt;code&gt;POST /v1/messages/count_tokens&lt;/code&gt;. Claude Code respects &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;, so pointing the CLI at your Mac is a matter of exporting environment variables. First, turn off the attribution header in Claude Code’s global config so the gateway does not bolt extra noise onto the prompt:&lt;/p&gt;

&lt;p&gt;Since 0.5.0 there is a shortcut that does all of this for you and did not exist when this recipe was written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;omlx launch claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sets &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; against your server, puts the configured key in &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; (or &lt;code&gt;omlx&lt;/code&gt; if there is none), empties &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; to force the base URL to be used, raises &lt;code&gt;API_TIMEOUT_MS&lt;/code&gt; to 3,000,000 and disables non-essential traffic. It also sets &lt;code&gt;CLAUDE_CODE_MAX_CONTEXT_TOKENS&lt;/code&gt; and &lt;code&gt;CLAUDE_CODE_AUTO_COMPACT_WINDOW&lt;/code&gt; to what the engine can actually do, which is what the context-scaling option used to do by hand. It enforces a minimum window of 48,000 tokens and will not start if the chosen model falls short. There are equivalent commands for Codex, OpenCode, OpenClaw, Hermes and Pi, described in &lt;a href="https://jacar.es/en/omlx-admin-dashboard-cli/" rel="noopener noreferrer"&gt;the dashboard and command line guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The manual recipe below still works and is the one to use if you want fine control over each variable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CLAUDE_CODE_ATTRIBUTION_HEADER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the startup command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:8000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your_api_key&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_OPUS_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unsloth/Qwen3.6-35B-A3B-MLX-8bit
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_SONNET_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unsloth/Qwen3.6-35B-A3B-MLX-8bit
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_HAIKU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Qwen3-14B-Instruct-mlx-4bit
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unsloth/Qwen3.6-35B-A3B-MLX-8bit
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_TIMEOUT_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;600000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_USE_BEDROCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DISABLE_NONESSENTIAL_TRAFFIC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
claude &lt;span class="nt"&gt;--bare&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--bare&lt;/code&gt; flag skips hooks, LSP, plugin sync and auto-memory, dropping Claude Code’s system prompt to around 1,795 tokens (versus the thousands it uses with everything loaded). For offline work against a local model that is the sensible default: every token in the system prompt is bandwidth your Mac would otherwise burn. Drop the flag when you point back at &lt;code&gt;api.anthropic.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The oMLX dashboard builds this command for you in the &lt;strong&gt;Claude Code with oMLX&lt;/strong&gt; section: pick Opus, Sonnet and Haiku from three dropdowns and copy the ready-to-paste command. Set &lt;strong&gt;Context scaling for Claude Code&lt;/strong&gt; to &lt;code&gt;64000&lt;/code&gt;: Claude Code requests 200k tokens by default, but a local model behaves better with a 64k ask than a 200k ask it cannot honor. The option scales the reported counts so auto-compact triggers at the target size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5qlpia97sgyzhjdmoa7o.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5qlpia97sgyzhjdmoa7o.webp" alt="oMLX dashboard with the Claude Code with oMLX section fully configured: Local mode active, Opus mapped to Huihui-Qwen3.6-35B-A3B-Claude-4.7-Opus-abliterated-mlx-8bit, Sonnet to Qwen3.6-40B-Claude-4.6-Opus-Deckard-Heretic-Uncensored-Thinking-8bit, Haiku to Qwen3.5-9B-Claude-4.6-HighIQ-INSTRUCT-HERETIC-UNCENSORED-MLX-mxfp8, Context scaling for Claude Code enabled with target 200000, plus pre-built commands for Codex, OpenCode and other integrations below" width="800" height="1497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A real caveat: Claude Code is tuned to Claude’s tool-use format and response patterns. A non-Claude model behind &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; works for autocomplete and reasoning, but you will see drops in tool-call reliability and in agentic loops. Use this for offline work, sensitive data that should not leave the Mac, or as a fallback when api.anthropic.com is rate-limiting you. It is not a 1:1 substitute for Claude Opus 4.7.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSH from another Mac
&lt;/h2&gt;

&lt;p&gt;If oMLX runs on a Mac Studio and you want to use Claude Code from your MacBook, SSH port forwarding is the bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 8000:localhost:8000 user@mac-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once connected, &lt;code&gt;127.0.0.1:8000&lt;/code&gt; on your laptop points to the Studio’s oMLX. Save this script as &lt;code&gt;claude-local.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'http://localhost:8000'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_OPUS_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Qwen3.6-35B-A3B-MLX-8bit'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_SONNET_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Qwen3.6-35B-A3B-MLX-8bit'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_HAIKU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Qwen3-14B-MLX-4bit'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Qwen3.6-35B-A3B-MLX-8bit'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_TIMEOUT_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;600000
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_USE_BEDROCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
claude &lt;span class="nt"&gt;--bare&lt;/span&gt; &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable with &lt;code&gt;chmod +x claude-local.sh&lt;/code&gt; and run it from the SSH session. The default API key is the empty field. If you set one in Settings → Auth &amp;amp; Info, replace &lt;code&gt;''&lt;/code&gt; with it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--bare&lt;/code&gt; drops Claude Code’s system prompt to ~1,795 tokens. &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; bypasses interactive permission prompts that don’t make sense in an automated pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark on real hardware
&lt;/h2&gt;

&lt;p&gt;Bench → Performance runs tests on your actual hardware. The panel covers prefill at eight prompt sizes (&lt;code&gt;pp1024&lt;/code&gt;, &lt;code&gt;pp4096&lt;/code&gt;, &lt;code&gt;pp8192&lt;/code&gt;, &lt;code&gt;pp16384&lt;/code&gt;, &lt;code&gt;pp32768&lt;/code&gt;, &lt;code&gt;pp65536&lt;/code&gt;, &lt;code&gt;pp131072&lt;/code&gt;, &lt;code&gt;pp200000&lt;/code&gt;) and continuous batching at 2x, 4x and 8x concurrency. Results go to the &lt;a href="https://omlx.ai/" rel="noopener noreferrer"&gt;public oMLX leaderboard&lt;/a&gt;, and &lt;strong&gt;My Submissions&lt;/strong&gt; shows yours next to other machines on the same profile. On 0.6.4 the upload happens automatically when each run finishes; how to avoid it is in &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/#benchmarks-on-0-6-4-on-this-m5-max" rel="noopener noreferrer"&gt;the 0.6.4 benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reference numbers measured on M5 Max 40-core with 128 GB &lt;strong&gt;on oMLX 0.3.8, in May 2026&lt;/strong&gt;. Not extrapolated (the &lt;a href="https://omlx.ai/" rel="noopener noreferrer"&gt;omlx.ai leaderboard&lt;/a&gt; already has M5 Max submissions, and &lt;a href="https://vllm.ai/blog/2026-05-11-turboquant" rel="noopener noreferrer"&gt;vLLM’s independent TurboQuant study published 11 May 2026&lt;/a&gt; confirms the figures for the large dense models):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qwen 3.6 35B-A3B 8-bit (MoE, 3B active): 65-80 tok/s on decode at short context. The model that most changes day-to-day use on this machine. &lt;strong&gt;Live benchmarks from this setup:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;th&gt;Decode TPS&lt;/th&gt;
&lt;th&gt;E2E&lt;/th&gt;
&lt;th&gt;Peak Mem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pp1024/tg128&lt;/td&gt;
&lt;td&gt;577 ms&lt;/td&gt;
&lt;td&gt;72.4 tok/s&lt;/td&gt;
&lt;td&gt;2.35s&lt;/td&gt;
&lt;td&gt;38.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp4096/tg128&lt;/td&gt;
&lt;td&gt;1,293 ms&lt;/td&gt;
&lt;td&gt;83.1 tok/s&lt;/td&gt;
&lt;td&gt;2.83s&lt;/td&gt;
&lt;td&gt;36.2 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp8192/tg128&lt;/td&gt;
&lt;td&gt;2,995 ms&lt;/td&gt;
&lt;td&gt;82.1 tok/s&lt;/td&gt;
&lt;td&gt;4.55s&lt;/td&gt;
&lt;td&gt;36.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp32768/tg128&lt;/td&gt;
&lt;td&gt;13,785 ms&lt;/td&gt;
&lt;td&gt;15.2 tok/s&lt;/td&gt;
&lt;td&gt;22.18s&lt;/td&gt;
&lt;td&gt;40.9 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Continuous batching&lt;/strong&gt; (pp1024 / tg128):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Decode TPS&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1x (baseline)&lt;/td&gt;
&lt;td&gt;72.4 tok/s&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2x&lt;/td&gt;
&lt;td&gt;87.1 tok/s&lt;/td&gt;
&lt;td&gt;1.20x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4x&lt;/td&gt;
&lt;td&gt;123.8 tok/s&lt;/td&gt;
&lt;td&gt;1.71x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8x&lt;/td&gt;
&lt;td&gt;237.7 tok/s&lt;/td&gt;
&lt;td&gt;3.28x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Llama 3.3 70B 4-bit: 14-18 tok/s on decode.&lt;/li&gt;
&lt;li&gt;Mistral Large 2 123B 4-bit with TurboQuant 3.5-bit on KV cache: 8-11 tok/s on decode. The number that matters at 128k context is peak memory, ~74 GB (FP16 KV blows past 128 GB). &lt;a href="https://dasroot.net/posts/2026/04/m5-max-context-windows-memory-fragmentation-turbo-quant/" rel="noopener noreferrer"&gt;dasroot.net’s long-context M5 Max experiment&lt;/a&gt; documents the same peaks for the 104B dense model.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omlx.ai/benchmarks/hulvfapi" rel="noopener noreferrer"&gt;gpt-oss-20b MXFP4-Q4 on M5 Max 40c per the public oMLX benchmark submission&lt;/a&gt; lands around 100 tok/s on decode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tables above are our own measurement on 0.3.8, and on 15 September 2026 I re-ran the tests on 0.6.4 on this same machine. With no acceleration at all, this model goes from 15.2 to 77.9 tok/s with a 32k prompt. Speculative decoding adds between 1.07x and 1.88x, depending on the model and the context. The new tables are in &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/#benchmarks-on-0-6-4-on-this-m5-max" rel="noopener noreferrer"&gt;the 0.6.4 benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;vLLM measured the TurboQuant KV-cache compression at 4.41x in May 2026. That figure is what separates "I have 128k context but it does not fit" from "I have it and can load two other models alongside it". At 4-bit and 3.5-bit, quality stays close to full precision; at 3-bit you start to feel it in code and long-form reasoning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fng06u1evfx0h6lfurk1f.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fng06u1evfx0h6lfurk1f.webp" alt="oMLX Bench → Performance Benchmark page with Qwen3.6-35B-A3B-MLX-8bit selected, Single Request Test checkboxes (pp1024, pp4096, pp8192, pp32768) and Continuous Batching Test checkboxes (2x, 4x, 8x), Run Benchmark button, and My Submissions link" width="780" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced settings for maximum performance on 0.6.4
&lt;/h2&gt;

&lt;p&gt;On 0.6.4, the biggest speed lever on this M5 Max is speculative decoding, and the right method depends on the model. Measured through the API at temperature 0.6, Qwen3.8-27B oQ4e went from 21.1 to 39.7 tok/s with Lightning MTP. Qwen3.6-35B-A3B 8-bit went from 79.7 to 117.5 tok/s with VLM MTP. Everything in this section was measured on 15 September 2026 on the machine this guide is about, and the method and the full tables are in &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/#benchmarks-on-0-6-4-on-this-m5-max" rel="noopener noreferrer"&gt;the 0.6.4 benchmarks section&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which acceleration method to use with each model
&lt;/h3&gt;

&lt;p&gt;oMLX 0.6.4 has three speculative decoding methods and allows only one per model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightning MTP&lt;/strong&gt;: uses the multi-token prediction (MTP) head that ships inside the weights. It only works if the conversion kept the &lt;code&gt;mtp.*&lt;/code&gt; tensors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VLM MTP&lt;/strong&gt;: uses an external MTP drafter under 2 GB, trained for that base model. It only kicks in when no other request is running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DFlash&lt;/strong&gt;: uses a z-lab block-diffusion drafter that proposes up to 16 tokens per pass. It serves requests one at a time and keeps its own prefix cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all three the large model verifies every proposed token, so the output comes from the large model, not from the drafter. This is what each combination measured on this M5 Max:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What to download&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Decode writing code&lt;/th&gt;
&lt;th&gt;Decode with 15,000 tokens of context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B 8-bit&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mlx-community/Qwen3.6-35B-A3B-MTP-bf16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.7 GB&lt;/td&gt;
&lt;td&gt;79.7 → 117.5 tok/s&lt;/td&gt;
&lt;td&gt;84.0 → 90.7 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B oQ4e&lt;/td&gt;
&lt;td&gt;Lightning MTP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Jundot/Qwen3.6-35B-A3B-oQ4e-mtp&lt;/code&gt; (full model)&lt;/td&gt;
&lt;td&gt;21.6 GB&lt;/td&gt;
&lt;td&gt;100.2 → 119.2 tok/s&lt;/td&gt;
&lt;td&gt;100.2 → 115.5 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e&lt;/td&gt;
&lt;td&gt;Lightning MTP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Jundot/Qwen3.8-27B-oQ4e-mtp&lt;/code&gt; (full model)&lt;/td&gt;
&lt;td&gt;17.0 GB&lt;/td&gt;
&lt;td&gt;21.1 → 39.7 tok/s&lt;/td&gt;
&lt;td&gt;21.4 → 27.2 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mlx-community/Qwen3.8-27B-MTP-bf16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.87 GB&lt;/td&gt;
&lt;td&gt;24.1 → 34.5 tok/s&lt;/td&gt;
&lt;td&gt;21.7 → 23.2 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B 8-bit&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mlx-community/gemma-4-12B-it-assistant-bf16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.88 GB&lt;/td&gt;
&lt;td&gt;27.4 → 50.5 tok/s&lt;/td&gt;
&lt;td&gt;26.6 → 39.2 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-oss-20b&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For Qwen3.6-35B-A3B, the oQ4e build with Lightning MTP was the fastest in both tests and takes about 15 GB less than the 8-bit one. With long context and prose answers every gain shrinks, because the large model accepts fewer draft tokens, but no configuration was slower than its baseline.&lt;/p&gt;

&lt;p&gt;If a Qwen model’s settings show the warning &lt;em&gt;Config declares MTP layers but the weight files contain neither mtp.* tensors nor native nextn layers&lt;/em&gt;, the conversion stripped the MTP head. The default mlx-lm converters do that: it applied to the Qwen3.6-35B-A3B builds from mlx-community and the Qwen3.8-27B builds from lmstudio-community installed on this Mac. You have two ways out: download a build that keeps the head, such as the oQ4e-mtp builds published by the oMLX author, or turn on VLM MTP with an external drafter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Downloading the drafters
&lt;/h3&gt;

&lt;p&gt;Drafters download like any other model. In &lt;strong&gt;Models → Downloader&lt;/strong&gt;, paste the Hugging Face repository id and start the download. oMLX recognises drafters by their type and marks them as helpers; with &lt;strong&gt;Hide Helper Models&lt;/strong&gt; on, they also stay out of the API’s model list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpis5728rp3ajqzdu2qo.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpis5728rp3ajqzdu2qo.webp" alt="oMLX 0.6.4 Download from HuggingFace form with the repository id mlx-community/Qwen3.6-35B-A3B-MTP-bf16 filled in, the HF Token field empty and the Download button" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Turning on Lightning MTP
&lt;/h3&gt;

&lt;p&gt;With a model that keeps its MTP head, open &lt;strong&gt;Settings → Model Settings&lt;/strong&gt;, click the model’s gear icon and turn on &lt;strong&gt;Lightning MTP&lt;/strong&gt; in the &lt;strong&gt;Acceleration&lt;/strong&gt; block. When you save, oMLX reloads the model.&lt;/p&gt;

&lt;p&gt;On Qwen the default is 3 draft tokens per cycle, and an adaptive controller adjusts it from each response’s acceptance rate. The oMLX log writes one line per response with that rate: on Qwen3.6-35B-A3B oQ4e it sat around 72-77%, with 2.5 to 2.9 tokens emitted per cycle. With greedy sampling the output matches the model without MTP, apart from tiny numerical differences. With temperature, rejection sampling keeps the large model’s distribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fatyls0pshhc02loz24ao.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fatyls0pshhc02loz24ao.webp" alt="Acceleration block of the Jundot/Qwen3.8-27B-oQ4e-mtp model settings in oMLX 0.6.4, with the Lightning MTP switch turned on" width="797" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Turning on VLM MTP with an external drafter
&lt;/h3&gt;

&lt;p&gt;In the same &lt;strong&gt;Acceleration&lt;/strong&gt; block, turn on &lt;strong&gt;VLM MTP&lt;/strong&gt;, pick the drafter from the drop-down and save. Three conditions in the 0.6.4 code are worth knowing first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model has to load on the vision engine. The multimodal Qwen3.6 and Qwen3.8 builds do so by default, but on a text-only model the setting is silently ignored.&lt;/li&gt;
&lt;li&gt;It cannot be combined with TurboQuant or with repetition or presence penalties. The built-in Qwen presets set &lt;code&gt;presence_penalty&lt;/code&gt; to 1.5, which turns it off.&lt;/li&gt;
&lt;li&gt;It only speeds up a request when nothing else is running. With concurrent requests, oMLX falls back to normal batching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs9eyj8xuuypcdegwqaj2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs9eyj8xuuypcdegwqaj2.webp" alt="VLM MTP card in the mlx-community/Qwen3.6-35B-A3B-8bit settings in oMLX 0.6.4, turned on with the Qwen3.6-35B-A3B-MTP-bf16 drafter and the block size left empty, which means 4 tokens" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can apply the same change through the admin API, which is handier when you configure more than one model or script it. The first command logs in with your key and stores the cookie; the second updates the model’s settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-c&lt;/span&gt; omlx.cookies &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:8000/admin/api/login &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"api_key": "your_api_key_here"}'&lt;/span&gt;

curl &lt;span class="nt"&gt;-b&lt;/span&gt; omlx.cookies &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:8000/admin/api/models/Qwen3.6-35B-A3B-8bit/settings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"vlm_mtp_enabled": true,
       "vlm_mtp_draft_model": "Qwen3.6-35B-A3B-MTP-bf16"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model name in the path and the drafter name are the ids shown in the model list. For Lightning MTP the body is &lt;code&gt;{"mtp_enabled": true}&lt;/code&gt;. The endpoint rejects any field it does not know, so a typo does not slip through.&lt;/p&gt;

&lt;h3&gt;
  
  
  DFlash only pays off with short prompts
&lt;/h3&gt;

&lt;p&gt;DFlash was the fastest method with 1,000-token prompts and the worst with long context. In the built-in benchmark, Qwen3.6-35B-A3B 8-bit rose from 92 to 112 tok/s with a 1k prompt, but dropped from 78 to 17 tok/s with 32k. Qwen3.8-27B 4-bit dropped from 23.5 to 6.3 tok/s with 32k. If you use Claude Code, which sends tens of thousands of tokens of context every turn, leave it off.&lt;/p&gt;

&lt;p&gt;For short-prompt work such as chat or autocomplete, download the matching z-lab drafter: &lt;code&gt;z-lab/Qwen3.6-35B-A3B-DFlash&lt;/code&gt; (0.77 GB), &lt;code&gt;z-lab/Qwen3.8-27B-DFlash2&lt;/code&gt; (3.85 GB) or &lt;code&gt;z-lab/gemma4-12B-it-DFlash&lt;/code&gt; (1.46 GB). Turn it on in &lt;strong&gt;Acceleration → DFlash&lt;/strong&gt; and quantise the drafter to 4 bits. On the 27B, the 4-bit drafter gave 48.2 tok/s with a 1k prompt against 36.9 tok/s in bf16, and used 2.5 GiB less.&lt;/p&gt;

&lt;p&gt;It has two more limits. It serves one request at a time. And although &lt;code&gt;dflash_max_ctx&lt;/code&gt; sends long requests to the normal engine, after the first one DFlash stays off until you reload the model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flvonm1bgfjl21gr6ghua.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flvonm1bgfjl21gr6ghua.webp" alt="oMLX 0.6.4 DFlash card with the Qwen3.6-35B-A3B-DFlash drafter selected, drafter quantisation on with 4-bit weights, 16-bit activations and group size 64, and the max context threshold left unlimited" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefill on the Neural Engine: no gain with Lightning MTP
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Qwen ANE Prompt Processing&lt;/strong&gt; splits part of the Qwen3.5, 3.6 and 3.8 prefill between Apple’s Neural Engine (ANE) and the GPU. The &lt;strong&gt;Tune for this Mac&lt;/strong&gt; button measures the best split on your machine and saves nothing until you apply the result. On Qwen3.8-27B oQ4e it recommended sending 35% of the MLP channels and 37.5% of the GDN layers to the ANE. With that split, prefill on an 8,193-token prompt went from 452 to 510 tok/s, 12.8% more.&lt;/p&gt;

&lt;p&gt;That 12.8% did not hold with the full model. With the split applied and Lightning MTP on, the built-in benchmark measured 505 tok/s of prefill with a 4k prompt against 499 without the ANE, and 422 against 471 with 32k. Through the API, the first token with 15,100 tokens of context took 34.8 s, against 35.2 s without the ANE. In exchange the loaded model grew from 16.0 to 20.7 GB and loading went from 2 to 20 s, so on this Mac I left it off.&lt;/p&gt;

&lt;p&gt;Before tuning it on an M5 Max, turn off &lt;strong&gt;Use both ANEs&lt;/strong&gt; in the model settings. That mode is meant for the M3 Ultra, which joins two chips and has two ANEs. It only speeds up prompts of 2,048 tokens or more; generation stays on the GPU.&lt;/p&gt;

&lt;p&gt;The project itself documents three costs. It is not bit-exact, because the ANE runs weights requantised to INT8. It relies on private Apple interfaces that a macOS update can break, and it lengthens model load. If you turn it on anyway, also turn on &lt;strong&gt;Reuse Compiled ANE Programs&lt;/strong&gt; under &lt;strong&gt;Global Settings → Advanced&lt;/strong&gt; so it does not recompile on every start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhpl7gyksjtg0rupqc746.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhpl7gyksjtg0rupqc746.webp" alt="oMLX 0.6.4 Qwen ANE Prompt Processing card with the split the tuner proposed: 2048-token block, padding from 1816, 0.35 of the MLPs and 0.375 of the GDN layers on the ANE, Use both ANEs off and the Tune for this Mac button" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The global settings that matter
&lt;/h3&gt;

&lt;p&gt;oMLX’s default global configuration already suits a single user, and only four settings change anything measurable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kernel memory limit&lt;/strong&gt;: macOS caps the memory the GPU can wire by default, and the dashboard shows a red warning when that cap is below what oMLX could use. Run &lt;code&gt;sudo sysctl iogpu.wired_limit_mb=124518&lt;/code&gt;, which on 128 GB is RAM minus 5%, and restart oMLX, which applies it at start-up. The value is lost when the Mac restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefill Priority&lt;/strong&gt;: set it to &lt;code&gt;Speed&lt;/code&gt;. When memory is tight, &lt;code&gt;Max Context&lt;/code&gt; shrinks prefill down to 32 tokens per step so the prompt fits, while &lt;code&gt;Speed&lt;/code&gt; keeps full speed and rejects what does not fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hide Helper Models&lt;/strong&gt;: turn it on so drafters stay out of &lt;code&gt;/v1/models&lt;/code&gt; and no client picks one by mistake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max Concurrent Requests&lt;/strong&gt;: keep it between 8 and 16, and do not lower it to 1. The scheduler admits no new request while as many cache writes are pending as that limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Burst Decode&lt;/strong&gt; can stay on &lt;code&gt;Balanced&lt;/code&gt;. &lt;code&gt;Aggressive&lt;/code&gt; holds tokens for up to 200 ms before sending them, against 100 ms, so the client sees the first token later. One last warning: clicking &lt;strong&gt;Save Settings&lt;/strong&gt; unloads every loaded model, even if you did not touch the cache, because the dashboard resends every field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbde1dkgr2c25c45kcbr7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbde1dkgr2c25c45kcbr7.webp" alt="Resource Management section of the oMLX 0.6.4 global settings: Balanced memory guard, hot cache at 9% (12 GB), SSD cache at 10% (372 GB), 16 concurrent requests, Chunked Prefill off, Prefill Priority set to Speed and no idle timeout" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What does not make it faster
&lt;/h3&gt;

&lt;p&gt;Three settings with promising names did not improve single-user speed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TurboQuant KV&lt;/strong&gt;: with Lightning MTP on Qwen3.8-27B oQ4e, turning it on at 4 bits left peak memory unchanged with a 32k prompt (24.4 GiB) and lowered decode from 35.4 to 30.3 tok/s. In these hybrid models only the full-attention layers keep a KV cache, so there is little to compress. It saves memory on huge contexts; it does not make generation faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SpecPrefill&lt;/strong&gt;: brings the first token forward by having a small model drop the prompt tokens it scores as unimportant. The large model never sees them, so it loses information by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunked Prefill&lt;/strong&gt;: it only cuts time to first token when requests overlap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benchmarks on 0.6.4 on this M5 Max
&lt;/h2&gt;

&lt;p&gt;With 0.6.4 and the settings above, this M5 Max generates between 1.19 and 1.88 times more tokens per second when writing code. With 15,000 tokens of context, the gain sits between 1.07 and 1.47 times. Without any acceleration, Qwen3.6-35B-A3B 8-bit now holds 77.9 tok/s with a 32k prompt, where 0.3.8 dropped to 15.2 tok/s. The tests date from 15 September 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it was measured
&lt;/h3&gt;

&lt;p&gt;There are two test sets, because the built-in benchmark shows how speed changes with context but cannot compare with and without acceleration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Through the API&lt;/strong&gt;: streaming requests to &lt;code&gt;/v1/chat/completions&lt;/code&gt; at temperature 0.6 with reasoning off. There are two tests. In the short one, each configuration answers three coding tasks (Python, TypeScript and Go) twice, with 768-token answers. In the long one, it answers two questions about roughly 15,100 tokens of source code, with 512-token answers. Timing is taken on the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in benchmark&lt;/strong&gt;: started from &lt;code&gt;POST /admin/api/bench/start&lt;/code&gt;, three repeats per configuration and the median, prompts of 1,025, 4,097, 16,385 and 32,769 tokens, 256 generated tokens and the &lt;code&gt;code_python&lt;/code&gt; corpus. It always samples greedily, which is the best case for speculative decoding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every request starts with a unique prefix, so none of them benefits from the prefix cache. Tables show the median, with the minimum and maximum in brackets when they spread more than 10% from the median in the API tests, or more than 25% in the built-in benchmark.&lt;/p&gt;

&lt;p&gt;The tests ran back to back for about three hours, with other applications open. The built-in benchmark records macOS thermal pressure, and 194 of its 242 measurements were taken at the Heavy level, the third of five (Nominal, Moderate, Heavy, Trapping and Sleeping). Only the first gpt-oss batch ran cold. Its prefill with a 16k prompt gave 3,140 tok/s cold and 1,755 tok/s hot.&lt;/p&gt;

&lt;p&gt;Read the absolute figures as those of a hot Mac. The comparisons with and without each setting were run one after the other, so the relative gains are more reliable than the absolute values.&lt;/p&gt;

&lt;p&gt;Two details of the 0.6.4 built-in benchmark change how to read it. First, when it finishes it uploads the results to omlx.ai with the chip, the models and an identifier derived from the Mac, and the dashboard has no option to prevent it. With a local model, the only way to keep the results on the Mac is to tick &lt;strong&gt;ANE-aligned prompts (+1 token)&lt;/strong&gt;. That is the checkbox I used, which is why the prompts are one token longer.&lt;/p&gt;

&lt;p&gt;Second, it loads vision models on the text-only engine unless MTP is on, so comparing with and without acceleration mixes two engines. With short prompts, Qwen3.6-35B-A3B oQ4e scored 49 tok/s on that engine and 100 tok/s through the API. That is why the gains in this guide come from the API tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed through the API with coding answers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Decode&lt;/th&gt;
&lt;th&gt;First token&lt;/th&gt;
&lt;th&gt;768-token answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B 8-bit&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;79.7 tok/s&lt;/td&gt;
&lt;td&gt;0.37 s&lt;/td&gt;
&lt;td&gt;10.0 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;117.5 (110.5-125.5) tok/s&lt;/td&gt;
&lt;td&gt;0.37 s&lt;/td&gt;
&lt;td&gt;6.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B oQ4e-mtp&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;100.2 tok/s&lt;/td&gt;
&lt;td&gt;0.34 s&lt;/td&gt;
&lt;td&gt;8.0 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Lightning MTP&lt;/td&gt;
&lt;td&gt;119.2 (108.5-139.1) tok/s&lt;/td&gt;
&lt;td&gt;0.46 s&lt;/td&gt;
&lt;td&gt;6.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;24.1 tok/s&lt;/td&gt;
&lt;td&gt;0.79 s&lt;/td&gt;
&lt;td&gt;32.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;34.5 tok/s&lt;/td&gt;
&lt;td&gt;0.65 s&lt;/td&gt;
&lt;td&gt;22.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;21.1 tok/s&lt;/td&gt;
&lt;td&gt;0.82 s&lt;/td&gt;
&lt;td&gt;37.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Lightning MTP&lt;/td&gt;
&lt;td&gt;39.7 tok/s&lt;/td&gt;
&lt;td&gt;0.94 s&lt;/td&gt;
&lt;td&gt;20.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B 8-bit&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;27.4 tok/s&lt;/td&gt;
&lt;td&gt;0.84 s&lt;/td&gt;
&lt;td&gt;28.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;50.5 (47.5-52.7) tok/s&lt;/td&gt;
&lt;td&gt;0.66 s&lt;/td&gt;
&lt;td&gt;15.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Speed through the API with 15,000 tokens of context
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Decode&lt;/th&gt;
&lt;th&gt;First token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B 8-bit&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;84.0 tok/s&lt;/td&gt;
&lt;td&gt;6.20 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;90.7 tok/s&lt;/td&gt;
&lt;td&gt;5.96 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B oQ4e-mtp&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;100.2 tok/s&lt;/td&gt;
&lt;td&gt;5.83 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Lightning MTP&lt;/td&gt;
&lt;td&gt;115.5 tok/s&lt;/td&gt;
&lt;td&gt;6.14 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;21.7 tok/s&lt;/td&gt;
&lt;td&gt;34.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;23.2 tok/s&lt;/td&gt;
&lt;td&gt;32.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;21.4 tok/s&lt;/td&gt;
&lt;td&gt;31.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Lightning MTP&lt;/td&gt;
&lt;td&gt;27.2 (24.4-30.1) tok/s&lt;/td&gt;
&lt;td&gt;35.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B 8-bit&lt;/td&gt;
&lt;td&gt;No acceleration&lt;/td&gt;
&lt;td&gt;26.6 tok/s&lt;/td&gt;
&lt;td&gt;20.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;VLM MTP&lt;/td&gt;
&lt;td&gt;39.2 tok/s&lt;/td&gt;
&lt;td&gt;19.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row is the median of two 512-token answers over the same code context. With this context the large model accepted fewer draft tokens than when writing code: on Qwen3.8-27B 4-bit, the VLM MTP gain fell from 1.43x to 1.07x.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in benchmark by prompt length
&lt;/h3&gt;

&lt;p&gt;Generation speed in tok/s by prompt length, with prefill at 4k and MLX peak memory, which includes the weights:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model and configuration&lt;/th&gt;
&lt;th&gt;1k&lt;/th&gt;
&lt;th&gt;4k&lt;/th&gt;
&lt;th&gt;16k&lt;/th&gt;
&lt;th&gt;32k&lt;/th&gt;
&lt;th&gt;Prefill 4k&lt;/th&gt;
&lt;th&gt;Peak&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B 8-bit, no acceleration&lt;/td&gt;
&lt;td&gt;92.1&lt;/td&gt;
&lt;td&gt;91.1&lt;/td&gt;
&lt;td&gt;84.1&lt;/td&gt;
&lt;td&gt;77.9&lt;/td&gt;
&lt;td&gt;2,426&lt;/td&gt;
&lt;td&gt;38.7 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B 8-bit, VLM MTP&lt;/td&gt;
&lt;td&gt;118.3&lt;/td&gt;
&lt;td&gt;106.6&lt;/td&gt;
&lt;td&gt;100.2&lt;/td&gt;
&lt;td&gt;94.4&lt;/td&gt;
&lt;td&gt;3,113&lt;/td&gt;
&lt;td&gt;41.8 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B 8-bit, DFlash (4-bit drafter)&lt;/td&gt;
&lt;td&gt;112.4&lt;/td&gt;
&lt;td&gt;110.5&lt;/td&gt;
&lt;td&gt;32.6 (23.3-35.0)&lt;/td&gt;
&lt;td&gt;16.6&lt;/td&gt;
&lt;td&gt;2,734&lt;/td&gt;
&lt;td&gt;38.2 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B oQ4e-mtp, no acceleration&lt;/td&gt;
&lt;td&gt;49.3&lt;/td&gt;
&lt;td&gt;46.1&lt;/td&gt;
&lt;td&gt;45.2&lt;/td&gt;
&lt;td&gt;42.2&lt;/td&gt;
&lt;td&gt;2,258&lt;/td&gt;
&lt;td&gt;23.2 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B oQ4e-mtp, Lightning MTP&lt;/td&gt;
&lt;td&gt;77.2 (47.0-101.7)&lt;/td&gt;
&lt;td&gt;85.8&lt;/td&gt;
&lt;td&gt;87.9 (85.7-116.6)&lt;/td&gt;
&lt;td&gt;82.8 (54.7-99.5)&lt;/td&gt;
&lt;td&gt;2,014&lt;/td&gt;
&lt;td&gt;24.6 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6-35B-A3B oQ4e-mtp, DFlash (4-bit drafter)&lt;/td&gt;
&lt;td&gt;142.7&lt;/td&gt;
&lt;td&gt;86.3&lt;/td&gt;
&lt;td&gt;39.2&lt;/td&gt;
&lt;td&gt;17.3&lt;/td&gt;
&lt;td&gt;3,373&lt;/td&gt;
&lt;td&gt;22.7 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit, no acceleration&lt;/td&gt;
&lt;td&gt;30.6&lt;/td&gt;
&lt;td&gt;27.3&lt;/td&gt;
&lt;td&gt;25.0&lt;/td&gt;
&lt;td&gt;23.5&lt;/td&gt;
&lt;td&gt;525&lt;/td&gt;
&lt;td&gt;21.7 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit, VLM MTP&lt;/td&gt;
&lt;td&gt;39.3&lt;/td&gt;
&lt;td&gt;36.6&lt;/td&gt;
&lt;td&gt;36.4&lt;/td&gt;
&lt;td&gt;30.9&lt;/td&gt;
&lt;td&gt;619&lt;/td&gt;
&lt;td&gt;25.4 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit, DFlash2 (4-bit drafter)&lt;/td&gt;
&lt;td&gt;48.2 (38.7-51.1)&lt;/td&gt;
&lt;td&gt;43.7&lt;/td&gt;
&lt;td&gt;35.8 (22.0-39.0)&lt;/td&gt;
&lt;td&gt;6.3&lt;/td&gt;
&lt;td&gt;561&lt;/td&gt;
&lt;td&gt;22.8 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B 4-bit, DFlash2 (bf16 drafter)&lt;/td&gt;
&lt;td&gt;36.9&lt;/td&gt;
&lt;td&gt;35.1&lt;/td&gt;
&lt;td&gt;22.3&lt;/td&gt;
&lt;td&gt;5.9&lt;/td&gt;
&lt;td&gt;568&lt;/td&gt;
&lt;td&gt;25.3 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp, no acceleration&lt;/td&gt;
&lt;td&gt;27.0&lt;/td&gt;
&lt;td&gt;26.4&lt;/td&gt;
&lt;td&gt;24.4&lt;/td&gt;
&lt;td&gt;21.4&lt;/td&gt;
&lt;td&gt;545&lt;/td&gt;
&lt;td&gt;22.2 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp, Lightning MTP&lt;/td&gt;
&lt;td&gt;48.0 (42.6-58.6)&lt;/td&gt;
&lt;td&gt;37.7&lt;/td&gt;
&lt;td&gt;41.1&lt;/td&gt;
&lt;td&gt;35.4&lt;/td&gt;
&lt;td&gt;499&lt;/td&gt;
&lt;td&gt;24.4 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp, Lightning MTP + TurboQuant 4-bit&lt;/td&gt;
&lt;td&gt;42.2&lt;/td&gt;
&lt;td&gt;37.7&lt;/td&gt;
&lt;td&gt;39.7&lt;/td&gt;
&lt;td&gt;30.3&lt;/td&gt;
&lt;td&gt;547&lt;/td&gt;
&lt;td&gt;24.4 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp, Lightning MTP + ANE prefill&lt;/td&gt;
&lt;td&gt;35.6&lt;/td&gt;
&lt;td&gt;33.1&lt;/td&gt;
&lt;td&gt;35.8&lt;/td&gt;
&lt;td&gt;30.9&lt;/td&gt;
&lt;td&gt;505&lt;/td&gt;
&lt;td&gt;28.3 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp, DFlash2 (4-bit drafter)&lt;/td&gt;
&lt;td&gt;41.4&lt;/td&gt;
&lt;td&gt;33.8&lt;/td&gt;
&lt;td&gt;18.8 (16.6-21.9)&lt;/td&gt;
&lt;td&gt;5.8&lt;/td&gt;
&lt;td&gt;458&lt;/td&gt;
&lt;td&gt;23.3 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B 8-bit, no acceleration&lt;/td&gt;
&lt;td&gt;31.0&lt;/td&gt;
&lt;td&gt;30.5&lt;/td&gt;
&lt;td&gt;30.1&lt;/td&gt;
&lt;td&gt;28.8&lt;/td&gt;
&lt;td&gt;955&lt;/td&gt;
&lt;td&gt;14.5 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B 8-bit, VLM MTP&lt;/td&gt;
&lt;td&gt;65.6&lt;/td&gt;
&lt;td&gt;42.7&lt;/td&gt;
&lt;td&gt;50.6&lt;/td&gt;
&lt;td&gt;49.0&lt;/td&gt;
&lt;td&gt;940&lt;/td&gt;
&lt;td&gt;15.5 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 12B 8-bit, DFlash (4-bit drafter)&lt;/td&gt;
&lt;td&gt;92.4 (35.5-158.0)&lt;/td&gt;
&lt;td&gt;34.4&lt;/td&gt;
&lt;td&gt;53.4&lt;/td&gt;
&lt;td&gt;51.2&lt;/td&gt;
&lt;td&gt;997&lt;/td&gt;
&lt;td&gt;18.1 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-oss-20b, no acceleration&lt;/td&gt;
&lt;td&gt;120.2&lt;/td&gt;
&lt;td&gt;106.5&lt;/td&gt;
&lt;td&gt;100.4&lt;/td&gt;
&lt;td&gt;78.8&lt;/td&gt;
&lt;td&gt;3,491&lt;/td&gt;
&lt;td&gt;12.4 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DFlash figures with a 1k prompt varied widely between repeats: on Gemma 4 12B they were 35.5, 92.4 and 158.0 tok/s. Speed depends on how many draft tokens the large model accepts, and that changes with the text it has to continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compared with 0.3.8
&lt;/h3&gt;

&lt;p&gt;The May table and this one are not fully comparable. That one was measured on the Qwen3.6-35B-A3B-MLX-8bit copy with 128 output tokens; this one, on the mlx-community copy with 256. Even so, the long-context improvement is too large to come from those differences:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Qwen3.6-35B-A3B 8-bit, built-in benchmark&lt;/th&gt;
&lt;th&gt;0.3.8 (May)&lt;/th&gt;
&lt;th&gt;0.6.4 (September)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decode with a 1k prompt&lt;/td&gt;
&lt;td&gt;72.4 tok/s&lt;/td&gt;
&lt;td&gt;92.1 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode with a 4k prompt&lt;/td&gt;
&lt;td&gt;83.1 tok/s&lt;/td&gt;
&lt;td&gt;91.1 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode with a 32k prompt&lt;/td&gt;
&lt;td&gt;15.2 tok/s&lt;/td&gt;
&lt;td&gt;77.9 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First token with a 1k prompt&lt;/td&gt;
&lt;td&gt;577 ms&lt;/td&gt;
&lt;td&gt;298 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  End-to-end verification
&lt;/h2&gt;

&lt;p&gt;A curl call to the OpenAI-compatible API to confirm the server is alive and a model responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:8000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer your_api_key_here"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "Qwen3-Coder-30B-A3B-Instruct-mlx-8bit",
    "messages": [{"role": "user", "content": "Hello from oMLX"}]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And from Claude Code, once you have exported the variables from the previous section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--print&lt;/span&gt; &lt;span class="s2"&gt;"Are you running locally?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reply should come back from local without touching &lt;code&gt;api.anthropic.com&lt;/code&gt;. To confirm at the network level, open Activity Monitor → Network, filter by the &lt;code&gt;claude&lt;/code&gt; process and check that the outbound connection is against &lt;code&gt;127.0.0.1:8000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lc19o6a8f01gkyx5xjt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lc19o6a8f01gkyx5xjt.webp" alt="oMLX Chat view at localhost:8000/admin/chat with the model dropdown open, listing the four chat-completion models available locally (both Huihui-Qwen3.6-35B-A3B-Claude-4.7-Opus variants, Qwen3.5-9B-Claude-4.6-HighIQ, and Qwen3.6-40B-Claude-4.6-Opus-Deckard-Thinking); the embeddings and reranker models do not appear here because they are not chat-completion" width="800" height="778"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What has changed since 0.3.8
&lt;/h2&gt;

&lt;p&gt;This guide was written against oMLX 0.3.8 in May 2026. The current version is &lt;strong&gt;0.6.4, released on 29 August 2026&lt;/strong&gt;, and more than a dozen releases in between touch exactly what is tuned here. What follows is the summary of what changed, with the figures attributed to whoever measured them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lightning MTP speculative decoding
&lt;/h3&gt;

&lt;p&gt;This is the change that affects this configuration most. &lt;a href="https://github.com/jundot/omlx/releases/tag/v0.5.0" rel="noopener noreferrer"&gt;Version 0.5.0, from 10 July 2026&lt;/a&gt;, added native depth-k speculative decoding for Qwen3.6-27B, Qwen3.6-35B-A3B, DeepSeek-V4-Flash and GLM-5.2.&lt;/p&gt;

&lt;p&gt;On an Apple M3 Ultra the project measured &lt;strong&gt;Qwen3.6-35B-A3B going from 89.6 to 140.4 tok/s, a 1.57x gain&lt;/strong&gt;. Qwen3.6-27B went from 35.0 to 55.1 tok/s. On this M5 Max, measured through the API on 15 September 2026, Lightning MTP took Qwen3.8-27B oQ4e from 21.1 to 39.7 tok/s. How to turn it on, and when another method is the better choice, is in &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/#advanced-settings-for-maximum-performance-on-0-6-4" rel="noopener noreferrer"&gt;the advanced settings&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom kernels, with a note for M5
&lt;/h3&gt;

&lt;p&gt;The same 0.5.0 added custom compute kernels for DeepSeek V4, Qwen3.5/3.6 and GLM-5.2, with prefill improvements measured on M3 Ultra. They are 45% on DeepSeek-V4-Flash (313.3 to 455.8 tok/s at 64k context), 33% on Qwen3.6-27B and 99% on GLM-5.2 at 32k. What matters for this machine is that dispatch is NAX-aware precisely to avoid regressions on the M5 series, so it is worth confirming the effect rather than assuming it.&lt;/p&gt;

&lt;h3&gt;
  
  
  oQ and oQe quantisation
&lt;/h3&gt;

&lt;p&gt;oMLX no longer depends only on the weights you download: it brings its own quantisation scheme. Version 0.5.0 added oQe, an activation-importance calibration pass with expert-coverage tracking for MoE models. In the project’s measurements oQ4e improves average accuracy over oQ4 while staying in the same disk-size class. That is 83.88% against 82.83% on Qwen3.6-35B-A3B, and 73.09% against 70.01% on Qwen3.5-9B.&lt;/p&gt;

&lt;h3&gt;
  
  
  The neural engine and cold starts
&lt;/h3&gt;

&lt;p&gt;The 0.6.3 branch worked on the prefill path over Apple’s neural engine. Two of the project’s figures matter day to day. Bank compilation memory dropped from 35.8 GB to 4.7 GB, and an optional persistent compile cache cuts fresh-process startup by 53% to 66%. Version 0.6.4 continued down that road and improved Qwen3.8-Flash-Next prompt processing by 33.5%, with 24.2% less total request time at 32k, again on M3 Ultra.&lt;/p&gt;

&lt;h3&gt;
  
  
  New endpoints
&lt;/h3&gt;

&lt;p&gt;The OpenAI- and Anthropic-compatible pair has been joined by &lt;code&gt;/v1/rerank&lt;/code&gt; for document reranking, &lt;code&gt;/v1/responses&lt;/code&gt; for Codex compatibility and &lt;code&gt;/v1/mcp/tools&lt;/code&gt; for Model Context Protocol. With an embedding model and a reranker loaded at once, a whole retrieval pipeline fits on the same server. The full breakdown is in &lt;a href="https://jacar.es/en/omlx-api-key-port-endpoints/" rel="noopener noreferrer"&gt;the guide to the API, the key and the port&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sub-key authentication
&lt;/h3&gt;

&lt;p&gt;Where there used to be a list of equivalent keys, there are now three credentials with different powers. A main key opens the API and the panel; sub-keys only call the API and cannot log into the panel or change settings; panel session tokens are held in a signed cookie. If you are handing access to more than one application, hand out sub-keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  The setting names
&lt;/h3&gt;

&lt;p&gt;The memory configuration in the earlier section still holds as judgement, but the keys are named differently in &lt;code&gt;~/.omlx/settings.json&lt;/code&gt;. The memory ceiling is now &lt;code&gt;memory_guard_tier&lt;/code&gt;, with four levels (&lt;code&gt;safe&lt;/code&gt;, &lt;code&gt;balanced&lt;/code&gt;, &lt;code&gt;aggressive&lt;/code&gt; and &lt;code&gt;custom&lt;/code&gt;), and the bespoke value goes in &lt;code&gt;memory_guard_custom_ceiling_gb&lt;/code&gt;. By default the ceiling is system RAM minus 8 GB. The two cache tiers are &lt;code&gt;hot_cache_max_size&lt;/code&gt; and &lt;code&gt;ssd_cache_max_size&lt;/code&gt;, and the concurrency limit is &lt;code&gt;max_concurrent_requests&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Watch one of them: &lt;code&gt;hot_cache_max_size&lt;/code&gt; ships as &lt;code&gt;"0"&lt;/code&gt;, which disables the hot cache in RAM. I go through it in &lt;a href="https://jacar.es/en/omlx-benchmarks-memory-ceiling/" rel="noopener noreferrer"&gt;benchmarks and the memory ceiling&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other pieces that did not exist
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Profiles&lt;/strong&gt; save named bundles of settings over a single model, exposed as &lt;code&gt;model:profile&lt;/code&gt; and at no extra memory cost. &lt;strong&gt;Aliases&lt;/strong&gt; rename a model as far as the API is concerned. And there is experimental &lt;strong&gt;distributed inference&lt;/strong&gt;, splitting a model’s layers across more than one Mac over SSH according to each machine’s memory, accounting for whether the link is Thunderbolt or 10GbE. The first two are in &lt;a href="https://jacar.es/en/omlx-admin-dashboard-cli/" rel="noopener noreferrer"&gt;the dashboard and command line guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/DiegoRBaquero/f53ab22ae978226c86158a60dad8199d" rel="noopener noreferrer"&gt;Diego R. Baquero, "Running Claude Code with a local LLM"&lt;/a&gt;: the tested configuration recipe this post’s settings are based on (TurboQuant 3.5-bit, 64k context, attribution off, Qwen 3.6 35B-A3B models).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vllm.ai/blog/2026-05-11-turboquant" rel="noopener noreferrer"&gt;A First Comprehensive Study of TurboQuant, vLLM blog, 11 May 2026&lt;/a&gt;: independent analysis confirming 3.5-bit matches full-precision quality.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omlx.ai/" rel="noopener noreferrer"&gt;omlx.ai/benchmarks&lt;/a&gt;: public leaderboard with real submissions from M5 Max and other machines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dasroot.net/posts/2026/04/m5-max-context-windows-memory-fragmentation-turbo-quant/" rel="noopener noreferrer"&gt;Maxing Out M5 Max Context Windows: Memory Fragmentation and TurboQuant Benchmarks (dasroot.net, April 2026)&lt;/a&gt;: the other detailed experiment on long-context M5 Max with TurboQuant.&lt;/li&gt;
&lt;li&gt;Repositories: &lt;a href="https://github.com/jundot/omlx" rel="noopener noreferrer"&gt;jundot/omlx&lt;/a&gt;, &lt;a href="https://github.com/ml-explore/mlx" rel="noopener noreferrer"&gt;ml-explore/mlx&lt;/a&gt;, &lt;a href="https://github.com/ml-explore/mlx-lm" rel="noopener noreferrer"&gt;ml-explore/mlx-lm&lt;/a&gt;, &lt;a href="https://huggingface.co/mlx-community" rel="noopener noreferrer"&gt;huggingface.co/mlx-community&lt;/a&gt;, &lt;a href="https://huggingface.co/unsloth" rel="noopener noreferrer"&gt;huggingface.co/unsloth&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you came here wondering what this actually is and how it differs from Apple’s MLX framework, I separate the two in &lt;a href="https://jacar.es/en/what-is-omlx/" rel="noopener noreferrer"&gt;what is oMLX&lt;/a&gt;. For a cross-platform alternative outside Apple Silicon, the &lt;a href="https://jacar.es/ollama-local-llm/" rel="noopener noreferrer"&gt;Ollama local LLM guide&lt;/a&gt; covers the model catalogue and OpenAI-compatible API. To wire the local endpoint into an agent, the &lt;a href="https://jacar.es/agente-anthropic-sdk-tutorial/" rel="noopener noreferrer"&gt;Anthropic SDK tutorial&lt;/a&gt; and the &lt;a href="https://jacar.es/mcp-multivendor-patrones/" rel="noopener noreferrer"&gt;MCP multi-vendor patterns&lt;/a&gt; already talk to this same format with no code changes.&lt;/p&gt;

&lt;p&gt;Related resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jacar.es/en/local-llm-calculator/" rel="noopener noreferrer"&gt;Calculator: which LLM fits your GPU?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jacar.es/en/rutas/local-llms/" rel="noopener noreferrer"&gt;Learning path: Local LLMs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to set an API key to use oMLX locally only?
&lt;/h3&gt;

&lt;p&gt;No. oMLX ships with the API key field empty, and as long as the instance only listens on 127.0.0.1 that is safe for local testing. The moment you expose it to the LAN or share the endpoint, go to Settings → Auth &amp;amp; Info and set a long random string. Current versions also offer sub-keys that can only call the API and cannot log into the panel or change settings, meant for handing access to more than one application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Claude Code from a MacBook against oMLX running on a Mac Studio?
&lt;/h3&gt;

&lt;p&gt;Yes, through SSH port forwarding: &lt;code&gt;ssh -L 8000:localhost:8000 user@mac-studio&lt;/code&gt; makes 127.0.0.1:8000 on the laptop point at the Studio's oMLX. Then export the same variables (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN and the ANTHROPIC_DEFAULT_*_MODEL set) in a script such as &lt;code&gt;claude-local.sh&lt;/code&gt;, make it executable with &lt;code&gt;chmod +x&lt;/code&gt; and run &lt;code&gt;claude --bare&lt;/code&gt; from the SSH session. If you set a key in Settings → Auth &amp;amp; Info, replace the empty string with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are the benchmarks in this post still valid if I install 0.6.4?
&lt;/h3&gt;

&lt;p&gt;The May tables, no: they were measured on 0.3.8, and when I re-ran them on 15 September 2026, 0.6.4 was faster on this same machine. Without acceleration, Qwen3.6-35B-A3B 8-bit goes from 15.2 to 77.9 tok/s with a 32k prompt. Lightning MTP or VLM MTP add between 1.07x and 1.88x, depending on the model and the context. If you measure on your own Mac, compare through the API and not only with Bench → Performance, which runs vision models on a different engine when MTP is off.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://jacar.es/en/how-to-install-and-tune-omlx-on-m5-max-128-gb/" rel="noopener noreferrer"&gt;jacar.es&lt;/a&gt;, where the benchmarks are kept up to date with each oMLX release.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>llm</category>
      <category>ai</category>
      <category>apple</category>
    </item>
  </channel>
</rss>
