Asosiy qismga o'tish

tez10 va tez11: qayta training qayerda to'xtadi

22-bob 14 kod bloki 3597 so'z

Bitta o'zgarish plani

21-bob shu plan bilan tugadi: tez10 = tez8 ning aynan o'zi + faqat 858 ta "qila olmayman" qatori. tez9 da oltita narsa birga o'zgargani uchun sababni bilib bo'lmagan edi. Endi bitta o'zgarish — va natija nima uchun shunday ekanini aniq bilamiz, degan umid.

Avval ma'lumotni tekshiramiz. Ikkita training faylini qatorma-qator solishtiramiz:

python19 qator
import json
from collections import Counter

def qatorlar(model):
    return [json.loads(q) for q in open(Path.home() / f"tez_runs/{model}/data/train.jsonl", encoding="utf-8")]

t8, t10 = qatorlar("tez8"), qatorlar("tez10")
print(f"tez8: {len(t8):,} qator, tez10: {len(t10):,} qator, farq: {len(t10) - len(t8):,}")

def kalit(qator):
    return json.dumps(qator["messages"][1:], ensure_ascii=False)

def tizim(qator):
    return qator["messages"][0]["content"]

savol8 = {kalit(q): tizim(q) for q in t8}
umumiy = [q for q in t10 if kalit(q) in savol8]
print(f"tez8 ning so'rov va javoblari tez10 da: {len(umumiy):,} ta")
print(f"ulardan tizim prompti ham aynan bir xil: {sum(tizim(q) == savol8[kalit(q)] for q in umumiy):,} ta")
Chiqish
tez8: 19,886 qator, tez10: 20,744 qator, farq: 858
tez8 ning so'rov va javoblari tez10 da: 19,886 ta
ulardan tizim prompti ham aynan bir xil: 0 ta

So'rovlar va javoblar — aynan tez8 niki. Lekin tizim prompti bittasida ham bir xil emas. Qaysi qator farq qiladi?

python7 qator
birinchi = umumiy[0]
eski = savol8[kalit(birinchi)].splitlines()
yangi = tizim(birinchi).splitlines()
for a, b in zip(eski, yangi):
    if a != b:
        print("tez8 :", a)
        print("tez10:", b)
Chiqish
tez8 : - group_and_total(group_by: columns, value_column: column, agg: sum|count|avg|min|max, share: boolean?): Group rows by columns and aggregate a value column. share adds each group's percent of the total
tez10: - group_and_total(group_by: columns?, value_column: column, agg: sum|count|count_distinct|avg|min|max, share: boolean?): Group rows by columns and aggregate a value column. Without group_by it gives one total for all rows. count_distinct counts different values. share adds each group's percent of the total

