Download viewer/src/components/Sidebar.jsx from Celsia/torch-dimensions: direct link, hf CLI and curl.
- Browser
- Download file 14.1 kB
-
https://huggingface.co/Celsia/torch-dimensions/resolve/main/viewer/src/components/Sidebar.jsx
- Command line
-
hf download hf://Celsia/torch-dimensions/viewer/src/components/Sidebar.jsx
-
curl -L -o Sidebar.jsx https://huggingface.co/Celsia/torch-dimensions/resolve/main/viewer/src/components/Sidebar.jsx
14.1 kB
| import { useEffect, useRef } from "react"; | |
| import { fmtParams, latticeAxisOf } from "../spec.js"; | |
| // One column tried to hold the model summary, the sweep directions, the run | |
| // metrics and every layer at once, so everything was cramped and the layer | |
| // list β the part that changes as you watch β sat below the fold. Tabs put one | |
| // concern on screen at a time; the run metrics moved out entirely, to the dock | |
| // under the scene where a training curve has room to be a curve. | |
| const S = { | |
| panel: { | |
| width: 320, | |
| height: "100%", | |
| display: "flex", | |
| flexDirection: "column", | |
| background: "linear-gradient(180deg, #131926 0%, #10141d 42%, #0e121a 100%)", | |
| borderRight: "1px solid #1e2635", | |
| flexShrink: 0, | |
| }, | |
| head: { padding: "16px 18px 0" }, | |
| body: { flex: 1, overflowY: "auto", padding: "4px 18px 18px" }, | |
| h1: { fontSize: 15, fontWeight: 650, letterSpacing: 0.2, marginBottom: 2 }, | |
| sub: { color: "#8b95a8", fontSize: 12, marginBottom: 12 }, | |
| tabs: { | |
| display: "flex", | |
| gap: 2, | |
| padding: "0 12px", | |
| borderBottom: "1px solid #1e2635", | |
| }, | |
| tab: (active) => ({ | |
| flex: 1, | |
| background: "none", | |
| border: "none", | |
| borderBottom: active ? "2px solid #ffc061" : "2px solid transparent", | |
| color: active ? "#e8eef8" : "#7d8aa3", | |
| fontSize: 12.5, | |
| fontWeight: active ? 650 : 500, | |
| padding: "9px 4px", | |
| cursor: "pointer", | |
| letterSpacing: 0.3, | |
| }), | |
| section: { | |
| color: "#5d6b84", | |
| fontSize: 10.5, | |
| fontWeight: 700, | |
| letterSpacing: 1.4, | |
| textTransform: "uppercase", | |
| margin: "16px 2px 6px", | |
| }, | |
| card: { | |
| background: "#151b28", | |
| border: "1px solid #232d40", | |
| borderRadius: 8, | |
| padding: "10px 12px", | |
| }, | |
| row: { | |
| display: "flex", | |
| justifyContent: "space-between", | |
| padding: "2px 0", | |
| fontSize: 13, | |
| }, | |
| key: { color: "#8b95a8" }, | |
| warn: { | |
| background: "#2a1418", | |
| border: "1px solid #7f1d1d", | |
| color: "#fca5a5", | |
| borderRadius: 8, | |
| padding: "8px 12px", | |
| marginTop: 10, | |
| fontSize: 13, | |
| }, | |
| layer: (active) => ({ | |
| position: "relative", | |
| overflow: "hidden", | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 8, | |
| padding: "6px 10px", | |
| borderRadius: 6, | |
| cursor: "pointer", | |
| fontSize: 13, | |
| background: active ? "#22304c" : "transparent", | |
| border: active ? "1px solid #41568a" : "1px solid transparent", | |
| boxShadow: active ? "inset 3px 0 0 #ffc061" : "none", | |
| }), | |
| // The sweep's progress *through the active layer*, drawn behind its row. | |
| layerFill: { | |
| position: "absolute", | |
| inset: 0, | |
| transformOrigin: "left center", | |
| background: | |
| "linear-gradient(90deg, rgba(255,192,97,0.20), rgba(255,192,97,0.05))", | |
| pointerEvents: "none", | |
| }, | |
| chip: { | |
| fontSize: 11, | |
| padding: "1px 7px", | |
| borderRadius: 99, | |
| background: "#233049", | |
| color: "#9fb3d9", | |
| }, | |
| controls: { display: "flex", gap: 8, marginTop: 10 }, | |
| btn: { | |
| background: "#1c2536", | |
| color: "#d6dbe4", | |
| border: "1px solid #2b3850", | |
| borderRadius: 6, | |
| padding: "7px 12px", | |
| cursor: "pointer", | |
| fontSize: 13, | |
| flex: 1, | |
| }, | |
| select: { | |
| background: "#1c2536", | |
| color: "#d6dbe4", | |
| border: "1px solid #2b3850", | |
| borderRadius: 6, | |
| padding: "6px 8px", | |
| fontSize: 13, | |
| width: "100%", | |
| }, | |
| switch: (on) => ({ | |
| display: "flex", | |
| alignItems: "center", | |
| gap: 10, | |
| width: "100%", | |
| background: on ? "#1d2b21" : "#1c2536", | |
| color: on ? "#9ece6a" : "#d6dbe4", | |
| border: `1px solid ${on ? "#2f6e42" : "#2b3850"}`, | |
| borderRadius: 8, | |
| padding: "10px 12px", | |
| cursor: "pointer", | |
| fontSize: 13.5, | |
| fontWeight: 600, | |
| textAlign: "left", | |
| }), | |
| knob: (on) => ({ | |
| width: 30, | |
| height: 17, | |
| borderRadius: 99, | |
| background: on ? "#2f6e42" : "#2b3850", | |
| position: "relative", | |
| flexShrink: 0, | |
| transition: "background 160ms ease", | |
| }), | |
| dot: (on) => ({ | |
| position: "absolute", | |
| top: 2, | |
| left: on ? 15 : 2, | |
| width: 13, | |
| height: 13, | |
| borderRadius: 99, | |
| background: on ? "#9ece6a" : "#7d8aa3", | |
| transition: "left 160ms ease", | |
| }), | |
| note: { color: "#5d6b84", fontSize: 11.5, marginTop: 8, lineHeight: 1.5 }, | |
| }; | |
| // Progress through the active layer, written straight to the DOM node. Going | |
| // through React state here would re-render the whole panel sixty times a | |
| // second to move one bar. | |
| function LayerFill({ anim }) { | |
| const ref = useRef(); | |
| useEffect(() => { | |
| let raf; | |
| const tick = () => { | |
| const el = ref.current; | |
| if (el) el.style.transform = `scaleX(${anim.current.progress})`; | |
| raf = requestAnimationFrame(tick); | |
| }; | |
| raf = requestAnimationFrame(tick); | |
| return () => cancelAnimationFrame(raf); | |
| }, [anim]); | |
| return <div ref={ref} style={S.layerFill} />; | |
| } | |
| function ModelTab({ parsed }) { | |
| const { spec } = parsed; | |
| const m = spec.model; | |
| const cells = spec.lattice.cells; | |
| return ( | |
| <> | |
| <div style={S.section}>model</div> | |
| <div style={S.card} className="td-card"> | |
| <div style={S.row}> | |
| <span style={S.key}>model</span> | |
| <b>{m.kind}</b> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>d_model</span> | |
| <span>{m.d_model}</span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>layers</span> | |
| <span>{m.n_layers}</span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>parameters</span> | |
| <span>{fmtParams(m.n_params)}</span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>method</span> | |
| <span>{spec.nd_method.name}</span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>lattice</span> | |
| <span> | |
| {spec.lattice.shape.join(" Γ ") || "β"} | |
| {spec.lattice.time ? " + time" : ""} | |
| </span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>cells</span> | |
| <span> | |
| {cells.present} / {cells.total} | |
| {cells.dense ? "" : " (sparse)"} | |
| </span> | |
| </div> | |
| </div> | |
| {spec.sweeps.unswept_axes.length > 0 && ( | |
| <div style={S.warn}> | |
| <b>never swept:</b> {spec.sweeps.unswept_axes.join(", ")} β these axes | |
| get no mixing. | |
| </div> | |
| )} | |
| <div style={S.section}>directions</div> | |
| <div style={S.card} className="td-card"> | |
| {Object.entries(spec.sweeps.directions).map(([axis, dir]) => ( | |
| <div key={axis} style={S.row}> | |
| <span style={S.key}>{axis}</span> | |
| <span> | |
| {dir === "both" | |
| ? "β and β" | |
| : dir === "forward" | |
| ? "β only" | |
| : "β only"} | |
| </span> | |
| </div> | |
| ))} | |
| {(spec.sweeps.joint_axes ?? []).map((axis) => ( | |
| <div key={axis} style={S.row}> | |
| <span style={S.key}>{axis}</span> | |
| <span style={{ color: "#5eead4" }}>β joint</span> | |
| </div> | |
| ))} | |
| {(spec.sweeps.contracted_axes ?? []).map((axis) => ( | |
| <div key={axis} style={S.row}> | |
| <span style={S.key}>{axis}</span> | |
| {/* A contraction has no direction: the whole axis is mixed at | |
| once, every layer. Listing it as "β only" would invent a | |
| property the model does not have. */} | |
| <span style={{ color: "#c39bd8" }}>β contracted</span> | |
| </div> | |
| ))} | |
| </div> | |
| </> | |
| ); | |
| } | |
| function LayersTab({ | |
| parsed, | |
| layerIndex, | |
| playing, | |
| onTogglePlay, | |
| onSelectLayer, | |
| anim, | |
| }) { | |
| const { spec } = parsed; | |
| const dirGlyph = (r) => (r ? "β" : "β"); | |
| return ( | |
| <> | |
| <div style={S.section}>sweep animation</div> | |
| <div style={{ ...S.controls, marginTop: 0, marginBottom: 8 }}> | |
| <button style={S.btn} className="td-btn" onClick={onTogglePlay}> | |
| {playing ? "βΈ pause" : "βΆ play"} | |
| </button> | |
| <button | |
| style={S.btn} | |
| className="td-btn" | |
| onClick={() => onSelectLayer(layerIndex - 1)} | |
| > | |
| β prev | |
| </button> | |
| <button | |
| style={S.btn} | |
| className="td-btn" | |
| onClick={() => onSelectLayer(layerIndex + 1)} | |
| > | |
| next βΆ | |
| </button> | |
| </div> | |
| {spec.layers.map((l, i) => { | |
| // A kernel-family layer contracts every spatial axis at once and, in | |
| // the hybrid form, sweeps time. Rendering it with the scan family's | |
| // "one axis, one arrow" was drawing sweeps that never happen β the | |
| // spec used to claim them (DEBUG.md #26). | |
| const kernel = l.kind === "kernel"; | |
| const joint = l.kind === "flatten"; | |
| const isTime = !kernel && !joint && latticeAxisOf(l, spec) === null; | |
| return ( | |
| <div | |
| key={i} | |
| style={S.layer(i === layerIndex)} | |
| className="td-row" | |
| onClick={() => onSelectLayer(i)} | |
| > | |
| {i === layerIndex && anim && <LayerFill anim={anim} />} | |
| <span style={{ color: "#8b95a8", width: 28, position: "relative" }}> | |
| L{i} | |
| </span> | |
| {joint ? ( | |
| <> | |
| <b style={{ color: "#5eead4" }}>{(l.axes ?? []).join(" β ")}</b> | |
| <span style={S.key}>{l.tokens ? `${l.tokens} tokens` : ""}</span> | |
| </> | |
| ) : kernel ? ( | |
| <> | |
| <b style={{ color: "#c39bd8" }}> | |
| {(l.contracted ?? []).join(" β ")} | |
| </b> | |
| <span style={S.key}>{l.axis ? `+ ${l.axis} β` : ""}</span> | |
| </> | |
| ) : ( | |
| <> | |
| <b>{l.axis}</b> | |
| <span>{isTime ? "β (causal)" : dirGlyph(l.reverse)}</span> | |
| </> | |
| )} | |
| <span style={{ flex: 1 }} /> | |
| {l.mixer && <span style={S.chip}>{l.mixer}</span>} | |
| </div> | |
| ); | |
| })} | |
| </> | |
| ); | |
| } | |
| function DataTab({ parsed, dataShow, onToggleData }) { | |
| const { spec } = parsed; | |
| const cells = spec.lattice.cells; | |
| const names = (spec.lattice.names ?? []).slice(spec.lattice.time ? 1 : 0); | |
| return ( | |
| <> | |
| <div style={S.section}>cell data</div> | |
| <button style={S.switch(dataShow)} className="td-btn" onClick={onToggleData}> | |
| <span style={S.knob(dataShow)}> | |
| <span style={S.dot(dataShow)} /> | |
| </span> | |
| data_show β label every cell | |
| </button> | |
| <div style={S.note}> | |
| Each cell is labelled with its axis names and indices, and tinted by | |
| which side of the wavefront it is on. Only the cells nearest the camera | |
| get one: a label per cell across a rank-4 lattice is thousands of text | |
| meshes and an unreadable thicket besides. Zoom in to name more of them. | |
| </div> | |
| <div style={S.section}>axes</div> | |
| <div style={S.card} className="td-card"> | |
| {parsed.shape.map((size, i) => ( | |
| <div key={i} style={S.row}> | |
| <span style={S.key}>{names[i] ?? `dim${i}`}</span> | |
| <span>{size}</span> | |
| </div> | |
| ))} | |
| {spec.lattice.time && ( | |
| <div style={S.row}> | |
| <span style={S.key}>time</span> | |
| <span style={{ color: "#8b95a8" }}>dynamic</span> | |
| </div> | |
| )} | |
| </div> | |
| <div style={S.section}>presence</div> | |
| <div style={S.card} className="td-card"> | |
| <div style={S.row}> | |
| <span style={S.key}>present</span> | |
| <span style={{ color: "#9ece6a" }}>{cells.present}</span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>absent</span> | |
| <span style={{ color: cells.dense ? "#8b95a8" : "#e0af68" }}> | |
| {cells.total - cells.present} | |
| </span> | |
| </div> | |
| <div style={S.row}> | |
| <span style={S.key}>total</span> | |
| <span>{cells.total}</span> | |
| </div> | |
| </div> | |
| {!cells.dense && ( | |
| <div style={S.note}> | |
| Absent cells are not drawn at all, and their values can never reach an | |
| output β that is tested bitwise, not asserted. | |
| </div> | |
| )} | |
| </> | |
| ); | |
| } | |
| const TABS = ["model", "layers", "data"]; | |
| export default function Sidebar({ | |
| parsed, | |
| layerIndex, | |
| playing, | |
| onTogglePlay, | |
| onSelectLayer, | |
| samples, | |
| sampleKey, | |
| onSample, | |
| onFile, | |
| anim, | |
| tab, | |
| onTab, | |
| dataShow, | |
| onToggleData, | |
| }) { | |
| const { spec } = parsed; | |
| return ( | |
| <div style={S.panel}> | |
| <div style={S.head}> | |
| <div style={S.h1}>torch-dimensions</div> | |
| <div style={S.sub}>architecture viewer · spec v{spec.version}</div> | |
| <select | |
| style={S.select} | |
| value={sampleKey} | |
| onChange={(e) => onSample(e.target.value)} | |
| > | |
| {Object.keys(samples).map((k) => ( | |
| <option key={k} value={k}> | |
| {k} | |
| </option> | |
| ))} | |
| </select> | |
| <div style={{ margin: "8px 0 12px" }}> | |
| <label | |
| style={{ ...S.btn, display: "inline-block", flex: "none" }} | |
| className="td-btn" | |
| > | |
| open spec JSON | |
| <input | |
| type="file" | |
| accept=".json" | |
| style={{ display: "none" }} | |
| onChange={(e) => e.target.files[0] && onFile(e.target.files[0])} | |
| /> | |
| </label> | |
| </div> | |
| </div> | |
| <div style={S.tabs}> | |
| {TABS.map((t) => ( | |
| <button key={t} style={S.tab(tab === t)} onClick={() => onTab(t)}> | |
| {t} | |
| </button> | |
| ))} | |
| </div> | |
| <div style={S.body} className="td-panel"> | |
| {tab === "model" && <ModelTab parsed={parsed} />} | |
| {tab === "layers" && ( | |
| <LayersTab | |
| parsed={parsed} | |
| layerIndex={layerIndex} | |
| playing={playing} | |
| onTogglePlay={onTogglePlay} | |
| onSelectLayer={onSelectLayer} | |
| anim={anim} | |
| /> | |
| )} | |
| {tab === "data" && ( | |
| <DataTab | |
| parsed={parsed} | |
| dataShow={dataShow} | |
| onToggleData={onToggleData} | |
| /> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |