Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,23 @@ jobs:
steps:
- uses: actions/checkout@v4

# The harness uses Verilator 5 APIs (VerilatedContext); build from source like rtl.yml.
- name: Cache Verilator build
id: cache-verilator
uses: actions/cache@v4
with:
path: ~/verilator-install
key: verilator-v5.036-ubuntu-24.04

- name: Build Verilator v5.036 from source
if: steps.cache-verilator.outputs.cache-hit != 'true'
# The same Verilator build as the project's .tools/setup.sh (CMake, v5.036 source, same patch). The runtime's
# version string is compiled into the engine, so the build method has to match the committed one.
- name: Build Verilator v5.036 (as .tools/setup.sh)
run: |
sudo apt-get update -qq
sudo apt-get install -y autoconf flex bison libfl2 libfl-dev help2man perl build-essential
git clone --depth 1 --branch v5.036 https://github.com/verilator/verilator.git /tmp/verilator
cd /tmp/verilator
autoconf
./configure --prefix="$HOME/verilator-install"
make -j$(nproc)
make install
sudo apt-get install -y cmake ninja-build autoconf flex bison libfl2 libfl-dev help2man perl build-essential
bash .tools/setup.sh

- name: Add Verilator to PATH
run: echo "$HOME/verilator-install/bin" >> $GITHUB_PATH
run: echo "$GITHUB_WORKSPACE/.tools/verilator-install/bin" >> $GITHUB_PATH

# Pinned to the version the committed engine was built with: the compiled .wasm changes between Emscripten
# releases, and the freshness check below compares bytes.
- name: Set up Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
version: '6.0.13'

- uses: actions/setup-node@v4
with:
Expand All @@ -54,6 +46,14 @@ jobs:
test -f web/public/spike/counter.wasm
echo "spike wasm: $(du -h web/public/spike/counter.wasm | cut -f1)"

# The committed engine (web/public, served by the site) must pass the golden fixtures as it is, before the
# rebuild below overwrites it. Byte-identical rebuilds across machines are not guaranteed, so behaviour is the check.
- name: Committed engine passes the golden fixtures
run: |
mkdir -p /tmp/committed
cp web/public/tiny_attn.mjs web/public/tiny_attn.wasm /tmp/committed/
ENGINE_DIR=/tmp/committed node wasm/test.mjs

