<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lockwo.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lockwo.github.io/" rel="alternate" type="text/html" /><updated>2026-06-15T06:39:11+00:00</updated><id>https://lockwo.github.io/feed.xml</id><title type="html">Owen Lockwood</title><subtitle>Computing.</subtitle><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><entry><title type="html">EB JEPAs in jepax</title><link href="https://lockwo.github.io/posts/2026/02/eb-jepas" rel="alternate" type="text/html" title="EB JEPAs in jepax" /><published>2026-02-25T00:00:00+00:00</published><updated>2026-02-25T00:00:00+00:00</updated><id>https://lockwo.github.io/posts/2026/02/eb-jepas</id><content type="html" xml:base="https://lockwo.github.io/posts/2026/02/eb-jepas"><![CDATA[<p>Recently a paper proposing Energy-Based Joint-Embedding Predictive Architectures <a href="https://arxiv.org/abs/2602.03604">EB-JEPA</a> came out and had some interesting perspectives JEPAs that I will discuss and I wanted to include in <a href="https://github.com/sugolov/jepax"><code class="language-plaintext highlighter-rouge">jepax</code></a>. We have previously covered the first JAX implementation of a JEPA, I-JEPA <a href="/posts/2026/02/jepax/">in a previous post</a>.</p>

<p>First, the question arises, what makes these JEPAs “energy based”? They say “An EBM defines a scalar energy function E(x,y) measuring compatibility between inputs x and outputs y, where low energy indicates high compatibility. Learning consists of shaping the energy landscape so that correct input-output pairs have lower energy than incorrect ones.” and thus you can achieve training by minimizing E, subject to some regularization to prevent universally minimizing E. This seems like a more general concept than energy based models, in which your scalar energy function actually represents the un-normalized distribution (perhaps why they call them energy based JEPAs and not energy based models). It isn’t clear whether this energy objective carries any practical impact that would normally be associated with energy based models (since EBMs have certain properties, e.g. sampling, composability, etc.). Perhaps on a smaller problem it could be actually measurable, but does the energy learned by these JEPA regularized approaches actually look like the energy landscape? Does it match the true energy better or worse than other, more explicit, EBM methods (e.g. contrastive divergence)? Can we sample from these models? These are just some of the interesting questions that the energy based framing of SSL brings up, although I don’t seem them frequently addressed.</p>

<p>But perhaps the more interesting question is, what makes these EB-JEPAs JEPAs? If you look at Figure 2 of the <a href="https://arxiv.org/abs/2301.08243">I-JEPA paper</a>, you will see that a JEPA is distinguished from a Joint-Embedding Architecture (JEA) through its predictive component (in the embedding space), and in the background, Section 2, the authors remark that JEAs can be cast in this energy minimization framework. Many more common forms of JEA exist, e.g. <a href="https://arxiv.org/abs/2002.05709">SimCLR</a>, but come with tradeoffs as the paper outlines. In the EB-JEPA, they present an image JEPA that looks quite different (<a href="https://github.com/facebookresearch/eb_jepa/blob/main/examples/image_jepa/README.md">their image example</a> looks like Fig 1 of SimCLR, along with the usage of image augmentations, unlike I-JEPA). It seems at first glance to be more of a JEA than a JEPA. However, these are not solid lines, and equations (1) and (5) reveal more insight into these differences. Although the image case has no $g_\phi$ (predictor), it would seem like a JEA, but really a JEA is a subclass of JEPAs (in which predictors are identity functions).</p>

<p>In <code class="language-plaintext highlighter-rouge">jepax</code> we are able to recreate similar performance to the published results, see the figure below.</p>

<p><img src="/images/eb_jepa_cifar10.png" alt="Plot showing Top 1% Accuracy on CIFAR-10 for EB-JEPA variants comparing ViT-S and ResNet18 with BCS and VICReg losses" width="400" /></p>

<p>Interestingly, ViT based models seem to substantially underperform ResNet/CNN architectures. We investigated a variety of architecture choices (e.g. Batch Norm, Group Norm, depth, size, loss weightings, etc.) and found this performance to be generally representative. However, it is often said that ViT (and transformer based models more broadly) require more data to achieve high quality performance, so this may simply be an example of that phenomenon. Additionally, we saw similar results with I-JEPA, with the ViT performing much better on ImageNet1k than on CIFAR-100.</p>

<p>With the above in mind, we are hopeful that the addition of EB-JEPAs in <code class="language-plaintext highlighter-rouge">jepax</code> will allow for faster and more fruitful explorations of JEPAs and further support the growth of JAX based JEPA researchers.</p>

<h2 id="changelog">Changelog</h2>

<ol>
  <li>February 25, 2026: Published initial version.</li>
</ol>]]></content><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><category term="machine learning" /><category term="JEPA" /><category term="JAX" /><summary type="html"><![CDATA[Recently a paper proposing Energy-Based Joint-Embedding Predictive Architectures EB-JEPA came out and had some interesting perspectives JEPAs that I will discuss and I wanted to include in jepax. We have previously covered the first JAX implementation of a JEPA, I-JEPA in a previous post.]]></summary></entry><entry><title type="html">jepax v0: an implementation of IJEPA training in JAX/Equinox</title><link href="https://lockwo.github.io/posts/2026/02/jepax/" rel="alternate" type="text/html" title="jepax v0: an implementation of IJEPA training in JAX/Equinox" /><published>2026-02-11T00:00:00+00:00</published><updated>2026-02-11T00:00:00+00:00</updated><id>https://lockwo.github.io/posts/2026/02/jepax</id><content type="html" xml:base="https://lockwo.github.io/posts/2026/02/jepax/"><![CDATA[]]></content><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><category term="machine learning" /><category term="JAX" /><category term="JEPA" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Improved batch norm with `hijax`?</title><link href="https://lockwo.github.io/posts/2026/02/hijax-batch-norm/" rel="alternate" type="text/html" title="Improved batch norm with `hijax`?" /><published>2026-02-02T00:00:00+00:00</published><updated>2026-02-02T00:00:00+00:00</updated><id>https://lockwo.github.io/posts/2026/02/improved-batch-norm-with-hijax</id><content type="html" xml:base="https://lockwo.github.io/posts/2026/02/hijax-batch-norm/"><![CDATA[<p>Stateful operations in JAX, such as batchnorm, can sometimes be annoying to work with. As a DSL that relies on pure functions, it requires the usual functional approach of passing around a different state object to all the functions, changing $f :: x \to y$ to $f :: (x, state) \to (y, state)$. While this may not sound like much work, it can be quite tedious to manage with large and complex machine learning network workflows. Part of this complexity comes from the fact that we often want to use <code class="language-plaintext highlighter-rouge">vmap</code> to batch, rather than having a batch dimension for everything. Although different libraries in JAX implement stateful operations in different ways, they have a common mechanism for batchnorm, all of them keep track of a vmap named axis, then do collective operation like <code class="language-plaintext highlighter-rouge">pmean</code> to get the stats across the vmapped batch. Here, we won’t be looking to simplify that dimension of batchnorm, but the state management.</p>

<p>State management is not always pretty, and leads to unergonomic interfaces such as flax:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logits</span><span class="p">,</span> <span class="n">updates</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">apply_fn</span><span class="p">(</span>
      <span class="p">{</span><span class="s">'params'</span><span class="p">:</span> <span class="n">params</span><span class="p">,</span> <span class="s">'batch_stats'</span><span class="p">:</span> <span class="n">state</span><span class="p">.</span><span class="n">batch_stats</span><span class="p">},</span>
      <span class="n">x</span><span class="o">=</span><span class="n">batch</span><span class="p">[</span><span class="s">'image'</span><span class="p">],</span> <span class="n">train</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">mutable</span><span class="o">=</span><span class="p">[</span><span class="s">'batch_stats'</span><span class="p">])</span>
</code></pre></div></div>

<p>or somewhat complicated <a href="https://github.com/patrick-kidger/equinox/blob/main/equinox/nn/_stateful.py">implementations</a> that require careful operations for edge cases.</p>

<p>But what if there was a way to simplify this? Thanks to the experimental hijax features, I think there will be. A further explanation of what hijax is will be in a future blog, but for now check out the <a href="https://www.youtube.com/watch?v=lVNgUUC34tM">JAX devlabs keynote</a> for more. Basically, hijax is a new interface to working with intermediate levels of operations, between pytrees and primitives. It’s experimental and changing, so the interface is subject to change (this code is with v0.9.0 JAX), but the idea has a lot of potential to simplify these workloads.</p>