group_and_total toolining ta'rifi. tez9 uchun toollar fayli o'zgargan edi (butun jadval bo'yicha jami), tez10 ning asosi esa shu yangi fayl bilan qayta eksport qilingan. Natijada tez10 da bitta emas, ikkita o'zgarish bor: 858 ta yangi qator va har bir qatordagi yangi tool ta'rifi. Bu farq trainingdan keyin, natija tahlilida topildi. Xatolar jurnalida — 21-raqam:

python7 qator
def jurnal(raqam):
    for qator in Path("/mnt/c/Users/Admin/Desktop/Offline/12-mistakes-and-lessons.md").read_text(encoding="utf-8").splitlines():
        if qator.startswith(f"| {raqam} |"):
            return [q.strip() for q in qator.strip("|").split("|")]

for ustun, matn in zip(("xato", "narxi", "qoida"), jurnal(21)[2:]):
    print(f"{ustun}: {matn}\n")
Chiqish
xato: Called tez10 "tez8's data plus 858 refusal rows" (plan 14, book chapter 29) without diffing the two training files. The base was re-exported after the tools.json change, so the `group_and_total` description line differs from tez8's in all 19,886 rows; requests, answers and tables are identical.

narxi: tez10 carries two changes, not one, and the single-change rule from mistake 16 was broken without anyone noticing until after the benchmark.

qoida: Before training, diff the new training file against the current model's file field by field and write every difference in the run note.

Qoida oddiy: trainingdan oldin yangi faylni joriy model fayli bilan maydonma-maydon solishtirish va har bir farqni yozib qo'yish.

Natija

tez10 ham 745 savolda, tez8 bilan bir xil grammar va bir xil hakam bilan sinaldi:

python27 qator
import glob
import random

TOZA = ["loans", "deposits", "cash_desk", "transactions", "realistic_loans", "realistic_deposits", "realistic_cash"]
GURUHLAR = {
    "toza": TOZA,
    "shovqinli": [f"noisy_{t}" for t in TOZA],
    "real": sorted(Path(f).parent.name for f in glob.glob(str(TEZ / "accuracy/suites/real_*/questions.json"))),
}
HAMMASI = sum(GURUHLAR.values(), [])

def hukmlar(prefiks, toplamlar):
    return {(t, j["id"]): j for t in toplamlar for j in map(json.loads, open(TEZ / f"results/{prefiks}_cpu_{t}.jsonl", encoding="utf-8"))}

def oraliq(prefiks_a, prefiks_b, toplamlar, marta=10000):
    a, b = hukmlar(prefiks_a, toplamlar), hukmlar(prefiks_b, toplamlar)
    farqlar = [int(b[k]["correct"]) - int(a[k]["correct"]) for k in sorted(a)]
    tasodif = random.Random(1)
    yigindilar = sorted(sum(tasodif.choice(farqlar) for _ in farqlar) for _ in range(marta))
    return sum(farqlar), yigindilar[int(0.025 * marta)], yigindilar[int(0.975 * marta)]

for guruh, toplamlar in GURUHLAR.items():
    a, b = hukmlar("tez8g", toplamlar), hukmlar("tez10g", toplamlar)
    print(f"{guruh:10s} {len(a):4d} savol:  tez8 {sum(j['correct'] for j in a.values())},  tez10 {sum(j['correct'] for j in b.values())}")
for nom, toplamlar in (("real", GURUHLAR["real"]), ("hammasi", HAMMASI)):
    farq, past, yuqori = oraliq("tez8g", "tez10g", toplamlar)
    print(f"{nom:8s} tez10 - tez8 = {farq:+d}, 95% oraliq [{past:+d}, {yuqori:+d}]")
Chiqish
toza        320 savol:  tez8 315,  tez10 307
shovqinli   320 savol:  tez8 311,  tez10 309
real        105 savol:  tez8 81,  tez10 82
real     tez10 - tez8 = +1, 95% oraliq [-5, +7]
hammasi  tez10 - tez8 = -9, 95% oraliq [-20, +1]

Jami bo'yicha tez10 tez8 dan to'qqiz savolga past — xuddi tez9 kabi. Real jadvallarda esa biroz yuqori, lekin oraliq nolni o'z ichiga oladi.

Faqat to'g'ri javoblar sonini sanash bu yerda yetarli emas. Tez uchun ikkita savol muhimroq: model javob berganda qanchalik ishonchli (precision — aniqlik), va berishi kerak bo'lgan savolni qanchalik beradi (ask recall — savolni topish ulushi):

python10 qator
def matritsa(prefiks, toplamlar):
    sanoq = Counter(j["reason"] if not j["reason"].startswith("plan failed") else "plan failed"
                    for j in hukmlar(prefiks, toplamlar).values())
    javoblar = sum(v for k, v in sanoq.items() if k not in ("asked", "asked on a clear request"))
    savollar = sanoq["asked"] + sanoq["guessed instead of asking"]
    return sanoq["same result"], javoblar, sanoq["asked"], savollar

for prefiks in ("tez8g", "tez10g"):
    togri, javoblar, topildi, savollar = matritsa(prefiks, GURUHLAR["real"])
    print(f"{prefiks[:-1]:6s} real: precision {togri}/{javoblar} = {togri / javoblar:.1%},  ask recall {topildi}/{savollar}")
Chiqish
tez8   real: precision 75/99 = 75.8%,  ask recall 6/15
tez10  real: precision 71/90 = 78.9%,  ask recall 11/15

Mana tez10 ning haqiqiy yutug'i: real jadvallarda javob berganda u tez8 dan aniqroq, va berishi kerak bo'lgan savollarning ko'pini beradi. "Qila olmayman" qatorlari ishladi. Lekin jami bo'yicha baribir past — boshqa joylarda to'g'ri javoblar yo'qoldi.

858 qatorning shakli

Nega "qila olmayman" ko'nikmasi yarim-yarti ishladi? Qatorlarning o'zini ko'raylik — tez10 ga qo'shilgan 858 ta so'rov va real testdagi tool_gap tuzoqlari uzunligi (so'zlarda):

python8 qator
yangi = [json.loads(q) for q in open(Path.home() / "tez_runs/tez11/data/new.jsonl", encoding="utf-8")]
qator_soz = Counter(len(q["messages"][-2]["content"].split()) for q in yangi)
test_soz = Counter(len(s["text"].split()) for t in GURUHLAR["real"]
                   for s in json.load(open(TEZ / f"accuracy/suites/{t}/questions.json", encoding="utf-8")) if s.get("trap") == "tool_gap")
print(f"o'qitish qatorlari: {len(yangi)} ta, eng qisqasi {min(qator_soz)} so'z, eng uzuni {max(qator_soz)} so'z")
print(f"  10-16 so'zli: {sum(v for k, v in qator_soz.items() if 10 <= k <= 16)} ta")
print(f"test tool_gap savollari: {sum(test_soz.values())} ta, {min(test_soz)} dan {max(test_soz)} so'zgacha")
print("namuna:", yangi[0]["messages"][-2]["content"])
Chiqish
o'qitish qatorlari: 858 ta, eng qisqasi 6 so'z, eng uzuni 36 so'z
  10-16 so'zli: 586 ta
test tool_gap savollari: 6 ta, 4 dan 8 so'zgacha
namuna: Kelgusi chorakda 'Logistika' sohasi uchun 'Kredit liniyasi' mahsuloti bo'yicha kutilgan jami summan taxmin qilish mumkinmi?

Training qatorlari uzun, rasmiy jumlalar. Test savollari esa — foydalanuvchi qanday yozsa shunday: qisqa. Model "qila olmayman"ni faqat uzun, rasmiy so'rovlarda o'rgandi. Bu 6-xatoning takrori edi — jurnalda 22-raqam (jurnalda test savollari "3 dan 8 so'zgacha" deb yozilgan; bu yerda o'lchangan oltita real tool_gap savoli — 4 dan 8 gacha; xulosa o'zgarmaydi):

python2 qator
for ustun, matn in zip(("xato", "narxi", "qoida"), jurnal(22)[2:]):
    print(f"{ustun}: {matn}\n")
Chiqish
xato: The 858 refusal rows went into tez10 without the length check from mistake 6: none is shorter than 6 words, most are 10 to 16, while the test tool-gap questions are 3 to 8 words.

narxi: The gate's short-request gaps moved the wrong way (4-5 words 21% to 20%, 1-3 words 16% to 15%); the model saw refusals only in long, formal phrasing.

qoida: Every added row set, refusals included, passes the coverage gate on its own before it joins the training file.

Javoblar almashinuvi

tez9 va tez10 — ikkalasi ham to'qqiz savolga past. Tasodifmi? Ikkalasida ham asos tez8 ning ma'lumoti, o'zgarish esa kichik. Har bir yangi training tez8 ning javoblaridan qanchasini o'zgartiradi?

