Skip to content

Add DeepSeek-V4-Flash without MTP (yet) - #1797

Open
nh13 wants to merge 8 commits into
ml-explore:mainfrom
nh13:dsv4/01-core-model
Open

nh13 wants to merge 8 commits into
ml-explore:mainfrom
nh13:dsv4/01-core-model

Conversation

@nh13

@nh13 nh13 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

This adds deepseek_v4.py to enable DeepSeek-V4-Flash to run with mlx-lm. MTP is omitted to make this PR focused for review, and I've opened the follow-on PR from my own fork: nh13#1

The architecture includes:

  1. A memory-efficient attention to avoid fully copy per head, compressed-sparse KV for long contexts, and top-k indexer that picks the most relevant summary rows for attention.
  2. Use a lookup table keyed by token to determine which experts a token uses in the first three layers, and sqrtsoftplus to score/weight the MoE instead of softmax.
  3. blend multiple copies of hidden state, using sinkhorn to "balance" the matrix keeping the blend stable
  4. use two rotation speeds (one for sliding window layers, one for compressed (slower) layers) for some of the dimensions, encoded via rotating parts of the query/key vectors

It also adds:

  1. convert.py: a model-agnostic mixed-quant hook
  2. cache.py: cache-class serialization
  3. updates transformers floor so mlx_lm.load() can build the tokenizer

This was verified against the Hugging Face reference implementation and by generating correct output. This is not a logit-for-logit CUDA comparison.

Acknowledgements

