# supertensor.sty API ```tex \documentclass[border=10pt]{standalone} \usepackage[cjk]{supertensor} % cjk: ctex + fandol (XeLaTeX). en: English rail labels. ``` Build with `./scripts/build.sh fig.tex` — it puts `assets/` on `TEXINPUTS`, so the package does not need to be installed into your texmf tree. The build first runs `scripts/lint.py`. It rejects ledger changes, untracked absolute objects, repeated anonymous dimensions, raw TikZ rectangles, a formula placed before the last row, an unclosed `\stgroup`, a callout anchored to anything other than a band or a second callout on one band, and more than four active hue families per row. Intentional galleries/tests may put `% supertensor-lint: allow-absolute, allow-missing-formula` near the top; do not add an exemption to a deliverable merely to make it pass. ## Ledgers ```tex \stsetrole{q}{stTeal} % role -> color. Macros take a ROLE, never a color. \stdim{T}{6} % symbolic axis -> physical edge length in cells \stsetlabels{A}{O}{M} % override the three meaning-box rail labels \stsetrail{3.2em} % width of the bold label rail ``` Colors: `stTeal stOrange stCoral stViolet stGray stInk`. An unknown role falls back to gray **and emits a package warning**, which `build.sh` turns into a failed build. Redeclaring an axis or role with the same value is harmless; changing its value emits a warning and keeps the original mapping. Lengths: `\stunit` (one cell, 4.6 mm) and `\sttilegap` (white gutter, 0.5 mm). ## Flow layout **This is the default. Leave the coordinate argument empty and the object is placed by a cursor.** Hand-written offsets are the main source of layout bugs in these figures: every gap becomes a tuned magic number, so a label that grows by two characters silently lands on the next tensor, and two stages started from two different `x` share no rail. ```tex \ststage{SA}{stage heading} % on the left rail, below the previous band \strow{rowA}{T} % open a band, declared height T \stface[role=q]{Q}{}{T}{d} % empty coord = place at the cursor \stglyph{mA}{$\times$} % operator glyph; reserves its own width \stface[role=w]{W}{}{d}{d} \stlink{lA}{softmax} % connector whose LABEL is a flow object \stface[role=s]{S}{}{T}{d} \strowend % fit the band, arm the caption lanes \stcaption{Q}{$\mathbf Q$}{$T\times d$} ``` | macro | does | |---|---| | `\ststage{name}{text}` | stage heading on the left rail, below all ink so far | | `\strow{name}{height}` | open a band; `height` is an axis name or an integer | | `\strowend` | `fit` the band into `name`, then `\stlane` it | | `\stcol{name}{height}` … `\stcolend` | vertical sub-flow filling one slot of the band | | `\stgroup[role=]{name}` … `\stgroupend` | outline naming the objects inside it as one composite | | `\stglyph{name}{$\times$}` | operator glyph (not `\stop` — plain TeX owns that name) | | `\stcomm{name}{All-Reduce}` | collective node | | `\stnode[style]{name}{text}` | any node, placed and measured by the cursor | | `\stlink{name}{label}` | connector; empty label reserves `\stlinklen` of bare arrow | | `\stgap{4mm}` / `\stvgap{4mm}` | one-off extra space, horizontal / vertical | | `\stbbox{all}` | everything drawn so far, as one node, for `\stmeaningbox` | | `\sttopformula{F}{math}` | the formula line, centered on what was actually drawn | | `\sttrack{node}` | fold a hand-placed node into the bbox and the vertical cursor | | `\stleftrail{x}` / `\stlayoutreset` | move the rail / start over | Gaps are declared once: `\stgutter` (6 mm, between objects in a band), `\strowgap` (3.5 mm, above a band), `\stblockgap` (9 mm, above a stage heading), `\stlinklen` (10 mm). Four properties follow by construction, and each of them is a bug class removed: - **The gap belongs to the object that *follows* it**, and the first object in a band gets none — so every band starts flush on the same rail, and `gap=0pt` means *exactly adjacent*, which is how shards are made to tile their parent. - **Every object reserves its own width**, including a stack's offset sheets and a bracket's overhang. `right=6mm of X` reserves nothing, so the next face is free to land on top. - **A connector's label is a flow object**, so a label can never be wider than its arrow. - **A band declares its height**, so an object that does not fit is a *build failure* (`Package supertensor Warning`), not something the reader discovers. Call `\sttopformula` **after** the bands. A formula placed first can only be centered on a figure whose width is not yet known — that is how the top line ends up visibly off-center. `\stcol` is what a split along the contracted axis looks like: ```tex \stcol{W2}{dff} % declared total height \stface[role=r1]{W2a}{}{dffl}{d} \stface[role=r2, gap=0pt]{W2b}{}{dffl}{d} % tiles W2a exactly \stcolend ``` A column that consumes a height other than the one it declares is drawn off-center, so that is a warning too. Absolute placement still works everywhere — pass a coordinate instead of `{}`. Mix freely, but wrap hand-placed nodes in `\sttrack` so the cursor knows about them. ## Groups `\stgroup` … `\stgroupend` is the second sub-flow. It draws a thin rounded outline in the role hue around whatever is placed between them, naming those objects as one composite: ```tex \stgroup[role=q]{qg} % keys: role= (hue), pad= (default \stgrouppad) \ststack[role=q]{qh}{}{T}{dh}{3} % "these three sheets are q" \stgroupend \stcaption{qg}{$\mathbf q$}{$B\times T\times h\times d_h$} ``` The members go **inside** the block, and three properties follow from that: - a group can only wrap *adjacent* objects — one that reached across the band would swallow whatever sat between its members; - the group, not its last member, terminates a pending `\stlink` and sources the next one, so an arrow lands **on** the outline instead of ending inside it and crossing a border that is not its endpoint; - the padding is reserved on both sides, so the neighbour cannot land tangent to it. `\strowend` fits the group, so `\stcaption{qg}{...}` hangs from the caption lane below the outline, not below the member. A group may contain a `\stcol`; it may not sit inside one, and it may not nest. An unclosed group is a lint error. ## Callouts `\stcallout{name}{text width}{band}{title}{body}` hangs a side note card off the right edge of a **finished** band, top-aligned with it. Call it after `\strowend`: ```tex \strowend \stcaption{g}{$\mathbf g$}{$B\times T\times h\times d_k$} \stcallout{n1}{5.2cm}{rowB}{下界化换来了什么}{Kimi Linear 的 $g$ 无下界……} ``` Arg 3 must be a `\strow` band name — one aside per band, and never anchored to a single face. Both rules are lint errors, because a card beside one operand reads as a step in the computation (`layout.md`). Inside an open band it is a package error. A callout pushes the vertical cursor below its own bottom edge. A card taller than its band therefore opens visible white space rather than colliding with the next stage — which is the signal that its text belongs in `\stmeaningbox` instead. ## Faces ```tex \stface[keys]{name}{}{rows}{cols} % flow \stface[keys]{name}{(coord)}{rows}{cols} % absolute \ststack[keys]{name}{(coord)}{rows}{cols}{sheets} \stindexface[keys]{name}{(coord)}{rows}{cols}{entries} ``` `rows`/`cols` accept a declared axis name or a raw integer. A non-empty `(coord)` must include its own parentheses — `{(0,0)}`, `{($(A.east)+(1.5,0)$)}`. `name` becomes a TikZ node you can anchor against; `\ststack` also defines `name-front`. Keys: | key | default | meaning | |---|---|---| | `role=` | `neutral` | hue, via `\stsetrole` | | `pattern=` | `dense` | `solid dense diag band lower upper causal empty data` | | `data=` | — | with `pattern=data`: comma-separated rows, one digit per cell, `0`–`3` = level | | `level=` | `2` | level for `pattern=solid` and the flat level of a mask | | `bracket=` | `false` | thin neutral matrix brackets | | `border=` | `true` | outer `black!60` border | | `tiles=` | `true` | `false` = one flat filled rectangle | | `gap=` | `\stgutter` | flow only: space *before* this object. `gap=0pt` = exactly adjacent | `\stindexface` entries are row-major, `rows*cols` of them; `.` leaves a cell blank. It deliberately has no lightness ramp — see `semantics.md`. The package validates the entry count, pattern name, level range, and every `pattern=data` row's count, width and `0`–`3` domain; any mismatch makes `build.sh` fail. ```tex \stface[role=w, pattern=data, level=3, data={3300,0330,0033,3003,3030,0303}]{D}{(0,0)}{T}{E} \stindexface[role=idx]{I}{(b)}{T}{k}{0,1, 1,2, 2,3, 3,0, 0,2, 1,3} ``` ## Captions `\strowend` calls `\stlane` for you, so in flow mode captions just follow the band: ```tex \strowend \stcaption{A}{$\mathbf A$}{$T\times d$} % symbol lane, shape lane \stcaptiontop{A}{\stshapefont{token}} % occasional label above a face ``` Arming the lane by hand (absolute placement): ```tex \node[inner sep=0pt, fit=(A)(B)(C)] (rowA) {}; \stlane{rowA} \stcaption{A}{$\mathbf A$}{$T\times d$} \stnolane ``` `\stcaption` defines `name-sym` and `name-shape` nodes; anchor the next stage heading against `name-shape.south`. ## Operators, connectors, nodes Prefer `\stglyph` / `\stcomm` / `\stlink` (above). The raw forms are for absolute placement: ```tex \node[st op, right=6mm of A] (m) {$\times$}; % reserves nothing -- see layout.md \node[st comm, right=9mm of P] (ar) {All-Reduce}; \starrow{P.east}{ar.west} \starrowlabel{M.east}{A.west}{softmax} ``` Styles: `st sym st shape st stage st op st note st arrow st comm st brace`. Text helpers: `\stformula \ststagelabel \stoperator \stsymfont \stshapefont \stprose`. Connectors route on the background layer automatically. ## Bottom ```tex \stbbox{all} % flow: everything drawn so far \stmeaningbox{mb}{16.6cm}{all}{axes text}{objects text}{mechanism text} \stsignature{因果多头注意力(掩码 + 拼接投影)}{mb} % optional ``` Arg 2 is the total box width; arg 3 is the node it hangs below. `\stbbox` already contains every caption and top label; if you build the `fit` by hand, include them yourself or the box will overlap them. An empty `{}` row is dropped. `\stsignature` renders only its subject; it has no author or handle mechanism. ## Gotchas - `\ststack` never re-enters `\stface`; if you extend the package, do not pass `role=\st@role` back through pgfkeys — it defines the macro in terms of itself and hangs. - A coordinate expression inside `fit=` needs braces: `fit={(a) ($(b)+(1,0)$)}`. - `\foreach {\macro,...,1}` cannot infer its direction from an unexpanded macro. - `\strole` is expandable on purpose (it is used inside `\edef`); the warning lives in `\stcheckrole`. - Neither the TikZ path parser nor the `calc` library expands a macro sitting where it expects `(`. Every cursor-computed coordinate therefore reaches the parser as literal text, via `\edef ... \noexpand`. Same class of trap: pgfmath cannot digest `\stresolve`'s `\ifcsname`, so a ledger lookup must be pre-resolved with `\edef` before it is measured. - `\sttopformula` puts a group around its argument, which breaks TikZ's own `\\`. For more than one line, wrap the math in amsmath's `gathered`.