Skip to content

Commit 171f4cd

Browse files
lukebaumanncopybara-github
authored andcommitted
Introduce jax shim to support multiple versions of JAX
PiperOrigin-RevId: 789952304
1 parent fd7ab09 commit 171f4cd

7 files changed

Lines changed: 177 additions & 112 deletions

File tree

pathwaysutils/__init__.py

Lines changed: 2 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -12,95 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Package of Pathways-on-Cloud utilities."""
15+
from pathwaysutils import _initialize
1516

16-
import datetime
17-
import logging
18-
import os
17+
initialize = _initialize.initialize
1918

20-
import jax
21-
from pathwaysutils import profiling
22-
from pathwaysutils import proxy_backend
23-
from pathwaysutils.persistence import orbax_handler
2419

25-
26-
_logger = logging.getLogger(__name__)
27-
_initialization_count = 0
2820
# When changing this, also update the CHANGELOG.md.
2921
__version__ = "v0.1.1"
30-
31-
32-
# This is a brittle implementation since the platforms value is not necessarily
33-
# which backend is ultimately selected
34-
def is_pathways_backend_used() -> bool:
35-
"""Returns whether Pathways backend is used.
36-
37-
This function checks the JAX platforms configuration to determine whether
38-
Pathways is used. If the platforms configuration contains the string "proxy",
39-
Pathways is used. This is a brittle implementation since the platforms value
40-
is not necessarily which backend is ultimately selected or there may be more
41-
than one platform specified and another may have higher priority.
42-
"""
43-
return jax.config.jax_platforms and "proxy" in jax.config.jax_platforms
44-
45-
46-
def _is_persistence_enabled() -> bool:
47-
"""Returns whether persistence is enabled.
48-
49-
This function checks the environment variable ENABLE_PATHWAYS_PERSISTENCE to
50-
determine whether persistence is enabled. If the variable is set to "1",
51-
persistence is enabled. If the variable is set to "0" or unset, persistence is
52-
disabled.
53-
54-
Returns:
55-
True if persistence is enabled, False otherwise.
56-
"""
57-
if "ENABLE_PATHWAYS_PERSISTENCE" in os.environ:
58-
if os.environ["ENABLE_PATHWAYS_PERSISTENCE"] == "1":
59-
return True
60-
if os.environ["ENABLE_PATHWAYS_PERSISTENCE"] == "0":
61-
return False
62-
else:
63-
raise ValueError(
64-
"ENABLE_PATHWAYS_PERSISTENCE must be set to 1/0 or unset, got: "
65-
+ os.environ["ENABLE_PATHWAYS_PERSISTENCE"]
66-
)
67-
return False
68-
69-
70-
def initialize() -> None:
71-
"""Initializes pathwaysutils.
72-
73-
This function is called by the user to initialize pathwaysutils. It is
74-
responsible for setting up the logging, profiling, and persistence handlers
75-
through various monkey patching functions. It is also responsible for
76-
registering the proxy backend factory.
77-
"""
78-
global _initialization_count
79-
_initialization_count += 1
80-
81-
# Ignoring the second call to initialize() is a temporary measure so that this
82-
# debug log is not triggered for customers who are following our instructions
83-
# and using the new initialize() function only once but have already had the
84-
# legacy initialization triggered.
85-
if _initialization_count > 1:
86-
_logger.debug("Already initialized. Ignoring duplicate call.")
87-
return
88-
89-
_logger.debug("Starting initialize.")
90-
91-
if is_pathways_backend_used():
92-
_logger.debug("Detected Pathways-on-Cloud backend. Applying changes.")
93-
proxy_backend.register_backend_factory()
94-
profiling.monkey_patch_jax()
95-
# TODO: b/365549911 - Remove when OCDBT-compatible
96-
if _is_persistence_enabled():
97-
orbax_handler.register_pathways_handlers(datetime.timedelta(hours=1))
98-
99-
# Turn off JAX compilation cache because Pathways handles its own
100-
# compilation cache.
101-
jax.config.update("jax_enable_compilation_cache", False)
102-
103-
else:
104-
_logger.debug(
105-
"Did not detect Pathways-on-Cloud backend. No changes applied."
106-
)

pathwaysutils/_initialize.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Initialization of Pathways-on-Cloud utilities."""
15+
16+
import datetime
17+
import logging
18+
import os
19+
20+
import jax
21+
from pathwaysutils import profiling
22+
from pathwaysutils import proxy_backend
23+
from pathwaysutils.persistence import orbax_handler
24+
25+
26+
_logger = logging.getLogger(__name__)
27+
_initialization_count = 0
28+
29+
30+
# This is a brittle implementation since the platforms value is not necessarily
31+
# which backend is ultimately selected
32+
def is_pathways_backend_used() -> bool:
33+
"""Returns whether Pathways backend is used.
34+
35+
This function checks the JAX platforms configuration to determine whether
36+
Pathways is used. If the platforms configuration contains the string "proxy",
37+
Pathways is used. This is a brittle implementation since the platforms value
38+
is not necessarily which backend is ultimately selected or there may be more
39+
than one platform specified and another may have higher priority.
40+
"""
41+
return jax.config.jax_platforms and "proxy" in jax.config.jax_platforms
42+
43+
44+
def _is_persistence_enabled() -> bool:
45+
"""Returns whether persistence is enabled.
46+
47+
This function checks the environment variable ENABLE_PATHWAYS_PERSISTENCE to
48+
determine whether persistence is enabled. If the variable is set to "1",
49+
persistence is enabled. If the variable is set to "0" or unset, persistence is
50+
disabled.
51+
52+
Returns:
53+
True if persistence is enabled, False otherwise.
54+
"""
55+
if "ENABLE_PATHWAYS_PERSISTENCE" in os.environ:
56+
if os.environ["ENABLE_PATHWAYS_PERSISTENCE"] == "1":
57+
return True
58+
if os.environ["ENABLE_PATHWAYS_PERSISTENCE"] == "0":
59+
return False
60+
else:
61+
raise ValueError(
62+
"ENABLE_PATHWAYS_PERSISTENCE must be set to 1/0 or unset, got: "
63+
+ os.environ["ENABLE_PATHWAYS_PERSISTENCE"]
64+
)
65+
return False
66+
67+
68+
def initialize() -> None:
69+
"""Initializes pathwaysutils.
70+
71+
This function is called by the user to initialize pathwaysutils. It is
72+
responsible for setting up the logging, profiling, and persistence handlers
73+
through various monkey patching functions. It is also responsible for
74+
registering the proxy backend factory.
75+
"""
76+
global _initialization_count
77+
_initialization_count += 1
78+
79+
# Ignoring the second call to initialize() is a temporary measure so that this
80+
# debug log is not triggered for customers who are following our instructions
81+
# and using the new initialize() function only once but have already had the
82+
# legacy initialization triggered.
83+
if _initialization_count > 1:
84+
_logger.debug("Already initialized. Ignoring duplicate call.")
85+
return
86+
87+
_logger.debug("Starting initialize.")
88+
89+
if is_pathways_backend_used():
90+
_logger.debug("Detected Pathways-on-Cloud backend. Applying changes.")
91+
proxy_backend.register_backend_factory()
92+
profiling.monkey_patch_jax()
93+
# TODO: b/365549911 - Remove when OCDBT-compatible
94+
if _is_persistence_enabled():
95+
orbax_handler.register_pathways_handlers(datetime.timedelta(hours=1))
96+
97+
# Turn off JAX compilation cache because Pathways handles its own
98+
# compilation cache.
99+
jax.config.update("jax_enable_compilation_cache", False)
100+
101+
else:
102+
_logger.debug(
103+
"Did not detect Pathways-on-Cloud backend. No changes applied."
104+
)

