|
|
||
|---|---|---|
| .. | ||
| convert | ||
| fw | ||
| kernels | ||
| tests | ||
| tools | ||
| README.md | ||
| build_tensix_dataflow_kernel.sh | ||
| build_tensix_kernel.sh | ||
| build_tensix_program.sh | ||
| ft_tt_arc_telemetry.c | ||
| ft_tt_arc_telemetry.h | ||
| ft_tt_arch.c | ||
| ft_tt_arch.h | ||
| ft_tt_arch_blackhole.c | ||
| ft_tt_buffer.c | ||
| ft_tt_buffer.h | ||
| ft_tt_coord.c | ||
| ft_tt_coord.h | ||
| ft_tt_core_readiness.c | ||
| ft_tt_core_readiness.h | ||
| ft_tt_core_status.c | ||
| ft_tt_core_status.h | ||
| ft_tt_device.c | ||
| ft_tt_device.h | ||
| ft_tt_dram_shard.c | ||
| ft_tt_dram_shard.h | ||
| ft_tt_dram_stripe.c | ||
| ft_tt_dram_stripe.h | ||
| ft_tt_elf.c | ||
| ft_tt_elf.h | ||
| ft_tt_fttw.c | ||
| ft_tt_fttw.h | ||
| ft_tt_group.c | ||
| ft_tt_group.h | ||
| ft_tt_group_broadcast_partition.c | ||
| ft_tt_group_broadcast_partition.h | ||
| ft_tt_group_shared_operand.c | ||
| ft_tt_group_shared_operand.h | ||
| ft_tt_kernel_blob.c | ||
| ft_tt_kernel_blob.h | ||
| ft_tt_kernels.h | ||
| ft_tt_kmd.c | ||
| ft_tt_kmd.h | ||
| ft_tt_kmd_abi.h | ||
| ft_tt_launch.c | ||
| ft_tt_launch.h | ||
| ft_tt_mailbox.h | ||
| ft_tt_memmap.h | ||
| ft_tt_multicast.c | ||
| ft_tt_multicast.h | ||
| ft_tt_noc.c | ||
| ft_tt_noc.h | ||
| ft_tt_noc_copy.h | ||
| ft_tt_op_attention.c | ||
| ft_tt_op_attention.h | ||
| ft_tt_op_dispatch.c | ||
| ft_tt_op_dispatch.h | ||
| ft_tt_op_gdn.c | ||
| ft_tt_op_gdn.h | ||
| ft_tt_op_gdnc.c | ||
| ft_tt_op_gdnc.h | ||
| ft_tt_op_l1_layout.h | ||
| ft_tt_op_matmul.c | ||
| ft_tt_op_matmul.h | ||
| ft_tt_op_matmul_cooperative.c | ||
| ft_tt_op_matmul_cooperative.h | ||
| ft_tt_op_matmul_resident_multicast.c | ||
| ft_tt_op_matmul_resident_multicast.h | ||
| ft_tt_op_memory.c | ||
| ft_tt_op_memory.h | ||
| ft_tt_op_replicas.c | ||
| ft_tt_op_replicas.h | ||
| ft_tt_op_rms_norm.c | ||
| ft_tt_op_rms_norm.h | ||
| ft_tt_op_rope.c | ||
| ft_tt_op_rope.h | ||
| ft_tt_op_sample.c | ||
| ft_tt_op_sample.h | ||
| ft_tt_op_vector.c | ||
| ft_tt_op_vector.h | ||
| ft_tt_program_blob.c | ||
| ft_tt_program_blob.h | ||
| ft_tt_program_resolve.c | ||
| ft_tt_program_resolve.h | ||
| ft_tt_programs.h | ||
| ft_tt_queue.c | ||
| ft_tt_queue.h | ||
| ft_tt_runtime.c | ||
| ft_tt_runtime.h | ||
| ft_tt_runtime_udrv.c | ||
| ft_tt_runtime_udrv.h | ||
| ft_tt_tensix_claim.c | ||
| ft_tt_tensix_claim.h | ||
| ft_tt_tile_row0.c | ||
| ft_tt_tile_row0.h | ||
| ft_tt_tilize.c | ||
| ft_tt_tilize.h | ||
| ft_tt_tlb.c | ||
| ft_tt_tlb.h | ||
| ft_tt_udrv.h | ||
| ft_tt_udrv_arc_msg.c | ||
| ft_tt_udrv_arc_msg.h | ||
| ft_tt_udrv_arc_probe.c | ||
| ft_tt_udrv_arc_probe.h | ||
| ft_tt_udrv_claim.c | ||
| ft_tt_udrv_claim.h | ||
| ft_tt_udrv_noc.c | ||
| ft_tt_udrv_noc.h | ||
| ft_tt_udrv_noc_bank.c | ||
| ft_tt_udrv_noc_bank.h | ||
| ft_tt_udrv_pci_sysfs.c | ||
| ft_tt_udrv_pci_sysfs.h | ||
| ft_tt_udrv_reset.c | ||
| ft_tt_udrv_reset.h | ||
| ft_tt_udrv_tlb_encode.c | ||
| ft_tt_udrv_tlb_encode.h | ||
| ft_tt_watchdog.c | ||
| ft_tt_watchdog.h | ||
| tensix_kernel_build_arch.sh | ||
README.md
tt/
Tenstorrent Blackhole GPU backend. Host-side C plus hand-written Tensix kernels, driven straight through the kernel driver.
Card: Tenstorrent Blackhole p150a, 140 Tensix cores, firmware pinned at 19.2.0.
Status: 8 op families green on silicon (2026-08-12), all measured on one core of 140.
Device kernels are C-only as of 2026-08-13 — Phase 6.6 converted all 36, and the last C++
in the tree (a compute arm inside resident_queue_protocol.h) went with them.
Read first: "Build and test" below — on-card runs can wedge cores.
Dependencies
SFPI compiler only (/opt/tenstorrent/sfpi: GCC 15.1 for riscv-tt-elf, a tool like GCC),
plus python3 for the tools/emit_tensix_*.py generators, and only to rebuild Tensix programs. The built images are embedded in ft_tt_programs.h, so a
Foundry build needs no toolchain — the same arrangement as cuda/, which embeds PTX.
- Provenance. The Tenstorrent backend was rewritten from public documentation and measured
hardware behaviour. No tt-metal, TT-NN, tt-umd or tt-llk source is compiled, linked or
vendored. Firmware (
fw/), startup, linker scripts, hardware headers, kernel library and host driver are Foundry's own, rewritten in 2026-10 from the publictt-isa-documentationand our own measurements; every image is linked-nostdlib. - No C++ anywhere, host or device. Host and kernels are C99.
- Driver: the in-tree userspace driver (
ft_tt_udrv_*, PCI sysfs) drives the card with no kernel module.ft_tt_kmd.ccan use tt-kmd's ioctl ABI instead (numbers only,ft_tt_kmd_abi.h); tt-kmd is optional, not a dependency.
Layout
| Path | What |
|---|---|
ft_tt_*.{c,h} |
Host backend: device open, TLBs, NoC, launch, queue, ops, telemetry |
ft_tt_op_*.{c,h} |
One host driver per op family (matmul, vector, memory, rope, sample, attention, rms_norm, dispatch) |
kernels/*.c |
Tensix device kernels, named <op>_{reader,compute,writer}.c |
kernels/ft_tensix_*.h |
The C99 device library the kernels are written against — CB primitives, NoC, the unpack/math/pack sequences, the SFPU |
kernels/*_protocol.h |
Host↔device wire contracts — the two sides must agree word for word |
kernels/cb_tables/*.cbtable |
Circular-buffer descriptor tables (compile-time format contract) |
convert/ |
FTTW weight converter: GGUF in, tiled on-card artifact out |
tests/ |
16 Makefiles; off-hardware gates and on-card acceptance are separate build trees |
tools/ |
ft_tt_core (per-core status / probe / recovery), the kernel + CB emitters, and the three gates below |
build_tensix_*.sh |
The kernel build scripts (SFPI) |
.cbtable files are the compile-time half of the CB contract — data format, tile geometry,
math fidelity — turned into ft_tensix_cb_formats.h by tools/emit_tensix_cb_formats.py.
There is no runtime check: what the host writes into the L1 CB config must match what was
baked in, or the kernel reads the right bytes as the wrong format.
The three gates a kernel change must pass
Instruction equality is the main one, but it is not sufficient — two of the three findings that nearly shipped in Phase 6.6 were invisible to it.
| gate | what it catches | how |
|---|---|---|
| instruction-stream diff | a different Tensix instruction, store or immediate | tools/diff_tensix_kernel_instructions.sh — identical stream, plus a named cause for any RISC-V difference |
| text size vs the slab | a kernel that will not load at all | size against FT_TT_OP_TEXT_SLAB_BYTES (0x3000). The first C build of resident_attention_compute was 836 B over — and the instruction diff was perfectly happy, because C inlined what C++ outlined |
Two more, for the artifacts rather than the code: make -f tools/Makefile regen-kernels rebuilds
the embedded ELFs in ft_tt_kernels.h (and refuses if the built firmware differs from
FT_TT_FIRMWARE_DIR — kernels link at their firmware's __fw_export_text_end, so the two must
move together), and make -f tests/Makefile.vector check-programs reads the .src sidecars to
refuse any on-card program directory built from a source that has since changed or vanished.
Both exist because a stale artifact shipped twice in one day.
Execution model — it is not cuda/
Three differences that change how you write an op:
- Cores are programmed independently. There is no grid launch. Each Tensix is loaded,
started, and waited on by name;
ft_tt_group.cbatches that, it does not hide it. - A
__global__becomes a reader/compute/writer triad. Five RISC-Vs per core: reader on BRISC (NoC 0), compute on TRISC0/1/2, writer on NCRISC (NoC 1) — the two data-movement kernels sit on different NoCs so they do not contend. The unit of data is a 32x32 tile (four 16x16 faces), not a scalar or a warp lane. - Ops are resident, not dispatched.
ft_tt_queue.his a descriptor ring in the core's own L1; a resident kernel polls it and executes 32-byte descriptors. The host enqueues work and bumps a counter — no per-op launch. The consequence is stated in the header: do not fuse ops to save dispatch, there is no dispatch to save.
Retirement is by asking, never by reset: every blocking wait polls stop_request in the same
loop, and FT_TT_QUEUE_OP_STOP is the in-stream sentinel. assert_reset on a blocked resident
kernel is how cores were wedged.
What works, with numbers
All figures are one core, at HiFi4 and this card's 800 MHz (measured busy).
| Op family | State |
|---|---|
| matmul (bf16 / Bfp8_b / Bfp4_b) | green, the load-bearing op |
| elementwise (SiLU, sigmoid, add, mul, sub, SiLU-mul) | one kernel, runtime-selected |
| reduce (softmax, RMS norm, layer norm, router normalise) | one residency, four ops |
| embedding, KV-cache append | green |
| RoPE (NeoX), flash-decode attention | green |
| on-card top-k + sampling | green, gated on exact index agreement |
| Gated DeltaNet (Qwen3.5 linear layers: conv1d+SiLU, delta rule) | built (ft_tt_op_gdn, ft_tt_op_gdnc); runs on the card through Pantheon's Ornith forward |
| MoE, fusions | not built |
- matmul 468.8 GFLOP/s (
ffn_down, prefetch depth 48) to 631.0 GFLOP/s (ffn_gate/up, Mt=32) = 28.6% to 38.5% of one Tensix. The 1.64 TFLOP/s denominator is derived (2048 FLOP/cycle at HiFi4 x 800 MHz), not measured; the 800 MHz busy clock is measured. Through the resident triad on a real op: 595.2 GFLOP/s (36.30%). - dispatch 1.363 µs per op host-side at batch 32, 0.391 µs device-side op-to-op.
Upstream's
bh_p150CI gates op-to-op at 2.08 µs. - attention (flash-decode) 0.840 ms at
block_tiles=16/descriptor at 8k KV = 40.3 ms/token = 24.8 tok/s. L1 is flat in context at 413,696 B. Correctness verified kv=1..2048, PCC 0.99998-1.00000 — the timing point is beyond the verified range. - top-k 2.590 ms/token over a 130,560 vocabulary, against 55.6 ms to ship that row to the host: 21x, and context-independent.
- binary switch 0.484 ms, of which ~93% is the loader, not the x1 link.
- cores
ft_tt_core probe allis 140/140.
Honest limits
- The per-op figures above are one core of 140. Multi-core matmul exists — split groups
(
ft_tt_group.c) and a cooperative matmul that multicasts the shared A block (ft_tt_op_matmul_cooperative.c,ft_tt_op_matmul_resident_multicast.c). Scaling is far from linear: an N=8 split offfn_gate/upmeasured 2.07x over one core (2026-08-13, operands in two DRAM banks). - One forward pass on one core is ~111.9 ms = 8.94 decode tok/s. That is composed from
measured per-op costs (3.745 ms/layer x 24 layers + 22.0 ms lm_head, dispatch floor
subtracted). Separately, Pantheon (a separate engine built on this backend) runs a 9B
qwen35model (Ornith) end-to-end on the card, with greedy output byte-identical to its CPU oracle (Pantheon39d0512, 2026-08-18). - The card's Aggregate DRAM read bandwidth is >= 537 GB/s sustained across 14 concurrent cores and NOT saturated — the ceiling is still unknown, and every core in that test read the same ~17 MB working set, which is the friendliest possible pattern. The reader reaches 39.4 GB/s and the measured floor was transaction count, not bandwidth; the residual ~103 ns/tile-pair floor under that is unidentified.
- Card→host DMA does not exist on Blackhole; host→card is 287 MB/s (write-combined) on this host's x1 link.
Build and test
Kernel builds need SFPI, so kernel compilation runs on a host with SFPI installed, and every on-card suite runs on the host with the card. Off-hardware suites build anywhere.
make USE_TT=1 tt from Foundry's root builds the host backend objects (no SFPI
needed — see above). Suites still have no root-Makefile target; each has its own
Makefile, run from tt/tests/:
# off-hardware (no card, safe while someone else owns the silicon)
make -f Makefile.coord check # coordinates, arch tables, harvesting
make -f Makefile.noc check
make -f Makefile.queue check # the resident-queue protocol, host side
make -f Makefile.vector all # -> build-vector/test_vector_ops
# on-card (the host with the card). PHASE1B=1 opens the NoC fence and is mandatory.
make -f Makefile.ops PHASE1B=1 all
make -f Makefile.phase5 PHASE1B=1 all
make -f Makefile.phase6 PHASE1B=1 all
make -f Makefile.vector PHASE1B=1 card
# the kernel gates (no card needed; needs SFPI)
make -f tools/Makefile regen-kernels # rebuild the embedded ELFs in ft_tt_kernels.h
make -f tests/Makefile.vector check-programs # refuse stale on-card program directories
# per-core status / recovery tool
cd ../tools && make PHASE1B=1 && ./build/ft_tt_core status all
# weight converter (no card, no tt-metal)
cd ../convert/tests && make check
make red on any of these means clean-then-build-then-run. It exists because Apple ships GNU
make 3.81, whose one-second mtime granularity silently skips a rebuild of a source edited in
the same second as its object — a mutation loop on incremental make can be testing the
previous binary.
On-card runs are detached and polled, never held over an SSH session, and never launched against cores another job owns.
See also
../docs/tenstorrent.md— the backend page