<?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: Sandeep Das</title>
    <description>The latest articles on DEV Community by Sandeep Das (@sandeep_das_615685faf5ed5).</description>
    <link>https://dev.to/sandeep_das_615685faf5ed5</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%2F4225167%2Fe31de750-1b4c-43e9-89b0-81d6811f76d3.png</url>
      <title>DEV Community: Sandeep Das</title>
      <link>https://dev.to/sandeep_das_615685faf5ed5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandeep_das_615685faf5ed5"/>
    <language>en</language>
    <item>
      <title>Getting MTP to actually work on Apple Silicon</title>
      <dc:creator>Sandeep Das</dc:creator>
      <pubDate>Wed, 04 Nov 2026 00:52:14 +0000</pubDate>
      <link>https://dev.to/sandeep_das_615685faf5ed5/getting-mtp-to-actually-work-on-apple-silicon-2jol</link>
      <guid>https://dev.to/sandeep_das_615685faf5ed5/getting-mtp-to-actually-work-on-apple-silicon-2jol</guid>
      <description>&lt;p&gt;Qwen3.5 and Qwen3.6 ship with a small extra prediction head bundled into the model. The literature calls it MTP, short for Multi-Token Prediction. The point of having it is to use the head as a draft model for speculative decoding: it spits out K candidate tokens fast, and the main model verifies all K in one parallel forward pass. When the head's guesses turn out to match what the main model would have produced anyway, you get those tokens essentially free.&lt;/p&gt;

&lt;p&gt;We expected this to be a straightforward feature to wire through OptiQ: hook into the serving stack's already-patched &lt;code&gt;stream_generate&lt;/code&gt; path, route through our &lt;code&gt;OptiqEngine&lt;/code&gt;, run a bench, and publish the speedup. We budgeted about an afternoon for it.&lt;/p&gt;

&lt;p&gt;It took longer. The wiring itself worked on the first try, but the surrounding pieces did not. We found three separate issues, fixed them in order, and ended up at the same 1.4x speedup unsloth publishes for the same model class on an RTX 6000. This is what each fix was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory
&lt;/h2&gt;

&lt;p&gt;OptiQ's &lt;code&gt;install_mtp_speculation&lt;/code&gt; patched mlx-lm's &lt;code&gt;stream_generate&lt;/code&gt; so that when &lt;code&gt;--mtp&lt;/code&gt; was on, generation routed through our &lt;code&gt;OptiqEngine&lt;/code&gt;. The engine, on first request, called &lt;code&gt;mtplx_load(model_path)&lt;/code&gt;, which calls &lt;code&gt;mlx_lm.load&lt;/code&gt; internally. The catch: &lt;code&gt;mlx-lm.server&lt;/code&gt; had already loaded the same model. So we were holding two full copies of the base weights in unified memory.&lt;/p&gt;

&lt;p&gt;For Qwen3.5-9B this meant peak memory went from 6.5 GB without MTP to 16.7 GB with it. The MTP head itself is only about 185 MB. The other 10 GB was the duplicate. On a 24 GB Mac this also explained why 27B refused to run with MTP at all. Two copies of a 17 GB model do not fit anywhere.&lt;/p&gt;

&lt;p&gt;The fix was an &lt;code&gt;OptiqEngine.from_loaded(model, tokenizer, path)&lt;/code&gt; classmethod that takes the already-loaded model and injects MTP support into it in place. After the fix, 9B MTP peak dropped to 6.7 GB. 27B MTP fit at 17.6 GB peak, which is what actually made benching it possible at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verify was wrong at temperature
&lt;/h2&gt;

&lt;p&gt;Greedy decoding worked fine. The verify took the main model's argmax and compared it to the drafted token. If they matched, accept. Standard greedy spec decoding.&lt;/p&gt;

&lt;p&gt;At &lt;code&gt;temp &amp;gt; 0&lt;/code&gt; the same code path applied, just with sampled tokens instead of argmaxes. Sample from the main model's softmax, compare to the drafted token, accept on equality. This is wrong in a subtle way: both sides are now independent samples from their respective distributions. Two independent samples almost never match exactly.&lt;/p&gt;

&lt;p&gt;We measured this on 4B with &lt;code&gt;temp=1.0, top_p=0.95, top_k=20&lt;/code&gt; (the recipe Qwen ships in their &lt;code&gt;generation_config.json&lt;/code&gt;). Acceptance came out at 28 percent. The greedy version on the same model was 67 percent.&lt;/p&gt;

