def to_bf16(model):
"""A bf16 compute copy: cast every floating array AND the compute
dtype each flax module remembers from construction."""
copy = bf16_arrays(model)
for _, m in nnx.iter_graph(copy):
if isinstance(m, nnx.Module) and hasattr(m, 'dtype'):
m.dtype = jnp.bfloat16
return copy
class Bf16GPT(d2l.GPT):
"""ch. 11's GPT with one dtype leak patched: rope's fp32 trigonometry
promotes q and k back to fp32 (and the attention kernel rightly
refuses mixed inputs), so cast them back after rope."""
class CausalAttention(d2l.GPT.CausalAttention):
def __call__(self, X, *_):
B, T, D = X.shape
q, k, v = jnp.split(self.W_qkv(X), 3, axis=-1)
q, k, v = (u.reshape(B, T, self.num_heads, -1)
for u in (q, k, v))
if self.rope:
q, k = self._rope(q), self._rope(k)
q, k = q.astype(v.dtype), k.astype(v.dtype)
Y = jax.nn.dot_product_attention(q, k, v, is_causal=True)
return self.W_o(Y.reshape(B, T, D)), None
def make_bf16_gpt():
return Bf16GPT(vocab_size, num_hiddens=512, num_blks=6,
rngs=nnx.Rngs(0))
model16 = make_bf16_gpt()
opt16 = nnx.Optimizer(model16, optax.adamw(1e-3), wrt=nnx.Param)
@nnx.jit
def step_bf16(model, optimizer, X, Y):
def loss_fn(model):
logits = to_bf16(model)(X) # bf16 compute copy of the step;
return optax.softmax_cross_entropy_with_integer_labels(
logits.reshape(-1, vocab_size).astype(jnp.float32), # fp32 loss
Y.reshape(-1)).mean()
loss, grads = nnx.value_and_grad(loss_fn)(model) # grads arrive fp32
optimizer.update(model, grads)
return loss
print('the receipt, again:', to_bf16(model16).token_emb(X).dtype)
plan16 = step_bf16.lower(model16, opt16, X, Y).compile().memory_analysis()
print(f'planned temp: bf16 {plan16.temp_size_in_bytes / 2**20:.0f} MiB '
f'(fp32: {compiled.memory_analysis().temp_size_in_bytes / 2**20:.0f} '
f'MiB)')
tput2 = throughput(lambda X, Y: step_bf16(model16, opt16, X, Y), stream)
assert all(p.dtype == jnp.float32 # fp32 masters intact
for p in jax.tree.leaves(nnx.state(model16, nnx.Param)))
print(f'R2 +bf16, threaded: {tput2:.0f} tokens/s ({tput2 / tput1:.2f}x)')