Compare commits
7
Commits
7a22bef9e3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ba01fe5e2 | ||
|
|
7210f890ca | ||
|
|
8ada8e57c5 | ||
|
|
7d62e0279a | ||
|
|
de917a2fbd | ||
|
|
866173a831 | ||
|
|
7b59c81d02 |
@@ -4,6 +4,11 @@ A shape-aware toolkit for drawing tensor formulas: a LaTeX/TikZ macro package, a
|
|||||||
pipeline that fails on silent corruption, worked examples, and an agent skill that ties
|
pipeline that fails on silent corruption, worked examples, and an agent skill that ties
|
||||||
them together.
|
them together.
|
||||||
|
|
||||||
|
This repository is a **child** of SuperPaper, the family parent that
|
||||||
|
routes paper notes to the right figure toolkit. Clone SuperPaper with
|
||||||
|
`--recurse-submodules` for the whole family; use this directory alone
|
||||||
|
when you only need shape-aware tensor figures.
|
||||||
|
|
||||||
It exists because figures of this kind fail in a specific way — they compile, they look
|
It exists because figures of this kind fail in a specific way — they compile, they look
|
||||||
clean, and they tell the reader something false. A face captioned `Kᵀ` that was never
|
clean, and they tell the reader something false. A face captioned `Kᵀ` that was never
|
||||||
transposed; shards that do not tile their parent; an index tensor drawn with a lightness
|
transposed; shards that do not tile their parent; an index tensor drawn with a lightness
|
||||||
@@ -14,7 +19,8 @@ SKILL.md the skill entry point (lean; loads references on demand)
|
|||||||
references/ geometry, semantics, layout, style, api, checklist, antipatterns
|
references/ geometry, semantics, layout, style, api, checklist, antipatterns
|
||||||
assets/supertensor.sty the macro package
|
assets/supertensor.sty the macro package
|
||||||
scripts/preflight.sh is the TikZ + CJK path available?
|
scripts/preflight.sh is the TikZ + CJK path available?
|
||||||
scripts/build.sh compile, audit the log, export pdf/svg/png/thumb
|
scripts/lint.py reject source-level invariant escapes
|
||||||
|
scripts/build.sh lint, compile, audit the log, export pdf/svg/png/thumb
|
||||||
examples/ three golden examples + an anti-pattern gallery
|
examples/ three golden examples + an anti-pattern gallery
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -37,14 +43,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 +58,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,8 +89,15 @@ 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.
|
||||||
|
|
||||||
A clean build still proves nothing about collisions, hue budget or whether the math is
|
The flow layout adds two of its own: an object that overflows its band, and a `\stcol`
|
||||||
right. That is what `references/checklist.md` is for.
|
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 now also proves the source avoided untracked absolute objects, ledger
|
||||||
|
changes, raw rectangles, unclosed `\stgroup` blocks, side cards anchored to a single face
|
||||||
|
or doubled up on one band, and excess per-row hues. It still cannot prove that the math,
|
||||||
|
semantics or rendered relationships are right; that is what `references/checklist.md` is
|
||||||
|
for.
|
||||||
|
|
||||||
## Provenance
|
## Provenance
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: supertensor
|
name: supertensor
|
||||||
description: Create or refine clean, shape-aware figures for tensor/matrix/vector formulas or tensor code — matrix-block diagrams, entry heatmaps, row/column shard stripes, stacked 3D/4D tensors, attention, tensor/expert parallelism, broadcasting, reductions, contractions, gather/scatter and routing. Use whenever tensor shapes or axis meanings must be visually aligned with the computation. Not for plotting numeric data (loss curves, benchmark bars, scatter plots), architecture block diagrams without shapes, or generic flowcharts.
|
description: Create or refine clean, shape-aware figures for tensor/matrix/vector formulas or tensor code — matrix-block diagrams, entry heatmaps, row/column shard stripes, stacked 3D/4D tensors, attention, tensor/expert parallelism, broadcasting, reductions, contractions, gather/scatter and routing. Use whenever tensor shapes or axis meanings must be visually aligned with the computation, or the user runs $supertensor. Not for plotting numeric data (loss curves, benchmark bars, scatter plots), architecture block diagrams without shapes (use superfig), stepwise rewrite figures (use superderive), or full paper notes (use superpaper).
|
||||||
---
|
---
|
||||||
|
|
||||||
# supertensor
|
# supertensor
|
||||||
@@ -18,8 +18,8 @@ A figure built from raw TikZ has to re-earn every invariant by hand and usually
|
|||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
1. **Preflight.** `./scripts/preflight.sh`. Exit 0 = TikZ+CJK path. Exit 1 = degraded
|
1. **Preflight.** `./scripts/preflight.sh`. Exit 0 = TikZ+CJK path. Exit 1 = degraded
|
||||||
(say so in the delivery). Exit 2 = no LaTeX; fall back to SVG/matplotlib and say
|
(say so in the delivery). Exit 2 = no LaTeX; read `references/fallback.md` before
|
||||||
explicitly that the figure is not TikZ.
|
falling back and state explicitly which package guarantees are unavailable.
|
||||||
2. **Reduce** the input to one primary computation path. Drop equivalent objectives,
|
2. **Reduce** the input to one primary computation path. Drop equivalent objectives,
|
||||||
diagnostics, and secondary metrics unless asked for.
|
diagnostics, and secondary metrics unless asked for.
|
||||||
3. **Build two ledgers** before drawing anything:
|
3. **Build two ledgers** before drawing anything:
|
||||||
@@ -29,11 +29,13 @@ 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
|
||||||
5. **Build and audit.** `./scripts/build.sh fig.tex`. A clean build only proves TeX was
|
declared once. Reach for an absolute coordinate only when no band can express the
|
||||||
happy; then run the visual audit in `references/checklist.md` against the PNG at full
|
placement. See `references/api.md` and `references/layout.md`.
|
||||||
size and at thumbnail size. Redraw on any mandatory-invariant violation.
|
5. **Build and audit.** `./scripts/build.sh fig.tex` runs the source linter, TeX checks and
|
||||||
|
exports. Then run the remaining visual/semantic audit in `references/checklist.md`
|
||||||
|
against the PNG at full size and thumbnail size. Redraw on any mandatory violation.
|
||||||
|
|
||||||
For code input, trace the concrete `matmul`, `einsum`, `reshape/view`, `transpose/permute`,
|
For code input, trace the concrete `matmul`, `einsum`, `reshape/view`, `transpose/permute`,
|
||||||
concat, broadcast, and collective calls. Keep code variable names where useful; state any
|
concat, broadcast, and collective calls. Keep code variable names where useful; state any
|
||||||
@@ -47,7 +49,10 @@ 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` |
|
||||||
|
| adjacent objects that are one composite (heads of `q`, shards of `W`) | an outline in their own role hue | `\stgroup` … `\stgroupend` |
|
||||||
|
| an aside about a whole band | a side card off its right edge, one per figure | `\stcallout` |
|
||||||
|
|
||||||
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 +70,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.
|
||||||
|
|
||||||
@@ -82,8 +88,8 @@ and the `.tex` source plus vector artifact.
|
|||||||
select an OS-specific CJK font unless the user asks and accepts the portability cost.
|
select an OS-specific CJK font unless the user asks and accepts the portability cost.
|
||||||
- **English figures:** `\usepackage[en]{supertensor}` — same geometry, English rail labels.
|
- **English figures:** `\usepackage[en]{supertensor}` — same geometry, English rail labels.
|
||||||
- Keep math in LaTeX, not raw Unicode.
|
- Keep math in LaTeX, not raw Unicode.
|
||||||
- The identification line is `\stsignature{<subject>}{<box>}`; it renders
|
- Add `\stsignature{<subject>}{<box>}` only when the user or house template asks for an
|
||||||
`<subject>@五道口纳什`. Change the handle with `\stsetauthor{...}` only when asked.
|
identification line. It renders only the subject, with no author or handle.
|
||||||
|
|
||||||
## Iterating
|
## Iterating
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Supertensor"
|
||||||
|
short_description: "Create shape-aware tensor and matrix figures"
|
||||||
|
default_prompt: "Use $supertensor to turn this tensor formula or code path into an editable, shape-correct figure."
|
||||||
+524
-20
@@ -6,7 +6,7 @@
|
|||||||
%% Options: cjk load ctex with the portable fandol fontset (XeLaTeX)
|
%% Options: cjk load ctex with the portable fandol fontset (XeLaTeX)
|
||||||
%% en English rail labels in the meaning box (default: zh)
|
%% en English rail labels in the meaning box (default: zh)
|
||||||
\NeedsTeXFormat{LaTeX2e}
|
\NeedsTeXFormat{LaTeX2e}
|
||||||
\ProvidesPackage{supertensor}[2026/08/04 v0.1 shape-aware tensor figure toolkit]
|
\ProvidesPackage{supertensor}[2026/08/05 v0.2 shape-aware tensor figure toolkit]
|
||||||
|
|
||||||
\newif\ifst@cjk\st@cjkfalse
|
\newif\ifst@cjk\st@cjkfalse
|
||||||
\newif\ifst@en\st@enfalse
|
\newif\ifst@en\st@enfalse
|
||||||
@@ -46,8 +46,20 @@
|
|||||||
|
|
||||||
% Role registry: draw macros take a ROLE, never a color, so one tensor role
|
% Role registry: draw macros take a ROLE, never a color, so one tensor role
|
||||||
% keeps one hue across every stage of the figure.
|
% keeps one hue across every stage of the figure.
|
||||||
% \stsetrole{X}{stTeal} -> role "X" is teal everywhere
|
% \stsetrole{X}{stTeal} -> role "X" is teal everywhere. Repeating the same
|
||||||
\newcommand{\stsetrole}[2]{\expandafter\gdef\csname st@role@#1\endcsname{#2}}
|
% declaration is harmless; changing it would make one role change meaning
|
||||||
|
% halfway through the figure, so report it as a dirty-build warning.
|
||||||
|
\newcommand{\stsetrole}[2]{%
|
||||||
|
\edef\st@newrole{#2}%
|
||||||
|
\ifcsname st@role@#1\endcsname
|
||||||
|
\edef\st@oldrole{\csname st@role@#1\endcsname}%
|
||||||
|
\ifx\st@oldrole\st@newrole\else
|
||||||
|
\PackageWarning{supertensor}{Role `#1' was already mapped to
|
||||||
|
`\st@oldrole' and cannot be remapped to `\st@newrole'}%
|
||||||
|
\fi
|
||||||
|
\else
|
||||||
|
\expandafter\gdef\csname st@role@#1\endcsname{#2}%
|
||||||
|
\fi}
|
||||||
% Expandable on purpose: usable inside \edef. Undeclared roles fall back to
|
% Expandable on purpose: usable inside \edef. Undeclared roles fall back to
|
||||||
% neutral gray and are reported at the end of the run.
|
% neutral gray and are reported at the end of the run.
|
||||||
\newcommand{\strole}[1]{%
|
\newcommand{\strole}[1]{%
|
||||||
@@ -68,7 +80,17 @@
|
|||||||
% then every face built from `d' has the same physical edge, everywhere.
|
% then every face built from `d' has the same physical edge, everywhere.
|
||||||
\newlength{\stunit}\setlength{\stunit}{4.6mm}
|
\newlength{\stunit}\setlength{\stunit}{4.6mm}
|
||||||
\newlength{\sttilegap}\setlength{\sttilegap}{0.5mm}
|
\newlength{\sttilegap}\setlength{\sttilegap}{0.5mm}
|
||||||
\newcommand{\stdim}[2]{\expandafter\gdef\csname st@dim@#1\endcsname{#2}}
|
\newcommand{\stdim}[2]{%
|
||||||
|
\edef\st@newdim{#2}%
|
||||||
|
\ifcsname st@dim@#1\endcsname
|
||||||
|
\edef\st@olddim{\csname st@dim@#1\endcsname}%
|
||||||
|
\ifx\st@olddim\st@newdim\else
|
||||||
|
\PackageWarning{supertensor}{Axis `#1' was already declared as
|
||||||
|
`\st@olddim' cells and cannot be redeclared as `\st@newdim'}%
|
||||||
|
\fi
|
||||||
|
\else
|
||||||
|
\expandafter\gdef\csname st@dim@#1\endcsname{#2}%
|
||||||
|
\fi}
|
||||||
\newcommand{\stresolve}[1]{\ifcsname st@dim@#1\endcsname\csname st@dim@#1\endcsname\else#1\fi}
|
\newcommand{\stresolve}[1]{\ifcsname st@dim@#1\endcsname\csname st@dim@#1\endcsname\else#1\fi}
|
||||||
|
|
||||||
% --------------------------------------------------------- type hierarchy ---
|
% --------------------------------------------------------- type hierarchy ---
|
||||||
@@ -92,6 +114,280 @@
|
|||||||
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@ingroup
|
||||||
|
\newif\ifst@first
|
||||||
|
\newcount\st@gcount % members registered by the open group
|
||||||
|
\newif\ifst@ghascol % the open group contains a \stcol partition
|
||||||
|
|
||||||
|
\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 \global\st@ingroupfalse
|
||||||
|
\gdef\st@rowlist{}\gdef\st@alllist{}\gdef\st@rowname{}%
|
||||||
|
\gdef\st@collist{}\gdef\st@colname{}%
|
||||||
|
\gdef\st@grouplist{}\gdef\st@gname{}%
|
||||||
|
\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.
|
||||||
|
% A group behaves the same way: the wrapper, not the wrapped face, is what a
|
||||||
|
% connector may attach to. Otherwise the arrow ends inside the outline and
|
||||||
|
% crosses a box that is not its endpoint.
|
||||||
|
\newcommand{\st@regrow}[1]{%
|
||||||
|
\ifst@incol
|
||||||
|
\xdef\st@collist{\st@collist(#1)}\st@regall{#1}%
|
||||||
|
\else\ifst@ingroup
|
||||||
|
\xdef\st@grouplist{\st@grouplist(#1)}\st@regall{#1}%
|
||||||
|
\global\advance\st@gcount by 1
|
||||||
|
\else
|
||||||
|
\xdef\st@rowlist{\st@rowlist(#1)}\st@regall{#1}%
|
||||||
|
\st@drawpendinglink{#1}%
|
||||||
|
\gdef\st@lastnode{#1}%
|
||||||
|
\fi\fi}
|
||||||
|
% Bracket arms are drawn OUTSIDE the node box, so a fit over the node alone is
|
||||||
|
% smaller than the ink. Register the bracket's extreme coordinates with
|
||||||
|
% whatever fit will enclose the node, or a group outline (pad 1.6mm, overhang
|
||||||
|
% 2.2mm) is crossed by the bracket it claims to contain.
|
||||||
|
\newcommand{\st@regink}[1]{%
|
||||||
|
\ifst@incol \xdef\st@collist{\st@collist#1}%
|
||||||
|
\else\ifst@ingroup \xdef\st@grouplist{\st@grouplist#1}%
|
||||||
|
\else \xdef\st@rowlist{\st@rowlist#1}%
|
||||||
|
\fi\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}%
|
||||||
|
\ifst@ingroup\global\st@ghascoltrue\fi
|
||||||
|
\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}%
|
||||||
|
% Sub-flow members never terminate a pending connector (\st@regrow), so a
|
||||||
|
% link opened inside one would build cleanly and draw no arrow at all.
|
||||||
|
\ifst@ingroup
|
||||||
|
\PackageError{supertensor}{\string\stlink\space inside \string\stgroup}%
|
||||||
|
{A group member cannot terminate a connector, so the arrow would be
|
||||||
|
dropped silently. Close the group first; the link then attaches to the
|
||||||
|
outline itself.}%
|
||||||
|
\fi
|
||||||
|
\ifst@incol
|
||||||
|
\PackageError{supertensor}{\string\stlink\space inside \string\stcol}%
|
||||||
|
{A column item cannot terminate a connector. Close the column first;
|
||||||
|
the link then attaches to the column as a whole.}%
|
||||||
|
\fi
|
||||||
|
\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,13 +398,65 @@
|
|||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
% Validate face keys before drawing. Unknown patterns used to fall through to
|
||||||
|
% dense, which produced a plausible but semantically false picture.
|
||||||
|
\newif\ifst@validpattern
|
||||||
|
\newcommand{\st@checkpattern}{%
|
||||||
|
\st@validpatternfalse
|
||||||
|
\foreach \st@known in {solid,dense,diag,band,lower,upper,causal,empty,data}{%
|
||||||
|
\IfStrEq{\st@pattern}{\st@known}{\global\st@validpatterntrue}{}}%
|
||||||
|
\ifst@validpattern\else
|
||||||
|
\PackageWarning{supertensor}{Unknown face pattern `\st@pattern'.
|
||||||
|
Use solid, dense, diag, band, lower, upper, causal, empty, or data}%
|
||||||
|
\fi}
|
||||||
|
\newcommand{\st@checklevel}{%
|
||||||
|
\IfInteger{\st@level}{%
|
||||||
|
\ifnum\st@level<0
|
||||||
|
\PackageWarning{supertensor}{Face level `\st@level' is outside 0--3}%
|
||||||
|
\def\st@level{2}%
|
||||||
|
\else\ifnum\st@level>3
|
||||||
|
\PackageWarning{supertensor}{Face level `\st@level' is outside 0--3}%
|
||||||
|
\def\st@level{2}%
|
||||||
|
\fi\fi
|
||||||
|
}{\PackageWarning{supertensor}{Face level `\st@level' is not an integer in 0--3}%
|
||||||
|
\def\st@level{2}}}
|
||||||
|
|
||||||
|
% pattern=data is a rectangular rows-by-cols array of digits 0--3. Validate
|
||||||
|
% all three facts before \StrChar reaches a missing or malformed cell.
|
||||||
|
\newcommand{\st@checkdata}{%
|
||||||
|
\IfStrEq{\st@pattern}{data}{%
|
||||||
|
\def\st@datacount{0}%
|
||||||
|
\foreach \st@drow [count=\st@di] in \st@data {\xdef\st@datacount{\st@di}}%
|
||||||
|
\ifnum\st@datacount=\st@rows\else
|
||||||
|
\PackageWarning{supertensor}{pattern=data has \st@datacount\space rows;
|
||||||
|
expected \st@rows}%
|
||||||
|
\fi
|
||||||
|
\foreach \st@drow in \st@data {%
|
||||||
|
\StrLen{\st@drow}[\st@dlen]%
|
||||||
|
\ifnum\st@dlen=\st@cols\else
|
||||||
|
\PackageWarning{supertensor}{Data row `\st@drow' has \st@dlen\space cells;
|
||||||
|
expected \st@cols}%
|
||||||
|
\fi
|
||||||
|
\edef\st@badchars{\st@drow}%
|
||||||
|
\StrSubstitute{\st@badchars}{0}{}[\st@badchars]%
|
||||||
|
\StrSubstitute{\st@badchars}{1}{}[\st@badchars]%
|
||||||
|
\StrSubstitute{\st@badchars}{2}{}[\st@badchars]%
|
||||||
|
\StrSubstitute{\st@badchars}{3}{}[\st@badchars]%
|
||||||
|
\ifdefempty{\st@badchars}{}{%
|
||||||
|
\PackageWarning{supertensor}{Data row `\st@drow' contains values outside 0--3}}%
|
||||||
|
}%
|
||||||
|
}{}}
|
||||||
|
|
||||||
% \st@hash{i}{j}{n} -> \st@hv in 0..n-1.
|
% \st@hash{i}{j}{n} -> \st@hv in 0..n-1.
|
||||||
% Nested mods on purpose. Any polynomial in (i,j) reduced mod 3 is periodic
|
% Nested mods on purpose. Any polynomial in (i,j) reduced mod 3 is periodic
|
||||||
% with period 3 in BOTH directions, so a polynomial hash makes rows 1,2,4,5 of
|
% with period 3 in BOTH directions, so a polynomial hash makes rows 1,2,4,5 of
|
||||||
@@ -143,6 +491,9 @@
|
|||||||
\pgfkeys{/st/face/.cd,#1}%
|
\pgfkeys{/st/face/.cd,#1}%
|
||||||
\edef\st@rows{\stresolve{#2}}%
|
\edef\st@rows{\stresolve{#2}}%
|
||||||
\edef\st@cols{\stresolve{#3}}%
|
\edef\st@cols{\stresolve{#3}}%
|
||||||
|
\st@checkpattern
|
||||||
|
\st@checklevel
|
||||||
|
\st@checkdata
|
||||||
\stcheckrole{\st@role}%
|
\stcheckrole{\st@role}%
|
||||||
\edef\st@col{\strole{\st@role}}%
|
\edef\st@col{\strole{\st@role}}%
|
||||||
\pgfmathsetlengthmacro{\st@w}{\st@cols*\stunit}%
|
\pgfmathsetlengthmacro{\st@w}{\st@cols*\stunit}%
|
||||||
@@ -153,17 +504,37 @@
|
|||||||
% `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}%
|
||||||
|
\ifst@bracket\st@regink{(#2-inkw)(#2-inke)}\fi
|
||||||
|
}{\st@facecore{#2}{#3}}%
|
||||||
\endgroup}
|
\endgroup}
|
||||||
|
|
||||||
% \stindexface[keys]{name}{center coord}{rows}{cols}{entries}
|
% \stindexface[keys]{name}{center coord}{rows}{cols}{entries}
|
||||||
@@ -175,8 +546,18 @@
|
|||||||
\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]
|
\def\st@indexcount{0}%
|
||||||
(#2) at #3 {};
|
\foreach \st@e [count=\st@zi] in {#6} {\xdef\st@indexcount{\st@zi}}%
|
||||||
|
\pgfmathtruncatemacro{\st@indexexpected}{\st@rows*\st@cols}%
|
||||||
|
\ifnum\st@indexcount=\st@indexexpected\else
|
||||||
|
\PackageWarning{supertensor}{Index face `#2' has \st@indexcount\space entries;
|
||||||
|
expected \st@indexexpected\space for \st@rows\space x \st@cols}%
|
||||||
|
\fi
|
||||||
|
\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@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 +577,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]{%
|
||||||
@@ -205,9 +587,10 @@
|
|||||||
\foreach \st@row [count=\st@ii] in \st@data {%
|
\foreach \st@row [count=\st@ii] in \st@data {%
|
||||||
\foreach \st@jj in {1,...,\st@cols} {%
|
\foreach \st@jj in {1,...,\st@cols} {%
|
||||||
\StrChar{\st@row}{\st@jj}[\st@c]%
|
\StrChar{\st@row}{\st@jj}[\st@c]%
|
||||||
|
\IfInteger{\st@c}{%
|
||||||
\ifnum\st@c>0
|
\ifnum\st@c>0
|
||||||
\st@tile{#1}{\st@ii}{\st@jj}{\st@c}%
|
\ifnum\st@c<4 \st@tile{#1}{\st@ii}{\st@jj}{\st@c}\fi
|
||||||
\fi}}%
|
\fi}{} }}%
|
||||||
}{%
|
}{%
|
||||||
\foreach \st@ii in {1,...,\st@rows} {%
|
\foreach \st@ii in {1,...,\st@rows} {%
|
||||||
\foreach \st@jj in {1,...,\st@cols} {%
|
\foreach \st@jj in {1,...,\st@cols} {%
|
||||||
@@ -231,6 +614,10 @@
|
|||||||
\draw[black!55, line width=0.5pt]
|
\draw[black!55, line width=0.5pt]
|
||||||
($(#1.north east)+(1.1mm,0.6mm)$) -- ++(1.1mm,0)
|
($(#1.north east)+(1.1mm,0.6mm)$) -- ++(1.1mm,0)
|
||||||
-- ($(#1.south east)+(2.2mm,-0.6mm)$) -- ++(-1.1mm,0);
|
-- ($(#1.south east)+(2.2mm,-0.6mm)$) -- ++(-1.1mm,0);
|
||||||
|
% The true ink extent, for \st@regink: fits over the bare node undershoot
|
||||||
|
% the bracket by 2.2mm horizontally and 0.6mm vertically.
|
||||||
|
\coordinate (#1-inkw) at ($(#1.south west)+(-2.2mm,-0.6mm)$);
|
||||||
|
\coordinate (#1-inke) at ($(#1.north east)+(2.2mm,0.6mm)$);
|
||||||
\fi}
|
\fi}
|
||||||
|
|
||||||
% \st@tile{face}{row}{col}{level} -- one rounded tile with a white gutter.
|
% \st@tile{face}{row}{col}{level} -- one rounded tile with a white gutter.
|
||||||
@@ -250,9 +637,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,8 +664,92 @@
|
|||||||
\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}%
|
||||||
|
\ifst@bracket\st@regink{(#2-front-inkw)(#2-front-inke)}\fi}{}%
|
||||||
\endgroup}
|
\endgroup}
|
||||||
|
|
||||||
|
% ------------------------------------------------------------- grouping ----
|
||||||
|
% \stgroup[keys]{name} ... \stgroupend -- a thin rounded outline naming the
|
||||||
|
% objects drawn between them as one composite ("these three sheets are q";
|
||||||
|
% "these two shards are W"). A sub-flow, like \stcol, and for the same reason:
|
||||||
|
%
|
||||||
|
% - the members are drawn INSIDE the block, so a group can only ever wrap
|
||||||
|
% adjacent objects -- one that reached across the band would swallow
|
||||||
|
% whatever sat in between;
|
||||||
|
% - the group, not the 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 the outline.
|
||||||
|
%
|
||||||
|
% This is the one place a tensor-colored outline is allowed, because here the
|
||||||
|
% outline IS the object being drawn -- see style.md.
|
||||||
|
\newlength{\stgrouppad}\setlength{\stgrouppad}{1.6mm}
|
||||||
|
\pgfkeys{
|
||||||
|
/st/group/.cd,
|
||||||
|
role/.store in=\st@grole,
|
||||||
|
pad/.store in=\st@gpad,
|
||||||
|
% No default role on purpose: a homogeneous group silently drawn in neutral
|
||||||
|
% gray violates the "outline in the composite's own hue" rule without any
|
||||||
|
% signal. A genuinely mixed group passes role=neutral explicitly.
|
||||||
|
role={}, pad=\stgrouppad,
|
||||||
|
}
|
||||||
|
\newcommand{\stgroup}[2][]{%
|
||||||
|
\st@needrow{\stgroup}%
|
||||||
|
\ifst@ingroup
|
||||||
|
\PackageError{supertensor}{Nested \string\stgroup}%
|
||||||
|
{Close the open group with \string\stgroupend\space first.}%
|
||||||
|
\fi
|
||||||
|
\ifst@incol
|
||||||
|
\PackageError{supertensor}{\string\stgroup\space inside \string\stcol}%
|
||||||
|
{A group reserves its padding along the band, not down the column. Wrap
|
||||||
|
the whole \string\stcol\space in the group instead.}%
|
||||||
|
\fi
|
||||||
|
\pgfkeys{/st/group/.cd,#1}%
|
||||||
|
\ifdefempty{\st@grole}{%
|
||||||
|
\PackageError{supertensor}{\string\stgroup\space `#2' has no role}%
|
||||||
|
{The outline is drawn in the hue of the composite it names. Pass
|
||||||
|
role=<declared role>, or role=neutral explicitly for a mixed group.}%
|
||||||
|
\def\st@grole{neutral}}{}%
|
||||||
|
\stcheckrole{\st@grole}%
|
||||||
|
% Snapshot everything the closing macro needs: the keys are re-read by the
|
||||||
|
% next face and the role ledger lookup must not be deferred.
|
||||||
|
\xdef\st@gname{#2}%
|
||||||
|
\xdef\st@gcolname{\strole{\st@grole}}%
|
||||||
|
\xdef\st@gpadval{\st@gpad}%
|
||||||
|
\st@leadgap{\stgutter}%
|
||||||
|
\global\advance\st@cx by \dimexpr\st@gpad\relax
|
||||||
|
\gdef\st@grouplist{}%
|
||||||
|
\global\st@gcount=0 \global\st@ghascolfalse
|
||||||
|
\global\st@ingrouptrue\global\st@firsttrue}
|
||||||
|
|
||||||
|
\newcommand{\stgroupend}{%
|
||||||
|
\global\st@ingroupfalse
|
||||||
|
\ifdefempty{\st@grouplist}%
|
||||||
|
{\PackageWarning{supertensor}{Empty \string\stgroup\space `\st@gname'}}%
|
||||||
|
{\global\advance\st@cx by \dimexpr\st@gpadval\relax
|
||||||
|
\begin{pgfonlayer}{stbg}
|
||||||
|
\edef\st@do{\noexpand\node[inner sep=\st@gpadval, outer sep=0pt,
|
||||||
|
draw=\st@gcolname!65, line width=0.5pt, rounded corners=2pt,
|
||||||
|
fit={\st@grouplist}] (\st@gname) {};}\st@do
|
||||||
|
\end{pgfonlayer}
|
||||||
|
\global\st@firstfalse
|
||||||
|
% A group must add information the members do not already carry: at least
|
||||||
|
% two adjacent objects, or one \stcol partition. Around a single face or
|
||||||
|
% stack the outline is decoration -- the stack already reads as one thing.
|
||||||
|
\ifnum\st@gcount<2
|
||||||
|
\ifst@ghascol\else
|
||||||
|
\PackageWarning{supertensor}{Group `\st@gname' wraps a single object.
|
||||||
|
Bind at least two members or one \string\stcol\space partition;
|
||||||
|
a lone stack or face already reads as one composite}%
|
||||||
|
\fi
|
||||||
|
\fi
|
||||||
|
% No \st@checkh: the overhang is 2*pad by construction, not driven by
|
||||||
|
% content, and the members were already checked against the band. Warning
|
||||||
|
% about it would only teach authors to inflate the declared band height.
|
||||||
|
% \strowend fits the group, so the caption lane still clears it.
|
||||||
|
\edef\st@do{\noexpand\st@regrow{\st@gname}}\st@do}}
|
||||||
|
|
||||||
% ------------------------------------------------- symbol / shape captions ---
|
% ------------------------------------------------- symbol / shape captions ---
|
||||||
% Symbol immediately under the block, shape on the next line. Both are reserved
|
% Symbol immediately under the block, shape on the next line. Both are reserved
|
||||||
% lanes: nothing else may be placed between a face and its caption.
|
% lanes: nothing else may be placed between a face and its caption.
|
||||||
@@ -286,7 +765,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};}
|
||||||
|
|
||||||
@@ -301,6 +781,32 @@
|
|||||||
\draw[st arrow,#1] (#2) -- node[st note, above, fill=white, inner sep=1pt] {#4} (#3);
|
\draw[st arrow,#1] (#2) -- node[st note, above, fill=white, inner sep=1pt] {#4} (#3);
|
||||||
\end{pgfonlayer}}
|
\end{pgfonlayer}}
|
||||||
|
|
||||||
|
% --------------------------------------------------------------- callout ---
|
||||||
|
% \stcallout{name}{text width}{anchor}{title}{body} -- a side note card hanging
|
||||||
|
% off the RIGHT edge of a finished band, top-aligned with it.
|
||||||
|
%
|
||||||
|
% It is deliberately hard to misuse. Inside an open band it is an error: a
|
||||||
|
% commentary card between two operands reads as a step in the computation,
|
||||||
|
% which is the antipattern layout.md names. Outside one it still pushes the
|
||||||
|
% vertical cursor below its own bottom edge, so a card taller than its band
|
||||||
|
% opens visible space rather than colliding with the next stage -- which is the
|
||||||
|
% signal that the text belongs in \stmeaningbox instead.
|
||||||
|
\newcommand{\stcallout}[5]{%
|
||||||
|
\ifst@inrow
|
||||||
|
\PackageError{supertensor}{\string\stcallout\space inside an open
|
||||||
|
\string\strow}%
|
||||||
|
{A callout is an aside, not an operand. Close the band with
|
||||||
|
\string\strowend\space first. If the text explains a step rather than
|
||||||
|
the band, it belongs in the stage heading or \string\stmeaningbox.}%
|
||||||
|
\fi
|
||||||
|
\node[anchor=north west, draw=black!18, fill=black!3, rounded corners=1.5pt,
|
||||||
|
inner xsep=7pt, inner ysep=6pt, text width=#2] (#1)
|
||||||
|
at ([xshift=\stgutter]#3.north east) {%
|
||||||
|
\scriptsize\raggedright
|
||||||
|
\ifblank{#4}{}{\textbf{#4}\par\vspace{1.5pt}}%
|
||||||
|
#5\par};
|
||||||
|
\sttrack{#1}}
|
||||||
|
|
||||||
% ------------------------------------------------------------ meaning box ---
|
% ------------------------------------------------------------ meaning box ---
|
||||||
\ifst@en
|
\ifst@en
|
||||||
\def\st@lblaxes{Axes}\def\st@lblobj{Objects}\def\st@lblmech{Mechanism}
|
\def\st@lblaxes{Axes}\def\st@lblobj{Objects}\def\st@lblmech{Mechanism}
|
||||||
@@ -330,10 +836,8 @@
|
|||||||
\newcommand{\stsetrail}[1]{\setlength{\st@raillen}{#1}}
|
\newcommand{\stsetrail}[1]{\setlength{\st@raillen}{#1}}
|
||||||
|
|
||||||
% ------------------------------------------------------------- signature ----
|
% ------------------------------------------------------------- signature ----
|
||||||
% One centered identification line, outside the meaning box, low contrast.
|
% Optional centered subject line, outside the meaning box, low contrast.
|
||||||
\def\st@author{五道口纳什}
|
|
||||||
\newcommand{\stsetauthor}[1]{\def\st@author{#1}}
|
|
||||||
\newcommand{\stsignature}[2]{%
|
\newcommand{\stsignature}[2]{%
|
||||||
\node[below=2.2mm of #2, font=\scriptsize, text=black!45] (st-signature) {#1@\st@author};}
|
\node[below=2.2mm of #2, font=\scriptsize, text=black!45] (#2-sig) {#1};}
|
||||||
|
|
||||||
\endinput
|
\endinput
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
% Anti-pattern gallery -- four ways to draw a figure that compiles cleanly and
|
% Anti-pattern gallery -- four ways to draw a figure that compiles cleanly and
|
||||||
% still teaches the reader something false. Left of each pair is wrong.
|
% still teaches the reader something false. Left of each pair is wrong.
|
||||||
% ../scripts/build.sh antipatterns.tex
|
% ../scripts/build.sh antipatterns.tex
|
||||||
|
% supertensor-lint: allow-absolute, allow-missing-formula
|
||||||
\documentclass[border=10pt]{standalone}
|
\documentclass[border=10pt]{standalone}
|
||||||
\usepackage[cjk]{supertensor}
|
\usepackage[cjk]{supertensor}
|
||||||
|
|
||||||
|
|||||||
+44
-56
@@ -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}{}{dh}{T}{3}
|
||||||
\ststack[role=k, bracket=true]{KT}{($(mA)+(1.9,0)$)}{dh}{T}{3}
|
\stglyph{eA}{$=$}
|
||||||
\node[st op, right=6mm of KT] (eA) {$=$};
|
\ststack[role=s]{S}{}{T}{T}{3}
|
||||||
\ststack[role=s]{S}{($(eA)+(2.0,0)$)}{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
|
||||||
|
% triangle, no stack -- it is shared by every head.
|
||||||
% The mask is a Boolean support, not a magnitude: one flat level, exact
|
\stface[role=w, pattern=data,
|
||||||
% triangle, no stack -- it is shared by every head.
|
data={300000,330000,333000,333300,333330,333333}]{M}{}{T}{T}
|
||||||
\stface[role=w, pattern=data,
|
\stlink{lB}{softmax}
|
||||||
data={300000,330000,333000,333300,333330,333333}]{M}{(b)}{T}{T}
|
\ststack[role=s, pattern=causal]{A}{}{T}{T}{3}
|
||||||
\ststack[role=s, pattern=causal]{A}{($(M.east)+(3.75,0)$)}{T}{T}{3}
|
\stglyph{mB}{$\times$}
|
||||||
\starrowlabel{M.east}{A.west}{softmax}
|
\ststack[role=v, bracket=true]{V}{}{T}{dh}{3}
|
||||||
\node[st op, right=6mm of A] (mB) {$\times$};
|
\stglyph{eB}{$=$}
|
||||||
\ststack[role=v, bracket=true]{V}{($(mB)+(1.4,0)$)}{T}{dh}{3}
|
\ststack[role=v]{O}{}{T}{dh}{3}
|
||||||
\node[st op, right=6mm of V] (eB) {$=$};
|
\strowend
|
||||||
\ststack[role=v]{O}{($(eB)+(1.4,0)$)}{T}{dh}{3}
|
|
||||||
|
|
||||||
\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$ 省略}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
% than expert 0, so the index face gets no lightness ramp.
|
||||||
% Indices are drawn as symbols, not as magnitudes: expert 3 is not "bigger"
|
\stindexface[role=idx]{I}{}{T}{k}{0,1, 1,2, 2,3, 3,0, 0,2, 1,3}
|
||||||
% than expert 0, so the index face gets no lightness ramp.
|
\stlink{lb}{one-hot}
|
||||||
\stindexface[role=idx]{I}{(b)}{T}{k}{0,1, 1,2, 2,3, 3,0, 0,2, 1,3}
|
% 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}{}{T}{E}
|
||||||
data={3300,0330,0033,3003,3030,0303}]{D}{($(I.east)+(3.1,0)$)}{T}{E}
|
\stlink{lg}{}
|
||||||
\starrowlabel{I.east}{D.west}{one-hot}
|
\stcomm{gz}{Gather}
|
||||||
|
\strowend
|
||||||
\node[st comm, right=9mm of D] (gz) {Gather};
|
|
||||||
\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 数}
|
||||||
|
|||||||
@@ -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$)}
|
||||||
|
|||||||
@@ -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,14 +41,31 @@ 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, the bottom box, or a `\stcallout` beside the whole band.
|
||||||
|
A card anchored to a single face reads as a step in the computation, and two cards on one
|
||||||
|
band turn the figure into a dashboard; both are lint errors.
|
||||||
|
- **A callout that should have been the meaning box.** If the card is taller than the band
|
||||||
|
it hangs off, it is not an aside — it is the **Mechanism** row, and leaving it as a card
|
||||||
|
only opens white space, since the callout pushes the vertical cursor below itself.
|
||||||
|
- **A group border used as decoration.** `\stgroup` names its members as one composite
|
||||||
|
object; drawn around whatever happened to be adjacent, it invents a grouping the
|
||||||
|
computation does not have. If you cannot caption the outline, do not draw it.
|
||||||
|
- **A group whose hue invents a new object.** The outline around the three `q` sheets is
|
||||||
|
still `q`. A fresh hue there claims a fourth tensor exists; use the members' role, or
|
||||||
|
`neutral` when the members really are of mixed roles. See `semantics.md`.
|
||||||
- **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
|
||||||
box is for what the axes *mean* and what the operation *does*.
|
box is for what the axes *mean* and what the operation *does*.
|
||||||
- **Solving crowding by shrinking type.** The type hierarchy is a hard floor; move the
|
- **Solving crowding by shrinking type.** The type hierarchy is a hard floor; move the
|
||||||
|
|||||||
+167
-12
@@ -8,32 +8,164 @@
|
|||||||
Build with `./scripts/build.sh fig.tex` — it puts `assets/` on `TEXINPUTS`, so the package
|
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.
|
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
|
## Ledgers
|
||||||
|
|
||||||
```tex
|
```tex
|
||||||
\stsetrole{q}{stTeal} % role -> color. Macros take a ROLE, never a color.
|
\stsetrole{q}{stTeal} % role -> color. Macros take a ROLE, never a color.
|
||||||
\stdim{T}{6} % symbolic axis -> physical edge length in cells
|
\stdim{T}{6} % symbolic axis -> physical edge length in cells
|
||||||
\stsetauthor{...} % default 五道口纳什
|
|
||||||
\stsetlabels{A}{O}{M} % override the three meaning-box rail labels
|
\stsetlabels{A}{O}{M} % override the three meaning-box rail labels
|
||||||
\stsetrail{3.2em} % width of the bold label rail
|
\stsetrail{3.2em} % width of the bold label rail
|
||||||
```
|
```
|
||||||
|
|
||||||
Colors: `stTeal stOrange stCoral stViolet stGray stInk`. An unknown role falls back to gray
|
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.
|
**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).
|
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
|
## 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,9 +178,12 @@ 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`.
|
||||||
|
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
|
```tex
|
||||||
\stface[role=w, pattern=data, level=3,
|
\stface[role=w, pattern=data, level=3,
|
||||||
@@ -58,12 +193,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 +215,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 +231,16 @@ 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} % optional
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
`\stsignature` renders only its subject; it has no author or handle mechanism.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
@@ -101,3 +250,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`.
|
||||||
|
|||||||
+14
-5
@@ -1,7 +1,8 @@
|
|||||||
# Pre-delivery checklist
|
# Pre-delivery checklist
|
||||||
|
|
||||||
A clean `build.sh` proves only that TeX was happy. Nothing below is checked by the compiler.
|
`build.sh` already checks the source rules, package invariants, missing glyphs and text-box
|
||||||
Work through it against the rendered PNG. Any mandatory violation means redraw, not patch.
|
overflow. The math, semantics and rendered relationships below still require inspection.
|
||||||
|
Any mandatory violation means redraw, not patch.
|
||||||
|
|
||||||
## 1. Math (before looking at the picture)
|
## 1. Math (before looking at the picture)
|
||||||
|
|
||||||
@@ -26,17 +27,25 @@ 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.
|
||||||
|
|
||||||
|
- [ ] Every lint exemption in the source is genuinely required and explained; deliverables
|
||||||
|
normally have none.
|
||||||
- [ ] 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.
|
||||||
- [ ] No connector crosses a box that is not its endpoint.
|
- [ ] No connector crosses a box that is not its endpoint.
|
||||||
- [ ] Symbols and shapes sit on two flat lanes per row; stage headings share a left rail.
|
- [ ] Symbols and shapes sit on two flat lanes per row; stage headings share a left rail.
|
||||||
|
- [ ] Every `\stgroup` outline names a composite the computation actually has, binds at
|
||||||
|
least two members or a `\stcol` partition, is captioned, and carries its members'
|
||||||
|
role hue (`neutral` only, and explicitly, for genuinely mixed members).
|
||||||
|
- [ ] At most one `\stcallout` in the figure (exemption comment if more), an aside about
|
||||||
|
its whole band, no taller than it, nothing in it that belongs in `\stmeaningbox`.
|
||||||
- [ ] Top zone compact (≤2 formula lines, no shape underbraces).
|
- [ ] Top zone compact (≤2 formula lines, no shape underbraces).
|
||||||
- [ ] Bottom box: one column, ≤3 rows, no overflow, no shrunken type.
|
- [ ] Bottom box: one column, ≤3 rows, no overflow, no shrunken type.
|
||||||
- [ ] Signature outside the box, one line, names what the figure actually shows, not clipped
|
- [ ] If requested, signature is outside the box, one line, accurate, unclipped and subdued.
|
||||||
and not visually dominant.
|
|
||||||
- [ ] Structural support exact: known zeros unfilled, masks and diagonals exactly right.
|
- [ ] Structural support exact: known zeros unfilled, masks and diagonals exactly right.
|
||||||
|
|
||||||
## 5. Thumbnail audit
|
## 5. Thumbnail audit
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Fallback without LaTeX
|
||||||
|
|
||||||
|
Use this path only when `scripts/preflight.sh` exits 2. A fallback is not equivalent to the
|
||||||
|
TikZ package: say explicitly that cursor placement, ledger locking, face-data validation,
|
||||||
|
caption lanes and package warnings are unavailable.
|
||||||
|
|
||||||
|
## Preserve manually
|
||||||
|
|
||||||
|
1. Recompute the shape/semantics ledger before drawing.
|
||||||
|
2. Use one scale function from symbolic axis names to physical lengths; never size two
|
||||||
|
occurrences of the same axis independently.
|
||||||
|
3. Keep scores, indices and masks in distinct grammars; preserve exact zeros and support.
|
||||||
|
4. Derive every placement from previous bounding boxes and one gutter constant.
|
||||||
|
5. Export SVG plus PNG and inspect both full-size and at 360 px using `checklist.md`.
|
||||||
|
|
||||||
|
Prefer SVG for editability. Use matplotlib only when it can emit SVG and the tensor cells
|
||||||
|
remain individually inspectable. Do not imitate package compliance in the delivery: name
|
||||||
|
the fallback renderer and list any inferred shape, convention or reduced guarantee.
|
||||||
@@ -17,8 +17,12 @@ to exactly one physical edge length across the whole figure**. Equal shapes ther
|
|||||||
an equivalence class automatically: `Q` and `V` at `T×d_h` come out identical without you
|
an equivalence class automatically: `Q` and `V` at `T×d_h` come out identical without you
|
||||||
lining anything up by hand.
|
lining anything up by hand.
|
||||||
|
|
||||||
|
Declaring the same axis twice with the same value is allowed. Redeclaring it with a
|
||||||
|
different value emits a package warning, preserves the first value, and fails `build.sh`.
|
||||||
|
|
||||||
Raw integers are accepted (`\stface{A}{(0,0)}{4}{4}`) but they opt out of the guarantee.
|
Raw integers are accepted (`\stface{A}{(0,0)}{4}{4}`) but they opt out of the guarantee.
|
||||||
Use them only for a face whose axis appears nowhere else.
|
Use them only for a face whose axis appears nowhere else. The source linter rejects a
|
||||||
|
repeated raw dimension greater than one; give repeated dimensions a symbolic name.
|
||||||
|
|
||||||
## The rules
|
## The rules
|
||||||
|
|
||||||
|
|||||||
+52
-21
@@ -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:
|
||||||
|
|
||||||
@@ -16,6 +34,7 @@ Overlap is allowed only inside one declared composite:
|
|||||||
- shards tiling a parent,
|
- shards tiling a parent,
|
||||||
- outline sheets in one `\ststack`,
|
- outline sheets in one `\ststack`,
|
||||||
- a bracket around its own tensor,
|
- a bracket around its own tensor,
|
||||||
|
- a `\stgroup` outline around its own members,
|
||||||
- a connector endpoint touching its source/target border.
|
- a connector endpoint touching its source/target border.
|
||||||
|
|
||||||
Every other intersection or occlusion is forbidden.
|
Every other intersection or occlusion is forbidden.
|
||||||
@@ -33,30 +52,40 @@ 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}
|
...
|
||||||
|
\strowend
|
||||||
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
|
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
|
||||||
...
|
|
||||||
\stnolane
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
Explanatory prose belongs in the stage subtitle, the bottom box, above its own connector,
|
||||||
\coordinate (cy) at ($(I-shape.south)+(0,-9mm)$);
|
or on a `\stcallout` card hanging off the right edge of a band. Never drop a floating
|
||||||
\node[st stage, anchor=north west] (SC) at (SB.west |- cy) {...};
|
commentary card between two operands unless it is a real operation node (`st comm`).
|
||||||
```
|
|
||||||
|
|
||||||
Explanatory prose belongs in the stage subtitle, the bottom box, or above its own
|
`\stcallout` is that rule made structural: it refuses to open inside a band, the linter
|
||||||
connector. Never drop a floating commentary card between two operands unless it is a real
|
rejects it unless it is anchored to a `\strow` name, and a second card in the figure is an
|
||||||
operation node (`st comm`).
|
error without an explicit exemption — a card per band is a dashboard, not a figure. A card that ends up much taller
|
||||||
|
than its band is telling you the same thing the overflow warning does: that text is not an
|
||||||
|
aside, it is the **Mechanism** row of `\stmeaningbox`.
|
||||||
|
|
||||||
|
## Composites
|
||||||
|
|
||||||
|
`\stcol` and `\stgroup` are the two sub-flows, and both exist so that a *relationship* can
|
||||||
|
be stated rather than arranged for. A group wraps its members from the inside, which is
|
||||||
|
what makes an arrow attach to the outline rather than end inside it — a connector whose
|
||||||
|
endpoint is a member but which crosses the group border is the ordinary version of "a
|
||||||
|
connector may not cross a box that is not its endpoint".
|
||||||
|
|
||||||
## Layers
|
## Layers
|
||||||
|
|
||||||
@@ -67,8 +96,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
|
||||||
|
|
||||||
|
|||||||
@@ -65,3 +65,9 @@ One tensor role keeps one hue for the whole figure — that is what `\stsetrole`
|
|||||||
gathered, resharded or regrouped view of the same data keeps the *same* role color; a new
|
gathered, resharded or regrouped view of the same data keeps the *same* role color; a new
|
||||||
hue means a new object. Derived tensors may reuse their parent's family rather than
|
hue means a new object. Derived tensors may reuse their parent's family rather than
|
||||||
spending a hue (`V → O → Y` in the MHA example are all violet).
|
spending a hue (`V → O → Y` in the MHA example are all violet).
|
||||||
|
|
||||||
|
A `\stgroup` outline follows the same rule, because a group *is* a regrouped view: give it
|
||||||
|
the role of the objects it wraps — the three-sheet `q` stack and the outline that names it
|
||||||
|
as one composite are the same object, and a new hue there would claim a new tensor exists.
|
||||||
|
Only when the members genuinely differ in role does the group take `role=neutral`; that is
|
||||||
|
also the honest signal that the box is naming an arrangement rather than an object.
|
||||||
|
|||||||
+18
-6
@@ -21,8 +21,9 @@ stages that preserve the primary path. No unrelated branches, no dashboard panel
|
|||||||
| operator | `\Large` | `st op` |
|
| operator | `\Large` | `st op` |
|
||||||
| symbol | `\small` | `\stcaption` arg 2 |
|
| symbol | `\small` | `\stcaption` arg 2 |
|
||||||
| shape | `\scriptsize`, muted | `\stcaption` arg 3 |
|
| shape | `\scriptsize`, muted | `\stcaption` arg 3 |
|
||||||
|
| side card | `\scriptsize\bfseries` title, `\scriptsize` body | `\stcallout`, same tier as `st note` |
|
||||||
| bottom prose | `\small` | `\stmeaningbox` |
|
| bottom prose | `\small` | `\stmeaningbox` |
|
||||||
| signature | `\scriptsize`, low contrast | `\stsignature` |
|
| optional signature | `\scriptsize`, low contrast | `\stsignature` |
|
||||||
|
|
||||||
Never shrink below this to make something fit — see `layout.md`.
|
Never shrink below this to make something fit — see `layout.md`.
|
||||||
|
|
||||||
@@ -32,6 +33,10 @@ Separate tiles with a small white gutter and 0.5–1 pt corner rounding (`\st@ti
|
|||||||
this). Thin neutral brackets, `black!55`–`black!70` outer borders. No saturated
|
this). Thin neutral brackets, `black!55`–`black!70` outer borders. No saturated
|
||||||
tensor-colored outlines, no continuous spreadsheet grid.
|
tensor-colored outlines, no continuous spreadsheet grid.
|
||||||
|
|
||||||
|
The one exception is `\stgroup`, whose outline is drawn at `role!65`: there the outline
|
||||||
|
*is* the object being named, so the hue is doing semantic work rather than decorating a
|
||||||
|
face that already has its own fill.
|
||||||
|
|
||||||
**Encode support before magnitude.** Every known zero stays white/unfilled; every shown
|
**Encode support before magnitude.** Every known zero stays white/unfilled; every shown
|
||||||
nonzero gets color. A diagonal matrix must read instantly as colored diagonal cells on a
|
nonzero gets color. A diagonal matrix must read instantly as colored diagonal cells on a
|
||||||
white field. `pattern=diag/band/lower/upper/causal/data` fill exactly the structural
|
white field. `pattern=diag/band/lower/upper/causal/data` fill exactly the structural
|
||||||
@@ -69,16 +74,23 @@ Narrow bold label rail, left-aligned ragged-right `\small` content, 8–10 pt in
|
|||||||
Keep each row compact: prefer symbol semantics over numeric configuration. When it is too
|
Keep each row compact: prefer symbol semantics over numeric configuration. When it is too
|
||||||
long, **remove content** — never add cards, columns or smaller type. Pass `{}` to omit a row.
|
long, **remove content** — never add cards, columns or smaller type. Pass `{}` to omit a row.
|
||||||
|
|
||||||
## Signature
|
## Side cards
|
||||||
|
|
||||||
One centered line below the box, outside it, low-contrast gray, `\scriptsize` or smaller:
|
`\stcallout` is the only sanctioned floating text card, and it is deliberately narrow in
|
||||||
`\stsignature{<subject>}{<fit node>}` renders `<subject>@五道口纳什`. The subject must name
|
scope: one per figure by default, hung off the right edge of a *finished* band, never
|
||||||
what this figure actually visualizes. Keep it on one line, with a small but visible gap.
|
between two operands. When the text outgrows the height of its band, it is not an aside — move it into
|
||||||
|
the **Mechanism** row of `\stmeaningbox` instead of widening or shrinking the card.
|
||||||
|
|
||||||
|
## Optional signature
|
||||||
|
|
||||||
|
Add a centered line only when the user or house template requests it. Keep it below the
|
||||||
|
box, outside it, low-contrast gray and on one line. `\stsignature{<subject>}{<fit node>}`
|
||||||
|
renders only the subject. Do not append an author, handle or brand identity.
|
||||||
|
|
||||||
## Never
|
## Never
|
||||||
|
|
||||||
Charts or metric insets not present in the primary formula. Decorative pills, banners,
|
Charts or metric insets not present in the primary formula. Decorative pills, banners,
|
||||||
shadows, repeated separators, explanatory cards.
|
shadows, repeated separators, explanatory cards other than the one budgeted `\stcallout`.
|
||||||
|
|
||||||
## Reference image
|
## Reference image
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Compile a supertensor figure and export every delivery artifact.
|
# Lint, compile, and export every delivery artifact for a supertensor figure.
|
||||||
#
|
#
|
||||||
# ./scripts/build.sh figure.tex [outdir]
|
# ./scripts/build.sh figure.tex [outdir]
|
||||||
#
|
#
|
||||||
@@ -23,6 +23,11 @@ OUT="${2:-$SRCDIR/build}"
|
|||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
mkdir -p "$OUT"
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
if [[ "${ST_SKIP_LINT:-0}" != "1" ]]; then
|
||||||
|
echo "==> lint $BASE"
|
||||||
|
python3 "$ROOT/scripts/lint.py" "$SRC"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> xelatex $BASE"
|
echo "==> xelatex $BASE"
|
||||||
# supertensor.sty lives in assets/; keep it off the user's texmf tree.
|
# supertensor.sty lives in assets/; keep it off the user's texmf tree.
|
||||||
TEXINPUTS="$ROOT/assets:$SRCDIR:" \
|
TEXINPUTS="$ROOT/assets:$SRCDIR:" \
|
||||||
@@ -69,6 +74,6 @@ if [[ $status -ne 0 ]]; then
|
|||||||
echo "==> BUILD DIRTY: fix the warnings above before delivering." >&2
|
echo "==> BUILD DIRTY: fix the warnings above before delivering." >&2
|
||||||
else
|
else
|
||||||
echo "==> clean. Now do the visual audit (references/checklist.md) --"
|
echo "==> clean. Now do the visual audit (references/checklist.md) --"
|
||||||
echo " a clean build says nothing about collisions or hue budget."
|
echo " a clean build says nothing about collisions or thumbnail legibility."
|
||||||
fi
|
fi
|
||||||
exit $status
|
exit $status
|
||||||
|
|||||||
Executable
+195
@@ -0,0 +1,195 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Static source checks for supertensor figures.
|
||||||
|
|
||||||
|
The TeX package owns geometry at render time; this linter catches source-level
|
||||||
|
escapes that TeX cannot see: mutable ledgers, hand placement, formula order,
|
||||||
|
raw rectangles, repeated anonymous dimensions, and per-row hue budget.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
DIM_RE = re.compile(r"\\stdim\{([^{}]+)\}\{([^{}]+)\}")
|
||||||
|
ROLE_RE = re.compile(r"\\stsetrole\{([^{}]+)\}\{([^{}]+)\}")
|
||||||
|
FACE_RE = re.compile(
|
||||||
|
r"\\(stface|ststack|stindexface)(?:\[[^\]]*\])?"
|
||||||
|
r"\{([^{}]+)\}\{([^{}]*)\}\{([^{}]+)\}\{([^{}]+)\}",
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
ROW_RE = re.compile(r"\\strow\{[^{}]+\}\{[^{}]+\}(.*?)\\strowend", re.DOTALL)
|
||||||
|
ROW_NAME_RE = re.compile(r"\\strow\{([^{}]+)\}\{[^{}]+\}")
|
||||||
|
ROLE_KEY_RE = re.compile(r"role\s*=\s*([A-Za-z0-9_-]+)")
|
||||||
|
CALLOUT_RE = re.compile(r"\\stcallout\{([^{}]+)\}\{[^{}]+\}\{([^{}]+)\}")
|
||||||
|
|
||||||
|
|
||||||
|
def strip_comments(source: str) -> str:
|
||||||
|
return re.sub(r"(?<!\\)%.*$", "", source, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def line_of(source: str, offset: int) -> int:
|
||||||
|
return source.count("\n", 0, offset) + 1
|
||||||
|
|
||||||
|
|
||||||
|
def directives(source: str) -> set[str]:
|
||||||
|
found: set[str] = set()
|
||||||
|
for match in re.finditer(r"^\s*%\s*supertensor-lint:\s*(.+)$", source, re.MULTILINE):
|
||||||
|
found.update(item.strip() for item in match.group(1).split(","))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def check_unique(
|
||||||
|
source: str, pattern: re.Pattern[str], kind: str, errors: list[str]
|
||||||
|
) -> dict[str, str]:
|
||||||
|
values: dict[str, str] = {}
|
||||||
|
for match in pattern.finditer(source):
|
||||||
|
name, value = (part.strip() for part in match.groups())
|
||||||
|
previous = values.get(name)
|
||||||
|
if previous is not None and previous != value:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: {kind} {name!r} changes "
|
||||||
|
f"from {previous!r} to {value!r}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
values[name] = value
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def lint(path: Path) -> list[str]:
|
||||||
|
raw = path.read_text(encoding="utf-8")
|
||||||
|
allowed = directives(raw)
|
||||||
|
source = strip_comments(raw)
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
dimensions = check_unique(source, DIM_RE, "axis", errors)
|
||||||
|
roles = check_unique(source, ROLE_RE, "role", errors)
|
||||||
|
|
||||||
|
for match in ROLE_KEY_RE.finditer(source):
|
||||||
|
role = match.group(1)
|
||||||
|
if role not in roles:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: role {role!r} is used "
|
||||||
|
f"before \\stsetrole"
|
||||||
|
)
|
||||||
|
|
||||||
|
formula_positions = [m.start() for m in re.finditer(r"\\sttopformula\b", source)]
|
||||||
|
row_ends = [m.start() for m in re.finditer(r"\\strowend\b", source)]
|
||||||
|
if not formula_positions and "allow-missing-formula" not in allowed:
|
||||||
|
errors.append("missing \\sttopformula (add an explicit lint exemption for galleries/tests)")
|
||||||
|
elif formula_positions and row_ends and formula_positions[-1] < row_ends[-1]:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, formula_positions[-1])}: \\sttopformula must follow the last \\strowend"
|
||||||
|
)
|
||||||
|
|
||||||
|
tracked = set(re.findall(r"\\sttrack\{([^{}]+)\}", source))
|
||||||
|
raw_dimensions: dict[str, list[int]] = {}
|
||||||
|
for match in FACE_RE.finditer(source):
|
||||||
|
_macro, name, coordinate, rows, cols = match.groups()
|
||||||
|
if coordinate.strip() and name not in tracked and "allow-absolute" not in allowed:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: absolute object {name!r} has no \\sttrack{{{name}}}"
|
||||||
|
)
|
||||||
|
for value in (rows.strip(), cols.strip()):
|
||||||
|
if value.isdigit() and value != "1":
|
||||||
|
raw_dimensions.setdefault(value, []).append(line_of(source, match.start()))
|
||||||
|
|
||||||
|
if "allow-repeated-raw-dim" not in allowed:
|
||||||
|
for value, lines in sorted(raw_dimensions.items()):
|
||||||
|
if len(lines) > 1:
|
||||||
|
errors.append(
|
||||||
|
f"lines {', '.join(map(str, lines))}: raw dimension {value!r} is reused; declare a symbolic axis with \\stdim"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "allow-raw-tikz" not in allowed:
|
||||||
|
for match in re.finditer(
|
||||||
|
r"\\(?:draw|fill|path)\b[^;]*\brectangle\b", source, re.DOTALL
|
||||||
|
):
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, match.start())}: hand-drawn rectangle bypasses \\stface"
|
||||||
|
)
|
||||||
|
|
||||||
|
# \stgroup is a block. An unclosed one silently swallows the rest of the
|
||||||
|
# band into the group's fit list, which TeX only reports as an empty row.
|
||||||
|
opened = len(re.findall(r"\\stgroup(?![A-Za-z])", source))
|
||||||
|
closed = len(re.findall(r"\\stgroupend(?![A-Za-z])", source))
|
||||||
|
if opened != closed:
|
||||||
|
errors.append(
|
||||||
|
f"{opened} \\stgroup vs {closed} \\stgroupend: every group must be closed"
|
||||||
|
)
|
||||||
|
|
||||||
|
# A callout hangs off a BAND. Anchored to a face it becomes the floating
|
||||||
|
# commentary card between two operands that layout.md forbids. The default
|
||||||
|
# budget is ONE callout for the whole figure: a card per band is already a
|
||||||
|
# dashboard, and at thumbnail size the cards are unreadable anyway. The
|
||||||
|
# allow-multiple-callouts directive relaxes this to one per band.
|
||||||
|
band_names = set(ROW_NAME_RE.findall(source))
|
||||||
|
callout_anchors: dict[str, str] = {}
|
||||||
|
first_callout: str | None = None
|
||||||
|
for match in CALLOUT_RE.finditer(source):
|
||||||
|
name, anchor = (part.strip() for part in match.groups())
|
||||||
|
line = line_of(source, match.start())
|
||||||
|
if anchor not in band_names:
|
||||||
|
errors.append(
|
||||||
|
f"line {line}: callout {name!r} is anchored to {anchor!r}, which is not a "
|
||||||
|
f"\\strow band; a card hanging off a single object reads as a step"
|
||||||
|
)
|
||||||
|
elif anchor in callout_anchors:
|
||||||
|
errors.append(
|
||||||
|
f"line {line}: callout {name!r} is the second card on band {anchor!r} "
|
||||||
|
f"(after {callout_anchors[anchor]!r}); one aside per band"
|
||||||
|
)
|
||||||
|
elif first_callout is not None and "allow-multiple-callouts" not in allowed:
|
||||||
|
errors.append(
|
||||||
|
f"line {line}: callout {name!r} is the second card in the figure "
|
||||||
|
f"(after {first_callout!r}); the budget is one callout per figure — "
|
||||||
|
f"move the text to \\stmeaningbox, or add the "
|
||||||
|
f"allow-multiple-callouts exemption"
|
||||||
|
)
|
||||||
|
callout_anchors.setdefault(anchor, name)
|
||||||
|
if first_callout is None:
|
||||||
|
first_callout = name
|
||||||
|
|
||||||
|
for row_number, row in enumerate(ROW_RE.finditer(source), start=1):
|
||||||
|
row_roles = set(ROLE_KEY_RE.findall(row.group(1)))
|
||||||
|
colors = {roles.get(role, "<undeclared>") for role in row_roles}
|
||||||
|
active = colors - {"stGray"}
|
||||||
|
if len(active) > 4:
|
||||||
|
errors.append(
|
||||||
|
f"line {line_of(source, row.start())}: row {row_number} uses {len(active)} active hue families "
|
||||||
|
f"({', '.join(sorted(active))}); maximum is 4 plus gray"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Declared dimensions are intentionally allowed to be illustrative. This
|
||||||
|
# lookup merely keeps the declaration parse exercised and future-proof.
|
||||||
|
del dimensions
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="lint a supertensor .tex source")
|
||||||
|
parser.add_argument("sources", nargs="+", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
for path in args.sources:
|
||||||
|
if not path.is_file():
|
||||||
|
print(f"supertensor-lint: no such file: {path}", file=sys.stderr)
|
||||||
|
failed = True
|
||||||
|
continue
|
||||||
|
errors = lint(path)
|
||||||
|
if errors:
|
||||||
|
failed = True
|
||||||
|
print(f"!! {path}", file=sys.stderr)
|
||||||
|
for error in errors:
|
||||||
|
print(f" {error}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f" lint {path}")
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -12,7 +12,7 @@ QUIET=0
|
|||||||
[[ "${1:-}" == "--quiet" ]] && QUIET=1
|
[[ "${1:-}" == "--quiet" ]] && QUIET=1
|
||||||
say() { [[ $QUIET -eq 1 ]] || echo -e "$*"; }
|
say() { [[ $QUIET -eq 1 ]] || echo -e "$*"; }
|
||||||
|
|
||||||
ok=0; warn=0; fail=0
|
ok=0; warn=0; fail=0; cjk_warn=0; export_warn=0
|
||||||
check() { # name, command
|
check() { # name, command
|
||||||
local name="$1"; shift
|
local name="$1"; shift
|
||||||
if "$@" >/dev/null 2>&1; then say " ok $name"; ok=$((ok+1)); return 0
|
if "$@" >/dev/null 2>&1; then say " ok $name"; ok=$((ok+1)); return 0
|
||||||
@@ -27,11 +27,11 @@ check "tikz.sty" kpsewhich tikz.sty || fail=$((fail+1))
|
|||||||
check "xstring.sty" kpsewhich xstring.sty || fail=$((fail+1))
|
check "xstring.sty" kpsewhich xstring.sty || fail=$((fail+1))
|
||||||
|
|
||||||
say "--- chinese figures ---"
|
say "--- chinese figures ---"
|
||||||
check "ctex.sty" kpsewhich ctex.sty || warn=$((warn+1))
|
check "ctex.sty" kpsewhich ctex.sty || { warn=$((warn+1)); cjk_warn=1; }
|
||||||
check "fandol font" kpsewhich FandolSong-Regular.otf || warn=$((warn+1))
|
check "fandol font" kpsewhich FandolSong-Regular.otf || { warn=$((warn+1)); cjk_warn=1; }
|
||||||
|
|
||||||
say "--- raster / vector export ---"
|
say "--- raster / vector export ---"
|
||||||
check "pdftocairo" command -v pdftocairo || warn=$((warn+1))
|
check "pdftocairo" command -v pdftocairo || { warn=$((warn+1)); export_warn=1; }
|
||||||
check "latexmk (optional)" command -v latexmk || true
|
check "latexmk (optional)" command -v latexmk || true
|
||||||
|
|
||||||
if [[ $fail -gt 0 ]]; then
|
if [[ $fail -gt 0 ]]; then
|
||||||
@@ -44,9 +44,13 @@ fi
|
|||||||
if [[ $warn -gt 0 ]]; then
|
if [[ $warn -gt 0 ]]; then
|
||||||
say ""
|
say ""
|
||||||
say "RESULT: degraded."
|
say "RESULT: degraded."
|
||||||
|
if [[ $cjk_warn -eq 1 ]]; then
|
||||||
say " - missing ctex/fandol -> English-label figures only; do not substitute"
|
say " - missing ctex/fandol -> English-label figures only; do not substitute"
|
||||||
say " an OS-specific CJK font without telling the user it costs portability."
|
say " an OS-specific CJK font without telling the user it costs portability."
|
||||||
|
fi
|
||||||
|
if [[ $export_warn -eq 1 ]]; then
|
||||||
say " - missing pdftocairo -> deliver PDF only, and say PNG/SVG were skipped."
|
say " - missing pdftocairo -> deliver PDF only, and say PNG/SVG were skipped."
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
say ""
|
say ""
|
||||||
|
|||||||
+25
-3
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Build every example and the smoke test. Any dirty build fails the run.
|
# Build every valid example, then prove invalid TeX and lint fixtures fail.
|
||||||
#
|
#
|
||||||
# ./scripts/test.sh
|
# ./scripts/test.sh
|
||||||
#
|
#
|
||||||
@@ -21,8 +21,30 @@ for f in "$ROOT"/tests/*.tex "$ROOT"/examples/*.tex; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for f in "$ROOT"/tests/invalid/*.tex; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
name="$(basename "$f")"
|
||||||
|
if ST_SKIP_LINT=1 "$ROOT/scripts/build.sh" "$f" >/dev/null 2>&1; then
|
||||||
|
echo " FAIL $name (invalid fixture built cleanly)"
|
||||||
|
fail=$((fail+1))
|
||||||
|
else
|
||||||
|
echo " ok $name (rejected by package/build)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in "$ROOT"/tests/lint-invalid/*.tex; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
name="$(basename "$f")"
|
||||||
|
if python3 "$ROOT/scripts/lint.py" "$f" >/dev/null 2>&1; then
|
||||||
|
echo " FAIL $name (invalid fixture passed lint)"
|
||||||
|
fail=$((fail+1))
|
||||||
|
else
|
||||||
|
echo " ok $name (rejected by lint)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
if [[ $fail -gt 0 ]]; then
|
if [[ $fail -gt 0 ]]; then
|
||||||
echo "$fail failing figure(s); rerun scripts/build.sh on one to see why" >&2
|
echo "$fail failing check(s); rerun the reported build or lint command to inspect" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "all figures build clean"
|
echo "all positive and negative checks passed"
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
% 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$}
|
||||||
|
|
||||||
|
\sttopformula{F}{$\displaystyle
|
||||||
|
\operatorname{flow}(X,W)\;:\;\text{对象按自身边界框依次占位}$}
|
||||||
|
|
||||||
|
\stbbox{all}
|
||||||
|
\stmeaningbox{mb}{15cm}{all}
|
||||||
|
{$T$ 序列长,$d$ 模型维,$r$ 低秩维}
|
||||||
|
{}
|
||||||
|
{三行的左端都落在同一条左轨上,行内间距由 \texttt{\string\stgutter} 声明一次,
|
||||||
|
标签自己占位,所以任何一处变宽都只会把后面的东西推开,不会盖住它们;
|
||||||
|
第三行的列子流用 \texttt{gap=0pt} 声明两片相邻,声明高度与实际不符会直接报警}
|
||||||
|
\stsignature{流式排版自测}{mb}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
% Regression test for \stgroup and \stcallout.
|
||||||
|
% - a group over three adjacent head faces (the trio is one composite q);
|
||||||
|
% the first face carries bracket=true, probing that bracket ink is part
|
||||||
|
% of the group's fit -- the outline must clear the bracket arms,
|
||||||
|
% - a group over a \stcol partition (two shards tiling one parent W),
|
||||||
|
% - one side card for the whole figure, hanging off a finished band.
|
||||||
|
% No absolute coordinate appears anywhere in this figure.
|
||||||
|
% ../scripts/build.sh group-callout.tex
|
||||||
|
\documentclass[border=10pt]{standalone}
|
||||||
|
\usepackage[cjk]{supertensor}
|
||||||
|
|
||||||
|
\stsetrole{act}{stTeal}
|
||||||
|
\stsetrole{w}{stOrange}
|
||||||
|
\stsetrole{q}{stViolet}
|
||||||
|
|
||||||
|
\stdim{T}{7}
|
||||||
|
\stdim{d}{5}
|
||||||
|
\stdim{dh}{3}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
|
||||||
|
\ststage{S1}{一、分组框:三个相邻的头合起来是一个复合对象}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stface[role=act, bracket=true]{X}{}{T}{d}
|
||||||
|
\stlink{l1}{按头切分}
|
||||||
|
\stgroup[role=q]{QG}
|
||||||
|
\stface[role=q, bracket=true]{Q1}{}{T}{dh}
|
||||||
|
\stface[role=q, gap=2mm]{Q2}{}{T}{dh}
|
||||||
|
\stface[role=q, gap=2mm]{Q3}{}{T}{dh}
|
||||||
|
\stgroupend
|
||||||
|
\strowend
|
||||||
|
\stcaption{X}{$\mathbf X$}{$T\times d$}
|
||||||
|
\stcaption{QG}{$\mathbf q$}{$h\times T\times d_h$}
|
||||||
|
|
||||||
|
\ststage{S2}{二、分组框圈住两片相邻的分片,标注它们合起来是谁}
|
||||||
|
\strow{R2}{T}
|
||||||
|
\stface[role=act]{H}{}{T}{d}
|
||||||
|
\stglyph{m2}{$\times$}
|
||||||
|
\stgroup[role=w, pad=2.2mm]{WG}
|
||||||
|
\stcol{Wc}{d}
|
||||||
|
\stface[role=w]{Wa}{}{dh}{T}
|
||||||
|
\stface[role=w, gap=0pt]{Wb}{}{2}{T}
|
||||||
|
\stcolend
|
||||||
|
\stgroupend
|
||||||
|
\stlink{l2}{}
|
||||||
|
\stface[role=q, bracket=true]{Z}{}{T}{T}
|
||||||
|
\strowend
|
||||||
|
\stcaption{H}{$\mathbf H$}{$T\times d$}
|
||||||
|
\stcaption{WG}{$\mathbf W$}{$d\times T$}
|
||||||
|
\stcaption{Z}{$\mathbf Z$}{$T\times T$}
|
||||||
|
\stcallout{N1}{4.2cm}{R2}{分组框在说什么}%
|
||||||
|
{外框与成员同色,因为它圈的是同一个对象的分片视图,不是一个新张量。
|
||||||
|
括号等装饰墨迹也计入外框的包围盒,所以框永远不会被成员的括号穿过;
|
||||||
|
整张图默认只允许一张这样的卡片,再多就是仪表盘。}
|
||||||
|
|
||||||
|
\sttopformula{F}{$\displaystyle \mathbf Z=\mathbf H\,\mathbf W,\qquad
|
||||||
|
\mathbf W=\begin{bmatrix}\mathbf W_a\\ \mathbf W_b\end{bmatrix}$}
|
||||||
|
|
||||||
|
\stbbox{all}
|
||||||
|
\stmeaningbox{mb}{17cm}{all}
|
||||||
|
{$T$ 序列长,$d$ 模型维,$d_h$ 头宽,$h$ 头数}
|
||||||
|
{}
|
||||||
|
{分组框是横向的子流:成员写在块里,所以它只能圈住相邻的对象;
|
||||||
|
它必须绑定至少两个成员或一个 \texttt{stcol} 分片,单个对象外再画框是装饰。
|
||||||
|
连接线不能在框内起止——\texttt{stlink} 写在组里是构建错误,
|
||||||
|
闭合后箭头接在外框上,而不是穿过一个并非自己端点的边。}
|
||||||
|
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{d}{4}
|
||||||
|
\stdim{d}{7}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface{A}{(0,0)}{d}{d}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
% \stlink inside a group: members never terminate a pending connector, so the
|
||||||
|
% arrow would be dropped silently. The package must error rather than build a
|
||||||
|
% figure whose label appears but whose arrow does not.
|
||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stgroup[role=x]{G}
|
||||||
|
\stface[role=x]{A}{}{T}{T}
|
||||||
|
\stlink{l1}{lost arrow}
|
||||||
|
\stface[role=x]{B}{}{T}{T}
|
||||||
|
\stgroupend
|
||||||
|
\strowend
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
% A group with no role would silently fall back to neutral gray, violating
|
||||||
|
% the "outline in the composite's own hue" rule. The package must error.
|
||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stgroup{G}
|
||||||
|
\stface[role=x]{A}{}{T}{T}
|
||||||
|
\stface[role=x]{B}{}{T}{T}
|
||||||
|
\stgroupend
|
||||||
|
\strowend
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
% A group around a single object is decoration: the stack already reads as
|
||||||
|
% one composite. The package must reject this with a dirty-build warning.
|
||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stgroup[role=x]{G}
|
||||||
|
\ststack[role=x]{Q}{}{T}{T}{3}
|
||||||
|
\stgroupend
|
||||||
|
\strowend
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{idx}{stOrange}
|
||||||
|
\stdim{m}{2}
|
||||||
|
\stdim{n}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stindexface[role=idx]{I}{(0,0)}{m}{n}{0,1,2,3,4}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{m}{2}
|
||||||
|
\stdim{n}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface[role=x,pattern=data,data={303}]{A}{(0,0)}{m}{n}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{m}{2}
|
||||||
|
\stdim{n}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface[role=x,pattern=data,data={303,34}]{A}{(0,0)}{m}{n}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{d}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface[role=x,pattern=solid,level=9]{A}{(0,0)}{d}{d}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stdim{d}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface[role=x,pattern=checkerboard]{A}{(0,0)}{d}{d}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
\documentclass[border=2pt]{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{x}{stTeal}
|
||||||
|
\stsetrole{x}{stCoral}
|
||||||
|
\stdim{d}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface[role=x]{A}{(0,0)}{d}{d}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{d}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\stface{A}{(0,0)}{d}{d}
|
||||||
|
\sttopformula{F}{$A$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
% A callout anchored to a face rather than to a band: the card lands beside a
|
||||||
|
% single operand and reads as a step in the computation.
|
||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stface{A}{}{T}{T}
|
||||||
|
\strowend
|
||||||
|
\stcallout{N1}{3cm}{A}{aside}{anchored to a face, not to the band}
|
||||||
|
\sttopformula{F}{$A$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
% One card per band, but a card on EVERY band, is still a dashboard. The
|
||||||
|
% default budget is one callout per figure; this must fail lint without the
|
||||||
|
% allow-multiple-callouts exemption.
|
||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stface{A}{}{T}{T}
|
||||||
|
\strowend
|
||||||
|
\stcallout{N1}{3cm}{R1}{first}{one per figure}
|
||||||
|
\strow{R2}{T}
|
||||||
|
\stface{B}{}{T}{T}
|
||||||
|
\strowend
|
||||||
|
\stcallout{N2}{3cm}{R2}{second}{this one belongs in the meaning box}
|
||||||
|
\sttopformula{F}{$A,B$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
% Two asides on one band: a dashboard, not a figure. The second card belongs
|
||||||
|
% in \stmeaningbox.
|
||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stface{A}{}{T}{T}
|
||||||
|
\strowend
|
||||||
|
\stcallout{N1}{3cm}{R1}{first}{one aside per band}
|
||||||
|
\stcallout{N2}{3cm}{R1}{second}{this one has nowhere to go}
|
||||||
|
\sttopformula{F}{$A$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{d}{3}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\sttopformula{F}{$A$}
|
||||||
|
\strow{row}{d}
|
||||||
|
\stface{A}{}{d}{d}
|
||||||
|
\strowend
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
% An unclosed \stgroup swallows the rest of the band into its fit list; TeX
|
||||||
|
% only reports the resulting empty row, which does not point at the cause.
|
||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stdim{T}{4}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{R1}{T}
|
||||||
|
\stgroup{G}
|
||||||
|
\stface{A}{}{T}{T}
|
||||||
|
\strowend
|
||||||
|
\sttopformula{F}{$A$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\stsetrole{a}{stTeal}
|
||||||
|
\stsetrole{b}{stOrange}
|
||||||
|
\stsetrole{c}{stCoral}
|
||||||
|
\stsetrole{d}{stViolet}
|
||||||
|
\stsetrole{e}{stInk}
|
||||||
|
\stdim{x}{2}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\strow{row}{x}
|
||||||
|
\stface[role=a]{A}{}{x}{x}
|
||||||
|
\stface[role=b]{B}{}{x}{x}
|
||||||
|
\stface[role=c]{C}{}{x}{x}
|
||||||
|
\stface[role=d]{D}{}{x}{x}
|
||||||
|
\stface[role=e]{E}{}{x}{x}
|
||||||
|
\strowend
|
||||||
|
\sttopformula{F}{$A+B+C+D+E$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
\documentclass{standalone}
|
||||||
|
\usepackage[en]{supertensor}
|
||||||
|
\begin{document}\begin{tikzpicture}
|
||||||
|
\fill (0,0) rectangle (2,2);
|
||||||
|
\sttopformula{F}{$A$}
|
||||||
|
\end{tikzpicture}\end{document}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
% supertensor-lint: allow-absolute, allow-missing-formula
|
||||||
|
% Legacy absolute-placement smoke test; flow layout is covered by flow.tex.
|
||||||
\documentclass[border=8pt]{standalone}
|
\documentclass[border=8pt]{standalone}
|
||||||
\usepackage[cjk]{supertensor}
|
\usepackage[cjk]{supertensor}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user