Add flow layout: cursor placement, left rail, declared band heights

Figures were positioned by hand-written offsets. Every gap was a magic
number tuned against the content that happened to be there, so a label
that grew two characters landed on the next tensor, and two stages
started from two different x shared no rail. Both failures compile
cleanly.

Replace it with a cursor. Objects placed with an empty coordinate
argument reserve their own width -- including a stack's offset sheets
and a bracket's overhang -- and gaps are declared once (\stgutter,
\strowgap, \stblockgap). The gap belongs to the object that follows it
and the first object in a band gets none, so every band starts flush on
a shared rail and gap=0pt states that two shards tile exactly.

\stlink makes a connector's label a flow object, which is what removes
the label-wider-than-its-arrow failure entirely. \stcol is a vertical
sub-flow for a split along the contracted axis. \strow declares its
height, so an object that does not fit -- or a column that does not add
up to what it declared, and is therefore drawn off-center -- becomes a
package warning, which build.sh fails on.

Absolute placement is unchanged: passing a coordinate takes the original
code path, and \sttrack folds a hand-placed node back into the cursor.

All three golden examples and the new tests/flow.tex are converted and
build clean.
This commit is contained in:
dela
2026-08-05 15:55:21 +08:00
parent 7a22bef9e3
commit 7b59c81d02
11 changed files with 672 additions and 223 deletions
+17 -7
View File
@@ -37,14 +37,14 @@ A minimal figure:
\stdim{d}{4} \stdim{d}{4}
\begin{document}\begin{tikzpicture} \begin{document}\begin{tikzpicture}
\stface[role=act, bracket=true]{X}{(0,0)}{T}{d} \ststage{S1}{one band, placed by cursor}
\node[st op, right=6mm of X] (m) {$\times$}; \strow{row}{T} % band height, declared once
\stface[role=w]{W}{($(m)+(1.4,0)$)}{d}{d} \stface[role=act, bracket=true]{X}{}{T}{d} % empty coord = at the cursor
\node[inner sep=0pt, fit=(X)(W)] (row) {}; \stglyph{m}{$\times$}
\stlane{row} \stface[role=w]{W}{}{d}{d}
\strowend
\stcaption{X}{$\mathbf X$}{$T\times d$} \stcaption{X}{$\mathbf X$}{$T\times d$}
\stcaption{W}{$\mathbf W$}{$d\times d$} \stcaption{W}{$\mathbf W$}{$d\times d$}
\stnolane
\end{tikzpicture}\end{document} \end{tikzpicture}\end{document}
``` ```
@@ -52,13 +52,19 @@ Because `T` and `d` come from the ledger, the contracted axis is automatically o
length in both operands, `d×d` is automatically square, and any other face of shape `T×d` length in both operands, `d×d` is automatically square, and any other face of shape `T×d`
in the figure is automatically identical to `X`. in the figure is automatically identical to `X`.
Because the coordinates are empty, each object reserves its own width and the gap between
them is `\stgutter`, declared once. Nothing here is a tuned offset, so growing a label can
only push its neighbours apart — it can never land on top of one. And the band declares its
height, so an object that does not fit is a failed build rather than something the reader
discovers.
See `references/api.md` for the full macro list. See `references/api.md` for the full macro list.
## Examples ## Examples
| file | shows | | file | shows |
|---|---| |---|---|
| `tp-ffn-allreduce.tex` | column-then-row sharding, exact tiling, one hue per TP rank, a collective as a real node | | `tp-ffn-allreduce.tex` | column-then-row sharding, exact tiling, one hue per TP rank, a collective as a real node, a `\stcol` split along the contracted axis |
| `mha-causal.tex` | leading axes as stack depth, a physically swapped `Kᵀ`, a mask in a different grammar from the scores it gates | | `mha-causal.tex` | leading axes as stack depth, a physically swapped `Kᵀ`, a mask in a different grammar from the scores it gates |
| `moe-topk-gather.tex` | scores → indices → Boolean support → gather, with all three cell grammars side by side | | `moe-topk-gather.tex` | scores → indices → Boolean support → gather, with all three cell grammars side by side |
| `antipatterns.tex` | four figures that compile cleanly and still teach something false | | `antipatterns.tex` | four figures that compile cleanly and still teach something false |
@@ -77,6 +83,10 @@ Two LaTeX warnings produce a figure that is quietly wrong rather than visibly br
greps for both and exits non-zero. A `Package supertensor Warning` — an undeclared role greps for both and exits non-zero. A `Package supertensor Warning` — an undeclared role
falling back to gray — is treated the same way. falling back to gray — is treated the same way.
The flow layout adds two of its own: an object that overflows its band, and a `\stcol`
whose contents do not add up to the height it declared (which means it is drawn
off-center). Both are things a reader would have to notice for you.
A clean build still proves nothing about collisions, hue budget or whether the math is A clean build still proves nothing about collisions, hue budget or whether the math is
right. That is what `references/checklist.md` is for. right. That is what `references/checklist.md` is for.
+8 -4
View File
@@ -29,8 +29,10 @@ A figure built from raw TikZ has to re-earn every invariant by hand and usually
- *geometry* — one `\stdim{axis}{cells}` per symbolic axis, one `\stsetrole{role}{color}` - *geometry* — one `\stdim{axis}{cells}` per symbolic axis, one `\stsetrole{role}{color}`
per tensor role. Declaring these makes the invariants automatic. per tensor role. Declaring these makes the invariants automatic.
See `references/geometry.md`. See `references/geometry.md`.
4. **Pick the smallest grammar** that exposes the mechanism (see below), then reserve 4. **Pick the smallest grammar** that exposes the mechanism (see below), then draw with the
stage lanes and draw. See `references/layout.md` and `references/api.md`. flow layout — `\ststage` / `\strow` … `\strowend`, empty coordinate arguments, gaps
declared once. Reach for an absolute coordinate only when no band can express the
placement. See `references/api.md` and `references/layout.md`.
5. **Build and audit.** `./scripts/build.sh fig.tex`. A clean build only proves TeX was 5. **Build and audit.** `./scripts/build.sh fig.tex`. A clean build only proves TeX was
happy; then run the visual audit in `references/checklist.md` against the PNG at full happy; then run the visual audit in `references/checklist.md` against the PNG at full
size and at thumbnail size. Redraw on any mandatory-invariant violation. size and at thumbnail size. Redraw on any mandatory-invariant violation.
@@ -47,7 +49,8 @@ shape or convention you inferred.
| sharding, device ownership, channel groups | adjacent faces tiling a parent | two `\stface` calls, one role each | | sharding, device ownership, channel groups | adjacent faces tiling a parent | two `\stface` calls, one role each |
| leading axes (`B`, `h`) | depth | `\ststack{...}{sheets}` | | leading axes (`B`, `h`) | depth | `\ststack{...}{sheets}` |
| discrete choices (indices, token ids, expert ids) | symbols in cells, no ramp | `\stindexface` | | discrete choices (indices, token ids, expert ids) | symbols in cells, no ramp | `\stindexface` |
| data movement, collectives, non-linear ops | arrows and nodes | `\starrow`, `\starrowlabel`, `st comm` | | data movement, collectives, non-linear ops | arrows and nodes | `\stlink`, `\stcomm` |
| a split along the contracted axis | a vertical pair inside one band slot | `\stcol` … `\stcolend` |
Combine grammars only when each one adds information. Known zeros stay unfilled; masks, Combine grammars only when each one adds information. Known zeros stay unfilled; masks,
diagonals, sparsity and partitions must encode their exact structure. diagonals, sparsity and partitions must encode their exact structure.
@@ -65,7 +68,8 @@ reference file with the full statement and the failure it prevents.
from continuous score to discrete index to gathered value. from continuous score to discrete index to gathered value.
- **Layout** (`references/layout.md`) — everything is a bounding box; tangency counts as - **Layout** (`references/layout.md`) — everything is a bounding box; tangency counts as
collision. Reserved lanes for stage heading / tensors / symbols / shapes. Connectors on collision. Reserved lanes for stage heading / tensors / symbols / shapes. Connectors on
the background layer, text on the foreground layer. the background layer, text on the foreground layer. Place by cursor, not by hand-tuned
coordinate: a magic-number offset is only valid for the content it was tuned against.
- **Style** (`references/style.md`) — muted palette, one hue per role, ≤4 active hues per - **Style** (`references/style.md`) — muted palette, one hue per role, ≤4 active hues per
row plus gray, three separated lightness levels, non-periodic texture, no decoration. row plus gray, three separated lightness levels, non-periodic texture, no decoration.
+287 -10
View File
@@ -92,6 +92,247 @@
line width=0.4pt}, line width=0.4pt},
} }
% --------------------------------------------------------- flow layout ------
% Hand-placed absolute coordinates are the main source of layout bugs in these
% figures. Every gap ends up 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. The cursor below removes both by construction:
% gaps are declared once, positions are derived from the objects actually
% drawn. Absolute placement still works -- pass a coordinate instead of `{}'.
%
% \ststage{S1}{stage heading} % anchored on the left rail
% \strow{R1}{T} % band of height T units
% \stface[role=q]{Q}{}{T}{d} % empty coord = place at the cursor
% \stglyph{m}{$\times$}
% \stface[role=w]{W}{}{d}{d}
% \stcol{C}{2*d units} % a vertical pair in one band slot
% \stface[role=r1]{Ca}{}{d}{d}
% \stface[role=r2, gap=0pt]{Cb}{}{d}{d}
% \stcolend
% \strowend % fits the band, arms \stlane
\newlength{\stgutter}\setlength{\stgutter}{6mm} % between objects in a band
\newlength{\strowgap}\setlength{\strowgap}{3.5mm} % above a band
\newlength{\stblockgap}\setlength{\stblockgap}{9mm} % above a stage heading
\newlength{\stlinklen}\setlength{\stlinklen}{10mm} % bare connector reservation
\newlength{\st@railx}\newlength{\st@cx}\newlength{\st@cy}
\newlength{\st@ycur}\newlength{\st@bandh}\newlength{\st@tmpx}\newlength{\st@tmpy}
% Column sub-flow state. Named st@v* rather than st@col*: \st@cols and \st@col
% are already the column count and the resolved hue of a face.
\newlength{\st@vx}\newlength{\st@vy}\newlength{\st@vw}\newlength{\st@vtop}
\newlength{\st@vh}
\newif\ifst@inrow
\newif\ifst@incol
\newif\ifst@first
\newcommand{\stlayoutreset}{%
\global\st@railx=0pt \global\st@ycur=0pt
\global\st@cx=0pt \global\st@cy=0pt \global\st@bandh=0pt
\global\st@inrowfalse \global\st@incolfalse
\gdef\st@rowlist{}\gdef\st@alllist{}\gdef\st@rowname{}%
\gdef\st@collist{}\gdef\st@colname{}%
\gdef\st@lastnode{}\gdef\st@linklabel{}\gdef\st@linkprev{}}
\stlayoutreset
\newcommand{\stleftrail}[1]{\global\st@railx=\dimexpr#1\relax}
\newcommand{\stvgap}[1]{\global\advance\st@ycur by -\dimexpr#1\relax}
% The vertical cursor tracks the lowest ink so far, so the next band never has
% to be positioned by eye.
\newcommand{\st@lower}[1]{%
\pgfextracty{\st@tmpy}{\pgfpointanchor{#1}{south}}%
\ifdim\st@tmpy<\st@ycur \global\st@ycur=\st@tmpy \fi}
\newcommand{\st@regall}[1]{\xdef\st@alllist{\st@alllist(#1)}}
% Inside a column the items belong to the column, not to the band: they must not
% terminate a pending connector, or the arrow would land on the first sheet of
% the stack instead of on the column as a whole.
\newcommand{\st@regrow}[1]{%
\ifst@incol
\xdef\st@collist{\st@collist(#1)}\st@regall{#1}%
\else
\xdef\st@rowlist{\st@rowlist(#1)}\st@regall{#1}%
\st@drawpendinglink{#1}%
\gdef\st@lastnode{#1}%
\fi}
\newcommand{\st@needrow}[1]{%
\ifst@inrow\else
\PackageError{supertensor}{\string#1\space needs an open \string\strow}%
{Flow placement only works between \string\strow\space and
\string\strowend. Pass an explicit coordinate instead.}%
\fi}
% The gap belongs to the object that follows it, and the first object in a band
% gets none -- that is what puts every band's left edge on the same rail.
\newcommand{\st@leadgap}[1]{%
\ifst@first
\global\st@firstfalse
\else
\ifst@incol\global\advance\st@vy by -\dimexpr#1\relax
\else\global\advance\st@cx by \dimexpr#1\relax\fi
\fi}
% Reserve #1 of horizontal space, hand back the center coordinate for it.
\newcommand{\st@flow}[1]{%
\dimen0=\dimexpr#1\relax
\dimen2=\st@cx \advance\dimen2 by 0.5\dimen0
\edef\st@pos{(\the\dimen2,\the\st@cy)}%
\global\advance\st@cx by \dimen0}
% Reserve #2 of vertical space inside a column, hand back the center for it.
% Items are centered on the column's own axis, so a narrow shard sits under a
% wide one without an eyeballed offset.
\newcommand{\st@vflow}[2]{%
\dimen0=\dimexpr#1\relax \dimen4=\dimexpr#2\relax
\dimen2=\st@vx \advance\dimen2 by 0.5\dimen0
\dimen6=\st@vy \advance\dimen6 by -0.5\dimen4
\edef\st@pos{(\the\dimen2,\the\dimen6)}%
\global\advance\st@vy by -\dimen4
\ifdim\dimen0>\st@vw \global\st@vw=\dimen0 \fi}
% A band declares its height, so an object that does not fit is a build
% failure rather than something the reader discovers.
\newcommand{\st@checkh}[2]{%
\dimen0=\dimexpr#2\relax \advance\dimen0 by -\st@bandh
\ifdim\dimen0>4mm
\PackageWarning{supertensor}{Object `#1' overflows its band by
\the\dimen0. Raise the height declared in \string\strow\space or give it
its own band}%
\fi}
\newcommand{\ststage}[2]{%
\dimen0=\st@ycur \advance\dimen0 by -\stblockgap
\node[st stage, anchor=north west] (#1) at (\the\st@railx,\the\dimen0) {#2};
\st@regall{#1}\st@lower{#1}}
% \strow{name}{height in axis units or a declared axis}
\newcommand{\strow}[2]{%
\gdef\st@rowlist{}\gdef\st@rowname{#1}\gdef\st@lastnode{}%
\gdef\st@linklabel{}\gdef\st@linkprev{}%
\global\st@inrowtrue\global\st@firsttrue
% Resolve through the ledger first: pgfmath's parser cannot digest
% \stresolve's \ifcsname on its own.
\edef\st@bandu{\stresolve{#2}}%
\pgfmathsetlengthmacro{\st@bh}{\st@bandu*\stunit}%
\global\st@bandh=\st@bh
\global\st@cx=\st@railx
\dimen0=\st@ycur \advance\dimen0 by -\strowgap \advance\dimen0 by -0.5\st@bandh
\global\st@cy=\dimen0}
\newcommand{\strowend}{%
\ifdefempty{\st@rowlist}%
{\PackageWarning{supertensor}{Empty \string\strow\space `\st@rowname'}}%
{\edef\st@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
fit={\st@rowlist}] (\st@rowname) {};}\st@do
\stlane{\st@rowname}\st@regall{\st@rowname}\st@lower{\st@rowname}}%
\global\st@inrowfalse}
% \stcol{name}{height} ... \stcolend -- a vertical sub-flow occupying one slot
% of the band. This is what a split along a shared axis looks like: two shards
% stacked with gap=0pt tile their parent exactly, which no pair of hand-tuned
% y offsets can guarantee.
\newcommand{\stcol}[2]{%
\st@needrow{\stcol}%
\ifst@incol
\PackageError{supertensor}{Nested \string\stcol}%
{Close the open column with \string\stcolend\space first.}%
\fi
\st@leadgap{\stgutter}%
\gdef\st@collist{}\gdef\st@colname{#1}%
\global\st@vx=\st@cx \global\st@vw=0pt
\edef\st@colu{\stresolve{#2}}%
\pgfmathsetlengthmacro{\st@ch}{\st@colu*\stunit}%
\global\st@vh=\st@ch
\dimen0=\st@cy \advance\dimen0 by 0.5\st@vh
\global\st@vtop=\dimen0 \global\st@vy=\dimen0
\global\st@incoltrue\global\st@firsttrue}
\newcommand{\stcolend}{%
\ifdefempty{\st@collist}%
{\PackageWarning{supertensor}{Empty \string\stcol\space `\st@colname'}%
\global\st@incolfalse}%
{\dimen0=\st@vtop \advance\dimen0 by -\st@vy % height actually consumed
\global\st@incolfalse
\edef\st@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
fit={\st@collist}] (\st@colname) {};}\st@do
\global\advance\st@cx by \st@vw
\global\st@firstfalse
% The column is centered on the band using its DECLARED height, so a
% mismatch is not just an overflow -- it is a column drawn off-center.
\dimen2=\dimen0 \advance\dimen2 by -\st@vh
\ifdim\dimen2<0pt \dimen2=-\dimen2 \fi
\ifdim\dimen2>2mm
\PackageWarning{supertensor}{Column `\st@colname' consumes
\the\dimen0\space but declares \the\st@vh; it is drawn off-center.
Fix the height in \string\stcol}%
\fi
\st@checkh{\st@colname}{\the\dimen0}%
\st@regrow{\st@colname}}}
% A node in the flow: an operator glyph, a collective, a note. It reserves its
% own width, which is the whole point -- `right=6mm of X' reserves nothing, so
% the next face is free to land on top of it.
\newcommand{\stnode}[3][]{%
\st@needrow{\stnode}\st@leadgap{\stgutter}%
\ifst@incol
\node[#1, anchor=north west] (#2) at (\the\st@vx,\the\st@vy) {#3};
\pgfextractx{\st@tmpx}{\pgfpointanchor{#2}{east}}%
\advance\st@tmpx by -\st@vx
\ifdim\st@tmpx>\st@vw \global\st@vw=\st@tmpx \fi
\pgfextracty{\st@tmpy}{\pgfpointanchor{#2}{south}}%
\global\st@vy=\st@tmpy
\else
\node[#1, anchor=west] (#2) at (\the\st@cx,\the\st@cy) {#3};
\pgfextractx{\st@tmpx}{\pgfpointanchor{#2}{east}}%
\global\st@cx=\st@tmpx
\fi
\st@regrow{#2}}
% Not \stop: that name is already taken by plain TeX.
\newcommand{\stglyph}[2]{\stnode[st op]{#1}{#2}}
\newcommand{\stcomm}[2]{\stnode[st comm]{#1}{#2}}
\newcommand{\stgap}[1]{\global\advance\st@cx by \dimexpr#1\relax}
% \stlink{name}{label} -- a connector whose LABEL is a flow object. The label
% reserves its own width and the arrows are drawn to the neighbours once the
% right-hand one exists, so a label can never be wider than its connector.
\newcommand{\stlink}[2]{%
\st@needrow{\stlink}%
\xdef\st@linkprev{\st@lastnode}%
\ifblank{#2}%
{\gdef\st@linklabel{}\stgap{\stlinklen}}%
{\st@leadgap{\stgutter}%
\node[st note, anchor=west, inner sep=1pt] (#1) at (\the\st@cx,\the\st@cy) {#2};
\pgfextractx{\st@tmpx}{\pgfpointanchor{#1}{east}}%
\global\st@cx=\st@tmpx
\gdef\st@linklabel{#1}\st@regall{#1}}}
\newcommand{\st@drawpendinglink}[1]{%
\ifdefempty{\st@linkprev}{}{%
\begin{pgfonlayer}{stbg}
\ifdefempty{\st@linklabel}%
{\draw[st arrow] (\st@linkprev.east) -- (#1.west);}%
{\draw[st arrow] (\st@linkprev.east) -- (\st@linklabel.west);
\draw[st arrow] (\st@linklabel.east) -- (#1.west);}%
\end{pgfonlayer}
\gdef\st@linkprev{}\gdef\st@linklabel{}}}
% Everything drawn through the flow, as one node -- for the meaning box anchor.
\newcommand{\stbbox}[1]{%
\edef\st@do{\noexpand\node[inner sep=0pt, outer sep=0pt,
fit={\st@alllist}] (#1) {};}\st@do}
% Escape hatch: fold a hand-placed node into the bounding box and push the
% vertical cursor below it, so the next band still knows where the ink ends.
\newcommand{\sttrack}[1]{\st@regall{#1}\st@lower{#1}}
% \sttopformula{name}{math} -- the formula line, centered on everything drawn
% so far. Call it AFTER the bands: a formula placed first can only be centered
% on a figure whose width is not known yet, which is how the top line ends up
% visibly off-center.
\newcommand{\sttopformula}[2]{%
\stbbox{st@bbt}%
% For more than one line, wrap the math in amsmath's `gathered': TikZ's own
% \\ handling does not survive the group \stformula puts around the content.
\node[above=6mm of st@bbt, anchor=south] (#1) {\stformula{#2}};
\st@regall{#1}}
% ------------------------------------------------------------- faces -------- % ------------------------------------------------------------- faces --------
\newif\ifst@bracket \newif\ifst@bracket
\newif\ifst@border \newif\ifst@border
@@ -102,10 +343,13 @@
pattern/.store in=\st@pattern, pattern/.store in=\st@pattern,
data/.store in=\st@data, data/.store in=\st@data,
level/.store in=\st@level, level/.store in=\st@level,
gap/.store in=\st@gap,
bracket/.is if=st@bracket, bracket/.is if=st@bracket,
border/.is if=st@border, border/.is if=st@border,
tiles/.is if=st@tiles, tiles/.is if=st@tiles,
role=neutral, pattern=dense, data={}, level=2, % gap= is the space BEFORE this object, replacing the standing gutter.
% gap=0pt is how shards are made to tile their parent exactly.
role=neutral, pattern=dense, data={}, level=2, gap=\stgutter,
bracket=false, border=true, tiles=true, bracket=false, border=true, tiles=true,
} }
@@ -153,17 +397,36 @@
% `role=\st@role' back through pgfkeys would define \st@role in terms of % `role=\st@role' back through pgfkeys would define \st@role in terms of
% itself and hang the run. % itself and hang the run.
\newcommand{\st@facecore}[2]{% \newcommand{\st@facecore}[2]{%
\node[inner sep=0pt, outer sep=0pt, minimum width=\st@w, minimum height=\st@h] \st@basenode{#1}{#2}%
(#1) at #2 {};
\st@facebody{#1}} \st@facebody{#1}}
% Shared entry for cursor placement: reserve the width, check the band.
\newcommand{\st@flowbegin}[3]{%
\st@needrow{\stface}\st@leadgap{\st@gap}%
\ifst@incol \st@vflow{#2}{#3}\else \st@flow{#2}\st@checkh{#1}{#3}\fi}
% Neither the TikZ path parser nor calc expands a macro sitting where it
% expects `('. Everything the cursor computes therefore goes through \edef and
% reaches the parser as literal text.
\newcommand{\st@coordat}[2]{\coordinate (#1) at #2;}
\newcommand{\st@basenode}[2]{%
\node[inner sep=0pt, outer sep=0pt, minimum width=\st@w, minimum height=\st@h]
(#1) at #2 {};}
% \stface[keys]{name}{center coord}{rows}{cols} % \stface[keys]{name}{center coord}{rows}{cols}
% rows/cols accept a declared axis name or a raw integer. Height <- rows, % rows/cols accept a declared axis name or a raw integer. Height <- rows,
% width <- cols, always: a matrix face a x b never renders sideways. % width <- cols, always: a matrix face a x b never renders sideways.
% An empty coord places the face at the row cursor. The bracket decoration is
% drawn outside the node box, so its 3.4mm on each side is reserved too.
\newcommand{\stface}[5][]{% \newcommand{\stface}[5][]{%
\begingroup \begingroup
\st@setup{#1}{#4}{#5}% \st@setup{#1}{#4}{#5}%
\st@facecore{#2}{#3}% \ifblank{#3}{%
\ifst@bracket\pgfmathsetlengthmacro{\st@tw}{\st@w+6.8mm}\else\let\st@tw\st@w\fi
\st@flowbegin{#2}{\st@tw}{\st@h}%
\edef\st@do{\noexpand\st@facecore{#2}{\st@pos}}\st@do
\st@regrow{#2}%
}{\st@facecore{#2}{#3}}%
\endgroup} \endgroup}
% \stindexface[keys]{name}{center coord}{rows}{cols}{entries} % \stindexface[keys]{name}{center coord}{rows}{cols}{entries}
@@ -175,8 +438,11 @@
\newcommand{\stindexface}[6][]{% \newcommand{\stindexface}[6][]{%
\begingroup \begingroup
\st@setup{#1}{#4}{#5}% \st@setup{#1}{#4}{#5}%
\node[inner sep=0pt, outer sep=0pt, minimum width=\st@w, minimum height=\st@h] \ifblank{#3}%
(#2) at #3 {}; {\ifst@bracket\pgfmathsetlengthmacro{\st@tw}{\st@w+6.8mm}\else\let\st@tw\st@w\fi
\st@flowbegin{#2}{\st@tw}{\st@h}%
\edef\st@do{\noexpand\st@basenode{#2}{\st@pos}}\st@do}%
{\st@basenode{#2}{#3}}%
\foreach \st@e [count=\st@z from 0] in {#6} {% \foreach \st@e [count=\st@z from 0] in {#6} {%
\pgfmathtruncatemacro{\st@ii}{div(\st@z,\st@cols)+1}% \pgfmathtruncatemacro{\st@ii}{div(\st@z,\st@cols)+1}%
\pgfmathtruncatemacro{\st@jj}{mod(\st@z,\st@cols)+1}% \pgfmathtruncatemacro{\st@jj}{mod(\st@z,\st@cols)+1}%
@@ -196,6 +462,7 @@
\draw[draw=black!60, line width=0.5pt, rounded corners=1pt] \draw[draw=black!60, line width=0.5pt, rounded corners=1pt]
(#2.south west) rectangle (#2.north east); (#2.south west) rectangle (#2.north east);
\fi \fi
\ifblank{#3}{\st@regrow{#2}}{}%
\endgroup} \endgroup}
\newcommand{\st@facebody}[1]{% \newcommand{\st@facebody}[1]{%
@@ -250,9 +517,17 @@
\st@setup{#1}{#4}{#5}% \st@setup{#1}{#4}{#5}%
\pgfmathtruncatemacro{\st@back}{#6-1}% \pgfmathtruncatemacro{\st@back}{#6-1}%
\pgfmathsetlengthmacro{\st@dx}{1.3mm}% \pgfmathsetlengthmacro{\st@dx}{1.3mm}%
\coordinate (#2-c) at ($#3+(-0.5*\st@back*\st@dx,-0.5*\st@back*\st@dx)$); % The offset sheets are part of the object: reserve their extent too, or the
\node[inner sep=0pt, outer sep=0pt, minimum width=\st@w, minimum height=\st@h] % neighbour is spaced against the front sheet and lands on the back ones.
(#2-front) at (#2-c) {}; \ifblank{#3}%
{\pgfmathsetlengthmacro{\st@tw}{\st@w+\st@back*\st@dx}%
\pgfmathsetlengthmacro{\st@th}{\st@h+\st@back*\st@dx}%
\ifst@bracket\pgfmathsetlengthmacro{\st@tw}{\st@tw+6.8mm}\fi
\st@flowbegin{#2}{\st@tw}{\st@th}%
\edef\st@do{\noexpand\st@coordat{#2-c}%
{($\st@pos+(-0.5*\st@back*\st@dx,-0.5*\st@back*\st@dx)$)}}\st@do}%
{\coordinate (#2-c) at ($#3+(-0.5*\st@back*\st@dx,-0.5*\st@back*\st@dx)$);}%
\st@basenode{#2-front}{(#2-c)}%
\ifnum\st@back>0 \ifnum\st@back>0
% Ascending loop, descending index: `{\macro,...,1}' cannot infer its % Ascending loop, descending index: `{\macro,...,1}' cannot infer its
% direction from an unexpanded macro and runs away. % direction from an unexpanded macro and runs away.
@@ -269,6 +544,7 @@
\node[inner sep=0pt, outer sep=0pt, \node[inner sep=0pt, outer sep=0pt,
fit={(#2-front) ($(#2-front.north east)+(\st@back*\st@dx,\st@back*\st@dx)$)}] fit={(#2-front) ($(#2-front.north east)+(\st@back*\st@dx,\st@back*\st@dx)$)}]
(#2) {}; (#2) {};
\ifblank{#3}{\st@regrow{#2}}{}%
\endgroup} \endgroup}
% ------------------------------------------------- symbol / shape captions --- % ------------------------------------------------- symbol / shape captions ---
@@ -286,7 +562,8 @@
{\node[st sym, below=1.6mm of #1] (#1-sym) {#2};}% {\node[st sym, below=1.6mm of #1] (#1-sym) {#2};}%
{\node[st sym, anchor=north] {\node[st sym, anchor=north]
at ($(#1.center |- \st@lane.south)+(0,-1.6mm)$) (#1-sym) {#2};}% at ($(#1.center |- \st@lane.south)+(0,-1.6mm)$) (#1-sym) {#2};}%
\node[st shape, below=0.6mm of #1-sym] (#1-shape) {#3};} \node[st shape, below=0.6mm of #1-sym] (#1-shape) {#3};%
\st@regall{#1-shape}\st@lower{#1-shape}}
\newcommand{\stcaptiontop}[2]{% \newcommand{\stcaptiontop}[2]{%
\node[st sym, above=1.6mm of #1] (#1-top) {#2};} \node[st sym, above=1.6mm of #1] (#1-top) {#2};}
+39 -51
View File
@@ -2,6 +2,7 @@
% Shows: leading axes as stack depth, a transpose that physically swaps the % Shows: leading axes as stack depth, a transpose that physically swaps the
% face, equal edge length on the contracted axis, and a Boolean mask drawn in % face, equal edge length on the contracted axis, and a Boolean mask drawn in
% a different grammar from the scores it gates. % a different grammar from the scores it gates.
% Layout is entirely by cursor: no absolute coordinate appears below.
% ../scripts/build.sh mha-causal.tex % ../scripts/build.sh mha-causal.tex
\documentclass[border=10pt]{standalone} \documentclass[border=10pt]{standalone}
\usepackage[cjk]{supertensor} \usepackage[cjk]{supertensor}
@@ -19,79 +20,66 @@
\begin{document} \begin{document}
\begin{tikzpicture} \begin{tikzpicture}
% ================================================================= formula ==
\node (F) at (0,0) {\stformula{$\displaystyle
\mathbf A^{(i)}=\mathrm{softmax}\!\left(
\frac{\mathbf Q^{(i)}\mathbf K^{(i)\top}}{\sqrt{d_h}}+\mathbf M\right),\qquad
\mathbf O^{(i)}=\mathbf A^{(i)}\mathbf V^{(i)}$}};
% ============================================================ stage A row === % ============================================================ stage A row ===
\node[st stage, below=7mm of F] (SA) {每头打分:沿 $d_h$ 收缩}; \ststage{SA}{每头打分:沿 $d_h$ 收缩}
\coordinate (a) at ($(SA)+(-3.9,-1.9)$); \strow{rowA}{T}
\ststack[role=q, bracket=true]{Q}{}{T}{dh}{3}
\ststack[role=q, bracket=true]{Q}{(a)}{T}{dh}{3} \stglyph{mA}{$\times$}
\node[st op, right=6mm of Q] (mA) {$\times$};
% K^T: the face is physically swapped, not relabelled. Its height equals Q's % K^T: the face is physically swapped, not relabelled. Its height equals Q's
% width -- that is the contracted axis d_h, drawn at one edge length. % width -- that is the contracted axis d_h, drawn at one edge length.
\ststack[role=k, bracket=true]{KT}{($(mA)+(1.9,0)$)}{dh}{T}{3} \ststack[role=k, bracket=true]{KT}{}{dh}{T}{3}
\node[st op, right=6mm of KT] (eA) {$=$}; \stglyph{eA}{$=$}
\ststack[role=s]{S}{($(eA)+(2.0,0)$)}{T}{T}{3} \ststack[role=s]{S}{}{T}{T}{3}
\strowend
\node[inner sep=0pt, fit=(Q)(KT)(S)] (rowA) {};
\stlane{rowA}
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$} \stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
\stcaption{KT}{$\mathbf K^{(i)\top}$}{$h\times d_h\times T$} \stcaption{KT}{$\mathbf K^{(i)\top}$}{$h\times d_h\times T$}
\stcaption{S}{$\mathbf S^{(i)}$}{$h\times T\times T$} \stcaption{S}{$\mathbf S^{(i)}$}{$h\times T\times T$}
\stnolane
% ============================================================ stage B row === % ============================================================ stage B row ===
\node[st stage, below=9mm of Q-shape.south west, anchor=north west] (SB) \ststage{SB}{因果掩码与加权求和}
{因果掩码与加权求和}; \strow{rowB}{T}
\coordinate (b) at ($(SB)+(0.6,-2.0)$);
% The mask is a Boolean support, not a magnitude: one flat level, exact % The mask is a Boolean support, not a magnitude: one flat level, exact
% triangle, no stack -- it is shared by every head. % triangle, no stack -- it is shared by every head.
\stface[role=w, pattern=data, \stface[role=w, pattern=data,
data={300000,330000,333000,333300,333330,333333}]{M}{(b)}{T}{T} data={300000,330000,333000,333300,333330,333333}]{M}{}{T}{T}
\ststack[role=s, pattern=causal]{A}{($(M.east)+(3.75,0)$)}{T}{T}{3} \stlink{lB}{softmax}
\starrowlabel{M.east}{A.west}{softmax} \ststack[role=s, pattern=causal]{A}{}{T}{T}{3}
\node[st op, right=6mm of A] (mB) {$\times$}; \stglyph{mB}{$\times$}
\ststack[role=v, bracket=true]{V}{($(mB)+(1.4,0)$)}{T}{dh}{3} \ststack[role=v, bracket=true]{V}{}{T}{dh}{3}
\node[st op, right=6mm of V] (eB) {$=$}; \stglyph{eB}{$=$}
\ststack[role=v]{O}{($(eB)+(1.4,0)$)}{T}{dh}{3} \ststack[role=v]{O}{}{T}{dh}{3}
\strowend
\node[inner sep=0pt, fit=(M)(A)(V)(O)] (rowB) {};
\stlane{rowB}
\stcaption{M}{$\mathbf M$}{$T\times T$} \stcaption{M}{$\mathbf M$}{$T\times T$}
\stcaption{A}{$\mathbf A^{(i)}$}{$h\times T\times T$} \stcaption{A}{$\mathbf A^{(i)}$}{$h\times T\times T$}
\stcaption{V}{$\mathbf V^{(i)}$}{$h\times T\times d_h$} \stcaption{V}{$\mathbf V^{(i)}$}{$h\times T\times d_h$}
\stcaption{O}{$\mathbf O^{(i)}$}{$h\times T\times d_h$} \stcaption{O}{$\mathbf O^{(i)}$}{$h\times T\times d_h$}
\stnolane
% ============================================================ stage C row === % ============================================================ stage C row ===
\node[st stage, below=9mm of M-shape.south west, anchor=north west] (SC) \ststage{SC}{沿 $d_h$ 拼接后投影}
{沿 $d_h$ 拼接后投影}; \strow{rowC}{d} % the d x d projection is the tallest object here
\coordinate (c) at ($(SC)+(1.2,-2.0)$); % Concatenation reverses the split: gap=0pt makes the three h-shards of width
% d_h tile a face of width d exactly, with no eyeballed offset.
% Concatenation reverses the split: three h-shards of width d_h tile a face of \stface[role=v]{C1}{}{T}{dh}
% width d exactly. \stface[role=v, gap=0pt]{C2}{}{T}{dh}
\stface[role=v]{C1}{(c)}{T}{dh} \stface[role=v, gap=0pt]{C3}{}{T}{dh}
\stface[role=v]{C2}{($(C1.east)+(1.5*\stunit,0)$)}{T}{dh} \stglyph{mC}{$\times$}
\stface[role=v]{C3}{($(C2.east)+(1.5*\stunit,0)$)}{T}{dh} \stface[role=w]{WO}{}{d}{d}
\node[st op, right=6mm of C3] (mC) {$\times$}; \stglyph{eC}{$=$}
\stface[role=w]{WO}{($(mC)+(2.5,0)$)}{d}{d} \stface[role=v, bracket=true]{Y}{}{T}{d}
\node[st op, right=6mm of WO] (eC) {$=$}; \strowend
\stface[role=v, bracket=true]{Y}{($(eC)+(2.5,0)$)}{T}{d}
\node[inner sep=0pt, fit=(C1)(WO)(Y)] (rowC) {};
\stlane{rowC}
\stcaption{C2}{$[\,\mathbf O^{(1)}\mid\mathbf O^{(2)}\mid\mathbf O^{(3)}\,]$}{$T\times d$} \stcaption{C2}{$[\,\mathbf O^{(1)}\mid\mathbf O^{(2)}\mid\mathbf O^{(3)}\,]$}{$T\times d$}
\stcaption{WO}{$\mathbf W_O$}{$d\times d$} \stcaption{WO}{$\mathbf W_O$}{$d\times d$}
\stcaption{Y}{$\mathbf Y$}{$T\times d$} \stcaption{Y}{$\mathbf Y$}{$T\times d$}
\stnolane
% ================================================================= formula ==
% Placed last so it is centered on the figure that was actually drawn.
\sttopformula{F}{$\displaystyle
\mathbf A^{(i)}=\mathrm{softmax}\!\left(
\frac{\mathbf Q^{(i)}\mathbf K^{(i)\top}}{\sqrt{d_h}}+\mathbf M\right),\qquad
\mathbf O^{(i)}=\mathbf A^{(i)}\mathbf V^{(i)}$}
% ============================================================== meaning box == % ============================================================== meaning box ==
\node[inner sep=0pt, fit=(F)(rowA)(rowB)(rowC)(Y-shape)(C2-shape)] (all) {}; \stbbox{all}
\stmeaningbox{mb}{16.8cm}{all} \stmeaningbox{mb}{16.8cm}{all}
{$T$ 序列长度,$d_h$ 单头宽度,$h$ 头数(图中 $h=3$,即堆叠的三张面), {$T$ 序列长度,$d_h$ 单头宽度,$h$ 头数(图中 $h=3$,即堆叠的三张面),
$d=h\,d_h$;批轴 $B$ 省略} $d=h\,d_h$;批轴 $B$ 省略}
+42 -56
View File
@@ -3,6 +3,7 @@
% (graded lightness), an INDEX face (discrete symbols, no ramp), and a BOOLEAN % (graded lightness), an INDEX face (discrete symbols, no ramp), and a BOOLEAN
% support face (one flat level) -- plus a gather whose output heights are data % support face (one flat level) -- plus a gather whose output heights are data
% dependent and must sum back to T*k. % dependent and must sum back to T*k.
% Layout is entirely by cursor: no absolute coordinate appears below.
% ../scripts/build.sh moe-topk-gather.tex % ../scripts/build.sh moe-topk-gather.tex
\documentclass[border=10pt]{standalone} \documentclass[border=10pt]{standalone}
\usepackage[cjk]{supertensor} \usepackage[cjk]{supertensor}
@@ -22,84 +23,69 @@
\begin{document} \begin{document}
\begin{tikzpicture} \begin{tikzpicture}
% ================================================================= formula ==
\node (F) at (0,0) {\stformula{$\displaystyle
\mathbf G=\mathrm{softmax}(\mathbf{XW}_g),\quad
\mathcal I_t=\operatorname*{top-}k_{e}\,\mathbf G_{t,e},\quad
\mathbf D_{t,e}=\mathbf 1[e\in\mathcal I_t],\quad
\mathbf X^{(e)}=\mathrm{gather}(\mathbf X,\mathbf D_{:,e})$}};
% ============================================================ stage A row === % ============================================================ stage A row ===
\node[st stage, below=7mm of F] (SA) {打分:token 对专家}; \ststage{SA}{打分:token 对专家}
\coordinate (a) at ($(SA)+(-3.6,-1.8)$); \strow{rowA}{T}
\stface[role=act, bracket=true]{X}{}{T}{d}
\stface[role=act, bracket=true]{X}{(a)}{T}{d} \stglyph{mA}{$\times$}
\node[st op, right=6mm of X] (mA) {$\times$}; \stface[role=wg]{Wg}{}{d}{E}
\stface[role=wg]{Wg}{($(mA)+(1.6,0)$)}{d}{E} \stglyph{eA}{$=$}
\node[st op, right=6mm of Wg] (eA) {$=$}; \stface[role=s]{G}{}{T}{E}
\stface[role=s]{G}{($(eA)+(1.6,0)$)}{T}{E} \strowend
\node[inner sep=0pt, fit=(X)(Wg)(G)] (rowA) {};
\stlane{rowA}
\stcaption{X}{$\mathbf X$}{$T\times d$} \stcaption{X}{$\mathbf X$}{$T\times d$}
\stcaption{Wg}{$\mathbf W_g$}{$d\times E$} \stcaption{Wg}{$\mathbf W_g$}{$d\times E$}
\stcaption{G}{$\mathbf G$}{$T\times E$} \stcaption{G}{$\mathbf G$}{$T\times E$}
\stnolane
% ============================================================ stage B row === % ============================================================ stage B row ===
\node[st stage, below=9mm of X-shape.south west, anchor=north west] (SB) \ststage{SB}{取前 $k$:连续分数 $\rightarrow$ 离散选择}
{取前 $k$:连续分数 $\rightarrow$ 离散选择}; \strow{rowB}{T}
\coordinate (b) at ($(SB.west)+(0.6,-1.7)$);
% Indices are drawn as symbols, not as magnitudes: expert 3 is not "bigger" % Indices are drawn as symbols, not as magnitudes: expert 3 is not "bigger"
% than expert 0, so the index face gets no lightness ramp. % than expert 0, so the index face gets no lightness ramp.
\stindexface[role=idx]{I}{(b)}{T}{k}{0,1, 1,2, 2,3, 3,0, 0,2, 1,3} \stindexface[role=idx]{I}{}{T}{k}{0,1, 1,2, 2,3, 3,0, 0,2, 1,3}
\stlink{lb}{one-hot}
% The same routing decision as a boolean support: one flat level, exactly k % The same routing decision as a boolean support: one flat level, exactly k
% cells per row, and every unselected cell left unfilled. % cells per row, and every unselected cell left unfilled.
\stface[role=m, pattern=data, level=3, \stface[role=m, pattern=data, level=3,
data={3300,0330,0033,3003,3030,0303}]{D}{($(I.east)+(3.1,0)$)}{T}{E} data={3300,0330,0033,3003,3030,0303}]{D}{}{T}{E}
\starrowlabel{I.east}{D.west}{one-hot} \stlink{lg}{}
\stcomm{gz}{Gather}
\node[st comm, right=9mm of D] (gz) {Gather}; \strowend
\starrow{D.east}{gz.west}
\node[inner sep=0pt, fit=(I)(D)(gz)] (rowB) {};
\stlane{rowB}
\stcaption{I}{$\mathcal I$}{$T\times k$} \stcaption{I}{$\mathcal I$}{$T\times k$}
\stcaption{D}{$\mathbf D$}{$T\times E$} \stcaption{D}{$\mathbf D$}{$T\times E$}
\stnolane
% ============================================================ stage C row === % ============================================================ stage C row ===
% Every stage heading starts on the same left rail; only the vertical position \ststage{SC}{按专家聚合:每个缓冲区的高度是数据决定的}
% follows the previous row. \strow{rowC}{ne}
\coordinate (cy) at ($(I-shape.south)+(0,-9mm)$); % Each buffer keeps X's width d -- gather regroups rows, it never reshapes
\node[st stage, anchor=north west] (SC) at (SB.west |- cy) % the feature axis. The heights are n_e, and they must sum to T*k.
{按专家聚合:每个缓冲区的高度是数据决定的}; % A token column and its buffer are one object: tight gap inside the pair,
\coordinate (c) at ($(SC.west)+(0.5,-1.9)$); % the standing gutter (widened) between pairs.
\stindexface[role=idx, border=false]{t0}{}{ne}{1}{1,4,5}
% Each buffer keeps X's width d -- gather regroups rows, it never reshapes the \stface[role=act, gap=2.5mm]{B0}{}{ne}{d}
% feature axis. The heights are n_e, and they must sum to T*k. \stindexface[role=idx, border=false, gap=12mm]{t1}{}{ne}{1}{1,2,6}
\stindexface[role=idx, border=false]{t0}{(c)}{ne}{1}{1,4,5} \stface[role=act, gap=2.5mm]{B1}{}{ne}{d}
\stface[role=act]{B0}{($(t0.east)+(2*\stunit,0)$)}{ne}{d} \stindexface[role=idx, border=false, gap=12mm]{t2}{}{ne}{1}{2,3,5}
\stindexface[role=idx, border=false]{t1}{($(B0.east)+(1.1,0)$)}{ne}{1}{1,2,6} \stface[role=act, gap=2.5mm]{B2}{}{ne}{d}
\stface[role=act]{B1}{($(t1.east)+(2*\stunit,0)$)}{ne}{d} \stindexface[role=idx, border=false, gap=12mm]{t3}{}{ne}{1}{3,4,6}
\stindexface[role=idx, border=false]{t2}{($(B1.east)+(1.1,0)$)}{ne}{1}{2,3,5} \stface[role=act, gap=2.5mm]{B3}{}{ne}{d}
\stface[role=act]{B2}{($(t2.east)+(2*\stunit,0)$)}{ne}{d} \strowend
\stindexface[role=idx, border=false]{t3}{($(B2.east)+(1.1,0)$)}{ne}{1}{3,4,6}
\stface[role=act]{B3}{($(t3.east)+(2*\stunit,0)$)}{ne}{d}
\stcaptiontop{t0}{\stshapefont{token}} \stcaptiontop{t0}{\stshapefont{token}}
\sttrack{t0-top}
\node[inner sep=0pt, fit=(t0)(B3)] (rowC) {};
\stlane{rowC}
\stcaption{B0}{$\mathbf X^{(1)}$}{$n_1\times d$} \stcaption{B0}{$\mathbf X^{(1)}$}{$n_1\times d$}
\stcaption{B1}{$\mathbf X^{(2)}$}{$n_2\times d$} \stcaption{B1}{$\mathbf X^{(2)}$}{$n_2\times d$}
\stcaption{B2}{$\mathbf X^{(3)}$}{$n_3\times d$} \stcaption{B2}{$\mathbf X^{(3)}$}{$n_3\times d$}
\stcaption{B3}{$\mathbf X^{(4)}$}{$n_4\times d$} \stcaption{B3}{$\mathbf X^{(4)}$}{$n_4\times d$}
\stnolane
% ================================================================= formula ==
% Placed last so it is centered on the figure that was actually drawn.
\sttopformula{F}{$\displaystyle
\mathbf G=\mathrm{softmax}(\mathbf{XW}_g),\quad
\mathcal I_t=\operatorname*{top-}k_{e}\,\mathbf G_{t,e},\quad
\mathbf D_{t,e}=\mathbf 1[e\in\mathcal I_t],\quad
\mathbf X^{(e)}=\mathrm{gather}(\mathbf X,\mathbf D_{:,e})$}
% ============================================================== meaning box == % ============================================================== meaning box ==
\node[inner sep=0pt, fit=(F)(rowA)(rowB)(rowC)(B3-shape)(t0-top)] (all) {}; \stbbox{all}
\stmeaningbox{mb}{16.6cm}{all} \stmeaningbox{mb}{16.6cm}{all}
{$T$ token 数,$d$ 模型宽度,$E$ 专家数(图中 $E=4$),$k$ 每 token 选中的专家数 {$T$ token 数,$d$ 模型宽度,$E$ 专家数(图中 $E=4$),$k$ 每 token 选中的专家数
(图中 $k=2$),$n_e$ 落到第 $e$ 个专家的 token 数} (图中 $k=2$),$n_e$ 落到第 $e$ 个专家的 token 数}
+49 -50
View File
@@ -1,6 +1,8 @@
% Golden example 1 -- tensor parallel FFN, column-then-row sharding + AllReduce. % Golden example 1 -- tensor parallel FFN, column-then-row sharding + AllReduce.
% Shows: partition geometry (shards tile the parent exactly), one hue per TP % Shows: partition geometry (shards tile the parent exactly), one hue per TP
% rank held across every stage, a collective node as a real operation. % rank held across every stage, a collective node as a real operation, and a
% split along the contracted axis drawn as a column sub-flow.
% Layout is entirely by cursor: no absolute coordinate appears below.
% ../scripts/build.sh tp-ffn-allreduce.tex % ../scripts/build.sh tp-ffn-allreduce.tex
\documentclass[border=10pt]{standalone} \documentclass[border=10pt]{standalone}
\usepackage[cjk]{supertensor} \usepackage[cjk]{supertensor}
@@ -14,74 +16,71 @@
\stdim{bt}{6} % B*T rows \stdim{bt}{6} % B*T rows
\stdim{d}{4} % model width \stdim{d}{4} % model width
\stdim{dffl}{4} % d_ff / p (per-rank hidden width) \stdim{dffl}{4} % d_ff / p (per-rank hidden width)
\stdim{dff}{8} % d_ff = p * (d_ff/p); the height of the stacked W_2 column
\begin{document} \begin{document}
\begin{tikzpicture} \begin{tikzpicture}
% ================================================================= formula ==
\node (F) at (0,0) {\stformula{$\mathbf{XW}_1=[\,\mathbf{XW}_1^{(1)}\mid
\mathbf{XW}_1^{(2)}\,]=[\,\mathbf H^{(1)}\mid\mathbf H^{(2)}\,]$}};
\node[below=1.2mm of F] (F2) {\stformula{$\displaystyle
[\,\mathbf H^{(1)}\mid\mathbf H^{(2)}\,]
\begin{bmatrix}\mathbf W_2^{(1)}\\[-1pt]\mathbf W_2^{(2)}\end{bmatrix}
=\sum_{r}\mathbf H^{(r)}\mathbf W_2^{(r)}=\sum_r\mathbf P^{(r)}$}};
% ============================================================ stage A row === % ============================================================ stage A row ===
\node[st stage, below=7mm of F2] (SA) {列切 $\mathbf W_1$:无通信}; \ststage{SA}{列切 $\mathbf W_1$:无通信}
\coordinate (a) at ($(SA)+(-5.6,-1.8)$); \strow{rowA}{bt}
\stface[role=act, bracket=true]{X}{}{bt}{d}
\stface[role=act, bracket=true]{X}{(a)}{bt}{d} \stglyph{mA}{$\times$}
\node[st op, right=5mm of X] (mA) {$\times$}; % Two shards, tiled exactly: gap=0pt makes them adjacent by construction,
% Two shards, tiled exactly: adjacent faces, no stretching, no gap. % so neither stretching nor an eyeballed offset can creep in.
\stface[role=r1]{W1a}{($(mA)+(1.5,0)$)}{d}{dffl} \stface[role=r1]{W1a}{}{d}{dffl}
\stface[role=r2]{W1b}{($(W1a.east)+(2*\stunit,0)$)}{d}{dffl} \stface[role=r2, gap=0pt]{W1b}{}{d}{dffl}
\node[st op, right=5mm of W1b] (eA) {$=$}; \stglyph{eA}{$=$}
\stface[role=r1]{Ha}{($(eA)+(1.5,0)$)}{bt}{dffl} \stface[role=r1]{Ha}{}{bt}{dffl}
\stface[role=r2]{Hb}{($(Ha.east)+(2*\stunit,0)$)}{bt}{dffl} \stface[role=r2, gap=0pt]{Hb}{}{bt}{dffl}
\strowend
\node[inner sep=0pt, fit=(X)(W1a)(Ha)(Hb)] (rowA) {};
\stlane{rowA}
\stcaption{X}{$\mathbf X$}{$BT\times d$} \stcaption{X}{$\mathbf X$}{$BT\times d$}
\stcaption{W1a}{$\mathbf W_1^{(1)}$}{$d\times d_{\mathrm{ff}}/p$} \stcaption{W1a}{$\mathbf W_1^{(1)}$}{$d\times d_{\mathrm{ff}}/p$}
\stcaption{W1b}{$\mathbf W_1^{(2)}$}{$d\times d_{\mathrm{ff}}/p$} \stcaption{W1b}{$\mathbf W_1^{(2)}$}{$d\times d_{\mathrm{ff}}/p$}
\stcaption{Ha}{$\mathbf H^{(1)}$}{$BT\times d_{\mathrm{ff}}/p$} \stcaption{Ha}{$\mathbf H^{(1)}$}{$BT\times d_{\mathrm{ff}}/p$}
\stcaption{Hb}{$\mathbf H^{(2)}$}{$BT\times d_{\mathrm{ff}}/p$} \stcaption{Hb}{$\mathbf H^{(2)}$}{$BT\times d_{\mathrm{ff}}/p$}
\stnolane
% ============================================================ stage B row === % ============================================================ stage B row ===
\node[st stage, below=9mm of X-shape.south west, anchor=north west] (SB) \ststage{SB}{行切 $\mathbf W_2$:一次 All-Reduce}
{行切 $\mathbf W_2$:一次 All-Reduce}; \strow{rowB}{dff} % the stacked W_2 column is the tallest object here
\coordinate (b) at ($(SB)+(-0.4,-1.9)$); \stface[role=r1]{Ga}{}{bt}{dffl}
\stface[role=r2, gap=0pt]{Gb}{}{bt}{dffl}
\stface[role=r1]{Ga}{(b)}{bt}{dffl} \stglyph{mB}{$\times$}
\stface[role=r2]{Gb}{($(Ga.east)+(2*\stunit,0)$)}{bt}{dffl} % W_2 is split along the CONTRACTED axis: the shards stack vertically and
\node[st op, right=5mm of Gb] (mB) {$\times$}; % together have exactly the height of H's width. Splitting reverses concat,
% W_2 is split along the CONTRACTED axis: the two shards stack vertically and % which is what gap=0pt inside the column states.
% together have exactly the height of H's width. Splitting reverses concat. \stcol{W2}{dff}
\stface[role=r1]{W2a}{($(mB)+(1.35,0.46)$)}{dffl}{d} \stface[role=r1]{W2a}{}{dffl}{d}
\stface[role=r2]{W2b}{($(W2a.south)+(0,-2*\stunit)$)}{dffl}{d} \stface[role=r2, gap=0pt]{W2b}{}{dffl}{d}
\node[st op, right=5mm of W2a.east |- W2a.south] (eB) {$=$}; \stcolend
\stface[role=r1]{Pa}{($(eB)+(1.3,0)$)}{bt}{d} \stglyph{eB}{$=$}
\node[st op, right=4mm of Pa] (plus) {$+$}; \stface[role=r1]{Pa}{}{bt}{d}
\stface[role=r2]{Pb}{($(plus)+(1.3,0)$)}{bt}{d} \stglyph{plus}{$+$}
\stface[role=r2]{Pb}{}{bt}{d}
\node[st comm, right=9mm of Pb] (ar) {All-Reduce}; \stlink{lc}{}
\stface[role=act, bracket=true]{Y}{($(ar)+(1.9,0)$)}{bt}{d} \stcomm{ar}{All-Reduce}
\starrow{Pb.east}{ar.west} \stlink{ly}{}
\starrow{ar.east}{Y.west} \stface[role=act, bracket=true]{Y}{}{bt}{d}
\strowend
\node[inner sep=0pt, fit=(Ga)(W2a)(W2b)(Pa)(Pb)(Y)] (rowB) {};
\stlane{rowB}
\stcaption{Ga}{$\mathbf G^{(1)}$}{$BT\times d_{\mathrm{ff}}/p$} \stcaption{Ga}{$\mathbf G^{(1)}$}{$BT\times d_{\mathrm{ff}}/p$}
\stcaption{Gb}{$\mathbf G^{(2)}$}{$BT\times d_{\mathrm{ff}}/p$} \stcaption{Gb}{$\mathbf G^{(2)}$}{$BT\times d_{\mathrm{ff}}/p$}
\stcaption{W2b}{$\mathbf W_2^{(r)}$}{$d_{\mathrm{ff}}/p\times d$} \stcaption{W2}{$\mathbf W_2^{(r)}$}{$d_{\mathrm{ff}}/p\times d$}
\stcaption{Pa}{$\mathbf P^{(1)}$}{$BT\times d$} \stcaption{Pa}{$\mathbf P^{(1)}$}{$BT\times d$}
\stcaption{Pb}{$\mathbf P^{(2)}$}{$BT\times d$} \stcaption{Pb}{$\mathbf P^{(2)}$}{$BT\times d$}
\stcaption{Y}{$\mathbf Y$}{$BT\times d$} \stcaption{Y}{$\mathbf Y$}{$BT\times d$}
\stnolane
% ================================================================= formula ==
% Placed last so it is centered on the figure that was actually drawn.
\sttopformula{F}{$\begin{gathered}
\mathbf{XW}_1=[\,\mathbf{XW}_1^{(1)}\mid
\mathbf{XW}_1^{(2)}\,]=[\,\mathbf H^{(1)}\mid\mathbf H^{(2)}\,]\\[1.2mm]
[\,\mathbf H^{(1)}\mid\mathbf H^{(2)}\,]
\begin{bmatrix}\mathbf W_2^{(1)}\\[-1pt]\mathbf W_2^{(2)}\end{bmatrix}
=\sum_{r}\mathbf H^{(r)}\mathbf W_2^{(r)}=\sum_r\mathbf P^{(r)}
\end{gathered}$}
% ============================================================== meaning box == % ============================================================== meaning box ==
\node[inner sep=0pt, fit=(F)(rowA)(rowB)(Y-shape)(Ga-shape)] (all) {}; \stbbox{all}
\stmeaningbox{mb}{16.4cm}{all} \stmeaningbox{mb}{16.4cm}{all}
{$BT$ 展平后的 token 数,$d$ 模型宽度,$d_{\mathrm{ff}}$ 前馈中间宽度, {$BT$ 展平后的 token 数,$d$ 模型宽度,$d_{\mathrm{ff}}$ 前馈中间宽度,
$p$ TP 并行度(图中 $p=2$)} $p$ TP 并行度(图中 $p=2$)}
+11 -5
View File
@@ -15,9 +15,9 @@ dimension. **Fix:** swap the arguments — `\ststack{KT}{...}{dh}{T}{3}`. See `g
## 2. Shards that do not tile their parent ## 2. Shards that do not tile their parent
Two shards drawn with a gap, or stretched to fill a parent whose other shards were elided. Two shards drawn with a gap, or stretched to fill a parent whose other shards were elided.
Both assert a width that the tensor does not have. **Fix:** place each shard from the Both assert a width that the tensor does not have. **Fix:** draw the shards in one band
previous one's edge (`($(W1a.east)+(2*\stunit,0)$)`), and draw an ellipsis for anything and give every shard after the first `gap=0pt`, which *states* that they are adjacent
omitted. See `geometry.md` §5–6. instead of arranging for it; draw an ellipsis for anything omitted. See `geometry.md` §5–6.
## 3. An index drawn as a heatmap ## 3. An index drawn as a heatmap
@@ -41,12 +41,18 @@ See `style.md`.
reads the resulting stripe as real structure. `pattern=dense` avoids it; if you write reads the resulting stripe as real structure. `pattern=dense` avoids it; if you write
your own filler, check that rows 1, 2, 4, 5 of a tall face are not identical. your own filler, check that rows 1, 2, 4, 5 of a tall face are not identical.
- **A label wider than its connector.** The white underlay then covers the target tensor. - **A label wider than its connector.** The white underlay then covers the target tensor.
Shorten the label or widen the gap — never let it sit on a face. See `layout.md`. `\stlink` makes this unrepresentable: the label reserves its own width in the band and
the arrow is drawn to whatever lands beside it. See `layout.md`.
- **A new hue for a regrouped view of the same data.** `X` and the per-expert buffers - **A new hue for a regrouped view of the same data.** `X` and the per-expert buffers
gathered out of `X` are the same object in a different order; a second hue claims they gathered out of `X` are the same object in a different order; a second hue claims they
are different tensors. are different tensors.
- **Captions hanging at different depths** because the faces in a row have different - **Captions hanging at different depths** because the faces in a row have different
heights. Use `\stlane`. heights. Use `\strow`/`\strowend`, which arms `\stlane` for you.
- **Hand-tuned offsets.** Each one is a magic number valid only for the content that was
there when you tuned it; the figure that breaks is the *next* one, when a label grows two
characters and lands on a face. Use the cursor. See `layout.md`.
- **A formula line placed first.** It can only be centered on a figure whose width is not
known yet, so it ends up visibly off-center. Call `\sttopformula` after the bands.
- **A floating commentary card between two operands.** If it is not a real operation, it - **A floating commentary card between two operands.** If it is not a real operation, it
belongs in the stage subtitle or the bottom box. belongs in the stage subtitle or the bottom box.
- **A meaning box that repeats the shapes.** The shapes are already under every block. The - **A meaning box that repeats the shapes.** The shapes are already under every block. The
+97 -10
View File
@@ -23,17 +23,85 @@ Colors: `stTeal stOrange stCoral stViolet stGray stInk`. An unknown role falls b
Lengths: `\stunit` (one cell, 4.6 mm) and `\sttilegap` (white gutter, 0.5 mm). 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 |
| `\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.
## Faces ## Faces
```tex ```tex
\stface[keys]{name}{(coord)}{rows}{cols} \stface[keys]{name}{}{rows}{cols} % flow
\stface[keys]{name}{(coord)}{rows}{cols} % absolute
\ststack[keys]{name}{(coord)}{rows}{cols}{sheets} \ststack[keys]{name}{(coord)}{rows}{cols}{sheets}
\stindexface[keys]{name}{(coord)}{rows}{cols}{entries} \stindexface[keys]{name}{(coord)}{rows}{cols}{entries}
``` ```
`rows`/`cols` accept a declared axis name or a raw integer. `(coord)` must include its own `rows`/`cols` accept a declared axis name or a raw integer. A non-empty `(coord)` must
parentheses — `{(0,0)}`, `{($(A.east)+(1.5,0)$)}`. `name` becomes a TikZ node you can include its own parentheses — `{(0,0)}`, `{($(A.east)+(1.5,0)$)}`. `name` becomes a TikZ
anchor against; `\ststack` also defines `name-front`. node you can anchor against; `\ststack` also defines `name-front`.
Keys: Keys:
@@ -46,6 +114,7 @@ Keys:
| `bracket=` | `false` | thin neutral matrix brackets | | `bracket=` | `false` | thin neutral matrix brackets |
| `border=` | `true` | outer `black!60` border | | `border=` | `true` | outer `black!60` border |
| `tiles=` | `true` | `false` = one flat filled rectangle | | `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. `\stindexface` entries are row-major, `rows*cols` of them; `.` leaves a cell blank.
It deliberately has no lightness ramp — see `semantics.md`. It deliberately has no lightness ramp — see `semantics.md`.
@@ -58,12 +127,21 @@ It deliberately has no lightness ramp — see `semantics.md`.
## Captions ## 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 ```tex
\node[inner sep=0pt, fit=(A)(B)(C)] (rowA) {}; \node[inner sep=0pt, fit=(A)(B)(C)] (rowA) {};
\stlane{rowA} \stlane{rowA}
\stcaption{A}{$\mathbf A$}{$T\times d$} % symbol lane, shape lane \stcaption{A}{$\mathbf A$}{$T\times d$}
\stnolane \stnolane
\stcaptiontop{A}{\stshapefont{token}} % occasional label above a face
``` ```
`\stcaption` defines `name-sym` and `name-shape` nodes; anchor the next stage heading `\stcaption` defines `name-sym` and `name-shape` nodes; anchor the next stage heading
@@ -71,8 +149,10 @@ against `name-shape.south`.
## Operators, connectors, nodes ## Operators, connectors, nodes
Prefer `\stglyph` / `\stcomm` / `\stlink` (above). The raw forms are for absolute placement:
```tex ```tex
\node[st op, right=6mm of A] (m) {$\times$}; \node[st op, right=6mm of A] (m) {$\times$}; % reserves nothing -- see layout.md
\node[st comm, right=9mm of P] (ar) {All-Reduce}; \node[st comm, right=9mm of P] (ar) {All-Reduce};
\starrow{P.east}{ar.west} \starrow{P.east}{ar.west}
\starrowlabel{M.east}{A.west}{softmax} \starrowlabel{M.east}{A.west}{softmax}
@@ -85,13 +165,14 @@ Connectors route on the background layer automatically.
## Bottom ## Bottom
```tex ```tex
\node[inner sep=0pt, fit=(F)(rowA)(rowB)(Y-shape)] (all) {}; \stbbox{all} % flow: everything drawn so far
\stmeaningbox{mb}{16.6cm}{all}{axes text}{objects text}{mechanism text} \stmeaningbox{mb}{16.6cm}{all}{axes text}{objects text}{mechanism text}
\stsignature{因果多头注意力(掩码 + 拼接投影)}{mb} \stsignature{因果多头注意力(掩码 + 拼接投影)}{mb}
``` ```
Arg 2 is the total box width; arg 3 is the node it hangs below — include every caption and Arg 2 is the total box width; arg 3 is the node it hangs below. `\stbbox` already contains
top label in that `fit` or the box will overlap them. An empty `{}` row is dropped. 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.
## Gotchas ## Gotchas
@@ -101,3 +182,9 @@ top label in that `fit` or the box will overlap them. An empty `{}` row is dropp
- `\foreach {\macro,...,1}` cannot infer its direction from an unexpanded macro. - `\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 - `\strole` is expandable on purpose (it is used inside `\edef`); the warning lives in
`\stcheckrole`. `\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`.
+6 -1
View File
@@ -26,8 +26,13 @@ Work through it against the rendered PNG. Any mandatory violation means redraw,
## 4. Full-size visual audit ## 4. Full-size visual audit
Open the PNG at 100 %. Open the PNG at 100 %. Most of the first three items are automatic under the flow layout;
they still need looking at, because the cursor only guarantees that objects do not *push*
into each other, not that the figure reads correctly.
- [ ] No absolute coordinate that a `\strow`/`\stcol` could have expressed. Every remaining
hand-placed node is wrapped in `\sttrack`.
- [ ] `\sttopformula` called after the bands, so the formula is centered on the real figure.
- [ ] No forbidden intersection, tangency, clipping or occlusion — including stack offset - [ ] No forbidden intersection, tangency, clipping or occlusion — including stack offset
sheets, brackets, arrow labels and the meaning box. sheets, brackets, arrow labels and the meaning box.
- [ ] Every connector's white label underlay covers only its own connector. - [ ] Every connector's white label underlay covers only its own connector.
+35 -19
View File
@@ -4,11 +4,29 @@ Everything on the canvas is a bounding box: tensors, full offset stacks, bracket
operators, arrow labels, annotations, symbols, shape labels, stage headings, the meaning operators, arrow labels, annotations, symbols, shape labels, stage headings, the meaning
box, the signature. **Tangency counts as collision.** box, the signature. **Tangency counts as collision.**
## Place by cursor, not by coordinate
The invariants below are *statements about the finished figure*; the flow layout in
`api.md` is how you get them without checking each one by hand. Use it by default:
`\ststage` / `\strow` … `\strowend` / `\stcol` … `\stcolend`, with an empty coordinate
argument on every face.
The failure it removes is specific. With hand-written offsets, each gap is a magic number
tuned against the *current* content, so the day a label grows by two characters it silently
lands on the next tensor — the figure still compiles and still looks clean. With the
cursor, every object reserves its own width, so growing one object can only push the rest
apart. And because a band declares its height, an object that does not fit becomes a
`Package supertensor Warning`, which `build.sh` turns into a failed build.
Hand placement remains available for the cases the cursor cannot express. When you use it,
wrap the node in `\sttrack` so the bounding box and the vertical cursor still see it.
## Gutters ## Gutters
Define one base gutter `g ≥ 1 em`. Unrelated boxes stay at least `g` apart; stage bands at Define one base gutter `g ≥ 1 em`; that is what `\stgutter` (6 mm) is. Unrelated boxes stay
least `1.5g`. In practice: `right=5mm–9mm` between an operator and its operands, `7mm–9mm` at least `g` apart; stage bands at least `1.5g` (`\strowgap`, `\stblockgap`). Set them once
between the last caption of one row and the next stage heading. at the top of the figure rather than per call — a per-call `gap=` is for stating a
*relationship* (`gap=0pt` = these shards tile), not for nudging.
Overlap is allowed only inside one declared composite: Overlap is allowed only inside one declared composite:
@@ -33,26 +51,22 @@ shapes <- \stcaption arg 3
``` ```
Faces of different heights would otherwise hang their captions at different depths. Fix it Faces of different heights would otherwise hang their captions at different depths. Fix it
with a shared baseline: with a shared baseline — `\strowend` arms one automatically:
```tex ```tex
\node[inner sep=0pt, fit=(Q)(KT)(S)] (rowA) {}; \strow{rowA}{T}
\stlane{rowA}
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
... ...
\stnolane \strowend
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
``` ```
Every `\stcaption` between `\stlane` and `\stnolane` hangs from the bottom of `rowA`, so Under absolute placement, arm it yourself with `\stlane{rowA}` … `\stnolane`. Either way
symbols and shapes form two flat lanes. every `\stcaption` inside hangs from the bottom of `rowA`, so symbols and shapes form two
flat lanes.
Stage headings share one left rail. Anchor each heading below the previous row but at the Stage headings share one left rail. `\ststage` puts them there: the rail is a single stored
previous *heading's* x, not at the previous row's content: `x` (`\stleftrail` to move it), and the `y` is derived from the lowest ink drawn so far, so
a heading can neither drift right nor collide with the row above it.
```tex
\coordinate (cy) at ($(I-shape.south)+(0,-9mm)$);
\node[st stage, anchor=north west] (SC) at (SB.west |- cy) {...};
```
Explanatory prose belongs in the stage subtitle, the bottom box, or above its own Explanatory prose belongs in the stage subtitle, the bottom box, or above its own
connector. Never drop a floating commentary card between two operands unless it is a real connector. Never drop a floating commentary card between two operands unless it is a real
@@ -67,8 +81,10 @@ never cover a face. Two consequences you still own:
- A connector may not cross a box that is not one of its endpoints. Move the row, don't - A connector may not cross a box that is not one of its endpoints. Move the row, don't
route over. route over.
- A label's white underlay may cover only its own connector — never a tensor, never - A label's white underlay may cover only its own connector — never a tensor, never
another label. If the label is wider than the arrow, shorten the label or widen the gap. another label. This is the single most common failure after a first draft, and `\stlink`
This is the single most common failure after a first draft. is the fix: it makes the label itself a flow object and draws the arrow to whatever
lands on either side of it, so the label cannot be wider than its connector.
`\starrowlabel` between two hand-placed nodes still has the old failure mode.
## Stacks ## Stacks
+71
View File
@@ -0,0 +1,71 @@
% Regression test for the flow layout: stage headings on a shared left rail,
% bands whose objects reserve their own width, connectors whose labels are flow
% objects. No absolute coordinate appears anywhere in this figure.
% ../scripts/build.sh flow.tex
\documentclass[border=10pt]{standalone}
\usepackage[cjk]{supertensor}
\stsetrole{act}{stTeal}
\stsetrole{w}{stOrange}
\stsetrole{o}{stViolet}
\stdim{T}{7}
\stdim{d}{5}
\stdim{r}{3}
\begin{document}
\begin{tikzpicture}
\ststage{S1}{一、面、算子、带标签的连接线都由游标排布}
\strow{R1}{T}
\stface[role=act, bracket=true]{X}{}{T}{d}
\stglyph{m1}{$\times$}
\stface[role=w, bracket=true]{W}{}{d}{r}
\stlink{l1}{一个很长的标签也不会压到下一个张量}
\stface[role=o, bracket=true]{Y}{}{T}{r}
\strowend
\stcaption{X}{$\mathbf X$}{$T\times d$}
\stcaption{W}{$\mathbf W$}{$d\times r$}
\stcaption{Y}{$\mathbf Y$}{$T\times r$}
\ststage{S2}{二、堆叠的偏移页也计入宽度,行首自动回到同一条左轨}
\strow{R2}{T}
\ststack[role=act]{Q}{}{T}{r}{3}
\stlink{l2}{}
\stcomm{c1}{All-Reduce}
\stlink{l3}{sum}
\stface[role=o, pattern=causal]{A}{}{T}{T}
\stgap{4mm}
\stface[role=w, pattern=diag]{D}{}{r}{r}
\strowend
\stcaption{Q}{$\mathbf Q$}{$3\times T\times r$}
\stcaption{A}{$\mathbf A$}{$T\times T$}
\stcaption{D}{$\mathbf D$}{$r\times r$}
\ststage{S3}{三、列子流:两片沿收缩维叠放,恰好铺满一格}
\strow{R3}{T}
\stface[role=act]{H}{}{T}{d}
\stglyph{m3}{$\times$}
\stcol{Wc}{d}
% d = 2 * (d/2)... here r=3 and d=5, so the two shards are 3 and 2 units.
\stface[role=w]{Wa}{}{r}{T}
\stface[role=o, gap=0pt]{Wb}{}{2}{T}
\stcolend
\stglyph{e3}{$=$}
\stface[role=o, bracket=true]{Z}{}{T}{T}
\strowend
\stcaption{H}{$\mathbf H$}{$T\times d$}
\stcaption{Wc}{$\mathbf W^{(r)}$}{$d\times T$}
\stcaption{Z}{$\mathbf Z$}{$T\times T$}
\stbbox{all}
\stmeaningbox{mb}{15cm}{all}
{$T$ 序列长,$d$ 模型维,$r$ 低秩维}
{}
{三行的左端都落在同一条左轨上,行内间距由 \texttt{\string\stgutter} 声明一次,
标签自己占位,所以任何一处变宽都只会把后面的东西推开,不会盖住它们;
第三行的列子流用 \texttt{gap=0pt} 声明两片相邻,声明高度与实际不符会直接报警}
\stsignature{流式排版自测}{mb}
\end{tikzpicture}
\end{document}