google / google/flax

Using `vmap` on the loss function change results

Open
#3,973 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Jupyter Notebook
Stars
7.3k
Forks
833
Avg merge
5h 11m
Merged PRs (30d)
5

Description

Hello, I am not sure whether this should be written as a JAX or Flax issue.

Here the simplified description of the training steps in my context (see the example below for more detail):
- I apply the model on the whole input data
- I select some indices of the obtained predictions
- These predictions are used along with the corresponding label for the loss computation

The reason I do so is that I am working with a graph neural network (for a node regression). I then get a prediction for all nodes, but I split nodes into batches to compute the average loss/gradient among batches.

My loss function takes as an input the indices to be used for the computation. Instead of calling the function for each batch, I wanted to use `vmap` on the loss function which I expect to be faster. It however seems that it leads to different results. Here is an example of the issue:

```python
import flax.linen as nn
import jax
import jax.numpy as N
import jax.random as R
import jax.tree_util as T
import optax

key = R.PRNGKey(0)
rng1, rng2, rng3, rng4 = R.split(key, 4)

X = R.normal(rng1, (1000, 20)) # Input: x
Y = R.normal(rng2, (1000, 1)) # Label: x 1
# Batches of indices: x
batches = R.randint(rng3, (10, 500), 0, 1000)

model = nn.Dense(1)
model_vars = model.init(rng4, N.ones((1, 20)))

# Method 1: with vmap on loss function
@jax.jit
def train_step1(variables, x, y, bs):

def loss_fn(v, i):
p = model.apply(v, x)
loss = N.mean(optax.sigmoid_binary_cross_entropy(p[i], y[i]))
return loss

v_loss_grad_fn = jax.vmap(jax.value_and_grad(loss_fn), in_axes=(None, 0), out_axes=0)
loss, grads = v_loss_grad_fn(variables, bs)
loss = N.mean(loss, axis=0)
grads = T.tree_map(lambda g: N.mean(g, axis=0), grads)
variables = T.tree_map(lambda v, g: v - 0.01*g, variables, grads)
return variables, loss

# Method 2: without vmap on loss function
@jax.jit
def train_step2(variables, x, y, bs):

def loss_fn(v, i):
p = model.apply(v, x)
loss = N.mean(optax.sigmoid_binary_cross_entropy(p[i], y[i]))
return loss

loss_grad_fn = jax.value_and_grad(loss_fn)
l_list = []
g_list = []
for i in range(10): # Number of batches
idx = bs[i]
loss, grads = loss_grad_fn(variables, idx)
l_list.append(loss)
g_list.append(grads)
loss = N.mean(N.stack(l_list, axis=0), axis=0)
grads = T.tree_map(lambda *g: N.mean(N.stack(g, axis=0), axis=0), *g_list)
variables = T.tree_map(lambda v, g: v - 0.01*g, variables, grads)
return variables, loss

# Warming up
train_step1(model_vars, N.ones((1, 20)), N.ones((1, 1)), N.asarray([[0]]))
train_step2(model_vars, N.ones((1, 20)), N.ones((1, 1)), N.asarray([[0]]))

v1 = model_vars
v2 = model_vars

# Comparing results before trainings
p1 = model.apply(v1, X)
p2 = model.apply(v2, X)
print("Same results before trainings:", N.all(p1 == p2))

# Training
for _ in range(1000):
v1, l1 = train_step1(v1, X, Y, batches)
v2, l2 = train_step2(v2, X, Y, batches)

# Comparing results after both trainings
p1 = model.apply(v1, X)
p2 = model.apply(v2, X)
print("Same results after trainings:", N.all(p1 == p2))
print("Same losses after trainings", l1 == l2)
```

### System information
- OS Platform and Distribution: Windows 11
- Flax, jax, jaxlib versions: flax (0.8.0) / jax (0.4.28) / jaxlib (0.4.28)
- Python version: 3.11.5

Maybe I misunderstood how `vmap` is working, but I think that both methods described above should have the same behaviour. So I don't understand why they both lead to different results. Are those due to some approximation during the computation (because losses are still identical at last iteration)?
Thank you for your help.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.