# Tensor-Transformer Variants are Surprisingly Performant

I've been researching tensor networks as a more interpretable architecture, but whenever I tell people this, they always ask "But is it any good?"

So I trained multiple 500M parameter LLMs on fineweb, showing the tensor variant needed ~4% more batches of data to match CE-loss. 

There's a few caveats, so my personal estimate is around 15% worst to 10% better. Details below.

The Architecture
================

Replacing MLP w/ a Bilinear Layer
---------------------------------

![](https://39669.cdn.cke-cs.com/rQvD3VnunXZu34m86e5f/images/535b3ea90f032f6aa3bee59feaad8ff544c9f23e04e29ce2.png)

An MLP is a linear encoder, ReLU, then linear decoder. 

$$
MLP(x) = D(ReLU(E(x)))
$$

A bilinear layer asks "what's better than one encoder? Two!"

$$
Bilinear(x) = D(Lx \odot Rx)
$$

Where $\odot$ means "element-wise multiply"  eg

$$
[1, 2, 3] \odot [1, 2, 3] = [1, 4, 9]
$$

A SwiGLU Layer (Swish Gated Linear Unit) says "Let's add in nonlinearities"

$$
SwiGLU(x) = D(swish(Lx) \odot Rx)
$$

SwiGLU is a SOTA architecture & Bilinear is a tensor network.

Replacing Softmax Attn w/ Bilinear Attn
---------------------------------------

For a tensor network, we are only allowed polynomial nonlinearities. For attention, this means we need to replace softmax w/ a polynomial. The simplest is an element-wise squaring of the attention pattern with itself. 

$$
Attn^2 = (xQK^\top x^\top)^2
$$

$$
= (xQK^\top x^\top) \odot (xQK^\top x^\top)
$$

Notice how this is like computing the same attention pattern twice, then element-wise multiplying.

We can instead be slightly more expressive by training two sets of Q & K:

$$
Bilinear\_Attn = (xQ_1K_1^\top x^\top) \odot (xQ_2K_2^\top x^\top)
$$

Experiment & Results
====================

I forked an older commit of [modded-nanoGPT](https://github.com/loganriggs/modded-nanogpt/tree/master) and trained four ~500M parameter LLMs (~GPT-medium size) on fineweb, switching out Bilinear & SwiGLU for the MLP-component and Softmax Attn & Bilinear attn for Attention. I trained on CE loss, comparing when they reached a loss of 3.0.

![](https://39669.cdn.cke-cs.com/rQvD3VnunXZu34m86e5f/images/a8ec58ce51be724477dbaf7efe6c86035a4c66d9d8b7db07.png)

Here, using the Bilinear or SwiGLU layer were basically the same, but switching to bilinear attn came at cost (but a very small one. Though note the 10% more parameters for Bi_attn)

I initially ran experiments in the GPT-2 small range:

![](https://39669.cdn.cke-cs.com/rQvD3VnunXZu34m86e5f/images/6b6e34f768b6684333dd5a818ab7202829f8674e7b82cc73.png)

Which were much worse for the tensor-variants. 

One might think "Oh, the tensor-variants actually scale better?", but I think it's because I forked from modded-nanogpt who's hyperparams & architecture is overfitted to the GPT-2 size model for Softmax attention. You'd need to do a larger hyperparam sweep (& various architecture changes, yikes) to get a better idea!

Caveats:
--------

### (1) Softmax attention ran faster cause it has a CUDA kernel

Softmax attention ran much faster because I was using F.scaled\_dot\_product_attention() which uses a cuda kernel under the hood. How to adjust? I don't want to write my own custom CUDA kernel, so I adjusted by saying they ran just as fast per step. This isn't quite true for the reasons below

### (2) Bilinear Attention can run much faster than Softmax Attn

Bilinear Attention is $O(seq⋅d_h^3​​)$ vs $O(\text{seq}^2 \cdot d_h)$ for normal softmax, where $d_h$ := the head\_dimension (usually d\_model/num of heads)

So Bilinear attention is more efficient when:

$$
seq > d_h^2
$$

For a seq length of 1M & $d_h$ of 128 (from deepseek-v3):

$$
1e6 > 1.6e4
$$

In other words, bilinear attention is more efficient computationally in this case when sequence length is > 1,600.

\[More details & proof in [appendix B](https://www.lesswrong.com/posts/hp9bvkiN3RzHgP9cq/tensor-transformer-variants-are-surprisingly-performant#Appendix_B__Scaling_of_Bilinear_Attention)\]

As a quick aside, we're gaining on computational efficiency, but this does come at a cost of less expressivity (see [Appendix C](https://www.lesswrong.com/posts/hp9bvkiN3RzHgP9cq/tensor-transformer-variants-are-surprisingly-performant#Appendix_C__Bilinear_Attention_Expressivity)) 

*But what about Flash Attention?* 

See [appendix D](https://www.lesswrong.com/posts/hp9bvkiN3RzHgP9cq/tensor-transformer-variants-are-surprisingly-performant#Appendix_D__But_what_about_Flash_Attention_)

### (3) Bilinear Attention has more Parameters

It's not fair that bilinear attention has twice the number of Q & K matrices, so I tried a baseline of [differential attention](https://arxiv.org/pdf/2410.05258) from the literature which claims to need ~38% fewer parameters or ~36% fewer tokens. But it performed very poorly in my case(ie 100% worse)!  It could've been a scaling issue, hyper-parameters, or coding bug (but the implementation is simple, see my code [here](https://github.com/loganriggs/modded-nanogpt/blob/master/train_gpt2.py#L264)). 

### (4) This was the 2nd-Dumbest Tensor-Attn Variant

There are likely way more efficient tensor-attn variants that exist. The 2nd dumbest is this bilinear attention, where the dumbest is just the .square() (which is like bilinear attention, but w/ tied Q's & tied K's weights).

Overall, I'm thinking this tensor attention variant is 15% worse to 10% better than softmax attention. 

Replication & Trained Models
============================

Code [here](https://github.com/loganriggs/modded-nanogpt), majority of code is just [train_gpt2.py](https://github.com/loganriggs/modded-nanogpt/blob/master/train_gpt2.py)

Trained models [here](https://huggingface.co/Elriggs/models).[^vv3e6rsf0w]

Future Work
===========

There's many more experiments one could run (eg scaling laws), but I'm currently focusing on actually doing interp w/ these models.

(Also, I've already spent ~$500 on 8 H100's for all the experiments. Speaking of which, special thanks to [Principles of Intelligence](https://princint.ai/) (formerly PIBBSS) for funding me at the time of this research!)

Path to Impact
==============

Tensor networks might actually be a viable alternative to typical NNs! However, if scaling is way worse (say 50%), then I highly doubt they'll be deployed as a frontier model. 

But suppose we can solve ambitious mech interp w/ tensor networks (debatable but I lean yes), then there are two regimes:

1\. Low Reliability  
2\. High Reliability

For writing math proofs we can verify, it's fine to have low reliability because failure is cheap. For self-driving cars though, you really want high reliability!

So we can do distillation or just train smaller tensor networks that aren't as generally capable, but are *task specific*. 

Having highly robust, task-specific AI sounds great. So great, it might actually make a lot of money for various tasks.

This could change the financial incentives away from investing in low reliability AGI and towards highly reliable task-AI. 

Interp w/ Tensor Networks
=========================

The second question I get when I bring up Tensor Networks is how they're actually more interpretable.

Most work on tensor networks isn't concerned with both ambitious interp and viability; however, [Thomas Dooms](https://tdooms.github.io/), Ward Gauderis et al have been cooking this year!

[Bilinear Autoencoders](https://arxiv.org/pdf/2510.16820) \- they find structure in models using a bilinear AEs. See example manifolds [here](https://tdooms.github.io/demos/manifolds/?negation-directions).

[Compositionality Unlocks Deep Interpretable Models](https://arxiv.org/pdf/2504.02667) \- a stack of bilinear layers is performant, while enabling us to view the global structure across the whole tensor network (since you can compose them together), though be warned, lots of math and kind of hard to understand. 

[Bilinear MLPS Enable Weight-Based Mech Interp](https://arxiv.org/pdf/2410.08417?) \- *"Bilinear MLPs can be fully expressed in terms of linear operations using a third-order tensor, allowing flexible analysis of the weights. Analyzing the spectra of bilinear MLP weights using eigendecomposition reveals interpretable low-rank structure across toy tasks, image classification, and language modeling. We use this understanding to craft adversarial examples, uncover overfitting, and identify small language model circuits directly from the weights alone."*

\[And if I understand correctly, [Transluce](https://transluce.org/neuron-circuits) linearized their LLM per datapoint (making it a tensor network, but, again, only for that datapoint) to improve attribution.\]

But I really believe this is just the tip of the iceberg. Tensor networks have a lot of useful properties that make them amenable to analytic tools. In short:

1.  They can compose to show structure across the entire network.
2.  They shift our focus from activations & data to directly interpreting the weights (which apply to the *entire* data distribution)
3.  They are scale-invariant, meaning all circuits within the model don't change if the input changes scale. In other words, if you have a direction in layer 1, it will affect the exact same components downstream in the exact same ratios, regardless of how much you scale that direction.

As well as some forthcoming work from them that I'm (very) excited to see released!

As for me, I'm currently working on circuits applied to tensor networks. Do feel free to reach out to me here or on discord ( # loganriggs) if you're interested in this research direction!

*Special thanks to Thomas Dooms for reviewing an earlier draft of this post.*

Appendix A: Noam Shazeer's 2020 paper:
--------------------------------------

In Noam Shazeer's [2020 paper](https://arxiv.org/pdf/2002.05202), he trains these different architectures on a span filling task on the C4 dataset, showing their log-perplexity loss. 

![](https://39669.cdn.cke-cs.com/rQvD3VnunXZu34m86e5f/images/a9d7a52ef764c8d78b202750c36b9063d85865d9c8563d76.png)

Where the Bilinear layer does quite well! Even beating GLU (which should be called SiGLU for sigmoid GLU). 

Appendix B: Scaling of Bilinear Attention
-----------------------------------------

Proof from Claude Opus 4.5, edited & reviewed by me, w/ code verification [here](https://gist.github.com/loganriggs/329173a4ed4978940884714726f290a2).

Our bilinear form is:

$$
\underbrace{(x Q_1 K_1^\top x^\top)}_{\text{seq} \times \text{seq}} \odot \underbrace{(x Q_2 K_2^\top x^\top)}_{\text{seq} \times \text{seq}} \cdot v
$$

where ⊙ is the elementwise product. Naively this requires materializing a seq×seq matrix, which is $O(\text{seq}^2)$.

 Defining:

$$
q_1 = xQ_1 \in \mathbb{R}^{\text{seq} \times d_h}, \quad k_1 = x K_1 \in \mathbb{R}^{\text{seq} \times d_h}
$$

$$
q_2 = x Q_2 \in \mathbb{R}^{\text{seq} \times d_h}, \quad k_2 = x K_2 \in \mathbb{R}^{\text{seq} \times d_h}
$$

where $d_h$ is the hidden dimension of the attention head. So the attention patterns are:

$$
A_1 = q_1 k_1^\top \in \mathbb{R}^{\text{seq} \times \text{seq}}, \quad A_2 = q_2 k_2^\top \in \mathbb{R}^{\text{seq} \times \text{seq}}
$$

The [Row-wise Khatri-Rao Product](https://en.wikipedia.org/wiki/Khatri%E2%80%93Rao_product), $\circledast$, is defined row-by-row as the Kronecker (outer) product of each row:

$$
(A \circledast B)_{i,:} = A_{i,:} \otimes B_{i,:}
$$

For example:

$A_{i,:} = [a_1, a_2]$ and $B_{i,:} = [b_1, b_2, b_3]$, then:

$$
(A \circledast B)_{i,:} = [a_1 b_1, a_1 b_2, a_1 b_3, a_2 b_1, a_2 b_2, a_2 b_3]
$$

A [key identity](https://en.wikipedia.org/wiki/Khatri%E2%80%93Rao_product#Main_properties)[^9a6s5x8bbk9] is 

$$
(A B^\top) \odot (C D^\top) = (A \circledast C)(B \circledast D)^\top
$$

Using this identity:

$$
A_1 \odot A_2 = (q_1 k_1^\top) \odot (q_2 k_2^\top) = (q_1 \circledast q_2)(k_1 \circledast k_2)^\top
$$

Define:

$$
\tilde{Q} = q_1 \circledast q_2 \in \mathbb{R}^{\text{seq} \times d_h^2}
$$

$$
\tilde{K} = k_1 \circledast k_2 \in \mathbb{R}^{\text{seq} \times d_h^2}
$$

So to compute $\tilde{Q}$ we're taking the row-wise outer product of the hidden dimensions of $q_1$ & $q_2$, repeating this for every sequence position (later, we'll mix across sequences when combining $\tilde{Q}$ & $\tilde{K}$).

Now the output becomes:

$$
(A_1 \odot A_2) \cdot v = \tilde{Q} \tilde{K}^\top v
$$

Everything here is just matrices/vectors being matrix-multiplied, so we have two choices on how to combine them:

**1\. Left to Right (Inefficient):** $(\tilde{Q} \tilde{K}^\top) v$

*   $\tilde{Q} \tilde{K}^\top$is $(\text{seq} \times d_h^2) \cdot (d_h^2 \times \text{seq}) = O(\text{seq}^2 \cdot d_h^2)$
*   Then multiply by v: $O(\text{seq}^2 \cdot d_h)$

So we're scaling quadratically in both seq-size & $d_h$

**2\. Right-to-Left (Efficient):** $\tilde{Q} (\tilde{K}^\top v)$

*   $\tilde{K}^\top v$ is $(d_h^2 \times \text{seq}) \cdot (\text{seq} \times d_h)$ $= O(\text{seq} \cdot d_h^3)$
*   Then $\tilde{Q} \cdot (\ldots)$ is $(\text{seq} \times d_h^2) \cdot (d_h^2 \times d_h) = O(\text{seq} \cdot d_h^3)$

So we're scaling linearly in seq-size & cubically in $d_h$

Appendix C: Bilinear Attention Expressivity
-------------------------------------------

Softmax attention is more expressive. The max rank of the attention matrix for softmax is seq (full rank) whereas for bilinear attention, we're stuck at $d_k^2$. There's no free lunch, so while we're gaining a lot in computational efficiency, we're losing in expressivity.

In the kernel view, softmax approximates a Gaussian kernel, meaning it can approximate any continuous function, while bilinear attention is just a degree-2 polynomial kernel.

Appendix D: But what about Flash Attention?
-------------------------------------------

Flash attention is about memory, not computation. It's purpose is to avoid materializing the full seq x seq matrix by splitting it up into tiles. You can't compute softmax over tiles since it's a property of entire rows, so they compute statistics on each tile which can be combined to ~compute softmax. 

Tensor-Attention variants don't use softmax, so you don't need to do any clever tricks there (although the efficient bilinear attention method probably requires larger memory? I'll leave this as an exercise to the reader).

[^vv3e6rsf0w]: The naming scheme goes as follows:eg  Elriggs/gpt2-swiglu-18l-9h-1152embdis read sensibly as gpt2 w/ swiglu, 18 layers, 9 attn heads, & 1152 embedding dim. If attention isn't mentioned, it's assumed to be softmax.The rest of my naming scheme ended up being mixed up & non-ideal. I recommend going to each one's config.json if you're looking for a particular run.squared_mlp is squaring the output of ReLU as typically done in modded-nano-gptbilinear :=bilinear layerbilinear AND gated := SwiGLU (since a SwiGLU is a bilinear layer w/ a gate)bilinear_attn := bilinear attention (but will be named "sqrd_attn" in the name. That's my mistake) 

[^9a6s5x8bbk9]: Section 3, fourth equation down, citation is [16]