| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const HEADLINE_FIELDS = [ |
| "family", |
| "view", |
| "attention_variant", |
| "positional", |
| "is_moe", |
| "moe", |
| "sliding_window", |
| "tie_word_embeddings", |
| ]; |
|
|
| |
| function resolveVal(v, fields) { |
| if (typeof v === "string" && v.startsWith("config.")) { |
| const rv = fields ? fields[v.slice("config.".length)] : undefined; |
| return rv != null ? rv : v.slice("config.".length); |
| } |
| return v; |
| } |
|
|
| function fmt(v) { |
| if (v == null) return "—"; |
| if (typeof v === "object") return JSON.stringify(v); |
| return String(v); |
| } |
|
|
| |
| function realNodes(ir) { |
| const out = new Map(); |
| for (const [id, n] of ir.nodes) out.set(id, n); |
| return out; |
| } |
|
|
| function diffAttrs(aAttrs, bAttrs, aFields, bFields) { |
| aAttrs = aAttrs || {}; |
| bAttrs = bAttrs || {}; |
| const keys = [...new Set([...Object.keys(aAttrs), ...Object.keys(bAttrs)])].sort(); |
| return keys.map((k) => { |
| const av = resolveVal(aAttrs[k], aFields); |
| const bv = resolveVal(bAttrs[k], bFields); |
| const same = k in aAttrs && k in bAttrs && String(av) === String(bv); |
| return { key: k, a: fmt(av), b: fmt(bv), same }; |
| }); |
| } |
|
|
| function decideRegime(a, b) { |
| const aChild = a.extends === b.modelType; |
| const bChild = b.extends === a.modelType; |
| if (aChild || bChild) { |
| const parent = aChild ? b : a; |
| return { kind: "lineage", basis: "id", note: `Parent/child — ${parent.modelType} is the base; diff comes from modular patches.` }; |
| } |
| |
| const idsA = new Set(a.nodes.keys()); |
| const idsB = new Set(b.nodes.keys()); |
| let shared = 0; |
| for (const id of idsA) if (idsB.has(id)) shared++; |
| const overlap = shared / Math.max(1, Math.min(idsA.size, idsB.size)); |
| if (overlap >= 0.5) { |
| return { kind: "shared_ids", basis: "id", note: `Stable ids align (${Math.round(overlap * 100)}% overlap).`, overlap }; |
| } |
| return { |
| kind: "cross_lineage", |
| basis: "kind", |
| note: `Different lineages (${Math.round(overlap * 100)}% id overlap) — aligned by semantic kind.`, |
| overlap, |
| }; |
| } |
|
|
| function diffHeadline(a, b) { |
| const fa = a.architecture || {}; |
| const fb = b.architecture || {}; |
| const fields = HEADLINE_FIELDS.filter((k) => fa[k] !== undefined || fb[k] !== undefined); |
| return fields.map((k) => ({ |
| field: k, |
| a: fmt(fa[k]), |
| b: fmt(fb[k]), |
| same: fmt(fa[k]) === fmt(fb[k]), |
| })); |
| } |
|
|
| function pick(fields, keys) { |
| for (const k of keys) if (fields[k] != null) return { key: k, val: fields[k] }; |
| return null; |
| } |
|
|
| function totalDepth(ir) { |
| let n = 0; |
| for (const r of ir.repeatsById.values()) n += ir.resolveCount(r, ir.configFields); |
| return n; |
| } |
|
|
| function diffScale(a, b) { |
| const rows = []; |
| const add = (label, av, bv) => { |
| if (av == null && bv == null) return; |
| rows.push({ field: label, a: fmt(av), b: fmt(bv), same: fmt(av) === fmt(bv) }); |
| }; |
| const hidA = pick(a.configFields, ["hidden_size", "d_model"]); |
| const hidB = pick(b.configFields, ["hidden_size", "d_model"]); |
| add("hidden size", hidA && hidA.val, hidB && hidB.val); |
| const ffA = pick(a.configFields, ["intermediate_size", "d_ff"]); |
| const ffB = pick(b.configFields, ["intermediate_size", "d_ff"]); |
| add("intermediate", ffA && ffA.val, ffB && ffB.val); |
| const hA = pick(a.configFields, ["num_attention_heads", "num_heads"]); |
| const hB = pick(b.configFields, ["num_attention_heads", "num_heads"]); |
| add("attention heads", hA && hA.val, hB && hB.val); |
| add("total layers (resolved)", totalDepth(a), totalDepth(b)); |
| add("vocab size", a.configFields.vocab_size, b.configFields.vocab_size); |
| return rows; |
| } |
|
|
| function diffTopology(a, b) { |
| const setA = new Set(a.edges.map((e) => e.kind)); |
| const setB = new Set(b.edges.map((e) => e.kind)); |
| const kinds = [...new Set([...setA, ...setB])].sort(); |
| return kinds.map((k) => ({ kind: k, inA: setA.has(k), inB: setB.has(k), same: setA.has(k) === setB.has(k) })); |
| } |
|
|
| function kindCounts(ir) { |
| const m = new Map(); |
| for (const n of ir.nodes.values()) m.set(n.kind, (m.get(n.kind) || 0) + 1); |
| return m; |
| } |
|
|
| function diffKindCounts(a, b) { |
| const ca = kindCounts(a); |
| const cb = kindCounts(b); |
| const kinds = [...new Set([...ca.keys(), ...cb.keys()])].sort(); |
| return kinds.map((k) => { |
| const av = ca.get(k) || 0; |
| const bv = cb.get(k) || 0; |
| return { kind: k, a: av, b: bv, same: av === bv }; |
| }); |
| } |
|
|
| |
| function alignById(a, b) { |
| const na = realNodes(a); |
| const nb = realNodes(b); |
| const matched = []; |
| const removed = []; |
| const added = []; |
| for (const [id, node] of na) { |
| if (nb.has(id)) { |
| const other = nb.get(id); |
| const attrDeltas = diffAttrs(node.attributes, other.attributes, a.configFields, b.configFields); |
| const kindChanged = node.kind !== other.kind; |
| const changed = kindChanged || attrDeltas.some((d) => !d.same); |
| matched.push({ |
| id, |
| label: a.label(id, a.configFields), |
| kindA: node.kind, |
| kindB: other.kind, |
| kindChanged, |
| changed, |
| attrDeltas: attrDeltas.filter((d) => !d.same), |
| }); |
| } else { |
| removed.push({ id, label: a.label(id, a.configFields), kind: node.kind }); |
| } |
| } |
| for (const [id, node] of nb) { |
| if (!na.has(id)) added.push({ id, label: b.label(id, b.configFields), kind: node.kind }); |
| } |
| |
| matched.sort((x, y) => Number(y.changed) - Number(x.changed) || y.attrDeltas.length - x.attrDeltas.length); |
| return { basis: "id", matched, added, removed }; |
| } |
|
|
| |
| function alignByKind(a, b) { |
| const byKind = (ir) => { |
| const m = new Map(); |
| for (const [id, n] of ir.nodes) { |
| if (!m.has(n.kind)) m.set(n.kind, []); |
| m.get(n.kind).push({ id, node: n }); |
| } |
| return m; |
| }; |
| const ka = byKind(a); |
| const kb = byKind(b); |
| const kinds = [...new Set([...ka.keys(), ...kb.keys()])].sort(); |
| const sample = (list) => (list || []).find((x) => x.node.attributes) || (list || [])[0]; |
| const lanes = kinds.map((k) => { |
| const la = ka.get(k) || []; |
| const lb = kb.get(k) || []; |
| const sa = sample(la); |
| const sb = sample(lb); |
| const attrDeltas = diffAttrs( |
| sa && sa.node.attributes, |
| sb && sb.node.attributes, |
| a.configFields, |
| b.configFields |
| ).filter((d) => !d.same); |
| return { |
| kind: k, |
| countA: la.length, |
| countB: lb.length, |
| onlyA: la.length > 0 && lb.length === 0, |
| onlyB: lb.length > 0 && la.length === 0, |
| exampleA: sa ? a.label(sa.id, a.configFields) : null, |
| exampleB: sb ? b.label(sb.id, b.configFields) : null, |
| attrDeltas, |
| }; |
| }); |
| return { basis: "kind", lanes }; |
| } |
|
|
| |
| function diffSet(listA, listB) { |
| const sa = new Set(listA || []); |
| const sb = new Set(listB || []); |
| const items = [...new Set([...sa, ...sb])].sort(); |
| return items.map((item) => ({ item, inA: sa.has(item), inB: sb.has(item), same: sa.has(item) === sb.has(item) })); |
| } |
|
|
| function diffCapabilities(a, b) { |
| const ca = a.capabilities; |
| const cb = b.capabilities; |
| if (!ca && !cb) return null; |
| const schedStr = (ir, c) => { |
| const s = c && c.attention_schedule; |
| return Array.isArray(s) && s.length ? ir.scheduleSummary(s) : "uniform"; |
| }; |
| return { |
| backends: diffSet(ca && ca.attention_backends, cb && cb.attention_backends), |
| taskHeads: diffSet(ca && ca.task_heads, cb && cb.task_heads), |
| rows: [ |
| { |
| field: "tensor parallel", |
| a: ca ? String(!!ca.tensor_parallel) : "—", |
| b: cb ? String(!!cb.tensor_parallel) : "—", |
| same: !!(ca && ca.tensor_parallel) === !!(cb && cb.tensor_parallel), |
| }, |
| { |
| field: "attention patterns", |
| a: (ca && ca.attention_patterns || []).join(", ") || "—", |
| b: (cb && cb.attention_patterns || []).join(", ") || "—", |
| same: JSON.stringify(ca && ca.attention_patterns) === JSON.stringify(cb && cb.attention_patterns), |
| }, |
| { |
| field: "attention schedule", |
| a: schedStr(a, ca), |
| b: schedStr(b, cb), |
| same: schedStr(a, ca) === schedStr(b, cb), |
| }, |
| ], |
| }; |
| } |
|
|
| function compareIR(a, b) { |
| const regime = decideRegime(a, b); |
| return { |
| a: { modelType: a.modelType, modelClass: (a.provenance && a.provenance.model_class) || a.modelType }, |
| b: { modelType: b.modelType, modelClass: (b.provenance && b.provenance.model_class) || b.modelType }, |
| regime, |
| headline: diffHeadline(a, b), |
| scale: diffScale(a, b), |
| capabilities: diffCapabilities(a, b), |
| topology: diffTopology(a, b), |
| kinds: diffKindCounts(a, b), |
| nodes: regime.basis === "id" ? alignById(a, b) : alignByKind(a, b), |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| function mergeIR(a, b) { |
| const clone = (o) => JSON.parse(JSON.stringify(o)); |
| const unionArr = (x, y) => [...(x || []), ...(y || []).filter((v) => !(x || []).includes(v))]; |
| const A = a.raw; |
| const B = b.raw; |
| const raw = { ...A }; |
|
|
| const mergeList = (key) => { |
| const by = new Map((A[key] || []).map((n) => [n.id, clone(n)])); |
| for (const n of B[key] || []) { |
| if (by.has(n.id)) by.get(n.id).children = unionArr(by.get(n.id).children, n.children); |
| else by.set(n.id, clone(n)); |
| } |
| return [...by.values()]; |
| }; |
| raw.components = mergeList("components"); |
| raw.templates = mergeList("templates"); |
|
|
| const repById = new Map((A.repeats || []).map((r) => [r.id, r])); |
| for (const r of B.repeats || []) if (!repById.has(r.id)) repById.set(r.id, r); |
| raw.repeats = [...repById.values()]; |
|
|
| const eKey = (e) => `${e.source}|${e.target}|${e.kind}`; |
| const edgeMap = new Map(); |
| for (const e of A.edges || []) edgeMap.set(eKey(e), e); |
| for (const e of B.edges || []) if (!edgeMap.has(eKey(e))) edgeMap.set(eKey(e), e); |
| raw.edges = [...edgeMap.values()]; |
|
|
| |
| const has = (ir, id) => ir.nodes.has(id) || ir.pseudoIds.has(id); |
| const ids = new Set([...a.nodes.keys(), ...b.nodes.keys(), ...a.pseudoIds, ...b.pseudoIds]); |
| const origin = new Map(); |
| for (const id of ids) { |
| const inA = has(a, id); |
| const inB = has(b, id); |
| origin.set(id, inA && inB ? "both" : inA ? "a" : "b"); |
| } |
| |
| const aE = new Set((A.edges || []).map(eKey)); |
| const bE = new Set((B.edges || []).map(eKey)); |
| const edgeOrigin = new Map(); |
| for (const k of edgeMap.keys()) edgeOrigin.set(k, aE.has(k) && bE.has(k) ? "both" : aE.has(k) ? "a" : "b"); |
|
|
| return { raw, origin, edgeOrigin }; |
| } |
|
|
| export { compareIR, mergeIR }; |
|
|