Safe kernels are fast kernels: tile-rs next to Mojo 1.0 On 18 August 2026, Modular open-sourced the Mojo compiler and toolchain under Apache 2, enabling a head-to-head comparison with tile-rs, a Rust dialect for accelerator kernels that enforces type, concurrency, and resource safety. On an Apple M1 Ultra across 16 kernels, tile-rs-generated Metal kernels were faster than Mojo on 15 of 16 kernels, with a median gap of 4.7%, and tied on the last, showing that safety does not cost speed. Safe kernels are fast kernels: tile-rs next to Mojo 1.0 TL;DR— Mojo's compiler and toolchain went open source on 18 August 2026, which for the first time lets anyone put it side by side with something else on the same silicon. We did that with tile-rs, a Rust project for writing accelerator kernels that are type-, concurrency- and resource-safe by construction. On an Apple M1 Ultra, across sixteen kernels — five primitives and eleven from the decode path of DeepSeek-R1-Distill-Qwen-1.5B — the tile-rs-generated Metal kernel is faster than the Mojo one on15 of 16and ties on the last. Not by a lot on most of them: the median gap is 4.7%. The interesting claim isn't the margin, it's the direction —you do not have to spend safety to get speed. Why this comparison is possible now Chris Lattner has shipped the compiler infrastructure most of us build on: LLVM, Clang, Swift, MLIR. In June 2026 Qualcomm agreed to acquire Modular https://www.cnbc.com/2026/06/24/qualcomm-ai-chip-modular-software.html — the company behind Mojo — for about $3.9 billion in stock ; the deal closed on 29 July https://www.qualcomm.com/news/releases/2026/07/qualcomm-completes-acquisition-of-modular , with Lattner becoming EVP of Advanced AI Software and Platforms. On 11 August Modular shipped Mojo 1.0 https://www.modular.com/blog/modular-26-5-mojo-1-0-is-here with stability guarantees, and on 18 August they open-sourced the compiler and toolchain https://www.phoronix.com/news/Modular-Mojo-Open-Source under Apache 2. That last step is what makes this post possible. Before it, Mojo's numbers were Modular's to publish. Now anyone can build it, run it, and check. tile-rs is a much smaller thing, open-sourced in late June 2026. It is a Rust dialect for accelerator kernels whose defining property is that the compiler refuses to emit a kernel that could race, overflow a buffer, or read a tile it does not own. Both projects target Apple GPUs. That overlap is the whole experiment. The head-to-head Sixteen kernels, Apple M1 Ultra, timed on the GPU's own clock — MTLCommandBuffer.gpuStartTime / gpuEndTime , so the host's submit-and-wait sits outside the measurement. Median of 5 blocks of 40 launches, minimum within each block. Mojo built with optimisation on. | kernel | tile-rs → Metal µs | Mojo µs | tile-rs is | |---|---|---|---| vec add | 44.1 | 45.4 | 1.03× faster | vec mul | 44.1 | 45.4 | 1.03× faster | vec sub | 44.1 | 45.3 | 1.03× faster | vec exp | 34.2 | 35.5 | 1.04× faster | residual add | 44.1 | 45.4 | 1.03× faster | rms norm | 49.3 | 64.6 | 1.31× faster | rope | 29.9 | 33.1 | 1.10× faster | argmax | 274.7 | 281.5 | 1.02× faster | matmul | 244.6 | 272.0 | 1.11× faster | q proj | 30.5 | 34.2 | 1.12× faster | k proj | 30.2 | 32.2 | 1.07× faster | v proj | 30.5 | 32.2 | 1.05× faster | o proj | 30.6 | 33.6 | 1.10× faster | down proj | 45.4 | 45.9 | tie 1.01× | gate up silu | 147.6 | 152.5 | 1.03× faster | attn gqa | 8616.9 | 9801.1 | 1.14× faster | Read this honestly. The corpus total is 9.74 ms against 11.00 ms — 1.13× — but attn gqa is 88% of that total, so the corpus number is very nearly a single kernel's result wearing a suit. The per-kernel view is the fair one, and there the story is consistent small wins : 15 of 16, median 4.7%, one tie, nothing lost. A 4.7% median is not a rout. What it rules out is the thing people assume must be true — that memory safety has to be paid for in throughput. How? Three claims One: the kernels are safe, in three specific senses. Type safety. No raw pointers, and nothing that needs a runtime check to stay true. Concurrency safety. No data races, with no synchronisation the programmer has to remember to add. Resource safety. A kernel cannot allocate more than the buffer it was given. This one is not theoretical — see the section on the four Ascend backends below, where three of four got a hardware capacity limit wrong, each in a different way. Two: hardware affinity is not optional, and not uniform. GPUs, TPUs and NPUs have genuinely different architectures, and no single emitted implementation suits all of them. What they do share is enough structure to be captured by one type system and one set of invariants. tile-rs generates a different implementation per target, keeping the operator's meaning while satisfying that target's memory, concurrency, synchronisation and capacity constraints. Three: the two directions compose. Lowering goes from types and invariants to a hardware-affine implementation; it must preserve the safety properties while introducing hardware detail. Lifting goes the other way — from an optimised, target-specific kernel back to types and invariants — and it must re-establish safety. The payoff is what happens when you lift and then lower again to a different target: an optimisation discovered on one backend transfers to the others. If you know Journey to the West : tile-rs is the Monkey King. Safety is the golden headband, hardware affinity is the staff that grows to any length, and the lift/lower cycle is his seventy-two transformations. The headband is usually read as a punishment, but it is the thing that makes the rest of him safe to let loose — and wearing it he still goes everywhere, heaven and the dragon king's palace included. In practice this has been a magpie exercise. tile-rs borrows from NVIDIA's cuTile, Huawei's PTO and Ascend C, and Mojo, then lifts and lowers repeatedly. Adding safety to those ideas has, repeatedly, also made them faster. Two objections worth answering "Rust only draws level with C++, and it cheats with unsafe " The old complaint: Rust and C++ share an LLVM backend, so Rust merely ties, and C++ has decades of high-performance template libraries. And Rust has unsafe , so the optimisations sneak in through a back door anyway — which lets a C++ advocate say "I'm unsafe, but so are you, and I'm better at poking the hardware." Look at the tile-rs kernels. The body a person writes contains no unsafe at all. What remains is confined to a macro-generated prelude that does two things no safe API can do for you: read the hardware block index, and construct the global-memory views from the raw device context. Everything above that line — every operator in both tables below — is safe Rust, and on this hardware it is faster than the alternatives. "Python is AI-native; Rust isn't" Where did that idea come from? When Python became popular, AI in the statistical sense barely existed. And look at what the "AI-native" stack is actually made of once you get below the API surface. OpenAI's tiktoken , Hugging Face's tokenizers , safetensors and candle , NVIDIA's cuTile-rs, and uv — the package manager for Python itself — are all Rust. Python serving stacks like vLLM and SGLang are Python and CUDA where the scheduling lives, but the tokenizer they load is somebody's Rust crate. That is the actual division of labour: Python is where the interface is AI-native, and a systems language is where the throughput is. The question is only which systems language, and whether it hands you a compiler that objects before the chip does. At minimum: if a large model is going to generate the code, I would rather have a Rust compiler checking it for hazards before it ships. The wider table: five backends, one source The Ascend numbers below deserve a precise reading, because it is easy to misread them as "tile-rs versus Triton". They are not. Every column is generated from tile-rs. The question they answer is which backend should tile-rs lower to for this kernel , not whose language is faster . Lines of code | kernel | tile-rs | Ascend C | PTO | TileLang | Triton | Metal | Mojo | |---|---|---|---|---|---|---|---| | all 16, total | 223 | 1276 | 1605 | 498 | 283 | 635 | 606 | tile-rs is the most concise of the seven, and it is the only one a human writes — the rest are emitted. PTO's 1605 is inflated by two kernels, attn gqa at 621 lines and argmax at 365, both of which are fully unrolled. Seven of the sixteen kernels, when an AI first generated the equivalent tile-rs operator, did not compile . Those compiler errors were real defects, not pedantry; fixing them is what produced the operators measured here. Relative performance, Ascend 910B2, DeepSeek-R1-Distill-Qwen-1.5B | tile-rs best-of | Ascend C | PTO | TileLang | Triton | | |---|---|---|---|---|---| | total-time ratio | 51.08× | 1.62× | 2.28× | 1.42× | 22.33× | Every one of these numbers needs its caveats stated, not buried. It is a total-time ratio, not a geometric mean or a median of speedups. So the expensive kernels dominate. Triton reads 22.33× while being fastest on only 5 of the 16 — two kernels carry the whole figure. TileLang makes the same point from the other side: it is fastest on 8 of 16 , more than any other backend, and has the largest total, because its launch path is the leanest on the part while its scans are single-core. The tile-rs column is a best-of, and therefore theoretical. It takes the fastest generated kernel per row against the slowest. It is achievable in principle — each kernel is compiled independently, so a compiler may pick a different backend for each — but if a deployment requires one backend throughout, or fuses only within a backend, that headroom does not materialise. Triton and TileLang reach the NPU through their own Ascend C generation. So those two columns measure Ascend C that was generated from tile-rs-generated Triton or TileLang — a longer path, and worth remembering when comparing them to the direct Ascend C column. What the safety argument looks like in practice The most concrete evidence for the "resource safety" claim came from the four Ascend backends. Each has a 192 KB vector unified buffer to respect. Three of the four got it wrong, each differently, and — this is the part that matters — each was caught at a different distance from the hardware : | backend | how the limit was missed | who caught it | |---|---|---| | PTO | budget constant set above the hardware's | the assembler | | Triton | gated on Triton's representational limit, not the buffer | the compiler | | TileLang | no check at all | the device, mid-run | | Ascend C | — | it asks the hardware | A budget that is merely wrong gets caught by the next tool down. A budget that is absent does not get caught until the chip faults. That ordering is the argument for pushing the constraint into the type system, where it is checked before anything is emitted. Credit where it is due Mojo's abstraction level holds up well on safety, and here is the evidence for that from outside the timing tables. Every kernel in this corpus exists in two readings: the shapes as they were first captured, some of them degenerate and ill-formed, and the same kernels after those shapes were fixed. Ascend C, TileLang, Triton and PTO all emit different code for the two readings — they were sensitive to a malformation they had no business being sensitive to. The Mojo operator is byte-identical across both. It never had to care. That is a real design win, and hats off to the Modular team for it. An invitation Measuring nine days after a 1.0 open-sourcing is not especially fair to Chris Lattner and his team. Nine days is not long. So: consider this a challenge, warmly meant. The kernel corpus and the benchmark entries are published at pu-rs.org https://pu-rs.org , and the measurement method is described above in enough detail to be argued with — which is the part that matters. If the next Mojo release takes these numbers back, everyone who runs a model on any of this hardware wins, and that is rather the point of doing it in the open. All Ascend figures: Ascend 910B2, DeepSeek-R1-Distill-Qwen-1.5B, CANN 8.5.2. All Apple figures: M1 Ultra, 128 GB. Every kernel in both tables is verified against a host reference before it is timed.