Full-FPGA inference research project for FPGA-native language models.
Autoregressive LLM decode at batch size 1 is constrained by memory movement,
low utilization on general-purpose GPUs, and the overhead of launching thousands
of small kernels per token. This project investigates whether a model co-designed
for FPGA execution can beat a GPU baseline in energy efficiency (joule/token)
for inference-only text generation.
The approach is not to port an existing dense Transformer unchanged, but to co-design hardware and software:
You can validate the quant ablation and measure GPU baseline tok/s on a free T4 without installing anything locally.
Option A — One-click notebook:
Option B — One-cell script (copy-paste):
Open Colab, create a new notebook, paste the contents of scripts/colab_quickstart.py into a single code cell, and run it. It installs deps, clones the repo, loads a proxy model, runs INT3/INT4/BFP4 tests, and measures tok/s automatically. Results are saved to the Files panel for download.
If you have Colab Pro/Pro+ with an A100 (40 GB or 80 GB) or RTX PRO 6000 and a HuggingFace token with access to the gated model, you can benchmark the actual target model.
Prerequisite:
HF_TOKEN, Value = your tokenRecommended — vLLM (best MoE support):
Known issue: Qwen3.6-35B-A3B is a MoE model that llama.cpp does NOT support well (hangs during load). vLLM has native MoE support and is the most reliable backend.
This notebook offers three backends:
One-cell script (manual paste):
Paste the contents of scripts/colab_35b_vllm.py for vLLM (recommended), or scripts/colab_35b_baseline.py for the original transformers path.
If you do not have HF access to the gated model, switch to a public proxy (e.g. Qwen/Qwen2.5-14B) by editing the MODEL_NAME variable at the top of the script.
joule/token
Secondary metrics:
tokens/s/W
ms/token
tokens/s
model perplexity
FPGA resource utilization
Qwen3-35B-A3B on AWS F2 (2× AMD VU47P). See docs/QWEN3_A3B_FPGA_MAPPING.md.
Hardware specs:
fpga/ HLS kernels (host-compilable with g++ via hls_stubs.hpp)
matvec_int4.hpp INT4×INT8 matvec tile (dense + 2:4 sparse)
matvec_int2.hpp INT2×INT8 ternary matvec tile
rmsnorm_engine.hpp Streaming RMSNorm with rsqrt LUT
silu_lut.hpp Piecewise-linear SiLU via BRAM LUT
softmax_engine.hpp Shifted softmax with exp LUT
rope_engine.hpp Rotary Position Embedding (decode)
sampler_engine.hpp Greedy + categorical PRNG sampler
block_pipeline_dense.hpp Full 10-stage dense block DATAFLOW
*_tb.cpp Host testbenches (no Vitis HLS required)
src/fllm/ Python reference implementation
model.py Transformer + generate loop
quant.py Fake-quant INT4 / INT2 / per-channel scales
sparsity.py N:M structured pruning
vocab_cache.py Two-path LM head (URAM cache + HBM fallback)
speculative.py Draft model + speculative decode
export.py Packed INT4 binary exporter (FLLM v1 format)
scripts/ Research & utility scripts
token_loop_sim.py Cycle-accurate decode simulator
cost_report.py FPGA-vs-GPU roofline comparison
prepare_qwen_weights.py HF download → INT4 → FLLM export
prepare_dummy_weights.py Random Qwen-shaped → FLLM export
cycle_report.py Per-kernel cycle budget
tests/ Smoke & integration tests
All kernels compile with standard g++ (no Xilinx tools installed) using
fpga/hls_stubs.hpp, which provides ap_uint<N>, ap_int<N>, and hls::stream
up to 65536 bits.
| Kernel | Testbench | Max Error vs Python |
|---|---|---|
matvec_int4 | matvec_int4_kernel_tb.cpp | 0 (dense + sparse) |
matvec_int2 | matvec_int2_tb.cpp | 0 (ternary) |
rmsnorm_engine | rmsnorm_engine_tb.cpp | 1e-6 |
silu_lut | silu_lut_tb.cpp | 1.5e-5 |
softmax_engine | softmax_engine_tb.cpp | < 1e-2 |
rope_engine | rope_engine_tb.cpp | 0 |
sampler_engine | sampler_engine_tb.cpp | exact (deterministic) |
block_pipeline_dense | block_pipeline_dense_tb.cpp | structural (zero-weight sanity) |
g++ -std=c++17 -I. -I./fpga -DFLLM_LANES=32 -DFLLM_TILE_ROWS=16 \
-o /tmp/matvec_int4_tb fpga/matvec_int4_kernel_tb.cpp && /tmp/matvec_int4_tb
g++ -std=c++17 -I. -I./fpga -DFLLM_LANES=32 -DFLLM_TILE_ROWS=16 \
-o /tmp/matvec_int2_tb fpga/matvec_int2_tb.cpp && /tmp/matvec_int2_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/rmsnorm_tb fpga/rmsnorm_engine_tb.cpp && /tmp/rmsnorm_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/silu_tb fpga/silu_lut_tb.cpp && /tmp/silu_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/softmax_tb fpga/softmax_engine_tb.cpp && /tmp/softmax_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/rope_tb fpga/rope_engine_tb.cpp && /tmp/rope_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/sampler_tb fpga/sampler_engine_tb.cpp && /tmp/sampler_tb
g++ -std=c++17 -I. -I./fpga -DFLLM_LANES=32 -DFLLM_TILE_ROWS=16 \
-o fpga/block_pipeline_dense_tb fpga/block_pipeline_dense_tb.cpp && ./fpga/block_pipeline_dense_tb
PYTHONPATH=src python3 tests/test_qwen_fpga_sim.py
PYTHONPATH=src python3 tests/test_ternary_linear.py
PYTHONPATH=src python3 tests/test_speculative.py
PYTHONPATH=src python3 tests/test_end_to_end.py
PYTHONPATH=src python3 scripts/cost_report.py
PYTHONPATH=src python3 scripts/token_loop_sim.py \
--decode-steps 128 --spec-draft 4 --spec-accept 0.70
PYTHONPATH=src python3 scripts/prepare_dummy_weights.py --out-dir checkpoints/dummy-fpga
# → 121 MB, manifest.json, round-trip MSE < 0.02
transformers, ~22 GB disk for 35B INT4)PYTHONPATH=src python3 scripts/prepare_qwen_weights.py \
--model-id Qwen/Qwen3.6-35B-A3B \
--out-dir checkpoints/qwen3-fpga \
--nm-n 2 --nm-m 4
For smaller variants (0.5B–7B) to test the pipeline on a laptop:
PYTHONPATH=src python3 scripts/prepare_qwen_weights.py \
--model-id Qwen/Qwen2.5-0.5B-Instruct \
--out-dir checkpoints/qwen05b-fpga
Run Qwen3-35B-A3B decode on AWS F2 at >300 tok/s with lower joule/token
than a g6/g6e GPU instance.
39 commits
Python
62.4%
C++
34.8%
Jupyter Notebook
2.8%
Full-FPGA inference research project for FPGA-native language models.
Autoregressive LLM decode at batch size 1 is constrained by memory movement,
low utilization on general-purpose GPUs, and the overhead of launching thousands
of small kernels per token. This project investigates whether a model co-designed
for FPGA execution can beat a GPU baseline in energy efficiency (joule/token)
for inference-only text generation.
The approach is not to port an existing dense Transformer unchanged, but to co-design hardware and software:
You can validate the quant ablation and measure GPU baseline tok/s on a free T4 without installing anything locally.
Option A — One-click notebook:
Option B — One-cell script (copy-paste):
Open Colab, create a new notebook, paste the contents of scripts/colab_quickstart.py into a single code cell, and run it. It installs deps, clones the repo, loads a proxy model, runs INT3/INT4/BFP4 tests, and measures tok/s automatically. Results are saved to the Files panel for download.
If you have Colab Pro/Pro+ with an A100 (40 GB or 80 GB) or RTX PRO 6000 and a HuggingFace token with access to the gated model, you can benchmark the actual target model.
Prerequisite:
HF_TOKEN, Value = your tokenRecommended — vLLM (best MoE support):
Known issue: Qwen3.6-35B-A3B is a MoE model that llama.cpp does NOT support well (hangs during load). vLLM has native MoE support and is the most reliable backend.
This notebook offers three backends:
One-cell script (manual paste):
Paste the contents of scripts/colab_35b_vllm.py for vLLM (recommended), or scripts/colab_35b_baseline.py for the original transformers path.
If you do not have HF access to the gated model, switch to a public proxy (e.g. Qwen/Qwen2.5-14B) by editing the MODEL_NAME variable at the top of the script.
joule/token
Secondary metrics:
tokens/s/W
ms/token
tokens/s
model perplexity
FPGA resource utilization
Qwen3-35B-A3B on AWS F2 (2× AMD VU47P). See docs/QWEN3_A3B_FPGA_MAPPING.md.
Hardware specs:
fpga/ HLS kernels (host-compilable with g++ via hls_stubs.hpp)
matvec_int4.hpp INT4×INT8 matvec tile (dense + 2:4 sparse)
matvec_int2.hpp INT2×INT8 ternary matvec tile
rmsnorm_engine.hpp Streaming RMSNorm with rsqrt LUT
silu_lut.hpp Piecewise-linear SiLU via BRAM LUT
softmax_engine.hpp Shifted softmax with exp LUT
rope_engine.hpp Rotary Position Embedding (decode)
sampler_engine.hpp Greedy + categorical PRNG sampler
block_pipeline_dense.hpp Full 10-stage dense block DATAFLOW
*_tb.cpp Host testbenches (no Vitis HLS required)
src/fllm/ Python reference implementation
model.py Transformer + generate loop
quant.py Fake-quant INT4 / INT2 / per-channel scales
sparsity.py N:M structured pruning
vocab_cache.py Two-path LM head (URAM cache + HBM fallback)
speculative.py Draft model + speculative decode
export.py Packed INT4 binary exporter (FLLM v1 format)
scripts/ Research & utility scripts
token_loop_sim.py Cycle-accurate decode simulator
cost_report.py FPGA-vs-GPU roofline comparison
prepare_qwen_weights.py HF download → INT4 → FLLM export
prepare_dummy_weights.py Random Qwen-shaped → FLLM export
cycle_report.py Per-kernel cycle budget
tests/ Smoke & integration tests
All kernels compile with standard g++ (no Xilinx tools installed) using
fpga/hls_stubs.hpp, which provides ap_uint<N>, ap_int<N>, and hls::stream
up to 65536 bits.
| Kernel | Testbench | Max Error vs Python |
|---|---|---|
matvec_int4 | matvec_int4_kernel_tb.cpp | 0 (dense + sparse) |
matvec_int2 | matvec_int2_tb.cpp | 0 (ternary) |
rmsnorm_engine | rmsnorm_engine_tb.cpp | 1e-6 |
silu_lut | silu_lut_tb.cpp | 1.5e-5 |
softmax_engine | softmax_engine_tb.cpp | < 1e-2 |
rope_engine | rope_engine_tb.cpp | 0 |
sampler_engine | sampler_engine_tb.cpp | exact (deterministic) |
block_pipeline_dense | block_pipeline_dense_tb.cpp | structural (zero-weight sanity) |
g++ -std=c++17 -I. -I./fpga -DFLLM_LANES=32 -DFLLM_TILE_ROWS=16 \
-o /tmp/matvec_int4_tb fpga/matvec_int4_kernel_tb.cpp && /tmp/matvec_int4_tb
g++ -std=c++17 -I. -I./fpga -DFLLM_LANES=32 -DFLLM_TILE_ROWS=16 \
-o /tmp/matvec_int2_tb fpga/matvec_int2_tb.cpp && /tmp/matvec_int2_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/rmsnorm_tb fpga/rmsnorm_engine_tb.cpp && /tmp/rmsnorm_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/silu_tb fpga/silu_lut_tb.cpp && /tmp/silu_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/softmax_tb fpga/softmax_engine_tb.cpp && /tmp/softmax_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/rope_tb fpga/rope_engine_tb.cpp && /tmp/rope_tb
g++ -std=c++17 -I. -I./fpga -o /tmp/sampler_tb fpga/sampler_engine_tb.cpp && /tmp/sampler_tb
g++ -std=c++17 -I. -I./fpga -DFLLM_LANES=32 -DFLLM_TILE_ROWS=16 \
-o fpga/block_pipeline_dense_tb fpga/block_pipeline_dense_tb.cpp && ./fpga/block_pipeline_dense_tb
PYTHONPATH=src python3 tests/test_qwen_fpga_sim.py
PYTHONPATH=src python3 tests/test_ternary_linear.py
PYTHONPATH=src python3 tests/test_speculative.py
PYTHONPATH=src python3 tests/test_end_to_end.py
PYTHONPATH=src python3 scripts/cost_report.py
PYTHONPATH=src python3 scripts/token_loop_sim.py \
--decode-steps 128 --spec-draft 4 --spec-accept 0.70
PYTHONPATH=src python3 scripts/prepare_dummy_weights.py --out-dir checkpoints/dummy-fpga
# → 121 MB, manifest.json, round-trip MSE < 0.02
transformers, ~22 GB disk for 35B INT4)PYTHONPATH=src python3 scripts/prepare_qwen_weights.py \
--model-id Qwen/Qwen3.6-35B-A3B \
--out-dir checkpoints/qwen3-fpga \
--nm-n 2 --nm-m 4
For smaller variants (0.5B–7B) to test the pipeline on a laptop:
PYTHONPATH=src python3 scripts/prepare_qwen_weights.py \
--model-id Qwen/Qwen2.5-0.5B-Instruct \
--out-dir checkpoints/qwen05b-fpga
Run Qwen3-35B-A3B decode on AWS F2 at >300 tok/s with lower joule/token
than a g6/g6e GPU instance.
39 commits
Python
62.4%
C++
34.8%
Jupyter Notebook
2.8%