<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Projects |</title><link>https://jesseli2002.github.io/blog/projects/</link><atom:link href="https://jesseli2002.github.io/blog/projects/index.xml" rel="self" type="application/rss+xml"/><description>Projects</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Tue, 04 Aug 2026 00:00:00 +0000</lastBuildDate><image><url>https://jesseli2002.github.io/blog/media/icon_hu_1c0e9cb08cfb822a.png</url><title>Projects</title><link>https://jesseli2002.github.io/blog/projects/</link></image><item><title>Toy Model of Activation Obfuscation</title><link>https://jesseli2002.github.io/blog/projects/toy-model-of-activation-obfuscation/</link><pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate><guid>https://jesseli2002.github.io/blog/projects/toy-model-of-activation-obfuscation/</guid><description>&lt;p&gt;&lt;em&gt;I completed this work as part of the
.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Training against probes is considered a
, because the model might learn to obfuscate its activations instead of behaving better. Can we create a toy example of this? More specifically &amp;ndash; under optimization pressure, will a toy model learn to encode a feature to be challenging to detect with linear probes?&lt;/p&gt;
&lt;p&gt;In this research, I give theoretical and empirical evidence that models can and will defeat adversarially-trained linear probes, in at least some configurations, when trained against those probes.
I investigated this problem with a simple residual-stream MLP architecture:
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;&lt;img alt=""
src="https://jesseli2002.github.io/blog/projects/toy-model-of-activation-obfuscation/model_architecture.svg"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The model tries to learn $y = \text{sat}(x, -c, c)$ (the saturation function, equivalently $y = \max(-c, \min(c, x))$).
$x$ is a vector of dense features sampled uniformly from $[-3, 3]$, and $c$ is a scalar sampled uniformly in $[1, 2]$.
This is a fairly easy function to learn - it takes just $2 \cdot \text{len}(x)$ neurons to learn exactly if you use ReLUs. &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;
The harder question is, can a model do this without storing $c$ in the residual stream, in a way that can be linearly probed?
Here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$c$ represents some safety-relevant behaviour we want to probe for - say, deception.&lt;/li&gt;
&lt;li&gt;$x$ represent various other features that a model might want to represent, uncorrelated with $c$.&lt;/li&gt;
&lt;li&gt;$y$ represents some sort of computation the model would like to make, dependent on $c$.
&lt;ul&gt;
&lt;li&gt;For example, based on how deceptive the model intends to be (represented at some earlier layer), the model might upweight misleading information at some later layer.&lt;/li&gt;
&lt;li&gt;Such an action will ultimately come down to various computational primitives.&lt;/li&gt;
&lt;li&gt;$\text{sat}$ is hopefully representative of such a primitive &amp;ndash; for example, it can be considered a rough approximation to the more common sigmoid function. (Note: $\text{sat}$ was primarily chosen for its theoretical properties, as discussed below, not because it&amp;rsquo;s necessarily a natural computaitional primitive.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-this-task-and-why-this-model"&gt;Why this task, and why this model?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The toy architecture is at least vaguely similar to the transformer architecture.
It&amp;rsquo;s missing attention (and the entire notion of a token sequence) and LayerNorm.
However, it doesn&amp;rsquo;t really add anything new to a transformer, so any obfuscation that this model learns could plausibly also be learned by a transformer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The learned function is not linearly separable in $c$.
Thus, once the model has finished computing $y_i$ for one feature $x_i$, it can just leave $y_i$ in the residual stream.
This is vaguely analogous to how probe accuracy gets worse on the last layers of a transformer model, as the model starts shifting its representation from more abstract concepts to the exact text it wants to output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because it takes at least $2 \cdot \text{len}(x)$ neurons to implement $y$, as long as the width of the MLP blocks is less than $2 \cdot \text{len}(x)$, we can be sure the model has represented $c$ somehow in the first layer (or the model hasn&amp;rsquo;t actually learned the task fully).
More generally, the model needs to encounter at least $2 \cdot \text{len}(x)$ neurons before it can &amp;ldquo;forget&amp;rdquo; about $c$ and fully erase its representation.
We can use this property in experiments to guarantee that the model is representing $c$ at a particular early layer.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: for training and theoretical simplicity, this work fixes the embedding and unembedding matrices to be rectangular identity matrices (i.e. ones on the main diagonal, zero elsewhere)&lt;/p&gt;
&lt;h2 id="related-work"&gt;Related work&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
find that you can fine-tune models to evade latent space linear probes, including for previously unseen probes
&lt;ul&gt;
&lt;li&gt;However, as far as I can tell, they use fixed probes, which don&amp;rsquo;t rule out the model simply learning to apply a fixed translation or rotation to its representation.
This is obfuscation in some sense, but not particularly complex obfuscation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
train adaptive probes on harmfulness, where the probe is updated regularly along with model training, and find that this successfully improves harmlessness.
&lt;ul&gt;
&lt;li&gt;They do this on a real model.
Plausibly, the task that their model learns can be accomplished with equally high scores without needing to be harmful.
In contrast, since I use a toy model, it&amp;rsquo;s easy to construct an environment where the model has to hide its activations to succeed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;I present my results in parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;An analytic construction using a small number of neurons, that fully defeat Difference-of-Means probes (by having the mean activations be a constant in $c$) and effectively defeat logistic regression probes, as long as the probes are spaced at least every 2 MLP blocks apart.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;The setup and results for a single training run, giving evidence that the trained model has successfully learned to reversibly obfuscate its activations from a linear probe at a single layer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: Sweeping hyperparameters (WIP)
&lt;ul&gt;
&lt;li&gt;The results of sweeping some hyperparameters, to see how robust the model&amp;rsquo;s training is to different conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="future-work"&gt;Future work&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a lot of potential extensions to this work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Most obviously &amp;ndash; can this behaviour be reproduced at LLM scale?&lt;/li&gt;
&lt;li&gt;How hard is it for a model to hide from a nonlinear probe? Or from probes at multiple or all layers? Can we study how likely &amp;ldquo;obfuscation at layer N&amp;rdquo; is to generalize to obfuscation at other layers or more complex obfuscation?&lt;/li&gt;
&lt;li&gt;Are there different obfuscation strategies that occur when attention is introduced?&lt;/li&gt;
&lt;li&gt;The model was forced to obfuscate its activations by the experiment setup. What happens if we give it an &amp;ldquo;out&amp;rdquo; - i.e. it can solve the task in some harder way?
&lt;ul&gt;
&lt;li&gt;This might better mirror how real-world environments are (ideally) set-up: ensuring that there is a genuine acceptable solution, rather than the reward-hacking solution being the only success path.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Specifically, $y = x - \text{ReLU}(x - c) + \text{ReLU}(-x - c)$&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Activation Steering with Greedy Coordinate Gradients</title><link>https://jesseli2002.github.io/blog/projects/gcg-activation-steering/</link><pubDate>Tue, 28 Jul 2026 00:00:00 +0000</pubDate><guid>https://jesseli2002.github.io/blog/projects/gcg-activation-steering/</guid><description>&lt;p&gt;I stumbled upon
, a project by
. This competition challenges people to find an LLM prompt prefix that increases the score of the rest of the prompt on a linear probe.&lt;/p&gt;
&lt;p&gt;For those unaware: linear probes are an important basic tool in the mechanistic interpretability toolkit. Other sources explain them in more depth &amp;ndash; for example,
&amp;ndash; but briefly, LLMs seem to store concepts as directions in a high-dimensional space. By contrasting prompts with and without a given concept (for example, whether a sentence is talking about cats, or whether a fact is true) and looking at the model&amp;rsquo;s activations, we can infer what direction corresponds to that concept. We can then use that to monitor a model, to see if it&amp;rsquo;s thinking about something &amp;ndash; particularly useful if you&amp;rsquo;re trying to figure out, for example, if your model is about to give advice on how to build a bomb.&lt;/p&gt;
&lt;p&gt;The idea of Steering Arena is, can we find some prefix string, to be prepended to a range of neutral prompts, that will consistently steer the model&amp;rsquo;s activations in the probed direction? Aside from just being an interesting question, this also has applications in model security &amp;ndash; it&amp;rsquo;s important to understand how robust monitoring techniques are to adversarial attacks. (As we&amp;rsquo;ll see, linear probes probably aren&amp;rsquo;t very robust.)&lt;/p&gt;
&lt;h2 id="greedy-coordinate-gradients"&gt;Greedy Coordinate Gradients&lt;/h2&gt;
&lt;p&gt;I chose to tackle this problem with Greedy Coordinate Gradients, described in
. That paper optimizes a prompt in an attempt to find a universal jailbreak for LLMs, with a substantial amount of effort and insight dedicated to what the objective function even should be. Luckily for me, this project doesn&amp;rsquo;t face that issue; my objective is simply the cosine similarity between the activations at a particular layer and a specified probe direction. (It is worth noting that this score is simply what Steering Arena implements; usually linear probe monitors evaluate activations by projecting onto the probe&amp;rsquo;s direction and evaluating with respect to some threshold.)&lt;/p&gt;
&lt;p&gt;Slightly more formally, the problem is as follows: We control a prefix string (&lt;code&gt;prefix&lt;/code&gt;), which gets prepended to one of several uncontrolled but known prompts (&lt;code&gt;suffix&lt;/code&gt;).
The most immediate problem is that optimizing over tokens is a discrete problem, whereas optimizers tend to prefer continuous spaces. However, we basically immediately embed the tokens into the continuous space $\mathbb{R}^v$ (using one-hot embedding, where $v$ is the vocabulary size), so the simple workaround is to get gradients in the one-hot embedding space, and &amp;ldquo;snap to&amp;rdquo; the valid points.
At a high level, each iteration of the optimization algorithm works like this (Algorithm 1 in the paper):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run a forward pass on your data, and evaluate the gradient of the score with respect to the one-hot embeddings.&lt;/li&gt;
&lt;li&gt;For each token position, find the tokens (which correspond to the vocab dimension) with the highest gradients. Pick the top $k$ such tokens.&lt;/li&gt;
&lt;li&gt;On each iteration, generate $B$ candidates (the &amp;ldquo;batch size&amp;rdquo;).
&lt;ul&gt;
&lt;li&gt;Each candidate modifies a single token from the current &lt;code&gt;prefix&lt;/code&gt;. Which token is modified is randomly selected (uniformly over token positions), and what it&amp;rsquo;s modified to is also randomly selected (uniformly over the top $k$ tokens we found in the previous step).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Across your $B$ candidates, pick the best performing one as your new &lt;code&gt;prefix&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="implementation-details-and-tricks"&gt;Implementation details and tricks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The actual evaluation score on Steering Arena includes a constant bias term, to compensate for the baseline probe score from the test prompts themselves &amp;ndash; but since those are constant, I don&amp;rsquo;t include them when optimizing.&lt;/li&gt;
&lt;li&gt;The initial prompt doesn&amp;rsquo;t really matter too much; I just repeat token ID 0 (which turns out to be &amp;ldquo;!&amp;rdquo;) for however many tokens are controlled.&lt;/li&gt;
&lt;li&gt;To speed up convergence, I start with a smaller batch size $B$ and top-$k$ value.
&lt;ul&gt;
&lt;li&gt;I find that a good training schedule is to increase $B$ by a factor of 4 and $k$ by a factor of 2, every $4N$ iterations (where $N$ is the number of controlled tokens) &amp;ndash; although I didn&amp;rsquo;t rigorously validate this.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I hypothesize (mostly with intuition rather than actual justification) that after a certain point, the gradient doesn&amp;rsquo;t really give you much signal anymore, since the loss landscape changes too quickly. At that point, using something like
might be more effective for further hill climbing. To that end, I did put in an SA-like check on when to accept a new candidate. If the new candidate scores better, it&amp;rsquo;s always accepted; otherwise, the acceptance probability is less than 1, and decreases for worse candidate scores.
&lt;ul&gt;
&lt;li&gt;I didn&amp;rsquo;t actually check to see if this is an improvement, although I figure it probably is.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optimizers tend to be really good at exploiting differences between your training metric and evaluation metric, and this project is no different.
&lt;ul&gt;
&lt;li&gt;I find that at later stages of optimization, the score reported by the local implementation (&lt;code&gt;transformers&lt;/code&gt; library) can differ quite substantially from that reported by the Steering Arena website (which uses NDIF servers), possibly due to e.g. differences in order of specific floating-point operations.
However, I didn&amp;rsquo;t have time to look into the cause further.
(This discrepancy is different from the constant bias term, since it differs on a per-submission basis.)&lt;/li&gt;
&lt;li&gt;For reproducers: As of writing, I used &lt;code&gt;transformers&lt;/code&gt; version 5.10.2; this is different from the latest version and turns out to have substantial impact on scored results. However, this is not sufficient to fully reproduce the NDIF values.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;I didn&amp;rsquo;t want to spend too much on compute, so I didn&amp;rsquo;t make that many runs. I only have three runs in what I&amp;rsquo;d call a controlled experiment: runs for 8, 16, and 32 controlled tokens. For each run, I ran at least $4N$ iterations, following the training schedule mentioned earlier for batch size and top-$k$ tokens.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard to get confident results with limited data, but there&amp;rsquo;s still some hints of interesting conclusions. Note that because I don&amp;rsquo;t have access to NDIF servers myself, reported scores here come from the &lt;code&gt;transformers&lt;/code&gt; library implementation, and (as noted above) differ from the scores seen on Steering Arena.&lt;/p&gt;
&lt;p&gt;First, unsurprisingly, more controlled tokens lead to higher scores:
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Plot showing max achieved score in each run. 8 tokens achieved 0.07695, 16 tokens achieved 0.1163, and 32 tokens achieved 0.1361"
srcset="https://jesseli2002.github.io/blog/projects/gcg-activation-steering/max_score_vs_tokens_hu_3e028b2e8ef2f914.webp 320w, https://jesseli2002.github.io/blog/projects/gcg-activation-steering/max_score_vs_tokens_hu_65166df20de6f9ae.webp 480w, https://jesseli2002.github.io/blog/projects/gcg-activation-steering/max_score_vs_tokens_hu_2fa34e828e61928a.webp 640w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://jesseli2002.github.io/blog/projects/gcg-activation-steering/max_score_vs_tokens_hu_3e028b2e8ef2f914.webp"
width="640"
height="480"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a plot of training score as a function of number of iterations:
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt=""
srcset="https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_iteration_hu_2847ab5bbc58f3ea.webp 320w, https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_iteration_hu_452a68bf6a9bd2bf.webp 480w, https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_iteration_hu_57bbf072e34bd65c.webp 640w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_iteration_hu_2847ab5bbc58f3ea.webp"
width="640"
height="480"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;However, what&amp;rsquo;s more striking is if you plot score against cumulative number of samples (increases by the batch size on each iteration), on a semilog plot:
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt=""
srcset="https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_samples_hu_ad4ce8ac32d271ff.webp 320w, https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_samples_hu_c8dde7639ee023d.webp 480w, https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_samples_hu_41be11592c3f58e5.webp 640w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://jesseli2002.github.io/blog/projects/gcg-activation-steering/score_vs_samples_hu_ad4ce8ac32d271ff.webp"
width="640"
height="480"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Training score seems to basically follow a straight line, although the 8 tokens run saturates and slows down after a bit. Of course, all runs eventually have to saturate, but it&amp;rsquo;s plausible you could throw more compute at the 16 and 32 token runs to get even higher scores. I fit a line to the 16 and 32 token runs and get a slope of 0.115 score/ln(samples). I don&amp;rsquo;t know how readily this generalizes (to steering in other directions, different test prompts, etc.), but it seems like a reasonable baseline to estimate compute requirements from.&lt;/p&gt;
&lt;h2 id="future-work"&gt;Future work&lt;/h2&gt;
&lt;h3 id="faster-optimizers"&gt;Faster optimizers&lt;/h3&gt;
&lt;p&gt;There are definitely more performance gains to be made and directions to explore, in terms of convergence speed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First of all, it&amp;rsquo;d be ideal if the training schedule I tried was actually validated more carefully.&lt;/li&gt;
&lt;li&gt;You could probably try changing more than one token at a time, or sampling the top-$k$ choices non-uniformly so that candidates with higher gradients are more likely.
&lt;ul&gt;
&lt;li&gt;These might work better at earlier stages where the gradient provides a more accurate signal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Overall optimization time is dominated by later stages of optimization, so it might be worth it to run multiple initial starts to see which one gives a better starting point, for later stages.&lt;/li&gt;
&lt;li&gt;There are algorithmic improvements to GCG in the literature, for example
which uses a smaller &amp;ldquo;draft&amp;rdquo; model to reduce the cost of sampling.&lt;/li&gt;
&lt;li&gt;More generally, the optimization literature has been around for a long time &amp;ndash; I&amp;rsquo;m sure the simulated annealing literature has more tricks that might apply here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, there&amp;rsquo;s a bit more work to improve reproducibility between the training and NDIF implementations &amp;ndash; or, barring that, some workaround to improve robustness to implementation differences (such as injecting small amounts of simulated numerical noise).&lt;/p&gt;
&lt;h3 id="robustness"&gt;Robustness&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Steering Arena only tests the results on 16 prompts. How overfitted is the prompt prefix to those exact prompts? How high of a score can you get when you need to work with a larger set of prompts?
&lt;ul&gt;
&lt;li&gt;Conversely, how high of a score can you get if you&amp;rsquo;re only working with one prompt?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Steering Arena scores results by cosine similarity, rather than absolute magnitude along the probe direction. So, what is the adversarial prompt doing to the magnitude of the activations? Is it driving the overall magnitude down, so that the direction corresponding to the probe contributes relatively more?&lt;/li&gt;
&lt;li&gt;The original GCG paper found that, surprisingly, their prompts transferred to other models that they didn&amp;rsquo;t train against. Does that happen here too &amp;ndash; if you retrain the probe direction with a new model, and use the GCG-optimized prompt from the existing model, does the prompt still score highly on the probe?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;
.&lt;/p&gt;</description></item></channel></rss>