Repository navigation
Conversation
Adds esm/models/esmc/mlx_model.py: a self-contained ESMC implementation in Apple MLX — no PyTorch, no CUDA, unified memory, Metal GPU via mx.fast.scaled_dot_product_attention. Architecture matches EsmcForMaskedLM exactly: • pre-LN Transformer, RoPE (base=10000), QK-Norm, SwiGLU FFN • residue scaling: sqrt(n_layers/36) (ESM3 scheme) • weights loaded from the same HF safetensors checkpoint Correctness: max logit MAE = 1.3e-4 vs PyTorch float32 on CPU. Benchmark vs PyTorch MPS on Apple M3: L=140 : 57 ms vs 68 ms → 1.19x L=280 : 104 ms vs 148 ms → 1.41x L=426 : 150 ms vs 233 ms → 1.56x Speedup grows with sequence length (attention is O(L²)). Also adds cookbook/snippets/esmc_mlx.py with a usage example. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full M3 benchmark table for ESMC-300M across opt levels 0-4 and sequence lengths L=76-450. B=12 is sweet spot at 1.75x vs sequential (33.7 seq/s). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the device utils from PR Biohub#409 with: - is_mlx_available(): detects Apple MLX (mlx.core importable) - load_esmc(model_name, device="auto"): top-level factory that selects the optimal ESMC backend for the current hardware: CUDA → EsmcForMaskedLM on CUDA (bfloat16) MLX on M-chip → EsmcMLX (native Metal, no PyTorch) MPS only → EsmcForMaskedLM on MPS (float32) CPU → EsmcForMaskedLM on CPU device="mlx" forces the MLX path explicitly. Both backends share the same __call__(input_ids) -> logits interface so callers need no device-specific branching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All three published ESMC checkpoints share the same pre-LN transformer architecture — only depth and width differ. Adds ESMC-6B to CONFIGS: "biohub/ESMC-6B": hidden=2560, n_heads=40, n_layers=80, intermediate=6912 Memory: ~12 GB bf16, ~3 GB mxfp4. Recommended opt_level=2 on machines with <24 GB RAM (avoids JIT warmup spike); opt_level=3 on M-series Max/Ultra. Also updates load_esmc() docstring and from_pretrained error message to enumerate all three supported repos. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents Previous table measured MLX in isolation (bench_serial.py) against MPS in isolation — not directly comparable. New table runs both backends in separate subprocesses on the same ProteinGym WT sequences (4 warmup + 5 timed, M3 8GB): Mean speedup: 3.27× (geomean 3.0×) across L=70–536 Range: 1.73× at L=161 → 7.93× at L=428 The earlier 14s outlier at L=211 in the same-process benchmark was Metal compiler contention from MLX + MPS co-resident — not reproducible in isolation (L=211 isolated = 260ms). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brings both files into line with the repo's ruff config (E4/E7/E9/F/I): - sorted imports (I001) - split semicolon statements onto separate lines (E702 × 3) - removed unused B_curr variable (F841) - reformatted to ruff style (line length, trailing commas, blank lines) No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Narrow list|None unions with `is not None` guards instead of `if return_hidden_states` so ty can infer the type - Add mlx/mlx.** to ty replace-imports-with-any (optional dep absent from the CPU lint env, same pattern as flash_attn) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Combines two complementary Apple Silicon contributions into a single PR:
Layer 1 — PyTorch MPS routing (credit: @eamag, PR #409)
esm/utils/device.py:resolve_device("auto")picks CUDA → MPS → CPUget_default_model_dtype(): float32 on MPS, bfloat16 on CUDAfrom_pretrainedcalls updated todevice="auto"Layer 2 — Native MLX backend for all ESMC models
esm/models/esmc/mlx_model.py: full ESMC forward pass in Apple MLX — no PyTorch, zero CPU↔GPU copiesLayer 3 — Unified factory
load_esmc(model_name, device="auto")selects the optimal backend automatically:EsmcForMaskedLM(bfloat16)EsmcMLX(native Metal, no PyTorch overhead)EsmcForMaskedLM(float32, via feat: add Apple Metal (MPS) support #409)EsmcForMaskedLMWhy two backends for Apple Silicon?
MPS is the correct fallback when
mlxis not installed. MLX is the preferred path when it is.Model coverage
Correctness
tol=0.01)Closes / builds on
Test plan
load_esmc("biohub/ESMC-300M")on M-series Mac selects MLX backendload_esmc("biohub/ESMC-300M", "mps")forces MPS pathload_esmc("biohub/ESMC-6B", "mlx")loads and runs on ≥16 GB Maccookbook/snippets/esmc_mlx.pyruns end-to-endmlxis an optional import)🤖 Generated with Claude Code