<p>Similar to how array refs allow for stateful operations (<a href="/posts/2025/12/jax-random">see my previous blog</a>), hijax has features that enable functionality in that direction. Specifically, the idea of Quasi Dynamic Data (QDD) which allows for stateful operations from the user perspective. Under the hood, when a trace requires lowering, hijax primitives call their <a href="https://github.com/jax-ml/jax/blob/5f5be6f18373fb1dc4700ec70c1791cd098fcf6d/jax/_src/core.py#L657-L659"><code class="language-plaintext highlighter-rouge">to_lojax</code></a> methods. For <code class="language-plaintext highlighter-rouge">Box</code>, <a href="https://github.com/jax-ml/jax/blob/5f5be6f18373fb1dc4700ec70c1791cd098fcf6d/jax/_src/hijax.py#L336-L337"><code class="language-plaintext highlighter-rouge">box_get_p.to_lojax</code></a> simply returns the stored value’s leaves, while <a href="https://github.com/jax-ml/jax/blob/5f5be6f18373fb1dc4700ec70c1791cd098fcf6d/jax/_src/hijax.py#L311-L313"><code class="language-plaintext highlighter-rouge">box_set_p.to_lojax</code></a> updates the internal <code class="language-plaintext highlighter-rouge">_val</code> attribute. This means the mutable semantics the user programs get compiled down to direct value access. This allows a nice simplification of batch norm implementations of state. For example,</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">jax</span>
<span class="kn">import</span> <span class="nn">jax.numpy</span> <span class="k">as</span> <span class="n">jnp</span>
<span class="kn">from</span> <span class="nn">jax.tree_util</span> <span class="kn">import</span> <span class="n">register_pytree_node_class</span>
<span class="kn">from</span> <span class="nn">jax._src.hijax</span> <span class="kn">import</span> <span class="n">Box</span>