python6 qator
for prefiks in ("tez9g", "tez10g"):
    a, b = hukmlar("tez8g", HAMMASI), hukmlar(prefiks, HAMMASI)
    ozgardi = sum(a[k]["output"] != b[k]["output"] for k in a)
    buzildi = sum(a[k]["correct"] and not b[k]["correct"] for k in a)
    tuzaldi = sum(b[k]["correct"] and not a[k]["correct"] for k in a)
    print(f"{prefiks[:-1]:6s}: {ozgardi} ta javob o'zgardi ({ozgardi / len(a):.1%}), {tuzaldi} tasi tuzaldi, {buzildi} tasi buzildi")
Chiqish
tez9  : 128 ta javob o'zgardi (17.2%), 8 tasi tuzaldi, 17 tasi buzildi
tez10 : 120 ta javob o'zgardi (16.1%), 10 tasi tuzaldi, 19 tasi buzildi

Bu hodisaning nomi — churn (javoblar almashinuvi). Har bir qayta training javoblarning taxminan oltidan birini o'zgartiradi, ba'zisini to'g'riga, ba'zisini noto'g'riga. Toza to'plamlardagi ship chegarasi esa (320 dan 310) shu almashinuv ichida turardi: oddiy qayta trainingning o'zi uni kesib o'tishi mumkin edi. Jurnalda — 23-raqam:

python2 qator
for ustun, matn in zip(("xato", "narxi", "qoida"), jurnal(23)[2:]):
    print(f"{ustun}: {matn}\n")
Chiqish
xato: Set the clean ship bar (310 of 320) without measuring how much a plain retrain changes. Older consecutive models changed 16 to 18% of outputs and broke 1 to 5 of 200 answers with near-identical data; tez8 sits at 315, so that churn alone can cross the bar.

narxi: tez9 and tez10 both missed clean by 7 to 8 answers, a size the churn baseline can produce on its own; the bar cannot tell a harmful change from an ordinary retrain.

qoida: Measure retrain churn with a control run before setting a regression bar, and set the bar as a margin above that churn.

tez11: noldan emas, davomidan

Agar har bir noldan training javoblarning oltidan birini o'zgartirsa, yechim ko'rinadi: noldan o'qitmaslik. tez8 ning tayyor adapter ini olib, uni yangi qatorlar bilan davom ettirish — continuation training (davomli training). Eski ko'nikmalar unutilmasligi uchun yangi qatorlarga tez8 ning eski qatorlaridan tasodifiy namuna qo'shiladi — replay (takrorlash). O'quv tezligi (learning rate) esa noldan trainingnikidan to'rt barobar past.

Buning uchun alohida skript yozildi, training/update.py. Uning yuragi — ikki kichik funksiya:

python5 qator
import ast
manba = (TEZ / "training/update.py").read_text(encoding="utf-8")
for tugun in ast.parse(manba).body:
    if isinstance(tugun, ast.FunctionDef) and tugun.name in ("mix", "load_model"):
        print(ast.get_source_segment(manba, tugun), end="\n\n")
Chiqish
def mix(new, replay, replay_rows, seed):
    rng = random.Random(seed)
    rows = [*new, *rng.sample(replay, min(replay_rows, len(replay)))]
    rng.shuffle(rows)
    return rows

def load_model(base, adapter, quantization):
    import torch
    from peft import PeftModel, prepare_model_for_kbit_training
    from transformers import AutoModelForCausalLM, BitsAndBytesConfig

    quant = BitsAndBytesConfig(**{**quantization, "bnb_4bit_compute_dtype": getattr(torch, quantization["bnb_4bit_compute_dtype"])})
    model = AutoModelForCausalLM.from_pretrained(base, quantization_config=quant, dtype=torch.bfloat16, device_map={"": 0})
    model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True)
    return PeftModel.from_pretrained(model, adapter, is_trainable=True)

