Celsia's picture
Upload folder using huggingface_hub
ecc81b3 verified
Raw
History Blame Contribute Delete
8.77 kB
import { useEffect, useMemo, useRef, useState } from "react";
import Weights from "./Weights.jsx";
import { fmtParams, latticeAxisOf } from "../spec.js";
// The right half: what the model *is*, next to what it does to the lattice.
// The 3-D view answers "which cells, in what order"; this answers "through
// what, with how many weights". Same spec, same active layer — the two halves
// are one picture cut down the middle, not two tools sharing a window.
const FAMILY = {
scan: { color: "#ffc061", glyph: "→" },
kernel: { color: "#c39bd8", glyph: "⊗" },
flatten: { color: "#5eead4", glyph: "⊕" },
};
const S = {
wrap: {
width: "44%",
minWidth: 300,
display: "flex",
flexDirection: "column",
borderLeft: "1px solid #1e2635",
background: "linear-gradient(180deg, #0e1320 0%, #0b0f18 100%)",
},
head: { padding: "12px 16px 10px", borderBottom: "1px solid #1a2233" },
title: {
color: "#5d6b84",
fontSize: 10.5,
fontWeight: 700,
letterSpacing: 1.4,
textTransform: "uppercase",
},
io: {
display: "flex",
alignItems: "center",
gap: 8,
marginTop: 7,
fontSize: 12,
color: "#9fb0cc",
fontVariantNumeric: "tabular-nums",
flexWrap: "wrap",
},
pill: {
background: "#151d2e",
border: "1px solid #253048",
borderRadius: 6,
padding: "3px 8px",
},
body: { flex: 1, overflowY: "auto", padding: "10px 16px 16px" },
node: (active, color) => ({
position: "relative",
display: "flex",
alignItems: "center",
gap: 9,
padding: "7px 10px",
borderRadius: 8,
fontSize: 12.5,
cursor: "pointer",
background: active ? "#1a2438" : "#121926",
border: `1px solid ${active ? color : "#1f2a3d"}`,
boxShadow: active ? `0 0 0 1px ${color}55, inset 2px 0 0 ${color}` : "none",
}),
bar: {
position: "relative",
height: 4,
borderRadius: 99,
background: "#0c1018",
overflow: "hidden",
marginTop: 5,
},
rail: {
width: 1,
height: 12,
background: "#243049",
margin: "0 auto",
},
chip: {
fontSize: 10.5,
padding: "1px 6px",
borderRadius: 99,
background: "#1c2740",
color: "#9fb3d9",
whiteSpace: "nowrap",
},
mech: {
display: "flex",
gap: 6,
flexWrap: "wrap",
marginTop: 8,
},
tabs: { display: "flex", gap: 2, marginTop: 10 },
tab: (active) => ({
background: "none",
border: "none",
borderBottom: active ? "2px solid #ffc061" : "2px solid transparent",
color: active ? "#e8eef8" : "#7d8aa3",
fontSize: 11.5,
fontWeight: active ? 650 : 500,
padding: "5px 10px",
cursor: "pointer",
letterSpacing: 0.3,
}),
};
function Node({ layer, i, spec, active, maxParams, onSelect }) {
const ref = useRef();
const kind = layer.kind ?? "scan";
const fam = FAMILY[kind] ?? FAMILY.scan;
const isTime = kind === "scan" && latticeAxisOf(layer, spec) === null;
// Keep the active layer in view as the animation advances, but never yank
// the panel while the user is reading somewhere else — only scroll when the
// node is actually outside the viewport.
useEffect(() => {
if (active && ref.current) {
const el = ref.current;
const box = el.getBoundingClientRect();
const par = el.parentElement.parentElement.getBoundingClientRect();
if (box.top < par.top || box.bottom > par.bottom) {
el.scrollIntoView({ block: "nearest", behavior: "smooth" });
}
}
}, [active]);
const label =
kind === "flatten"
? (layer.axes ?? []).join(" ⊕ ")
: kind === "kernel"
? (layer.contracted ?? []).join(" ⊗ ")
: layer.axis;
const detail =
kind === "flatten"
? layer.tokens
? `${layer.tokens} tokens`
: ""
: kind === "kernel"
? layer.axis
? `+ ${layer.axis} →`
: ""
: isTime
? "→ causal"
: layer.reverse
? "←"
: "→";
const pct = maxParams ? (100 * (layer.n_params ?? 0)) / maxParams : 0;
return (
<div>
<div
ref={ref}
className="td-row"
style={S.node(active, fam.color)}
onClick={() => onSelect(i)}
>
<span style={{ color: "#6b7a96", width: 26, flexShrink: 0 }}>L{i}</span>
<span style={{ color: fam.color, width: 14, flexShrink: 0 }}>
{fam.glyph}
</span>
<div style={{ flex: 1, minWidth: 0 }}>
<div
style={{
display: "flex",
alignItems: "baseline",
gap: 6,
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
<b style={{ color: active ? "#e8eef8" : "#c3cdde" }}>{label}</b>
<span style={{ color: "#6b7a96", fontSize: 11.5 }}>{detail}</span>
</div>
<div style={S.bar}>
<div
style={{
height: "100%",
width: `${pct}%`,
background: fam.color,
opacity: active ? 0.95 : 0.45,
transition: "opacity 160ms ease",
}}
/>
</div>
</div>
<div style={{ textAlign: "right", flexShrink: 0 }}>
{layer.mixer && <div style={S.chip}>{layer.mixer}</div>}
<div
style={{
color: "#6b7a96",
fontSize: 11,
marginTop: 3,
fontVariantNumeric: "tabular-nums",
}}
>
{fmtParams(layer.n_params ?? 0)}
</div>
</div>
</div>
<div style={S.rail} />
</div>
);
}
export default function ModelView({
parsed,
layerIndex,
onSelectLayer,
weights,
}) {
const [view, setView] = useState("flow");
const { spec } = parsed;
const m = spec.model;
const io = spec.io ?? {};
const maxParams = useMemo(
() => Math.max(1, ...spec.layers.map((l) => l.n_params ?? 0)),
[spec],
);
// Which distinct mechanisms this model is actually built from — the mixers
// in play plus the composition, rather than a class name that hides both.
const mechanisms = useMemo(() => {
const mixers = [
...new Set(spec.layers.map((l) => l.mixer).filter(Boolean)),
];
const kinds = [...new Set(spec.layers.map((l) => l.kind ?? "scan"))];
return { mixers, kinds };
}, [spec]);
const swept = spec.layers.reduce((a, l) => a + (l.n_params ?? 0), 0);
return (
<div style={S.wrap}>
<div style={S.head}>
<div style={S.title}>model</div>
<div style={S.io}>
<span style={S.pill}>{(io.input ?? []).join(" × ") || "—"}</span>
<span style={{ color: "#41568a" }}></span>
<b style={{ color: "#e8eef8" }}>{m.kind}</b>
<span style={{ color: "#41568a" }}></span>
<span style={S.pill}>{(io.output ?? []).join(" × ") || "—"}</span>
</div>
<div style={S.mech}>
<span style={S.chip}>{spec.nd_method.name}</span>
{mechanisms.mixers.map((x) => (
<span key={x} style={{ ...S.chip, color: "#ffc9a0" }}>
{x}
</span>
))}
{mechanisms.kinds.map((k) => (
<span
key={k}
style={{ ...S.chip, color: FAMILY[k]?.color ?? "#9fb3d9" }}
>
{FAMILY[k]?.glyph} {k}
</span>
))}
<span style={S.chip}>{fmtParams(m.n_params)} total</span>
{swept !== m.n_params && (
// Layer weights rarely add up to the whole model: projections,
// norms and the head live outside the swept stack. Saying so beats
// letting the two numbers quietly disagree.
<span style={{ ...S.chip, color: "#7d8aa3" }}>
{fmtParams(m.n_params - swept)} outside layers
</span>
)}
</div>
<div style={S.tabs}>
{["flow", "weights"].map((v) => (
<button
key={v}
style={S.tab(view === v)}
onClick={() => setView(v)}
>
{v === "flow" ? "layer flow" : `weights · L${layerIndex}`}
</button>
))}
</div>
</div>
<div style={S.body} className="td-panel">
{view === "weights" && (
<Weights weights={weights} layerIndex={layerIndex} />
)}
{view === "flow" &&
spec.layers.map((l, i) => (
<Node
key={i}
layer={l}
i={i}
spec={spec}
active={i === layerIndex}
maxParams={maxParams}
onSelect={onSelectLayer}
/>
))}
</div>
</div>
);
}