-
Notifications
You must be signed in to change notification settings - Fork 641
Add MLX backend support and optimize inference for Apple Silicon #1426
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
base: main
Are you sure you want to change the base?
Changes from all commits
e418d9c
9ec3435
fe09e09
bfb1a75
c603300
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 |
|---|---|---|
|
|
@@ -30,6 +30,11 @@ def get_arg_parser(): | |
| hardware_args.add_argument( | ||
| "--gpu_device", required=False, default="0", type=str, | ||
| help="which gpu device to use, use an integer for torch, or mps for M1") | ||
| hardware_args.add_argument( | ||
| "--use_mlx", nargs="?", const=True, default=False, | ||
| help="use MLX backend for Apple Silicon acceleration. " | ||
| "Pass without value to enable, or pass 'auto' to auto-detect " | ||
| "(requires macOS + Apple Silicon + mlx package)") | ||
|
Comment on lines
+33
to
+37
|
||
|
|
||
| # settings for locating and formatting images | ||
| input_img_args = parser.add_argument_group("Input Image Arguments") | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,12 +10,18 @@ | |||||||||||||||
|
|
||||||||||||||||
| TORCH_ENABLED = True | ||||||||||||||||
|
|
||||||||||||||||
| try: | ||||||||||||||||
| import mlx.core as mx | ||||||||||||||||
| MLX_AVAILABLE = True | ||||||||||||||||
| except ImportError: | ||||||||||||||||
| MLX_AVAILABLE = False | ||||||||||||||||
|
|
||||||||||||||||
| core_logger = logging.getLogger(__name__) | ||||||||||||||||
| tqdm_out = utils.TqdmToLogger(core_logger, level=logging.INFO) | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def use_gpu(gpu_number=0, use_torch=True): | ||||||||||||||||
| """ | ||||||||||||||||
| """ | ||||||||||||||||
| Check if GPU is available for use. | ||||||||||||||||
|
|
||||||||||||||||
| Args: | ||||||||||||||||
|
|
@@ -34,6 +40,23 @@ def use_gpu(gpu_number=0, use_torch=True): | |||||||||||||||
| raise ValueError("cellpose only runs with PyTorch now") | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def use_mlx(): | ||||||||||||||||
| """Check if MLX is available and running on Apple Silicon. | ||||||||||||||||
|
|
||||||||||||||||
| Returns: | ||||||||||||||||
| bool: True if MLX is available. | ||||||||||||||||
| """ | ||||||||||||||||
| if not MLX_AVAILABLE: | ||||||||||||||||
| return False | ||||||||||||||||
| try: | ||||||||||||||||
| _ = mx.zeros((1, 1)) | ||||||||||||||||
| mx.eval(_) | ||||||||||||||||
| core_logger.info("** MLX available on Apple Silicon. **") | ||||||||||||||||
| return True | ||||||||||||||||
| except Exception: | ||||||||||||||||
| return False | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def _use_gpu_torch(gpu_number=0): | ||||||||||||||||
| """ | ||||||||||||||||
| Checks if CUDA or MPS is available and working with PyTorch. | ||||||||||||||||
|
|
@@ -109,6 +132,22 @@ def assign_device(use_torch=True, gpu=False, device=0): | |||||||||||||||
| return device, gpu | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def _forward_mlx(net, x): | ||||||||||||||||
| """Converts images to MLX arrays, runs the network model, and returns numpy arrays. | ||||||||||||||||
|
|
||||||||||||||||
| Args: | ||||||||||||||||
| net: The MLX TransformerMLX model. | ||||||||||||||||
| x (numpy.ndarray): The input images of shape (N, C, H, W). | ||||||||||||||||
|
|
||||||||||||||||
| Returns: | ||||||||||||||||
| Tuple[numpy.ndarray, numpy.ndarray]: The output predictions and style features. | ||||||||||||||||
| """ | ||||||||||||||||
| X = mx.array(x) | ||||||||||||||||
| y, style = net(X) | ||||||||||||||||
| mx.eval(y, style) | ||||||||||||||||
| return np.array(y, copy=False), np.array(style, copy=False) | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def _to_device(x, device, dtype=torch.float32): | ||||||||||||||||
| """ | ||||||||||||||||
| Converts the input tensor or numpy array to the specified device. | ||||||||||||||||
|
|
@@ -163,10 +202,10 @@ def _forward(net, x): | |||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def run_net(net, imgi, batch_size=8, augment=False, tile_overlap=0.1, bsize=224, | ||||||||||||||||
| rsz=None): | ||||||||||||||||
| """ | ||||||||||||||||
| rsz=None, use_mlx_backend=False): | ||||||||||||||||
| """ | ||||||||||||||||
| Run network on stack of images. | ||||||||||||||||
|
|
||||||||||||||||
| (faster if augment is False) | ||||||||||||||||
|
|
||||||||||||||||
| Args: | ||||||||||||||||
|
|
@@ -177,39 +216,42 @@ def run_net(net, imgi, batch_size=8, augment=False, tile_overlap=0.1, bsize=224, | |||||||||||||||
| augment (bool, optional): Tiles image with overlapping tiles and flips overlapped regions to augment. Defaults to False. | ||||||||||||||||
| tile_overlap (float, optional): Fraction of overlap of tiles when computing flows. Defaults to 0.1. | ||||||||||||||||
| bsize (int, optional): Size of tiles to use in pixels [bsize x bsize]. Defaults to 224. | ||||||||||||||||
| use_mlx_backend (bool, optional): Use MLX backend for inference. Defaults to False. | ||||||||||||||||
|
|
||||||||||||||||
| Returns: | ||||||||||||||||
| Tuple[numpy.ndarray, numpy.ndarray]: outputs of network y and style. If tiled `y` is averaged in tile overlaps. Size of [Ly x Lx x 3] or [Lz x Ly x Lx x 3]. | ||||||||||||||||
| y[...,0] is Y flow; y[...,1] is X flow; y[...,2] is cell probability. | ||||||||||||||||
| y[...,0] is Y flow; y[...,1] is X flow; y[...,2] is cell probability. | ||||||||||||||||
| style is a 1D array of size 256 summarizing the style of the image, if tiled `style` is averaged over tiles. | ||||||||||||||||
| """ | ||||||||||||||||
|
||||||||||||||||
| """ | |
| """ | |
| if use_mlx_backend and not MLX_AVAILABLE: | |
| raise ImportError( | |
| "MLX backend was requested (use_mlx_backend=True), but the 'mlx' package is not " | |
| "installed. Please install 'mlx' or set use_mlx_backend=False." | |
| ) |
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.
--use_mlxusesnargs='?'withouttype/choices, so any provided string becomes truthy (e.g.--use_mlx falsesets'false'and will be treated as enabled). Consider restricting allowed values (e.g.choices=['auto']plus a separate--use_mlxflag) or parsing common boolean strings into a real bool to avoid surprising behavior.