UncleanCode's picture
Dynamic Accent Duplex 0.2.0: Dynamic ASR real as default ASR (omniASR fallback)
ac573f1 verified
Raw History Blame
4.95 kB
"""Compare response LLMs on mix-matched replies.
gen <tag> prompts.json out.json (needs a vLLM server at LLM_BASE; full Responder path: mix -> n=3 candidates -> validator -> regen)
bt out1.json [out2.json ...] (MADLAD-400-3B (Apache-2.0) back-translates the non-English spans of user + reply into English)
judge out1.json ... (server LLM rates coherence/relevance of the back-translated reply, 1-5)
summary out1.json ..."""
import sys, json, re, os, statistics as st
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
TAG = re.compile(r"<(eng|ibo|yor|hau|pcm)>(.*?)</\1>", re.S)
def add_native(P):
"""+4 native WAXAL test transcripts per ibo/yor/hau (monolingual user turns)"""
L = [json.loads(l) for l in open("/scratch/duplex/asr_eval/list.jsonl")]
for lg in ("ibo", "yor", "hau"):
rs = [r for r in L if r["lang"] == lg and len(r["text"].split()) >= 6][:4]
P += [dict(id=f"n_{lg}_{i}", kind=f"{lg} native", text=r["text"]) for i, r in enumerate(rs)]
return P
def gen(tag, pf, out):
from respond import Responder
import mix as M
R = Responder(); res = []
for p in add_native(json.load(open(pf))):
R.style = None
r = R.reply(p["text"])
spans = TAG.findall(r["reply"]); glot = []
for l, t in spans:
if l in ("ibo", "yor", "hau") and len(t.split()) >= 2:
s = M.tagger("glotlid")._lid_scores(t); glot.append(max(s, key=s.get) == l)
res.append(dict(p, model=tag, **{k: r[k] for k in ("target", "reply", "reply_mix", "tvd", "how", "first_cand_ok", "any_first_round_ok", "t_llm", "rounds")}, glot_ok=glot))
print(json.dumps({"id": p["id"], "reply": r["reply"], "how": r["how"], "tvd": r["tvd"]}, ensure_ascii=False), flush=True)
json.dump(res, open(out, "w"), ensure_ascii=False, indent=1)
def bt(files):
import torch
from transformers import T5ForConditionalGeneration, T5Tokenizer
import mix as M
tok = T5Tokenizer.from_pretrained("google/madlad400-3b-mt"); m = T5ForConditionalGeneration.from_pretrained("google/madlad400-3b-mt", torch_dtype=torch.bfloat16).cuda().eval()
def tr(s):
ids = tok("<2en> " + s, return_tensors="pt").input_ids.cuda()
with torch.no_grad(): o = m.generate(ids, max_new_tokens=3 * len(s.split()) + 16, num_beams=2, no_repeat_ngram_size=4)
return tok.decode(o[0], skip_special_tokens=True)
def en(tagged):
return " ".join(t if l in ("eng", "pcm") else tr(t) for l, t in TAG.findall(tagged))
for f in files:
R = json.load(open(f))
for r in R:
r["user_bt"] = en(M.tagger("glotlid").tag(r["text"])); r["reply_bt"] = en(r["reply"])
json.dump(R, open(f, "w"), ensure_ascii=False, indent=1); print("bt", f, flush=True)
def judge(files):
from llm import LLM
J = LLM()
for f in files:
R = json.load(open(f))
for r in R:
q = (f"A user said (English gloss): \"{r['user_bt']}\"\nThe assistant replied (English back-translation): \"{r['reply_bt']}\"\n"
"Rate from 1 to 5 how coherent, sensible and relevant the reply is as a spoken assistant response (5 = fully sensible and on-topic, "
"1 = nonsense or off-topic). Output only the digit.")
o, _ = J.generate([{"role": "user", "content": q}], n=1, temperature=0.0, max_tokens=4)
m = re.search(r"[1-5]", o[0]); r["judge"] = int(m.group()) if m else None
json.dump(R, open(f, "w"), ensure_ascii=False, indent=1); print("judged", f, flush=True)
def summary(files):
rows = []
for f in files:
R = json.load(open(f)); tag = R[0]["model"]
def grp(pred):
S = [r for r in R if pred(r)]
if not S: return None
g = [x for r in S for x in r["glot_ok"]]
j = [r["judge"] for r in S if r.get("judge")]
return dict(n=len(S), first_cand_ok=round(sum(r["first_cand_ok"] for r in S) / len(S), 2), regen=round(sum(r["how"] != "first_round" for r in S) / len(S), 2),
fallback=round(sum(r["how"] in ("best_failed", "plain_matrix") for r in S) / len(S), 2), tvd=round(st.mean(r["tvd"] or 0 for r in S), 3),
glot_ok=round(sum(g) / len(g), 2) if g else None, judge=round(st.mean(j), 2) if j else None, llm_s=round(st.median(r["t_llm"] for r in S), 3))
rows.append(dict(model=tag, all=grp(lambda r: True), mixed=grp(lambda r: not r["id"].startswith("n_") and not r["id"].startswith("e_")),
native=grp(lambda r: r["id"].startswith("n_"))))
print(json.dumps(rows, indent=1)); return rows
if __name__ == "__main__":
c = sys.argv[1]
if c == "gen": gen(sys.argv[2], sys.argv[3], sys.argv[4])
elif c == "bt": bt(sys.argv[2:])
elif c == "judge": judge(sys.argv[2:])
else: json.dump(summary(sys.argv[2:]), open("/scratch/duplex/llm_summary.json", "w"), indent=1)