k3-a40-bootstrap / agent_bench.py
patdev's picture
v68 final : A/B du pas, coupure proxy, pont par defaut
52caca6 verified
Raw
History Blame Contribute Delete
3.78 kB
"""Banc 'N agents Claude Code' : N sessions en parallele, chacune un prompt
propre (~PROMPT_K jetons, sale), puis T tours qui ajoutent un tool_result de
~3 k jetons. Flux SSE : TTFT, debit de decodage par flux, cache_read. Verdict
= debit agrege en regime mixte prefill/decodage, ce que les bancs courts ne
voient pas."""
import json, sys, time, uuid, threading, requests
B="https://y26flhzhufrkb3-8080.proxy.runpod.net"
H={"User-Agent":"curl/8","x-api-key":"x","anthropic-version":"2023-06-01","content-type":"application/json","accept":"text/event-stream"}
N=int(sys.argv[1]) if len(sys.argv)>1 else 6
T=int(sys.argv[2]) if len(sys.argv)>2 else 4
PROMPT_K=int(sys.argv[3]) if len(sys.argv)>3 else 50
OUT=int(sys.argv[4]) if len(sys.argv)>4 else 300
ADD_K=int(sys.argv[5]) if len(sys.argv)>5 else 1 # k jetons ajoutes par tour (tool_result) ; 30 = "agent lecteur"
MODEL="claude-ornith[1m]"
res=[]; lock=threading.Lock()
def stream(msgs, agent, tour):
body={"model":MODEL,"max_tokens":OUT,"stream":True,"messages":msgs,"thinking":{"type":"enabled","budget_tokens":OUT//2}}
t0=time.time(); first=None; n=0; usage={}
with requests.post(B+"/v1/messages",headers=H,json=body,stream=True,timeout=900) as r:
if r.status_code!=200:
with lock: print(f"A{agent} t{tour} HTTP {r.status_code}",flush=True)
return None
for line in r.iter_lines():
if not line or not line.startswith(b"data:"): continue
try: d=json.loads(line[5:])
except Exception: continue
ty=d.get("type")
if ty=="content_block_delta":
if first is None: first=time.time()
n+=1
elif ty=="message_start": usage.update(d["message"].get("usage",{}))
elif ty=="message_delta": usage.update(d.get("usage",{}))
t1=time.time(); ttft=(first or t1)-t0; dec=(t1-(first or t1))
out=usage.get("output_tokens",n); cr=usage.get("cache_read_input_tokens",0) or 0; inp=usage.get("input_tokens",0)
row=dict(agent=agent,tour=tour,total=inp+cr,cache_read=cr,miss=inp,ttft=ttft,out=out,tps=out/dec if dec>0 else 0,wall=t1-t0)
with lock:
res.append(row); print(f"A{agent} t{tour} ctx={inp+cr:6d} miss={inp:6d} TTFT={ttft:5.1f}s out={out:4d} decode={row['tps']:5.1f} j/s ({t1-t0:5.1f}s)",flush=True)
return row
def agent(i):
salt=uuid.uuid4().hex
doc=("Dossier %s, pièce %%d : le contrôleur valide la saisie, journalise l'appel, puis délègue au service métier qui persiste l'entité et publie un événement. " % salt)
doc="".join(doc % k for k in range(PROMPT_K*1000//45))
msgs=[{"role":"user","content":doc+"\nRelis ce dossier et dis-moi ce qu'il manque."}]
for t in range(T):
stream(msgs,i,t)
msgs.append({"role":"assistant","content":[{"type":"text","text":"Je lis la pièce suivante."}]})
msgs.append({"role":"user","content":("Pièce complémentaire %d-%d : "%(i,t)+"ligne de journal applicatif, horodatée, niveau INFO, corrélée à la requête. ")*(150*ADD_K)})
t0=time.time(); th=[threading.Thread(target=agent,args=(i,)) for i in range(N)]
[x.start() for x in th]; [x.join() for x in th]
wall=time.time()-t0
tot_out=sum(r["out"] for r in res); tot_miss=sum(r["miss"] for r in res); tot_ctx=sum(r["total"] for r in res)
print(f"\nBILAN N={N} T={T} prompt~{PROMPT_K}k : {len(res)} requetes en {wall:.0f}s | sortie {tot_out} jetons = {tot_out/wall:.0f} j/s agrege | prefill reel {tot_miss} jetons = {tot_miss/wall:.0f} j/s | cache {100*(1-tot_miss/max(1,tot_ctx)):.0f} % | TTFT moy {sum(r['ttft'] for r in res)/len(res):.1f}s | decode moy/flux {sum(r['tps'] for r in res)/len(res):.1f} j/s")
json.dump(res,open("agent_bench_last.json","w"))