Présentation du script

Le code présenté charge le modèle Qwen3‑0.6B‑GGUF depuis le dépôt HuggingFace Qwen/Qwen3-0.6B-GGUF en utilisant la bibliothèque llama-cpp-python. Le paramètre n_ctx=512 fixe la taille du contexte à 512 tokens, ce qui limite la longueur maximale du prompt. Le script s’appuie sur numpy pour transformer les scores bruts du modèle en probabilités exploitable.

# requires-python = ">=3.12"
# dependencies = ["huggingface-hub", "llama-cpp-python", "numpy"]
import numpy
from llama_cpp import Llama
model = Llama.from_pretrained(
    repo_id="Qwen/Qwen3-0.6B-GGUF",
    filename="Qwen3-0.6B-Q8_0.gguf",
    n_ctx=512,
    logits_all=True,
    verbose=False,
)

Mécanique d’inférence et calcul des probabilités

Le prompt construit combine le texte de l’e‑mail à analyser avec trois options libellées A, B, C correspondant respectivement à Legitimate, Spam et Phishing. La fonction model.eval reçoit les tokens du prompt sans token BOS, grâce à add_bos=False. Après l’inférence, le tableau model.scores contient les logits de chaque token ; le script extrait le vecteur du dernier token (model.n_tokens‑1) pour les trois labels. Les logits bruts (26.254, 27.262, 29.614) sont convertis en log‑probabilités via numpy.logaddexp.reduce, puis exponentiés pour obtenir les probabilités finales (0.031, 0.084, 0.885). Le résultat montre que le modèle attribue 88 % de probabilité à la classe Phishing.

logits = model.scores[model.n_tokens - 1]
token_ids = [model.tokenize(label.encode(), add_bos=False)[0] for label in labels]
choice_logits = numpy.asarray([logits[t] for t in token_ids])
logprobs = choice_logits - numpy.logaddexp.reduce(choice_logits)
probabilities = numpy.exp(logprobs)
for name, scores in (("Logits", choice_logits), ("Log probabilities", logprobs), ("Probabilities", probabilities)):
    values = numpy.round(scores.astype(float), 3).tolist()
    print(f"{name}:", dict(zip(choices, values, strict=True)))

Analyse des performances et limites

Le modèle Qwen3‑0.6B compte environ 600 M paramètres, ce qui le rend compatible avec une exécution CPU rapide sur du matériel grand public. La configuration logits_all=True permet d’accéder aux scores de chaque token sans appel supplémentaire, réduisant ainsi le nombre d’interactions avec le moteur d’inférence. Cependant, la fenêtre de contexte de 512 tokens contraint la complexité des prompts ; des e‑mails plus longs ou des contextes supplémentaires dépasseraient cette limite et nécessiteraient un découpage manuel.

Le calcul des probabilités repose sur le principe que les logits du dernier token représentent la distribution sur les labels. Cette hypothèse fonctionne tant que le modèle a bien appris à associer chaque label à un token unique, ce qui n’est pas garanti pour tous les modèles GGUF. De plus, aucune calibration n’est appliquée : les valeurs de probabilité reflètent la confiance interne du modèle, mais elles ne sont pas calibrées pour correspondre à une fréquence réelle d’erreur. Enfin, le script ne gère pas les cas d’ambiguïté où plusieurs labels pourraient recevoir des scores proches, ni les scénarios où le modèle génère un token hors du vocabulaire attendu.

En résumé, le script démontre qu’une classification binaire ou multiclasse peut être réalisée localement en moins de trente lignes de Python, avec un modèle ouvert et sans échange de données vers le cloud. La simplicité du flux de travail le rend intéressant pour des prototypes rapides, mais la petite taille du modèle, l’absence de calibration et la limite de contexte imposent une vigilance lors d’une utilisation en production.