Skip to content

Commit 329fd7b

Browse files
committed
add util function get_offload_remat_names
1 parent 71c7ea9 commit 329fd7b

3 files changed

Lines changed: 83 additions & 29 deletions

File tree

src/maxtext/layers/decoders.py

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -411,36 +411,15 @@ def get_remat_policy(self):
411411
"kv_proj",
412412
"qkv_proj",
413413
)
414-
elif cfg.remat_policy == "qkv_proj_offloaded":
414+
elif cfg.remat_policy in ("qkv_proj_offloaded", "minimal_offloaded", "custom"):
415+
# minimal_offloaded offloads all except context. All three share a single
416+
# source of truth for their save/offload name lists (see
417+
# maxtext_utils.get_offload_remat_names) so that offloading configured via
418+
# `custom` resolves identically to the named presets.
419+
save_names, offload_names = maxtext_utils.get_offload_remat_names(cfg)
415420
policy = jax.checkpoint_policies.save_and_offload_only_these_names(
416-
names_which_can_be_saved=[],
417-
names_which_can_be_offloaded=["query_proj", "value_proj", "key_proj", "kv_proj"],
418-
offload_src="device",
419-
offload_dst="pinned_host",
420-
)
421-
elif cfg.remat_policy == "minimal_offloaded":
422-
# offload all except context
423-
policy = jax.checkpoint_policies.save_and_offload_only_these_names(
424-
names_which_can_be_saved=[],
425-
names_which_can_be_offloaded=[
426-
"query_proj",
427-
"value_proj",
428-
"key_proj",
429-
"kv_proj",
430-
"qkv_proj",
431-
"out_proj",
432-
"mlpwi_0",
433-
"mlpwi_1",
434-
"mlpwi",
435-
"mlpwo",
436-
],
437-
offload_src="device",
438-
offload_dst="pinned_host",
439-
)
440-
elif cfg.remat_policy == "custom":
441-
policy = jax.checkpoint_policies.save_and_offload_only_these_names(
442-
names_which_can_be_saved=cfg.tensors_on_device,
443-
names_which_can_be_offloaded=cfg.tensors_to_offload,
421+
names_which_can_be_saved=save_names,
422+
names_which_can_be_offloaded=offload_names,
444423
offload_src="device",
445424
offload_dst="pinned_host",
446425
)

src/maxtext/utils/maxtext_utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,39 @@ def should_prevent_cse_in_remat(config):
195195
return True
196196

197197

198+
def get_offload_remat_names(config):
199+
"""Returns ``(save_names, offload_names)`` for offloading remat policies, else ``None``.
200+
201+
Single source of truth for which checkpointed tensors a remat policy saves on
202+
device vs. offloads to pinned host. Shared by ``Decoder.get_remat_policy`` (which
203+
builds the save-and-offload policy) and by models that must convert an offload
204+
into a device-save at a scan boundary that cannot carry pinned-host residuals
205+
(e.g. Gemma4's global-layer trip-count-one scan). Keeping both callers on this
206+
helper ensures ``remat_policy=qkv_proj_offloaded`` and ``remat_policy=custom``
207+
with the same tensors marked ``offload`` resolve to identical name sets.
208+
209+
Returns ``None`` for non-offloading policies.
210+
"""
211+
if config.remat_policy == "qkv_proj_offloaded":
212+
return [], ["query_proj", "value_proj", "key_proj", "kv_proj"]
213+
if config.remat_policy == "minimal_offloaded":
214+
return [], [
215+
"query_proj",
216+
"value_proj",
217+
"key_proj",
218+
"kv_proj",
219+
"qkv_proj",
220+
"out_proj",
221+
"mlpwi_0",
222+
"mlpwi_1",
223+
"mlpwi",
224+
"mlpwo",
225+
]
226+
if config.remat_policy == "custom":
227+
return list(config.tensors_on_device or []), list(config.tensors_to_offload or [])
228+
return None
229+
230+
198231
def load_compiled(config, partial_train, state, execution_devices):
199232
"""# Loading a serialized compiled train step function."""
200233

tests/unit/maxtext_utils_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from collections.abc import Callable
1818
from dataclasses import dataclass, field
1919
import functools
20+
from types import SimpleNamespace
2021
from typing import Any, Sequence
2122
import unittest
2223
from unittest.mock import MagicMock, Mock, patch
@@ -1730,5 +1731,46 @@ def test_update_kv_caches_after_scan_invalid_type(self):
17301731
maxtext_utils.update_kv_caches_after_scan(kv_caches_tuple, returned_kv_cache, scan_length=1, block_len=2)
17311732

17321733

1734+
class TestGetOffloadRematNames(unittest.TestCase):
1735+
"""Tests for maxtext_utils.get_offload_remat_names."""
1736+
1737+
@staticmethod
1738+
def _cfg(remat_policy, tensors_on_device=None, tensors_to_offload=None):
1739+
return SimpleNamespace(
1740+
remat_policy=remat_policy,
1741+
tensors_on_device=tensors_on_device,
1742+
tensors_to_offload=tensors_to_offload,
1743+
)
1744+
1745+
def test_named_preset_matches_equivalent_custom(self):
1746+
"""qkv_proj_offloaded resolves identically to custom with the same offloads."""
1747+
preset = maxtext_utils.get_offload_remat_names(self._cfg("qkv_proj_offloaded"))
1748+
custom = maxtext_utils.get_offload_remat_names(
1749+
self._cfg("custom", tensors_on_device=[], tensors_to_offload=["query_proj", "value_proj", "key_proj", "kv_proj"])
1750+
)
1751+
self.assertEqual(preset, custom)
1752+
1753+
def test_kv_proj_retained_in_offload_presets(self):
1754+
"""Regression guard: kv_proj must stay in the offload presets (it is a real checkpoint name)."""
1755+
_, qkv_offload = maxtext_utils.get_offload_remat_names(self._cfg("qkv_proj_offloaded"))
1756+
_, minimal_offload = maxtext_utils.get_offload_remat_names(self._cfg("minimal_offloaded"))
1757+
self.assertIn("kv_proj", qkv_offload)
1758+
self.assertIn("kv_proj", minimal_offload)
1759+
1760+
def test_custom_reads_config_lists(self):
1761+
save, offload = maxtext_utils.get_offload_remat_names(
1762+
self._cfg("custom", tensors_on_device=["context"], tensors_to_offload=["out_proj"])
1763+
)
1764+
self.assertEqual(save, ["context"])
1765+
self.assertEqual(offload, ["out_proj"])
1766+
1767+
def test_custom_handles_none_lists(self):
1768+
self.assertEqual(maxtext_utils.get_offload_remat_names(self._cfg("custom")), ([], []))
1769+
1770+
def test_non_offloading_policies_return_none(self):
1771+
for policy in ("full", "minimal", "save_out_proj", "save_qkv_proj", "none"):
1772+
self.assertIsNone(maxtext_utils.get_offload_remat_names(self._cfg(policy)))
1773+
1774+
17331775
if __name__ == "__main__":
17341776
unittest.main()

0 commit comments

Comments
 (0)