google / google/grain

Advice on using a JIT function inside a __getitem__ or transform?

Open
#495 3 comments 1 reaction 0 assignees View on GitHub
type:performance
Dominant language
Python
Stars
779
Forks
86
Avg merge
2d 6h
Merged PRs (30d)
6

Description

I want to put JAX jitted batched data augmentation inside my grain dataloader. I'm currently pretending this augmentation is a jitted batch inference of a Flax model. With `worker_count=0`, it smoothly processes about 390-400 batches per second. However, with `worker_count=1` it becomes more sporadic and slower. I suppose having `worker_count=0` is acceptable, and I can use this to feed a model for training. However, it might be useful to have a spare batch ready with `worker_count=1` and `worker_buffer_size=2`, assuming my GPU has the memory for *two* of the jitted functions to be run in parallel. In this case it does, and I still see issues even when I make the Flax model much smaller. What is your advice?

```python
from typing import SupportsIndex

import jax
import jax.numpy as jnp
import jax.random as random

import flax.linen as nn

from tqdm import tqdm
from absl import logging

import grain.python as grain

class Model(nn.Module):

n_layers: int = 10
features: int = 10

@nn.compact
def __call__(self, x):

for _ in range(self.n_layers):
x = nn.Dense(features=self.features)(x)
x = nn.relu(x)

return x

Model = nn.vmap(Model, variable_axes={'params': None}, split_rngs={'params': False})

B = 4
IN_FEATURES = 100
N_LAYERS = 20
FEATURES = 20

dummy_input = jnp.zeros(shape=(B, IN_FEATURES))

model = Model(n_layers=N_LAYERS, features=FEATURES)

params = model.init({'params': random.key(0), 'rng_stream': random.key(1)}, dummy_input)['params']

print(model.tabulate({'params': random.key(0), 'rng_stream': random.key(1)}, dummy_input))

@jax.jit
def jit_batch_inference(x):
return model.apply({'params': params}, x)

class DataSimpleSource(grain.RandomAccessDataSource):

def __init__(self, num_steps):

self._num_steps = num_steps

def __len__(self) -> int:
return self._num_steps

def __getitem__(self, record_key: SupportsIndex):
record_key = int(record_key)
return random.uniform(random.key(record_key), shape=(IN_FEATURES,))

class JITBatchTransform(grain.MapTransform):

def map(self, batch: jnp.ndarray):
assert batch.ndim == 2
assert batch.shape == (B, IN_FEATURES)

x = jit_batch_inference(batch)
return x

if __name__ == '__main__':

logging.set_verbosity(logging.INFO)

num_steps = 1000000
worker_count = 0 # todo:
worker_buffer_size = 1 # todo:

datasource = DataSimpleSource(num_steps=num_steps)

index_sampler = grain.IndexSampler(
num_records=len(datasource),
num_epochs=1,
shard_options=grain.NoSharding(),
shuffle=False,
seed=0,
)

pygrain_ops = [
# grain.BatchOperation(batch_size=B, drop_remainder=True), # deprecated alternative to grain.Batch
grain.Batch(batch_size=B, drop_remainder=True),
JITBatchTransform(),
]

batched_dataloader = grain.DataLoader(
data_source=datasource,
sampler=index_sampler,
operations=pygrain_ops,
worker_count=worker_count,
worker_buffer_size=worker_buffer_size,
enable_profiling=False, # todo:
)

for x in tqdm(batched_dataloader, total=num_steps, desc='Grain Dataset'):
pass
```

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.