Files
SuperTensor/references/api.md
T
dela de917a2fbd Harden \stgroup and tighten the callout budget (review follow-up)
- Bracket ink is part of the fit: \st@facebody drops -inkw/-inke extreme
  coordinates and \stface/\ststack register them with the enclosing
  group/col/row fit, so a group outline can no longer be crossed by a
  member's bracket arms
- \stlink inside \stgroup or \stcol is now a package error: sub-flow
  members never terminate a pending connector, so the arrow was dropped
  silently while the label still rendered
- \stgroup requires role= (explicit role=neutral for mixed groups) and
  must bind at least two members or one \stcol partition; a lone stack
  or face inside a group is a dirty-build warning
- lint: default budget is one \stcallout per figure; the
  allow-multiple-callouts directive relaxes it to one per band
- build.sh: clean-build hint no longer names hue budget (lint owns it)
- tests/group-callout.tex reworked: multi-member group with a bracketed
  member as a regression probe, single callout; new negative fixtures
  group-link, group-norole, group-single, callout-budget
- api.md, checklist.md, style.md, layout.md, SKILL.md updated to match
2026-08-05 17:03:14 +08:00

259 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, more
than one callout in the figure, 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= (REQUIRED, hue), pad= (default \stgrouppad)
\stface[role=q]{q1}{}{T}{dh} % "these three heads are q"
\stface[role=q, gap=2mm]{q2}{}{T}{dh}
\stface[role=q, gap=2mm]{q3}{}{T}{dh}
\stgroupend
\stcaption{qg}{$\mathbf q$}{$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. For the same reason `\stlink` **inside** a group is a
package error: a member never terminates a connector, so the arrow would be dropped
silently;
- the padding is reserved on both sides, so the neighbour cannot land tangent to it. The
fit includes decoration ink too: a `bracket=true` member's arms stay inside the outline.
A group must bind **at least two members, or one `\stcol` partition** — around a single
face or stack the outline is decoration, and the package warns. `role=` is required; a
genuinely mixed group passes `role=neutral` explicitly. `\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, never a single face — a card beside one operand reads
as a step in the computation (`layout.md`). The budget is **one callout per figure**: a
card on every band is a dashboard, and the cards are unreadable at thumbnail size anyway.
Both rules are lint errors; `% supertensor-lint: allow-multiple-callouts` relaxes the
budget to one per band for a figure that genuinely needs it. Inside an open band a callout
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`.