-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Mac OSX on Apple Silicon #465
Changes from 15 commits
8430794
7f3ff3d
b333bce
26f12b2
1076dad
51f8644
6f3f2da
9922080
b3d3e6e
39057d9
df428e3
4df0dc1
1aa1cc8
a2bfc79
72a81bd
c081121
b76aead
5110fd7
fce7289
0cea788
c83a88d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,6 +84,7 @@ class BuildError(Exception): | |
|
||
class Architecture(enum.Enum): | ||
X64 = ("x86_64", "amd64") | ||
ARM64 = ("arm64",) | ||
|
||
@classmethod | ||
def from_str(cls, string: str, /) -> "Architecture": | ||
|
@@ -366,6 +367,8 @@ class RedisAIBuilder(Builder): | |
|
||
def __init__( | ||
self, | ||
_os: OperatingSystem = OperatingSystem.from_str(platform.system()), | ||
architecture: Architecture = Architecture.from_str(platform.machine()), | ||
build_env: t.Optional[t.Dict[str, t.Any]] = None, | ||
torch_dir: str = "", | ||
libtf_dir: str = "", | ||
|
@@ -376,7 +379,14 @@ def __init__( | |
verbose: bool = False, | ||
) -> None: | ||
super().__init__(build_env or {}, jobs=jobs, verbose=verbose) | ||
|
||
self.rai_install_path: t.Optional[Path] = None | ||
if _os not in OperatingSystem: | ||
raise BuildError(f"Unsupported operating system: {_os}") | ||
self._os = _os | ||
if architecture not in Architecture: | ||
raise BuildError(f"Unsupported architecture: {architecture}") | ||
ashao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._architecture = architecture | ||
|
||
# convert to int for RAI build script | ||
self._torch = build_torch | ||
|
@@ -385,10 +395,21 @@ def __init__( | |
self.libtf_dir = libtf_dir | ||
self.torch_dir = torch_dir | ||
|
||
# TODO: It might be worth making these constructor args so that users | ||
# of this class can configure exactly _what_ they are building. | ||
self._os = OperatingSystem.from_str(platform.system()) | ||
self._architecture = Architecture.from_str(platform.machine()) | ||
# Sanity checks | ||
self._check_backends_arm64() | ||
|
||
def _check_backends_arm64(self) -> None: | ||
if self._architecture == Architecture.ARM64: | ||
unsupported = [] | ||
if self.build_tf: | ||
unsupported.append("Tensorflow") | ||
if self.build_onnx: | ||
unsupported.append("ONNX") | ||
ashao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if unsupported: | ||
raise BuildError( | ||
f"The {'.'.join(unsupported)} backends are not " | ||
MattToast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"supported on ARM64. Run with `smart build --no_tf`" | ||
ashao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the decision to try and move as much of the validation of dependencies into the constructor to error out early! But I don't necessarily love the existence of this method. It's going to become very difficult to maintain if we were to expand out into different architectures w/o full dependency support and/or we want to become more prescriptive in our builds (e.g. different versions of libraries for different architectures, etc.) What would you think of, instead, expanding the class _RAIBuildDependency(ABC):
...
@abstractmethod
def __is_satisfiable__(self) -> bool: ... which can then be implemented by all dependencies that implement the interface: class _TFArchive(_WebTGZ, _RAIBuildDependency):
...
def __is_satisfiable__(self) -> bool:
try:
self.url
except BuildError:
return False
else:
return True
# And presumably very similar implementations
# for `_PTArchiveLinux`, `_PTArchiveMacOSX`, and `_ORTArchive` and then checking for dependency satisfiability in class RedisAIBuilder(Builder):
...
def __init__(self, ...) -> None:
...
if not all(
dep.__is_satisfiable__() for dep
in sequence_of_deps_that_the_rai_builder_needs
):
raise BuildError(f"{type(self).__name__} failed to satisfy "
"dependencies for this platform")
# We can workshop the error message if want to be a bit
# more prescriptive, but you get the idea! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per conversation yesterday, this solution is not viable as we do not have all the information necessary at Switching to having all |
||
|
||
@property | ||
def rai_build_path(self) -> Path: | ||
|
@@ -436,6 +457,8 @@ def fail_to_format(reason: str) -> BuildError: # pragma: no cover | |
raise fail_to_format(f"Unknown operating system: {self._os}") | ||
if self._architecture == Architecture.X64: | ||
arch = "x64" | ||
elif self._architecture == Architecture.ARM64: | ||
arch = "arm64v8" | ||
else: # pragma: no cover | ||
raise fail_to_format(f"Unknown architecture: {self._architecture}") | ||
return self.rai_build_path / f"deps/{os_}-{arch}-{device}" | ||
|
@@ -450,13 +473,17 @@ def _get_deps_to_fetch_for( | |
# dependency versions were declared in single location. | ||
# Unfortunately importing into this module is non-trivial as it | ||
# is used as script in the SmartSim `setup.py`. | ||
fetchable_deps: t.Sequence[t.Tuple[bool, _RAIBuildDependency]] = ( | ||
(True, _DLPackRepository("v0.5_RAI")), | ||
(self.fetch_torch, _PTArchive(os_, device, "2.0.1")), | ||
(self.fetch_tf, _TFArchive(os_, arch, device, "2.13.1")), | ||
(self.fetch_onnx, _ORTArchive(os_, device, "1.16.3")), | ||
) | ||
return tuple(dep for should_fetch, dep in fetchable_deps if should_fetch) | ||
|
||
# DLPack is always required | ||
fetchable_deps: t.List[_RAIBuildDependency] = [_DLPackRepository("v0.5_RAI")] | ||
if self.fetch_torch: | ||
fetchable_deps.append(choose_pt_variant(os_, arch, device, "2.0.1")) | ||
if self.fetch_tf: | ||
fetchable_deps.append(_TFArchive(os_, arch, device, "2.13.1")) | ||
if self.fetch_onnx: | ||
fetchable_deps.append(_ORTArchive(os_, device, "1.16.3")) | ||
|
||
return tuple(fetchable_deps) | ||
|
||
def symlink_libtf(self, device: str) -> None: | ||
"""Add symbolic link to available libtensorflow in RedisAI deps. | ||
|
@@ -756,31 +783,12 @@ def _extract_download( | |
zip_file.extractall(target) | ||
|
||
|
||
@t.final | ||
@dataclass(frozen=True) | ||
class _PTArchive(_WebZip, _RAIBuildDependency): | ||
os_: OperatingSystem | ||
architecture: Architecture | ||
device: TDeviceStr | ||
version: str | ||
|
||
@property | ||
def url(self) -> str: | ||
if self.os_ == OperatingSystem.LINUX: | ||
if self.device == "gpu": | ||
pt_build = "cu117" | ||
else: | ||
pt_build = "cpu" | ||
# pylint: disable-next=line-too-long | ||
libtorch_arch = f"libtorch-cxx11-abi-shared-without-deps-{self.version}%2B{pt_build}.zip" | ||
elif self.os_ == OperatingSystem.DARWIN: | ||
if self.device == "gpu": | ||
raise BuildError("RedisAI does not currently support GPU on Macos") | ||
pt_build = "cpu" | ||
libtorch_arch = f"libtorch-macos-{self.version}.zip" | ||
else: | ||
raise BuildError(f"Unexpected OS for the PT Archive: {self.os_}") | ||
return f"https://download.pytorch.org/libtorch/{pt_build}/{libtorch_arch}" | ||
|
||
@property | ||
def __rai_dependency_name__(self) -> str: | ||
return f"libtorch@{self.url}" | ||
|
@@ -793,6 +801,54 @@ def __place_for_rai__(self, target: t.Union[str, "os.PathLike[str]"]) -> Path: | |
return target | ||
|
||
|
||
@t.final | ||
class _PTArchiveLinux(_PTArchive): | ||
@property | ||
def url(self) -> str: | ||
if self.device == "gpu": | ||
pt_build = "cu117" | ||
else: | ||
pt_build = "cpu" | ||
# pylint: disable-next=line-too-long | ||
libtorch_archive = ( | ||
f"libtorch-cxx11-abi-shared-without-deps-{self.version}%2B{pt_build}.zip" | ||
) | ||
return f"https://download.pytorch.org/libtorch/{pt_build}/{libtorch_archive}" | ||
|
||
|
||
@t.final | ||
class _PTArchiveMacOSX(_PTArchive): | ||
@property | ||
def url(self) -> str: | ||
if self.device == "gpu": | ||
raise BuildError("RedisAI does not currently support GPU on Mac OSX") | ||
if self.architecture == Architecture.X64: | ||
pt_build = "cpu" | ||
libtorch_archive = f"libtorch-macos-{self.version}.zip" | ||
root_url = "https://download.pytorch.org/libtorch" | ||
return f"{root_url}/{pt_build}/{libtorch_archive}" | ||
if self.architecture == Architecture.ARM64: | ||
libtorch_archive = f"libtorch-macos-arm64-{self.version}.zip" | ||
# pylint: disable-next=line-too-long | ||
root_url = ( | ||
"https://github.com/CrayLabs/ml_lib_builder/releases/download/v0.1/" | ||
) | ||
return f"{root_url}/{libtorch_archive}" | ||
|
||
raise BuildError("Unsupported architecture for Pytorch: {self.architecture}") | ||
|
||
|
||
def choose_pt_variant( | ||
os_: OperatingSystem, arch: Architecture, device: TDeviceStr, version: str | ||
) -> t.Union[_PTArchiveLinux, _PTArchiveMacOSX]: | ||
if os_ == OperatingSystem.DARWIN: | ||
return _PTArchiveMacOSX(arch, device, version) | ||
if os_ == OperatingSystem.LINUX: | ||
return _PTArchiveLinux(arch, device, version) | ||
|
||
raise BuildError(f"Unsupported OS for pyTorch: {os_}") | ||
ashao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@t.final | ||
@dataclass(frozen=True) | ||
class _TFArchive(_WebTGZ, _RAIBuildDependency): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong but isn't the preferred capitalization
? At least I thought that was how they titled their releases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're totally correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-surfacing this
pyTorch
->PyTorch