Skip to content
ARGBE.TECH

Article in English · Deutsch

, 7 min readLast checked By Aleksandr Argunov

On OpenAI's chart, the internal model's lowest compute beats Astra's highest

About 26% against about 17%, with about 12× less plotted compute. The chart readings, the sum behind them, and a 2D Navier-Stokes simulation you can run yourself.

On OpenAI’s chart of selected open math problems, its internal model passes about 26% at its lowest plotted compute. GPT-6 Astra passes about 17% at its highest. The chart came out the same day as the OpenAI Navier-Stokes proof. So I drew it with a real 2D Navier-Stokes simulation. 12 threads of smoke vs 1.

Silver smoke from twelve threads flattens at seventeen percent. One blue thread passes through that height and stops at twenty-six. A 2D fluid simulation, with heights set to OpenAI's chart.
Embed this GIF
<a href="https://argbe.tech/blog/openai-chart-navier-stokes-simulation"><img src="https://argbe.tech/media/openai-chart-navier-stokes-simulation.gif" alt="Silver smoke from twelve threads flattens at seventeen percent. One blue thread passes through that height and stops at twenty-six. A 2D fluid simulation, with heights set to OpenAI's chart." width="600" height="750"></a>

The silver smoke is Astra at its highest compute. The blue thread is the internal model at its lowest. Twelve threads against one is the compute gap. The one thread still goes higher.

What the chart shows

OpenAI published the chart on September 8. Its title is “Performance of GPT-6 Astra and our Internal Model on a curated set of open math problems”. It plots pass rate against test-time compute, on a log scale. @chetaslua shared an annotated version on November 3. The readings below are checked against OpenAI’s original.

Point on OpenAI’s chartApproximate pass rate
GPT-6 Astra, lowest compute8–9%
GPT-6 Astra, highest compute17%
Internal model, lowest compute26%
Internal model, highest compute48%

These are readings from the chart’s pixels. They aren’t benchmark data.

Here’s the sum, so you can check it or cite it. In the 1199×858 image, the two markers sit near x=379 and x=841. One decade on the log axis is about 430 pixels. The two pass rates read as about 0.263 and 0.171.

compute gap     10^((841 − 379) / 430) ≈ 11.9, so about 12×
pass rate gap   0.263 − 0.171 ≈ 0.09, so about 9 percentage points
relative rise   0.263 / 0.171 − 1 ≈ 54%

What that sum doesn’t give you:

  • The axis has no numbers on it. The 12× assumes the repeating ticks mark decades.
  • The chart, the announcement and the proof repository give no problem count, problem list or pass definition.
  • Lowest and highest are the ends of each plotted line. The chart shows no presets.
  • This is one evaluation. It doesn’t say the internal model wins on every task.
  • No price, latency or hardware claim follows. GPU hours have real prices, but this axis has no units.

What the OpenAI Navier-Stokes proof claims

OpenAI has published a claimed blowup proof for 3D Navier-Stokes with smooth external forcing. It comes with a Lean formalization. Independent formal checks and mathematical analysis have followed. So has criticism of how the written argument matches the Lean. Those authors say they aren’t claiming the written proof is wrong.

Clay’s notice says the problem has “apparently been settled”. It calls its process “deliberately unhurried”, and its problem page still says Active. So I’m not calling it solved or not solved. Clay hasn’t finished.

The run is OpenAI’s own account. It reports about 10,000 concurrent agents and a result after 88 hours. GPT-6 Astra then took 17 more hours to formalize and verify it. OpenAI counts 2.7M agent messages and about 130B output tokens. All of that is separate from the chart’s compute sweep.

Noam Brown said “Yes, this result cost millions of dollars.” That’s his statement, not an audited bill. On your own runs, a hard budget cap is what decides where it stops.

In a third-party transcript from November 2, Sam Altman describes agents working on a Millennium Prize problem. Then he says “the model that is going to be released soon, the next GPT”. That transcript isn’t audio-checked. OpenAI’s model catalog doesn’t map a public name to this model. So here it stays the internal model.

How the picture is drawn

I had Claude write the solver. It’s real, and it’s 2D.