mix — yangi qatorlar va replay namunasi, aralashtirilgan. load_model — asos model 4-bitda, ustiga tez8 ning adapter i o'qitiladigan holda yuklanadi. Training yozuvi:

python3 qator
yozuv = json.load(open(Path.home() / "tez_runs/tez11/tez11a/output/update_run.json", encoding="utf-8"))
for k in ("rows", "replay_rows", "lr", "epochs"):
    print(f"{k}: {yozuv[k]}")
Chiqish
rows: 4290
replay_rows: 3432
lr: 5e-05
epochs: 1

858 ta yangi qator va 3,432 ta replay — jami 4,290 qator, o'quv tezligi 5e-5. Ikkinchi variant, tez11b, ikki barobar yuqori tezlik bilan o'qitildi.

tez11 natijasi

python10 qator
a, b = hukmlar("tez8g", HAMMASI), hukmlar("tez11ag", HAMMASI)
for guruh, toplamlar in GURUHLAR.items():
    print(f"{guruh:10s} tez8 {sum(a[k]['correct'] for k in a if k[0] in toplamlar)},  tez11a {sum(b[k]['correct'] for k in b if k[0] in toplamlar)}")
ozgardi = sum(a[k]["output"] != b[k]["output"] for k in a)
print(f"o'zgargan javoblar: {ozgardi} ({ozgardi / len(a):.1%}), tuzaldi {sum(b[k]['correct'] and not a[k]['correct'] for k in a)}, buzildi {sum(a[k]['correct'] and not b[k]['correct'] for k in a)}")

tayyor = [t for t in HAMMASI if (TEZ / f"results/tez11bg_cpu_{t}.jsonl").exists()
          and sum(1 for _ in open(TEZ / f"results/tez11bg_cpu_{t}.jsonl")) == len([k for k in a if k[0] == t])]
