<?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: Aymen Naghmouchi</title>
    <description>The latest articles on DEV Community by Aymen Naghmouchi (@aymen94).</description>
    <link>https://dev.to/aymen94</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%2F126265%2Fab7def7f-9502-4591-bfe1-6c7a340d42ef.png</url>
      <title>DEV Community: Aymen Naghmouchi</title>
      <link>https://dev.to/aymen94</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aymen94"/>
    <language>en</language>
    <item>
      <title>NeuronDump: look inside any local LLM, follow a token, and make it smaller</title>
      <dc:creator>Aymen Naghmouchi</dc:creator>
      <pubDate>Sun, 25 Oct 2026 19:45:41 +0000</pubDate>
      <link>https://dev.to/aymen94/neurondump-look-inside-any-local-llm-and-make-it-smaller-o16</link>
      <guid>https://dev.to/aymen94/neurondump-look-inside-any-local-llm-and-make-it-smaller-o16</guid>
      <description>&lt;p&gt;If you run models locally with Ollama or llama.cpp, you've probably downloaded a few gigabytes of GGUF and treated it as a black box. I wanted to open that box: see the layers, the attention heads, the vocabulary, the chat template, and then ask two simple questions: &lt;strong&gt;why did the model pick this token, and which parts does it not actually need?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;NeuronDump&lt;/strong&gt;, a single Rust binary (MIT) with a CLI and a local web UI. It reads GGUF files directly, with nothing hard-coded per model, and everything (including the 3D engine) is bundled, so it works offline.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/aymen94/NeuronDump" rel="noopener noreferrer"&gt;https://github.com/aymen94/NeuronDump&lt;/a&gt;&lt;br&gt;
👉 &lt;strong&gt;Docs &amp;amp; illustrated intro:&lt;/strong&gt; &lt;a href="https://aymen.xyz/NeuronDump/" rel="noopener noreferrer"&gt;https://aymen.xyz/NeuronDump/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inspect&lt;/strong&gt;: architecture, layers (including hybrid SSM/attention models), attention heads, the vocabulary decoded and classified, the chat template as a decision tree, and weight statistics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph&lt;/strong&gt;: the model as a 3D (or 2D) dataflow graph you can open from sections down to single attention heads, neuron buckets and MoE experts. WebGPU, with an automatic WebGL 2 fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow a token&lt;/strong&gt;: the logit lens per layer, attention heatmaps per head, the most active FFN neurons, and a breakdown of how much each layer and head pushed the chosen token against the runner-up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit live&lt;/strong&gt;: scale or zero any part, skip a layer, ban or boost tokens, steer, patch activations, change the template, and compare baseline vs edited, with undo / redo and snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut and see the impact&lt;/strong&gt;: select any parts and instantly see the size saved, what gets disconnected, and whether llama.cpp still loads the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sweep and slim&lt;/strong&gt;: cut each layer (or head) in turn, rank them by damage, and write the least important ones out as a smaller GGUF plus an Ollama Modelfile.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The model graph
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neurondump web &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's qwen3.8:27b in the 3D city view. Every part is coloured by whether you can remove it: &lt;strong&gt;safe&lt;/strong&gt;, &lt;strong&gt;risky&lt;/strong&gt;, or &lt;strong&gt;breaks the model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jl9na105y01wo5mptk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jl9na105y01wo5mptk0.png" alt="The model graph of qwen3.8:27b in NeuronDump's 3D city view" width="799" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click any block to see its size, quantization and importance, expand it into its children, or add it to the cut selection. Type a tensor name like &lt;code&gt;blk.3.ffn_down&lt;/code&gt; and the graph opens to show it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Follow one token through the model
&lt;/h2&gt;

&lt;p&gt;This is the part I've been using the most. Pick a prompt and a token, and NeuronDump shows what every layer "thinks" the next token is.&lt;/p&gt;