It solves the incompressible Navier-Stokes equations in vorticity form. Hot fluid leaves a nozzle at the floor and rises. The rise comes from Boussinesq buoyancy: small density differences drive the flow, and density is otherwise treated as constant. The method is pseudo-spectral. Fields are held in Fourier space, and nonlinear products are taken on the grid. Particles then ride that flow. The particles are the smoke you see.

Four hand-set choices shape what you see.

The heights. A stable layer sits in the solver at each number. A strong enough stable layer can hold back rising air, and this one is set strong enough. That’s what flattens the smoke. The renderer also fades anything drawn above that line.

The threads. Twelve threads against one is the compute gap, drawn. They’re smoke threads, not CPU threads. Each thread gets the same number of particles.

The wind. The single thread rises in a slow upward co-flow, like a smoke wire in a wind tunnel. Without it the thread breaks into a cloud. That wind is added by hand, and it isn’t strictly incompressible. It’s a choice made for the picture. The twelve threads rise on buoyancy alone.

The tidying. Drag and sponge layers take energy out of the flow. Near its ceiling, the single thread’s particles also age out fast. That’s why it ends in a curl.

So the numbers come from the chart. The simulation doesn’t produce them, and the picture isn’t evidence. The swirl and the spread are the solver’s. The heights are OpenAI’s.

It can’t show the proof either. The claimed blowup is in 3D. The official problem description says classical 2D flow, with smooth data and positive viscosity, stays regular. This solver adds buoyancy, so that statement doesn’t automatically cover it. Either way, a 2D picture can’t demonstrate a 3D singularity.

The solver diverged three times while it was being tuned. A solver blowing up proves nothing about Navier-Stokes. That’s numerical instability, not mathematical blowup. A timestep past the method’s CFL limit can do it. So can aliasing or too little resolution. The same problem description warns about interpreting apparent numerical blowup in Euler.

A Navier-Stokes plume you can run in Python

This is a smaller version of the solver. It has one plume on a 200×250 grid. It has no fade and no co-flow, so nothing in the drawing stops the smoke. The stable layer does. CEILING is the height, and it’s the constant to change.

# A 2D Boussinesq plume, pseudo-spectral, rising into a stable layer. Writes plume.png.
import numpy as np
from PIL import Image
from scipy.fft import fftfreq, irfft2, rfft2, rfftfreq
from scipy.ndimage import gaussian_filter

CEILING = 110  # where the stable layer starts, in grid cells above the floor. Try 60 to 170.
N2 = 0.05  # how stable that layer is. Set it to 0 and there's no ceiling.
NX, NY, FLOOR, NOZZLE, DT, STEPS = 200, 250, 30, 36, 0.125, 6000
NU, KAPPA, DRAG, SOURCE = 0.05, 0.025, 0.004, 0.022
rng = np.random.default_rng(17)

y, x = np.arange(NY, dtype=float)[:, None], np.arange(NX, dtype=float)[None, :]
kx, ky = 2 * np.pi * rfftfreq(NX)[None, :], 2 * np.pi * fftfreq(NY)[:, None]
k2 = kx**2 + ky**2
k2_inv = np.where(k2 > 0, 1 / np.where(k2 > 0, k2, 1), 0)
dealias = np.exp(-36 * (k2 / k2.max()) ** 4)
ifft = lambda f: irfft2(f, s=(NY, NX))
ramp = lambda t: np.clip(t, 0, 1) ** 2 * (3 - 2 * np.clip(t, 0, 1))
stable = N2 * ramp((y - FLOOR - CEILING) / 8)  # stable air, not a wall
sponge = 0.12 * (ramp((FLOOR - 22 - y) / 18) + ramp((y - NY + 28) / 20))  # soaks up waves, top and bottom
nozzle = np.exp(-(((x - NX / 2) / (NOZZLE / 2)) ** 4) - ((y - FLOOR) / 5) ** 2)

def velocity(wh):  # vorticity to velocity, through the streamfunction
    psi = wh * k2_inv
    return ifft(1j * ky * psi), ifft(-1j * kx * psi)

