1616
1717from __future__ import annotations
1818
19+ import asyncio
1920import concurrent
2021import dataclasses
2122import datetime
6162from orbax .checkpoint ._src .path import step as step_lib
6263from orbax .checkpoint ._src .path import temporary_paths
6364from orbax .checkpoint ._src .path import utils as path_utils
65+ from orbax .checkpoint .experimental .tiering_service import client as cts_client
6466from typing_extensions import Self # for Python version < 3.11
6567
6668
67-
6869PyTree = Any
6970CheckpointDirs = Tuple [str , str ]
7071SaveParams = Mapping [str , Any ]
9697MultiprocessingOptions = options_lib .MultiprocessingOptions
9798FileOptions = options_lib .FileOptions
9899
100+
99101DEFAULT_ITEM_NAME = 'default'
100102METRIC_ITEM_NAME = 'metrics'
101103METADATA_ITEM_NAME = 'metadata'
@@ -407,10 +409,11 @@ class CheckpointManagerOptions:
407409 None
408410 )
409411 prevent_write_metrics : bool = False
410- # TODO(b/428061876) Remove this option.
411412 enable_should_save_is_saving_in_progress_check : bool = True
413+ # TODO(b/428061876) Remove this option.
412414 enable_per_process_directory_creation : bool = False
413415 lightweight_initialize : bool = False
416+ tiering_client : Optional [cts_client .TieringClient ] = None
414417
415418 def __post_init__ (self ):
416419 step_name_format_single_host_load_and_broadcast = (
@@ -719,6 +722,8 @@ def __init__(
719722
720723 self ._options = options or CheckpointManagerOptions ()
721724 self ._multiprocessing_options = self ._options .multiprocessing_options
725+ self ._tiering_client = self ._options .tiering_client
726+ self ._tiering_uuids = {} # maps step (int) -> uuid (str)
722727
723728 if self ._options .enable_per_process_directory_creation :
724729 future .AwaitableSignalsContract .awaitable_signals_contract_prefix += (
@@ -957,6 +962,7 @@ def _configure_checkpointer_common(
957962 options : CheckpointManagerOptions ,
958963 use_async : bool ,
959964 ) -> Checkpointer :
965+ kwargs = {}
960966 if use_async :
961967 return async_checkpointer .AsyncCheckpointer (
962968 handler ,
@@ -965,6 +971,7 @@ def _configure_checkpointer_common(
965971 file_options = options .file_options ,
966972 checkpoint_metadata_store = self ._non_blocking_metadata_store ,
967973 temporary_path_class = options .temporary_path_class ,
974+ ** kwargs ,
968975 )
969976 else :
970977 return Checkpointer (
@@ -973,6 +980,7 @@ def _configure_checkpointer_common(
973980 file_options = options .file_options ,
974981 checkpoint_metadata_store = self ._blocking_metadata_store ,
975982 temporary_path_class = options .temporary_path_class ,
983+ ** kwargs ,
976984 )
977985
978986 def _configure_checkpointer_legacy_init (
@@ -1516,6 +1524,17 @@ def save(
15161524 args = args_lib .Composite (** args_dict )
15171525
15181526 save_directory = self ._get_write_step_directory (step , self .directory )
1527+ if self ._tiering_client is not None :
1528+ logging .info ('[CTS] Reserving path: %s' , save_directory )
1529+ uuid , lustre_path = self ._run_async (
1530+ self ._tiering_client .reserve (str (save_directory ))
1531+ )
1532+ logging .info (
1533+ '[CTS] Reserved path. UUID: %s, Lustre Path: %s' , uuid , lustre_path
1534+ )
1535+ self ._tiering_uuids [step ] = uuid
1536+ save_directory = epath .Path (lustre_path )
1537+
15191538 logging .info (
15201539 '[process=%s] Saving checkpoint at step %d' , process_index , step
15211540 )
@@ -1737,8 +1756,24 @@ def restore(
17371756 args = typing .cast (args_lib .Composite , args )
17381757
17391758 restore_directory = self ._get_read_step_directory (step , directory )
1759+ if self ._tiering_client is not None :
1760+ logging .info ('[CTS] Prefetching path: %s' , restore_directory )
1761+
1762+ async def _do_prefetch ():
1763+ fut = await self ._tiering_client .prefetch (str (restore_directory ))
1764+ return await fut
1765+
1766+ lustre_path = self ._run_async (_do_prefetch ())
1767+ logging .info ('[CTS] Prefetch complete. Lustre Path: %s' , lustre_path )
1768+ restore_directory = epath .Path (lustre_path )
1769+
17401770 step_stats .checkpointer_start_time = time .time ()
17411771 restored = self ._checkpointer .restore (restore_directory , args = args )
1772+ if self ._tiering_client is not None :
1773+ logical_restore_dir = self ._get_read_step_directory (step , directory )
1774+ self ._run_async (
1775+ self ._tiering_client .release_path (str (logical_restore_dir ))
1776+ )
17421777 step_stats .checkpointer_duration_secs = (
17431778 time .time () - step_stats .checkpointer_start_time
17441779 )
@@ -2153,6 +2188,12 @@ def _finalize(self, step: int, steps_to_remove: List[int]):
21532188 # If an error is encountered while waiting for commit futures to complete,
21542189 # we will not proceed past this point.
21552190 self ._finalize_checkpoint (step )
2191+ if self ._tiering_client is not None and step in self ._tiering_uuids :
2192+ uuid = self ._tiering_uuids [step ]
2193+ logging .info ('[CTS] Finalizing step %d, UUID: %s' , step , uuid )
2194+ self ._run_async (self ._tiering_client .finalize (uuid ))
2195+ del self ._tiering_uuids [step ]
2196+
21562197 remove_steps_start_time = time .time ()
21572198 self ._checkpoint_deleter .delete_steps (steps_to_remove )
21582199 jax .monitoring .record_event_duration_secs (
@@ -2179,6 +2220,20 @@ def _finalize(self, step: int, steps_to_remove: List[int]):
21792220 # This time is tracked for metric purposes only.
21802221 self ._last_save_time = time .time ()
21812222
2223+ def _run_async (self , coro ):
2224+
2225+ try :
2226+ loop = asyncio .get_event_loop ()
2227+ except RuntimeError :
2228+ loop = asyncio .new_event_loop ()
2229+ asyncio .set_event_loop (loop )
2230+ if loop .is_running ():
2231+ with concurrent .futures .ThreadPoolExecutor (max_workers = 1 ) as executor :
2232+ fut = executor .submit (asyncio .run , coro )
2233+ return fut .result ()
2234+ else :
2235+ return loop .run_until_complete (coro )
2236+
21822237 def close (self ):
21832238 """Waits for outstanding operations to finish and closes internal objects."""
21842239 self .wait_until_finished ()
0 commit comments