&lt;p&gt;Here's llama3.1:8b on &lt;em&gt;"Peanut butter and"&lt;/em&gt;. The final answer is " jelly" (0.74). But up to layer 29, the logit lens reads "and" as grammar ("and/or"). " PB" shows up from layer 22, and only the last two layers switch to food.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvz2czxstw2gmvi79tagx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvz2czxstw2gmvi79tagx.png" alt="Token journey of llama3.1:8b on " width="799" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below that are the attention heatmaps and the decision breakdown. Every layer mostly attends to &lt;code&gt;&amp;lt;|begin_of_text|&amp;gt;&lt;/code&gt; (an attention sink), and in layer 30 a handful of heads decide it: " jelly" beats " banana" by 2.2 logits, with head 24 pushing for it and head 25 pushing against it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngy6e2voaq0zxabar66n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngy6e2voaq0zxabar66n.png" alt="Attention heatmaps per layer and per head, and the decision breakdown" width="799" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The generation timeline does the same for each generated token, and the uncertain steps stand out by their entropy. On &lt;em&gt;"The Mediterranean Sea is located between"&lt;/em&gt;, llama3.1:8b hesitates twice (" three" vs " Europe", then " Europe" vs " Africa"), and clicking a later step shows " Africa" winning at 0.99, with the push per layer and the heads behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fpmvcvrhu43fvjqe62d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1fpmvcvrhu43fvjqe62d.png" alt="Generation timeline of llama3.1:8b, with one step's decision breakdown open" width="799" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neurondump tokens llama3.2:1b &lt;span class="s2"&gt;"fn main() {"&lt;/span&gt; &lt;span class="nt"&gt;--chat&lt;/span&gt;     &lt;span class="c"&gt;# tokens, template tokens, nearest tokens&lt;/span&gt;
neurondump journey llama3.2:1b &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"The name of the star that Earth orbits is"&lt;/span&gt;
neurondump generate llama3.2:1b &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"The name of the star that Earth orbits is"&lt;/span&gt; &lt;span class="nt"&gt;--steps&lt;/span&gt; 8 &lt;span class="nt"&gt;--explain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Change the model and see what happens
&lt;/h2&gt;

&lt;p&gt;Every change runs in memory, next to the original: you get the text, top-k, KL divergence, perplexity, logit lens and decision breakdown for both. You can scale or zero a layer, block, head or single neuron, skip a layer, edit a token's embedding, ban or boost tokens, add a steering vector, patch activations from another prompt, or swap the system prompt and template.&lt;/p&gt;

&lt;p&gt;Edits stack, with undo / redo / restore all, named snapshots, and a portable &lt;code&gt;edits.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;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"llama3.2:1b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"edits"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero layer 7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layer.7"&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"steer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dear Sir or Madam, I am writing to formally request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hey dude, just wanna ask"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"strength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&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;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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neurondump edit llama3.2:1b &lt;span class="nt"&gt;--apply&lt;/span&gt; edits.json &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"The name of the star that Earth orbits is"&lt;/span&gt;   &lt;span class="c"&gt;# baseline vs edited&lt;/span&gt;
neurondump edit llama3.2:1b &lt;span class="nt"&gt;--apply&lt;/span&gt; edits.json &lt;span class="nt"&gt;--bake&lt;/span&gt; llama-edited.gguf &lt;span class="nt"&gt;--no-compare&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bake&lt;/strong&gt; writes the edits into a new GGUF (plus a Modelfile). The original file is never touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cut, sweep, slim
&lt;/h2&gt;

&lt;p&gt;Slimming is a two-step process: analyze first, then apply a plan you can review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neurondump analyze llama3.2:1b &lt;span class="nt"&gt;--out&lt;/span&gt; plan.json
neurondump slim llama3.2:1b &lt;span class="nt"&gt;--plan&lt;/span&gt; plan.json &lt;span class="nt"&gt;--only&lt;/span&gt; vocab &lt;span class="nt"&gt;--out&lt;/span&gt; small.gguf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plan is a plain JSON file, so you can inspect or edit it before anything gets written. &lt;code&gt;--only&lt;/code&gt; lets you restrict what gets cut (here, just the vocabulary).&lt;/p&gt;

&lt;p&gt;Or let it measure which layers matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;neurondump ablate llama3.2:1b &lt;span class="nt"&gt;--cut&lt;/span&gt; layer.7 &lt;span class="nt"&gt;--static&lt;/span&gt;                &lt;span class="c"&gt;# direct impact on the file&lt;/span&gt;
neurondump ablate llama3.2:1b &lt;span class="nt"&gt;--cut&lt;/span&gt; layer.7 &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"The name of the star that Earth orbits is"&lt;/span&gt;   &lt;span class="c"&gt;# simulated impact&lt;/span&gt;
neurondump sweep llama3.2:1b &lt;span class="nt"&gt;--corpus&lt;/span&gt; texts.txt &lt;span class="nt"&gt;--save-plan&lt;/span&gt; plan.json &lt;span class="nt"&gt;--top&lt;/span&gt; 2
neurondump slim llama3.2:1b &lt;span class="nt"&gt;--plan&lt;/span&gt; plan.json &lt;span class="nt"&gt;--out&lt;/span&gt; llama-14.gguf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Some things it showed me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek-R1 8B is Qwen3 8B with every weight slightly changed.&lt;/strong&gt; It has the same neurons in the same places, including one neuron in layer 6 that makes the internal state about 33× larger. Cut that layer and both models produce noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On llama3.2:1b, cutting the last layer made the model &lt;em&gt;more&lt;/em&gt; confident on one prompt&lt;/strong&gt;, even though, apart from the first two layers, no layer hurts perplexity on real text more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek's chat template emits tool-call markers that aren't in its vocabulary&lt;/strong&gt;, so each one gets split into 11 ordinary tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;llama3.1:8b knows the answer before it says it.&lt;/strong&gt; On "The name of the star that Earth orbits is", " Sun" is the top prediction from layer 21 to 26, then the last layers switch to " called", so it writes "…is called the Sun" instead of answering in one word.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Prebuilt &lt;strong&gt;Windows and Linux&lt;/strong&gt; binaries are on the &lt;a href="https://github.com/aymen94/NeuronDump/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;, with everything included (CLI, web UI, and running models). Unzip (or untar) and run &lt;code&gt;neurondump list&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or build from source (Rust 1.88+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aymen94/NeuronDump
&lt;span class="nb"&gt;cd &lt;/span&gt;NeuronDump
cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; crates/neurondump &lt;span class="nt"&gt;--features&lt;/span&gt; trace
neurondump list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave out &lt;code&gt;--features trace&lt;/code&gt; if you only want the static tools (inspect, graph, impact, slim, tokens, bake) and don't need to run models.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;neurondump&lt;/code&gt; with no arguments to choose between the interactive CLI and the web UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;It's early, and it has been tested mostly on Windows with Ollama models.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inference is CPU-only, with no KV cache, and all weights are loaded into RAM (an 8B Q4_K model needs about 5 GB).&lt;/li&gt;
&lt;li&gt;Running models (journey, timeline, simulation, live edits) covers the llama, qwen2/3, gemma (1–3), phi3 and bert families. MoE and hybrid models (like Qwen 3.5 / 3.8) get static analysis, static impact and bake only for now.&lt;/li&gt;
&lt;li&gt;Run-time-only edits (ban, boost, steering, patching) can't be baked into a GGUF.&lt;/li&gt;
&lt;li&gt;Zeroing heads or neurons keeps the file loadable but doesn't shrink it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Known gaps are listed in the &lt;a href="https://aymen.xyz/NeuronDump/limitations/" rel="noopener noreferrer"&gt;limitations page&lt;/a&gt; and in &lt;code&gt;TEST.md&lt;/code&gt;. If you try it on a model I haven't, I'd love to hear what you find: &lt;a href="https://github.com/aymen94/NeuronDump/issues" rel="noopener noreferrer"&gt;https://github.com/aymen94/NeuronDump/issues&lt;/a&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>opensource</category>
      <category>ai</category>
      <category>rust</category>
    </item>
    <item>
      <title>WAMR3D: A Native WebAssembly 3D Engine with a three.js-Style API and Its Own WebGPU Renderer</title>
      <dc:creator>Aymen Naghmouchi</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:06:55 +0000</pubDate>
      <link>https://dev.to/aymen94/wamr3d-a-native-webassembly-3d-engine-with-a-threejs-style-api-and-its-own-webgpu-renderer-9ok</link>
      <guid>https://dev.to/aymen94/wamr3d-a-native-webassembly-3d-engine-with-a-threejs-style-api-and-its-own-webgpu-renderer-9ok</guid>
      <description>&lt;p&gt;If you have ever shipped a three.js scene with thousands of animated objects, you know where the frame budget goes: JavaScript object updates, matrix math, and the cost of handing draw calls to the GPU. &lt;strong&gt;WAMR3D&lt;/strong&gt; is a new open-source attempt at that problem. It moves the scene graph, transforms, culling, and draw ordering into a &lt;strong&gt;Rust core compiled to WebAssembly&lt;/strong&gt;, pairs it with a &lt;strong&gt;WebGPU renderer written from scratch&lt;/strong&gt;, and keeps an API that reads like three.js.&lt;/p&gt;

&lt;p&gt;Here is what it is, what it measures, and where it still falls short.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://wamr3d.org/" rel="noopener noreferrer"&gt;wamr3d.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/wamr3d" rel="noopener noreferrer"&gt;wamr3d&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/aymen94/wamr3d" rel="noopener noreferrer"&gt;aymen94/wamr3d&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is WAMR3D?
&lt;/h2&gt;

&lt;p&gt;WAMR3D is a &lt;strong&gt;WebAssembly 3D rendering engine&lt;/strong&gt; with a Rust core and a separate WebGPU renderer. It is not a wrapper around three.js. It is also not a finished replacement for it. The project is young, and the README says so directly.&lt;/p&gt;

&lt;p&gt;Three parts make it up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A native core in WebAssembly.&lt;/strong&gt; The scene graph, world matrices, frustum culling, draw ordering, and raycasting run inside a Wasm module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its own WebGPU renderer.&lt;/strong&gt; Meshes, lines, points, sprites, and instanced meshes. Basic, Lambert, and standard materials. Colour textures, fog, up to 16 lights, PCF shadows, tone mapping, render targets, and 4× MSAA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A three.js-style object API&lt;/strong&gt; under &lt;code&gt;wamr3d/compat&lt;/code&gt;, plus a lower-level batched API for large scenes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The license is MIT. There are no runtime dependencies, and the Wasm module is about 488 KiB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a native core matters for three.js developers
&lt;/h2&gt;

&lt;p&gt;In most three.js apps, scene state lives in JavaScript objects. Every &lt;code&gt;position.set()&lt;/code&gt;, &lt;code&gt;rotation.y +=&lt;/code&gt;, and matrix update is JavaScript work, and the garbage collector eventually gets involved.&lt;/p&gt;

&lt;p&gt;WAMR3D takes a different approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The scene lives in engine memory.&lt;/strong&gt; Objects are created and destroyed explicitly in WebAssembly, so the engine itself adds no GC pauses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generational handles.&lt;/strong&gt; A handle to a destroyed object can never silently point at a different object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-copy geometry.&lt;/strong&gt; You can read and write vertex attributes through typed-array views directly over engine memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batched sync.&lt;/strong&gt; In the object API, position, rotation, and scale stay in JavaScript, and every change is sent to the engine in a single batch per frame.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is that WAMR3D wins when you batch work, and loses when you make many tiny calls. The project documents both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measured performance
&lt;/h2&gt;

&lt;p&gt;These are the numbers from the repo's benchmark record, recorded on 2026-10-14 in headless Chrome on Windows x64 with an RTX 2070 SUPER. They are medians from a single run per resolution on one machine. Treat them as a starting point, and measure your own scene.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10,000 separate animated meshes at 1920×1080:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;fps&lt;/th&gt;
&lt;th&gt;Draw submission (CPU)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WAMR3D, batched API&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;12.2 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WAMR3D, object API&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;12.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;three.js WebGPU&lt;/td&gt;
&lt;td&gt;9.5&lt;/td&gt;
&lt;td&gt;100.5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;three.js WebGL&lt;/td&gt;
&lt;td&gt;33.7&lt;/td&gt;
&lt;td&gt;23.7 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;50,000 instanced bodies, scene update on the CPU:&lt;/strong&gt; 12.0 ms for WAMR3D's batched API, against 39.1 ms for three.js WebGPU and 39.7 ms for three.js WebGL.&lt;/p&gt;

&lt;p&gt;Put simply: in this showcase, WAMR3D spends roughly &lt;strong&gt;8× less CPU time on draw submission than three.js WebGPU&lt;/strong&gt;, and about &lt;strong&gt;1.9× less than three.js WebGL&lt;/strong&gt;. The frame rate caps at 60 because the showcase has a display limit.&lt;/p&gt;

&lt;p&gt;The project also publishes the other side of the picture. Calls that do very little work, such as a single vector operation or one property at a time, measured &lt;strong&gt;1.3× to 3.8× slower&lt;/strong&gt; than three.js, because crossing into WebAssembly has a fixed cost. GPU time is not measured separately; it only shows up in the frame rate.&lt;/p&gt;

&lt;p&gt;If you want to check these yourself, the showcase page runs all four renderers live: &lt;a href="https://wamr3d.org/examples/showcase/" rel="noopener noreferrer"&gt;wamr3d.org/examples/showcase&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same results as three.js, checked by tests
&lt;/h2&gt;

&lt;p&gt;Speed means nothing if the output is wrong. WAMR3D replays &lt;strong&gt;over 7,600 reference cases&lt;/strong&gt; generated from the three.js r186 source against the Rust code. Individual operations agree to about one part in a million. All 23 renderer test cases are compared pixel by pixel against stored three.js images.&lt;/p&gt;

&lt;p&gt;The repo also includes 643 Rust tests covering unit, parity, and allocation behaviour, plus a visual parity page that shows each WAMR3D render next to the three.js image and the pixel difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started in two minutes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;wamr3d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the import, initialize once, and switch to the WebGPU renderer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- import * as THREE from 'three';
&lt;/span&gt;&lt;span class="gi"&gt;+ import * as THREE from 'wamr3d/compat';
+ await THREE.init();
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;- const renderer = new THREE.WebGLRenderer( { antialias: true } );
&lt;/span&gt;&lt;span class="gi"&gt;+ const renderer = new THREE.WebGPURenderer( { antialias: true } );
+ await renderer.init();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A complete example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wamr3d/compat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PerspectiveCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cube&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BoxGeometry&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshLambertMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x4f8cff&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DirectionalLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xffffff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WAMR3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGPURenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAnimationLoop&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&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;&lt;code&gt;scene.add&lt;/code&gt;, &lt;code&gt;position.set&lt;/code&gt;, &lt;code&gt;rotation.y +=&lt;/code&gt;, &lt;code&gt;lookAt&lt;/code&gt;, and raycasting work as written for the classes WAMR3D supports. The project's migration guide lists exactly which classes exist and what to watch for.&lt;/p&gt;

&lt;p&gt;Requirements: a browser with WebGPU (current Chrome or Edge), served over HTTP rather than &lt;code&gt;file://&lt;/code&gt;. Node.js 18 or newer can run the math and scene graph without a renderer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batching for large scenes
&lt;/h2&gt;

&lt;p&gt;When your scene grows past a few hundred objects, the batched API is where the gains come from. Handles and calls like &lt;code&gt;setRotations&lt;/code&gt; update thousands of objects in one crossing into WebAssembly. You read world matrices through typed-array views without copying them.&lt;/p&gt;

&lt;p&gt;The low-level API is also exported directly from &lt;code&gt;wamr3d&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;World&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wamr3d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;World&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XYZ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateMatrixWorld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matrixWorld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hand&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Float32Array(16), column-major&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details to know: matrices are column-major, and the low-level API has no default arguments, so rotation order and similar parameters are always written out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is not there yet
&lt;/h2&gt;

&lt;p&gt;This section matters most, so here it is in full. From the project's own status page, the following are &lt;strong&gt;not available yet&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment reflections (not connected to &lt;code&gt;scene.environment&lt;/code&gt; or &lt;code&gt;envMap&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Point-light shadows and area lights&lt;/li&gt;
&lt;li&gt;Post-processing and scene background textures&lt;/li&gt;
&lt;li&gt;Most loaders, animation, audio, and controls (the exception is &lt;code&gt;TextureLoader&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Shadow modes other than PCF&lt;/li&gt;
&lt;li&gt;A WebGL fallback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It has also been verified only in Node.js v24 and headless Chrome. Firefox, Safari, and bundler setups have not been tried yet.&lt;/p&gt;

&lt;p&gt;For those features, keep three.js for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;WAMR3D is a good fit if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have a three.js scene with many objects, and the bottleneck is JavaScript updates or draw submission&lt;/li&gt;
&lt;li&gt;Are experimenting with WebGPU and want a renderer that is not a thin wrapper&lt;/li&gt;
&lt;li&gt;Care about deterministic, checked behaviour, and want to see the parity tests&lt;/li&gt;
&lt;li&gt;Want to contribute to a Rust and WebAssembly graphics engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is probably not the right choice yet if you depend on post-processing, environment maps, loaders, animation, or Firefox and Safari support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, measure it, tell me what breaks
&lt;/h2&gt;

&lt;p&gt;The fastest way to judge WAMR3D is to run it on your own scene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live demos: &lt;a href="https://wamr3d.org/examples/webgpu/" rel="noopener noreferrer"&gt;WebGPU renderer&lt;/a&gt;, &lt;a href="https://wamr3d.org/examples/showcase/" rel="noopener noreferrer"&gt;showcase&lt;/a&gt;, &lt;a href="https://wamr3d.org/examples/compare/" rel="noopener noreferrer"&gt;side-by-side comparison&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Migration guide: &lt;a href="https://github.com/aymen94/wamr3d/blob/main/MIGRATION.md" rel="noopener noreferrer"&gt;MIGRATION.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Status and benchmarks: &lt;a href="https://github.com/aymen94/wamr3d/blob/main/STATUS.md" rel="noopener noreferrer"&gt;STATUS.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design decisions: &lt;a href="https://github.com/aymen94/wamr3d/blob/main/DECISIONS.md" rel="noopener noreferrer"&gt;DECISIONS.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project has one star and no forks so far, so early feedback will shape it a lot. Issues, benchmark results from other GPUs, and reports from Firefox or Safari are all useful. If you find a case where it is slower than three.js, please open an issue. The project explicitly values those results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/aymen94/wamr3d" rel="noopener noreferrer"&gt;Star it on GitHub&lt;/a&gt;&lt;/strong&gt;, and try &lt;code&gt;npm install wamr3d&lt;/code&gt; on a scene you already know.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;WAMR3D is an independent project, not affiliated with three.js. It is inspired by three.js and credits it throughout the repository.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>webgpu</category>
      <category>threejs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>OpenAPI to WebMCP in One Command</title>
      <dc:creator>Aymen Naghmouchi</dc:creator>
      <pubDate>Mon, 12 Oct 2026 19:47:43 +0000</pubDate>
      <link>https://dev.to/aymen94/openapi-to-webmcp-in-one-command-3cjc</link>
      <guid>https://dev.to/aymen94/openapi-to-webmcp-in-one-command-3cjc</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Introducing openapi-to-webmcp
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/openapi-to-webmcp" rel="noopener noreferrer"&gt;openapi-to-webmcp&lt;/a&gt;&lt;/strong&gt;, an open-source tool to generate WebMCP browser tools from OpenAPI specifications.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📄 Supports JSON and YAML specifications&lt;/li&gt;
&lt;li&gt;⚡ JavaScript by default, with TypeScript support&lt;/li&gt;
&lt;li&gt;🌐 Browser ES module available via CDN&lt;/li&gt;
&lt;li&gt;🛠️ CLI and programmatic API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx openapi-to-webmcp openapi.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package also provides a browser entry point for generating WebMCP modules from a normalized API object, without bundling the Node.js YAML parser.&lt;/p&gt;

&lt;p&gt;📦 &lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/openapi-to-webmcp" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/openapi-to-webmcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💻 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/aymen94/openapi-to-webmcp" rel="noopener noreferrer"&gt;https://github.com/aymen94/openapi-to-webmcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback and contributions are welcome😎!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>openapi</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
