Utiliser Claude 3 et Cursor pour créer un outil de monitoring

Bien cool ce petit monitoring, veux-tu bien partager ton mode opératoire avec Claude et Cursor ? Je découvre.

3 Likes

Je vois que tu es en train de répondre, mais du coup j’ai testé Cursor à l’instant, en littéralement 2 minutes j’ai produit cette petite application qui montre le bloc courant de la ĞDev (en temps réel) :

Le prompt donné à Cursor :

I would like you to initiate a Nuxt 3 based project, with a single screen showing in real time the current block of a Polkadot based blockchain whose RPC API is located at wss://gdev.cgeek.fr.

Please also:

  • generate the package.json file
  • typescript configuration file
3 Likes

Cursor est un IDE, fork de vs-codium qui focus sur l’intégration poussé de LLMs.
Donc je sais que comme tu es habitué aux IDE de JetBrains, ce n’est peut être pas parfaitement adapté à ton usage.

Globalement j’ai ouvert une nouvelle fenêtre, ctrl + i pour ouvrir le composer, et prompté ce que je voulais:

Mon prompt initial (avec ses fautes, imperfections et imprécisions)

Ta mission est de créer une page web ayant pour but de monitorer les noeuds du Réseau Duniter listés dans ce json: https://git.duniter.org/nodes/networks/-/raw/master/gdev.json?ref_type=heads

le bloc rpc étant les noeud Duniter, le bloc squid étant les instances de l’indexer.

Il faut quelque de sobre, simple mais moderne, pour voir si les noeuds et isntances sont synchronisés.

Pour l’indexer squid c’est du graphql, tu peux t’inspirer de ce code Dart pour check la sync:

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/providers/home.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart';
import 'package:gecko/models/transaction.dart';

class DuniterIndexer with ChangeNotifier {
  Map<String, String?> walletNameIndexer = {};
  String? fetchMoreCursor;
  Map? pageInfo;
  List<Transaction>? transBC;
  List listIndexerEndpoints = [];
  bool isLoadingIndexer = false;
  Future<QueryResult<Object?>?> Function()? refetch;
  late GraphQLClient indexerClient;

  void reload() {
    notifyListeners();
  }

  Future<bool> checkIndexerEndpoint(String endpoint) async {
    isLoadingIndexer = true;
    notifyListeners();
    final client = HttpClient();
    client.connectionTimeout = const Duration(milliseconds: 4000);
    try {
      final request = await client.postUrl(Uri.parse('https://$endpoint/v1beta1/relay'));
      final response = await request.close();
      if (response.statusCode != 200) {
        log.w('Indexer $endpoint is offline');
        indexerEndpoint = '';
        isLoadingIndexer = false;
        notifyListeners();
        return false;
      } else {
        final isSynced = await isIndexerSynced('https://$endpoint/v1/graphql');
        if (!isSynced) {
          log.e('This endpoint is not synced, next');
          return false;
        }

        indexerEndpoint = endpoint;
        await configBox.put('indexerEndpoint', endpoint);
        // await configBox.put('customEndpoint', endpoint);
        isLoadingIndexer = false;
        notifyListeners();
        final cache = HiveStore();
        cache.reset();
        return true;
      }
    } catch (e) {
      log.w('Indexer $endpoint is offline');
      indexerEndpoint = '';
      isLoadingIndexer = false;
      notifyListeners();
      return false;
    }
  }