a2, b2 = hukmlar("tez8g", tayyor), hukmlar("tez11bg", tayyor)
print(f"tez11b ({len(a2)} savolda to'xtatildi): tuzaldi {sum(b2[k]['correct'] and not a2[k]['correct'] for k in a2)}, buzildi {sum(a2[k]['correct'] and not b2[k]['correct'] for k in a2)}")
Chiqish
toza       tez8 315,  tez11a 310
shovqinli  tez8 311,  tez11a 310
real       tez8 81,  tez11a 84
o'zgargan javoblar: 111 (14.9%), tuzaldi 8, buzildi 11
tez11b (520 savolda to'xtatildi): tuzaldi 1, buzildi 9

Davomli training ham javoblarning taxminan yettidan birini o'zgartirdi — noldan trainingdagidan ozgina kam xolos. Real jadvallarda tez11a uchta savolga yaxshiroq, lekin toza va shovqinli to'plamlarda past, va jami bo'yicha tuzatganidan ko'ra ko'proq buzdi. tez11b esa yarmida to'xtatildi: 520 savolning o'zidayoq buzilganlar tuzalganlardan ko'p edi.

Nega aynan shu javoblar o'zgaradi

Bu bobning eng muhim natijasi shu savolga javob. Tez har bir javobning har bir token i uchun modelning ishonchini yozib oladi (29-bobda bu qayerdan kelishini ko'ramiz). tez8 ning shu javoblardagi eng past token ishonchini ikki guruhga ajratamiz: tez11a o'zgartirgan javoblar va o'zgartirmaganlari:

python5 qator
ishonch = {(t, j["id"]): j["min_prob"] for t in HAMMASI for j in map(json.loads, open(TEZ / f"results/tez8c_cpu_{t}.jsonl", encoding="utf-8"))}
ozgargan = sorted(ishonch[k] for k in a if a[k]["output"] != b[k]["output"])
qolgan = sorted(ishonch[k] for k in a if a[k]["output"] == b[k]["output"])
for nom, guruh in (("o'zgargan", ozgargan), ("o'zgarmagan", qolgan)):
    print(f"{nom:12s} {len(guruh):4d} ta: median {guruh[len(guruh) // 2]:.3f},  0.7 dan past: {sum(p < 0.7 for p in guruh) / len(guruh):.0%}")
Chiqish
o'zgargan     111 ta: median 0.320,  0.7 dan past: 92%
o'zgarmagan   634 ta: median 0.985,  0.7 dan past: 7%

Farq keskin. O'zgargan javoblar — tez8 o'zi ikkilangan javoblar: qaysidir token da u ikki variant orasida deyarli teng turgan. O'zgarmaganlari — ishonchli. Ya'ni almashinuv tasodifiy emas: model ikkilangan joylarda har qanday training — noldan yoki davomidan, past yoki yuqori tezlikda — tarozini u yoki bu tomonga og'diradi. Ba'zan to'g'riga, ba'zan noto'g'riga.

Shuning uchun har bir modelda "yangi teshiklar" paydo bo'lardi: tez9, tez10, tez11 — hammasi tez8 ning ikkilangan javoblarini qaytadan "tashlab" ko'rdi. Jurnalda — 24-raqam:

python2 qator
for ustun, matn in zip(("xato", "narxi", "qoida"), jurnal(24)[2:]):
    print(f"{ustun}: {matn}\n")
Chiqish
xato: Told the user continuation training (tez8 adapter, lr 5e-5, 858 refusal rows plus 3,432 replay rows) was the way to change the model without churn, without a small pilot measuring churn first.

narxi: tez11a changed 111 of 745 outputs (15%) and broke 11 tez8 answers for 8 fixes; the changed answers were the ones tez8 was already unsure about (median lowest-token probability 0.32 vs 0.985 for unchanged), so any weight change flips them.

qoida: Never promise a training method's side effects before measuring them on a pilot; check output churn on a few hundred questions before a full run.

Qayta training to'xtadi

tez10 va tez11 dan ikki xulosa chiqdi.

Birinchisi — yomon xabar: kichik modelni kichik qadam bilan yaxshilab bo'lmaydi. Har qanday training ikkilangan javoblarni aralashtiradi, va "tez8 ning to'g'ri javoblarini buzmaslik" talabi deyarli bajarib bo'lmaydigan bo'ladi.

Ikkinchisi — yaxshi xabar: model qayerda ikkilanayotganini biz ko'ra olamiz. Ishonch raqami o'zgargan javoblarni oldindan ajratdi. Demak modelni qayta training o'rniga uning atrofida ishlash mumkin: ishonch past bo'lsa — savol berish, natija shubhali bo'lsa — to'xtatish, jadvalni yaxshiroq ta'riflash. Bu qatlamlar modelning og'irliklariga tegmaydi, shuning uchun hech narsani aralashtirmaydi. Ular — 29-bobda.

Xulosa

  • tez10 "bitta o'zgarish" bo'lishi kerak edi, lekin toollar fayli o'zgargani uchun har bir qatorning tizim prompti ham o'zgargan: ikki o'zgarish. Trainingdan oldin fayllarni maydonma-maydon solishtirish kerak.
  • tez10: jami 745 dan tez8 dan to'qqiz savolga past; real jadvallarda precision va ask recall yuqori.
  • 858 ta "qila olmayman" qatori uzun va rasmiy, test savollari esa qisqa: ko'nikma yarim-yarti o'tdi.
  • Har bir qayta training javoblarning taxminan oltidan birini almashtiradi (churn), ship chegarasi esa shu almashinuv ichida turardi.
  • tez11 (davomli training, replay, past tezlik) ham deyarli shuncha almashtirdi va tuzatganidan ko'p buzdi.
  • O'zgaradigan javoblar — tez8 ning o'zi ikkilangan javoblari. Bu ishonchni o'lchab, model atrofida ishlash yo'lini ochdi.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar