| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const KIND_LABELS = { |
| model: "Model", |
| embedding: "Embedding", |
| encoder: "Encoder", |
| decoder: "Decoder", |
| attention: "Attention", |
| cross_attention: "Cross-Attention", |
| feed_forward: "Feed-Forward", |
| projection: "Projection", |
| moe: "Mixture-of-Experts", |
| lm_head: "LM Head", |
| normalization: "Normalization", |
| position: "Position", |
| pooler: "Pooler", |
| transformer_block: "Transformer Block", |
| repeated_container: "Container", |
| symbolic_repeat: "Repeat", |
| }; |
|
|
| |
| function humanizeClassName(name) { |
| if (!name) return ""; |
| return name |
| .replace(/([a-z0-9])([A-Z])/g, "$1 $2") |
| .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") |
| .trim(); |
| } |
|
|
| |
| function humanizeId(id) { |
| const leaf = String(id).split(".").pop(); |
| return leaf |
| .replace(/[_]+/g, " ") |
| .replace(/\b\w/g, (c) => c.toUpperCase()); |
| } |
|
|
| |
| |
| |
| function evalCountExpr(expr, fields, fallback) { |
| if (expr == null) return fallback; |
| if (typeof expr === "number") return expr; |
| const raw = String(expr).trim(); |
| |
| let substituted = raw.replace(/config\.([A-Za-z_][A-Za-z0-9_]*)/g, (_, key) => { |
| const v = fields ? fields[key] : undefined; |
| return typeof v === "number" ? String(v) : "NaN"; |
| }); |
| |
| if (/^[\d\s+\-*/().]+$/.test(substituted)) { |
| try { |
| |
| const val = Function(`"use strict";return (${substituted});`)(); |
| if (typeof val === "number" && Number.isFinite(val)) { |
| return Math.max(0, Math.round(val)); |
| } |
| } catch (_) { |
| |
| } |
| } |
| return fallback; |
| } |
|
|
| |
| |
| |
| |
| function collectConfigFields(cfg) { |
| const out = {}; |
| if (!cfg) return out; |
| for (const key of ["salient_fields", "referenced_fields"]) { |
| const v = cfg[key]; |
| if (v && typeof v === "object" && !Array.isArray(v)) Object.assign(out, v); |
| } |
| return out; |
| } |
|
|
| class IRModel { |
| constructor(raw) { |
| this.raw = raw; |
| this.modelType = raw.model_type || (raw.config && raw.config.model_type) || "model"; |
| this.schemaVersion = raw.schema_version; |
| |
| this.provenance = raw.provenance || {}; |
| |
| this.configFields = collectConfigFields(raw.config); |
| this.configClass = raw.config && raw.config.class_name; |
| |
| this.architecture = raw.architecture || null; |
| this.dataflow = raw.dataflow || null; |
| |
| this.extends = raw.extends || null; |
| this.patches = Array.isArray(raw.patches) ? raw.patches : []; |
| this.capabilities = raw.capabilities || null; |
|
|
| this.nodes = new Map(); |
| this.repeatsById = new Map(); |
| this.edges = []; |
| this.pseudoIds = new Set(); |
|
|
| this._index(); |
| this._buildTree(); |
| this._indexEdges(); |
| this._synthesizeLmHead(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| _synthesizeLmHead() { |
| if (!this.rootId) return; |
| const root = this.nodes.get(this.rootId); |
| if (!root) return; |
| const wordEmb = [...this.nodes.values()].find((n) => this.isWordEmbedding(n)); |
| if (!wordEmb) return; |
|
|
| |
| |
| const kidSet = new Set(root.children); |
| const topChild = (x) => { |
| let cur = x; |
| for (let g = 0; g < 64 && cur; g++) { |
| if (kidSet.has(cur)) return cur; |
| cur = (this.nodes.get(cur) || {}).parent; |
| } |
| return null; |
| }; |
| const hasIn = new Set(); |
| const hasOut = new Set(); |
| for (const e of this.edges) { |
| if (e.kind !== "data") continue; |
| const s = topChild(e.source); |
| const t = topChild(e.target); |
| if (s && t && s !== t) { |
| hasOut.add(s); |
| hasIn.add(t); |
| } |
| } |
| const sinks = root.children.filter((k) => hasIn.has(k) && !hasOut.has(k) && k !== wordEmb.id); |
| const sinkId = sinks.length ? sinks[sinks.length - 1] : null; |
| if (!sinkId) return; |
|
|
| const a = wordEmb.attributes || {}; |
| const lm = { |
| id: "lm_head", |
| kind: "lm_head", |
| className: "Linear", |
| pathPattern: "lm_head", |
| rawChildren: [], |
| |
| attributes: { |
| in_features: a.embedding_dim || "config.hidden_size", |
| out_features: a.num_embeddings || "config.vocab_size", |
| }, |
| nodeType: "synthetic", |
| children: [], |
| parent: this.rootId, |
| }; |
| this.nodes.set(lm.id, lm); |
| root.children.push(lm.id); |
| this.lmHeadId = lm.id; |
| this.lmHeadFrom = sinkId; |
| this.edges.push({ kind: "data", source: sinkId, target: lm.id, _idx: this.edges.length, _synthetic: true }); |
| } |
|
|
| _addRaw(list, nodeType) { |
| (list || []).forEach((c) => { |
| this.nodes.set(c.id, { |
| id: c.id, |
| kind: c.kind, |
| className: c.class_name, |
| pathPattern: c.path_pattern, |
| rawChildren: c.children ?? [], |
| attributes: c.attributes || null, |
| nodeType, |
| children: [], |
| parent: null, |
| }); |
| }); |
| } |
|
|
| _index() { |
| this._addRaw(this.raw.components, "component"); |
| this._addRaw(this.raw.templates, "template"); |
|
|
| (this.raw.repeats || []).forEach((r) => { |
| const node = { |
| id: r.id, |
| kind: "symbolic_repeat", |
| className: r.repeated_class_name, |
| pathPattern: r.container_path_pattern, |
| rawChildren: [r.body], |
| nodeType: "repeat", |
| children: [], |
| parent: null, |
| repeat: r, |
| }; |
| this.nodes.set(r.id, node); |
| this.repeatsById.set(r.id, node); |
| }); |
| } |
|
|
| _buildTree() { |
| |
| for (const node of this.nodes.values()) { |
| node.rawChildren.forEach((cid) => { |
| const child = this.nodes.get(cid); |
| if (child) { |
| node.children.push(cid); |
| child.parent = node.id; |
| } |
| }); |
| } |
| |
| |
| this.bodyToRepeat = new Map(); |
| for (const r of this.repeatsById.values()) { |
| const body = this.nodes.get(r.repeat.body); |
| if (body) { |
| body.parent = r.id; |
| this.bodyToRepeat.set(body.id, r.id); |
| } |
| } |
|
|
| |
| let root = |
| [...this.nodes.values()].find((n) => n.kind === "model" && !n.parent) || |
| [...this.nodes.values()].find((n) => !n.parent); |
| this.rootId = root ? root.id : null; |
|
|
| this._attachOrphans(); |
| } |
|
|
| |
| |
| |
| |
| |
| _attachOrphans() { |
| if (!this.rootId) return; |
| const rootPath = (this.nodes.get(this.rootId) || {}).pathPattern || "model"; |
| const pathToId = new Map(); |
| for (const [id, n] of this.nodes) { |
| if (n.nodeType === "template") continue; |
| if (n.pathPattern && !pathToId.has(n.pathPattern)) pathToId.set(n.pathPattern, id); |
| } |
|
|
| const ensureByPath = (path) => { |
| if (pathToId.has(path)) return pathToId.get(path); |
| if (!path || path === rootPath || !path.includes(".")) return this.rootId; |
| const synth = { |
| id: path, |
| kind: "module", |
| className: null, |
| module: null, |
| pathPattern: path, |
| rawChildren: [], |
| attributes: null, |
| nodeType: "synthetic", |
| children: [], |
| parent: null, |
| }; |
| this.nodes.set(path, synth); |
| pathToId.set(path, path); |
| const pid = ensureByPath(path.slice(0, path.lastIndexOf("."))); |
| synth.parent = pid; |
| const pn = this.nodes.get(pid); |
| if (pn && !pn.children.includes(path)) pn.children.push(path); |
| return path; |
| }; |
|
|
| const link = (id, parentId) => { |
| this.nodes.get(id).parent = parentId; |
| const pn = this.nodes.get(parentId); |
| if (pn && !pn.children.includes(id)) pn.children.push(id); |
| }; |
|
|
| for (const [id, n] of [...this.nodes]) { |
| if (id === this.rootId || n.parent || n.nodeType === "template" || n.nodeType === "synthetic") continue; |
| let parentPath; |
| if (n.nodeType === "repeat") { |
| const cpp = n.repeat.container_path_pattern; |
| parentPath = cpp && cpp.includes(".") ? cpp.slice(0, cpp.lastIndexOf(".")) : rootPath; |
| } else { |
| const p = n.pathPattern; |
| parentPath = p && p.includes(".") ? p.slice(0, p.lastIndexOf(".")) : rootPath; |
| } |
| link(id, ensureByPath(parentPath)); |
| } |
| } |
|
|
| |
| |
| isPseudo(id) { |
| return typeof id === "string" && id.includes(":") && !this.nodes.has(id); |
| } |
|
|
| _indexEdges() { |
| const present = new Set(); |
| (this.raw.edges || []).forEach((e, idx) => { |
| const edge = { ...e, _idx: idx }; |
| this.edges.push(edge); |
| if (e.kind) present.add(e.kind); |
| [e.source, e.target].forEach((ep) => { |
| if (this.isPseudo(ep)) this.pseudoIds.add(ep); |
| }); |
| }); |
| |
| |
| const extra = [...present].filter((k) => !EDGE_KINDS.includes(k)).sort(); |
| this.edgeKinds = [...EDGE_KINDS.filter((k) => present.has(k)), ...extra]; |
| if (!this.edgeKinds.length) this.edgeKinds = [...EDGE_KINDS]; |
| } |
|
|
| |
|
|
| node(id) { |
| return this.nodes.get(id); |
| } |
|
|
| kindLabel(kind) { |
| return KIND_LABELS[kind] || humanizeId(kind || ""); |
| } |
|
|
| |
| |
| |
| layoutChildrenOf(id) { |
| const n = this.nodes.get(id); |
| if (!n) return []; |
| if (n.nodeType === "repeat") { |
| const body = this.nodes.get(n.repeat.body); |
| return body ? body.children.slice() : []; |
| } |
| return n.children.slice(); |
| } |
|
|
| isContainer(id) { |
| return this.layoutChildrenOf(id).length > 0; |
| } |
|
|
| |
| resolveCount(repeatNode, fields) { |
| const r = repeatNode.repeat; |
| return evalCountExpr(r.count_expr, fields, r.count); |
| } |
|
|
| |
| repeatLabel(repeatNode, fields) { |
| const base = humanizeClassName(repeatNode.className) || humanizeId(repeatNode.id); |
| return `${base} × ${this.resolveCount(repeatNode, fields)}`; |
| } |
|
|
| |
| label(id, fields) { |
| const n = this.nodes.get(id); |
| if (!n) { |
| |
| const rest = typeof id === "string" && id.includes(":") ? id.slice(id.indexOf(":") + 1) : id; |
| return humanizeId(rest); |
| } |
| if (n.nodeType === "repeat") return this.repeatLabel(n, fields); |
| return humanizeClassName(n.className) || humanizeId(n.id); |
| } |
|
|
| |
| |
| nodeShapes(id) { |
| return (this.dataflow && this.dataflow.shapes && this.dataflow.shapes[id]) || null; |
| } |
|
|
| |
| |
| |
| tiedEmbeddings(fields) { |
| const f = fields || this.configFields; |
| if (f && Object.prototype.hasOwnProperty.call(f, "tie_word_embeddings")) return !!f.tie_word_embeddings; |
| return !!(this.architecture && this.architecture.tie_word_embeddings); |
| } |
|
|
| |
| |
| isWordEmbedding(node) { |
| return !!( |
| node && |
| node.kind === "embedding" && |
| node.attributes && |
| node.attributes.num_embeddings === "config.vocab_size" && |
| node.children.length === 0 |
| ); |
| } |
|
|
| |
| |
| nodeKernel(node) { |
| const name = node && node.attributes && node.attributes.kernel; |
| if (!name) return null; |
| const map = this.capabilities && this.capabilities.kernels; |
| return { name, repos: (map && map[name]) || [] }; |
| } |
|
|
| |
| |
| |
| architectureClasses(fields) { |
| const arch = fields && fields.architectures; |
| if (Array.isArray(arch) && arch.length) return arch; |
| const mc = this.provenance && this.provenance.model_class; |
| return mc ? [mc] : []; |
| } |
|
|
| |
| |
| |
| |
| deckGeometry(node, fields) { |
| if (!node || node.nodeType !== "repeat") return null; |
| const sched = this.scheduleForRepeat(node, fields); |
| let runs; |
| if (sched && sched.length) { |
| runs = []; |
| for (const p of sched) { |
| const last = runs[runs.length - 1]; |
| if (last && last.p === p) last.len++; |
| else runs.push({ p, len: 1 }); |
| } |
| } else { |
| const n = this.resolveCount(node, fields); |
| if (!n || n < 2) return null; |
| runs = [{ p: null, len: Math.min(n, 64) }]; |
| } |
| const UNIT = 1.6; |
| const MIN = 3; |
| const CAP = 48; |
| const bands = runs.map((r) => ({ p: r.p, t: r.p ? Math.max(r.len * UNIT, MIN) : r.len * UNIT })); |
| const total = bands.reduce((s, b) => s + b.t, 0); |
| const scale = total > CAP ? CAP / total : 1; |
| let off = 0; |
| const cards = []; |
| for (const b of bands) { |
| off += b.t * scale; |
| cards.push({ pattern: b.p, off }); |
| } |
| return { cards, dw: Math.round(off * 0.6), dh: Math.round(off) }; |
| } |
|
|
| |
| |
| |
| tpPlan(id) { |
| const start = this.nodes.get(id); |
| if (!start) return null; |
| const col = []; |
| const row = []; |
| const visit = (nid) => { |
| const n = this.nodes.get(nid); |
| if (!n) return; |
| const tp = n.attributes && n.attributes.tp; |
| if (n.kind === "projection" && tp) (tp === "rowwise" ? row : col).push(nid.split(".").pop()); |
| n.children.forEach(visit); |
| }; |
| start.children.forEach(visit); |
| return col.length || row.length ? { colwise: col, rowwise: row } : null; |
| } |
|
|
| |
| |
| diffSize() { |
| let n = 0; |
| for (const p of this.patches) { |
| for (const bucket of [p.added, p.overridden, p.deleted]) { |
| if (!bucket) continue; |
| n += (bucket.methods ?? []).length + (bucket.attrs ?? []).length; |
| } |
| if (p.relation === "new") n += 3; |
| } |
| return n; |
| } |
|
|
| |
| |
| |
| scheduleForRepeat(repeatNode, fields) { |
| const sched = this.capabilities && this.capabilities.attention_schedule; |
| if (!Array.isArray(sched) || !repeatNode || repeatNode.nodeType !== "repeat") return null; |
| return sched.length === this.resolveCount(repeatNode, fields) ? sched : null; |
| } |
|
|
| |
| |
| scheduleSummary(schedule) { |
| if (!Array.isArray(schedule) || !schedule.length) return ""; |
| const counts = new Map(); |
| for (const p of schedule) counts.set(p, (counts.get(p) || 0) + 1); |
| const short = (p) => String(p).replace(/_attention$/, ""); |
| const totals = [...counts.entries()].map(([p, c]) => `${c} ${short(p)}`).join(" · "); |
| |
| let period = schedule.length; |
| for (let k = 1; k <= schedule.length / 2; k++) { |
| if (schedule.length % k) continue; |
| if (schedule.every((v, i) => v === schedule[i % k])) { period = k; break; } |
| } |
| if (period < schedule.length) { |
| const runs = []; |
| for (let i = 0; i < period; ) { |
| let j = i; |
| while (j < period && schedule[j] === schedule[i]) j++; |
| runs.push(`${j - i}×${short(schedule[i])}`); |
| i = j; |
| } |
| return `${totals} — ${runs.join(" + ")}`; |
| } |
| return totals; |
| } |
|
|
| |
| |
| resolveShape(shape, fields) { |
| if (!Array.isArray(shape)) return null; |
| const dims = shape.map((d) => { |
| const s = String(d); |
| if (s.startsWith("config.")) { |
| const key = s.slice("config.".length); |
| const v = fields ? fields[key] : undefined; |
| return v != null ? String(v) : key; |
| } |
| return s; |
| }); |
| return `[${dims.join(", ")}]`; |
| } |
|
|
| |
| |
| |
| nodeInfo(id, fields) { |
| const parts = []; |
| const shapes = this.nodeShapes(id); |
| if (shapes && shapes.out) { |
| const sh = this.resolveShape(shapes.out, fields); |
| if (sh) parts.push(sh); |
| } |
| const n = this.nodes.get(id); |
| if (n && n.attributes && typeof n.attributes === "object") { |
| const attrs = n.attributes; |
| const val = (v) => { |
| 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; |
| }; |
| |
| if (attrs.in_features !== undefined && attrs.out_features !== undefined) { |
| parts.push(`${val(attrs.in_features)} → ${val(attrs.out_features)}`); |
| } |
| const a = []; |
| for (const [k, v] of Object.entries(attrs)) { |
| |
| if (k === "in_features" || k === "out_features" || k === "tp" || k === "kernel") continue; |
| if (v === true) a.push(k.replace(/_/g, " ")); |
| else if (v === false || v == null) continue; |
| else a.push(`${k.replace(/_/g, " ")} ${val(v)}`); |
| } |
| if (a.length) parts.push(a.slice(0, 3).join(" · ")); |
| } |
| let s = parts.join(" · "); |
| if (s.length > 42) s = s.slice(0, 41) + "…"; |
| return s || null; |
| } |
|
|
| |
| edgesFor(id) { |
| const incoming = this.edges.filter((e) => e.target === id); |
| const outgoing = this.edges.filter((e) => e.source === id); |
| return { incoming, outgoing }; |
| } |
| } |
|
|
| const EDGE_KINDS = [ |
| "data", |
| "residual", |
| "mask", |
| "position", |
| "cross_attention", |
| "cache_read", |
| "cache_write", |
| ]; |
|
|
| |
| |
| function patternColor(pattern) { |
| const p = String(pattern || ""); |
| if (p.startsWith("sliding")) return "#f59e0b"; |
| if (p.startsWith("full") || p === "causal") return "#3b82f6"; |
| if (p === "mamba") return "#a78bfa"; |
| if (p.startsWith("bidirectional")) return "#2dd4bf"; |
| return "#94a3b8"; |
| } |
|
|
| export { IRModel, EDGE_KINDS, humanizeClassName, humanizeId, evalCountExpr, patternColor }; |
|
|