CoolFace
Modelpublic

patdev/k3-a40-bootstrap

sourceHugging Faceotherupdated 19d agoView on Hugging Face
0likes1.3kdownloads
balayage5.py318 linesDownload Raw Back to root
1# Balayage 3 : reprise du 2, avec DEUX corrections.2#3# CORRECTION 1 -- le detecteur d'echec. Le balayage 2 declarait morte toute4# configuration dont le journal contenait "is not supported", et il a mordu sur5# un AVERTISSEMENT benin :6#     WARNING [compilation.py:1414] CUDAGraphMode.FULL is not supported with7#     Qwen3_8FlashNextQSAFlashAttentionBackend (support: UNIFORM_BATCH)8# Le moteur repliait gracieusement en PIECEWISE et demarrait normalement ; la9# preuve, l'abandon tombait 5,2 min apres le lancement, soit APRES le10# chargement des poids et la compilation. Desormais on n'abandonne que sur une11# ligne ERROR / Traceback / EngineDead, et JAMAIS sur une ligne WARNING.12#13# CORRECTION 2 -- une seule variable a la fois, pour de vrai. Passer14# --compilation-config remet `cudagraph_mode` a son defaut FULL au lieu du15# FULL_AND_PIECEWISE que vLLM choisit tout seul pour ce modele. Le balayage 216# changeait donc DEUX choses a la fois sans que je le voie. On restate donc17# cudagraph_mode dans chaque JSON, et la premiere configuration est un TEMOIN18# qui ne change que ca : s'il retrouve les chiffres de la reference, la syntaxe19# est bonne et tout ecart ulterieur est imputable au vrai levier.20import json21import os22import re23import subprocess24import time25import urllib.request26from concurrent.futures import ThreadPoolExecutor27 28BASE = "http://127.0.0.1:8000"29MODELE = "RadixArk/Qwen3.8-Flash-Next-NVFP4"30LOG = "/travail/vllm.log"31JOURNAL = "/travail/banc.log"32DEBUT = time.time()33PLAFOND = 330034 35SOCLE = {36    "--served-model-name": "flashnext",37    "--host": "127.0.0.1",38    "--port": "8000",39    "--max-model-len": "262144",40    "--max-num-seqs": "16",41    "--gpu-memory-utilization": "0.93",42    "--distributed-executor-backend": "mp",43    "--reasoning-parser": "qwen3",44    "--tool-call-parser": "qwen3_coder",45    "--limit-mm-per-prompt": '{"image":0,"video":0}',46}47# --enable-flashinfer-autotune fait desormais partie du socle : mesure du48# 26/08, +8,2 % solo et +7,4 % agrege, et -1,01 ms sur le temps par pas.49FANIONS = ["--trust-remote-code", "--enable-prefix-caching",50           "--enable-auto-tool-choice", "--enable-flashinfer-autotune"]51 52CG = {"cudagraph_mode": "FULL_AND_PIECEWISE"}53 54 55def cc(**extra):56    d = dict(CG)57    d.update(extra)58    return json.dumps(d)59 60 61CONFIGS = [62    # Les deux gains connus s'additionnent-ils ? customops valait +1,7 % seul.63    ("custom_auto", {"--compilation-config": cc(64        custom_ops=["+rms_norm", "+fused_add_rms_norm"])}, [], []),65    # Le noyau MoE de TensorRT-LLM, atteignable depuis vLLM. Prediction posee66    # AVANT mesure : aucun effet, le noyau MoE n'etant pas le goulot (marlin vs67    # humming : 0,9 % d'ecart sur trois architectures). Risque : refus, ces68    # noyaux etant verrouilles sur sm_100 dans nos mesures precedentes.69    ("moe_trtllm", {"--moe-backend": "flashinfer_trtllm"}, [], []),70    ("tout", {"--compilation-config": cc(71        custom_ops=["+rms_norm", "+fused_add_rms_norm"]),72        "--moe-backend": "flashinfer_trtllm"}, [], []),73]74 75 76def dire(msg):77    ligne = "[%5.1f min] %s" % ((time.time() - DEBUT) / 60, msg)78    print(ligne, flush=True)79    with open(JOURNAL, "a") as f:80        f.write(ligne + "\n")81 82 83def metrique(nom):84    try:85        t = urllib.request.urlopen(BASE + "/metrics", timeout=5).read().decode()86        m = re.search(r"^vllm:%s\S*\s+([0-9.eE+-]+)$" % nom, t, re.M)87        return float(m.group(1)) if m else None88    except Exception:89        return None90 91 92def attendre_vide(limite=180):93    t0, libre = time.time(), 094    while time.time() - t0 < limite:95        r, w = metrique("num_requests_running"), metrique("num_requests_waiting")96        libre = libre + 1 if (r == 0 and (w or 0) == 0) else 097        if libre >= 2:98            return True99        time.sleep(5)100    return False101 102 103def arreter():104    out = subprocess.run(["ps", "-eo", "pid,rss,comm", "--no-headers"],105                         capture_output=True, text=True).stdout106    for l in out.splitlines():107        p = l.split(None, 2)108        if len(p) != 3:109            continue110        pid, rss, comm = int(p[0]), int(p[1]), p[2].strip()111        if re.match(r"^(vllm|VLLM|Ple|EngineCore)", comm) or \112           (rss > 10_000_000 and re.match(r"^python3?$", comm)):113            try:114                os.kill(pid, 9)115            except Exception:116                pass117    time.sleep(12)118 119 120# Seuls ces trois motifs sont sans ambiguite. Le balayage 2 abandonnait sur121# "is not supported" (un WARNING de repli des graphes CUDA) et la premiere122# correction abandonnait sur "ERROR" (un lint de docstring de transformers).123# Un vrai echec produit TOUJOURS "Engine core initialization failed" -- verifie124# sur le plantage MTP et sur les trois refus de quantification de KV.125FATAL = re.compile(r"Engine core initialization failed|EngineDeadError|"126                   r"Traceback \(most recent call last\)")127 128 129def raison(txt):130    m = re.findall(r"^.*(?:Error|Exception|not supported|NotImplemented).*$",131                   txt, re.M)132    return " || ".join(x.strip()[:200] for x in m[-3:]) or "cause non identifiee"133 134 135def lancer(nom, remplace, ajout, retire):136    if os.path.exists(LOG):137        os.replace(LOG, "/travail/vllm-%s-precedent.log" % nom)138    opts = dict(SOCLE)139    opts.update(remplace)140    cmd = ["vllm", "serve", MODELE]141    for k, v in opts.items():142        cmd += [k, v]143    cmd += [f for f in FANIONS if f not in retire] + ajout144    env = dict(os.environ, VLLM_PLE_CPU_OFFLOAD="1")145    with open(LOG, "w") as sortie:146        subprocess.Popen(cmd, stdout=sortie, stderr=subprocess.STDOUT,147                         stdin=subprocess.DEVNULL, start_new_session=True, env=env)148    for i in range(45):149        time.sleep(20)150        try:151            urllib.request.urlopen(BASE + "/v1/models", timeout=5)152            return True, ""153        except Exception:154            pass155        try:156            txt = open(LOG, errors="replace").read()157        except Exception:158            txt = ""159        if FATAL.search(txt):160            return False, raison(txt)161    return False, "jamais pret en 15 min"162 163 164def kv_annonce():165    try:166        txt = open(LOG, errors="replace").read()167        m = re.findall(r"GPU KV cache size: ([\d,]+) tokens.*?concurrency "168                       r"for [\d,]+ tokens per request: ([\d.]+)x", txt)169        if m:170            return int(m[-1][0].replace(",", "")), float(m[-1][1])171    except Exception:172        pass173    return None, None174 175 176def preuve():177    """Le levier a-t-il MORDU ? Sans ca, un reglage ignore ressemble a un178    reglage sans effet -- exactement le piege d'--async-scheduling."""179    try:180        txt = open(LOG, errors="replace").read()181        out = []182        for motif in (r"cudagraph_mode[^,}\n]{0,40}",183                      r"enabled_custom_ops[^\n]{0,120}",184                      r"custom_ops'?: ?\[[^\]]{0,80}\]",185                      r"'fuse_norm_quant': ?\w+",186                      r"Capturing CUDA graphs \(\w+\)"):187            t = re.findall(motif, txt)188            if t:189                out.append(t[-1][:120])190        return " ; ".join(out)[:300]191    except Exception:192        return ""193 194 195def appel(prompt, n, ignore=True):196    corps = json.dumps({"model": "flashnext", "prompt": prompt, "max_tokens": n,197                        "ignore_eos": ignore, "temperature": 0.7}).encode()198    t = time.time()199    r = json.loads(urllib.request.urlopen(urllib.request.Request(200        BASE + "/v1/completions", data=corps,201        headers={"content-type": "application/json"}), timeout=400).read().decode())202    u = r["usage"]203    return (u["completion_tokens"], u["prompt_tokens"], time.time() - t,204            r["choices"][0]["text"])205 206 207def div4(texte):208    mots = texte.split()209    if len(mots) < 40:210        return None211    q = [tuple(mots[i:i + 4]) for i in range(len(mots) - 3)]212    return len(set(q)) / len(q)213 214 215def banc(nom):216    r = {"nom": nom}217    r["kv"], r["conc"] = kv_annonce()218    r["preuve"] = preuve()219    if not attendre_vide():220        r["erreur"] = "moteur jamais libre"221        return r222    try:223        appel("Bonjour.", 64)224        n, _, d, _ = appel("Ecris un texte long et detaille sur la mer.", 300)225        r["solo"] = round(n / d, 1)226        r["ms_pas"] = round(d * 1000 / n, 2)227        t = time.time()228        with ThreadPoolExecutor(max_workers=8) as ex:229            res = list(ex.map(lambda i: appel(230                "Ecris un texte long et detaille sur la mer numero %d." % i, 200),231                range(8)))232        r["agg8"] = round(sum(x[0] for x in res) / (time.time() - t), 1)233        long = " ".join("mot%d" % i for i in range(24000))234        _, p1, d1, _ = appel(long, 1)235        r["prefill"] = round(p1 / d1)236        _, _, _, txt = appel("Explique en detail comment fonctionne une maree, "237                             "en francais.", 220, ignore=False)238        r["div4"] = round(div4(txt), 3) if div4(txt) else None239    except Exception as e:240        r["erreur"] = repr(e)[:200]241    return r242 243 244resultats = []245dire("=== REFERENCE (sans redemarrage)")246ref = banc("base")247resultats.append(ref)248dire("  base : %s" % json.dumps(ref, ensure_ascii=False))249 250for nom, remplace, ajout, retire in CONFIGS:251    if time.time() - DEBUT > PLAFOND:252        dire("!!! plafond de temps, on passe a la config finale")253        break254    dire("=== %s" % nom)255    arreter()256    ok, motif = lancer(nom, remplace, ajout, retire)257    if not ok:258        dire("  %s : NE DEMARRE PAS -- %s" % (nom, motif))259        resultats.append({"nom": nom, "echec": motif})260        continue261    r = banc(nom)262    resultats.append(r)263    dire("  %s : %s" % (nom, json.dumps(r, ensure_ascii=False)))264 265with open("/travail/balayage5.json", "w") as f:266    json.dump(resultats, f, indent=1, ensure_ascii=False)267 268 269def trouve(nom):270    for r in resultats:271        if r["nom"] == nom and "echec" not in r and "erreur" not in r:272            return r273    return None274 275 276base = trouve("base") or {}277opts, ajout, retire, raisons, meilleur = {}, [], [], [], None278for nom, remplace, aj, re_ in CONFIGS:279    r = trouve(nom)280    if not r or not r.get("solo") or not r.get("agg8"):281        continue282    if (r["solo"] > (base.get("solo") or 0) * 1.01283            and r["agg8"] >= (base.get("agg8") or 0) * 0.98284            and (r.get("div4") or 0) >= 0.8):285        if not meilleur or r["solo"] > meilleur["solo"]:286            meilleur, opts, ajout, retire = r, remplace, aj, re_287            raisons = ["%s : solo %s vs %s, agg8 %s vs %s" %288                       (nom, r["solo"], base.get("solo"),289                        r["agg8"], base.get("agg8"))]290 291dire("=== CONFIG FINALE : %s" % (", ".join(raisons) or "reference telle quelle"))292arreter()293ok, motif = lancer("final", opts, ajout, retire)294if not ok:295    dire("  finale KO (%s) -- repli sur la reference" % motif)296    arreter()297    ok, motif = lancer("repli", {}, [], [])298    dire("  repli : %s %s" % ("OK" if ok else "ECHEC", motif))299kv, conc = kv_annonce()300dire("=== SERVEUR RENDU : kv=%s, concurrence %sx, options=%s %s" % (kv, conc, opts, ajout))301 302print("\n\n########## TABLEAU ##########")303print("%-11s %9s %6s %6s %8s %7s %6s" %304      ("config", "kv", "solo", "ms/pas", "agg8", "prefill", "div4"))305for r in resultats:306    if "echec" in r:307        print("%-11s  NE DEMARRE PAS : %s" % (r["nom"], r["echec"][:100]))308    elif "erreur" in r:309        print("%-11s  MESURE REFUSEE : %s" % (r["nom"], r["erreur"][:100]))310    else:311        print("%-11s %9s %6s %6s %8s %7s %6s" %312              (r["nom"], r.get("kv"), r.get("solo"), r.get("ms_pas"),313               r.get("agg8"), r.get("prefill"), r.get("div4")))314print("\n########## PREUVE QUE LE LEVIER A MORDU ##########")315for r in resultats:316    if r.get("preuve"):317        print("  %-11s %s" % (r["nom"], r["preuve"]))318