Ping pong computation in superposition
Overview:
This post builds on Circuits in Superposition 2, using the same terminology.
I focus on the case, meaning that exactly one circuit is active on each forward pass. This restriction simplifies the setting substantially and allows a construction with zero error, with , layers, and width . While the construction does not directly generalise to larger , the strategy for mitigating error should still be relevant. I think the ideas behind the construction could be useful for larger , and the construction itself is quite neat.
Ping-Pong Trick for :
This is basically the same trick as ping-pong buffers.
We divide the layer width into contiguous memory blocks of size .
We start with an input consisting of a vector , together with one-hot encodings of a pair of memory blocks ().
We initially load the input into memory block .
Using a linear map applied to the one-hot encoding of memory block , we add a massive negative bias to all blocks in the next layer except memory block . This ensures that only memory block can have neurons with non-zero activations in the following layer.
Additionally, using a linear map applied to the one-hot encoding of , we can freely set a size bias for the next layer’s block as a function of (see the green arrows in the figure above).
We are also free to pick the linear map between block in the first layer and block in the second layer.
The end result is that when we run the network, all the blocks apart from block in the second layer have activation (because of the negative bias added by the one-hot encoding of ), while Block has neuron activations corresponding to ().
Now we perform the same trick in reverse, this time bouncing block back to block . So that the third layer will have in the th block.
We continue this process:
Layer 1:
Layer 2:
Layer 3:
Layer : () or () depending on parity
Each transition between blocks we can freely specify a layer of a width neural network.
We transition times, and require layers to load and unload the inputs from the appropriate memory blocks, so we consume layers total.
Binary encoding trick:
We can optimize the above construction further by alternately swapping out the one-hot encoding of one of the blocks with a compressed length binary encoding.
On odd layers, we use a one-hot encoding for block , and a binary encoding for block .
On even layers, block uses a binary encoding, and block uses a one-hot encoding.
We can implement this swapping using an appropriately set bias and linear map between layers.
The idea is that on layers where a block simply suppresses all but one block, we only need the binary encoding. A rank argument tells us that we can’t naively compress the encoding of the block which provides the bias, however.
Parameter counting argument:
The above construction uses width and layers, to encode T = circuits of width and depth .
Note that according to a naive parameter counting argument, assuming injectivity of the map from parameters of a neural network to behaviours, we should at most be able to fit in circuits into such a network.
So for large the “theoretical” maximum number of circuits we can fit into the network tends to .
So is not bad, especially for large-ish where we can pay off the cost of the one-hot encoding.
Optimizing the number of layers?
layers seems hard to optimise down because the length input to the first layer of the model interacts with the same parameters on each forward pass, because the input is placed in the same initial position each time. The same is true for the final layer of the network where the output needs to be placed in a fixed final position. So the majority of the parameters on the first and final layers are wasted.
I would be very interested in even impractical ways to get around this barrier. Tentatively I think this layer constraint is not just a quirk of the particular setup.
Testing:
I have tested a similar construction up to , using this Google Colab script.
This was before I came up with the binary encoding trick, so it encodes randomly initialized circuits of width , given a width network. With the binary encoding trick, it should be possible to get this down to a width of ().
I have only tested one-layer networks, but since the output format matches the input format, this is sufficient to validate the construction.
Why focus on zero-error solutions?:
I’m pretty skeptical of computation in superposition solutions that involve any noise in intermediate layers whatsoever. Because the Lipschitz constants of neural networks are terrible, and so the constant factors involved in the asymptotics suffer as well. I want to have concrete numbers, not just asymptotics, and they seem hard to obtain when allowing for noise in intermediate layers.[1]
My way of avoiding having to think about Lipschitz/High-dimensional probability stuff is to work on finding solutions which work perfectly with probability , and otherwise fail catastrophically.
- ^
[Although it’d be nice if there’s a way to make use of Lipschitz-constrained neural networks here.]
I think this post is criminally underrated. Over half a year later, I still regularly return to this construction for inspiration when thinking about computation in superposition.
Thank you :)
I would be interested in hearing more about what you get out of the construction, because I am kinda obsessed with finding these sorts of toy networks at the moment, but am kind of unsure how to score the constructions.
It seems like there’s a lot of fruit in picking a mathematically well defined task and optimising constructions, and this is my current focus (see Bloom Filter post as another example). Coming up with good tasks to optimise is hard though.
I agree this isn’t easy. In this specific case, the construction is useful to me because it is fairly general, applying to all circuits with , has no noise, making it easier to reason about in many ways, and runs on a pretty different principle than all computation in superposition constructions I’d thought about before seeing this.
One construction that occurred to me that’s making me doubt the problem setup a little at least for :
You can abuse the linear circuit embedding associated with circuit to communicate arbitrary binary codes to the bottom stream provided that you assume the sum of the input coordinates is bounded away from (Analogous to the bloom filter scenario). So transmit a binary code encoding the weights of the circuit and you win by just writing a generic circuit emulator and lose the dependence on (Potentially need bits if you wanna encode output in superposition).
I think this is obviously pretty silly because in a real network you’d have to store the bits associated with the circuits somewhere and it’s just a quirk of the precise setup.
Wonder what you think of instead assuming smth like the residual stream takes the form of concatenated blocks of the form (input | dense binary code of length log(T) encoding the circuit). So making the formatting rigid to avoid encoding too much info in the weight matrices themselves.
In general transmitting binary codes to the bottom is fairly useful though. In the setup of Compressed computation (https://www.lesswrong.com/posts/ZxFchCFJFcgysYsT9/compressed-computation-is-probably-not-computation-in), feature can transmit an overcomplete basis vector associated with feature , which can then be used to encode the output of feature . Can also transmit the set of active features via a Bloom Filter.
Good point. I think this is a pretty good argument against using this setup. You can kind of guard against this failure mode by only looking at circuits that are very deep relative to the width of the network, but it’s a hacky fix.