Skip to content

Commit 8d82f17

Browse files
alfredyu-cienetcamiloCienet
authored andcommitted
Fix: Change name logging_mtc.py to validate_util.py. Since validate_util as the filename make more sense, we are validating the logs.
Fix: checkpoint_steps --> to Optional. Since this value exist but is not use in this test. Fix: Change name mx-sv --> max-sv-loc. Helps with clarity in bucket checkpoints. Fixes for code review
1 parent d157855 commit 8d82f17

3 files changed

Lines changed: 129 additions & 117 deletions

File tree

dags/orbax/maxtext_mtc_emergency_save_local.py

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import datetime
1111
from dataclasses import dataclass
1212
import posixpath
13+
from typing import Optional
1314

1415
from airflow import models
1516
from airflow.utils.task_group import TaskGroup
@@ -19,14 +20,14 @@
1920
from dags.common.vm_resource import DockerImage, XpkClusters
2021
from dags.multipod.configs import gke_config
2122
from dags.multipod.configs.common import SetupMode
22-
from dags.orbax.util import logging_mtc as log
23-
from dags.orbax.util import multi_tier_checkpoint_util as mtc
23+
from dags.orbax.util import validation_util as orbax_logging_util
24+
from dags.orbax.util import checkpoint_util as mtc
2425
from xlml.utils.xpk import BRANCH_ABHINAV_MTC
2526
from xlml.utils.gke import zone_to_region
2627

2728
SCHEDULE = "0 10 * * *" if composer_env.is_prod_env() else None
2829
DAG_TEST_NAME = "maxtext_emc_and_mtc_orbax_save_local"
29-
BASE_OUTPUT_DIRECTORY = gcs_bucket.MTC_AUTOMATION_BUCKET
30+
BASE_OUTPUT_DIR = gcs_bucket.MTC_AUTOMATION_BUCKET
3031

3132
# For this DAG, only one version of the Docker image is supported at the moment.
3233
# Other versions (e.g., "stable") may be introduced later.
@@ -79,8 +80,8 @@ def __init__(
7980
replicator_backup_time: int,
8081
step: int,
8182
local_checkpoint_step: int,
82-
checkpoint_step: int,
8383
ram_disk_size_in_mi: str,
84+
checkpoint_step: Optional[int] = None,
8485
):
8586
"""
8687
Initializes the test configurations.
@@ -127,7 +128,7 @@ def generate_workload_command(
127128
self,
128129
cp: Checkpointing,
129130
checkpoint_dir: str,
130-
out_dir: str,
131+
out_folder: str,
131132
slice_number: int,
132133
) -> str:
133134
run_time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M")
@@ -138,7 +139,7 @@ def generate_workload_command(
138139
"python3 -m MaxText.train MaxText/configs/base.yml "
139140
"remat_policy=full "
140141
"global_parameter_scale=1 "
141-
f"base_output_directory={out_dir} "
142+
f"base_output_directory={posixpath.join(BASE_OUTPUT_DIR, out_folder)} "
142143
"dataset_type=synthetic "
143144
f"steps={self.step} "
144145
"per_device_batch_size=1 "
@@ -169,21 +170,49 @@ def generate_workload_command(
169170
"orbax",
170171
],
171172
description="A DAG to run MaxText multi-tier checkpointing tests.",
172-
doc_md="",
173+
doc_md="""
174+
# Emergency Checkpoint Manager and Multi-tier Checkpoint Validation DAG
175+
176+
### Description
177+
This DAG (Directed Acyclic Graph) automates the process of validating
178+
checkpoint saving when using both **Emergency Checkpoint Manager**
179+
and **Multi-tier Checkpoint** features. The flag that controls the
180+
behaviour of using MTC or EMC is **user_replicatior**.
181+
Also the steps flag controls how many steps the job will run.
182+
183+
### Prerequisites
184+
To run this test, you need an existing cluster with the Multi-tier
185+
Checkpointing configuration enabled, as well as a bucket with HNS
186+
(Hierarchical Namespace) enabled.
187+
188+
### Procedures
189+
1. **Apply Configuration:** A Checkpoint Configuration YAML file is
190+
applied to the cluster, enabling Multi-tier Checkpoint (MTC) features.
191+
2. **Run Maxtext Jobsets:** The DAG runs a Maxtext jobset twice.
192+
One run has `replicator_enabled` set to `True` (for MTC), and the
193+
other has it set to `False` (for Emergency Checkpoint Manager).
194+
3. **Validate Checkpoints:** The DAG validates that **local checkpoints**
195+
are being saved correctly in the `local/` directory for both job runs.
196+
197+
4. The validation logic is the same for both the MTC and Emergency
198+
Checkpoint Manager job runs because their local checkpoint saving
199+
behavior is similar. This is why a single validation task is used for both.
200+
""",
173201
concurrency=2,
174202
) as dag:
203+
# TODO: In the near future this test configs will be add it in the
204+
# params {} attributes inside models.DAG
175205
test_configs = [
176206
TestConfig(
177207
cluster=XpkClusters.TPU_V5P_128_CLUSTER_ORBAX,
178208
machine_type="ct5p-hightpu-4t",
179209
accelerator="v5p-128",
180210
slices=[2],
181211
model_name="llama2-7b",
182-
short_id="max-sv",
212+
short_id="max-sv-loc",
183213
replicator_backup_time=30,
184214
step=100,
185215
local_checkpoint_step=20,
186-
checkpoint_step=25,
187216
ram_disk_size_in_mi="800000Mi",
188217
),
189218
]
@@ -200,9 +229,7 @@ def generate_workload_command(
200229
use_replicator=False,
201230
),
202231
]:
203-
folder = f"maxtext_{checkpointing.name}_orbax_save_local"
204-
output_dir = posixpath.join(BASE_OUTPUT_DIRECTORY, folder)
205-
232+
output_folder = f"maxtext_{checkpointing.name}_orbax_save_local"
206233
with TaskGroup(
207234
group_id=f"maxtext_{checkpointing.name}_orbax_save_local",
208235
) as group:
@@ -219,11 +246,11 @@ def generate_workload_command(
219246
workload_command = test_config.generate_workload_command(
220247
cp=checkpointing,
221248
checkpoint_dir=RAM_DISK,
222-
out_dir=output_dir,
249+
out_folder=output_folder,
223250
slice_number=slice_num,
224251
)
225252

226-
start_time = log.generate_timestamp()
253+
start_time = orbax_logging_util.generate_timestamp()
227254
maxtext_chkpt_run_test = gke_config.get_gke_config(
228255
num_slices=slice_num,
229256
cluster=test_config.cluster,
@@ -255,20 +282,15 @@ def generate_workload_command(
255282
skip_post_process=True,
256283
)
257284

258-
end_time = log.generate_timestamp()
285+
end_time = orbax_logging_util.generate_timestamp()
259286
vali_step = test_config.step - 1
260287
vali_step_list = [
261288
i
262289
for i in range(0, vali_step, test_config.local_checkpoint_step)
263290
]
264291
vali_step_list.append(vali_step)
265292

266-
# Here we are looking for the string '(blocking + background)'.
267-
# We will compare expected steps with the ones we found when query
268-
# this regex. Should be the same. If for some reason the restore
269-
# start from 0 this task will fail
270-
# because len(valid_step_list) != len(founded_steps)
271-
validate_log = log.validate_log_with_step(
293+
validate_log = orbax_logging_util.validate_log_with_step(
272294
project_id=test_config.cluster.project,
273295
location=zone_to_region(test_config.cluster.zone),
274296
cluster_name=test_config.cluster.name,

dags/orbax/util/multi_tier_checkpoint_util.py renamed to dags/orbax/util/checkpoint_util.py

Lines changed: 43 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from kubernetes.client.rest import ApiException
1313
from airflow.decorators import task
1414
from airflow.exceptions import AirflowFailException
15+
from http import HTTPStatus
1516

1617
from xlml.utils import gke
1718

@@ -27,7 +28,6 @@ class CheckpointConfiguration:
2728
machine_type: str
2829
ramdisk_memory: str
2930
toleration_key: str
30-
cpc_yaml_template: str
3131

3232
def __init__(
3333
self,
@@ -50,7 +50,7 @@ def __init__(
5050
machine_type (str): The machine type for the instance.
5151
ram_disk_memory_in_mi (str): The size of the RAM disk in mebibytes (Mi).
5252
The unit is in mebibytes (Mi) but the value should be passed as a
53-
string with the unit, e.g., "2G" or "2048M". Defaults to "100G"".
53+
string with the unit, e.g., "1G" or "1024Mi".
5454
toleration_key (str): The toleration key for the Kubernetes pod.
5555
Defaults to "google.com/tpu".
5656
"""
@@ -61,21 +61,33 @@ def __init__(
6161
self.machine_type = machine_type
6262
self.ramdisk_memory = ramdisk_memory_in_mi
6363
self.toleration_key = toleration_key
64-
self.cpc_yaml_template = f"""
65-
apiVersion: checkpointing.gke.io/v1
66-
kind: CheckpointConfiguration
67-
metadata:
68-
name: my-checkpointconfiguration # This name will be used for deletion
69-
spec:
70-
cloudStorageBucketName: {self.gcs_bucket}
71-
nodeSelector:
72-
node.kubernetes.io/instance-type: {self.machine_type}
73-
tolerations:
74-
- key: {self.toleration_key}
75-
operator: Exists
76-
effect: NoSchedule
77-
inMemoryVolumeSize: {self.ramdisk_memory}
78-
"""
64+
65+
def load_yaml_and_parse_body(
66+
self,
67+
) -> tuple[str, str, str, str, dict[str, any]]:
68+
"""doc"""
69+
70+
cpc_yaml_template = f"""
71+
apiVersion: checkpointing.gke.io/v1
72+
kind: CheckpointConfiguration
73+
metadata:
74+
name: my-checkpointconfiguration # This name will be used for deletion
75+
spec:
76+
cloudStorageBucketName: {self.gcs_bucket}
77+
nodeSelector:
78+
node.kubernetes.io/instance-type: {self.machine_type}
79+
tolerations:
80+
- key: {self.toleration_key}
81+
operator: Exists
82+
effect: NoSchedule
83+
inMemoryVolumeSize: {self.ramdisk_memory}
84+
"""
85+
cpc_body = yaml.safe_load(cpc_yaml_template)
86+
group, version = cpc_body.get("apiVersion").split("/", 1)
87+
plural = cpc_body.get("kind").lower() + "s"
88+
name = cpc_body.get("metadata", {}).get("name")
89+
90+
return group, version, plural, name, cpc_body
7991

8092
def create_custom_objects_api_client(self) -> k8s_client.CustomObjectsApi:
8193
"""Create a CustomObjectsApi client for the given cluster."""
@@ -90,13 +102,7 @@ def create_custom_objects_api_client(self) -> k8s_client.CustomObjectsApi:
90102
def apply_cpc(cpc: CheckpointConfiguration) -> None:
91103
"""Applies the CheckpointConfiguration to the cluster (create or replace)."""
92104
custom_api = cpc.create_custom_objects_api_client()
93-
cpc_body = yaml.safe_load(cpc.cpc_yaml_template)
94-
api_version = cpc_body.get("apiVersion")
95-
kind = cpc_body.get("kind")
96-
name = cpc_body.get("metadata", {}).get("name")
97-
98-
group, version = api_version.split("/", 1)
99-
plural = f"{kind.lower()}s"
105+
group, version, plural, name, cpc_body = cpc.load_yaml_and_parse_body()
100106

101107
try:
102108
# Here we first try to create a reasource
@@ -108,8 +114,7 @@ def apply_cpc(cpc: CheckpointConfiguration) -> None:
108114

109115
except ApiException as api_error:
110116
# If it already exists (409 Conflict), then try to replace it
111-
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
112-
if api_error.status == 409:
117+
if api_error.status == HTTPStatus.CONFLICT:
113118
logging.info(
114119
f"CheckpointConfiguration '{name}' already exists. Attempting to replace..."
115120
)
@@ -142,43 +147,31 @@ def initiate_cpc_deletion(cpc: CheckpointConfiguration) -> None:
142147
Sends the delete request for the CheckpointConfiguration.
143148
"""
144149
custom_api = cpc.create_custom_objects_api_client()
145-
cpc_body = yaml.safe_load(cpc.cpc_yaml_template)
146-
name_to_delete = cpc_body.get("metadata", {}).get("name")
150+
group, version, plural, name, _ = cpc.load_yaml_and_parse_body()
147151

148-
if not name_to_delete:
152+
if not name:
149153
logging.error(
150154
"Could not determine CheckpointConfiguration name for deletion."
151155
)
152156
raise AirflowFailException("Failed to determine CPC name for deletion.")
153157

154-
api_version = cpc_body.get("apiVersion")
155-
kind = cpc_body.get("kind")
156-
group, version = api_version.split("/", 1)
157-
plural = f"{kind.lower()}s"
158-
159158
delete_options = k8s_client.V1DeleteOptions(propagation_policy="Foreground")
160-
161159
try:
162-
logging.info(
163-
f"Attempting to delete CheckpointConfiguration: {name_to_delete}"
164-
)
160+
logging.info(f"Attempting to delete CheckpointConfiguration: {name}")
165161
custom_api.delete_cluster_custom_object(
166162
group=group,
167163
version=version,
168164
plural=plural,
169-
name=name_to_delete,
165+
name=name,
170166
body=delete_options,
171167
)
172-
logging.info(
173-
f"Delete request sent for CheckpointConfiguration '{name_to_delete}'."
174-
)
168+
logging.info(f"Delete request sent for CheckpointConfiguration '{name}'.")
175169

176170
except ApiException as e:
177171
# The resource is already gone (404), so we can exit successfully
178-
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
179-
if e.status == 404:
172+
if e.status == HTTPStatus.NOT_FOUND:
180173
logging.info(
181-
f"CheckpointConfiguration '{name_to_delete}' not found. Already deleted or never existed."
174+
f"CheckpointConfiguration '{name}' not found. Already deleted or never existed."
182175
)
183176
return
184177
else:
@@ -193,36 +186,28 @@ def wait_for_cpc_deletion(cpc: CheckpointConfiguration) -> bool:
193186
A sensor that waits for the CheckpointConfiguration to be completely deleted.
194187
"""
195188
custom_api = cpc.create_custom_objects_api_client()
196-
cpc_body = yaml.safe_load(cpc.cpc_yaml_template)
197-
name_to_delete = cpc_body.get("metadata", {}).get("name")
189+
group, version, plural, name, _ = cpc.load_yaml_and_parse_body()
198190

199-
if not name_to_delete:
191+
if not name:
200192
logging.error(
201193
"Could not determine CheckpointConfiguration name for deletion."
202194
)
203195
raise AirflowFailException("Failed to determine CPC name for deletion.")
204196

205-
api_version = cpc_body.get("apiVersion")
206-
kind = cpc_body.get("kind")
207-
group, version = api_version.split("/", 1)
208-
plural = f"{kind.lower()}s"
209-
210197
try:
211198
custom_api.get_cluster_custom_object(
212-
group=group, version=version, plural=plural, name=name_to_delete
199+
group=group, version=version, plural=plural, name=name
213200
)
214201
logging.info(
215-
f"CheckpointConfiguration '{name_to_delete}' still exists. "
202+
f"CheckpointConfiguration '{name}' still exists. "
216203
f"Polling again in 10s..."
217204
)
218205
return False
219206
except ApiException as e:
220207
# The resource is already gone (404), so we can exit successfully
221208
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
222209
if e.status == 404:
223-
logging.info(
224-
f"CheckpointConfiguration: {name_to_delete} successfully deleted"
225-
)
210+
logging.info(f"CheckpointConfiguration: {name} successfully deleted")
226211
return True # Return True to tell the sensor to succeed
227212
else:
228213
logging.error(

0 commit comments

Comments
 (0)