Why does my JAX + Haiku code don't run on GPU?

Viewed 1182

Good afertnoon,
I just started learning JAX and Haiku but I can't run my code on GPU. I ran my code in Google Colab and Kaggle notebooks with GPU activated but it takes more time than when the GPU is deactivated.
Moreover when I look into the GPU metrics I see that I'm only using 1% of the computation power but 90% of the GPU memory.
Here is my code (MLP for MNIST) :

def mlp(images):
  model = hk.Sequential([hk.Linear(128),
                      jax.nn.relu,
                      hk.Linear(64),
                      jax.nn.relu,
                      hk.Linear(10),
                      jax.nn.log_softmax])
  return model(images)

def loss(params, model, images, labels):
  logits = model.apply(params = params, images = images)
  labels = jax.nn.one_hot(labels, num_classes = 10)
  cross_entropy_loss = -jnp.sum(labels*logits)/len(labels)
  return cross_entropy_loss
# Initializing the MLP model
mlp = hk.without_apply_rng(hk.transform(mlp))
params = mlp.init(rng = jax.random.PRNGKey(0),
                  images = next(iter(train_loader))[0])

# Initializing the optimizer
opt = optax.adam(1e-4)
opt_state = opt.init(params = params)
@jax.jit
def update(params, opt_state, images, labels):
  grads = jax.grad(loss)(params,mlp,images,labels)
  updates, opt_state = opt.update(grads, opt_state)
  return optax.apply_updates(params, updates), opt_state

def train(params, opt_state, epochs):
  for epoch in range(epochs):
    for batch_idx, (images, labels) in enumerate(train_loader):
      if batch_idx == 0:
        print(f"Epoch {epoch} : loss = {loss(params,mlp,images,labels)}")
      params, opt_state = update(params, opt_state, images,labels)

%time train(params, opt_state, epochs = 10)

If you have any idea of what I'm doing wrong you would help me a lot. Thank you.

2 Answers

This question is difficult to answer because it's not clear what epochs or train_loader contain. But a general response:

  • By default, JAX will always pre-allocate 90% of the GPU memory at startup (see GPU Memory Allocation) so this is not indicative of how much memory your computation is consuming
  • Python control flow such as for loops will be executed on your CPU, dispatching the inner computations to GPU one-by-one. This dispatch will be asynchronous when possible (see Asynchronous Dispatch), unless the program encounters a blocking call such as printing of a computation result.

Given these facts, I suspect the reason your code runs slowly and doesn't saturate the GPU is because each update operation is a very small computation, such that the dispatch overhead within each loop dominates. Often that dispatch overhead is due to device transfer (i.e. if the contents of epochs or train_loader do not already live on the GPU). Due to asynchronous dispatch, the cumulative effect of this dispatch overhead would likely be less of a problem if you avoided blocking calls such as printing the loss function mid-loop. A better solution may be to push the loops into XLA (either by JIT-compiling the entire training procedure if the number of loops is small, or using lax control flow if the number of loops is large), but this depends on the size of epochs and train_loader and whether the data lives on-device or needs to be transferred.

I met this problem days ago, my process running on cpu but using so much GPU memory. The reason mitght be you installed false jax and jaxlib version which is cpu only, you can fix this problem by install gpu version like follow:

   pip install --upgrade jax==0.2.3 jaxlib==0.1.69+cuda111 -f https://storage.googleapis.com/jax-releases/jax_releases.html

attention!!! you'd better check the cuda driver version of you server/computer besides, you can browse https://github.com/google/jax

and you will know more details

Related