def rhs(wh, bh):  # vorticity and buoyancy; the nonlinear products are taken on the grid
    u, v = velocity(wh)
    wx, wy, bx, by = ifft(1j * kx * wh), ifft(1j * ky * wh), ifft(1j * kx * bh), ifft(1j * ky * bh)
    return (
        rfft2(-(u * wx + v * wy)) + 1j * kx * bh - (NU * k2 + DRAG) * wh,
        rfft2(-(u * bx + v * by) - stable * v) - KAPPA * k2 * bh,
    )

def at(f, px, py):  # bilinear lookup at particle positions
    i, j, a, b = np.floor(px).astype(int), np.floor(py).astype(int), px % 1, py % 1
    i0, i1, j0, j1 = i % NX, (i + 1) % NX, j % NY, (j + 1) % NY
    return (f[j0, i0] * (1 - a) + f[j0, i1] * a) * (1 - b) + (f[j1, i0] * (1 - a) + f[j1, i1] * a) * b

wh = np.zeros((NY, NX // 2 + 1), dtype=complex)
bh, px, py = wh.copy(), np.empty(0), np.empty(0)
for t in range(STEPS):
    k1w, k1b = rhs(wh, bh)
    k2w, k2b = rhs(wh + DT * k1w, bh + DT * k1b)
    wh, bh = (wh + DT / 2 * (k1w + k2w)) * dealias, (bh + DT / 2 * (k1b + k2b)) * dealias
    if not np.isfinite(wh).all():
        raise SystemExit(f"Solver blew up at step {t}. That's the numerics, not the equations. Lower DT or SOURCE.")
    w, b = ifft(wh), ifft(bh)
    b += DT * 0.5 * nozzle * (SOURCE * (1 + 0.25 * rng.standard_normal()) - b)  # the flicker seeds turbulence
    w += DT * 0.002 * nozzle * rng.standard_normal((NY, NX))
    wh, bh = rfft2(w * (1 - DT * sponge)), rfft2(b * (1 - DT * sponge))
    u, v = velocity(wh)
    px, py = np.r_[px, NX / 2 + (rng.random(6) - 0.5) * NOZZLE], np.r_[py, FLOOR + rng.random(6) * 3]
    px, py = px + DT * at(u, px, py), py + DT * at(v, px, py)
    px, py = px[py > FLOOR - 15][-24000:], py[py > FLOOR - 15][-24000:]  # the oldest smoke drops out

smoke = np.histogram2d(py, px % NX, bins=(NY * 3, NX * 3), range=((0, NY), (0, NX)))[0]
pixels = (255 * (s := gaussian_filter(smoke, 1.2) ** 0.6) / s.max()).astype("uint8")
pixels[3 * (FLOOR + CEILING), ::8] = 200  # a dotted line where the stable layer starts
Image.fromarray(pixels[::-1]).save("plume.png")
print(f"Stable layer starts at {CEILING}. 99% of the smoke is below {np.percentile(py, 99) - FLOOR:.0f}.")

Save it as plume.py, then:

uv run --no-project --with numpy --with scipy --with pillow python plume.py

Run this and you get plume.png in under a minute on an M1 Pro, and this line:

Stable layer starts at 110. 99% of the smoke is below 116.

The picture shows a column of smoke rising from the floor. It reaches the dotted line and spreads sideways in curls. A little goes past the line, because it’s stable air and not a wall. The seed is fixed, but another machine may differ slightly.

Now set CEILING = 60, then CEILING = 170:

Stable layer starts at 60. 99% of the smoke is below 65.
Stable layer starts at 170. 99% of the smoke is below 177.

Change one constant and the smoke stops somewhere else. That’s why the picture can’t check the chart.

Two more to try. Set N2 = 0 and the layer has no strength. The smoke runs through the line and off the top of the picture, and the last number jumps to 523. Set DT to 4 and you get the other kind of blowup:

Solver blew up at step 22. That's the numerics, not the equations. Lower DT or SOURCE.

Back to the chart

Less plotted compute, and a higher pass rate, on one set of math problems. That’s what the chart shows. It says nothing about an agent in a business workflow, or about the harness around it.

The number is OpenAI’s. The smoke only draws it.

A shorter version of this first appeared on X on 2026-11-04.