| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const PAD = 18; |
| const HGAP = 34; |
| const VGAP = 46; |
| const REPEAT_HEADER_H = 34; |
| const CONTAINER_HEADER_H = 26; |
| const LEAF_H = 54; |
| const REPEAT_LEAF_H = 66; |
| const SCHEDULE_STRIP_H = 16; |
| const MIN_W = 132; |
|
|
| |
| |
| const LAYERING_KINDS = new Set(["data", "cross_attention", "position", "mask"]); |
|
|
| class Layout { |
| constructor(ir, opts) { |
| this.ir = ir; |
| this.expanded = opts.expanded; |
| this.fields = opts.fields; |
| this.showInfo = opts.showInfo !== false; |
| this.tied = opts.tied !== false; |
|
|
| this.rectById = new Map(); |
| this.placed = []; |
| this.width = 0; |
| this.height = 0; |
|
|
| this._run(); |
| } |
|
|
| isRepeatOpen(id) { |
| return this.expanded.has(id); |
| } |
|
|
| isOpen(id) { |
| const n = this.ir.node(id); |
| if (!n) return false; |
| if (n.nodeType === "repeat") return this.isRepeatOpen(id); |
| return this.ir.layoutChildrenOf(id).length > 0; |
| } |
|
|
| |
| childrenOf(id) { |
| let kids = this.ir.layoutChildrenOf(id).slice(); |
| if (id === this.ir.rootId) { |
| kids = kids.concat([...this.ir.pseudoIds]); |
| |
| if (this.tied && this.ir.lmHeadId) kids = kids.filter((k) => k !== this.ir.lmHeadId); |
| } |
| return kids; |
| } |
|
|
| |
| childContaining(x, container, childSet) { |
| if (this.ir.isPseudo(x)) { |
| return childSet.has(x) ? x : null; |
| } |
| let cur = x; |
| let guard = 0; |
| while (cur && guard++ < 64) { |
| if (childSet.has(cur)) return cur; |
| const n = this.ir.node(cur); |
| if (!n || cur === container) return null; |
| cur = n.parent; |
| } |
| return null; |
| } |
|
|
| |
| representative(id) { |
| if (this.ir.isPseudo(id)) return id; |
| let cur = id; |
| let highestCollapsed = null; |
| let guard = 0; |
| while (cur && guard++ < 64) { |
| const n = this.ir.node(cur); |
| if (!n) break; |
| if (n.nodeType === "repeat" && !this.isRepeatOpen(cur)) highestCollapsed = cur; |
| cur = n.parent; |
| } |
| let base = highestCollapsed || id; |
| if (this.ir.bodyToRepeat.has(base)) base = this.ir.bodyToRepeat.get(base); |
| return base; |
| } |
|
|
| |
|
|
| measure(id) { |
| if (!this.isOpen(id)) return this._leaf(id); |
|
|
| const childIds = this.childrenOf(id); |
| const childBoxes = childIds.map((cid) => this.measure(cid)); |
| const boxByChild = new Map(childIds.map((cid, i) => [cid, childBoxes[i]])); |
|
|
| const rows = this._layerize(id, childIds); |
| this._placeRows(rows, boxByChild); |
|
|
| |
| |
| let contentW = Math.max(...childBoxes.map((b) => b.rx + b.w + (b.deckW || 0)), MIN_W); |
| const contentH = Math.max(...childBoxes.map((b) => b.ry + b.h + (b.deckH || 0)), LEAF_H); |
|
|
| const n = this.ir.node(id); |
| const isRoot = id === this.ir.rootId; |
| const isRepeat = n && n.nodeType === "repeat"; |
| |
| const hasSchedule = isRepeat && !!this.ir.scheduleForRepeat(n, this.fields); |
| const headerH = |
| (isRoot ? 0 : isRepeat ? REPEAT_HEADER_H : CONTAINER_HEADER_H) + |
| (hasSchedule ? SCHEDULE_STRIP_H : 0); |
| const drawFrame = !isRoot; |
|
|
| |
| |
| let extraX = 0; |
| if (drawFrame) { |
| const headerLabel = isRepeat |
| ? this.ir.label(id, this.fields) |
| : `${this.ir.label(id, this.fields)} · ${this.ir.kindLabel(n && n.kind)}`; |
| const headerNeed = Math.round(headerLabel.length * 6.9) + 24 + (isRepeat ? 34 : 8); |
| const innerNeed = headerNeed - PAD * 2; |
| if (innerNeed > contentW) { |
| extraX = (innerNeed - contentW) / 2; |
| contentW = innerNeed; |
| } |
| } |
|
|
| |
| const ox = drawFrame ? PAD + extraX : 0; |
| const oy = drawFrame ? PAD + headerH : 0; |
| childBoxes.forEach((b) => { |
| b.rx += ox; |
| b.ry += oy; |
| }); |
|
|
| const deck = this._deckExtent(n); |
| return { |
| id, |
| node: n, |
| kind: "container", |
| isRepeat, |
| isRoot, |
| drawFrame, |
| headerH, |
| hasSchedule, |
| deckW: deck.dw, |
| deckH: deck.dh, |
| children: childBoxes, |
| rx: 0, |
| ry: 0, |
| w: drawFrame ? contentW + PAD * 2 : contentW, |
| h: drawFrame ? contentH + PAD * 2 + headerH : contentH, |
| }; |
| } |
|
|
| _leaf(id) { |
| const n = this.ir.node(id); |
| const isRepeat = n && n.nodeType === "repeat"; |
| const label = this.ir.label(id, this.fields); |
| const isPseudo = !n; |
| const info = this.showInfo && !isPseudo ? this.ir.nodeInfo(id, this.fields) : null; |
| let w; |
| let h; |
| if (isPseudo) { |
| |
| w = Math.max(88, Math.min(240, Math.round(label.length * 6.2 + 34))); |
| h = 40; |
| } else { |
| |
| |
| |
| const LEFT = 28; |
| const rightPad = isRepeat ? 42 : 20; |
| let need = LEFT + Math.round(label.length * 8.0) + rightPad; |
| if (info) need = Math.max(need, LEFT + Math.round(info.length * 6.6) + 18); |
| |
| if (n && n.attributes && n.attributes.kernel) need += 52; |
| |
| if (this.tied && this.ir.isWordEmbedding(n)) need += 62; |
| w = Math.max(MIN_W, Math.min(400, need)); |
| h = (isRepeat ? REPEAT_LEAF_H : LEAF_H) + (info ? 16 : 0); |
| } |
| |
| const hasSchedule = isRepeat && !!this.ir.scheduleForRepeat(n, this.fields); |
| if (hasSchedule) h += SCHEDULE_STRIP_H; |
| const deck = this._deckExtent(n); |
| return { |
| id, |
| node: n, |
| kind: isRepeat ? "repeat" : n ? "leaf" : "input", |
| isRepeat, |
| info, |
| hasSchedule, |
| deckW: deck.dw, |
| deckH: deck.dh, |
| children: [], |
| rx: 0, |
| ry: 0, |
| w, |
| h, |
| }; |
| } |
|
|
| |
| |
| _deckExtent(node) { |
| const geo = this.ir.deckGeometry(node, this.fields); |
| return geo ? { dw: geo.dw, dh: geo.dh } : { dw: 0, dh: 0 }; |
| } |
|
|
| |
| _layerize(containerId, childIds) { |
| const childSet = new Set(childIds); |
| const adj = []; |
| for (const e of this.ir.edges) { |
| if (!LAYERING_KINDS.has(e.kind)) continue; |
| const a = this.childContaining(e.source, containerId, childSet); |
| const b = this.childContaining(e.target, containerId, childSet); |
| if (a && b && a !== b) adj.push([a, b]); |
| } |
| const layer = new Map(childIds.map((c) => [c, 0])); |
| |
| for (let it = 0; it < childIds.length; it++) { |
| let changed = false; |
| for (const [a, b] of adj) { |
| const cand = layer.get(a) + 1; |
| if (cand > layer.get(b)) { |
| layer.set(b, cand); |
| changed = true; |
| } |
| } |
| if (!changed) break; |
| } |
| const rows = new Map(); |
| childIds.forEach((c) => { |
| const l = layer.get(c); |
| if (!rows.has(l)) rows.set(l, []); |
| rows.get(l).push(c); |
| }); |
| return [...rows.keys()].sort((a, b) => a - b).map((k) => rows.get(k)); |
| } |
|
|
| _placeRows(rows, boxByChild) { |
| const fw = (b) => b.w + (b.deckW || 0); |
| const fh = (b) => b.h + (b.deckH || 0); |
| const rowWidths = rows.map((row) => |
| row.reduce((s, c) => s + fw(boxByChild.get(c)), 0) + HGAP * Math.max(0, row.length - 1) |
| ); |
| const maxW = Math.max(...rowWidths, MIN_W); |
| let y = 0; |
| rows.forEach((row, ri) => { |
| const rowH = Math.max(...row.map((c) => fh(boxByChild.get(c)))); |
| let x = (maxW - rowWidths[ri]) / 2; |
| row.forEach((c) => { |
| const b = boxByChild.get(c); |
| b.rx = x; |
| b.ry = y + (rowH - fh(b)) / 2; |
| x += fw(b) + HGAP; |
| }); |
| y += rowH + VGAP; |
| }); |
| } |
|
|
| |
|
|
| _flatten(box, px, py, parentId) { |
| const x = px + box.rx; |
| const y = py + box.ry; |
| const rect = { |
| id: box.id, |
| node: box.node, |
| kind: box.kind, |
| isRepeat: box.isRepeat, |
| isRoot: box.isRoot, |
| drawFrame: box.drawFrame, |
| headerH: box.headerH || 0, |
| info: box.info || null, |
| hasSchedule: !!box.hasSchedule, |
| deckW: box.deckW || 0, |
| deckH: box.deckH || 0, |
| parentId: parentId || null, |
| x, |
| y, |
| w: box.w, |
| h: box.h, |
| }; |
| this.rectById.set(box.id, rect); |
| this.placed.push(rect); |
| box.children.forEach((c) => this._flatten(c, x, y, box.id)); |
| } |
|
|
| _run() { |
| if (!this.ir.rootId) return; |
| const tree = this.measure(this.ir.rootId); |
| this._flatten(tree, 0, 0, null); |
| this.width = tree.w; |
| this.height = tree.h; |
| } |
|
|
| |
|
|
| |
| |
| visibleEdges(kindFilter) { |
| const seen = new Set(); |
| const out = []; |
| for (const e of this.ir.edges) { |
| if (kindFilter && !kindFilter.has(e.kind)) continue; |
| const s = this.representative(e.source); |
| const t = this.representative(e.target); |
| if (s === t) continue; |
| if (!this.rectById.has(s) || !this.rectById.has(t)) continue; |
| |
| |
| |
| |
| if (e.kind === "residual" && (this._encloses(s, t) || this._encloses(t, s))) continue; |
| const key = `${s} |