  Future<String> getValidIndexerEndpoint() async {
    final homeProvider = Provider.of<HomeProvider>(homeContext, listen: false);

    // await configBox.delete('indexerEndpoint');

    listIndexerEndpoints = await rootBundle.loadString('config/indexer_endpoints.json').then((jsonStr) => jsonDecode(jsonStr));
    // _listEndpoints.shuffle();

    listIndexerEndpoints.add('Personnalisé');

    if (configBox.containsKey('customIndexer')) {
      if (await checkIndexerEndpoint(configBox.get('customIndexer'))) {
        succesConnection(indexerEndpoint);
        return indexerEndpoint;
      }
    }

    if (configBox.containsKey('indexerEndpoint') && listIndexerEndpoints.contains(configBox.get('indexerEndpoint'))) {
      if (await checkIndexerEndpoint(configBox.get('indexerEndpoint'))) {
        succesConnection(indexerEndpoint);
        return indexerEndpoint;
      }
    }

    int i = 0;
    // String _endpoint = '';
    int statusCode = 0;

    final client = HttpClient();
    client.connectionTimeout = const Duration(milliseconds: 3000);

    do {
      final listLenght = listIndexerEndpoints.length - 1;
      if (i >= listLenght) {
        log.e('NO VALID INDEXER ENDPOINT FOUND');
        indexerEndpoint = '';
        break;
      }
      log.d('${i + 1}n indexer endpoint try: ${listIndexerEndpoints[i]}');

      if (i != 0) {
        await Future.delayed(const Duration(milliseconds: 300));
      }

      try {
        final endpointPath = 'https://${listIndexerEndpoints[i]}/v1beta1/relay';

        final request = await client.postUrl(Uri.parse(endpointPath));
        final response = await request.close();

        final isSynced = await isIndexerSynced('https://${listIndexerEndpoints[i]}/v1/graphql');

        if (!isSynced) {
          log.e('This endpoint is not synced, next');
          statusCode = 40;
          i++;
          continue;
        }

        indexerEndpoint = listIndexerEndpoints[i];
        await configBox.put('indexerEndpoint', listIndexerEndpoints[i]);

        statusCode = response.statusCode;
        i++;
      } on TimeoutException catch (_) {
        log.e('This endpoint is timeout, next');
        statusCode = 50;
        i++;
        continue;
      } on SocketException catch (_) {
        log.e('This endpoint is a bad endpoint, next');
        statusCode = 70;
        i++;
        continue;
      } on Exception {
        log.e('Unknown error');
        statusCode = 60;
        i++;
        continue;
      }
    } while (statusCode != 200);

    if (indexerEndpoint == '') {
      log.e('NO VALID INDEXER ENDPOINT FOUND');
      homeProvider.changeMessage("No valid indexer found =(".tr());
      return '';
    }
    succesConnection(indexerEndpoint);

    return indexerEndpoint;
  }

  void succesConnection(String endpoint) {
    final homeProvider = Provider.of<HomeProvider>(homeContext, listen: false);

    final wsLinkIndexer = WebSocketLink(
      'wss://$endpoint/v1beta1/relay',
    );

    indexerClient = GraphQLClient(
      cache: GraphQLCache(),
      link: wsLinkIndexer,
    );

    // Indexer Blockchain start
    getBlockStart();

    homeProvider.changeMessage("Node and indexer synced !".tr(), true);
    log.i('Indexer: $indexerEndpoint');
  }

  Future<bool> isIndexerSynced(String endpoint) async {
    try {
      final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
      var duniterFinilizedHash = await sub.getLastFinilizedHash();
      final duniterFinilizedNumber = await sub.getBlockNumberByHash(duniterFinilizedHash);
      duniterFinilizedHash = "\\x${duniterFinilizedHash.substring(2)}";

      final indexerLink = HttpLink(endpoint);
      final iClient = GraphQLClient(
        cache: GraphQLCache(),
        link: indexerLink,
      );
      final result = await iClient.query(QueryOptions(document: gql(getBlockByHash), variables: <String, dynamic>{'hash': duniterFinilizedHash}));

      if (result.hasException || result.data == null || result.data!['block'].isEmpty) {
        log.e('Indexer is not synced: ${result.exception} -- ${result.data}');
        return false;
      }

      final indexerFinilizedNumber = result.data!['block'][0]['height'] as int;
      if (duniterFinilizedNumber != indexerFinilizedNumber) {
        log.e('Indexer is not synced');
        return false;
      }
      return true;
    } catch (e) {
      log.e('An error occured while checking indexer sync: $e');
      return false;
    }
  }

  List<Transaction> parseHistory(List blockchainTX, String address) {
    // Create a list to store Transaction objects
    List<Transaction> transactions = [];

    for (final transactionNode in blockchainTX) {
      final transaction = transactionNode['node'];
      final isReceived = transaction['fromId'] != address;

      // Calculate amount
      final amount = transaction['amount'] as int;
      final comment = transaction['comment']?['remark'] ?? '';
      final commentType = transaction['comment']?['type'] ?? '';

      // Determine counterparty based on direction
      final String counterPartyId;
      final String counterPartyName;
      if (isReceived) {
        counterPartyId = transaction['fromId'];
        counterPartyName = transaction['from']['identity']?['name'] ?? '';
      } else {
        counterPartyId = transaction['toId'];
        counterPartyName = transaction['to']['identity']?['name'] ?? '';
      }

      // Create and add new Transaction object
      transactions.add(
        Transaction(
          timestamp: DateTime.parse(transaction['timestamp']),
          address: counterPartyId,
          username: counterPartyName,
          amount: amount,
          comment: commentType == 'ASCII' || commentType == 'UNICODE' ? comment : '',
          isReceived: isReceived,
        ),
      );
    }
    return transactions;
  }

