antirez/ds4 - Moteur local d’inférence DeepSeek V4 Flash pour Metal
(github.com/antirez)- Moteur local d’inférence dédié à DeepSeek V4 Flash optimisé pour les GPU Apple Metal, avec une implémentation native en C concentrée sur un seul modèle plutôt qu’un runner GGUF générique
- DeepSeek V4 Flash utilise peu de paramètres actifs, ce qui offre une grande vitesse, et en mode thinking génère des segments de réflexion environ 5 fois plus courts que d’autres modèles
- Grâce à une fenêtre de contexte de 1 million de tokens et à un cache KV extrêmement compressé, l’inférence sur de longs contextes reste possible en local, avec prise en charge de la persistance sur disque du cache KV
- Intègre un serveur HTTP API compatible OpenAI et Anthropic, permettant une connexion immédiate avec divers agents de code comme Claude Code, opencode et Pi
- Construit sur les fondations de l’écosystème llama.cpp et GGML, ce projet a été développé avec le fort soutien en code de GPT 5.5
Vue d’ensemble du projet et philosophie de conception
ds4.cest un petit moteur natif d’inférence dédié à DeepSeek V4 Flash, et non un runner GGUF générique ni un wrapper d’un autre runtime- Le chemin principal est un exécuteur de graphe Metal spécialisé pour DeepSeek V4 Flash, avec chargement DS4 dédié, rendu de prompts, état KV et code de liaison pour l’API serveur
- Il existe de nombreux excellents projets dans l’inférence locale, mais l’attention se disperse à mesure que de nouveaux modèles apparaissent
- Ce projet choisit délibérément de se concentrer sur un modèle à la fois, jusqu’à inclure validation vectorielle officielle (logits), tests de longs contextes et intégration d’agents
- La vision de l’inférence locale : A) un moteur d’inférence avec API HTTP intégrée + B) un GGUF optimisé pour un moteur donné + C) des implémentations d’agents de code pour les tests et la validation, ces trois éléments devant fonctionner ensemble
- Réservé à Metal, avec une éventuelle prise en charge future de CUDA, mais rien n’est confirmé
- Le chemin CPU n’existe que pour la validation de la justesse et, en raison d’un bug d’implémentation de mémoire virtuelle dans la version actuelle de macOS, l’exécution du code CPU provoque actuellement un crash du noyau
- Développé avec le fort soutien de GPT 5.5, tandis que les humains ont piloté les idées, les tests et le débogage
Pourquoi créer un moteur séparé pour DeepSeek V4 Flash
- Son faible nombre de paramètres actifs permet une vitesse d’inférence plus élevée
- En mode thinking, il produit des segments de réflexion environ 5 fois plus courts que d’autres modèles, avec une longueur proportionnelle à la complexité du problème
- Même dans des situations où d’autres modèles sont peu exploitables en mode thinking, DeepSeek V4 Flash reste utilisable
- Prend en charge une fenêtre de contexte de 1 million de tokens
- Avec 284B paramètres, il connaît davantage d’informations à la frontière de la connaissance que des modèles 27B ou 35B
- La différence se constate par exemple sur des émissions de télévision italiennes ou des questions politiques
- La qualité de rédaction en anglais et en italien atteint un niveau quasi frontier model
- Le cache KV est extrêmement compressé, ce qui rend possible l’inférence sur de longs contextes sur un ordinateur local, avec prise en charge de la persistance sur disque du cache KV
- Avec une quantification spéciale, il fonctionne bien même en quantification 2 bits, ce qui permet de l’exécuter sur un MacBook avec 128 Go de RAM
- Une version mise à jour de V4 Flash de DeepSeek est attendue à l’avenir
Remerciements à llama.cpp et GGML
- ds4.c n’est pas lié à GGML, mais existe sur la voie ouverte par le projet llama.cpp
- Les kernels de llama.cpp, les formats de quantification, l’écosystème GGUF et le savoir-faire d’ingénierie hard-won ont servi de références essentielles
- Une partie du code source est conservée ou adaptée sous licence MIT : disposition et tables de quantification GGUF, logique CPU quant/dot, certains kernels Metal, etc.
- Les mentions de copyright des auteurs de GGML sont conservées dans le fichier LICENSE
Poids du modèle
- Seuls les GGUF DeepSeek V4 Flash publiés pour ce projet fonctionnent ; les fichiers DeepSeek/GGUF arbitraires ne sont pas compatibles
- La quantification 2 bits applique une méthode de quantification asymétrique
- Seuls les experts MoE sont quantifiés : up/gate en
IQ2_XXS, down enQ2_K - Les autres composants, comme les experts partagés, projections ou routage, ne sont pas quantifiés afin de garantir la qualité
- Seuls les experts MoE sont quantifiés : up/gate en
./download_model.sh q2télécharge le modèle pour les machines avec 128 Go de RAM,./download_model.sh q4pour celles avec 256 Go de RAM ou plus- Téléchargement depuis Hugging Face (
antirez/deepseek-v4-gguf) avec reprise des téléchargements partiels viacurl -C -
- Téléchargement depuis Hugging Face (
./download_model.sh mtppermet aussi de télécharger le GGUF pour la décodage spéculatif (speculative decoding) optionnel- Le chemin MTP/décodage spéculatif reste expérimental et n’apporte actuellement qu’un léger gain de vitesse
Benchmarks de vitesse
- Mesures obtenues en une seule exécution via la CLI Metal avec
--ctx 32768,--nothink, décodage greedy et-n 256 - MacBook Pro M3 Max, 128 Go (q2)
- Prompt court : prefill 58.52 t/s, génération 26.68 t/s
- Prompt de 11709 tokens : prefill 250.11 t/s, génération 21.47 t/s
- q4 : N/A faute de mémoire
- Mac Studio M3 Ultra, 512 Go (q2)
- Prompt court : prefill 84.43 t/s, génération 36.86 t/s
- Prompt de 11709 tokens : prefill 468.03 t/s, génération 27.39 t/s
- Mac Studio M3 Ultra, 512 Go (q4)
- Prompt court : prefill 78.95 t/s, génération 35.50 t/s
- Prompt de 12018 tokens : prefill 448.82 t/s, génération 26.62 t/s
Utilisation de la CLI
- L’option
-pexécute un prompt one-shot ; sans-p, on entre en mode chat interactif multi-tour - La CLI interactive conserve la transcription de chat rendue et des checkpoints KV Metal en direct, de sorte que chaque tour prolonge la conversation précédente
- Commandes utiles :
/help,/think,/think-max,/nothink,/ctx N,/read FILE,/quitCtrl+Cinterrompt la génération en cours et revient au prompt
- Le mode thinking est activé par défaut ; utilisez
/nothinkou--nothinkpour passer au mode de réponse directe --mtp MTP.gguf --mtp-draft 2permet d’activer le chemin spéculatif MTP en option- Utile uniquement avec le décodage greedy, avec une confidence gate (
--mtp-margin) pour éviter d’accepter des portions lentes
- Utile uniquement avec le décodage greedy, avec une confidence gate (
Serveur
- Possibilité d’exécuter un serveur HTTP local compatible OpenAI/Anthropic
- Réservé à Metal, il conserve en mémoire un seul graphe/checkpoint KV modifiable
- Si un client stateless renvoie une version plus longue du même prompt, il est possible de réutiliser le préfixe partagé
- Le parsing des requêtes et les sockets s’exécutent dans le thread client, mais l’inférence elle-même est sérialisée via un unique worker Metal
- Le serveur actuel ne regroupe pas plusieurs requêtes indépendantes ; les requêtes concurrentes attendent leur tour
-
Endpoints pris en charge
GET /v1/models,GET /v1/models/deepseek-v4-flashPOST /v1/chat/completions,POST /v1/completions,POST /v1/messages
-
/v1/chat/completions(compatible OpenAI)- Prend en charge
messages,max_tokens/max_completion_tokens,temperature,top_p,top_k,min_p,seed,stream,stream_options.include_usage,tools,tool_choice - Le schéma d’outils est rendu au format d’outils DSML de DeepSeek, puis les appels d’outils DSML générés sont reconvertis en appels d’outils OpenAI
- Prend en charge
-
/v1/messages(compatible Anthropic)- Endpoint destiné aux clients de style Claude Code
- Prend en charge
system,messages,tools,tool_choice,max_tokens,temperature,top_p,top_k,stream,stop_sequenceset les contrôles thinking - L’usage des outils est renvoyé sous forme de blocs Anthropic
tool_use
- Les deux API prennent en charge le streaming SSE et, en mode thinking, le processus de raisonnement est diffusé sous forme d’API native
Intégration avec des clients agents
- ds4-server peut être intégré à des agents de code locaux utilisant des chat completions compatibles OpenAI
- Avec 128 Go de RAM et une quantification 2 bits (81 Go), une fenêtre de contexte de 100k à 300k tokens est adaptée
- Le contexte complet de 1M tokens utilise environ 26 Go de mémoire (dont environ 22 Go pour l’indexeur compressé uniquement)
- Une limite de sortie à
384000permet d’éviter un plafond de tokens (le modèle peut générer jusqu’à 384k tokens) -
Intégration opencode
- Configuration via ajout des entrées provider et agent dans
~/.config/opencode/opencode.json - Définir
baseURLsurhttp://127.0.0.1:8000/v1
- Configuration via ajout des entrées provider et agent dans
-
Intégration Pi
- Ajouter la configuration provider dans
~/.pi/agent/models.json - Inclut des options de compatibilité pour le format thinking de DeepSeek, le reasoning effort et le streaming usage
- Peut être défini comme modèle par défaut dans
~/.pi/agent/settings.json
- Ajouter la configuration provider dans
-
Intégration Claude Code
- Utilise l’endpoint compatible Anthropic, avec script wrapper
~/bin/claude-ds4pour définir les variables d’environnement - Définir
ANTHROPIC_BASE_URLsur le serveur local et toutes les variables de modèle surdeepseek-v4-flash - Claude Code envoie initialement un gros prompt d’environ 25k tokens ; il est donc indispensable d’activer
--kv-disk-dir- Après le premier prefill coûteux, le cache KV disque réutilise le préfixe enregistré et évite de retraiter tout le prompt dans les sessions suivantes
- Utilise l’endpoint compatible Anthropic, avec script wrapper
Mode thinking
- DeepSeek V4 Flash prend en charge trois modes : non-thinking, thinking et Think Max
- Le serveur utilise par défaut le mode thinking
reasoning_effort=maxpermet de demander Think Max, mais seulement si la taille du contexte est suffisamment grande au regard des recommandations de la model card- Avec un petit contexte, le système revient au thinking standard
reasoning_effort=xhighd’OpenAI est mappé au thinking standard, pas à Think Max- Si une réponse directe est nécessaire, utilisez
thinking: {"type":"disabled"},think:falseou un alias de modèle non-thinking commedeepseek-chat
Cache KV sur disque
- Les API chat/completion sont stateless, donc les clients agents renvoient l’intégralité de la conversation à chaque requête
- ds4-server traite cela en comparant le flux de tokens rendu avec un préfixe de tokens en cache
- Le checkpoint en mémoire active prend en charge la session en cours
- Le cache KV disque est le mécanisme qui préserve des préfixes utiles au changement de session et après redémarrage du serveur
- Il n’existe actuellement qu’un seul cache KV actif en mémoire ; si une nouvelle session non liée le remplace, l’ancienne ne pourra reprendre sans retraitement que si son checkpoint a été écrit dans le cache KV disque
- Activez-le avec
--kv-disk-diret--kv-disk-space-mb -
Clés de cache et structure des fichiers
- La clé de cache est un hachage SHA1 des identifiants exacts des tokens, et non du texte brut
- Chaque identifiant de token est haché en entier 32 bits little-endian, avec des noms de fichiers au format
<sha1>.kv - L’écriture se fait avec des E/S
read/writeclassiques, sansmmap(afin d’éviter des mappings VM supplémentaires dans un processus qui mappe déjà le modèle)
-
Layout des fichiers de cache disque
- En-tête fixe KVC de 48 octets : magic("KVC"), version, bits de quantification des routed experts, raison de la sauvegarde, nombre de tokens en cache, compteur de hits, taille du contexte, timestamps Unix de création/dernier usage, taille en octets du payload de session DS4
- Texte rendu : texte décodé par le tokenizer du préfixe de tokens mis en cache (à des fins d’observation, pas utilisé comme clé)
- Payload de session DS4 : commence par 13 champs u32 little-endian, contenant notamment magic("DSV4"), version du payload, taille du contexte, taille du chunk de prefill, capacité de l’anneau KV, etc.
- Sont stockés les identifiants de tokens du checkpoint, les logits float32 du token suivant, le nombre de lignes d’attention compressée par couche, les lignes KV de la fenêtre glissante raw active, les lignes KV des couches compressées et les tenseurs frontier du compresseur, etc.
-
Moments où les checkpoints sont sauvegardés
cold: après qu’un long prompt initial a atteint un préfixe stable, avant la générationcontinued: lorsque le prefill ou la génération a progressé de l’intervalle configuréevict: avant qu’une requête non liée ne remplace la session active en mémoireshutdown: lors de l’arrêt propre du serveur
- Lors d’une sauvegarde cold, un petit suffixe de tokens est tronqué et aligné sur la frontière des chunks de prefill afin d’éviter de futurs ratés de retokenisation sur les frontières BPE
- Valeurs par défaut : préfixe minimal de 512 tokens, maximum de 30000 tokens pour une sauvegarde cold, tronquage des 32 tokens de queue, alignement sur des chunks de 2048 tokens
- Par défaut, les checkpoints peuvent être réutilisés entre variantes routed-expert 2 bits et 4 bits si le préfixe de tokens correspond
--kv-cache-reject-different-quantpermet de limiter la réutilisation à une même quantification
Backend
- Le backend par défaut est Metal (
--metal) - Un chemin CPU de référence/débogage existe aussi (
--cpu), mais n’est pas destiné à la production- Le serveur est réservé à Metal et l’implémentation optimisée se trouve dans le chemin de graphe Metal
- Licence MIT, implémentation en C/Objective-C/Metal
1 commentaires
Avis Hacker News
Je l’ai essayé avec Claude Code sur une base de code existante, et il semble faire le travail malgré le modèle quantifié en 2 bits
Le traitement du prompt prend quelques minutes, mais l’édition proprement dite est assez rapide, à plus de 20 tokens/s
Sur de petites tâches, il a réussi à explorer le code, appliquer des modifications et écrire des tests, mais il n’a pas réussi à corriger une remarque mineure
Plus gênant encore, il a halluciné en ramenant une conversation parallèle sur « The Duck » pendant qu’il résolvait un autre problème. C’est probablement un des exemples du prompt initial de Claude Code
J’avais déjà construit quelque chose de très similaire pour les modèles Qwen3. Ça ne lançait que Qwen3, ne prenait en charge que certaines quantifications, chargeait depuis GGUF et utilisait une inférence optimisée itérativement par Claude
L’ensemble tenait en quelques fichiers, restait simple à comprendre, et le projet était conçu pour que des étudiants puissent apprendre en expérimentant avec l’ajout de stratégies de décodage ou des choses comme l’abliteration. Les frameworks connus sont gros et complexes, donc difficiles à bidouiller, tandis que les projets pédagogiques restent souvent bloqués sur des choses anciennes comme GPT-2
C’était parti comme un projet éducatif, mais une idée me revient sans cesse : et si on construisait un moteur d’inférence ultra-optimisé pour une combinaison GPU+modèle précise ? Les GPU coûtent cher et deviennent de plus en plus difficiles à obtenir, donc en retirant suffisamment d’abstraction et en ciblant directement le matériel et le modèle exacts, on pourrait sans doute optimiser assez fortement
Le problème, c’est qu’il faut tout refaire depuis zéro dès que le modèle devient obsolète
Il reste encore des gains faciles sur les plateformes moins populaires, mais il n’y a pas énormément de marge pour obtenir des performances bien meilleures en construisant un exécuteur de modèle ultra-optimisé pour une famille de GPU donnée. Les calculs essentiels sont déjà pris en charge par des kernels hautement optimisés pour chaque GPU
Il existe aussi des forks de llama.cpp optimisés pour mieux tourner sur certaines architectures CPU, mais sauf désaccord entre mainteneurs, mieux vaut investir son temps à fusionner ces améliorations en upstream plutôt que de créer un exécuteur séparé pour un modèle+GPU spécifique
https://codegolf.stackexchange.com/questions/215216/high-thr...
Est-ce qu’on pourrait faire les multiplications matricielles avec des amplificateurs opérationnels ? Et est-ce que cette approche analogique pourrait être bien plus efficace que les limites de la représentation en bits ?
Maintenant que l’IA la plus récente peut même faire de l’optimisation de kernels, je pense que davantage de gens devraient essayer de construire eux-mêmes une meilleure inférence adaptée à leur matériel
J’ai une vieille W7900 (RDNA3), et au-delà de ses 48 Go de VRAM, ses chiffres comme 123 FP16 TFLOPS/INT8 TOPS et 864 Go/s de bande passante mémoire sont assez corrects. Mais le support AMD ROCm comme le support llama.cpp ont eu une très mauvaise réputation
Récemment, j’ai commencé à ajuster des modèles W8A8-INT8 pour utiliser cette carte comme endpoint dédié aux agents/au code. J’ai lancé environ 800 itérations automatiques sur quelques jours, avec plusieurs modèles frontier/SOTA, et Kimi K2.6 s’en est étonnamment bien sorti. Au final, par rapport aux meilleurs chiffres de llama.cpp sur Qwen3.6 MoE, le prefill est 20 % plus rapide et le decode 50 % plus rapide
En ce moment, je continue à creuser les optimisations MTP et DFlash, avec des résultats assez satisfaisants, et j’envisage d’essayer Gemma 4 ensuite
Cela dit, même si llama.cpp n’offre peut-être pas les meilleures performances possibles, il permet d’exécuter la plupart des modèles de façon cohérente. Il lui manque MTP et il semble y avoir des problèmes d’invalidation de cache sur les modèles hybrides, mais au moins on sait ce qui fonctionne
Avec les moteurs d’inférence basés sur Python, entre uv/venv, mon venv, l’environnement système, Python et les bibliothèques, tout s’emmêle au point qu’il faudrait presque un agent juste pour comprendre ce qui tourne réellement. Je sais bien que c’est une question de compétences ou d’erreur utilisateur, mais je n’ai simplement plus de temps à y consacrer
Même si ce n’est pas parfait, si tu le publies sur GitHub ou Hugging Face, un autre agent pourra partir de là plutôt que de repartir de zéro. C’est ce que j’ai fait pour Ling-2.6-flash (107B-A7B4 MoE), et c’est le plus gros LLM que je puisse faire tourner de manière pratique sur mon autre matériel local, un M2 Max
Même si MTP ne marche pas très bien, c’est toujours mieux que la situation actuelle où llama.cpp ne peut pas du tout lancer Ling-2.6-flash. La discussion associée est ici : https://huggingface.co/inclusionAI/Ling-2.6-flash/discussion..., la quantification 4 bits ici : https://huggingface.co/ljupco/Ling-2.6-flash-GGUF, et la branche ici : https://github.com/ljubomirj/llama.cpp/tree/LJ-Ling-2.6-flas...
Je pense que llama.cpp aurait pu bien mieux gérer le support PC. Une partie vient sans doute de la mauvaise qualité du support constructeur, mais vu le nombre d’utilisateurs, c’est surprenant de ne pas voir davantage d’inférence optimisée sur des PC standard
C’est vraiment impressionnant. Je me demande ce qu’on obtiendrait en optimisant à fond un seul modèle open source pendant plusieurs mois
Pas seulement au niveau du serving d’inférence, mais aussi des optimisations de harnais et des workflows sur mesure : les modèles frontier peuvent raisonner et déduire, mais les modèles open source ont des limites intrinsèques de taille ou d’entraînement, donc j’aimerais voir jusqu’où on peut réduire cet écart
Faire tourner Kimi 2.6 à une vitesse correcte en tokens/s coûte 20 000 dollars par mois, et pour vendre ces tokens avec une marge, il faudrait que le coût matériel tombe sous les 1 000 dollars par mois
Si tu mises tes capacités sur un avenir où des milliardaires continueront gentiment à vendre des tokens à 1/10 ou 1/20 de leur coût, ou sur le fantasme de modèles open source compétents tournant sur du matériel grand public, alors c’est déjà terminé
Voilà un point de donnée amusant, intéressant et assez révélateur. Mon MacBook M3 Max monte à 50 W quand DS4 génère des tokens à pleine vitesse
Si DS4 Flash culmine à 50 W et a 280B paramètres, alors DS4 Pro à 1,6T paramètres serait autour de 300 W ? Et les derniers GPT 5 et Opus donneraient une impression comparable à 500 W
Quand j’utilise Claude Code et que le modèle part tout seul dans de longues sorties, est-ce qu’on peut considérer qu’un datacenter brûle quelque part 500 W pour ça ?
Il n’est pas possible de commander un Mac Studio avec plus de 96 Go de RAM. C’est pareil sur M3 Ultra ou M4 Max. Je ne sais pas si c’est spécifique à l’Australie
En revanche, sur le MacBook Pro, on peut choisir 128 Go avec les Mac M5
https://www.apple.com/au/shop/buy-mac/mac-studio
Apple a peut-être choisi de ne même pas la proposer plutôt que d’affronter une polémique sur des prix abusifs ou une réaction négative face à des stocks insuffisants
Toutes les configurations Mac Studio au-delà de 96 Go, ainsi que le Mac mini de base, ont été retirées. Il y aurait aussi des rumeurs selon lesquelles la configuration de base du Neo pourrait être retirée du marché
On dirait qu’ils gèrent ainsi les contraintes de capacité de production des fabs et d’approvisionnement en RAM
Je ne sais pas si j’ai raté un benchmark de motivation simple ou un objectif clair
J’imagine que c’est plus rapide qu’une toolchain généraliste, ou que ça permet de faire tourner des modèles plus gros et plus intelligents, mais il ne semble pas être indiqué clairement quelle est l’ampleur des gains existants ou attendus par rapport à cette base de référence
Cela dit, si on connaît les comparatifs pertinents, on peut probablement les déduire à partir des chiffres donnés
Très impressionnant. En revanche, le fait qu’il faille environ 4 minutes avant de commencer à répondre sur de grosses entrées paraît étrange
Je n’utilise pas de LLM sur du matériel Mac, mais c’est assez surprenant et cela semble être un obstacle important pour un usage réel
Cela dit, en usage normal, avec l’explication sur le cache, c’est beaucoup plus logique. Claude Code peut envoyer un gros prompt initial d’environ 25k tokens avant de commencer un travail utile, et si
--kv-disk-direst activé, alors après le premier prefill coûteux, le cache KV disque réutilise les préfixes enregistrés sans devoir retraiter l’intégralité du promptMais sur un M3 Ultra, la vitesse de prefill est proche de 500 tokens/s, donc on reste tout à fait dans une zone exploitable en pratique. Sur un M3 Max, il faut un peu plus de patience, mais ça marche bien, et si on utilise pi agent, il affiche le raisonnement, donc au lieu d’attendre, on peut lire une chain of thought non censurée
J’ai posté hier sur X une vidéo de l’utilisation sur M3 Max, et il sort des tokens à un rythme tout à fait correct
Sur MacBook, avec les gros LLM, la vitesse de génération des tokens est acceptable, mais le problème, c’est la lecture du contexte
Ce n’est pas la lecture incrémentale avec cache KV comme dans une session de chat ; c’est la lecture de grosses entrées, par exemple quand on colle un gros fichier. Là, cela peut prendre plusieurs minutes
On est probablement assez loin de l’intelligence d’origine proposée par un fournisseur cloud
Cela dit, ça montre mieux le potentiel des LLM locaux dans des workflows orientés agents
Existe-t-il des architectures qui ne dépendent pas de la réinjection de tout l’historique de conversation ? Quelque chose comme des LLM récurrents, par exemple ?