Dịch vụ kỹ thuật hư cấu Novaforge dùng các mã và cách diễn đạt lặp lại trong hướng dẫn của mình. Mục tiêu là điều chỉnh phân phối ngôn ngữ của mô hình, đồng thời giữ một đường tra cứu hướng dẫn đã sửa đổi cho các sự kiện thay đổi.
So sánh likelihood của hướng dẫn trước/sau adaptation, rồi các câu trả lời ngắn cho câu hỏi có hoặc không có ngữ cảnh do chỉ mục từ vựng cục bộ cung cấp. Hiển thị một sự kiện đã thay đổi trong hướng dẫn được sửa sau huấn luyện.
Tải dự án hoàn chỉnh, bản sửa đổi r001
Ba can thiệp khác nhau
CPT tiếp tục mục tiêu token kế tiếp từ trọng số tiền huấn luyện. Ở đây, mọi tham số được điều chỉnh trên các hướng dẫn không chú giải. Không ví dụ hỏi/đáp nào đi vào loss này. SFT sẽ học một phép biến đổi hay cách trả lời từ mục tiêu rõ ràng; đó không phải thí nghiệm được thực hiện. Tìm kiếm tăng cường thay đổi ngữ cảnh cấp khi suy luận và cho phép tra cứu tài liệu gần đây mà không sửa trọng số.
Mô hình là Qwen/Qwen2.5-0.5B base, revision 060db6499f32faf8b98477b0a26969ef7d8b9987, tokenizer không đổi và tải nghiêm ngặt. Nó có 24 block, chiều rộng 896, 14 head truy vấn, hai head K/V, MLP 4864 và vocabulary 151936. Embedding và head đầu ra gắn với nhau; số tham số duy nhất được đo. Mô hình base không phải assistant instruction-tuned. Lỗi định dạng khi trả lời ngắn không hủy một cải thiện NLL có thể có và không được che giấu.
Dữ liệu và điều kiện dành riêng
NovaLex-v1 có 500 family, mỗi family tám hướng dẫn: 350/75/75 family, tức 2800/600/600 tài liệu. CommonWorkshop-v1 có 200 family, mỗi family năm văn bản: 140/30/30 family, tức 700/150/150 tài liệu. Tham chiếu, giá trị và tổ hợp được sinh trước khi dùng; biến thể của một family không vượt partition. Mẫu và vocabulary vẫn chung. Kho nhân tạo này không đại diện độ phức tạp của một kho công nghiệp.
700 văn bản train đối chứng dùng cho replay. 150 văn bản test đối chứng không bao giờ đi vào đó. Delta NLL của chúng đo trôi dạt trên phạm vi tổng hợp này, không phải quên mọi tri thức chung. 200 câu hỏi dựa trên 200 hướng dẫn test đầu, chưa CPT thấy. Đánh giá đóng không cấp ngữ cảnh: mô hình có thể hợp lý khi không biết các sự kiện dành riêng. Đánh giá mở tìm trong 4000 hướng dẫn nguồn, gồm cả test; việc tra cứu này được nêu rõ và tách khỏi huấn luyện.
Bốn mươi hướng dẫn test có phiên bản 2, tạo riêng và đánh dấu inference_only. Số impulses của chúng tăng hai. Giá trị chính xác nằm trong tệp sinh; ví dụ 3→5 mô tả cơ chế mà không áp mọi trường hợp cùng số. v2 không bao giờ được mount vào learning.
Mục tiêu, ranh giới và ngân sách chính xác
Mỗi văn bản được token hóa không chat template hay thêm token ngầm. ID 151643 là anchor đầu và EOS cuối. Cùng ID dùng padding, nhưng attention_mask và label xây theo vị trí: EOS thật vẫn là mục tiêu; PAD bị bỏ qua. Cửa sổ tối đa 512 token bước 511. Mỗi token tiếp theo, cả EOS, xuất hiện đúng một lần trong một tài liệu, không nối tài liệu. Ngữ cảnh khởi động lại theo cửa sổ.
Đề xuất đầu 2048 mục tiêu mỗi cập nhật không chia được chính xác theo 80/20 nguyên. Trước chạy, giao thức cố định 2050 mục tiêu × 512 cập nhật = 1049600 mục tiêu mỗi nhánh, hơn đề xuất 1024. Chỉ miền: 2050 mục tiêu nghiệp vụ mỗi bước. Replay: đúng 1640 nghiệp vụ và 410 đối chứng, tổng 839680 và 209920. Replay nhận ít token nghiệp vụ hơn dù cùng ngân sách tổng: đây là một phần can thiệp, không phải chi tiết được bỏ qua.
Sampler duyệt hoán vị cửa sổ. Khi quota cắt một cửa sổ, chỉ vị trí mục tiêu chọn tham gia loss; toàn bộ cửa sổ vẫn là ngữ cảnh causal. Cập nhật tiếp theo bắt đầu ở token mục tiêu chưa dùng đầu tiên. Token tương lai trong tensor không ảnh hưởng logits trước nhờ mask causal. Nhật ký giữ cửa sổ, đầu/cuối mục tiêu, bộ đếm và loại luồng. Vì vậy tỷ lệ không phụ thuộc độ dài tài liệu.
Microbatch chứa mỗi batch một lựa chọn tài liệu. Mạng forward autocast BF16; trọng số master, gradient và trạng thái AdamW là FP32. Logits chọn được đổi FP32 cho CE. BF16 được kiểm tra trên GPU thật; không GradScaler. Attention eager và thuật toán xác định cố định, TF32 tắt. Chi phí forward còn phụ thuộc ngữ cảnh đọc lại khi quota cắt cửa sổ: token giám sát bằng nhau không hàm ý FLOPs hoàn toàn bằng nhau.
AdamW dùng lr tối đa 0,00001, weight_decay 0,01, warm-up 51 cập nhật rồi giảm tuyến tính tới 10 % lr tối đa; clipping chuẩn 1. Checkpoint cuối 512 cố định. Validation mỗi 128 bước được báo cáo không chọn. Dừng ở 200 và tiếp tục phải khôi phục chính xác mô hình, optimizer, scheduler, trạng thái ngẫu nhiên, hoán vị, con trỏ tài liệu và offset token. Tệp .pt là trạng thái tạo cục bộ, không thay bằng nội dung không đáng tin. Hash từng tensor và Safetensors đọc lại nghiêm ngặt kiểm tra trọng số cuối.
NLL và câu trả lời không đo cùng điều
NLL dành riêng dùng cùng token và mẫu số cho base, CPT miền và CPT replay: 600 hướng dẫn nghiệp vụ và 150 văn bản đối chứng. Loss đơn vị và logits tham chiếu đầy đủ được giữ. Kiểm thử so sánh tổng loss hoạt động, tích lũy streaming và Transformers trên cùng batch. NLL giảm chỉ cho biết các văn bản này có xác suất cao hơn; không chứng minh câu trả lời đúng hay nhớ sự kiện chưa thấy.
Ba trạng thái trọng số trả lời cùng 200 câu hỏi với ngữ cảnh rỗng rồi top-3 lexical: sáu điều kiện rõ ràng. Prompt giống nhau trừ có ngữ cảnh và không chỉnh theo test. Sinh greedy tối đa 32 token, không ràng buộc grammar hay chọn số đầu trong câu. Câu trả lời phải hoàn toàn số sau bỏ khoảng trắng và kết thúc EOS. UNKNOWN, văn bản lạ và cắt ngắn là exact-match thất bại. Đầu ra thô và lý do được giữ.
Chỉ mục lexical và provenance
TF-IDF được viết với tf=1+log(count), idf=log((N+1)/(df+1))+1, chuẩn hóa L2 và cosine. Nó chỉ lập chỉ mục văn bản của 4000 hướng dẫn, không bao giờ label trả lời. Truy vấn token hóa giống vậy; hòa điểm phân định theo định danh. Ba hướng dẫn giữ lại xuất hiện trong prompt cùng ID và version. Recall tài liệu mong đợi trong top-3 được tính riêng với exactitude sinh: tìm đúng văn bản không bảo đảm dùng đúng.
Chỉ mục v1 bất biến. Cập nhật tạo chỉ mục v2 mới có đúng 40 thay thế và hash mới, rồi tính lại tìm kiếm. Không cache ngữ cảnh nào có thể che revision. Lắp ráp từ chối cặp kết quả/version không nhất quán; prompt quá dài bị từ chối, không cắt ngầm. Prompt mở không quá 1024 token.
Bài tập revision
Sau CPT, trọng số và kết quả chính được bảo vệ SHA. Bài tập thực sự chạy 40 câu trả lời cho mỗi sáu điều kiện với index v2. Nó so đáp án v1 với sự kiện cũ/mới, rồi đáp án v2 với sự kiện mới. Prompt đóng phải không đổi; độ ổn định đầu ra được đo. Số bước học mới bằng không.
Sự sẵn có tốt hơn của sự kiện mới không bảo đảm câu trả lời tốt hơn. Vì vậy kết quả tìm kiếm, sinh và hash trọng số được trình bày riêng. Replay có thể hạn chế trôi NLL đối chứng, không bảo đảm và dùng ít token nghiệp vụ hơn. Chỉ một seed học được kiểm tra; cần lặp lại và mở rộng dữ liệu trước khi kết luận về chiến lược adaptation hữu ích trong sản xuất.
Chuẩn bị hướng dẫn và đối chứng riêng
Partition tài liệu và version
Bất biến: Số lượng và family tách biệt; 40 revision v2 được đánh dấu bổ sung inference.
Lỗi thường gặp: Hướng dẫn và paraphrase vượt các split: phân vùng trước biến thể.
Tệp src/preentrainement_continu/data.py
import hashlib
from pathlib import Path
from .config import save_json, sha
MODES = ["azur", "brume", "cuivre", "delta", "ambre", "givre", "orbe", "silex"]
def split_families(family, kind):
a, b = (350, 425) if kind == "domain" else (140, 170)
return "train" if family < a else "validation" if family < b else "test"
def notice_text(family, j, pulses, version=1):
alias = f"N{family:04d}"
mode = MODES[j]
lead = f"Novaforge {alias}, notice {mode}, revision {version}. "
variants = [
f"The {mode} mode waits for {pulses} pulses before its cycle.",
f"Before starting a cycle, {mode} mode expects {pulses} pulses.",
f"The pulse count before a cycle in {mode} mode is {pulses}.",
f"A cycle under {mode} mode begins after {pulses} pulses.",
f"For {mode} mode, wait for {pulses} pulses before the cycle.",
f"The {mode} cycle has a waiting count of {pulses} pulses.",
f"The required wait before the {mode} cycle is {pulses} pulses.",
f"In {mode} mode, {pulses} pulses precede the cycle.",
]
return (
lead
+ variants[j]
+ " This is a fictional language example, not an operating instruction."
)
def generate_notices():
pages = []
facts = []
for family in range(500):
for j in range(8):
value = 1 + (family * 7 + j * 3) % 9
text = notice_text(family, j, value)
identifier = f"N{family:04d}-{j}"
pages.append(
{
"document_id": identifier,
"kind": "domain",
"family": family,
"formulation": j,
"split": split_families(family, "domain"),
"version": 1,
"text": text,
"content_sha256": hashlib.sha256(text.encode()).hexdigest(),
}
)
facts.append({"document_id": identifier, "pulses": value})
for family in range(200):
alias = f"W{family:04d}"
count = 4 + family % 17
place = ["north", "south", "east", "west"][family % 4]
phrases = [
f"Workshop {alias} keeps {count} notebooks on the {place} shelf. The inventory is a written record.",
f"The {place} storage room of workshop {alias} contains {count} empty boxes. A clerk records their presence.",
f"A team at workshop {alias} reads {count} short reports near the {place} desk. The reports describe routine observations.",
f"Workshop {alias} has {count} labelled drawers in its {place} cabinet. A list records each drawer.",
f"The {place} noticeboard at workshop {alias} holds {count} paper cards. The cards remain available for the next shift.",
]
for j, text in enumerate(phrases):
pages.append(
{
"document_id": f"W{family:04d}-{j}",
"kind": "control",
"family": family,
"formulation": j,
"split": split_families(family, "control"),
"version": 1,
"text": text,
"content_sha256": hashlib.sha256(text.encode()).hexdigest(),
}
)
if len({p["text"].casefold() for p in pages}) != 5000:
raise ValueError("Duplicate normalized source")
return pages, facts
def build_revision_set(pages, facts):
lookup = {r["document_id"]: r["pulses"] for r in facts}
reserved = [p for p in pages if p["kind"] == "domain" and p["split"] == "test"][
:200
]
questions = []
gold = []
revisions = []
for index, p in enumerate(reserved):
alias = f"N{p['family']:04d}"
mode = MODES[p["formulation"]]
qid = f"Q{index:03d}"
question = (
f"How many pulses precede the cycle in {mode} mode for Novaforge {alias}?"
)
questions.append({"question_id": qid, "question": question})
value = lookup[p["document_id"]]
gold.append(
{
"question_id": qid,
"document_id": p["document_id"],
"v1_answer": str(value),
"v2_answer": str(value + 2) if index < 40 else str(value),
"revised": index < 40,
}
)
if index < 40:
text = notice_text(p["family"], p["formulation"], value + 2, 2)
revisions.append(
{
**p,
"version": 2,
"text": text,
"content_sha256": hashlib.sha256(text.encode()).hexdigest(),
"inference_only": True,
}
)
return questions, gold, revisions
def prepare(config):
root = Path("data/generated")
root.mkdir(parents=True, exist_ok=True)
if (root / "manifest.json").exists():
raise ValueError("Preserve corpus; use a clean extraction")
pages, facts = generate_notices()
save_json(root / "input/pages.json", pages)
questions, gold, revisions = build_revision_set(pages, facts)
save_json(root / "input/queries.json", questions)
save_json(root / "input/revisions.json", revisions)
save_json(root / "labels/gold.json", gold)
audit = {}
for kind in ["domain", "control"]:
audit[kind] = {}
for split in ["train", "validation", "test"]:
selected = [p for p in pages if p["kind"] == kind and p["split"] == split]
save_json(root / f"{split}/{kind}.json", selected)
audit[kind][split] = {
"documents": len(selected),
"families": len({p["family"] for p in selected}),
}
manifest = {
"datasets": ["NovaLex-v1", "CommonWorkshop-v1"],
"rights": "CC0-1.0",
"seed": config["seed"],
"split_audit": audit,
"questions": len(questions),
"revisions": len(revisions),
"normalized_duplicates": 0,
"files": {str(f.relative_to(root)): sha(f) for f in root.rglob("*.json")},
}
save_json(root / "manifest.json", manifest)
save_json("outputs/data-audit.json", manifest)
return audit
Lấy mô hình và kiểm tra NLL
Mô hình base, token và mục tiêu
Bất biến: SHA đã phân giải, tải có thể tái lập, số mục tiêu chính xác.
Lỗi thường gặp: Bỏ EOS như PAD làm cắt học kết thúc: ID và attention_mask riêng.
Tệp src/preentrainement_continu/batch.py
import random, torch
EOS = 151643
def encode_documents(tokenizer, pages, context=512):
if context < 2:
raise ValueError("Context needs an input and a target")
rows = []
for page in pages:
content = tokenizer.encode(page["text"], add_special_tokens=False)
if not content:
raise ValueError("Empty document")
ids = [EOS] + content + [EOS]
for start in range(0, len(ids) - 1, context - 1):
rows.append(
{
"document_id": page["document_id"],
"kind": page["kind"],
"window_start": start,
"ids": ids[start : start + context],
}
)
return rows
def collate_documents(selections, rows, device="cuda", pad_to=None):
if not selections:
raise ValueError("Empty token budget")
width = (
max(len(rows[s["window"]]["ids"]) for s in selections)
if pad_to is None
else pad_to
)
ids = torch.full((len(selections), width), EOS, dtype=torch.long, device=device)
mask = torch.zeros_like(ids, dtype=torch.bool)
labels = torch.full_like(ids, -100)
for i, selection in enumerate(selections):
row = rows[selection["window"]]
tokens = row["ids"]
start = selection["target_start"]
end = selection["target_end"]
if len(tokens) > width or not 1 <= start < end <= len(tokens):
raise ValueError("Invalid token slice; no silent truncation")
ids[i, : len(tokens)] = torch.tensor(tokens, device=device)
mask[i, : len(tokens)] = True
labels[i, start:end] = torch.tensor(tokens[start:end], device=device)
return ids, mask, labels
def sum_token_nll(logits, labels):
active = labels[:, 1:] != -100
if not active.any():
raise ValueError("No active shifted targets")
selected = logits[:, :-1][active].float()
targets = labels[:, 1:][active]
values = torch.nn.functional.cross_entropy(selected, targets, reduction="none")
return values.sum(), int(active.sum()), values
class TokenStream:
def __init__(self, rows, seed):
if not rows or any(len(r["ids"]) < 2 for r in rows):
raise ValueError("A stream needs nonempty next-token targets")
self.rows = rows
self.generator = random.Random(seed)
self.order = []
self.cursor = 0
self.target_offset = 0
self.epoch = 0
self.tokens = 0
def take(self, quota):
if quota < 0:
raise ValueError("Negative quota")
result = []
while quota:
if self.cursor == len(self.order):
self.order = list(range(len(self.rows)))
self.generator.shuffle(self.order)
self.cursor = 0
self.epoch += 1
index = self.order[self.cursor]
row = self.rows[index]
remaining = len(row["ids"]) - 1 - self.target_offset
take = min(quota, remaining)
start = 1 + self.target_offset
result.append(
{
"window": index,
"document_id": row["document_id"],
"kind": row["kind"],
"window_start": row["window_start"],
"target_start": start,
"target_end": start + take,
}
)
self.target_offset += take
self.tokens += take
quota -= take
if self.target_offset == len(row["ids"]) - 1:
self.target_offset = 0
self.cursor += 1
return result
def state_dict(self):
return {
"order": list(self.order),
"cursor": self.cursor,
"target_offset": self.target_offset,
"epoch": self.epoch,
"tokens": self.tokens,
"rng": self.generator.getstate(),
}
def load_state_dict(self, state):
if sorted(state["order"]) != list(range(len(self.rows))) or not 0 <= state[
"cursor"
] <= len(self.rows):
raise ValueError("Invalid stream order/cursor")
if state["cursor"] == len(self.rows):
if state["target_offset"] != 0:
raise ValueError("Invalid completed epoch offset")
elif (
not 0
<= state["target_offset"]
< len(self.rows[state["order"][state["cursor"]]]["ids"]) - 1
):
raise ValueError("Invalid token cursor")
self.order = list(state["order"])
self.cursor = state["cursor"]
self.target_offset = state["target_offset"]
self.epoch = state["epoch"]
self.tokens = state["tokens"]
self.generator.setstate(state["rng"])
class TokenMixer:
def __init__(self, domain, control, seed, branch):
if branch not in ["domain", "replay"]:
raise ValueError("Unknown CPT branch")
self.branch = branch
self.domain = TokenStream(domain, seed + 100)
self.control = TokenStream(control, seed + 200)
self.updates = 0
def take(self):
domain = self.domain.take(1640 if self.branch == "replay" else 2050)
control = self.control.take(410) if self.branch == "replay" else []
self.updates += 1
return domain, control
def state_dict(self):
return {
"branch": self.branch,
"updates": self.updates,
"domain": self.domain.state_dict(),
"control": self.control.state_dict(),
}
def load_state_dict(self, state):
if state["branch"] != self.branch:
raise ValueError("Changed replay policy")
self.domain.load_state_dict(state["domain"])
if self.branch == "replay":
self.control.load_state_dict(state["control"])
elif state["control"] != self.control.state_dict():
raise ValueError("Control stream changed in domain-only branch")
self.updates = state["updates"]
Cố định baseline và chỉ mục mở
Trọng số so với ngữ cảnh tìm kiếm
Bất biến: Truy vấn chạy không đọc target; mọi ngữ cảnh trích dẫn hướng dẫn có thật.
Lỗi thường gặp: Trường trả lời vô tình bị lập chỉ mục: chỉ lập chỉ mục tài liệu nguồn.
Tệp src/preentrainement_continu/retrieval.py
import collections, hashlib, json, math, re, time
from pathlib import Path
from .config import read_json, save_json, sha
def terms(text):
return re.findall(r"\w+", text.casefold())
def vector(text, idf):
counts = collections.Counter(terms(text))
weights = {
word: (1 + math.log(n)) * idf[word]
for word, n in sorted(counts.items())
if word in idf
}
norm = math.sqrt(math.fsum(v * v for v in weights.values()))
return {word: value / norm for word, value in weights.items()} if norm else {}
def fit_tfidf_index(documents):
begun = time.perf_counter()
if not documents or len({d["document_id"] for d in documents}) != len(documents):
raise ValueError("Empty or duplicate source ID")
frequencies = collections.Counter()
for document in documents:
if document["kind"] != "domain":
raise ValueError("The lexical index contains domain source texts only")
if (
hashlib.sha256(document["text"].encode()).hexdigest()
!= document["content_sha256"]
):
raise ValueError("Source digest changed")
frequencies.update(set(terms(document["text"])))
n = len(documents)
idf = {
word: math.log((n + 1) / (count + 1)) + 1
for word, count in sorted(frequencies.items())
}
rows = [
{
"document_id": d["document_id"],
"version": d["version"],
"text": d["text"],
"content_sha256": d["content_sha256"],
"vector": vector(d["text"], idf),
}
for d in documents
]
return {
"documents": rows,
"idf": idf,
"document_frequency": dict(sorted(frequencies.items())),
"document_count": n,
"build_seconds": time.perf_counter() - begun,
"formula": "tf=1+log(count), idf=log((N+1)/(df+1))+1, L2 cosine; source text only",
}
def retrieve_notices(index, question, k=3):
if k < 1:
raise ValueError("Positive retrieval count required")
query = vector(question, index["idf"])
scores = []
for document in index["documents"]:
score = math.fsum(
query[word] * document["vector"].get(word, 0.0) for word in sorted(query)
)
scores.append(
{
"document_id": document["document_id"],
"version": document["version"],
"score": score,
}
)
return sorted(scores, key=lambda row: (-row["score"], row["document_id"]))[:k]
def build_question_context(question, index, hits, condition):
if condition not in ["closed", "open"]:
raise ValueError("Name the evaluation condition")
lookup = {d["document_id"]: d for d in index["documents"]}
documents = [lookup[h["document_id"]] for h in hits] if condition == "open" else []
for d, h in zip(documents, hits):
if d["version"] != h["version"]:
raise ValueError("Stale retrieval version")
context = (
"\n".join(f"[{d['document_id']}@{d['version']}] {d['text']}" for d in documents)
if documents
else "None supplied."
)
text = (
"Answer with digits only. If the answer is unavailable, write UNKNOWN.\nContext:\n"
+ context
+ "\nQuestion: "
+ question
+ "\nAnswer:"
)
return text, [
{
"document_id": d["document_id"],
"version": d["version"],
"content_sha256": d["content_sha256"],
}
for d in documents
]
def build_index(version=1):
if Path("data/generated/labels").exists():
raise ValueError("Indexing service must not mount answer labels")
pages = [
p for p in read_json("data/generated/input/pages.json") if p["kind"] == "domain"
]
changed = []
if version == 2:
revisions = read_json("data/generated/input/revisions.json")
lookup = {p["document_id"]: p for p in pages}
for revision in revisions:
old = lookup[revision["document_id"]]
if (
old["version"] != 1
or revision["version"] != 2
or revision["split"] != "test"
or not revision["inference_only"]
):
raise ValueError("Unexpected revision boundary")
lookup[revision["document_id"]] = revision
changed.append(revision["document_id"])
pages = [lookup[p["document_id"]] for p in pages]
elif version != 1:
raise ValueError("Expected index version1or2")
path = Path(f"outputs/index-v{version}.json")
if path.exists():
raise ValueError("Never overwrite an index snapshot")
index = fit_tfidf_index(pages)
save_json(path, {**index, "snapshot_version": version, "revised_ids": changed})
questions = read_json("data/generated/input/queries.json")
rows = []
for q in questions:
begun = time.perf_counter()
hits = retrieve_notices(index, q["question"], 3)
rows.append({**q, "hits": hits, "seconds": time.perf_counter() - begun})
save_json(
f"outputs/retrieval-v{version}.json",
{
"index_sha256": sha(path),
"snapshot_version": version,
"rows": rows,
"answer_labels_visible": False,
},
)
print(
{
"snapshot_version": version,
"documents": len(pages),
"revised_documents": len(changed),
"build_seconds": index["build_seconds"],
},
flush=True,
)
Adaptation có và không replay
Ngân sách token và trộn rõ ràng
Bất biến: Trọng số thay đổi và bộ đếm khớp giao thức; base bất biến.
Lỗi thường gặp: Trộn 80/20 đã nói thành 95/5 do độ dài: điều khiển ngân sách bằng token.
Tệp src/preentrainement_continu/train.py
import math, time, shutil
from pathlib import Path
import torch
from .config import read_json, save_json, sha, seed_all, rng_state, restore_rng
from .model import (
load_base,
load_tokenizer,
parameter_manifest,
manifest_digest,
save_learned,
)
from .batch import encode_documents, collate_documents, sum_token_nll, TokenMixer
from .state import equal, save_resume_state
def lr_at(config, step):
warmup = config["warmup_updates"]
peak = config["learning_rate"]
total = config["optimizer_steps"]
return (
peak * (step + 1) / warmup
if step < warmup
else peak
* (
1
- min(1.0, (step - warmup) / (total - warmup - 1))
* (1 - config["minimum_lr_ratio"])
)
)
def training_rows(config, tokenizer):
result = {}
for kind, count in [("domain", 2800), ("control", 700)]:
pages = read_json(f"data/generated/train/{kind}.json")
if len(pages) != count or any(
p["split"] != "train" or p["kind"] != kind or p["version"] != 1
for p in pages
):
raise ValueError("Replay/train/version boundary violated")
result[kind] = encode_documents(tokenizer, pages, config["context"])
return result
@torch.no_grad()
def validation(model, rows):
model.eval()
total = 0.0
count = 0
for index, row in enumerate(rows):
selection = {"window": index, "target_start": 1, "target_end": len(row["ids"])}
ids, mask, labels = collate_documents([selection], rows)
with torch.autocast("cuda", dtype=torch.bfloat16):
logits = model(input_ids=ids, attention_mask=mask, use_cache=False).logits
_, n, values = sum_token_nll(logits, labels)
total += float(values.double().sum())
count += n
model.train()
return {
"windows": len(rows),
"active_tokens": count,
"nll_nats_per_token": total / count,
}
def train_cpt(config, branch, mode):
if (
Path("data/generated/test").exists()
or Path("data/generated/input").exists()
or Path("data/generated/labels").exists()
):
raise ValueError("Learning container must only mount train and validation")
if mode not in ["interrupt", "continuous", "resume"]:
raise ValueError("Unknown mode")
if not torch.cuda.is_bf16_supported():
raise ValueError("This reference profile requires CUDA bfloat16 support")
expected = (2050, 1049600, 1640, 410, 512)
if (
tuple(
config[k]
for k in [
"tokens_per_update",
"total_active_tokens",
"replay_domain_tokens_per_update",
"replay_control_tokens_per_update",
"optimizer_steps",
]
)
!= expected
):
raise ValueError("Frozen exact80/20token budget changed")
seed_all(config["seed"])
tokenizer = load_tokenizer()
rows = training_rows(config, tokenizer)
model = load_base()
initial = parameter_manifest(model)
initial_digest = manifest_digest(initial)
model = model.cuda()
optimizer = torch.optim.AdamW(
model.parameters(),
lr=config["learning_rate"],
weight_decay=config["weight_decay"],
)
mixer = TokenMixer(rows["domain"], rows["control"], config["seed"], branch)
validation_rows = {
k: encode_documents(
tokenizer,
read_json(f"data/generated/validation/{k}.json"),
config["context"],
)
for k in ["domain", "control"]
}
identity = {
"branch": branch,
"config": {k: v for k, v in config.items() if k != "exercise"},
"initial_model_digest": initial_digest,
"base_sha256": sha("assets/model/model.safetensors"),
"tokenizer_sha256": sha("assets/model/tokenizer.json"),
"model_config_sha256": sha("assets/model/config.json"),
"model_lock_sha256": sha("locks/models.lock.json"),
"training_sha256": {
k: sha(f"data/generated/train/{k}.json") for k in ["domain", "control"]
},
"validation_sha256": {
k: sha(f"data/generated/validation/{k}.json") for k in ["domain", "control"]
},
"implementation_sha256": {
n: sha("src/preentrainement_continu/" + n)
for n in ["model.py", "batch.py", "train.py", "state.py"]
},
}
root = Path("assets/checkpoints")
destination = root / f"{branch}-{mode}.pt"
history = []
validation_history = []
start = 0
if destination.exists():
raise ValueError("Preserve recorded checkpoint")
if mode == "resume":
state = torch.load(
root / f"{branch}-interrupt.pt", map_location="cpu", weights_only=False
)
if state["identity"] != identity or state["scheduler"] != {
"next_step": state["steps"],
"next_lr": lr_at(config, state["steps"]),
}:
raise ValueError("Resume identity or scheduler changed")
model.load_state_dict(state["model"], strict=True)
optimizer.load_state_dict(state["optimizer"])
mixer.load_state_dict(state["mixer"])
restore_rng(state["rng"])
start = state["steps"]
history = state["history"]
validation_history = state["validation_history"]
del state
stop = (
config["interrupt_step"] if mode == "interrupt" else config["optimizer_steps"]
)
begun = time.perf_counter()
torch.cuda.reset_peak_memory_stats()
model.train()
logit_dtype = None
for step in range(start, stop):
domain, control = mixer.take()
denominator = sum(r["target_end"] - r["target_start"] for r in domain + control)
if denominator != 2050:
raise ValueError("Token budget drift")
lr = lr_at(config, step)
for group in optimizer.param_groups:
group["lr"] = lr
optimizer.zero_grad(set_to_none=True)
loss_total = 0.0
for kind, selections in [("domain", domain), ("control", control)]:
for selection in selections:
ids, mask, labels = collate_documents([selection], rows[kind])
with torch.autocast("cuda", dtype=torch.bfloat16):
logits = model(
input_ids=ids, attention_mask=mask, use_cache=False
).logits
logit_dtype = str(logits.dtype)
summed, n, values = sum_token_nll(logits, labels)
if (
not torch.isfinite(summed)
or n != selection["target_end"] - selection["target_start"]
):
raise ValueError("Nonfinite or miscounted loss")
(summed / denominator).backward()
loss_total += float(values.detach().double().sum())
norm = torch.nn.utils.clip_grad_norm_(
model.parameters(), config["gradient_clip"], error_if_nonfinite=True
)
if any(
p.dtype != torch.float32 or p.grad is None or p.grad.dtype != torch.float32
for p in model.parameters()
):
raise ValueError("Expected finite FP32 master gradients")
optimizer.step()
history.append(
{
"step": step + 1,
"selections": domain + control,
"active_tokens": denominator,
"domain_tokens": sum(
r["target_end"] - r["target_start"] for r in domain
),
"control_tokens": sum(
r["target_end"] - r["target_start"] for r in control
),
"total_tokens_seen": mixer.domain.tokens + mixer.control.tokens,
"nll_nats_per_token": loss_total / denominator,
"learning_rate": lr,
"gradient_norm_before_clip": float(norm),
}
)
if (step + 1) % config["validation_every"] == 0:
observed = {k: validation(model, v) for k, v in validation_rows.items()}
validation_history.append({"step": step + 1, "results": observed})
print(
{
"branch": branch,
"mode": mode,
"step": step + 1,
"total_tokens_seen": history[-1]["total_tokens_seen"],
"training_nll": history[-1]["nll_nats_per_token"],
"validation": observed,
},
flush=True,
)
elif (step + 1) % 32 == 0:
print(
{
"branch": branch,
"mode": mode,
"step": step + 1,
"total_tokens_seen": history[-1]["total_tokens_seen"],
"training_nll": history[-1]["nll_nats_per_token"],
},
flush=True,
)
torch.cuda.synchronize()
final = parameter_manifest(model)
final_digest = manifest_digest(final)
if final_digest == initial_digest or logit_dtype != "torch.bfloat16":
raise ValueError("Expected real updates and BF16 forward logits")
state = {
"identity": identity,
"steps": stop,
"model": model.state_dict(),
"optimizer": optimizer.state_dict(),
"scheduler": {"next_step": stop, "next_lr": lr_at(config, stop)},
"mixer": mixer.state_dict(),
"rng": rng_state(),
"history": history,
"validation_history": validation_history,
}
save_resume_state(destination, state)
if mode == "resume":
save_learned(model, root / f"{branch}-resume.safetensors", identity)
save_json(
f"outputs/train-{branch}-{mode}.json",
{
"branch": branch,
"mode": mode,
"identity": identity,
"steps": stop,
"history": history,
"validation_history": validation_history,
"tokenized_train": rows,
"initial_parameters": initial,
"final_parameters": final,
"initial_model_digest": initial_digest,
"final_model_digest": final_digest,
"parameters": sum(p.numel() for p in model.parameters()),
"active_tokens": mixer.domain.tokens + mixer.control.tokens,
"domain_tokens": mixer.domain.tokens,
"control_tokens": mixer.control.tokens,
"checkpoint_sha256": sha(destination),
"checkpoint_bytes": destination.stat().st_size,
"precision": {
"master": "torch.float32",
"gradients": "torch.float32",
"logits": logit_dtype,
"ce": "torch.float32",
"scaler": False,
},
"seconds": time.perf_counter() - begun,
"max_torch_allocated_bytes": torch.cuda.max_memory_allocated(),
"test_visible": False,
"v2_visible": False,
},
)
def checkpoint_mixer(branch):
root = Path("assets/checkpoints")
a = torch.load(
root / f"{branch}-continuous.pt", map_location="cpu", weights_only=False
)
b = torch.load(root / f"{branch}-resume.pt", map_location="cpu", weights_only=False)
checks = {k: equal(a[k], b[k]) for k in a}
if not all(checks.values()):
raise ValueError("CPT resume differs: " + str(checks))
save_json(
f"outputs/resume-{branch}.json",
{
"status": "PASS",
"all_state_exact": True,
"checks": checks,
"boundary": "complete2050targettoken optimizer update; includes stream target offset",
},
)
Đo NLL và câu trả lời trong điều kiện có tên
Đánh giá đóng và mở
Bất biến: 200 câu hỏi giống nhau mỗi điều kiện, mọi lỗi định dạng được đếm.
Lỗi thường gặp: So CPT không ngữ cảnh với base có ngữ cảnh mà không nói là trộn hai can thiệp.
Tệp src/preentrainement_continu/evaluate.py
import hashlib, json, math, re, shutil, time
from pathlib import Path
import torch
from transformers import GenerationConfig
from .config import read_json, save_json, sha, seed_all
from .model import (
load_base,
load_tokenizer,
reload_learned,
parameter_manifest,
manifest_digest,
)
from .batch import encode_documents, collate_documents, sum_token_nll, EOS
from .retrieval import build_question_context
def weight_state(variant):
if variant == "base":
model = load_base()
path = Path("assets/model/model.safetensors")
identity = manifest_digest(parameter_manifest(model))
elif variant in ["domain", "replay"]:
path = Path(f"assets/checkpoints/{variant}-resume.safetensors")
model, metadata = reload_learned(path)
identity = metadata["model_digest"]
else:
raise ValueError("Unknown weight state")
return model, path, identity
def audit_base():
model = load_base()
rows = parameter_manifest(model)
save_json(
"outputs/base-parameters.json",
{
"parameters": rows,
"unique_parameters": sum(r["numel"] for r in rows),
"model_digest": manifest_digest(rows),
"model_sha256": sha("assets/model/model.safetensors"),
"tokenizer_sha256": sha("assets/model/tokenizer.json"),
"all_master_fp32": all(
p.dtype == torch.float32 for p in model.parameters()
),
"tied_embedding_output": model.lm_head.weight
is model.model.embed_tokens.weight,
},
)
shutil.copyfile("assets/model/tokenizer.json", "outputs/tokenizer-reference.json")
def parse_short_answer(text, finish):
stripped = text.strip()
valid = re.fullmatch(r"[0-9]{1,3}", stripped) is not None
return {
"numeric_format": valid,
"completion_valid": valid and finish == "eos",
"answer": stripped if valid else None,
"reason": (
"TRUNCATED"
if finish != "eos"
else "VALID_INTEGER" if valid else "UNKNOWN_OR_INVALID_FORMAT"
),
}
@torch.no_grad()
def predict_questions(config, variant, version=1):
if Path("data/generated/labels").exists():
raise ValueError("Question generation must not mount answer labels")
if version not in [1, 2]:
raise ValueError("Index version must be explicit")
seed_all(config["seed"])
model, path, identity = weight_state(variant)
model = model.cuda().eval()
tokenizer = load_tokenizer()
index_path = Path(f"outputs/index-v{version}.json")
index = read_json(index_path)
retrieval = read_json(f"outputs/retrieval-v{version}.json")
if retrieval["index_sha256"] != sha(index_path):
raise ValueError("Stale retrieval snapshot")
selected = retrieval["rows"] if version == 1 else retrieval["rows"][:40]
generation = GenerationConfig(
do_sample=False,
num_beams=1,
use_cache=True,
max_new_tokens=config["max_new_tokens"],
eos_token_id=EOS,
pad_token_id=EOS,
bos_token_id=EOS,
)
result = []
begun = time.perf_counter()
torch.cuda.reset_peak_memory_stats()
for condition in ["closed", "open"]:
for q in selected:
text, sources = build_question_context(
q["question"], index, q["hits"], condition
)
ids = tokenizer.encode(text, add_special_tokens=False)
if not ids or len(ids) > config["generation_max_prompt_tokens"]:
raise ValueError(
"No prompt truncation: declared context budget exceeded"
)
input_ids = torch.tensor([ids], device="cuda")
mask = torch.ones_like(input_ids)
started = time.perf_counter()
with torch.autocast("cuda", dtype=torch.bfloat16):
generated = (
model.generate(
input_ids=input_ids,
attention_mask=mask,
generation_config=generation,
)[0, len(ids) :]
.cpu()
.tolist()
)
torch.cuda.synchronize()
finish = "eos" if generated and generated[-1] == EOS else "length"
content = generated[:-1] if finish == "eos" else generated
decoded = tokenizer.decode(
content, skip_special_tokens=False, clean_up_tokenization_spaces=False
)
result.append(
{
"question_id": q["question_id"],
"question": q["question"],
"condition": condition,
"input_text": text,
"input_ids": ids,
"source_documents": sources,
"prompt_sha256": hashlib.sha256(text.encode()).hexdigest(),
"generated_ids": generated,
"text": decoded,
"finish_reason": finish,
"parsed": parse_short_answer(decoded, finish),
"seconds": time.perf_counter() - started,
}
)
print(
{
"variant": variant,
"version": version,
"condition": condition,
"questions": len(selected),
},
flush=True,
)
save_json(
f"outputs/answers-{variant}-v{version}.json",
{
"variant": variant,
"snapshot_version": version,
"model_digest": identity,
"model_file_sha256": sha(path),
"tokenizer_sha256": sha("assets/model/tokenizer.json"),
"index_sha256": sha(index_path),
"generation": generation.to_dict(),
"rows": result,
"seconds": time.perf_counter() - begun,
"max_torch_allocated_bytes": torch.cuda.max_memory_allocated(),
"answer_labels_visible": False,
"weights_updated": False,
},
)
@torch.no_grad()
def evaluate_nll(config, variant):
seed_all(config["seed"])
model, path, identity = weight_state(variant)
model = model.cuda().eval()
tokenizer = load_tokenizer()
pages = read_json("data/generated/input/pages.json")
results = {}
for kind, count in [("domain", 600), ("control", 150)]:
selected = [p for p in pages if p["kind"] == kind and p["split"] == "test"]
if len(selected) != count:
raise ValueError("Expected frozen heldout documents")
rows = encode_documents(tokenizer, selected, config["context"])
records = []
probes = []
for index, row in enumerate(rows):
selection = {
"window": index,
"target_start": 1,
"target_end": len(row["ids"]),
}
ids, mask, labels = collate_documents([selection], rows)
with torch.autocast("cuda", dtype=torch.bfloat16):
logits = model(
input_ids=ids, attention_mask=mask, use_cache=False
).logits
_, n, values = sum_token_nll(logits, labels)
records.append(
{
**row,
"active_tokens": n,
"targets": row["ids"][1:],
"token_nll": values.cpu().tolist(),
}
)
if index == 0:
for pos in [0, n - 1]:
probes.append(
{
"document_id": row["document_id"],
"target_position": pos + 1,
"target": row["ids"][pos + 1],
"logits": logits[0, pos].float().cpu().tolist(),
"nll": float(values[pos]),
}
)
total = math.fsum(v for row in records for v in row["token_nll"])
tokens = sum(r["active_tokens"] for r in records)
results[kind] = {
"documents": count,
"active_tokens": tokens,
"nll_nats_per_token": total / tokens,
"rows": records,
"probes": probes,
}
save_json(
f"outputs/nll-{variant}.json",
{
"variant": variant,
"model_digest": identity,
"model_file_sha256": sha(path),
"tokenizer_sha256": sha("assets/model/tokenizer.json"),
"precision": "FP32 master weights, BF16 autocast, FP32 CE",
"results": results,
},
)
def score_short_answers(rows, gold, version):
details = []
correct = numeric = valid = truncated = 0
for row in rows:
parsed = parse_short_answer(row["text"], row["finish_reason"])
if parsed != row["parsed"]:
raise ValueError("Stored parse changed")
expected = gold[row["question_id"]][f"v{version}_answer"]
exact = parsed["completion_valid"] and parsed["answer"] == expected
correct += exact
numeric += parsed["numeric_format"]
valid += parsed["completion_valid"]
truncated += row["finish_reason"] == "length"
details.append(
{
"question_id": row["question_id"],
"expected": expected,
"exact_match": exact,
"revised": gold[row["question_id"]]["revised"],
}
)
n = len(rows)
return {
"questions": n,
"numeric_format": numeric,
"valid_completions": valid,
"exact_match": correct,
"exact_match_rate": correct / n,
"truncated": truncated,
"rows": details,
}
def evaluate_retrieval(version):
gold = {r["question_id"]: r for r in read_json("data/generated/labels/gold.json")}
retrieval = read_json(f"outputs/retrieval-v{version}.json")
details = []
for q in retrieval["rows"]:
label = gold[q["question_id"]]
expected_version = 2 if version == 2 and label["revised"] else 1
hit = any(
h["document_id"] == label["document_id"]
and h["version"] == expected_version
for h in q["hits"]
)
details.append(
{
"question_id": q["question_id"],
"expected_document_id": label["document_id"],
"expected_version": expected_version,
"hit_top3": hit,
}
)
return {
"questions": len(details),
"hit_top3": sum(r["hit_top3"] for r in details),
"recall_top3": sum(r["hit_top3"] for r in details) / len(details),
"rows": details,
}
def compare_results(config):
gold = {r["question_id"]: r for r in read_json("data/generated/labels/gold.json")}
result = {}
base = read_json("outputs/nll-base.json")
for variant in ["base", "domain", "replay"]:
answers = read_json(f"outputs/answers-{variant}-v1.json")
nll = read_json(f"outputs/nll-{variant}.json")
result[variant] = {
"nll": {
kind: {
"documents": r["documents"],
"active_tokens": r["active_tokens"],
"nll_nats_per_token": r["nll_nats_per_token"],
"delta_from_base": r["nll_nats_per_token"]
- base["results"][kind]["nll_nats_per_token"],
}
for kind, r in nll["results"].items()
},
"answers": {
condition: score_short_answers(
[r for r in answers["rows"] if r["condition"] == condition], gold, 1
)
for condition in ["closed", "open"]
},
}
save_json(
"outputs/comparison.json",
{
"variants": result,
"retrieval": evaluate_retrieval(1),
"quality_gain_required": False,
"control_scope": "150synthetic heldout workshop texts, not a benchmark of all general capabilities",
"open_book_test": True,
},
)
Cập nhật một sự kiện không cập nhật trọng số
Kiểm thử phảnfactual của version
Bất biến: Chỉ mục tham chiếu v2 và trọng số giữ hash.
Lỗi thường gặp: Cache ngữ cảnh cũ tồn tại sau revision: đưa version tài liệu vào khóa.
Tệp src/preentrainement_continu/exercise.py
from pathlib import Path
from .config import read_json, save_json, sha
from .evaluate import score_short_answers, evaluate_retrieval
def preserve_main():
paths = [
"assets/model/model.safetensors",
"assets/checkpoints/domain-resume.safetensors",
"assets/checkpoints/replay-resume.safetensors",
"outputs/index-v1.json",
"outputs/retrieval-v1.json",
"outputs/comparison.json",
]
for variant in ["base", "domain", "replay"]:
paths.extend(
[f"outputs/nll-{variant}.json", f"outputs/answers-{variant}-v1.json"]
)
save_json("outputs/exercise-before.json", {name: sha(name) for name in paths})
def compare_revision_answers():
before = read_json("outputs/exercise-before.json")
if any(sha(name) != h for name, h in before.items()):
raise ValueError("Revision exercise changed a weight or main artifact")
gold = {r["question_id"]: r for r in read_json("data/generated/labels/gold.json")}
selected = {r["question_id"] for r in gold.values() if r["revised"]}
result = {}
if len(selected) != 40:
raise ValueError("Expected40revised notices")
for variant in ["base", "domain", "replay"]:
old = read_json(f"outputs/answers-{variant}-v1.json")
new = read_json(f"outputs/answers-{variant}-v2.json")
if (
old["model_digest"] != new["model_digest"]
or old["model_file_sha256"] != new["model_file_sha256"]
):
raise ValueError("Weights changed between document revisions")
result[variant] = {}
for condition in ["closed", "open"]:
a = [
r
for r in old["rows"]
if r["condition"] == condition and r["question_id"] in selected
]
b = [r for r in new["rows"] if r["condition"] == condition]
if (
len(a) != 40
or len(b) != 40
or [r["question_id"] for r in a] != [r["question_id"] for r in b]
):
raise ValueError("Revised question population changed")
same_inputs = [x["input_ids"] == y["input_ids"] for x, y in zip(a, b)]
same_outputs = [
x["generated_ids"] == y["generated_ids"] for x, y in zip(a, b)
]
if condition == "closed" and not all(same_inputs):
raise ValueError("Closed condition gained context")
result[variant][condition] = {
"v1_answers_against_old_facts": score_short_answers(a, gold, 1),
"v1_answers_against_new_facts": score_short_answers(a, gold, 2),
"v2_answers_against_new_facts": score_short_answers(b, gold, 2),
"identical_input_count": sum(same_inputs),
"identical_output_count": sum(same_outputs),
}
save_json(
"outputs/exercise/revised-notices.json",
{
"variants": result,
"retrieval_v1": evaluate_retrieval(1),
"retrieval_v2": evaluate_retrieval(2),
"protected_sha256": before,
"weights_and_main_artifacts_unchanged": True,
"new_training_steps": 0,
"quality_gain_required": False,
},
)
Nguồn
Don’t Stop Pretraining cung cấp bối cảnh adaptation miền; kết quả của nó không phải kết quả bài thực hành causal này. Thẻ Qwen2.5-0.5B và mục tiêu causal Transformers đã được tham khảo. Việc tải và version chạy được khóa cục bộ. Docker GPU và NVIDIA Container Toolkit ghi điều kiện cần.
Kết quả quan sát trong quá trình phát triển GPU
Luồng hoàn chỉnh thành công 14 kiểm thử trong 13 673,51 giây. Hai nhánh mỗi nhánh 512 cập nhật và 1 049 600 mục tiêu giám sát. Replay thay 20 % mục tiêu nghiệp vụ bằng 209 920 mục tiêu đối chứng; vì vậy còn 839 680 mục tiêu nghiệp vụ. Nó không thấy nhiều văn bản nghiệp vụ như nhánh riêng. Tiếp tục sau 200 bước khôi phục chính xác trọng số, optimizer, luồng token, RNG và lịch sử của đường liên tục. Tái lập từ ZIP công khai vẫn là kiểm tra riêng.
Trên 600 hướng dẫn nghiệp vụ test, tức 25 275 mục tiêu, NLL từ 5,197560 xuống 0,429035 sau CPT miền và 0,417514 với replay. Trên 150 văn bản test đối chứng, tức 3 840 mục tiêu, nó từ 5,193446 lên 6,220325 sau CPT miền, so với 1,255818 với replay. Điều này cho thấy trôi dạt trên đối chứng hư cấu hẹp này và sự giảm nhẹ bởi trộn. Đối chứng không đo mọi năng lực chung. Văn bản validation khác và số token của chúng không được thay cho test.
Loss và chất lượng câu trả lời tự do không kể cùng câu chuyện. Không ngữ cảnh, base trả lời đúng 13/200, so với 21/200 cho mỗi nhánh huấn luyện. Với ba tài liệu tìm được, base đạt 200/200, so với 168/200 sau CPT miền và 178/200 với replay. Recall tài liệu top-3 là 200/200: lỗi nhánh huấn luyện còn dù đúng hướng dẫn được cho. Không quan sát lợi thế CPT trong việc đọc tài liệu của base. 41 đầu ra base không ngữ cảnh bị cắt vẫn trong mẫu số; không sửa đáp án phần để tăng điểm.
Bài tập sửa 40 sự kiện trong tài liệu tra cứu, không huấn luyện lại mô hình. Với giá trị mới, tra index v2 cho 39/40 đáp án đúng với base, 34/40 sau CPT miền và 36/40 với replay. Không tài liệu, đầu vào và đầu ra giống nhau trên 40 ca; không đáp án nào đúng giá trị sửa. Recall top-3 của index sửa là 199/200 trên toàn câu hỏi. Cập nhật tài liệu cho phép cung cấp sự kiện mới, không bảo đảm đọc đúng từng ca.
Sợi chỉ thực sự giữ là hướng dẫn N0425-0: « 6 pulses » thành « 8 pulses ». Ba biến thể trả lời 6 với tài liệu cũ và 8 với revision, kết thúc EOS. Đây là ca thành công trong quần thể đầy đủ, không thay cho đánh giá. Hướng dẫn test được tra chưa từng dùng cho CPT: đây rõ ràng là test có tài liệu cấp.
Các đường liên tục mất 3029.10 s (nghiệp vụ) và 3412.02 s (replay), gồm validation và lưu. Đỉnh cấp phát PyTorch là 9990552576 và 9984970752 byte. Chúng không đại diện toàn VRAM Linux thấy. Chạy gián đoạn/tiếp tục, lấy mô hình, test, suy luận và audit giải thích chi phí lớn hơn luồng hoàn chỉnh. Chuẩn bị CPU cho hướng dẫn khác diễn ra trong luồng: không tuyên bố đo host hoàn toàn rảnh.
Một seed, kho tổng hợp đóng và định dạng đáp án số giới hạn không đủ thiết lập lợi ích nghiệp vụ chung. Replay ở đây có lợi ích đo được trên đối chứng ngôn ngữ; CPT không phải cơ sở tài liệu cập nhật được hay bảo đảm chính xác sự kiện.
Quan sát trong quá trình phát triển
Các giá trị được tính lại từ đầu ra thô của lượt chạy này. Chúng không phải ngưỡng cải thiện; lượt chạy từ ZIP sẽ tính lại chúng trên cùng các tập dữ liệu.
{
"status": "PASS",
"domain_documents": 4000,
"control_documents": 1000,
"questions": 200,
"revised_questions": 40,
"cpt_tokens_per_branch": 1049600,
"replay_domain_tokens": 839680,
"replay_control_tokens": 209920,
"metrics": {
"base": {
"nll": {
"domain": {
"documents": 600,
"active_tokens": 25275,
"nll_nats_per_token": 5.197560175851901,
"delta_from_base": 0.0
},
"control": {
"documents": 150,
"active_tokens": 3840,
"nll_nats_per_token": 5.19344591589955,
"delta_from_base": 0.0
}
},
"answers": {
"closed": {
"questions": 200,
"numeric_format": 159,
"valid_completions": 159,
"exact_match": 13,
"exact_match_rate": 0.065,
"truncated": 41
},
"open": {
"questions": 200,
"numeric_format": 200,
"valid_completions": 200,
"exact_match": 200,
"exact_match_rate": 1.0,
"truncated": 0
}
}
},
"domain": {
"nll": {
"domain": {
"documents": 600,
"active_tokens": 25275,
"nll_nats_per_token": 0.42903490273272693,
"delta_from_base": -4.768525273119174
},
"control": {
"documents": 150,
"active_tokens": 3840,
"nll_nats_per_token": 6.220325457319268,
"delta_from_base": 1.026879541419718
}
},
"answers": {
"closed": {
"questions": 200,
"numeric_format": 200,
"valid_completions": 200,
"exact_match": 21,
"exact_match_rate": 0.105,
"truncated": 0
},
"open": {
"questions": 200,
"numeric_format": 197,
"valid_completions": 197,
"exact_match": 168,
"exact_match_rate": 0.84,
"truncated": 0
}
}
},
"replay": {
"nll": {
"domain": {
"documents": 600,
"active_tokens": 25275,
"nll_nats_per_token": 0.4175135541870015,
"delta_from_base": -4.7800466216649
},
"control": {
"documents": 150,
"active_tokens": 3840,
"nll_nats_per_token": 1.255818447340746,
"delta_from_base": -3.9376274685588033
}
},
"answers": {
"closed": {
"questions": 200,
"numeric_format": 200,
"valid_completions": 200,
"exact_match": 21,
"exact_match_rate": 0.105,
"truncated": 0
},
"open": {
"questions": 200,
"numeric_format": 200,
"valid_completions": 200,
"exact_match": 178,
"exact_match_rate": 0.89,
"truncated": 0
}
}
}
},
"quality_gain_required": false,
"scope": "Data-only per-token sampler replay, tokenizer decoding, parameter manifests, resume evidence, all TF-IDF/cosines, CE probes, raw answers and revision metrics; no full BPE re-encoding or scalar Transformer reconstruction"
}