  FetchMoreOptions? mergeQueryResult(QueryResult result, FetchMoreOptions? opts, String address, int nRepositories) {
    final List<dynamic> blockchainTX = (result.data!['transferConnection']['edges'] as List<dynamic>);

    pageInfo = result.data!['transferConnection']['pageInfo'];
    fetchMoreCursor = pageInfo!['endCursor'];
    // final hasNextPage = pageInfo!['hasNextPage'];
    // log.d('endCursor: $fetchMoreCursor $hasNextPage');

    if (fetchMoreCursor != null) {
      opts = FetchMoreOptions(
        variables: {'after': fetchMoreCursor, 'first': nRepositories},
        updateQuery: (previousResultData, fetchMoreResultData) {
          final List<dynamic> repos = [
            ...previousResultData!['transferConnection']['edges'] as List<dynamic>,
            ...fetchMoreResultData!['transferConnection']['edges'] as List<dynamic>
          ];

          fetchMoreResultData['transferConnection']['edges'] = repos;
          return fetchMoreResultData;
        },
      );
    }

    if (fetchMoreCursor != null) {
      transBC = parseHistory(blockchainTX, address);
    } else {
      log.d("Activity start of $address");
    }
    return opts;
  }

//// Manuals queries

  Future<bool> isIdtyExist(String name) async {
    final variables = <String, dynamic>{
      'name': name,
    };
    final result = await _execQuery(isIdtyExistQ, variables);
    return result.data?['identityConnection']['edges']?.isNotEmpty ?? false;
  }

  Future<DateTime> getBlockStart() async {
    final result = await _execQuery(getBlockchainStartQ, {});
    if (!result.hasException) {
      startBlockchainTime = DateTime.parse(result.data!['blockConnection']['edges'][0]['node']['timestamp']);
      startBlockchainInitialized = true;
      return startBlockchainTime;
    }
    return DateTime(0, 0, 0, 0, 0);
  }

  Future<QueryResult> _execQuery(String query, Map<String, dynamic> variables) async {
    final options = QueryOptions(document: gql(query), variables: variables);

    // 5GMyvKsTNk9wDBy9jwKaX6mhSzmFFtpdK9KNnmrLoSTSuJHv

    return await indexerClient.query(options);
  }

  Stream<QueryResult> subscribeHistoryIssued(String address) {
    final variables = <String, dynamic>{
      'address': address,
    };

    final options = SubscriptionOptions(
      document: gql(subscribeHistoryIssuedQ),
      variables: variables,
    );

    return indexerClient.subscribe(options);
  }

  Map computeHistoryView(Transaction transaction, String address) {
    final DateTime date = transaction.timestamp;

    final dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, {1, 2, 7, 9}.contains(date.month) ? 4 : 3)}";

    DateTime normalizeDate(DateTime inputDate) {
      return DateTime(inputDate.year, inputDate.month, inputDate.day);
    }

    String getDateDelimiter() {
      DateTime now = DateTime.now();
      final transactionDate = normalizeDate(date.toLocal());
      final todayDate = normalizeDate(now);
      final yesterdayDate = normalizeDate(now.subtract(const Duration(days: 1)));
      final isSameWeek = weekNumber(transactionDate) == weekNumber(now) && transactionDate.year == now.year;
      final isTodayOrYesterday = transactionDate == todayDate || transactionDate == yesterdayDate;

      if (transactionDate == todayDate) {
        return "today".tr();
      } else if (transactionDate == yesterdayDate) {
        return "yesterday".tr();
      } else if (isSameWeek && !isTodayOrYesterday) {
        return "thisWeek".tr();
      } else if (!isSameWeek && !isTodayOrYesterday) {
        if (transactionDate.year == now.year) {
          return monthsInYear[transactionDate.month]!;
        } else {
          return "${monthsInYear[transactionDate.month]} ${transactionDate.year}";
        }
      } else {
        return '';
      }
    }

    final dateDelimiter = getDateDelimiter();

    final amount = transaction.isReceived ? transaction.amount : transaction.amount * -1;

    bool isMigrationTime = startBlockchainInitialized && date.compareTo(startBlockchainTime) < 0;

    return {
      'finalAmount': amount,
      'isMigrationTime': isMigrationTime,
      'dateDelimiter': dateDelimiter,
      'dateForm': dateForm,
    };
  }

  int weekNumber(DateTime date) {
    int dayOfYear = int.parse(DateFormat("D").format(date));
    return ((dayOfYear - date.weekday + 10) / 7).floor();
  }
}

Concernant les noeuds Duniter, il s’agit de noeud blockchain Substrate, comme Polkadot, donc utilisant une API RPC, je te laisse chercher sur le web comment faire pour check la sync facilement, la dernier blocknumber.


Je lui ai donné du code par flemme de lui expliquer l’architecture, mais ce n’était pas du tout nécessaire, il aurait finit pas trouver de lui même.

Sans donner de directive concernant la stack technologique souhaité (je m’en fiche), il est partie sur une stack React: nodes / v2s-monitor · GitLab

Il m’a créé tous les fichier de config nécessaires à la stack en quelques secondes, il a itéré 2 ou 3 fois sur des erreurs de hints qu’il a généré sans même que j’ai besoin de lui dire, puis m’a laissé la main avec quelque chose de fonctionnel techniquement, mais qui ne chargeais pas la liste des nœuds à cause d’un problème de CORS. Mais les cas d’erreurs étant déjà bien gérés, j’avais quand même une interface avec des listes vides.

J’ai donc simplement interagie avec le composer pour lui donner les erreurs javascript que j’avais (il avait bien accès aux hint de l’IDE, mais pas à la console javascript du navigateur pour le coup), il comprenait alors ce qui n’allait pas et le corrigeait. Je n’avais qu’a accepter ou non par shard ces changements présentés dans mon IDE au format git diff (ou tout accepter d’un coup).

Je n’ai donnée aucune directive d’UI hormis un code couleur (celui du fond de g1-stats, lui même venu de Duniter…) car par defaut il part sur des bleus foncés en général.
Le plus long ayant été de le faire itérer sur les bugs au début, notamment lorsque je lui ait demandé de souscrire en websockets aux noeuds duniter et aux indexer pour afficher en live les numéros de bloc.

Je n’ai quasiment rien codé, et par expérience (ça fait bien 4 mois que je l’utilise quotidiennement comme IDE principal au boulot), que je pars sur une session assisté en mode agent comme ça, je m’efforce même de ne pas corrigé les coquilles à la main pour le laisser faire, pour qu’il comprenne les corrections pour la suite, sans quoi il peut être capable de me les écrasés aux prochains passages (suite à quoi on s’énerve on lui dit que ce n’est qu’une merde, puis il s’excuse et se corrige).

On se retrouve plus dans une position de PO/PM que de dev.
Mais je précise bien que ce résultat n’est évidemment pas le premier jet, ça a été des dizaines de messages d’échanges avec le composer pour en arriver là.
Je voudrais aussi casser le mythe de l’art de savoir bien prompter: On peut lui parler n’importe comment, peu importe, il corrigera le tir très vite après quelques retours. Le tout étant simplement d’être le plus précis possible sur ce qu’on veut, mais bon, tu vois mon prompt de base, il est extrêmement simple et basique, direct, faut pas non plus perdre trop de temps en détail et lui laisser un peu de mou.
Seul une bonne compréhension je dirais dev ops est requise pour bien s’en sortir, et encore si ce n’est pas le cas, il vous apprendra.

Je dirais que cet outil m’a pris entre 3h et 4h de mon temps entre l’idée et le déploiement, sur une techno que je ne maîtrise absolument pas (je prends presque autant de temps à t’écrire ceci qu’a avoir réalisé l’outil).


De loin le meilleurs outil que j’ai pu tester pour coder avec des LLMs (pour 20$/mois, accès aux LLMs de sont choix parmi les plus performant du marché, une bouchée de pain. On peut aussi ne pas payer de forfait un insérer des clés API de providers LLM, ou encore paramétrer des modèles locaux, au choix).
Mais le marché évolue vite, et je suis sûr que JetBrains ne tardera pas à faire des intégrations aussi poussés.

Voilà pour mon retour d’expérience.


PS: Pour qu’il itère tous seul sur les hints de la console comme un grand, il faut cocher un case beta en paramètre.

7 Likes

C’est hyper impressionnant. Tu viens de me mettre un gros jouet entre les mains, j’ai déjà testé un peu plus et il réalise déjà des miracles.

Si déjà il pouvait me délester de toutes les tâches pénibles de dev, ça me libérera pour faire les choses les plus poussées.

Si tu veux tu peux remplacer la mention Pini par cgeek, moi je veux bien de ta pub :smile:

4 Likes

A la décharge de Pini, sa réaction fait suite à des échanges qu’on a eu en MP axiom, où il est vrai que je peux être un peu taquin, voir très con…

4 Likes

J’ai laissé l’option sur “Auto”, et je n’ai pas le détail de ce qui a réellement été appelé (pas très transparent de leur part, ça rend suspicieux). Je vais tester en forçant le LLM de temps à autres pour comparer.

Avec Claude 4 c’est le jour et la nuit. Par contre si tu as un usage un peu intensif tu peux facilement atteindre ton quota compris dans les 20€/mois et devoir payer à l’usage, mais il t’en informe clairement si tel est le cas.
Tu as un onglet Usage sur leur site sur ton compte pour voir chaque requête et leur coût.

Il y a eu une petite polémique au sujet de la facturation le mois dernier, au sujet de surcoût et manque de clarté sur leurs tarifs, mais ça tend à se tasser.

D’autres concurrents arrivent sur le marché pendant ce temps là.

Oui j’ai un peu suivi. Mais ça ne me gêne vraiment pas de payer par requête ou de faire rouler l’abonnement en remettant une pièce de 20, le gain de temps est tel (la plupart du temps !) que ça en vaut largement l’investissement.

Oui et précisément c’est marqué “auto”, ce qui n’est pas transparent et rend la chose suspicieuse. Ils pourraient tout à fait indiquer quel LLM/chatbot a été utilisé.

Ça va à une vitesse incroyable. Je manque même de matière pour pouvoir tester chaque nouveauté. Le pire, c’est que je bosse précisément dans un service qui fait de l’IA ! Même les Data Scientists peinent à suivre. Pour ma part je suis sur la partie “plateforme” donc je ne fais pas d’IA directement, j’aide les DS à pouvoir industrialiser rapidement leurs nouveaux outils.

C’est assez paradoxal que ce soit toi qui au final m’ait fait découvrir Cursor :sweat_smile:

4 Likes

Cursor ne fonctionne plus sur ma machine de coding. Je me rabats sur deepseek en gratuit. Ca me suffit pour l’instant.

J’ai vu cet outil : Aider, un outil en python, mais pour tous les langages.

il fait un arbre structuré de votre dépôt git pour optimiser les prompts visiblement.
C’est comme ci l’IA avait tout le code et comprend mieux dans quel contexte il doit générer des bouts de code.

mais pas encore testé :

2 Likes

Si quelqu’un à ce genre de machine dans sont salon on peut aussi faire tourner et mutualiser ce genre d’outils (500 Milliards d’hyper paramètres).

fuck paywalls (1,1 Mo)

3 Likes

Salut a tous, je trouve un peu de temps pour vous lire.

@cgeek tu n’utilises pas Junie les outils Jetbrains ? Je le trouve hyper rapide. Il profite de l’indexation de l’IDE et des fonctionnalités pratiques (organisation des imports, etc.). Ça rend l’expérience très conviviale et rapide.
On l’utilise de plus en plus et j’avoue qu’on gagne un temps fou !

2 Likes

J’ai pas ça (et ça doit coûter cher) il faut plus de 240Go de VRAM pour Qwen3-coder 405B.
Y’a que 24 Go sur ma RTX3090. Si on est plusieurs “PC Gamer” , on pourrait essayer de faire tourner GitHub - exo-explore/exo: Run your own AI cluster at home with everyday devices 📱💻 🖥️⌚

Concernant mon expérience de code assisté par IA, c’est fantastique ! Par contre, bien regarder avant de “commit”, parfois, il change une ligne ou ajoute une fonction qui ne sert à rien. Les LLM hallucinent en moyenne 1 fois sur 20. Mais bien prompté, qu’est ce que c’est efficace. Je recommande…

Le prix est indiqué sur mon lien.
C’est le moins chère que j’ai pu trouvé pour un model de 500 milliards de paramètres.

$233,678.50 !!

Et bien c’est hors de prix… Encore plus comparé à 10 PC Gamer à 3.000 boules…
Charger le modèle sur des VRAM en Virtual LAN est plus long, mais exo comble ce retard quand les traitements sont sérialisés.

  • Qui a un GPU à la maison ?
0 voters

Sinon y’a des Mac Studio avec 512 GB de mémoire unifiée (10k boules)

Comment déployer exo ?

Qwen3 Coder Is HERE — In-Depth Testing (Games, UI & Web Dev) - YouTube trailer

Qwen3 Coder Is HERE — In-Depth Testing (Games, UI & Web Dev) - YouTube

NVIDIA users: QWEN3 is FREE, but you'll pay double - YouTube trailer

NVIDIA users: QWEN3 is FREE, but you’ll pay double - YouTube

Qwen 3 Coder is INCREDIBLE but... - YouTube trailer

Qwen 3 Coder is INCREDIBLE but… - YouTube

Pour faire tourner Qwen3 Coder 405B sur un essaim de GPU « Gamer » à l’aide d’exo, voici les grandes étapes à suivre, accompagnées de conseils pratiques et de points d’attention tirés des ressources techniques et des retours d’expérience sur exo, Qwen3-Coder et le déploiement distribué :


1. Préparer l’environnement matériel et logiciel

  • Assurez-vous que la somme de la RAM et de la VRAM cumulée de vos machines/gamers couvre la taille du poids du modèle (Qwen3 Coder 405B en format quantifié fait tout de même plus de 240Go, à répartir sur tous les GPU et RAM du cluster).news.ycombinator+1
  • Installez Linux sur chaque machine (exo ne supporte pas encore Windows pour NVIDIA).techradar
  • Cartes supportées: exo permet d’utiliser simultanément des GPUs variés (RTX 3090, 4090, 4080, etc.), de la RAM CPU, Apple Silicon, etc., mais l’expérience est optimale sur du Linux avec CUDA/RTX.github
  • Installez Python ≥3.12, les drivers NVIDIA à jour, CUDA et cuDNN sur toutes les machines Linux équipées de GPU NVIDIA.

2. Installation d’exo sur chaque nœud du cluster

bash

git clone https://github.com/exo-explore/exo.git
cd exo
pip install -e .
  • Vérifiez le bon fonctionnement de nvidia-smi et nvcc --version sur chaque machine équipée d’un GPU.github

3. Lancement et découverte automatique du cluster

Sur chaque machine :

bash

exo
  • exo détecte automatiquement les autres machines du réseau local via UDP ou Tailscale.cnx-software+1
  • Aucune configuration manuelle n’est requise ; l’ensemble fonctionne en architecture pair-à-pair et non master-worker.github

4. Téléchargement et déploiement du modèle

  • Le modèle Qwen3-Coder-405B (ou 480B si c’est le plus proche) s’obtient depuis HuggingFace.
  • Indiquez à exo l’emplacement du modèle ou laissez-le le télécharger dans ~/.cache/exo/downloads.github
  • Si le réseau/censure pose problème, téléchargez manuellement et placez dans ce dossier.

5. Exécution distribuée

Lancez une requête sur le cluster (via WebUI ou API compatible ChatGPT sur le port 52415 par défaut) :

bash

curl http://localhost:52415/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
     "model": "qwen3-coder-405b",
     "messages": [{"role": "user", "content": "Code un quicksort en Python."}]
   }'
  • Le partitionnement dynamique « ring memory weighted » d’exo répartit les couches du modèle selon la mémoire disponible de chaque machine.cnx-software+1
  • Conseil : Plus le hardware est homogène et performant (GPU gamer récents, bon réseau LAN), plus l’inférence est fluide, même si chaque ajout de machine augmente la latence individuelle, le débit total reste élevé.techradar

6. Conseils de performance et limitations

  • Essayez d’avoir un réseau local filaire (gigabit ou plus si possible).techradar
  • Plus il y a de GPU, plus on peut charger de couches du modèle ; attention : des GPU avec peu de VRAM ou un CPU faiblard peuvent devenir un goulot d’étranglement.news.ycombinator+1
  • Les modèles MoE comme Qwen3-Coder activent seulement une partie des « experts » par jeton, ce qui améliore l’utilisation mémoire mais ne diminue pas la taille totale à charger.unsloth+1
  • Le support tinygrad et l’optimisation CUDA/tinygrad sont natifs dans exo pour ce type de modèle.github

7. Commandes utiles et debugging

  • Logs debug :

bash

DEBUG=9 exo
  • Gestion du cache des modèles :

bash

export EXO_HOME=/chemin/autre

REMARQUE

  • exo est encore un projet expérimental, donc attendez-vous à devoir faire quelques ajustements/configurations suivant vos setups, surtout sur de gros modèles comme Qwen3 Coder 405B.cnx-software+1
  • Plus d’informations sur les configs avancées (partitionnement manuel, API, compatibilité) sont sur le GitHub exo et la doc Qwen.huggingface+1

Résumé pratique :
Installez exo sur chaque machine, assurez-vous que la mémoire cumulée du parc suffit à héberger le modèle, démarrez exo, laissez-le organiser la distribution des couches, et utilisez l’API locale comme une API ChatGPT classique pour interroger Qwen3-Coder sur votre essaim de GPU gaming.digitalocean+2

  1. Qwen3-Coder: Agentic coding in the world | Hacker News
  2. Qwen3-Coder: How to Run Locally | Unsloth Documentation
  3. SETI but for LLM; how an LLM solution that's barely a few months old could revolutionize the way inference is done | TechRadar
  4. GitHub - exo-explore/exo: Run your own AI cluster at home with everyday devices 📱💻 🖥️⌚
  5. exo software - A distributed LLM solution running on a cluster of computers, smartphones, or SBCs - CNX Software
  6. Qwen3-Coder, An Open-Weight Agentic Coding Model | DigitalOcean
  7. Qwen/Qwen3-Coder-480B-A35B-Instruct · How to locally host qwen3 coder
  8. https://www.reddit.com/r/LocalLLaMA/comments/1mepr5q/how_to_run_qwen3_coder_30ba3b_the_fastest/
  9. Can I run the Qwen3-Coder-480B-A35B-Instruct model on such a system i9-14900K, RTX 3090, 96GB DD5,... · Issue #458 · QwenLM/Qwen3-Coder · GitHub
  10. How to self-host Qwen3-Coder on Northflank with vLLM | Blog — Northflank
  11. https://www.youtube.com/watch?v=7Ut2lpNVZSQ
  12. https://fr.investing.com/news/stock-market-news/alibaba-devoile-qwen3coder480b-son-modele-de-codage-le-plus-puissant-93CH-2967900
  13. GitHub - QwenLM/Qwen3-Coder: Qwen3-Coder is the code version of Qwen3, the large language model series developed by Qwen team, Alibaba Cloud.
  14. Qwen3-Coder-480B-A35B-Instruct-FP8 fails to launch with Triton shared memory error on H20e GPUs · Issue #461 · QwenLM/Qwen3-Coder · GitHub
  15. Qwen3-Coder: Agentic Coding in the World | Qwen
  16. https://www.youtube.com/watch?v=_KvpVHD_AkQ
  17. Transparent Benchmarks - 12 days of EXO | EXO
  18. Decentralized Training - 12 days of EXO | EXO
  19. Qwen3-Coder: Performance, Architecture & Access
  20. https://www.reddit.com/r/homeassistant/comments/1iumgm1/anyone_tested_exo_cluster_exo_cluster_opensource/
  21. https://blog.gopenai.com/the-open-source-challenger-weve-been-waiting-for-qwen3-coder-cdd7a502254f
  22. https://www.reddit.com/r/LocalLLaMA/comments/1hwcc6d/is_anyone_actually_running_an_exo_cluster/
  23. DavidAU/Qwen3-Zero-Coder-Reasoning-0.8B · Hugging Face
  24. OpenRouter
  25. GitHub · Where software is built
  26. Qwen3-Coder: The Most Capable Agentic Coding Model Now Available on Together AI
  27. https://www.reddit.com/r/LocalLLaMA/comments/1e6u9rx/exo_p2p_ai_inference_clustering/
  28. https://www.youtube.com/watch?v=0BHBoDABOfY
  29. https://www.youtube.com/watch?v=8jBw0CVPZPM
  30. https://www.youtube.com/watch?v=ITbB9nPCX04
  31. Fireworks AI