In 2023, I made a decision that saved CAM Grupo thousands of dollars monthly and kept our client data out of third-party APIs: I built a private LLM inference stack on Apple Silicon using Apple’s MLX framework, connected via Tailscale for secure remote access. This article explains the architecture, the trade-offs, and why on-premise inference is viable for small teams.
The Problem: API Costs and Data Sovereignty
CAM Grupo processes legal contracts, client communications, and proprietary market data. Sending this to OpenAI or Anthropic’s APIs introduces two problems: (1) recurring costs that scale with usage, and (2) data leaving our infrastructure. For a brokerage handling pre-sale developments with confidential pricing, data sovereignty is not optional.
The alternative — running models locally — was historically impractical. CUDA dependencies, power consumption, and model availability made self-hosting a full-time job. MLX changed that.
MLX: Why Apple Silicon?
Apple’s MLX framework is not just a PyTorch clone for Macs. It is designed specifically for the Unified Memory Architecture of Apple Silicon, where CPU, GPU, and Neural Engine share a single memory pool. This has a profound implication for LLM inference:
Traditional GPU (CUDA):
- Model weights in VRAM (24GB max on consumer cards)
- Activations copied between CPU RAM and VRAM
- Context window limited by VRAM capacity
Apple Silicon (MLX):
- Model weights in unified memory (up to 192GB on Mac Studio)
- Zero-copy tensor operations
- Context window limited by total RAM, not VRAM
On a Mac Studio with 64GB unified memory, I can run a 70B parameter model with 4-bit quantization — something that requires multiple A100s in a traditional setup. The throughput is lower, but for our use case (batch contract analysis, not real-time chat), latency is acceptable.
The Stack
Our inference stack has three layers:
Layer 1: Model Serving (MLX Server)
- Framework: mlx-lm
- Model: Meta-Llama-3.1-70B-Instruct (4-bit quantized)
- Interface: OpenAI-compatible HTTP API
- Local endpoint: http://localhost:8080/v1/completions
Layer 2: Network (Tailscale)
- VPN mesh network over WireGuard
- Each agent's MacBook is a node
- Mac Studio is the "server" node
- ACLs restrict who can reach port 8080
Layer 3: Application Integration
- Claude API for non-sensitive tasks (marketing copy)
- Local MLX for sensitive tasks (contract review)
- Router decides based on data classification
Setting Up MLX Server
The setup is surprisingly minimal. MLX provides a drop-in server compatible with the OpenAI API format:
# Install
pip install mlx-lm
# Download quantized model
huggingface-cli download mlx-community/Meta-Llama-3.1-70B-Instruct-4bit
# Start server
python -m mlx_lm.server \
--model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit \
--host 127.0.0.1 \
--port 8080
# Test
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local",
"messages": [{"role": "user", "content": "Hello"}]
}'
The key advantage is API compatibility. Existing code that calls OpenAI’s API works with minimal changes — just change the base URL.
Tailscale: The Networking Layer
Tailscale makes the Mac Studio accessible from anywhere without opening firewall ports or managing static IPs. Each agent installs Tailscale on their laptop, and I configure ACLs so only authorized devices can reach the inference endpoint:
{
"acls": [
{
"action": "accept",
"src": ["group:agents"],
"dst": ["tag:inference-server:8080"]
}
]
}
The connection is encrypted with WireGuard and routed through Tailscale’s DERP relays only when direct connections fail. In practice, two Macs on the same Tailscale network communicate directly with ~2ms latency.
Custom Skills: Contract Comparison
Raw inference is not enough. We built a “skill” layer that structures LLM calls into reusable workflows. The contract comparison skill, for example:
class ContractComparisonSkill:
def __init__(self, mlx_client):
self.client = mlx_client
def compare(self, contract_a, contract_b):
prompt = f"""
Compare these two real estate contracts.
Identify differences in: payment terms, delivery dates,
penalty clauses, and force majeure provisions.
Contract A: {contract_a}
Contract B: {contract_b}
"""
response = self.client.chat.completions.create(
model="local",
messages=[{"role": "user", "content": prompt}],
temperature=0.1, # Low temperature for consistency
)
return self.parse_comparison(response.choices[0].message.content)
The temperature of 0.1 is critical for legal analysis. High temperatures produce creative language; legal review requires deterministic outputs.
Performance Benchmarks
On a Mac Studio (M2 Ultra, 64GB RAM):
Model: Meta-Llama-3.1-70B-Instruct (4-bit)
Context: 4096 tokens
Throughput: ~18 tokens/second
Memory usage: ~42GB unified memory
Comparison: GPT-4 API
Cost per 1M tokens: ~$30
Our usage: ~500K tokens/month
Monthly savings: ~$15 (direct inference)
+ $0 (data never leaves premises)
The raw cost savings are modest because our volume is low. The real value is data sovereignty and zero latency for batch processing. A contract review job that would queue on OpenAI’s API runs immediately on our stack.
Limitations and Trade-offs
On-premise inference is not a universal replacement for API services:
- Throughput ceiling. A Mac Studio handles one inference stream well. Concurrent users require queuing or multiple machines.
- Model lag. New models appear on Hugging Face days or weeks after API release. If you need GPT-4-level reasoning on day one, APIs win.
- Operational burden. Someone has to update models, monitor memory usage, and restart the server. At CAM Grupo, that is me.
When to Choose On-Premise
Based on our experience, on-premise LLM inference makes sense when:
- Data cannot leave your infrastructure (legal, medical, financial).
- Usage is batch-oriented, not real-time (report generation, document review).
- You have Apple Silicon with 32GB+ unified memory available.
- You have one person who can manage the stack part-time.
If you need real-time chat for thousands of users, use the APIs. If you need to analyze sensitive documents without sending them to San Francisco, MLX + Tailscale is a pragmatic, surprisingly powerful solution.