diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 3099e57..d154637 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -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: @@ -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 diff --git a/README.md b/README.md index 9231f1f..cb0fa7e 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,310 @@ -# TinyAttention +
+ + + TinyAttention + -A synthesizable **FlashAttention engine** in SystemVerilog, verified bit-exact against a golden model, compiled to -WebAssembly, and running live in your browser. +

FlashAttention in SystemVerilog

-**Live:** https://tiny-attention.vercel.app +

+ A synthesizable FlashAttention engine, verified bit-exact against a golden model,
+ compiled to WebAssembly, and running live in your browser.
+

-![The visualizer running one FLASH pass](docs/media/visualizer.png) +

+ The tiled online-softmax schedule from the FlashAttention paper, as real RTL: the N × N score matrix never leaves the chip. + The same RTL runs in the browser and on an ECP5 FPGA. +

-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](https://github.com/tinycomputers/tiny-cpu). Same rule as the others: nothing on screen is faked. -Every value comes from a debug port on the compiled RTL, or from the golden model the RTL is checked against. +

+ MIT License + RTL: SystemVerilog + Runtime: WebAssembly + Verified: cocotb and a golden model + FPGA: ECP5 LFE5U-85F + Frontend: Astro and React + TypeScript strict +

-## What it does +

+ RTL CI + WASM Build + Web CI + FPGA synthesis +

-Attention computes `softmax(Q Kᵀ / √d) V`. The score matrix is N × N, and storing it is what makes standard attention -expensive in memory. FlashAttention computes the output one tile at a time with an online softmax, so the matrix never -exists in memory. This engine implements that schedule, plus the standard three-pass schedule for comparison, with -optional overlap of the K/V loads with compute. +

+ Open Live Visualizer +   + Read the Docs +

+
-## Verification +--- + +## Preview + +
+ +![The TinyAttention visualizer after one FLASH pass: schedule, tile values, HBM traffic and the output](docs/media/visualizer.png) + +
+ +Pick a preset (a toy sentence, a previous-token head, an attention sink, and more), press **Start**, and step the clock. +Every tile, buffer and byte in the screenshot is read from the compiled RTL running in your browser. + +--- + +## What is FlashAttention? -| What | Result | -|---|---| -| Engine vs golden model, end to end | Output, HBM bytes, tile count, K/V loads and busy cycles match on every run. Every sequence length 4 to 32, causal and not, every block size, both schedules, overlap on and off, random HBM latency, 200 seeds. | -| Exhaustive unit coverage | exp2 is checked on every input in its range; reciprocal on every mantissa. DMA burst timing is pinned cycle by cycle. | -| Output error vs float attention | Max 0.0028 to 0.0260 across the six presets (N = 12 to 16), in float units. The recorded 1000-seed study is in `sim/golden/study_results_1000seeds.txt`. | -| HBM traffic at N = 32 | Non-causal: FLASH 2432 B (4-row blocks) or 896 B (16-row blocks), NAIVE 12672 B. Causal: FLASH 1536 B or 768 B, NAIVE 7296 B. | -| WebAssembly | The compiled module, on golden fixtures for each schedule, matches the same quantities. 83 KB gzipped. | -| Web app | Browser tests of every page, the visualizer at phone width and in both colour schemes. | -| FPGA (ECP5 LFE5U-85F) | Synthesizes with no latches: 40,416 of 83,640 LUT4 positions, 8,726 flip-flops, 51 of 156 DSP blocks, no block RAM. fmax estimate 11.5 MHz, so timing is not closed at the ULX3S 25 MHz clock. The UART interface is verified in simulation; it has not run on a board. | +Attention computes `softmax(Q Kᵀ / √d) V`. Standard implementations build the full `N × N` score matrix in memory, +read it back for the softmax, and read it again for the product with `V`. At long sequence lengths that matrix dominates +the memory traffic, and that traffic is what limits speed. -Every row is a test in the repository, run by GitHub Actions on each pull request. The docs page -[Verification](https://tiny-attention.vercel.app/docs/verification) lists each check and its numbers. +**FlashAttention** computes the same output one tile at a time. A block of query rows stays on chip, key and value tiles +stream past it, and an **online softmax** keeps a running maximum `m` and running sum `l` for each row. When a new tile +raises the maximum, the old sum and the output accumulator are rescaled by `α = exp(m_old − m_new)`. The score matrix is never +written to memory, and the result is exact. -## Repository +This project implements that schedule in hardware, along with the standard three-pass schedule (NAIVE) for comparison. +Both run on the same arithmetic units, so the difference you see is the schedule, not the datapath. + +--- + +## Highlights + +- **A real FlashAttention datapath in RTL.** Two 4 × 4 systolic arrays (`S = Q·Kᵀ` and `P·V`), scale and causal mask, + online softmax with an exp2 unit and a reciprocal, and an O accumulator that rescales by α. All of it is in `rtl/`. +- **Bit-exact against a golden model.** The engine's output, HBM byte counts, tile counts and K/V loads match the Python + golden model on every end-to-end run, and busy cycles match a closed-form schedule model. +- **Fixed point, end to end.** INT8 Q, K and V with per-tensor scales folded into one 24-bit `cfg_scale`; UQ1.15 exp2 and + reciprocal from packed lookup tables; every width is documented in [ARCHITECTURE](docs/ARCHITECTURE.md). +- **Runs in the browser.** Verilator compiles the RTL to C++, Emscripten compiles that to a 84 KB (gzip) WebAssembly module, + and the visualizer reads every value from its debug ports. Nothing is reimplemented in JavaScript. +- **Synthesizes for an FPGA.** ECP5 (LFE5U-85F, the ULX3S chip) with no inferred latches, behind a UART command interface. +- **Teaching visualizer.** Four views: the softmax cell, the tile datapath, the tile schedule, and a naive-versus-flash + comparison with an N sweep and an error panel. + +--- + +## How It Works ``` -rtl/ SystemVerilog: arithmetic blocks, tile core, memory, controllers, top level -sim/ cocotb unit and end-to-end tests, the golden model (sim/golden), lint and run scripts -wasm/ Verilator + Emscripten build, the C++ harness, and the Node test over golden fixtures -web/ Astro + React site: the visualizer at /app, the docs, and the Vitest and Playwright tests -docs/ Architecture, the plan, the state schema, and the media for this README + rtl/*.sv ──► Verilator ──► C++ model ──► Emscripten ──► tiny_attn.wasm ──► Astro + React visualizer + (the design) (cycle-accurate) (wasm/build.sh) (web/public) (reads debug ports) + │ + ├── cocotb unit suites and end-to-end tests against sim/golden (Python, bit-exact) + ├── Verilator lint with -Wall, on every module and the full design + └── Yosys + nextpnr for ECP5 (fpga/ecp5) ``` -## Quick start +The browser never computes attention itself. It steps the compiled hardware one clock at a time and draws what the +hardware reports. If the RTL is wrong, the visualizer shows the wrong thing, and the test suites catch it first. + +--- + +## Hardware + +**Tile core (`attn_tile.sv`).** One 4-row group against one 4-row K/V tile per step, with 20 cycles from tile start to +tile done. MM1 computes `S` on a 4 × 4 weight-stationary array, the scale and causal mask apply, the online softmax updates +`m`, `l` and `α`, and MM2 computes `P·V` on a second array. The O accumulator rescales by `α` and adds the new product. +Five operations share this datapath: the FLASH step, and the four NAIVE passes. + +**Memory and control.** `hbm_model` is a 2,048-word memory with byte counters. `dma` moves bursts with a configurable +first-word latency (0 to 64 cycles). K and V are double-buffered when overlap is on. `flash_ctrl` loops over Q blocks and +K/V tiles, skips tiles above the causal diagonal, and normalizes and stores each block. `naive_ctrl` runs the three-pass +softmax through HBM. + +**Formats.** INT8 inputs, 17-bit `S`, Q8.8 log-domain `Z` and `m`, UQ1.15 exp2 and `α`, UQ1.8 `P`, 18-bit `l`, 26-bit `O`, +and a Q8.8 output. The full table, with the reasoning, is in the [fixed-point docs](https://tiny-attention.vercel.app/docs/fixed-point). + +### Schedule and traffic + +At `N = 32`, non-causal, the engine moves these bytes (from the golden traffic model, checked against the RTL counters): + +| Schedule | Q block | HBM bytes | vs NAIVE | +|:---|:---:|---:|:---:| +| NAIVE (three-pass, scores in HBM) | 4 rows | 12,672 | 1× | +| FLASH | 4 rows | 2,432 | 5.2× less | +| FLASH | 16 rows | 896 | 14.1× less | + +With the causal mask, the same schedules move 7,296 bytes (NAIVE), 1,536 (FLASH, 4 rows) and 768 (FLASH, 16 rows). +A larger Q block means fewer K/V loads: the output does not depend on the block size, only the traffic does. + +### Cycles + +Busy cycles at `N = 32`, non-causal, 10 cycles of HBM latency (`sim/perf_baseline.json`): + +| Configuration | Busy cycles | +|:---|---:| +| FLASH, 4-row Q block, no overlap | 4,113 | +| FLASH, 4-row Q block, K/V overlap | 2,937 | +| FLASH, 16-row Q block, no overlap | 2,133 | +| FLASH, 16-row Q block, K/V overlap | 1,657 | +| NAIVE | 10,961 | + +The sequential FLASH numbers match a closed-form schedule model exactly. The overlap and NAIVE numbers are pinned from the +RTL as regression baselines: a change to either fails the test suite. + +### Accuracy + +The engine's output against float attention on the same inputs, after dequantization. The error comes from INT8 input +quantization, not the datapath: + +| Preset | N | Causal | Max error | +|:---|---:|:---:|---:| +| toy sentence | 12 | yes | 0.0049 | +| previous token | 16 | yes | 0.0105 | +| attention sink | 16 | yes | 0.0253 | +| uniform | 16 | no | 0.0028 | +| self | 16 | no | 0.0260 | +| random (seeded) | 16 | no | 0.0163 | + +The 1,000-seed numerics study (`sim/golden/study_results_1000seeds.txt`) separates the datapath error from the total. +At `N = 32`, the datapath error averages 0.07 of one INT8 step of V for FLASH and 0.10 for NAIVE. + +--- + +## Verification + +Every row is a test in this repository, run in GitHub Actions on each pull request. + +| What | How it is checked | Scope | +|:---|:---|:---| +| Output, bytes, tiles, K/V loads and cycles | cocotb end-to-end test against the golden model | every N from 4 to 32, causal and not, every block size, both schedules, overlap on and off, random HBM latency, 200 seeds | +| Arithmetic blocks | cocotb unit suites | exhaustive exp2 and reciprocal sweeps; DMA burst timing cycle by cycle | +| Golden model | `pytest sim/golden` | 54 tests: quantization, exp2 and reciprocal, both schedules against float, traffic formulas, presets, and freshness of every generated table | +| Lint | Verilator `-Wall` | every module and the full design | +| WebAssembly | `node wasm/test.mjs` | the compiled engine on golden fixtures, both the committed build and a fresh build (CI) | +| UART interface | cocotb test of the FPGA top | the engine driven over its serial protocol, bit-exact | +| Web app | Vitest and Playwright | the quantizer and tile order against shared fixtures; every page in a real browser; the visualizer at phone width and in both colour schemes | + +--- + +## FPGA + +The engine behind a UART (`fpga/ecp5/fpga_top.sv`) synthesizes for the ECP5 LFE5U-85F (CABGA381, speed grade 6, the ULX3S +chip) with Yosys and nextpnr. `bash .tools/fpga.sh && bash fpga/ecp5/synth.sh` runs the flow; the `fpga.yml` workflow runs it in CI. + +| Resource (LFE5U-85F) | Used | Available | Share | +|:---|---:|---:|---:| +| LUT4 positions | 40,416 | 83,640 | 48% | +| Flip-flops | 8,726 | 83,640 | 10% | +| DSP (MULT18X18D) | 51 | 156 | 33% | +| Block RAM | 0 | 208 | 0% | + +Synthesis reports no inferred latches. The fmax estimate from nextpnr is **11.5 MHz**, below the 25 MHz board clock, so +**timing is not closed yet**. The estimate has no pin constraints. The memories read combinationally, so they map to LUT +RAM rather than block RAM. Moving them to block RAM changes the cycle model. The UART interface is verified in +simulation; the design has not run on a board yet. + +--- + +## Quick Start + +Requirements: Verilator 5.036, Python 3.12 with cocotb, Node 22 with pnpm, and Emscripten for the WebAssembly build. +The repo's `.tools/` scripts install the native and FPGA tools into the project. ```bash -bash .tools/setup.sh && bash .tools/emsdk.sh # Verilator 5.036 + Emscripten (project-local) +git clone https://github.com/tinycomputers/tiny-attention.git && cd tiny-attention +bash .tools/setup.sh && bash .tools/emsdk.sh # Verilator 5.036 and Emscripten (project-local) python3 -m venv .venv && .venv/bin/pip install -r sim/requirements.txt source scripts/env.sh bash sim/lint.sh # Verilator -Wall on every module bash sim/run_unit.sh # cocotb unit suites -SEEDS=200 bash sim/run_e2e.sh # engine vs golden, end to end -pytest sim/golden -q # golden model, fixtures, generated tables +SEEDS=200 bash sim/run_e2e.sh # engine against the golden model, end to end +pytest sim/golden -q # golden model, fixtures and generated tables -bash wasm/build.sh && node wasm/test.mjs # WebAssembly engine + golden fixtures +bash wasm/build.sh && node wasm/test.mjs # the WebAssembly engine on golden fixtures -cd web && pnpm install && pnpm dev # the site (dev server; see note below) -pnpm build && pnpm test:e2e # production build and browser tests +cd web && pnpm install && pnpm build && pnpm test:e2e # the site, production build, browser tests ``` -Note: `astro dev` hangs without output in some environments. `pnpm build` followed by any static file server over +`astro dev` can hang without output in some environments. `pnpm build` followed by any static file server over `web/dist` works. -## Docs +--- + +## Repository Structure + +```text +tiny-attention/ +│ +├── rtl/ SystemVerilog: arithmetic, tile core, memory, controllers, top level +│ ├── tiny_attn_top.sv top level: FLASH or NAIVE, K/V overlap, debug ports +│ ├── attn_tile.sv tile core: MM1, scale and mask, online softmax, MM2, O accumulator +│ ├── flash_ctrl.sv FlashAttention schedule and K/V prefetch +│ ├── naive_ctrl.sv three-pass softmax through HBM +│ └── lut_pkg.sv exp2 and reciprocal tables (generated) +│ +├── sim/ cocotb tests, golden model, runners +│ ├── golden/ Python golden model: fixed.py, float_ref.py, traffic.py, schedule.py +│ ├── test_e2e.py engine against the golden model, end to end +│ ├── test_fpga_uart.py the FPGA top over its UART +│ └── perf_baseline.json pinned busy cycles +│ +├── wasm/ Verilator and Emscripten build, C++ harness, Node test +├── fpga/ecp5/ UART wrapper, ECP5 synthesis script +├── web/ Astro and React site: /app visualizer, /docs pages, tests +└── docs/ ARCHITECTURE, PLAN, STATE_SCHEMA, media +``` + +--- + +## Documentation + +| Page | What it covers | +|:---|:---| +| [What is attention](https://tiny-attention.vercel.app/docs/what-is-attention) | Queries, keys, values, the score matrix, causal masks. A live float demo. | +| [Online softmax](https://tiny-attention.vercel.app/docs/online-softmax) | The running max and sum, the rescale, and the engine's row state recorded tile by tile. | +| [FlashAttention](https://tiny-attention.vercel.app/docs/flashattention) | The tile schedule, why it saves memory traffic, and the byte counts of both schedules. | +| [Fixed point](https://tiny-attention.vercel.app/docs/fixed-point) | Number formats, the exp2 unit, INT8 quantization with a live demo, and measured error. | +| [Architecture](https://tiny-attention.vercel.app/docs/architecture) | The blocks of the RTL and how they connect. | +| [Verification](https://tiny-attention.vercel.app/docs/verification) | Every check, the command that runs it, and the measured results. | + +The design plan and the status of each phase are in [`docs/PLAN.md`](docs/PLAN.md). The full architecture, with the +measured schedule and traffic models, is in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). + +--- + +## Roadmap + +| Status | Item | +|:---:|:---| +| ✅ | RTL FlashAttention engine (FLASH, NAIVE, K/V overlap), bit-exact against the golden model | +| ✅ | WebAssembly build and browser visualizer with four views | +| ✅ | Docs site, landing page, and FPGA synthesis for ECP5 with a UART interface | +| 🔲 | Timing closure at 25 MHz on the ECP5 | +| 🔲 | Block RAM for the HBM model and the K/V buffers (changes the cycle model) | +| 🔲 | ULX3S pin constraints and a run on the board | +| 🔲 | Larger head dimension (`d` = 8, 16) by accumulating MM1 over d-chunks | +| 🔲 | Decode mode: one query row against a growing K/V cache | + +--- + +## Related Projects + +Part of the [TinyComputers](https://github.com/tinycomputers) series of small, real hardware projects, each verified against +a reference model and visualized in the browser: + +- [tiny-gpu](https://github.com/tinycomputers/tiny-gpu): a SIMT GPU simulator in Python +- [tiny-tpu](https://github.com/tinycomputers/tiny-tpu): a 4 × 4 systolic array in SystemVerilog, running in the browser +- [tiny-compiler](https://github.com/tinycomputers/tiny-compiler): compiles one tensor program to tiny-tpu tile schedules or tiny-gpu kernels + +--- + +## Contributing + +Issues and pull requests are welcome. Run the checks in the Quick Start before opening a pull request. The RTL, the golden +model and the generated tables must stay in sync: `pytest sim/golden` fails if they drift. -Six pages with live demos: [what attention computes](https://tiny-attention.vercel.app/docs/what-is-attention), -[the online softmax](https://tiny-attention.vercel.app/docs/online-softmax), -[the FlashAttention schedule](https://tiny-attention.vercel.app/docs/flashattention), -[fixed-point formats and error](https://tiny-attention.vercel.app/docs/fixed-point), -[the architecture](https://tiny-attention.vercel.app/docs/architecture), and -[verification](https://tiny-attention.vercel.app/docs/verification). +## References -The plan and the status of each phase are in [`docs/PLAN.md`](docs/PLAN.md). The architecture, with the measured -schedule and traffic models, is in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). +- Tri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré. *FlashAttention: Fast and Memory-Efficient Exact + Attention with IO-Awareness.* 2022. [arXiv:2205.14135](https://arxiv.org/abs/2205.14135) +- Maxim Milakov, Natalia Gimelshein. *Online normalizer calculation for softmax.* 2018. [arXiv:1805.02867](https://arxiv.org/abs/1805.02867) ## License -MIT +MIT. See [LICENSE](LICENSE). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 4e7dd83..ff1be51 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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) diff --git a/docs/PLAN.md b/docs/PLAN.md index ef4857a..d8d8102 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -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`. @@ -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). diff --git a/docs/media/logo-dark.svg b/docs/media/logo-dark.svg new file mode 100644 index 0000000..a35fbc3 --- /dev/null +++ b/docs/media/logo-dark.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + TinyAttention + diff --git a/docs/media/logo-light.svg b/docs/media/logo-light.svg new file mode 100644 index 0000000..b6c0504 --- /dev/null +++ b/docs/media/logo-light.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + TinyAttention + diff --git a/wasm/build.sh b/wasm/build.sh index f5ac9ef..a79cc0e 100755 --- a/wasm/build.sh +++ b/wasm/build.sh @@ -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}..." @@ -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 \ diff --git a/wasm/test.mjs b/wasm/test.mjs index c8c5c65..d4cfe47 100644 --- a/wasm/test.mjs +++ b/wasm/test.mjs @@ -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 diff --git a/web/public/favicon.svg b/web/public/favicon.svg index ce622ea..f0aeee5 100644 --- a/web/public/favicon.svg +++ b/web/public/favicon.svg @@ -1 +1,19 @@ - + + + + + + + + + + + + + + + + + + + diff --git a/web/public/tiny_attn.wasm b/web/public/tiny_attn.wasm index 6de0941..fa4fc79 100755 Binary files a/web/public/tiny_attn.wasm and b/web/public/tiny_attn.wasm differ