When XLA Isn't Enough: Pallas, Mosaic, and Triton JAX's Pallas kernel system, which lowers through Triton on GPU and Mosaic on TPU, lets developers write custom kernels when XLA's automatic fusion is insufficient for operations like flash attention, according to a technical post on the JAX/XLA stack. The post, based on JAX 0.11.0 on an NVIDIA A10G, explains that Pallas kernels operate on references rather than values and compile to a custom call in XLA, carrying the entire Triton module as an attribute. When XLA Isn't Enough: Pallas, Mosaic, and Triton This is the last post on the JAX/XLA stack. We went from Python to a jaxpr, from a jaxpr to optimized single-device HLO, and from single-device HLO to a mesh of devices /posts/how-jax-shards-a-computation/ . One door left, and it is the one you take when the compiler’s automatic decisions are not good enough: writing the kernel yourself. In JAX that door is Pallas , and it lowers through two very different backends — Triton on GPU and Mosaic on TPU. 1 fn:1 The motivation is concrete and I raised it in the XLA review /posts/xla-review-and-critique/ : XLA fuses at the granularity of HLO ops using a cost model you do not control, and some kernels — flash attention is the canonical one — cannot be expressed as a good fusion at that granularity. When you hit that wall you stop describing what to compute and start describing how . Everything below is captured from JAX 0.11.0 on an NVIDIA A10G. That hardware choice turned out to matter more than I expected, and the reason is a section of its own. 2 fn:2 You write to references, not values A Pallas kernel does not look like normal JAX. Normal JAX is functional: values in, values out. A Pallas kernel is imperative and operates on references — you are handed input and output Ref s and you load from and store to them explicitly. 1 2 3 4 5 6 7 8 9 10 python def kernel x ref, o ref : o ref ... = jnp.maximum x ref ... 2.0 + 1.0, 0.0 n, blk = 1024, 256 def f x : return pl.pallas call kernel, out shape=jax.ShapeDtypeStruct n, , jnp.float32 , grid= n // blk, , in specs= pl.BlockSpec blk, , lambda i: i, , out specs=pl.BlockSpec blk, , lambda i: i, x Trace it and the shape of the thing is the whole point: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 js { lambda ; a:f32 1024 . let b:f32 1024 = pallas call grid mapping=GridMapping grid= 4, , block mappings= BlockMapping block shape= Blocked block size=256 , , BlockMapping block shape= Blocked block size=256 , jaxpr={ lambda ; c:Ref