pathwaysutils/jax/__init__.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Pathways JAX abstractions.
15+
16+
This introduces an abstrction layer some JAX APIs that have changed over
17+
`pathwaysutils`'s compatibility window.
18+
"""
19+
20+
from typing import Any
21+
22+
try:
23+
# jax>=0.7.0
24+
from jax.extend import backend # pylint: disable=g-import-not-at-top
25+
26+
register_backend_cache = backend.register_backend_cache
27+
28+
del backend
29+
except ImportError:
30+
# jax<0.7.0
31+
from jax._src import util # pylint: disable=g-import-not-at-top
32+
33+
def register_backend_cache(cache: Any, name: str): # pylint: disable=unused-argument
34+
return util.cache_clearing_funs.add(cache.cache_clear)
35+
36+
del util
37+
38+
try:
39+
# jax>0.7.0
40+
from jax.extend import backend # pylint: disable=g-import-not-at-top
41+
42+
ifrt_proxy = backend.ifrt_proxy
43+
del backend
44+
except ImportError:
45+
# jax<=0.7.0
46+
from jax.lib import xla_extension # pylint: disable=g-import-not-at-top
47+
48+
ifrt_proxy = xla_extension.ifrt_proxy
49+
del xla_extension

pathwaysutils/lru_cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import functools
1717
from typing import Any, Callable
1818

19-
from jax._src import util
19+
from pathwaysutils import jax as pw_jax
2020

2121

2222
def lru_cache(
@@ -38,7 +38,7 @@ def wrap(f):
3838

3939
wrapper.cache_clear = cached.cache_clear
4040
wrapper.cache_info = cached.cache_info
41-
util.cache_clearing_funs.add(wrapper.cache_clear)
41+
pw_jax.register_backend_cache(wrapper, "Pathways LRU cache")
4242
return wrapper
4343

4444
return wrap

pathwaysutils/proxy_backend.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515

1616
import jax
1717
from jax.extend import backend
18-
from jax.lib.xla_extension import ifrt_proxy
18+
from pathwaysutils import jax as pw_jax
1919

2020

2121
def register_backend_factory():
2222
backend.register_backend_factory(
2323
"proxy",
24-
lambda: ifrt_proxy.get_client(
24+
lambda: pw_jax.ifrt_proxy.get_client(
2525
jax.config.read("jax_backend_target"),
26-
ifrt_proxy.ClientConnectionOptions(),
26+
pw_jax.ifrt_proxy.ClientConnectionOptions(),
2727
),
2828
priority=-1,
2929
)
Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,25 @@
1313
# limitations under the License.
1414

1515
import os
16-
from unittest import mock
1716

1817
import jax
19-
import pathwaysutils
18+
from pathwaysutils import _initialize as initialize
2019

2120
from absl.testing import absltest
2221
from absl.testing import parameterized
2322

2423

25-
class PathwaysutilsTest(parameterized.TestCase):
24+
class InitializeTest(parameterized.TestCase):
2625

2726
def test_first_initialize(self):
2827
jax.config.update("jax_platforms", "proxy")
29-
pathwaysutils._initialization_count = 0
28+
initialize._initialization_count = 0
3029

31-
with self.assertLogs(pathwaysutils._logger, level="DEBUG") as logs:
32-
pathwaysutils.initialize()
30+
with self.assertLogs(initialize._logger, level="DEBUG") as logs:
31+
initialize.initialize()
3332

3433
self.assertLen(logs.output, 2)
35-
self.assertIn(
36-
"Starting initialize.", logs.output[0]
37-
)
34+
self.assertIn("Starting initialize.", logs.output[0])
3835
self.assertIn(
3936
"Detected Pathways-on-Cloud backend. Applying changes.", logs.output[1]
4037
)
@@ -46,10 +43,10 @@ def test_first_initialize(self):
4643
("initialization_count 1000", 1000),
4744
)
4845
def test_initialize_more_than_once(self, initialization_count):
49-
pathwaysutils._initialization_count = initialization_count
46+
initialize._initialization_count = initialization_count
5047

51-
with self.assertLogs(pathwaysutils._logger, level="DEBUG") as logs:
52-
pathwaysutils.initialize()
48+
with self.assertLogs(initialize._logger, level="DEBUG") as logs:
49+
initialize.initialize()
5350

5451
self.assertLen(logs.output, 1)
5552
self.assertIn(
@@ -65,7 +62,7 @@ def test_initialize_more_than_once(self, initialization_count):
6562
)
6663
def test_not_is_pathways_backend_used(self, platform: str):
6764
jax.config.update("jax_platforms", platform)
68-
self.assertFalse(pathwaysutils.is_pathways_backend_used())
65+
self.assertFalse(initialize.is_pathways_backend_used())
6966

7067
@parameterized.named_parameters(
7168
("proxy", "proxy"),
@@ -75,20 +72,20 @@ def test_not_is_pathways_backend_used(self, platform: str):
7572
)
7673
def test_is_pathways_backend_used(self, platform: str):
7774
jax.config.update("jax_platforms", platform)
78-
self.assertTrue(pathwaysutils.is_pathways_backend_used())
75+
self.assertTrue(initialize.is_pathways_backend_used())
7976

8077
def test_persistence_enabled(self):
8178
os.environ["ENABLE_PATHWAYS_PERSISTENCE"] = "1"
82-
self.assertTrue(pathwaysutils._is_persistence_enabled())
79+
self.assertTrue(initialize._is_persistence_enabled())
8380

8481
os.environ["ENABLE_PATHWAYS_PERSISTENCE"] = "0"
85-
self.assertFalse(pathwaysutils._is_persistence_enabled())
82+
self.assertFalse(initialize._is_persistence_enabled())
8683

8784
os.environ["ENABLE_PATHWAYS_PERSISTENCE"] = ""
88-
self.assertRaises(ValueError, pathwaysutils._is_persistence_enabled)
85+
self.assertRaises(ValueError, initialize._is_persistence_enabled)
8986

9087
del os.environ["ENABLE_PATHWAYS_PERSISTENCE"]
91-
self.assertFalse(pathwaysutils._is_persistence_enabled())
88+
self.assertFalse(initialize._is_persistence_enabled())
9289

9390

9491
if __name__ == "__main__":

pathwaysutils/test/proxy_backend_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import jax
1919
from jax.extend import backend
20-
from jax.lib.xla_extension import ifrt_proxy
20+
from pathwaysutils import jax as pw_jax
2121
from pathwaysutils import proxy_backend
2222

2323
from absl.testing import absltest
@@ -38,7 +38,7 @@ def test_no_proxy_backend_registration_raises_error(self):
3838
def test_proxy_backend_registration(self):
3939
self.enter_context(
4040
mock.patch.object(
41-
ifrt_proxy,
41+
pw_jax.ifrt_proxy,
4242
"get_client",
4343
return_value=mock.MagicMock(),
4444
)

0 commit comments

Comments
 (0)