Setting up an AI environment on Apple Silicon
I recently started working through an AI module that required a local Python environment with PyTorch, scikit-learn, and the usual Hugging Face stack. The guide was written for a generic Linux or Windows audience, and I was following along on a Mac. Within thirty minutes I had hit three separate problems, none of which were covered in the documentation, and all of which are things that trip up Apple Silicon users on a regular basis.
This is a walkthrough of the setup process for any M-series Mac, including the exact errors I ran into and how I fixed them. If you are setting up a machine learning environment on Apple Silicon for the first time, this should save you the debugging.
Why Miniconda and not plain Python
The module recommended Miniconda over a standard Python installation, and it is the right call for ML work. Conda handles binary dependencies in a way that pip alone does not. Libraries like NumPy, SciPy, and PyTorch ship with platform-specific compiled code, and conda resolves those binary compatibility requirements automatically. It also isolates environments properly, so a set of packages for one project cannot break another.
On macOS, the installation is straightforward with Homebrew:
brew install --cask miniconda
After installation, verify it worked:
conda --version
You should see something like conda 25.9.2 or later. If that command works, you have a working conda binary. But having a binary and having a functional shell integration are two different things, which brings us to the first problem.
Problem one: conda activate does not work after conda init
With Miniconda installed, I created a new environment:
conda create -n ai python=3.11
That completed without issues. Then I ran conda init as the documentation instructed, and tried to activate the environment:
conda init
conda activate ai
The response:
CondaError: Run 'conda init' before 'conda activate'
I had just run conda init. The output had said “no change” next to every file it checked, and the final line read “No action taken.” Closing and reopening the terminal did not help. Same error.
The problem is a shell mismatch. On modern Macs (anything from Catalina onwards), the default shell is zsh. But when you run conda init without specifying a shell, conda checks its default list and, depending on the installation method, may only write its initialisation hooks into ~/.bash_profile, which zsh does not read. You can confirm this by checking what is actually in your zsh configuration:
echo $SHELL
# /bin/zsh
cat ~/.zshrc
# (no conda block present)
Meanwhile, ~/.bash_profile has the full conda initialisation block sitting in it, completely ignored by zsh.
The fix is to tell conda exactly which shell to initialise:
conda init zsh
Then either restart your terminal or reload the config:
exec zsh
Now conda activate ai works. Your prompt changes to (ai), confirming you are inside the isolated environment.
This is a well-documented issue on conda’s GitHub tracker. The root cause is that conda’s default init behaviour does not reliably detect the active shell on macOS when installed through Homebrew’s cask mechanism. It writes to bash config files and reports “no change” because the bash files already exist from a previous partial setup, but it never touches zsh. The error message telling you to “run conda init” is misleading, because you already did, and it is not clear that you need to specify the shell explicitly.
Problem two: the CUDA command that does not belong on your Mac
With the environment active, the next step was installing PyTorch. The module’s instructions included this:
conda install -y pytorch torchvision torchaudio pytorch-cuda=12.4 -c pytorch -c nvidia
This command is written for machines with NVIDIA GPUs. M-series Macs do not have NVIDIA hardware. They use Apple’s own GPU cores, accessed through the Metal framework. Attempting to install pytorch-cuda on Apple Silicon will either fail outright or pull in packages that cannot function on your hardware.
The correct command for any M-series Mac:
conda install -y pytorch torchvision torchaudio -c pytorch
Drop pytorch-cuda=12.4 entirely. Drop the -c nvidia channel. PyTorch’s macOS builds have included Metal Performance Shaders (MPS) support since version 1.12, and the standard conda package from the pytorch channel includes it automatically on ARM64 Macs.
After installation, verify that your Mac’s GPU is accessible to PyTorch:
python -c "import torch; print(torch.backends.mps.is_available())"
This should print True. If it prints False, check that you are running a native ARM64 build of Python rather than an x86 build under Rosetta. An Intel-emulated Python is a common reason for MPS reporting itself as unavailable even on capable hardware.
How MPS compares to CUDA in practice
MPS is not a drop-in replacement for CUDA in every scenario. It covers the standard operations used in neural network training, including matrix multiplication, convolutions, batch normalisation, activation functions, and most loss functions. Common architectures like ResNet, BERT, and EfficientNet run on MPS without modification, and Hugging Face’s Trainer class detects and uses the MPS device automatically.
However, not every PyTorch operation has a Metal kernel implementation yet. When your code hits an unsupported operation, PyTorch raises an error. The workaround is an environment variable that falls back to CPU for unsupported ops:
PYTORCH_ENABLE_MPS_FALLBACK=1 python your_script.py
The other significant limitation is that MPS does not support distributed training. You are limited to the GPU cores in your SoC. For the kind of learning and prototyping that most AI modules involve, this is more than sufficient.
Apple Silicon’s unified memory architecture is actually an advantage for certain workloads. Unlike discrete NVIDIA GPUs where data must be copied across a PCIe bus between system RAM and GPU VRAM, M-series chips share a single memory pool between CPU and GPU. PyTorch still manages tensor placement internally, but the absence of physical data movement across a bus reduces overhead, particularly when working with models that are large relative to available memory.
Problem three: pip is missing from the environment
The module’s setup instructions included a couple of pip installs after the conda packages:
pip install requests requests_toolbelt
Running this inside the freshly created conda environment returned “command not found.” This was unexpected because pip normally ships with Python.
The fix is simple:
conda install pip
Or, if you want to use Python’s built-in bootstrap mechanism:
python -m ensurepip
Either approach installs pip into your active conda environment. After that, verify it is pointing to the right location:
pip --version
The path should include /envs/ai/ in it, confirming pip is installed inside your conda environment and not at the system level. If the path points to /usr/bin/pip or somewhere outside your conda directory, you are using the system pip, and packages will not install into your isolated environment.
A practical note on mixing conda and pip: prefer conda for core scientific packages (PyTorch, NumPy, SciPy, scikit-learn) and use pip only for packages that conda does not carry. Mixing the two package managers can create dependency conflicts because they resolve requirements differently. When you do use pip inside a conda environment, always install the conda packages first, then layer pip packages on top.
The full working setup sequence for M-series Macs
For anyone following along with a similar AI module on an M-series Mac, here is the complete sequence that works:
# Install Miniconda via Homebrew
brew install --cask miniconda
# Initialise conda for zsh specifically
conda init zsh
exec zsh
# Optionally stop the (base) prefix from appearing on every new terminal
conda config --set auto_activate_base false
# Add channels
conda config --add channels defaults
conda config --add channels conda-forge
conda config --add channels pytorch
conda config --set channel_priority strict
# Create and activate your environment
conda create -n ai python=3.11
conda activate ai
# Install core ML packages (no CUDA, no nvidia channel)
conda install -y numpy scipy pandas scikit-learn matplotlib seaborn transformers datasets tokenizers accelerate evaluate optimum huggingface_hub nltk category_encoders
conda install -y pytorch torchvision torchaudio -c pytorch
# Install pip, then use it for anything conda does not have
conda install pip
pip install requests requests_toolbelt
# Verify GPU access
python -c "import torch; print(torch.backends.mps.is_available())"
Keeping things updated
For conda-managed packages:
conda update --all
This updates everything conda installed but does not touch pip packages. For pip packages, check what is outdated first:
pip list --outdated
Then update selectively rather than in bulk:
pip install -U requests requests_toolbelt
Bulk-updating all pip packages at once (pip install -U $(pip list --outdated | awk 'NR>2 {print $1}')) works but risks breaking dependency chains. Be selective unless you are comfortable rebuilding the environment from scratch if something goes wrong.
What this setup actually gets you
With this environment running, you have PyTorch with Metal GPU acceleration, the full Hugging Face ecosystem for working with pre-trained models and datasets, and the standard scientific Python stack for data manipulation and visualisation. Training times on Apple Silicon are genuinely competitive for small to medium models. You will not match an A100, but for learning, prototyping, and fine-tuning, the M-series hardware is more than adequate, and you do not need to pay for cloud compute or wait for a VM to spin up.
The three problems I hit are all well-known in isolation, but I have not seen them documented together in the context of following an actual AI course setup guide on a Mac. The zsh/bash mismatch catches almost everyone on a first conda install. The CUDA command is copy-pasted from guides written for NVIDIA hardware without any Mac-specific annotation. And pip’s absence from a fresh conda environment is counterintuitive enough that people assume they broke something when they didn’t. None of these are difficult fixes.