Consider following code, which is partial code of model training:
from functools import partial
import mlx.core as mx
import mlx.nn as nn
from mlx.utils import tree_map
model = nn.Linear(1024, 3072, bias=True)
params = model.trainable_parameters()
mx.eval(params)
state = [mx.random.state]
@partial(mx.compile, inputs=state, outputs=state)
def step(x, params):
model.update(tree_map(lambda x: x.astype(mx.bfloat16), params))
return model(x.astype(mx.float16))
x = mx.random.uniform(shape=(4,512,1024), dtype=mx.bfloat16)
y = step(x, params)
mx.eval(y)
Which converts the Linear module's weights and bias from f32 to bf16, broadcasts the bias to (4,512,3072), and then calls addmm. And after JIT compilation the astype and broadcast would be fused together.
The problem is that with JIT compilation we would lose some optimization opportunities: if we remove the mx.compile line, the AddMM::eval_gpu would receive the bias input with data size of 3072, from which we know it is a bias vector to be added to the last dim of output, and can use a faster kernel (#2569). However with JIT compilation the bias input's data size becomes 6291456 (4 *512 *3072) because of op fusion, and we can no longer take the same optimization.
I can think of 2 solutions:
- Add bias epilogue as part of
Matmul primitive and make addmm redirect to it. The downside is it would be relatively large change and we would also need to apply the same optimization to Metal kernels.
- Move the broardcasting and reshaping of bias input from
addmm op to AddMM::eval_gpu, the change would be minimal and could also be a bit ugly.
And with both solutions I think the vjp/jvp would be a bit tricky to get right.
Consider following code, which is partial code of model training:
Which converts the Linear module's weights and bias from f32 to bf16, broadcasts the bias to (4,512,3072), and then calls
addmm. And after JIT compilation theastypeandbroadcastwould be fused together.The problem is that with JIT compilation we would lose some optimization opportunities: if we remove the
mx.compileline, theAddMM::eval_gpuwould receive the bias input with data size of 3072, from which we know it is a bias vector to be added to the last dim of output, and can use a faster kernel (#2569). However with JIT compilation the bias input's data size becomes 6291456 (4 *512 *3072) because of op fusion, and we can no longer take the same optimization.I can think of 2 solutions:
Matmulprimitive and makeaddmmredirect to it. The downside is it would be relatively large change and we would also need to apply the same optimization to Metal kernels.addmmop toAddMM::eval_gpu, the change would be minimal and could also be a bit ugly.And with both solutions I think the
vjp/jvpwould be a bit tricky to get right.