Skip to content

Commit 72de49b

Browse files
authored
Merge branch 'apple:main' into jax_py3.12_nightly
2 parents e2c5f23 + 723bfb1 commit 72de49b

7 files changed

Lines changed: 124 additions & 83 deletions

File tree

axlearn/cloud/common/bundler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -629,12 +629,12 @@ def install_command(self, bundle_id: str) -> str:
629629
)
630630
pip_install_cmd = (
631631
f"if [[ -f {config.CONFIG_DIR}/requirements.txt ]]; then "
632-
f"python3 -m pip install -r {config.CONFIG_DIR}/requirements.txt; "
633-
"else python3 -m pip install .; fi"
632+
f"python3 -m uv pip install -r {config.CONFIG_DIR}/requirements.txt; "
633+
"else python3 -m uv pip install .; fi"
634634
)
635635
return (
636636
f"{copy_cmd} && tar -xzf axlearn.tar.gz && "
637-
f"python3 -m pip install --upgrade pip && {pip_install_cmd}"
637+
f"python3 -m uv pip install --upgrade pip && {pip_install_cmd}"
638638
)
639639

640640

axlearn/cloud/gcp/jobset_utils.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,56 @@ def _build_shared_memory_volumes(self, shared_memory: str) -> Nested[Any]:
587587
"emptyDir": {"medium": "Memory", "sizeLimit": shared_memory},
588588
}
589589

590+
def set_up_gcsfuse(self, cfg: "TPUJobBuilder.Config", volumes: list, annotations: dict) -> None:
591+
"""Sets up GCSFuse volumes and annotations.
592+
593+
Args:
594+
cfg: The TPUJobBuilder configuration.
595+
volumes: The list of volumes to append GCSFuse volumes to.
596+
annotations: The dictionary of annotations to update with GCSFuse settings.
597+
"""
598+
# Increases the shared memory volumes when enabled gcsfuse. This is useful when grain
599+
# prefetch is enabled.
600+
volumes.append(self._build_shared_memory_volumes(cfg.gcsfuse_mount.shared_memory))
601+
# Mount a GCS bucket as a volume.
602+
annotations.update(
603+
{
604+
"gke-gcsfuse/volumes": "true",
605+
"gke-gcsfuse/cpu-request": cfg.gcsfuse_mount.cpu,
606+
"gke-gcsfuse/memory-request": cfg.gcsfuse_mount.memory,
607+
"gke-gcsfuse/ephemeral-storage-request": cfg.gcsfuse_mount.ephemeral_gb,
608+
# GCSFuse will set limits=request if we only set requests:
609+
# https://github.com/GoogleCloudPlatform/gcs-fuse-csi-driver/blob/main/pkg/webhook/config.go#L110
610+
"gke-gcsfuse/cpu-limit": "0",
611+
"gke-gcsfuse/memory-limit": "0",
612+
"gke-gcsfuse/ephemeral-storage-limit": "0",
613+
}
614+
)
615+
# Parse GCSFuseMount path into bucket, prefix.
616+
parsed = urlparse(cfg.gcsfuse_mount.gcs_path)
617+
# https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/cloud-storage-fuse-csi-driver#consume-ephemeral-volume-pod
618+
# Caveat: --implicit-dirs might have negative impacts on i/o performance. See
619+
# https://github.com/googlecloudplatform/gcsfuse/blob/master/docs/semantics.md .
620+
# See https://cloud.google.com/storage/docs/cloud-storage-fuse/config-file for more
621+
# details about mountOptions.
622+
# The mountOptions are following https://github.com/AI-Hypercomputer/maxtext/pull/1070.
623+
volumes.append(
624+
dict(
625+
name=cfg.gcsfuse_mount.name,
626+
csi=dict(
627+
driver="gcsfuse.csi.storage.gke.io",
628+
readOnly=cfg.gcsfuse_mount.read_only,
629+
volumeAttributes=dict(
630+
bucketName=parsed.netloc,
631+
# pylint: disable=line-too-long
632+
mountOptions=f"only-dir={parsed.path.lstrip('/')},implicit-dirs,metadata-cache:ttl-secs:-1,metadata-cache:stat-cache-max-size-mb:-1,metadata-cache:type-cache-max-size-mb:-1,kernel-list-cache-ttl-secs=-1,gcs-connection:http-client-timeout:{cfg.gcsfuse_mount.http_client_timeout}",
633+
gcsfuseMetadataPrefetchOnMount="false", # Improves first-time read.
634+
disableMetrics="false", # Enables GCSFuse metrics by default.
635+
),
636+
),
637+
)
638+
)
639+
590640
def _build_pod(self) -> Nested[Any]:
591641
"""Builds a config for a single Pod, which is a set of containers.
592642
@@ -601,47 +651,7 @@ def _build_pod(self) -> Nested[Any]:
601651

602652
volumes.append(dict(name="shared-output", emptyDir={}))
603653
if cfg.gcsfuse_mount:
604-
# Increases the shared memory volumes when enabled gcsfuse. This is useful when grain
605-
# prefetch is enabled.
606-
volumes.append(self._build_shared_memory_volumes(cfg.gcsfuse_mount.shared_memory))
607-
# Mount a GCS bucket as a volume.
608-
annotations.update(
609-
{
610-
"gke-gcsfuse/volumes": "true",
611-
"gke-gcsfuse/cpu-request": cfg.gcsfuse_mount.cpu,
612-
"gke-gcsfuse/memory-request": cfg.gcsfuse_mount.memory,
613-
"gke-gcsfuse/ephemeral-storage-request": cfg.gcsfuse_mount.ephemeral_gb,
614-
# GCSFuse will set limits=request if we only set requests:
615-
# https://github.com/GoogleCloudPlatform/gcs-fuse-csi-driver/blob/main/pkg/webhook/config.go#L110
616-
"gke-gcsfuse/cpu-limit": "0",
617-
"gke-gcsfuse/memory-limit": "0",
618-
"gke-gcsfuse/ephemeral-storage-limit": "0",
619-
}
620-
)
621-
# Parse GCSFuseMount path into bucket, prefix.
622-
parsed = urlparse(cfg.gcsfuse_mount.gcs_path)
623-
# https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/cloud-storage-fuse-csi-driver#consume-ephemeral-volume-pod
624-
# Caveat: --implicit-dirs might have negative impacts on i/o performance. See
625-
# https://github.com/googlecloudplatform/gcsfuse/blob/master/docs/semantics.md .
626-
# See https://cloud.google.com/storage/docs/cloud-storage-fuse/config-file for more
627-
# details about mountOptions.
628-
# The mountOptions are following https://github.com/AI-Hypercomputer/maxtext/pull/1070.
629-
volumes.append(
630-
dict(
631-
name=cfg.gcsfuse_mount.name,
632-
csi=dict(
633-
driver="gcsfuse.csi.storage.gke.io",
634-
readOnly=cfg.gcsfuse_mount.read_only,
635-
volumeAttributes=dict(
636-
bucketName=parsed.netloc,
637-
# pylint: disable=line-too-long
638-
mountOptions=f"only-dir={parsed.path.lstrip('/')},implicit-dirs,metadata-cache:ttl-secs:-1,metadata-cache:stat-cache-max-size-mb:-1,metadata-cache:type-cache-max-size-mb:-1,kernel-list-cache-ttl-secs=-1,gcs-connection:http-client-timeout:{cfg.gcsfuse_mount.http_client_timeout}",
639-
gcsfuseMetadataPrefetchOnMount="false", # Improves first-time read.
640-
disableMetrics="false", # Enables GCSFuse metrics by default.
641-
),
642-
),
643-
)
644-
)
654+
self.set_up_gcsfuse(cfg, volumes, annotations)
645655
if cfg.host_mounts:
646656
for mount in cfg.host_mounts:
647657
volumes.append(

axlearn/cloud/gcp/pathways_utils.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@
6363
# The container name of pathways proxy.
6464
_PATHWAYS_PROXY_CONTAINER_NAME = "pathways-proxy"
6565
# The k8s replicatedJob name for pathways-head pods.
66-
_PATHWAYS_HEAD_REPLICATED_JOB_NAME = "pathways-head"
66+
_PATHWAYS_HEAD_REPLICATED_JOB_NAME = "pwhd"
6767
# The k8s replicatedJob name for pathways-worker pods.
68-
_PATHWAYS_WORKER_REPLICATED_JOB_NAME = "pathways-worker"
68+
_PATHWAYS_WORKER_REPLICATED_JOB_NAME = "pwwk"
6969

7070
# Add node-selector for cpu workload to avoid sharing nodes with system services.
7171
_PATHWAYS_HEAD_NODE_POOL_SELECTOR_KEY = "axlearn/nodepool_type"
@@ -439,17 +439,12 @@ def _build_pathways_head_pod(self) -> Nested[Any]:
439439
labels.update({BASTION_JOB_VERSION_LABEL: os.environ.get(BASTION_JOB_VERSION_ENV_VAR)})
440440

441441
volumes.append(dict(name="shared-output", emptyDir={}))
442-
volumes.append(dict(name="shared-memory", emptyDir=dict(medium="Memory")))
443-
444442
if cfg.gcsfuse_mount:
445-
annotations.update(
446-
{
447-
"gke-gcsfuse/volumes": "true",
448-
"gke-gcsfuse/cpu-limit": cfg.gcsfuse_mount.cpu,
449-
"gke-gcsfuse/memory-limit": cfg.gcsfuse_mount.memory,
450-
"gke-gcsfuse/ephemeral-storage-limit": cfg.gcsfuse_mount.ephemeral_gb,
451-
}
452-
)
443+
self._inner.set_up_gcsfuse(cfg, volumes, annotations)
444+
else:
445+
# gcsfuse mounts shared-memory. To avoid double mounting, we only mount
446+
# shared-memory explicitly when gcsfuse_mount is not enabled.
447+
volumes.append(dict(name="shared-memory", emptyDir=dict(medium="Memory")))
453448

454449
node_selector = {
455450
_PATHWAYS_HEAD_NODE_POOL_SELECTOR_KEY: _PATHWAYS_HEAD_NODE_POOL_SELECTOR_VALUE,

axlearn/cloud/gcp/pathways_utils_test.py

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -299,30 +299,65 @@ def test_validate_head_name(self):
299299
with self._job_config(CloudBuildBundler) as (cfg, bundler_cfg):
300300
cfg.inner.set(
301301
project="test-project",
302-
name="a" * 40,
302+
name="a" * 49,
303303
command="test_command",
304304
output_dir="FAKE",
305-
).instantiate(bundler=bundler_cfg.instantiate())
305+
)
306306

307307
with self.assertRaisesRegex(
308-
ValueError, r"pathways-head-1-1-abcde exceeds max \(63\) by 1 chars."
308+
ValueError, r"pwhd-1-1-abcde exceeds max \(63\) by 1 chars."
309309
):
310310
_ = cfg.instantiate(bundler=bundler_cfg.instantiate())
311311

312312
def test_validate_worker_name(self):
313313
with self._job_config(CloudBuildBundler) as (cfg, bundler_cfg):
314314
cfg.inner.set(
315315
project="test-project",
316-
name="a" * 38,
316+
name="a" * 49,
317317
command="test_command",
318318
output_dir="FAKE",
319-
).instantiate(bundler=bundler_cfg.instantiate())
319+
)
320320

321+
# Both head and worker have the same name length, so head validation runs first
321322
with self.assertRaisesRegex(
322-
ValueError, r"pathways-worker-1-2-abcde exceeds max \(63\) by 1 chars."
323+
ValueError, r"pwhd-1-1-abcde exceeds max \(63\) by 1 chars."
323324
):
324325
_ = cfg.instantiate(bundler=bundler_cfg.instantiate())
325326

327+
def test_build_pathways_head_pod_with_gcsfuse(self):
328+
with (
329+
self._job_config(
330+
CloudBuildBundler,
331+
gcsfuse_mount_spec=["mount_path=/tmp/gcsfuse", "gcs_path=gs://test-bucket/path"],
332+
) as (cfg, bundler_cfg),
333+
):
334+
cfg.inner.set(
335+
project="test-project",
336+
name="test",
337+
command="test_command",
338+
output_dir="FAKE",
339+
).instantiate(bundler=bundler_cfg.instantiate())
340+
341+
builder = cfg.instantiate(bundler=bundler_cfg.instantiate())
342+
# pylint: disable-next=protected-access
343+
pod = builder._build_pathways_head_pod()
344+
pod_spec = pod["spec"]
345+
annotations = pod["metadata"]["annotations"]
346+
347+
# Verify gcsfuse annotations are set correctly
348+
self.assertEqual(annotations.get("gke-gcsfuse/volumes"), "true")
349+
self.assertIn("gke-gcsfuse/cpu-request", annotations)
350+
self.assertIn("gke-gcsfuse/memory-request", annotations)
351+
self.assertIn("gke-gcsfuse/ephemeral-storage-request", annotations)
352+
# Verify limit annotations are set to "0"
353+
self.assertEqual(annotations.get("gke-gcsfuse/cpu-limit"), "0")
354+
self.assertEqual(annotations.get("gke-gcsfuse/memory-limit"), "0")
355+
self.assertEqual(annotations.get("gke-gcsfuse/ephemeral-storage-limit"), "0")
356+
357+
# Verify shared memory volume is present
358+
volume_names = [v["name"] for v in pod_spec["volumes"]]
359+
self.assertIn("shared-memory", volume_names)
360+
326361

327362
class PathwaysMultiheadReplicatedJobTest(TestCase):
328363
"""Tests PathwaysMultiheadReplicatedJob."""
@@ -383,37 +418,33 @@ def test_replicated_job(self, num_replicas):
383418
annotations.get("axlearn/replicatedjob-load-balancer-port", {}),
384419
)
385420

386-
if replicated_job_name.startswith("pathways-head"):
421+
if replicated_job_name.startswith("pwhd"):
387422
self.assertEqual(replicated_job["replicas"], num_replicas)
388-
elif replicated_job_name.startswith("pathways-worker"):
423+
elif replicated_job_name.startswith("pwwk"):
389424
self.assertEqual(replicated_job["replicas"], 1)
390425

391426
def test_validate_head_name(self):
392427
with self._job_config(CloudBuildBundler, 2) as (cfg, bundler_cfg):
393428
cfg.inner.set(
394429
project="test-project",
395-
name="a" * 40,
430+
name="a" * 49,
396431
command="test_command",
397432
output_dir="FAKE",
398-
).instantiate(bundler=bundler_cfg.instantiate())
433+
)
399434

400-
with self.assertRaisesRegex(
401-
ValueError, r"pathways-head-1-1-abcde exceeds max \(63\) by 1 chars."
402-
):
435+
with self.assertRaisesRegex(ValueError, r"pwhd-1-1-abcde exceeds max \(63\) by 1 chars."):
403436
_ = cfg.instantiate(bundler=bundler_cfg.instantiate())
404437

405438
def test_validate_worker_name(self):
406439
with self._job_config(CloudBuildBundler, 2) as (cfg, bundler_cfg):
407440
cfg.inner.set(
408441
project="test-project",
409-
name="a" * 36,
442+
name="a" * 47,
410443
command="test_command",
411444
output_dir="FAKE",
412-
).instantiate(bundler=bundler_cfg.instantiate())
445+
)
413446

414-
with self.assertRaisesRegex(
415-
ValueError, r"pathways-worker-2-0-2-abcde exceeds max \(63\) by 1 chars."
416-
):
447+
with self.assertRaisesRegex(ValueError, r"pwwk-2-0-2-abcde exceeds max \(63\) by 1 chars."):
417448
_ = cfg.instantiate(bundler=bundler_cfg.instantiate())
418449

419450

axlearn/cloud/gcp/scripts/start_vm.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#
77
# Downloads install-bundle from metadata:bucket, installs, and sets label:boot_status = 'done'.
88

9+
set -xv
10+
911
echo "=== AXLearn start_vm.sh ==="
1012

1113
# Setup logs.
@@ -94,6 +96,8 @@ if [[ " ${tar_bundlers[*]} " =~ " ${BUNDLER_TYPE} " ]]; then
9496
conda create -y -n py310 python=3.10
9597
conda activate py310
9698
conda info --envs
99+
# update pip and pre-install needed utilities
100+
pip install -U pip swig flit uv
97101
# Add conda to .profile file, and use login shell to source.
98102
echo 'source /opt/conda/etc/profile.d/conda.sh && conda activate py310' >> ~/.profile
99103
}
@@ -105,7 +109,8 @@ if [[ " ${tar_bundlers[*]} " =~ " ${BUNDLER_TYPE} " ]]; then
105109
# 'Until' seemingly necessary as of 08/06/23 to avoid background setup process damaging partial
106110
# pip install state.
107111
# TODO(markblee,tom_gunter): Revisit this and understand why and how to wait until ready.
108-
until python3 -m pip install .[gcp]; do
112+
export UV_HTTP_TIMEOUT=300
113+
until uv pip install .[gcp]; do
109114
echo "Attempting to install bundle again..."
110115
sleep 1
111116
done

axlearn/common/array_serialization_test.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,10 @@ async def mock_ts_open(spec_arg, *args, **kwargs):
383383
deserialize_specs = captured_specs[-len(data) :]
384384
self.assertGreater(len(deserialize_specs), 0)
385385
for spec in deserialize_specs:
386-
# gcs_grpc driver should be used when either:
387-
# - JAX_PLATFORMS is "proxy" (running on GCP/Pathways), OR
386+
# gcs_grpc driver should be used when:
388387
# - ENABLE_GCS_GRPC is "true" (explicit opt-in)
389388
# Otherwise, gcs driver should be used (no change)
390-
should_use_grpc = jax_platforms == "proxy" or enable_gcs_grpc == "true"
389+
should_use_grpc = enable_gcs_grpc == "true"
391390
expected_driver = "gcs_grpc" if should_use_grpc else "gcs"
392391
self.assertEqual(spec["kvstore"]["driver"], expected_driver)
393392

axlearn/common/compiler_options.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,6 @@ def default_xla_options(
8787
xla_latency_hiding_scheduler_rerun=2,
8888
# Improved performance for v6e.
8989
xla_tpu_scoped_vmem_limit_kib=98304,
90-
# For megascale performance.
91-
xla_jf_crs_combiner_threshold_count=10,
9290
# Disable collective matmul. Collective matmul could negatively affect performance in
9391
# some cases. Even in cases where collective matmul provides gains, the gains are
9492
# marginal on v6e due to the high arithmetic intensity.
@@ -106,8 +104,9 @@ def default_xla_options(
106104
# fusion and allreduce SC offloading by default.
107105
options.update(
108106
xla_tpu_enable_async_collective_fusion_fuse_all_gather="true",
109-
# Always enable SparseCore offloading for allreduce.
110-
xla_tpu_enable_sparse_core_collective_offload_all_reduce="true",
107+
# Allreduce SparseCore offloading leads to quality discrepancy on v6e in JAX 0.6.2.
108+
# TODO(changlan): Review and enable it later.
109+
# xla_tpu_enable_sparse_core_collective_offload_all_reduce="true",
111110
)
112111

113112
options.update(
@@ -455,7 +454,9 @@ def infer_xla_performance_flags(
455454
xla_tpu_enable_async_collective_fusion_fuse_reduce_scatter="false",
456455
xla_tpu_enable_sparse_core_collective_offload_all_gather="true",
457456
xla_tpu_enable_sparse_core_collective_offload_reduce_scatter="true",
458-
xla_tpu_enable_sparse_core_collective_offload_all_reduce="true",
457+
# Allreduce SparseCore offloading leads to quality discrepancy on v6e in JAX 0.6.2.
458+
# TODO(changlan): Review and enable it later.
459+
# xla_tpu_enable_sparse_core_collective_offload_all_reduce="true",
459460
xla_tpu_enable_all_gather_offload_tracing="true",
460461
xla_tpu_enable_reduce_scatter_offload_tracing="true",
461462
xla_tpu_enable_all_reduce_offload_tracing="true",

0 commit comments

Comments
 (0)