Skip to content

Commit ff5e437

Browse files
timblakelycopybara-github
authored andcommitted
Fix batch size on tpu7x to be expected size.
PiperOrigin-RevId: 901363897
1 parent 9022306 commit ff5e437

4 files changed

Lines changed: 95 additions & 18 deletions

File tree

ffn/jax/accelerator_utils.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright 2026 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# ==============================================================================
15+
"""Accelerator topology and batch size utilities."""
16+
17+
import dataclasses
18+
import jax
19+
20+
21+
@dataclasses.dataclass(frozen=True)
22+
class AcceleratorTopologyInfo:
23+
global_batch_size: int
24+
host_batch_size: int
25+
cores_per_chip: int
26+
num_chips: int
27+
local_chips: int
28+
29+
30+
def get_accelerator_topology_info(
31+
per_device_batch_size: int,
32+
) -> AcceleratorTopologyInfo:
33+
"""Computes topology info and batch sizes.
34+
35+
Args:
36+
per_device_batch_size: Desired batch size per device (core).
37+
38+
Returns:
39+
AcceleratorTopologyInfo with topology and batch size info.
40+
"""
41+
devices = jax.local_devices()
42+
if devices and hasattr(devices[0], 'core_on_chip'):
43+
cores_per_chip = max(d.core_on_chip for d in devices) + 1
44+
else:
45+
cores_per_chip = 1
46+
47+
num_chips = jax.device_count() // cores_per_chip
48+
local_chips = jax.local_device_count() // cores_per_chip
49+
50+
global_batch_size = per_device_batch_size * num_chips
51+
host_batch_size = per_device_batch_size * local_chips
52+
53+
return AcceleratorTopologyInfo(
54+
global_batch_size=global_batch_size,
55+
host_batch_size=host_batch_size,
56+
cores_per_chip=cores_per_chip,
57+
num_chips=num_chips,
58+
local_chips=local_chips,
59+
)

ffn/jax/input_pipeline.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from connectomics.common import bounding_box
2525
from connectomics.common import utils
2626
from ffn.input import volume
27+
from ffn.jax import accelerator_utils
2728
from ffn.jax import tracker
2829
from ffn.training import examples
2930
from ffn.training import inputs
@@ -151,7 +152,10 @@ def _add_ffn_data(ex: volume.Example) -> volume.Example:
151152
patches=(emt - config.image_mean) / config.image_stddev,
152153
)
153154

154-
batch_size = config.per_device_batch_size * jax.local_device_count()
155+
topo_info = accelerator_utils.get_accelerator_topology_info(
156+
config.per_device_batch_size
157+
)
158+
batch_size = topo_info.host_batch_size
155159

156160
if cfg.sampling.vsi_coords:
157161
num_examples = getattr(config, 'train_num_coords', 100_000_000)

ffn/jax/train.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from clu import parameter_overview
2828
from connectomics.jax import training
2929
from etils import epath
30+
from ffn.jax import accelerator_utils
3031
from ffn.jax import input_pipeline
3132
from ffn.jax import tracker
3233
from ffn.training import examples
@@ -521,13 +522,27 @@ def train_and_evaluate(
521522
train_iter = checkpointed_state['train_iter']
522523
initial_step = int(state.step) + 1
523524

524-
global_batch_size = config.per_device_batch_size * jax.device_count()
525-
host_batch_size = config.per_device_batch_size * jax.local_device_count()
525+
# Compute batch sizes based on chip count, not core count. Multi-core
526+
# chips (e.g., tpu7x with 2 cores/chip) expose each core as a
527+
# separate JAX device, which would accidentally double the batch size.
528+
topo_info = accelerator_utils.get_accelerator_topology_info(
529+
config.per_device_batch_size
530+
)
531+
532+
logging.info(
533+
'cores_per_chip=%d, num_chips=%d (global), local_chips=%d, '
534+
'global_batch_size=%d, host_batch_size=%d',
535+
topo_info.cores_per_chip,
536+
topo_info.num_chips,
537+
topo_info.local_chips,
538+
topo_info.global_batch_size,
539+
topo_info.host_batch_size,
540+
)
526541

527542
# Upper bound. The real number will be lower as not all steps are
528543
# taken for every example.
529544
steps_per_epoch = (
530-
num_total_examples // global_batch_size * (len(fov_shifts) + 1)
545+
num_total_examples // topo_info.global_batch_size * (len(fov_shifts) + 1)
531546
)
532547
num_train_steps = steps_per_epoch * config.num_epochs
533548
logging.info(
@@ -593,7 +608,9 @@ def train_fn(state, batch, loss_scale):
593608
logging.info('Starting training loop at step %d.', initial_step)
594609
hooks = []
595610
report_progress = training.ReportProgress(
596-
global_batch_size, num_train_steps=num_train_steps, writer=writer
611+
topo_info.global_batch_size,
612+
num_train_steps=num_train_steps,
613+
writer=writer,
597614
)
598615
if jax.process_index() == 0:
599616
hooks.append(report_progress)
@@ -608,7 +625,7 @@ def train_fn(state, batch, loss_scale):
608625
info,
609626
config,
610627
seed_shape=tuple(train_canvas_size(info, config).tolist()[::-1]),
611-
batch_size=host_batch_size,
628+
batch_size=topo_info.host_batch_size,
612629
jmp_policy=jmp_policy,
613630
)
614631

@@ -623,9 +640,9 @@ def _reshape(x):
623640
per_device_data = np.split(x, len(mesh.local_devices), axis=0)
624641

625642
on_dev = jax.device_put(per_device_data, mesh.local_devices)
626-
global_shape = (
627-
len(batch_sharding.device_set) * config.per_device_batch_size,
628-
) + per_device_data[0].shape[1:]
643+
global_shape = (topo_info.global_batch_size,) + per_device_data[0].shape[
644+
1:
645+
]
629646
return jax.make_array_from_single_device_arrays(
630647
global_shape, batch_sharding, on_dev
631648
)
@@ -672,16 +689,12 @@ def _reshape(x):
672689
)
673690

674691
with training.MeasureTime(timings, 'update_seed'):
675-
host_local_seeds = [] # [b, z, y, x, 1] * num_devices
676-
dev_to_slice = batch_sharding.addressable_devices_indices_map(
677-
updated_seed.shape
678-
)
679-
680692
# Ensure device order is the same as that used to build the
681693
# global array in postprocess_batch().
682-
assert list(dev_to_slice.keys()) == list(mesh.local_devices)
683-
for slc in dev_to_slice.values():
684-
host_local_seeds.append(updated_seed[slc])
694+
shard_by_device = {
695+
s.device: s.data for s in updated_seed.addressable_shards
696+
}
697+
host_local_seeds = [shard_by_device[d] for d in mesh.local_devices]
685698

686699
batch_iter.update_seeds(host_local_seeds)
687700

ffn/training/mask.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def crop(tensor, offset, crop_shape):
4545
off_y = shape[-3] // 2 - crop_shape[1] // 2 + offset[1]
4646
off_x = shape[-2] // 2 - crop_shape[0] // 2 + offset[0]
4747

48-
# Note: native indexing syntax not used below due to TPU compatibility.
48+
# Note: native indexing syntax not used below due to accelerator
49+
# compatibility.
4950
if len(offset) == 2:
5051
cropped = tf.slice(
5152
tensor,

0 commit comments

Comments
 (0)