# The engine: Verilate tiny_attn_top, build the module, run the golden fixtures in Node, check the size budget.
- name: Build engine WASM
run: bash wasm/build.sh
Expand Down
327 changes: 278 additions & 49 deletions README.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,9 @@ Checks: `node wasm/test.mjs` (engine vs golden, now including the K/V buffer in
browser checks use `astro build` + `astro preview`.

**Committed engine build.** `web/public/tiny_attn.{mjs,wasm}` are tracked: the Vercel build has no Emscripten, so `/app`
needs the files in git. Rebuild with `bash wasm/build.sh` after any RTL or harness change and commit them with it. CI does
not yet check that the committed files match a fresh build; the wasm workflow's `node wasm/test.mjs` checks behavior, not
the files.
needs the files in git. Rebuild with `bash wasm/build.sh` after any RTL or harness change and commit them with it. CI runs the golden
fixtures against the committed files and against a fresh build. It does not require the bytes to match: the same sources
built on different machines do not always give identical `.wasm` bytes, so behaviour is what is checked.

## 13. Phase 8: the other levels (L1, L2, L4)

Expand Down
6 changes: 3 additions & 3 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
> running live in the browser.** You can watch the online softmax keep its running max and sum,
> watch SRAM tiles fill and empty, and see why FlashAttention moves so much less data than standard attention.
>
> This is the fourth project in the series, after [tiny-gpu](https://github.com/deaneeth/tiny-gpu),
> [tiny-tpu](https://github.com/deaneeth/tiny-tpu) and tiny-cpu. It takes over the toolchain tiny-cpu
> This is the fourth project in the series, after [tiny-gpu](https://github.com/tinycomputers/tiny-gpu),
> [tiny-tpu](https://github.com/tinycomputers/tiny-tpu) and tiny-cpu. It takes over the toolchain tiny-cpu
> already proved:
> `rtl/*.sv → Verilator → C++ harness → Emscripten → web/public/tiny_attn.wasm → React island → SVG`.

Expand Down Expand Up @@ -526,7 +526,7 @@ cd web && pnpm lint && pnpm typecheck && pnpm test && pnpm build

## 13. First week, concretely

1. Create `deaneeth/tiny-attention`. Copy `.tools/`, `scripts/env.sh`, `.editorconfig`, the workflow skeletons, `web/` config
1. Create `tinycomputers/tiny-attention` (done). Copy `.tools/`, `scripts/env.sh`, `.editorconfig`, the workflow skeletons, `web/` config
and `wasm/emscripten_stubs.cpp` from tiny-cpu. Write `CLAUDE.md`, `AGENTS.md` and `CONTEXT.md`.
2. Spike: two independent `counter.sv` model instances in one WASM page; get CI green.
3. `float_ref.py` + `fixed.py` exp2 and recip with `gen_luts.py`; the first numerics plot (exp2 error vs LUT size).
Expand Down
22 changes: 22 additions & 0 deletions docs/media/logo-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions docs/media/logo-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 12 additions & 8 deletions wasm/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OBJ="$ROOT/wasm/build/obj"
OUT="$ROOT/web/public"
OBJ="wasm/build/obj" # relative to ROOT: the build must not depend on where the repo lives
OUT="web/public"
cd "$ROOT"

VROOT="$(verilator --getenv VERILATOR_ROOT)"
[ -f "$VROOT/include/verilated.h" ] || { echo "VERILATOR_ROOT not found: $VROOT" >&2; exit 1; }

RTL_TOP=tiny_attn_top
SRCS=("$ROOT/rtl/lut_pkg.sv" "$ROOT/rtl/tiny_attn_pkg.sv")
for f in "$ROOT"/rtl/*.sv; do
case "$f" in */lut_pkg.sv|*/tiny_attn_pkg.sv) ;; *) SRCS+=("$f") ;; esac
SRCS=("rtl/lut_pkg.sv" "rtl/tiny_attn_pkg.sv")
for f in rtl/*.sv; do
case "$f" in rtl/lut_pkg.sv|rtl/tiny_attn_pkg.sv) ;; *) SRCS+=("$f") ;; esac
done

echo "[1/2] Verilating ${RTL_TOP}..."
Expand All @@ -25,13 +26,16 @@ verilator --cc "${SRCS[@]}" --top-module "$RTL_TOP" --prefix Vtiny_attn_top --Md

echo "[2/2] Compiling to WASM..."
mkdir -p "$OUT"
# -ffile-prefix-map strips the checkout and Verilator install paths from __FILE__ and debug info, so the same
# sources and toolchain give the same bytes on any machine (CI checks this against the committed engine).
em++ -O2 -std=c++17 -DVL_IGNORE_UNKNOWN_ARCH \
-I "$OBJ" -I "$VROOT/include" -I "$VROOT/include/vltstd" -I "$ROOT/wasm/harness" \
"$ROOT/wasm/harness/harness.cpp" "$ROOT/wasm/harness/bindings.cpp" \
-ffile-prefix-map="$ROOT"=. -ffile-prefix-map="$VROOT"=verilator \
-I "$OBJ" -I "$VROOT/include" -I "$VROOT/include/vltstd" -I "wasm/harness" \
"wasm/harness/harness.cpp" "wasm/harness/bindings.cpp" \
"$OBJ"/Vtiny_attn_top*.cpp \
"$VROOT/include/verilated.cpp" \
"$VROOT/include/verilated_threads.cpp" \
"$ROOT/wasm/emscripten_stubs.cpp" \
"wasm/emscripten_stubs.cpp" \
-lembind \
-s MODULARIZE=1 -s EXPORT_ES6=1 -s ALLOW_MEMORY_GROWTH=1 \
-s EXPORT_NAME=createTinyAttn -s ENVIRONMENT=web,node \
Expand Down
4 changes: 3 additions & 1 deletion wasm/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const { default: createTinyAttn } = await import(join(root, "web/public/tiny_attn.mjs"));
// ENGINE_DIR selects which build to test (CI tests the committed files before rebuilding them); default web/public.
const engineDir = process.env.ENGINE_DIR ?? join(root, "web/public");
const { default: createTinyAttn } = await import(join(engineDir, "tiny_attn.mjs"));
const mod = await createTinyAttn();

// engine runs only; quantize_cases.json holds quantization references for the web tests
Expand Down
20 changes: 19 additions & 1 deletion web/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web/public/tiny_attn.wasm
Binary file not shown.
Loading