&lt;p&gt;The correct probabilistic verify is rejection sampling, due to Leviathan et al and Chen et al. You accept draft &lt;code&gt;dt&lt;/code&gt; with probability &lt;code&gt;min(1, p_main(dt) / q_draft(dt))&lt;/code&gt;. If you reject, you sample a replacement from the residual distribution &lt;code&gt;(p_main - q_draft)_+&lt;/code&gt; rescaled. Rewriting the verify to do this raised 4B acceptance at the same sampler from 28 percent to 57 percent, and throughput went from 0.87x base (a regression) to 1.11x.&lt;/p&gt;

&lt;h2&gt;
  
  
  Truncation has to apply on both sides
&lt;/h2&gt;

&lt;p&gt;Rejection sampling fixed the catastrophic case, but our &lt;code&gt;temp=1.0&lt;/code&gt; numbers were still below greedy. We were losing 8 to 17 percent across model sizes versus the greedy 1.36x at 27B.&lt;/p&gt;

&lt;p&gt;Unsloth's published numbers report around 83 percent acceptance at this model class. We were at 57 percent. The gap turned out to be sampling truncation. Unsloth's MTP guide specifies &lt;code&gt;temp=1.0&lt;/code&gt; with &lt;code&gt;top_p=0.95&lt;/code&gt; and &lt;code&gt;top_k=20&lt;/code&gt;. Our verify was using the untruncated softmax even though the draft path was applying &lt;code&gt;top_p&lt;/code&gt; and &lt;code&gt;top_k&lt;/code&gt;. Truncating both the draft and verify distributions to the same support (top 20 tokens, top 0.95 cumulative mass) makes the L1 overlap between them much higher, and that overlap is exactly what rejection sampling acceptance measures.&lt;/p&gt;

&lt;p&gt;After applying the same truncation on both sides, acceptance on 9B jumped from 32 percent to 63 percent. Throughput jumped from 1.00x to 1.28x.&lt;/p&gt;

&lt;p&gt;We also added the model's recommended sampler from &lt;code&gt;generation_config.json&lt;/code&gt; as a default in &lt;code&gt;optiq serve&lt;/code&gt; and &lt;code&gt;optiq lab&lt;/code&gt;. Without that, users who set just &lt;code&gt;--temp 1.0&lt;/code&gt; would see the same diffuse-distribution behavior we hit. The new default reads &lt;code&gt;top_p&lt;/code&gt; and &lt;code&gt;top_k&lt;/code&gt; from the model's published settings unless the user passes them explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about depth 2 or higher
&lt;/h2&gt;

&lt;p&gt;vLLM, HuggingFace Transformers, and unsloth's MTP guide all use K=2 or higher as defaults. We tested K=2 through K=4 on all three model sizes and found K=1 universally wins on Apple Silicon. The numbers, greedy:&lt;/p&gt;

&lt;p&gt;| Model | K=1 | K=2 | K=3 | K=4&lt;/p&gt;

&lt;p&gt;| Qwen3.5-9B | 1.28x | 1.19x | 0.89x | 0.74x&lt;/p&gt;

&lt;p&gt;| Qwen3.6-27B | 1.36x | 1.34x | 0.94x | 0.74x&lt;/p&gt;

&lt;p&gt;The reason is hardware. On CUDA the K-token verify forward is nearly free because Tensor Cores have spare throughput during single-token decode, so adding tokens to verify just uses idle compute. Apple Silicon's Metal backend has a compute-to-bandwidth ratio roughly 10x lower than H100. K=2 verify on Metal costs about 2x what K=1 verify costs, not the 1.1x that CUDA gets.&lt;/p&gt;

&lt;p&gt;Tokens-per-verify-cost works out like this:&lt;/p&gt;

&lt;p&gt;| Outcome | Tokens | Cost | Tokens/cost&lt;/p&gt;

&lt;p&gt;| K=1 full accept | 2 | 1x | 2.00&lt;/p&gt;

&lt;p&gt;| K=2 full accept | 3 | 2x | 1.50&lt;/p&gt;

&lt;p&gt;| K=2 partial (1/2) | 2 | 2x | 1.00&lt;/p&gt;

&lt;p&gt;K=1's best case is already higher than K=2's best case on Apple Silicon. No algorithmic trick beats this without a custom Metal kernel that reduces the K-token verify cost.&lt;/p&gt;

&lt;p&gt;We also tried HuggingFace's dynamic depth heuristic, where K rises after a fully accepted cycle and falls on partial accept. It lost 4 to 17 percent across all sizes versus fixed K=1. The adapter cannot find any working pattern of K=2 cycles that outweighs the cost when the cost ratio is 2x.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we landed
&lt;/h2&gt;

&lt;p&gt;The OptiQ 0.1.0 MTP numbers, M4 Pro 24 GB, greedy decoding, 512-token generation:&lt;/p&gt;

&lt;p&gt;| Model | Base tok/s | MTP tok/s | Speedup&lt;/p&gt;

&lt;p&gt;| Qwen3.5-4B | 29.2 | 35.0 | 1.20x&lt;/p&gt;

&lt;p&gt;| Qwen3.5-9B | 19.5 | 25.8 | 1.32x&lt;/p&gt;

&lt;p&gt;| Qwen3.6-27B | 6.0 | 8.4 | 1.40x&lt;/p&gt;

&lt;p&gt;With Qwen's recommended sampler (&lt;code&gt;temp=1.0, top_p=0.95, top_k=20&lt;/code&gt;), the same range becomes 1.09x / 1.17x / 1.30x. The 27B greedy number is within 5 percent of unsloth's published 1.4x on the same model class running on an RTX 6000. We will take that on a Mac.&lt;/p&gt;

&lt;p&gt;For 0.1.0 we ship K=1 with the numbers above. The &lt;a href="https://mlx-optiq.com/docs/speculative?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=crosspost#mtp" rel="noopener noreferrer"&gt;MTP guide&lt;/a&gt; has the reference table, methodology, and the &lt;code&gt;--mtp&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Originally published on the &lt;a href="https://mlx-optiq.com/blog/mtp-on-apple-silicon?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=crosspost" rel="noopener noreferrer"&gt;OptiQ blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>llm</category>
      <category>apple</category>
      <category>performance</category>
    </item>
    <item>
      <title>An AI visibility tool scanned itself and scored 3 out of 100</title>
      <dc:creator>Sandeep Das</dc:creator>
      <pubDate>Wed, 04 Nov 2026 00:35:00 +0000</pubDate>
      <link>https://dev.to/sandeep_das_615685faf5ed5/an-ai-visibility-tool-scanned-itself-and-scored-3-out-of-100-1b4l</link>
      <guid>https://dev.to/sandeep_das_615685faf5ed5/an-ai-visibility-tool-scanned-itself-and-scored-3-out-of-100-1b4l</guid>
      <description>&lt;p&gt;meraGEO checks whether ChatGPT, Claude and Gemini mention a product when people ask them for recommendations in its category. On October 28 the stored scan of merageo.com came back with a visibility score of 3 out of 100, and a label of "Invisible".&lt;/p&gt;

&lt;p&gt;Here is the breakdown, from eight category questions per model:&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;Answers that mentioned meraGEO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT&lt;/td&gt;
&lt;td&gt;1 of 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude&lt;/td&gt;
&lt;td&gt;0 of 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;0 of 8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The names that did come up in those answers were Profound, Peec AI and Otterly.AI.&lt;/p&gt;

&lt;p&gt;The part I found most useful is what the site already had. merageo.com serves an llms.txt, a sitemap and a robots.txt, and all three were live when I checked them on November 3. That is the checklist a lot of GEO advice stops at, and the site still scored 3. It doesn't prove those files are useless, since one scan can't show that, but they clearly weren't enough here.&lt;/p&gt;

&lt;p&gt;My read is that assistants lean on what other sites say about a product, and a product only gets named once other people are writing about it. That is a hypothesis, not a finding. A score from 24 answers on one day is a small sample, and answers vary from run to run, so a move of a few points wouldn't mean much.&lt;/p&gt;

&lt;p&gt;So this is the baseline rather than a success story. Over the next few weeks the plan is to get listed on the directories that competitors appear on, publish honest comparison pages, and keep writing in places like this one. Then I'll rescan on December 1 and publish the number, whichever way it goes.&lt;/p&gt;

&lt;p&gt;You can see the current result here: &lt;a href="https://merageo.com/scan/merageo.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=own_case_study" rel="noopener noreferrer"&gt;merageo.com's scan&lt;/a&gt;. Checking your own site is free.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>startup</category>
      <category>marketing</category>
    </item>
  </channel>
</rss>