<span class="o">@</span><span class="n">register_pytree_node_class</span>
<span class="k">class</span> <span class="nc">BatchNorm</span><span class="p">:</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">num_features</span><span class="p">,</span> <span class="n">momentum</span><span class="o">=</span><span class="mf">0.1</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="mf">1e-5</span><span class="p">,</span> <span class="n">axis_name</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">running_mean</span> <span class="o">=</span> <span class="n">Box</span><span class="p">(</span><span class="n">jnp</span><span class="p">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">num_features</span><span class="p">))</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">running_var</span> <span class="o">=</span> <span class="n">Box</span><span class="p">(</span><span class="n">jnp</span><span class="p">.</span><span class="n">ones</span><span class="p">(</span><span class="n">num_features</span><span class="p">))</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">gamma</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">ones</span><span class="p">(</span><span class="n">num_features</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">beta</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">num_features</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">momentum</span> <span class="o">=</span> <span class="n">momentum</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">eps</span> <span class="o">=</span> <span class="n">eps</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">num_features</span> <span class="o">=</span> <span class="n">num_features</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">axis_name</span> <span class="o">=</span> <span class="n">axis_name</span>

    <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">training</span><span class="o">=</span><span class="bp">True</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">training</span><span class="p">:</span>
            <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">axis_name</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
                <span class="n">mean</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">pmean</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">axis_name</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">axis_name</span><span class="p">)</span>
                <span class="n">mean_of_squares</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">pmean</span><span class="p">(</span><span class="n">x</span> <span class="o">**</span> <span class="mi">2</span><span class="p">,</span> <span class="n">axis_name</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">axis_name</span><span class="p">)</span>
                <span class="n">var</span> <span class="o">=</span> <span class="n">mean_of_squares</span> <span class="o">-</span> <span class="n">mean</span> <span class="o">**</span> <span class="mi">2</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">mean</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">mean</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
                <span class="n">var</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">var</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>

            <span class="n">rm</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">running_mean</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>
            <span class="n">rv</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">running_var</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>
            <span class="n">new_rm</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">stop_gradient</span><span class="p">(</span><span class="n">rm</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="bp">self</span><span class="p">.</span><span class="n">momentum</span><span class="p">)</span> <span class="o">+</span> <span class="n">mean</span> <span class="o">*</span> <span class="bp">self</span><span class="p">.</span><span class="n">momentum</span><span class="p">)</span>
            <span class="n">new_rv</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">stop_gradient</span><span class="p">(</span><span class="n">rv</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="bp">self</span><span class="p">.</span><span class="n">momentum</span><span class="p">)</span> <span class="o">+</span> <span class="n">var</span> <span class="o">*</span> <span class="bp">self</span><span class="p">.</span><span class="n">momentum</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">running_mean</span><span class="p">.</span><span class="nb">set</span><span class="p">(</span><span class="n">new_rm</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">running_var</span><span class="p">.</span><span class="nb">set</span><span class="p">(</span><span class="n">new_rv</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">mean</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">running_mean</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>
            <span class="n">var</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">running_var</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>

        <span class="n">x_norm</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">mean</span><span class="p">)</span> <span class="o">/</span> <span class="n">jnp</span><span class="p">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">var</span> <span class="o">+</span> <span class="bp">self</span><span class="p">.</span><span class="n">eps</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">gamma</span> <span class="o">*</span> <span class="n">x_norm</span> <span class="o">+</span> <span class="bp">self</span><span class="p">.</span><span class="n">beta</span>
</code></pre></div></div>

<p>With a little sprinkling of vmap rules<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, like we did with refs, we can even get this to replace the somewhat complicated <a href="https://docs.kidger.site/equinox/api/nn/stateful/#vmapd-stateful-layers">substate/vmap setup</a> of equinox. Basically, we just have to tell JAX that we have a vmap rule for this Box, in which we just want to broadcast/propagate the vmaping to the elements of the Box. Certainly there would be other ways to approach this, but for the simple <code class="language-plaintext highlighter-rouge">Counter</code> example this is good enough.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">jax._src.interpreters</span> <span class="kn">import</span> <span class="n">batching</span>
<span class="kn">from</span> <span class="nn">jax._src.interpreters.batching</span> <span class="kn">import</span> <span class="n">not_mapped</span>
<span class="kn">from</span> <span class="nn">jax._src.hijax</span> <span class="kn">import</span> <span class="p">(</span>
    <span class="n">new_box_p</span><span class="p">,</span>
    <span class="n">box_get_p</span><span class="p">,</span>
    <span class="n">box_set_p</span><span class="p">,</span>
<span class="p">)</span>

<span class="k">def</span> <span class="nf">_new_box_batching</span><span class="p">(</span><span class="n">axis_data</span><span class="p">,</span> <span class="n">batched_args</span><span class="p">,</span> <span class="n">batch_dims</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">treedef</span><span class="p">):</span>
    <span class="n">box</span> <span class="o">=</span> <span class="n">new_box_p</span><span class="p">.</span><span class="n">bind</span><span class="p">(</span><span class="n">treedef</span><span class="o">=</span><span class="n">treedef</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">box</span><span class="p">,</span> <span class="n">not_mapped</span>


<span class="k">def</span> <span class="nf">_box_get_batching</span><span class="p">(</span><span class="n">axis_data</span><span class="p">,</span> <span class="n">batched_args</span><span class="p">,</span> <span class="n">batch_dims</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">avals</span><span class="p">):</span>
    <span class="p">(</span><span class="n">box</span><span class="p">,),</span> <span class="p">(</span><span class="n">box_bdim</span><span class="p">,)</span> <span class="o">=</span> <span class="n">batched_args</span><span class="p">,</span> <span class="n">batch_dims</span>

    <span class="k">if</span> <span class="n">box_bdim</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">not_mapped</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="s">"Box cannot be batched"</span><span class="p">)</span>

    <span class="n">results</span> <span class="o">=</span> <span class="n">box_get_p</span><span class="p">.</span><span class="n">bind</span><span class="p">(</span><span class="n">box</span><span class="p">,</span> <span class="n">avals</span><span class="o">=</span><span class="n">avals</span><span class="p">)</span>
    <span class="n">out_bdims</span> <span class="o">=</span> <span class="p">(</span><span class="n">not_mapped</span><span class="p">,)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">results</span><span class="p">,</span> <span class="n">out_bdims</span>


<span class="k">def</span> <span class="nf">_box_set_batching</span><span class="p">(</span><span class="n">axis_data</span><span class="p">,</span> <span class="n">batched_args</span><span class="p">,</span> <span class="n">batch_dims</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">treedef</span><span class="p">):</span>
    <span class="n">box</span><span class="p">,</span> <span class="o">*</span><span class="n">vals</span> <span class="o">=</span> <span class="n">batched_args</span>
    <span class="n">box_bdim</span><span class="p">,</span> <span class="o">*</span><span class="n">val_bdims</span> <span class="o">=</span> <span class="n">batch_dims</span>

    <span class="k">if</span> <span class="n">box_bdim</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">not_mapped</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="s">"Box cannot be batched"</span><span class="p">)</span>

    <span class="n">box_set_p</span><span class="p">.</span><span class="n">bind</span><span class="p">(</span><span class="n">box</span><span class="p">,</span> <span class="o">*</span><span class="n">vals</span><span class="p">,</span> <span class="n">treedef</span><span class="o">=</span><span class="n">treedef</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">[],</span> <span class="p">[]</span>

<span class="n">batching</span><span class="p">.</span><span class="n">fancy_primitive_batchers</span><span class="p">[</span><span class="n">new_box_p</span><span class="p">]</span> <span class="o">=</span> <span class="n">_new_box_batching</span>
<span class="n">batching</span><span class="p">.</span><span class="n">fancy_primitive_batchers</span><span class="p">[</span><span class="n">box_get_p</span><span class="p">]</span> <span class="o">=</span> <span class="n">_box_get_batching</span>
<span class="n">batching</span><span class="p">.</span><span class="n">fancy_primitive_batchers</span><span class="p">[</span><span class="n">box_set_p</span><span class="p">]</span> <span class="o">=</span> <span class="n">_box_set_batching</span>

<span class="o">@</span><span class="n">register_pytree_node_class</span>
<span class="o">@</span><span class="n">dataclass</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Counter</span><span class="p">:</span>
    <span class="n">index</span><span class="p">:</span> <span class="n">Box</span>
    <span class="n">_hash</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">_counter</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">index</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">Box</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_hash</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">.</span><span class="n">_counter</span>
        <span class="n">Counter</span><span class="p">.</span><span class="n">_counter</span> <span class="o">+=</span> <span class="mi">1</span>

    <span class="k">def</span> <span class="nf">__hash__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_hash</span>

    <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="n">get</span><span class="p">()</span>
        <span class="n">new_x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">value</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="nb">set</span><span class="p">(</span><span class="n">value</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">new_x</span>

    <span class="k">def</span> <span class="nf">tree_flatten</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">children</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">index</span><span class="p">,)</span>
        <span class="n">aux_data</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_hash</span>
        <span class="k">return</span> <span class="n">children</span><span class="p">,</span> <span class="n">aux_data</span>

    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">tree_unflatten</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">aux_data</span><span class="p">,</span> <span class="n">children</span><span class="p">):</span>
        <span class="p">(</span><span class="n">index_box</span><span class="p">,)</span> <span class="o">=</span> <span class="n">children</span>
        <span class="n">obj</span> <span class="o">=</span> <span class="nb">object</span><span class="p">.</span><span class="n">__new__</span><span class="p">(</span><span class="n">cls</span><span class="p">)</span>
        <span class="n">obj</span><span class="p">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">index_box</span>
        <span class="n">obj</span><span class="p">.</span><span class="n">_hash</span> <span class="o">=</span> <span class="n">aux_data</span>
        <span class="k">return</span> <span class="n">obj</span>


<span class="n">counter</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(</span><span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mf">2.3</span><span class="p">)</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">jit</span><span class="p">(</span><span class="n">counter</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="n">counter</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="n">get</span><span class="p">())</span>

<span class="n">_</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">counter</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="n">get</span><span class="p">())</span>

<span class="n">_</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">counter</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="n">get</span><span class="p">())</span>


<span class="k">class</span> <span class="nc">Model</span><span class="p">(</span><span class="n">eqx</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="n">linear</span><span class="p">:</span> <span class="n">eqx</span><span class="p">.</span><span class="n">nn</span><span class="p">.</span><span class="n">Linear</span>
    <span class="n">counter</span><span class="p">:</span> <span class="n">Counter</span>
    <span class="n">v_counter</span><span class="p">:</span> <span class="n">Counter</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
        <span class="c1"># Not-stateful layer
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">linear</span> <span class="o">=</span> <span class="n">eqx</span><span class="p">.</span><span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="n">key</span><span class="p">)</span>
        <span class="c1"># Stateful layer.
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">counter</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(</span><span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
        <span class="c1"># Vmap'd stateful layer. (Whose initial state will include a batch dimension.)
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">v_counter</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">vmap</span><span class="p">(</span><span class="n">Counter</span><span class="p">,</span> <span class="n">out_axes</span><span class="o">=</span><span class="bp">None</span><span class="p">)(</span><span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]))</span>

    <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="k">assert</span> <span class="n">x</span><span class="p">.</span><span class="n">shape</span> <span class="o">==</span> <span class="p">(</span><span class="mi">2</span><span class="p">,)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">linear</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">vmap</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">v_counter</span><span class="p">)(</span><span class="n">x</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">x</span>


<span class="n">key</span> <span class="o">=</span> <span class="n">jr</span><span class="p">.</span><span class="n">key</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Model</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="mf">5.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.0</span><span class="p">])</span>
<span class="n">_</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">counter</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="n">get</span><span class="p">())</span>
<span class="k">print</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">v_counter</span><span class="p">.</span><span class="n">index</span><span class="p">.</span><span class="n">get</span><span class="p">())</span>
</code></pre></div></div>

<p>As you can see, this replicates the effect of the vmap/substate of equinox in a much simpler and elegant manner (of course, the custom vmap rules are annoying, but this is mostly because it is experimental and requires only a single implementation for this Box type). This is just the beginnings of the cool stuff you can do with hijax, and I look forward to seeing more mainstream JAX package adoption, for example see the <a href="https://www.youtube.com/watch?v=4j88lxqraZU">Flax hijax talk</a>.</p>

<h2 id="changelog">Changelog</h2>

<ol>
  <li>February 2, 2026: Published initial version.</li>
  <li>February 17, 2026: Updated to reflect upstream <code class="language-plaintext highlighter-rouge">cur_qdd</code> fix.</li>
</ol>

<h2 id="footnotes">Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>No longer requires QDD hacks, since support was added in <a href="https://github.com/jax-ml/jax/pull/35137">PR #35137</a>. Previously required a hack. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><category term="JAX" /><category term="machine learning" /><category term="python" /><category term="hijax" /><summary type="html"><![CDATA[Stateful operations in JAX, such as batchnorm, can sometimes be annoying to work with. As a DSL that relies on pure functions, it requires the usual functional approach of passing around a different state object to all the functions, changing $f :: x \to y$ to $f :: (x, state) \to (y, state)$. While this may not sound like much work, it can be quite tedious to manage with large and complex machine learning network workflows. Part of this complexity comes from the fact that we often want to use vmap to batch, rather than having a batch dimension for everything. Although different libraries in JAX implement stateful operations in different ways, they have a common mechanism for batchnorm, all of them keep track of a vmap named axis, then do collective operation like pmean to get the stats across the vmapped batch. Here, we won’t be looking to simplify that dimension of batchnorm, but the state management.]]></summary></entry><entry><title type="html">Numpy Random Interface in JAX</title><link href="https://lockwo.github.io/posts/2025/12/jax-random" rel="alternate" type="text/html" title="Numpy Random Interface in JAX" /><published>2025-12-13T00:00:00+00:00</published><updated>2025-12-13T00:00:00+00:00</updated><id>https://lockwo.github.io/posts/2025/12/numpy-random-in-jax</id><content type="html" xml:base="https://lockwo.github.io/posts/2025/12/jax-random"><![CDATA[<p>In this blog, we’ll be looking at one of the <a href="https://docs.jax.dev/en/latest/notebooks/Common_Gotchas_in_JAX.html#random-numbers">“sharp bits”</a> of JAX: pseudorandomness, and see if we can’t smooth it out a little. <a href="https://docs.jax.dev/en/latest/">JAX</a> is an accelerated linear algebra library that forms the backbone of some modern python based <a href="https://github.com/lockwo/awesome-jax">scientific and machine learning libraries</a> (things like <a href="https://developers.googleblog.com/building-production-ai-on-google-cloud-tpus-with-jax/">Google’s Gemini were built on JAX</a>). Personally I’m a fan.</p>

<p><img src="/images/britta_jax.jpg" alt="A screenshot from the TV show Community in which a character says, without context, 'look, I hate cops', but instead the text says 'look, I love JAX'" width="700" /></p>

<p>This post is mostly an exploration of JAX, and the solutions presented below probably come with a lot of footguns (especially with any sort of sharding/distributed training) and this is meant to be pedagogical rather than practical (in fact, I even prefer the explicit key management of JAX, usually).</p>

<p>So, how does numpy/torch handle randomness and how does JAX differ? The traditional approach of numpy is to have the pseudo-random generator (PRNG) hidden from the user, so each time you call <code class="language-plaintext highlighter-rouge">np.random.normal</code> it implicitly handles the generation to be unique. It also allows for you to set a global seed with <code class="language-plaintext highlighter-rouge">np.random.seed(int)</code>. The JAX approach is to explicitly manage the <code class="language-plaintext highlighter-rouge">key</code> which is the driver of the pseudo-random generator (the motivations for JAX’s PRNG design are outlined <a href="https://docs.jax.dev/en/latest/jep/263-prng.html#prng-design-jep">here</a>). Historically, these impure functions have not meshed well with JAX’s design. While JAX’s design has a lot of benefits, sometimes you just want a random number and don’t want to think about the key (case in point: <a href="https://docs.jax.dev/en/latest/pytrees.html#example-of-jax-tree-map-with-ml-model-parameters">https://docs.jax.dev/en/latest/pytrees.html#example-of-jax-tree-map-with-ml-model-parameters</a>).</p>

<p>However, now that array refs are part of the exposed API, we can make things a little more numpy-like. Now, we all know what arrays are, but what is an array ref? That is beyond the scope of this blog (a clever way of saying: I’m not really sure myself), so let’s just use the physicist explanation of <a href="https://en.wikipedia.org/wiki/Spin_(physics)">spin</a> and say an array ref is like a C pointer except it’s not a pointer, and it’s not a first class citizen<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>With array refs, it’s quite straightforward to make a more numpy style array interface (in fact, there’s a <a href="https://github.com/jax-ml/jax/pull/28845">JEP for this exact thing</a>). We can just make a stateful ref counter, and increment that for stateful randomness. To be specific, consider the following:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">jax</span>
<span class="kn">from</span> <span class="nn">jax</span> <span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">jnp</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>

<span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">tree_util</span><span class="p">.</span><span class="n">register_dataclass</span>
<span class="o">@</span><span class="n">dataclass</span>
<span class="k">class</span> <span class="nc">RNG</span><span class="p">:</span>
    <span class="n">key</span><span class="p">:</span> <span class="n">jax</span><span class="p">.</span><span class="n">Array</span>
    <span class="n">counter</span><span class="p">:</span> <span class="n">jax</span><span class="p">.</span><span class="n">Ref</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">seed</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">key</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">counter</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">new_ref</span><span class="p">(</span><span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">jnp</span><span class="p">.</span><span class="n">int32</span><span class="p">))</span>

    <span class="k">def</span> <span class="nf">normal</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape</span> <span class="o">=</span> <span class="p">(),</span> <span class="n">dtype</span> <span class="o">=</span> <span class="bp">None</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">[...]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">[...]</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="n">key</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">fold_in</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">[...])</span>
        <span class="k">return</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">normal</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">shape</span><span class="o">=</span><span class="n">shape</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">dtype</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">set_seed</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">seed</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">[...]</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>


<span class="n">rng</span> <span class="o">=</span> <span class="n">RNG</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</code></pre></div></div>

<p>Then, when we use it in a package, we might see something like,</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">jox</span> <span class="kn">import</span> <span class="n">rng</span> <span class="k">as</span> <span class="n">jr</span>
<span class="n">jr</span><span class="p">.</span><span class="n">set_seed</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

<span class="n">weights</span> <span class="o">=</span> <span class="n">jr</span><span class="p">.</span><span class="n">normal</span><span class="p">((</span><span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">))</span>
</code></pre></div></div>

<p>However, right off the bat, this comes with a problem. We can’t <code class="language-plaintext highlighter-rouge">vmap</code> over it. If you try, you see a <code class="language-plaintext highlighter-rouge">Exception: performing a set/swap operation with vmapped value on an unbatched array reference of type Ref{int32[]}. Move the array reference to be an argument to the vmapped function?</code> error. The reason for that is the ref is being closed over, and so when the vmap-ing happens, JAX doesn’t know what the ref should become (since many operations in parallel are happening to the ref). Vmap is like 1/3 of the JAX transforms, so this is a bit of a problem. There’s (at least) two possible solutions. First, we can make the actual PRNG object be passed around and allow for splitting the object so it can also be vmap-ed over (if the ref is an argument of the function, you can vmap over refs). This is what the current JEP does. But the second route is to double down and embrace the numpy interface (the whole point of this was to avoid managing an explicit state as a user, key or otherwise). We can accomplish this second route because we know something specific about the outcome of our ref: it doesn’t depend on the inputs. The usual problem with closed over refs is if you do some <a href="https://docs.jax.dev/en/latest/array_refs.html#restrictions">operation on the inputs to the vmap and the ref depends on them, it’s unclear what the output should be</a>. However, we know our ref doesn’t depend on the values of the array (we can just increment the ref by the batch size, or even just by 1).</p>

<p>One way to accomplish this second approach is with a custom vmap rule. However, since we aren’t explicitly vmap-ing over the <code class="language-plaintext highlighter-rouge">jr.normal</code>, the custom vmap rule won’t necessarily trigger. So we have to make our implementation recognize when it is inside of vmap. This can be done using axis names, via the following:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">custom_batching</span><span class="p">.</span><span class="n">custom_vmap</span>
<span class="k">def</span> <span class="nf">_normal</span><span class="p">(</span><span class="n">counter</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">batch_idx</span><span class="p">,</span> <span class="n">shape</span><span class="p">,</span> <span class="n">dtype</span><span class="p">):</span>
    <span class="n">counter</span><span class="p">[...]</span> <span class="o">=</span> <span class="n">counter</span><span class="p">[...]</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="n">key</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">fold_in</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">counter</span><span class="p">[...])</span>
    <span class="k">return</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">normal</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">shape</span><span class="o">=</span><span class="n">shape</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">dtype</span><span class="p">)</span>

<span class="o">@</span><span class="n">_normal</span><span class="p">.</span><span class="n">def_vmap</span>
<span class="k">def</span> <span class="nf">_normal_vmap_rule</span><span class="p">(</span><span class="n">axis_size</span><span class="p">,</span> <span class="n">in_batched</span><span class="p">,</span> <span class="n">counter</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">batch_idx</span><span class="p">,</span> <span class="n">shape</span><span class="p">,</span> <span class="n">dtype</span><span class="p">):</span>
    <span class="c1"># Add by batch size (could just be 1?)
</span>    <span class="n">counter</span><span class="p">[...]</span> <span class="o">+=</span> <span class="n">axis_size</span>
    <span class="n">k</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">fold_in</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">counter</span><span class="p">[...])</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">normal</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">shape</span><span class="o">=</span><span class="p">(</span><span class="n">axis_size</span><span class="p">,</span> <span class="o">*</span><span class="n">shape</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="n">dtype</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">result</span><span class="p">,</span> <span class="bp">True</span>

<span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">tree_util</span><span class="p">.</span><span class="n">register_dataclass</span>
<span class="o">@</span><span class="n">dataclass</span>
<span class="k">class</span> <span class="nc">RNG</span><span class="p">:</span>
    <span class="n">key</span><span class="p">:</span> <span class="n">jax</span><span class="p">.</span><span class="n">Array</span>
    <span class="n">counter</span><span class="p">:</span> <span class="n">jax</span><span class="p">.</span><span class="n">Ref</span>
    <span class="n">axis</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">metadata</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="n">static</span><span class="o">=</span><span class="bp">True</span><span class="p">))</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">seed</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="s">"i"</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">key</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">counter</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">new_ref</span><span class="p">(</span><span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">jnp</span><span class="p">.</span><span class="n">int32</span><span class="p">))</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">axis</span> <span class="o">=</span> <span class="n">axis</span>

    <span class="k">def</span> <span class="nf">normal</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape</span> <span class="o">=</span> <span class="p">(),</span> <span class="n">dtype</span> <span class="o">=</span> <span class="bp">None</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">batch_idx</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">axis_index</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">axis</span><span class="p">)</span>
        <span class="k">except</span><span class="p">:</span>
            <span class="n">batch_idx</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">_normal</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="n">batch_idx</span><span class="p">,</span> <span class="n">shape</span><span class="p">,</span> <span class="n">dtype</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">set_seed</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">seed</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">[...]</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>


<span class="n">rng</span> <span class="o">=</span> <span class="n">RNG</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
</code></pre></div></div>

<p>This allows for vmap support, albeit not perfect since the user has to specify the axis name (also this isn’t robust to nested vmaps but could be made to be)<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. However, this is only at the vmap level and is a minor change (that could also be optimized at the interface). If they don’t specify it, the code will run, but all the values will be identical (silent PRNG failures are always preferable to raising errors right?).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">jr</span><span class="p">.</span><span class="n">normal</span><span class="p">()</span> <span class="o">+</span> <span class="n">x</span>

<span class="n">jax</span><span class="p">.</span><span class="n">vmap</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">axis_name</span><span class="o">=</span><span class="s">"i"</span><span class="p">)(</span><span class="n">jnp</span><span class="p">.</span><span class="n">ones</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span> <span class="c1"># works!
</span></code></pre></div></div>

<p><img src="/images/dog_meme.jpg" alt="The dog meme which says 'such numpy very wow'" width="400" /></p>

<p>Technically, we have one more hurdle to go through. If you set the shape (even outside of a <code class="language-plaintext highlighter-rouge">jit</code>), you will see an error like <code class="language-plaintext highlighter-rouge">TypeError: Shapes must be 1D sequences of concrete values of integer type, got (JitTracer&lt;~int32[]&gt;, JitTracer&lt;~int32[]&gt;)</code>. This is because the custom vmap code is immediately traced for a jaxpr, which means the static integers get promoted to tracers. It is not clear to me whether this is necessary, and the fix is to add something like static arguments (like custom vjp) or whether there is a simpler solution<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>. However, we can skirt around this for now by also capturing the shapes:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">normal</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape</span> <span class="o">=</span> <span class="p">(),</span> <span class="n">dtype</span> <span class="o">=</span> <span class="bp">None</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">batch_idx</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">axis_index</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">axis</span><span class="p">)</span>
    <span class="k">except</span><span class="p">:</span>
        <span class="n">batch_idx</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

    <span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">custom_batching</span><span class="p">.</span><span class="n">custom_vmap</span>
    <span class="k">def</span> <span class="nf">_normal</span><span class="p">(</span><span class="n">counter</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">batch_idx</span><span class="p">):</span>
        <span class="n">counter</span><span class="p">[...]</span> <span class="o">=</span> <span class="n">counter</span><span class="p">[...]</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="n">key</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">fold_in</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">counter</span><span class="p">[...])</span>
        <span class="k">return</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">normal</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">shape</span><span class="o">=</span><span class="n">shape</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">dtype</span><span class="p">)</span>

    <span class="o">@</span><span class="n">_normal</span><span class="p">.</span><span class="n">def_vmap</span>
    <span class="k">def</span> <span class="nf">_normal_vmap_rule</span><span class="p">(</span><span class="n">axis_size</span><span class="p">,</span> <span class="n">in_batched</span><span class="p">,</span> <span class="n">counter</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">batch_idx</span><span class="p">):</span>
        <span class="c1"># Add by batch size
</span>        <span class="n">counter</span><span class="p">[...]</span> <span class="o">+=</span> <span class="n">axis_size</span>
        <span class="n">k</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">fold_in</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">counter</span><span class="p">[...])</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="n">normal</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">shape</span><span class="o">=</span><span class="p">(</span><span class="n">axis_size</span><span class="p">,</span> <span class="o">*</span><span class="n">shape</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="n">dtype</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">result</span><span class="p">,</span> <span class="bp">True</span>

    <span class="k">return</span> <span class="n">_normal</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">counter</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="n">batch_idx</span><span class="p">)</span>
</code></pre></div></div>

<p>To prove this approach actually works, here is a pure JAX <a href="https://arxiv.org/abs/1312.6114">VAE</a>, with absolutely no <code class="language-plaintext highlighter-rouge">jax.random.key</code> and no <code class="language-plaintext highlighter-rouge">jax.random.split</code>. We can visualize the latent space in the same way as <a href="https://keras.io/examples/generative/vae/">https://keras.io/examples/generative/vae/</a>,</p>

<p><img src="/images/vae.png" alt="A grid of interpolated latents of the VAE" width="800" /></p>

<details>

  <summary> Full Training Code</summary>

  <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">jax</span>
<span class="kn">import</span> <span class="nn">jax.numpy</span> <span class="k">as</span> <span class="n">jnp</span>
<span class="kn">from</span> <span class="nn">jox</span> <span class="kn">import</span> <span class="n">rng</span> <span class="k">as</span> <span class="n">jr</span>
<span class="kn">import</span> <span class="nn">optax</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="nn">torchvision</span> <span class="kn">import</span> <span class="n">datasets</span>

<span class="n">train</span> <span class="o">=</span> <span class="n">datasets</span><span class="p">.</span><span class="n">MNIST</span><span class="p">(</span><span class="n">root</span><span class="o">=</span><span class="s">'./data'</span><span class="p">,</span> <span class="n">train</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">download</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">test</span> <span class="o">=</span> <span class="n">datasets</span><span class="p">.</span><span class="n">MNIST</span><span class="p">(</span><span class="n">root</span><span class="o">=</span><span class="s">'./data'</span><span class="p">,</span> <span class="n">train</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">download</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="n">x_all</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">concatenate</span><span class="p">([</span><span class="n">train</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">numpy</span><span class="p">(),</span> <span class="n">test</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">numpy</span><span class="p">()],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">x_all</span> <span class="o">=</span> <span class="n">x_all</span><span class="p">.</span><span class="n">astype</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span> <span class="o">/</span> <span class="mf">255.0</span>
<span class="n">x_all</span> <span class="o">=</span> <span class="n">x_all</span><span class="p">.</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">28</span><span class="p">)</span>

<span class="n">batch_size</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">num_samples</span> <span class="o">=</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">x_all</span><span class="p">)</span> <span class="o">//</span> <span class="n">batch_size</span><span class="p">)</span> <span class="o">*</span> <span class="n">batch_size</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">x_all</span><span class="p">[:</span><span class="n">num_samples</span><span class="p">].</span><span class="n">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">batch_size</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">28</span><span class="p">))</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Data shape: </span><span class="si">{</span><span class="n">x</span><span class="p">.</span><span class="n">shape</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">matshow</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">].</span><span class="n">reshape</span><span class="p">((</span><span class="mi">28</span><span class="p">,</span> <span class="mi">28</span><span class="p">)),</span> <span class="n">cmap</span><span class="o">=</span><span class="s">'gray'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">init_linear</span><span class="p">(</span><span class="n">in_features</span><span class="p">,</span> <span class="n">out_features</span><span class="p">):</span>
    <span class="n">stddev</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">sqrt</span><span class="p">(</span><span class="mf">2.0</span> <span class="o">/</span> <span class="p">(</span><span class="n">in_features</span> <span class="o">+</span> <span class="n">out_features</span><span class="p">))</span>
    <span class="n">w</span> <span class="o">=</span> <span class="n">jr</span><span class="p">.</span><span class="n">normal</span><span class="p">((</span><span class="n">in_features</span><span class="p">,</span> <span class="n">out_features</span><span class="p">))</span> <span class="o">*</span> <span class="n">stddev</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">out_features</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">{</span><span class="s">'w'</span><span class="p">:</span> <span class="n">w</span><span class="p">,</span> <span class="s">'b'</span><span class="p">:</span> <span class="n">b</span><span class="p">}</span>


<span class="k">def</span> <span class="nf">linear</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">x</span> <span class="o">@</span> <span class="n">params</span><span class="p">[</span><span class="s">'w'</span><span class="p">]</span> <span class="o">+</span> <span class="n">params</span><span class="p">[</span><span class="s">'b'</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">init_vae</span><span class="p">(</span><span class="n">input_dim</span><span class="o">=</span><span class="mi">784</span><span class="p">,</span> <span class="n">hidden_dims</span><span class="o">=</span><span class="p">[</span><span class="mi">512</span><span class="p">,</span> <span class="mi">256</span><span class="p">],</span> <span class="n">latent_dim</span><span class="o">=</span><span class="mi">2</span><span class="p">):</span>
    <span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s">'encoder'</span><span class="p">:</span> <span class="p">[],</span> <span class="s">'decoder'</span><span class="p">:</span> <span class="p">[],</span> <span class="s">'z_mean'</span><span class="p">:</span> <span class="bp">None</span><span class="p">,</span> <span class="s">'z_log_var'</span><span class="p">:</span> <span class="bp">None</span><span class="p">}</span>
    <span class="n">key_idx</span> <span class="o">=</span> <span class="mi">0</span>
    
    <span class="n">enc_dims</span> <span class="o">=</span> <span class="p">[</span><span class="n">input_dim</span><span class="p">]</span> <span class="o">+</span> <span class="n">hidden_dims</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">enc_dims</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">params</span><span class="p">[</span><span class="s">'encoder'</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">init_linear</span><span class="p">(</span><span class="n">enc_dims</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">enc_dims</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]))</span>
        <span class="n">key_idx</span> <span class="o">+=</span> <span class="mi">1</span>
    
    <span class="n">params</span><span class="p">[</span><span class="s">'z_mean'</span><span class="p">]</span> <span class="o">=</span> <span class="n">init_linear</span><span class="p">(</span><span class="n">hidden_dims</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">latent_dim</span><span class="p">)</span>
    <span class="n">key_idx</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="n">params</span><span class="p">[</span><span class="s">'z_log_var'</span><span class="p">]</span> <span class="o">=</span> <span class="n">init_linear</span><span class="p">(</span> <span class="n">hidden_dims</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">latent_dim</span><span class="p">)</span>
    <span class="n">key_idx</span> <span class="o">+=</span> <span class="mi">1</span>
    
    <span class="n">dec_dims</span> <span class="o">=</span> <span class="p">[</span><span class="n">latent_dim</span><span class="p">]</span> <span class="o">+</span> <span class="n">hidden_dims</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="n">input_dim</span><span class="p">]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">dec_dims</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">params</span><span class="p">[</span><span class="s">'decoder'</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">init_linear</span><span class="p">(</span><span class="n">dec_dims</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">dec_dims</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]))</span>
        <span class="n">key_idx</span> <span class="o">+=</span> <span class="mi">1</span>
    
    <span class="k">return</span> <span class="n">params</span>

<span class="k">def</span> <span class="nf">encode</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="n">flatten</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">layer_params</span> <span class="ow">in</span> <span class="n">params</span><span class="p">[</span><span class="s">'encoder'</span><span class="p">]:</span>
        <span class="n">h</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">nn</span><span class="p">.</span><span class="n">relu</span><span class="p">(</span><span class="n">linear</span><span class="p">(</span><span class="n">layer_params</span><span class="p">,</span> <span class="n">h</span><span class="p">))</span>
    <span class="n">z_mean</span> <span class="o">=</span> <span class="n">linear</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="s">'z_mean'</span><span class="p">],</span> <span class="n">h</span><span class="p">)</span>
    <span class="n">z_log_var</span> <span class="o">=</span> <span class="n">linear</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="s">'z_log_var'</span><span class="p">],</span> <span class="n">h</span><span class="p">)</span>
    <span class="n">z</span> <span class="o">=</span> <span class="n">z_mean</span> <span class="o">+</span> <span class="n">jnp</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="mf">0.5</span> <span class="o">*</span> <span class="n">z_log_var</span><span class="p">)</span> <span class="o">*</span> <span class="n">jr</span><span class="p">.</span><span class="n">normal</span><span class="p">(</span><span class="n">shape</span><span class="o">=</span><span class="n">z_mean</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">z_mean</span><span class="p">,</span> <span class="n">z_log_var</span><span class="p">,</span> <span class="n">z</span>


<span class="k">def</span> <span class="nf">decode</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
    <span class="n">h</span> <span class="o">=</span> <span class="n">z</span>
    <span class="k">for</span> <span class="n">layer_params</span> <span class="ow">in</span> <span class="n">params</span><span class="p">[</span><span class="s">'decoder'</span><span class="p">][:</span><span class="o">-</span><span class="mi">1</span><span class="p">]:</span>
        <span class="n">h</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">nn</span><span class="p">.</span><span class="n">relu</span><span class="p">(</span><span class="n">linear</span><span class="p">(</span><span class="n">layer_params</span><span class="p">,</span> <span class="n">h</span><span class="p">))</span>
    <span class="n">logits</span> <span class="o">=</span> <span class="n">linear</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="s">'decoder'</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">h</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">logits</span><span class="p">.</span><span class="n">reshape</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">28</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
    <span class="n">z_mean</span><span class="p">,</span> <span class="n">z_log_var</span><span class="p">,</span> <span class="n">z</span> <span class="o">=</span> <span class="n">encode</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
    <span class="n">logits</span> <span class="o">=</span> <span class="n">decode</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">z_mean</span><span class="p">,</span> <span class="n">z_log_var</span><span class="p">,</span> <span class="n">logits</span>

<span class="k">def</span> <span class="nf">binary_cross_entropy</span><span class="p">(</span><span class="n">labels</span><span class="p">,</span> <span class="n">logits</span><span class="p">):</span>
    <span class="c1"># https://github.com/google/flax/blob/main/examples/vae/train.py
</span>    <span class="n">logits</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">nn</span><span class="p">.</span><span class="n">log_sigmoid</span><span class="p">(</span><span class="n">logits</span><span class="p">)</span>
    <span class="k">return</span> <span class="o">-</span><span class="n">jnp</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">labels</span> <span class="o">*</span> <span class="n">logits</span> <span class="o">+</span> <span class="p">(</span><span class="mf">1.0</span> <span class="o">-</span> <span class="n">labels</span><span class="p">)</span> <span class="o">*</span> <span class="n">jnp</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="o">-</span><span class="n">jnp</span><span class="p">.</span><span class="n">expm1</span><span class="p">(</span><span class="n">logits</span><span class="p">)))</span>


<span class="k">def</span> <span class="nf">vae_loss_single</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">z_mean</span><span class="p">,</span> <span class="n">z_log_var</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="n">recon_loss</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">binary_cross_entropy</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">logits</span><span class="p">))</span>
    <span class="n">kl_loss</span> <span class="o">=</span> <span class="o">-</span><span class="mf">0.5</span> <span class="o">*</span> <span class="n">jnp</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="o">+</span> <span class="n">z_log_var</span> <span class="o">-</span> <span class="n">jnp</span><span class="p">.</span><span class="n">square</span><span class="p">(</span><span class="n">z_mean</span><span class="p">)</span> <span class="o">-</span> <span class="n">jnp</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="n">z_log_var</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">recon_loss</span> <span class="o">+</span> <span class="n">kl_loss</span>

<span class="k">def</span> <span class="nf">loss_fn</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">single_loss</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
        <span class="n">z_mean</span><span class="p">,</span> <span class="n">z_log_var</span><span class="p">,</span> <span class="n">logits</span> <span class="o">=</span> <span class="n">forward</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">vae_loss_single</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">z_mean</span><span class="p">,</span> <span class="n">z_log_var</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
    
    <span class="n">losses</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">vmap</span><span class="p">(</span><span class="n">single_loss</span><span class="p">,</span> <span class="n">axis_name</span><span class="o">=</span><span class="s">"i"</span><span class="p">)(</span><span class="n">data</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">jnp</span><span class="p">.</span><span class="n">mean</span><span class="p">(</span><span class="n">losses</span><span class="p">)</span>

<span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">jit</span>
<span class="k">def</span> <span class="nf">step</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">opt_state</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="n">loss_value</span><span class="p">,</span> <span class="n">grads</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">value_and_grad</span><span class="p">(</span><span class="n">loss_fn</span><span class="p">)(</span><span class="n">params</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
    <span class="n">updates</span><span class="p">,</span> <span class="n">opt_state</span> <span class="o">=</span> <span class="n">optimizer</span><span class="p">.</span><span class="n">update</span><span class="p">(</span><span class="n">grads</span><span class="p">,</span> <span class="n">opt_state</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span>
    <span class="n">params</span> <span class="o">=</span> <span class="n">optax</span><span class="p">.</span><span class="n">apply_updates</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">updates</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">params</span><span class="p">,</span> <span class="n">opt_state</span><span class="p">,</span> <span class="n">loss_value</span>

<span class="n">params</span> <span class="o">=</span> <span class="n">init_vae</span><span class="p">(</span><span class="n">input_dim</span><span class="o">=</span><span class="mi">784</span><span class="p">,</span> <span class="n">hidden_dims</span><span class="o">=</span><span class="p">[</span><span class="mi">512</span><span class="p">,</span> <span class="mi">256</span><span class="p">],</span> <span class="n">latent_dim</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="n">optimizer</span> <span class="o">=</span> <span class="n">optax</span><span class="p">.</span><span class="n">adam</span><span class="p">(</span><span class="n">learning_rate</span><span class="o">=</span><span class="mf">0.001</span><span class="p">)</span>
<span class="n">opt_state</span> <span class="o">=</span> <span class="n">optimizer</span><span class="p">.</span><span class="n">init</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>

<span class="n">losses</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
    <span class="n">batch_losses</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">batch</span> <span class="ow">in</span> <span class="n">x</span><span class="p">:</span>
        <span class="n">params</span><span class="p">,</span> <span class="n">opt_state</span><span class="p">,</span> <span class="n">l</span> <span class="o">=</span> <span class="n">step</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">opt_state</span><span class="p">,</span> <span class="n">batch</span><span class="p">)</span>
        <span class="n">batch_losses</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">l</span><span class="p">)</span>
    <span class="n">losses</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">mean</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">batch_losses</span><span class="p">)))</span>

<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">losses</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Epoch'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Loss'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">plot_latent_space</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">n</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="mi">15</span><span class="p">):</span>
    <span class="n">digit_size</span> <span class="o">=</span> <span class="mi">28</span>
    <span class="n">scale</span> <span class="o">=</span> <span class="mf">1.0</span>
    <span class="n">figure</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros</span><span class="p">((</span><span class="n">digit_size</span> <span class="o">*</span> <span class="n">n</span><span class="p">,</span> <span class="n">digit_size</span> <span class="o">*</span> <span class="n">n</span><span class="p">))</span>
    
    <span class="n">grid_x</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="o">-</span><span class="n">scale</span><span class="p">,</span> <span class="n">scale</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
    <span class="n">grid_y</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="o">-</span><span class="n">scale</span><span class="p">,</span> <span class="n">scale</span><span class="p">,</span> <span class="n">n</span><span class="p">)[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">yi</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">grid_y</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span><span class="p">,</span> <span class="n">xi</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">grid_x</span><span class="p">):</span>
            <span class="n">z_sample</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="n">xi</span><span class="p">,</span> <span class="n">yi</span><span class="p">])</span>
            <span class="n">x_decoded</span> <span class="o">=</span> <span class="n">jax</span><span class="p">.</span><span class="n">nn</span><span class="p">.</span><span class="n">sigmoid</span><span class="p">(</span><span class="n">decode</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">z_sample</span><span class="p">))</span>
            <span class="n">digit</span> <span class="o">=</span> <span class="n">x_decoded</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">reshape</span><span class="p">(</span><span class="n">digit_size</span><span class="p">,</span> <span class="n">digit_size</span><span class="p">)</span>
            <span class="n">figure</span><span class="p">[</span>
                <span class="n">i</span> <span class="o">*</span> <span class="n">digit_size</span> <span class="p">:</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">digit_size</span><span class="p">,</span>
                <span class="n">j</span> <span class="o">*</span> <span class="n">digit_size</span> <span class="p">:</span> <span class="p">(</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">digit_size</span><span class="p">,</span>
            <span class="p">]</span> <span class="o">=</span> <span class="n">digit</span>

    <span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="n">figsize</span><span class="p">,</span> <span class="n">figsize</span><span class="p">))</span>
    <span class="n">start_range</span> <span class="o">=</span> <span class="n">digit_size</span> <span class="o">//</span> <span class="mi">2</span>
    <span class="n">end_range</span> <span class="o">=</span> <span class="n">n</span> <span class="o">*</span> <span class="n">digit_size</span> <span class="o">+</span> <span class="n">start_range</span>
    <span class="n">pixel_range</span> <span class="o">=</span> <span class="n">jnp</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="n">start_range</span><span class="p">,</span> <span class="n">end_range</span><span class="p">,</span> <span class="n">digit_size</span><span class="p">)</span>
    <span class="n">sample_range_x</span> <span class="o">=</span> <span class="p">[</span><span class="nb">float</span><span class="p">(</span><span class="nb">round</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">grid_x</span><span class="p">.</span><span class="n">tolist</span><span class="p">()]</span>
    <span class="n">sample_range_y</span> <span class="o">=</span> <span class="p">[</span><span class="nb">float</span><span class="p">(</span><span class="nb">round</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">grid_y</span><span class="p">.</span><span class="n">tolist</span><span class="p">()]</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">xticks</span><span class="p">(</span><span class="n">pixel_range</span><span class="p">,</span> <span class="n">sample_range_x</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">yticks</span><span class="p">(</span><span class="n">pixel_range</span><span class="p">,</span> <span class="n">sample_range_y</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">"z[0]"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">"z[1]"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">imshow</span><span class="p">(</span><span class="n">figure</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"Greys_r"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="n">plot_latent_space</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
</code></pre></div>  </div>

</details>

<h2 id="changelog">Changelog</h2>

<ol>
  <li>December 15, 2025: Published initial version.</li>
  <li>February 2, 2026: Fixed some typos.</li>
</ol>

<h2 id="footnotes">Footnotes</h2>

<!-- [^1]: Live footage of me reading the JAX ref source code: <https://www.youtube.com/watch?v=xjDa-_Vq51I>.  -->

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>How do array refs interact with the C/C++ FFI interface? I’m not sure, but that’s an interesting question. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Another possible solution might be to make a <a href="https://docs.jax.dev/en/latest/jax-primitives.html#defining-new-jax-primitives">custom primitive</a>, but I haven’t given that as much thought. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>This might be fixable in JAX, I will update this blog as this JAX issue progresses <a href="https://github.com/jax-ml/jax/issues/33943">https://github.com/jax-ml/jax/issues/33943</a>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><category term="JAX" /><category term="python" /><category term="programming" /><summary type="html"><![CDATA[In this blog, we’ll be looking at one of the “sharp bits” of JAX: pseudorandomness, and see if we can’t smooth it out a little. JAX is an accelerated linear algebra library that forms the backbone of some modern python based scientific and machine learning libraries (things like Google’s Gemini were built on JAX). Personally I’m a fan.]]></summary></entry><entry><title type="html">Can Quantization Reduce Separability?</title><link href="https://lockwo.github.io/posts/2025/11/quantization-separability/" rel="alternate" type="text/html" title="Can Quantization Reduce Separability?" /><published>2025-11-29T00:00:00+00:00</published><updated>2025-11-29T00:00:00+00:00</updated><id>https://lockwo.github.io/posts/2025/11/can-quantization-reduce-separability</id><content type="html" xml:base="https://lockwo.github.io/posts/2025/11/quantization-separability/"><![CDATA[<p>I was recently experimenting with the linear separability of embeddings, and showed a graph which indicated the embeddings were substantially more linearly separable than the quantized embeddings. One person commented that this didn’t make sense, and the quantized vectors should be as separable as the non-quantized vectors. That didn’t seem right to me, but on the spot I couldn’t think of a trivial counter example. In this blog, I present two.</p>

<p>The first, and most trivial case, is when you have more classes than quantized vectors. If you are quantizing your vectors to a set of $N$ vectors, and you have $N + 1$ classes, then naturally two classes have to have overlap in this quantized space. However, such a reduced quantized space isn’t as common in machine learning problems.</p>

<p>For the second example, consider the XOR problem. This is known to be not linearly separable. So in the case where we have $2$ classes and $2$ quantization vectors, then if class 1 maps to $(0, 1)$ and $(1, 0)$ and class 2 maps to $(1, 1)$ and $(0, 0)$ in quantized space, but the non-quantized vectors are separable then we have shown our goal. Given these four quantization vectors, consider the following embedding points, class 1: $(0.75, -2)$, $(-2, 0.8)$, class 2: $(-0.75, 0.25)$, $(0.8, 0.8)$, these embed to $(1, 0)$, $(0, 1)$, $(0, 0)$, $(1, 1)$. Clearly these embeddings are not linearly separable, but with $f(x, y) = -0.225x - 0.37y - 0.1$, the non-quantized vectors are.</p>

<h2 id="changelog">Changelog</h2>

<ol>
  <li>November 29, 2025: Published initial version.</li>
  <li>February 2, 2026: Updated to use math mode for equations and vectors.</li>
</ol>]]></content><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><category term="machine learning" /><category term="embeddings" /><category term="quantization" /><summary type="html"><![CDATA[I was recently experimenting with the linear separability of embeddings, and showed a graph which indicated the embeddings were substantially more linearly separable than the quantized embeddings. One person commented that this didn’t make sense, and the quantized vectors should be as separable as the non-quantized vectors. That didn’t seem right to me, but on the spot I couldn’t think of a trivial counter example. In this blog, I present two.]]></summary></entry><entry><title type="html">Why The AI Bubble Will Never Pop</title><link href="https://lockwo.github.io/posts/2025/10/ai-bubble/" rel="alternate" type="text/html" title="Why The AI Bubble Will Never Pop" /><published>2025-10-28T00:00:00+00:00</published><updated>2025-10-28T00:00:00+00:00</updated><id>https://lockwo.github.io/posts/2025/10/why-the-ai-bubble-will-never-pop</id><content type="html" xml:base="https://lockwo.github.io/posts/2025/10/ai-bubble/"><![CDATA[<p>As the AI hype train has progressed and <a href="https://substack.com/home/post/p-170175290">consumed upwards of 35% of the US economy</a> (which surely is <a href="https://www.bloodinthemachine.com/p/the-ai-bubble-is-so-big-its-propping">not a problem</a>), a growing sentiment has emerged: that this is a <a href="https://www.cnbc.com/2025/08/18/openai-sam-altman-warns-ai-market-is-in-a-bubble.html">bubble</a>. This is probably now even the dominant opinion: that given the scale of investment, the large valuations, the <a href="https://insights.euclid.vc/p/deus-ex-capex">lack of returns</a>, and the slow progress, this constitutes a bubble. And bubbles always pop.</p>

<p>However, as a contrarian thought experiment, here I will posit an argument for why the bubble will not pop. Why will the bubble never pop? A simple argument: AI (specifically LLMs) in their current forms (without need for any future improvements) are the single greatest tool in the history of human civilization for surveillance and control of individuals at a massive society level scale. Thus, the bubble will never pop because governments and big tech will continue to operate AI, even at a seemingly substantial loss, due to the power it provides.</p>

<p>This doesn’t rely on any increases in performance, or any changes in how the technology works, and is purely based on the LLMs as they exist today. What makes them the most powerful tool for control? One of their biggest strengths is control of information, especially as enabled by social media. Already, on social media, specialized algorithms are deployed to surveil, <a href="https://www.bbc.com/news/technology-58570353">manipulate</a>, and <a href="https://en.wikipedia.org/wiki/Facebook-Cambridge_Analytica_data_scandal">control</a> users. LLMs supercharge these capabilities. LLMs can directly interact, engage, and <a href="https://www.science.org/content/article/unethical-ai-research-reddit-under-fire">influence</a> users through bot profiles. While existing bot/troll farms have been successful for years (<a href="https://www.technologyreview.com/2021/09/16/1035851/facebook-troll-farms-report-us-2020-election/">1</a>, <a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0264507">2</a>, <a href="https://www.gov.uk/government/news/uk-exposes-sick-russian-troll-factory-plaguing-social-media-with-kremlin-propaganda">3</a>), LLMs enable the large scale production and delivery of propaganda without the engineering requirements of bot farms, and are capable delivering more sophisticated messages. Additionally, as LLMs increasingly become a <a href="https://en.wikipedia.org/wiki/Grokipedia">source of truth</a>, they provide big tech with a means to further centralize narrative control. LLMs can be made to parrot anything, they have no intrinsic proclivity or bias for truthful statements. Thus, once companies with LLMs have monopolized trust (and squashed competing alternatives), they can begin to use this trust to manipulate and influence the public (i.e. the exact same playbook that was run for social media and has influenced politics on a global scale). In fact, even if those who control the LLMs are wholly benevolent, LLMs are <a href="https://thebulletin.org/2025/03/russian-networks-flood-the-internet-with-propaganda-aiming-to-corrupt-ai-chatbots/">still being used as tools of information control (powered by the blind collecting of training data)</a>. To be clear, this is not blind speculation, already LLMs are being adopted as tools for <a href="https://www.nytimes.com/2025/08/06/us/politics/china-artificial-intelligence-information-warfare.html">surveillance</a>, <a href="https://theintercept.com/2025/08/25/pentagon-military-ai-propaganda-influence/">“suppress[ing] dissenting arguments”</a>, and <a href="https://www.nytimes.com/2025/08/05/opinion/china-ai-propaganda.html">propaganda</a>. The full scale and power of these approaches is certainly underestimated by publicly available information.</p>

<p>Much of the above was also true for existing social media algorithms (albeit not at the same scale), but a key unique property of LLM technology is the individuals’ interactions. Many people use these models to serve a variety of needs, such as travel planner, doctor, email writer, therapist, friend, financial planner, etc, etc, etc. This is both a <a href="https://www.economist.com/by-invitation/2025/09/09/ai-agents-are-coming-for-your-privacy-warns-meredith-whittaker">massive amount of information that would otherwise be inaccessible</a>, and a potent tool for influence. Already, LLMs are influencing people in a variety of powerful and unprecedented ways. There is the obvious case of <a href="https://www.psychologytoday.com/us/blog/urban-survival/202507/the-emerging-problem-of-ai-psychosis">“AI psychosis”</a>, in which AI taps into users delusions and causes them to spiral, which has been reported on at length (e.g. <a href="https://www.nytimes.com/2025/08/08/technology/ai-chatbots-delusions-chatgpt.html">4</a>, <a href="https://www.nytimes.com/2025/06/13/technology/chatgpt-ai-chatbots-conspiracies.html">5</a>). More relevant though, is the recent trend of LLMs influencing particularly <a href="https://www.nytimes.com/2025/08/26/technology/chatgpt-openai-suicide.html">vulnerable people</a>. While these current cases perhaps stem more from <a href="https://www.argmin.net/p/the-banal-evil-of-ai-safety">negligence</a> than malice, they highlight the essential point about the capability of LLMs for influencing mental states and physical choices. Chatbots can be deployed to <a href="https://www.science.org/doi/10.1126/science.adq1814">guide users</a> to any action or any conclusion or any mental state (certainly they are not omnipotent mind control machines, but they wield substantial influence as people grow to depend on them). Imagine a therapist who is available 24/7, 365 days a year, ready to answer and help you through any crises, through any feeling, through any breakup. That is normally where the VC pitch ends, but the key element is that this therapist is not a real therapist, it is an LLM deployed by a company that doesn’t share your goals but seeks to profit off of you. Through a chat window, users are (willingly) dumping unbelievable amounts of personal data. Never before have peoples’ feelings, desires, plans, travel ideas, emails, health documents, legal documents, and more, been compiled so neatly. But this gold mine doesn’t stop there. Not only do companies and governments have data that would otherwise be difficult to come by (regardless of the extent they are currently taking advantage of this data), they can directly use it not just analyze for large scale patterns, but influence every single individual in a unique and personalized manner. No longer does big tech need crude algorithms to <a href="https://futurism.com/facebook-beauty-targeted-ads">take advantage of vulnerable teenage girls</a>, or rely on huge engineering efforts to <a href="https://en.wikipedia.org/wiki/Onavo">deploy spyware to surveil users encrypted chats</a>, or <a href="https://www.sfgate.com/tech/article/meta-eavesdropped-period-tracker-app-20803399.php">exploit adware to collect, log, and sell peoples’ personal health information</a>. Now people simply ask the AI to help them when they are feeling vulnerable, they have an AI browser or OS that can simply see any encrypted messages, they upload their doctors notes to chatbots to ask questions about them.</p>

<p>Probably the “best” case is that the result of all this control and manipulation is aggressive and invasive advertising (those who have read more science fiction books can probably imagine more creatively unpleasant use cases). But these ads will likely be exceedingly effective. While ads on most websites are easy to distinguish, the continual stream of text of a chatbot lends itself brilliantly to ads. If you ask an LLM, “what is the best printer?” you expect it to merely tell you an answer from an opaque computing process, and as such being served an ad in this format is all but impossible to determine.</p>

<h2 id="additional-reading">Additional Reading</h2>

<ul>
  <li><a href="https://arxiv.org/abs/2503.17473v2">How AI and Human Behaviors Shape Psychosocial Effects of Extended Chatbot Use: A Longitudinal Randomized Controlled Study</a></li>
  <li><a href="https://maxread.substack.com/p/ai-as-normal-technology-derogatory">A.I. as normal technology (derogatory)</a></li>
  <li><a href="https://arxiv.org/abs/2506.08872">Your Brain on ChatGPT: Accumulation of Cognitive Debt when Using an AI Assistant for Essay Writing Task</a></li>
  <li><a href="https://joinreboot.org/p/alignment">The Artificiality of Alignment</a></li>
  <li><a href="https://anthonymoser.github.io/writing/ai/haterdom/2025/08/26/i-am-an-ai-hater.html">I Am An AI Hater</a></li>
</ul>

<h2 id="changelog">Changelog</h2>

<ol>
  <li>October 28, 2025: Published initial version.</li>
  <li>November 29, 2025: Removed comment about market (ir)rationality.</li>
</ol>]]></content><author><name>Owen Lockwood</name><email>contact_owenl@protonmail.com</email></author><category term="AI" /><category term="LLMs" /><category term="society" /><summary type="html"><![CDATA[As the AI hype train has progressed and consumed upwards of 35% of the US economy (which surely is not a problem), a growing sentiment has emerged: that this is a bubble. This is probably now even the dominant opinion: that given the scale of investment, the large valuations, the lack of returns, and the slow progress, this constitutes a bubble. And bubbles always pop.]]></summary></entry></feed>