Skip to content

Commit 228cbd2

Browse files
committed
Add tests for chunk size tuner
1 parent 37ba7a6 commit 228cbd2

1 file changed

Lines changed: 128 additions & 1 deletion

File tree

openfold3/tests/test_utils.py

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import torch
1919

2020
from openfold3.core.model.primitives import Linear
21-
from openfold3.core.utils.chunk_utils import _chunk_slice, chunk_layer
21+
from openfold3.core.utils.chunk_utils import ChunkSizeTuner, _chunk_slice, chunk_layer
2222
from openfold3.core.utils.rigid_utils import (
2323
Rigid,
2424
Rotation,
@@ -196,3 +196,130 @@ def test_chunk_slice_dict(self):
196196
chunked_flattened = x_flat[i:j]
197197

198198
self.assertTrue(torch.all(chunked == chunked_flattened))
199+
200+
def test_chunk_size_tuner_picks_largest_viable(self):
201+
# When the cutoff sits between two power-of-2 candidates, the tuner
202+
# should pick the largest viable power of 2 at or below the cutoff.
203+
cases = [
204+
# (max_viable, expected_chunk_size)
205+
(1024, 1024),
206+
(512, 512),
207+
(511, 256),
208+
(256, 256),
209+
(255, 128),
210+
(128, 128),
211+
(4, 4),
212+
(3, 2),
213+
(1, 1),
214+
]
215+
for max_viable, expected in cases:
216+
with self.subTest(max_viable=max_viable):
217+
218+
def fn(arg, chunk_size, _max=max_viable):
219+
if chunk_size > _max:
220+
raise RuntimeError("simulated OOM")
221+
222+
result = ChunkSizeTuner._determine_favorable_chunk_size(
223+
fn, args=(None,), max_chunk_size=1024
224+
)
225+
self.assertEqual(result, expected)
226+
227+
def test_chunk_size_tuner_caps_at_max_chunk_size(self):
228+
# max_chunk_size is the config-level ceiling: even when much larger
229+
# values would fit, the tuner must not exceed it.
230+
for max_chunk_size in (4, 16, 128, 512, 1024):
231+
with self.subTest(max_chunk_size=max_chunk_size):
232+
233+
def fn(arg, chunk_size):
234+
return None # never raises -- any chunk_size "fits"
235+
236+
result = ChunkSizeTuner._determine_favorable_chunk_size(
237+
fn, args=(None,), max_chunk_size=max_chunk_size
238+
)
239+
self.assertEqual(result, max_chunk_size)
240+
241+
def test_chunk_size_tuner_caches_for_same_args(self):
242+
# Repeated calls with identical arg shapes should be a cache hit: the
243+
# fn must not be re-invoked after the initial tuning pass.
244+
tuner = ChunkSizeTuner()
245+
tested = []
246+
247+
def fn(t, chunk_size, tested=tested):
248+
tested.append(chunk_size)
249+
250+
args = (torch.zeros(2, 3, 4),)
251+
first = tuner.tune_chunk_size(
252+
representative_fn=fn, args=args, max_chunk_size=64
253+
)
254+
after_first = len(tested)
255+
second = tuner.tune_chunk_size(
256+
representative_fn=fn, args=args, max_chunk_size=64
257+
)
258+
259+
self.assertEqual(first, second)
260+
self.assertGreater(after_first, 0)
261+
self.assertEqual(
262+
len(tested),
263+
after_first,
264+
f"fn was re-invoked on cache hit: {tested[after_first:]}",
265+
)
266+
267+
def test_chunk_size_tuner_retunes_for_different_shape(self):
268+
# Different arg shapes should invalidate the cache and trigger
269+
# re-tuning.
270+
tuner = ChunkSizeTuner()
271+
tested = []
272+
273+
def fn(t, chunk_size, tested=tested):
274+
tested.append(chunk_size)
275+
if chunk_size > t.shape[-1]:
276+
raise RuntimeError("simulated OOM")
277+
278+
first = tuner.tune_chunk_size(
279+
representative_fn=fn,
280+
args=(torch.zeros(2, 3, 16),),
281+
max_chunk_size=256,
282+
)
283+
after_first = len(tested)
284+
second = tuner.tune_chunk_size(
285+
representative_fn=fn,
286+
args=(torch.zeros(2, 3, 128),),
287+
max_chunk_size=256,
288+
)
289+
290+
self.assertNotEqual(
291+
first,
292+
second,
293+
"Chunk size should have been re-tuned for new arg shape",
294+
)
295+
self.assertGreater(
296+
len(tested),
297+
after_first,
298+
"fn was not re-invoked on cache miss",
299+
)
300+
301+
def test_chunk_size_tuner_non_power_of_two_max(self):
302+
# When max_chunk_size isn't a power of 2, it should still be tried as
303+
# a candidate (and returned when viable).
304+
def fits_all(arg, chunk_size):
305+
return None
306+
307+
self.assertEqual(
308+
ChunkSizeTuner._determine_favorable_chunk_size(
309+
fits_all, args=(None,), max_chunk_size=500
310+
),
311+
500,
312+
)
313+
314+
# And when only powers of 2 below the max are viable, fall back to the
315+
# largest such power of 2.
316+
def fits_up_to_256(arg, chunk_size):
317+
if chunk_size > 256:
318+
raise RuntimeError("simulated OOM")
319+
320+
self.assertEqual(
321+
ChunkSizeTuner._determine_favorable_chunk_size(
322+
fits_up_to_256, args=(None,), max_chunk_size=500
323+
),
324+
256,
325+
)

0 commit comments

Comments
 (0)