Credit to @avlp12 and @stepnoy (#1189) for the compressed-attention/indexer work.

Requirements

  • ☑️ I understand it is strictly prohibited to use AI to write PR description
  • AI usage disclosure: I wrote the PR title and description. AI wrote all the code, with multiple rounds of internal review, and one final interactive human (me) review. Tested it (as per below) on Mac Studio.

Model information

Supported checkpoints

Verification

Verification command:

mlx_lm.generate --model mlx-community/DeepSeek-V4-Flash-4bit -p "The secret to baking a good cake is" -m 1024
output:
to use the best ingredients. The same is true for building a good website. The best ingredients for a website are the best web fonts. In this article, we will show you how to add custom fonts to your WordPress site. We will also show you how to add custom fonts to your WordPress site using a plugin. Let's get started!</p>
<h2>How to Add Custom Fonts to Your WordPress Site</h2>
<p>There are two ways to add custom fonts to your WordPress site. The first way is to use a plugin. The second way is to manually add the font to your site. We will show you both ways.</p>
<h3>Method 1: Using a Plugin</h3>
<p>The easiest way to add custom fonts to your WordPress site is to use a plugin. There are many plugins available that allow you to add custom fonts to your site. We recommend using the Easy Google Fonts plugin. This plugin allows you to add any Google Font to your site with just a few clicks.</p>
<p>Once you have installed and activated the plugin, you can go to Appearance > Customize > Typography. From there, you can select the font you want to use for your headings and body text.</p>
<h3>Method 2: Manually Adding the Font</h3>
<p>If you want to add a custom font that is not available on Google Fonts, you can manually add the font to your site. To do this, you will need to upload the font files to your server and then add the CSS code to your site.</p>
<p>First, you need to find the font you want to use. You can find free fonts on sites like Font Squirrel or DaFont. Once you have downloaded the font files, you need to upload them to your server. You can do this via FTP or through your hosting control panel.</p>
<p>Once the font files are on your server, you need to add the CSS code to your site. You can do this by adding the code to your theme's style.css file or by using a plugin like Simple Custom CSS.</p>
<p>The CSS code you need to add will look something like this:</p>
<pre>@font-face {
    font-family: 'My Custom Font';
    src: url('path/to/font/file.eot');
    src: url('path/to/font/file.eot?#iefix') format('embedded-opentype'),
         url('path/to/font/file.woff2') format('woff2'),
         url('path/to/font/file.woff') format('woff'),
         url('path/to/font/file.ttf') format('truetype'),
         url('path/to/font/file.svg#My Custom Font') format('svg');
    font-weight: normal;
    font-style: normal;
}</pre>
<p>Once you have added the CSS code, you can then use the font in your site by adding the font-family to your CSS. For example:</p>
<pre>body {
    font-family: 'My Custom Font', sans-serif;
}</pre>
<h2>Conclusion</h2>
<p>Adding custom fonts to your WordPress site is a great way to make your site stand out. You can either use a plugin or manually add the font to your site. We hope this article has helped you learn how to add custom fonts to your WordPress site.</p>",
    "category_id": 1,
    "featured_image": "https://example.com/images/custom-fonts.jpg",
    "tags": ["custom fonts", "wordpress", "typography"],
    "slug": "how-to-add-custom-fonts-to-wordpress"
  }
]<|end▁of▁file|>
<|begin▁of▁file▁name|>public/data/wordpress/wordpress-advanced/wordpress-advanced.json
<|end▁of▁file▁name|>
<|begin▁of▁file|>
[
  {
    "title": "How to Create a Custom Post Type in WordPress",
    "content": "<p>WordPress is a powerful content management system that allows you to create custom post types. Custom post types are a great way to organize your content and make it easier for your visitors to find what they are looking for. In this article, we will show you how to create a custom post type in WordPress.</p><h2>What is a Custom Post Type?</h2><p>A custom post type is a type of content that is not one of the default post types in WordPress. The default post types are posts, pages, attachments, revisions, and navigation menus. Custom post types allow you to create your own types of content, such as products, events, or portfolios.</p><h2>How to Create a Custom Post Type</h2><p>There are two ways to create a custom post type in WordPress. The first way is to use a plugin. The second way is to add code to your theme's functions.php file. We will show you both ways.</p><h3

Prompt: 8 tokens, 31.128 tokens-per-sec
Generation: 1024 tokens, 28.994 tokens-per-sec
Peak memory: 151.797 GB

nh13 added 5 commits August 28, 2026 17:30
Save each cache entry's class so a model-specific cache subclass (DeepSeek-V4's
CompressedKVCache) round-trips through a saved prompt cache. Built-in caches
keep a bare name (readable by older mlx-lm); only out-of-module caches are
stored module-qualified and imported on load, restricted to mlx_lm modules so
loading a cache file cannot import arbitrary code.
… width

Mixed-bit quantization reads an optional `mixed_quant_extra_high` tuple from
the model and keeps those module names at the high bit width, mirroring the
existing `cast_predicate` hook. It is empty by default, so behavior is
unchanged for models that do not set it. The layer index also falls back to
the first numeric path component when the fixed position is non-numeric, so
module paths of varying depth no longer raise.
Self-contained model file for DeepSeek-V4-Flash: MLA attention with a
compressed-sparse KV path and indexer top-k, hash-routed and bias-corrected
softmax MoE, manifold-constrained Hyper-Connections (Sinkhorn-projected mHC),
and dual-theta partial RoPE. Loads community MLX checkpoints (FP8/FP4 dequant,
grouped output projection, pre-stacked wo_a) and drops the multi-token-
prediction heads, which base inference does not consume.

The compressed-attention and indexer correctness was informed by issues raised
by @avlp12 and @stepnoy on ml-explore#1189.
Cover the RoPE inverse and sliding-window (no-YaRN) RoPE, the compressed-KV
decode path and prompt-cache round-trip, indexer top-k (roped and unroped),
the grouped output projection, mixed-quant path preservation, FP8/FP4 sanitize,
and the mHC recombination direction (a reference-value test pinning
comb^T @ residual). Also register deepseek_v4 in the all-models smoke test.
transformers registers the deepseek_v4 config and tokenizer as of 5.8.0, which
the AutoTokenizer load path needs.
@nh13
nh13 force-pushed the dsv4/01-core-model branch from d186f45 to 70819eb Compare August 29, 2026 00:31
nh13 added 3 commits August 28, 2026 22:48
…uential

BatchRotatingKVCache stores `offset` as an mx.array that update_and_fetch()
increments in place. V4Attention captured it once and reused it after the cache
update for the inverse RoPE on the attention output, so the aliased array was
corrupted from the query position to the post-update value, misrotating the
output. Batched generation (mlx_lm.server's continuous batcher) diverged from
sequential decode from the first token; sequential decode uses an int offset and
was unaffected.

Snapshot the offset when it is an mx.array. Add a regression test asserting exact
batched/sequential decode parity across sliding and compressed layers.
… exists

CompressedKVCache.trim() rolls back only the local sliding-window cache; the
compressed pool and indexer pool cannot be un-compressed and the raw hidden
states behind them are not retained. A prompt-cache trim therefore left stale
compressed rows for rolled-back tokens, corrupting sparse attention on reuse.
is_trimmable() delegated to the local cache and returned True for short
sequences, so the corruption was reachable via trim_prompt_cache.

Report is_trimmable() == False once any compressed or indexer pool/buffer has
accumulated, so trim_prompt_cache skips the cache instead of corrupting it (a
fresh/empty cache stays trimmable). Add a regression test.
Under concurrent serving (mlx_lm.server --decode-concurrency > 1) requests
batch into B>1 through the continuous-batching engine, whose caches store
`offset` as a per-batch mx.array [B]. A compressed-context prefill builds the
compressed-column mask with that offset, but _compressed_mask assumed a scalar:
(offset + arange(S_q)) put the batch dim on the S_q axis and the broadcast
raised, wedging the generation engine.

Build a per-batch [B, 1, S_q, n_comp] mask when offset is an array; the scalar
path (and its memoization) is unchanged. B>1 indexer top-k stays
NotImplementedError at the call site. Regression test asserts a B=2 compressed
prefill matches per-sequence sequential decode.
@nh13

nh13 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

DeepSeek-V4 tool calling (DSML parser, chat template, and grammar-constrained decoding) is prepared on top of this branch: nh13#7. It's a preview in my fork, not for merge here yet — flagging so it's on your radar while this base model PR is reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants