CoolFace
Modelpublic

patdev/k3-a40-bootstrap

sourceHugging Faceotherupdated 18d agoView on Hugging Face
0likes1.3kdownloads
chien.py195 linesDownload Raw Back to root
1# Chien de garde du pont et du moteur.2#3# POURQUOI. Trois fois le 27/08, le pont a cesse de servir alors que le moteur4# allait parfaitement bien. Signature invariable :5#6#     /v1/models          200          <- servi localement, sans appel amont7#     /v1/messages        500 en ~10 s <- pool=10.0 de httpx8#     127.0.0.1:8000      200 en 0,1 s <- LE MOTEUR VA BIEN9#10# La cause n'est PAS un plafond de temps : avec VL_TIMEOUT=240 la panne11# revient, et au moment du blocage il y avait **zero socket** ouvert vers le12# port 8000. Ce ne sont donc pas des connexions vivantes qui saturent le13# bassin, ce sont des PLACES comptabilisees et jamais rendues -- des flux14# abandonnes par le client dont le slot httpx n'est pas libere. La fuite est15# definitive par flux, pas temporelle.16#17# Tant que le correctif de code n'est pas ecrit et eprouve, ce chien convertit18# une panne silencieuse de plusieurs heures en une coupure de quelques secondes.19#20# IL NE SONDE JAMAIS /v1/models : c'est precisement l'endpoint qui ment.21import json22import os23import subprocess24import time25import urllib.error26import urllib.request27 28PONT = "http://127.0.0.1:8080"29MOTEUR = "http://127.0.0.1:8000"30JOURNAL = "/travail/chien.log"31PERIODE = 6032ECHECS_AVANT_ACTION = 2      # deux passes de suite : on ne relance pas sur un hoquet33 34ENV_PONT = {35    "VL_UPSTREAM": "http://127.0.0.1:8000",36    "VL_SERVED_NAME": "flashnext",37    "VL_REAL_MODEL": "RadixArk/Qwen3.8-Flash-Next-NVFP4",38    "VL_MAX_OUTPUT": "32768",39    "VL_TIMEOUT": "240",40}41 42MOTEUR_CMD = [43    "vllm", "serve", "RadixArk/Qwen3.8-Flash-Next-NVFP4",44    "--served-model-name", "flashnext", "--host", "127.0.0.1", "--port", "8000",45    "--max-model-len", "262144", "--max-num-seqs", "16",46    "--gpu-memory-utilization", "0.93",47    "--kv-cache-memory-bytes", "15569256448",   # 14,5 GiB : marge pour l'encodeur d'images48    "--distributed-executor-backend", "mp",49    "--reasoning-parser", "qwen3", "--tool-call-parser", "qwen3_coder",50    # Le plafond a 0 rendait un 400 sur toute capture collee dans Claude Code51    # ("At most 0 image(s) may be provided..."). Il doit rester identique a52    # celui de lancer_flashnext.sh : sinon le chien "repare" en remettant les53    # images hors service, et la panne revient sans que rien ne l'explique.54    "--limit-mm-per-prompt", '{"image":4,"video":0}',55    "--trust-remote-code", "--enable-prefix-caching", "--enable-auto-tool-choice",56    "--enable-flashinfer-autotune", "--enable-prompt-tokens-details",57]58 59 60def dire(m):61    l = "[%s] %s" % (time.strftime("%H:%M:%S"), m)62    print(l, flush=True)63    try:64        with open(JOURNAL, "a") as f:65            f.write(l + "\n")66    except Exception:67        pass68 69 70def poste(url, corps, delai):71    """Rend (ok, detail). Une VRAIE generation, jamais /v1/models."""72    try:73        r = urllib.request.Request(74            url, data=json.dumps(corps).encode(),75            headers={"content-type": "application/json",76                     "anthropic-version": "2023-06-01", "x-api-key": "x"})77        t = time.time()78        d = json.loads(urllib.request.urlopen(r, timeout=delai).read().decode())79        return True, "%.2fs" % (time.time() - t)80    except urllib.error.HTTPError as e:81        return False, "HTTP %d" % e.code82    except Exception as e:83        return False, repr(e)[:90]84 85 86def pont_vivant():87    return poste(PONT + "/v1/messages",88                 {"model": "flashnext", "max_tokens": 8,89                  "thinking": {"type": "disabled"},90                  "messages": [{"role": "user", "content": "ping"}]}, 45)91 92 93def moteur_vivant():94    return poste(MOTEUR + "/v1/completions",95                 {"model": "flashnext", "prompt": "ping", "max_tokens": 4,96                  "temperature": 0}, 45)97 98 99def tuer(motif):100    out = subprocess.run(["ps", "-eo", "pid,rss,comm", "--no-headers"],101                         capture_output=True, text=True).stdout102    for l in out.splitlines():103        p = l.split(None, 2)104        if len(p) != 3:105            continue106        pid, rss, comm = int(p[0]), int(p[1]), p[2].strip()107        import re as _re108        if _re.match(motif, comm) or (rss > 10_000_000 and _re.match(r"^python3?$", comm)):109            try:110                os.kill(pid, 9)111            except Exception:112                pass113 114 115def relancer_pont():116    """Le pont ne se relance JAMAIS depuis /proc/PID/cmdline : l'environnement117    n'y figure pas, et le defaut de VL_UPSTREAM est le port du pont lui-meme --118    il se parlerait a lui-meme. L'env est donc explicite ici."""119    p = subprocess.run(["pgrep", "-f", "uvicorn anthropic_proxy"],120                       capture_output=True, text=True).stdout.split()121    for pid in p:122        try:123            os.kill(int(pid), 9)124        except Exception:125            pass126    time.sleep(3)127    env = dict(os.environ)128    env.update(ENV_PONT)129    with open("/travail/pont.log", "a") as s:130        subprocess.Popen(["python3", "-m", "uvicorn", "anthropic_proxy:app",131                          "--host", "0.0.0.0", "--port", "8080",132                          "--log-level", "warning"],133                         cwd="/travail", stdout=s, stderr=subprocess.STDOUT,134                         stdin=subprocess.DEVNULL, start_new_session=True, env=env)135    for _ in range(20):136        time.sleep(3)137        ok, d = pont_vivant()138        if ok:139            return True, d140    return False, "pas de generation apres 60 s"141 142 143def relancer_moteur():144    tuer(r"^(vllm|VLLM|Ple|EngineCore)")145    time.sleep(12)146    if os.path.exists("/travail/vllm.log"):147        os.replace("/travail/vllm.log", "/travail/vllm-chien-precedent.log")148    with open("/travail/vllm.log", "w") as s:149        subprocess.Popen(MOTEUR_CMD, stdout=s, stderr=subprocess.STDOUT,150                         stdin=subprocess.DEVNULL, start_new_session=True,151                         env=dict(os.environ, VLLM_PLE_CPU_OFFLOAD="1"))152    for _ in range(45):153        time.sleep(20)154        ok, d = moteur_vivant()155        if ok:156            return True, d157    return False, "moteur pas pret en 15 min"158 159 160dire("chien de garde arme : generation reelle toutes les %d s, action apres "161     "%d echecs consecutifs" % (PERIODE, ECHECS_AVANT_ACTION))162echecs, reparations, tours = 0, 0, 0163while True:164    time.sleep(PERIODE)165    tours += 1166    ok, detail = pont_vivant()167    if ok:168        if echecs:169            dire("retabli tout seul apres %d echec(s)" % echecs)170        echecs = 0171        if tours % 15 == 0:172            dire("ok (%s) | %d reparation(s) depuis l'armement" % (detail, reparations))173        continue174 175    echecs += 1176    dire("pont KO (%s) -- echec %d/%d" % (detail, echecs, ECHECS_AVANT_ACTION))177    if echecs < ECHECS_AVANT_ACTION:178        continue179 180    # On distingue TOUJOURS les deux couches avant d'agir : trois fois cette181    # nuit, le moteur allait bien et j'ai failli le tuer pour rien.182    mok, mdetail = moteur_vivant()183    if mok:184        dire("le moteur va bien (%s) -> c'est le pont : relance du pont seul" % mdetail)185        r, d = relancer_pont()186        dire("  pont : %s (%s)" % ("REPARE" if r else "ECHEC", d))187    else:188        dire("le moteur aussi est KO (%s) -> relance moteur puis pont" % mdetail)189        r, d = relancer_moteur()190        dire("  moteur : %s (%s)" % ("REPARE" if r else "ECHEC", d))191        r2, d2 = relancer_pont()192        dire("  pont : %s (%s)" % ("REPARE" if r2 else "ECHEC", d2))193    reparations += 1194    echecs = 0195