# ESMFold 蛋白质结构

**Meta AI 的超高速蛋白质结构预测** — 无需多序列比对，从氨基酸序列在几秒内预测 3D 蛋白质结构。

> 🧬 开发者 **Meta AI 研究** | MIT 许可证 | 比 AlphaFold2 快 10x–60x

***

## 什么是 ESMFold？

ESMFold 是 Meta AI 的蛋白质结构预测系统，利用 **Evolutionary Scale Modeling (ESM-2)** — 世界上最大的蛋白质语言模型（150 亿参数）— 直接从氨基酸序列预测 3D 蛋白质结构。

### 相较于 AlphaFold2 的主要优势

| 功能            | ESMFold   | AlphaFold2  |
| ------------- | --------- | ----------- |
| 是否需要 MSA      | ❌ 否       | ✅ 是         |
| 速度（典型蛋白质）     | **约 2 秒** | 约 10 分钟–数小时 |
| 准确性（TM-score） | \~0.87    | \~0.92      |
| GPU 显存（650aa） | ≈8GB      | ≈8GB        |
| 单序列输入         | ✅ 是       | 有限          |
| 孤立蛋白（无同源序列）   | ✅ 优秀      | 困难          |

### 为什么不使用 MSA？

AlphaFold2 需要 **多序列比对（MSA）** — 收集并比对查询蛋白的进化同源序列。这个过程计算代价高，对于没有进化同源的全新或工程化蛋白是不可行的。

ESMFold 将进化信息存储在其语言模型权重中 **（训练于 2.5 亿条蛋白质序列），** 从而完全消除 MSA。这样它变得：

* **更快：** 无需 MSA 搜索（每次预测节省数分钟）
* **更具可扩展性：** 高效处理整个蛋白组
* **对新型蛋白更友好：** 工程化序列没有进化同源

***

## 在 Clore.ai 上快速开始

### 步骤 1：选择服务器

在 [clore.ai](https://clore.ai) 市场：

* **最低要求：** NVIDIA GPU，带有 **16GB 显存** （ESM-2 语言模型较大）
* **推荐：** A100 40GB、RTX 3090、RTX 4090 可用于完整模型
* **较小的选项：** 使用 `esm2_t33_650M_UR50D` 适用于 8GB 显存

GPU 显存指南：

| 蛋白质长度        | 模型变体        | 所需显存   |
| ------------ | ----------- | ------ |
| 最多 300 个氨基酸  | ESMFold（3B） | 约 16GB |
| 最多 500 个氨基酸  | ESMFold（3B） | 约 20GB |
| 最多 1000 个氨基酸 | ESMFold（3B） | 约 40GB |
| 最多 600 个氨基酸  | ESMFold（分块） | ≈8GB   |

### 步骤 2：构建自定义 Docker 镜像

```dockerfile
FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel

# 系统依赖
RUN apt-get update && apt-get install -y \
    git \
    wget \
    curl \
    openssh-server \
    libhdf5-dev \
    pkg-config \
    && rm -rf /var/lib/apt/lists/*

# 配置 SSH
RUN mkdir /var/run/sshd && \
    echo 'root:esmfold' | chpasswd && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# 安装 ESMFold 及依赖
RUN pip install --no-cache-dir \
    fair-esm[esmfold] \
    torch \
    biopython \
    biotite \
    fastapi \
    uvicorn \
    pydantic \
    openmm==8.0.0 \
    pdbfixer

# 安装 OpenFold（ESMFold 所需）
RUN pip install "git+https://github.com/aqlaboratory/openfold.git@4b41059694619831a7db195b7e0988fc4ff3a307"

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]
```

### 步骤 3：在 Clore.ai 上部署

* **Docker 镜像：** `yourname/esmfold:latest`
* **端口：** `22` （SSH）
* **环境：** `NVIDIA_VISIBLE_DEVICES=all`

***

## 安装与设置

### 方法 1：pip 安装

```bash
# 安装 ESMFold
pip install fair-esm[esmfold]

# 安装 OpenFold（必需依赖）
pip install "git+https://github.com/aqlaboratory/openfold.git@4b41059694619831a7db195b7e0988fc4ff3a307"

# 可选，但推荐
pip install biotite biopython
```

### 方法 2：从源码安装

```bash
git clone https://github.com/facebookresearch/esm.git
cd esm
pip install -e ".[esmfold]"
```

### 验证安装

```python
import esm
print("ESM 版本：", esm.__version__)

# 快速模型加载测试
model = esm.pretrained.esmfold_v1()
print("ESMFold 加载成功！")
```

***

## 基本用法

### 预测单个蛋白质结构

```python
import torch
import esm

# 加载 ESMFold 模型
model = esm.pretrained.esmfold_v1()
model = model.eval().cuda()

# 可选：启用分块大小以节省显存
# 增加计算时间但减少显存使用
model.set_chunk_size(64)  # 减少以降低显存需求

# 蛋白质序列（示例：溶菌酶 C）
sequence = "KVFGRCELAAAMKRHGLDNYRGYSLGNWVCAAKFESNFNTQATNRNTDGSTDYGILQINSRWWCNDGRTPGSRNLCNIPCSALLSSDITASVNCAKKIVSDGNGMNAWVAWRNRCKGTDVQAWIRGCRL"

# 预测结构
with torch.no_grad():
    output = model.infer_pdb(sequence)

# 保存 PDB 文件
with open("lysozyme.pdb", "w") as f:
    f.write(output)

print(f"结构已预测！已保存到 lysozyme.pdb")
print(f"序列长度：{len(sequence)} 个氨基酸")
```

### 批量预测多个序列

```python
import torch
import esm

model = esm.pretrained.esmfold_v1()
model = model.eval().cuda()

sequences = {
    "protein_A": "MKTAYIAKQRQISFVKSHFSRQ...",
    "protein_B": "MGDVEKGKKIFVQKCAQCHTVEK...",
    "ubiquitin": "MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDYNIQKESTLHLVLRLRGG",
}

for name, seq in sequences.items():
    with torch.no_grad():
        output = model.infer_pdb(seq)
    
    with open(f"{name}.pdb", "w") as f:
        f.write(output)
    
    print(f"已预测 {name}：{len(seq)} aa")

print("所有预测完成！")
```

### 获取每残基置信度（pLDDT）

```python
import torch
import esm
import numpy as np

model = esm.pretrained.esmfold_v1()
model = model.eval().cuda()

sequence = "MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDYNIQKESTLHLVLRLRGG"

with torch.no_grad():
    output = model.infer(sequence)

# 提取 pLDDT 分数（每残基置信度）
plddt = output["plddt"].cpu().numpy()  # 形状: [1, seq_len]
plddt_per_residue = plddt[0]

print(f"平均 pLDDT：{plddt_per_residue.mean():.2f}")
print(f"高置信残基（>90）：{(plddt_per_residue > 90).sum()}")
print(f"低置信残基（<50）：{(plddt_per_residue < 50).sum()}")

# 将置信区间分类
for i, score in enumerate(plddt_per_residue):
    if score >= 90:
        confidence = "非常高（蓝色）"
    elif score >= 70:
        confidence = "可信（浅蓝）"
    elif score >= 50:
        confidence = "低（黄色）"
    else:
        confidence = "非常低（橙色）"
    # print(f"残基 {i+1}：{score:.1f} - {confidence}")  # 取消注释以输出完整信息
```

***

## REST API 服务器

为 ESMFold 构建生产级 API：

```python
# api_server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import torch
import esm
import base64
from typing import Optional

app = FastAPI(
    title="ESMFold 蛋白质结构预测 API",
    description="从氨基酸序列预测蛋白质 3D 结构",
    version="1.0.0"
)

# 在启动时加载模型
print("正在加载 ESMFold 模型（约需 30 秒）...")
model = esm.pretrained.esmfold_v1()
model = model.eval().cuda()
model.set_chunk_size(64)  # 内存优化
print("ESMFold 已就绪！")

class PredictionRequest(BaseModel):
    sequence: str
    name: Optional[str] = "protein"

class PredictionResponse(BaseModel):
    name: str
    sequence_length: int
    pdb_content: str
    mean_plddt: float
    inference_time_seconds: float

@app.post("/predict", response_model=PredictionResponse)
async def predict_structure(request: PredictionRequest):
    """从氨基酸序列预测蛋白质 3D 结构。"""
    
    # 验证序列
    valid_aa = set("ACDEFGHIKLMNPQRSTVWY")
    sequence = request.sequence.upper().strip()
    
    invalid = set(sequence) - valid_aa
    if invalid:
        raise HTTPException(
            status_code=400,
            detail=f"序列中包含无效氨基酸：{invalid}。请使用标准的 20 种氨基酸。"
        )
    
    if len(sequence) > 2000:
        raise HTTPException(
            status_code=400,
            detail="序列过长（最多 2000 个氨基酸）。对于更长序列，请使用分块预测。"
        )
    
    start_time = time.time()
    
    try:
        with torch.no_grad():
            output = model.infer(sequence)
            pdb_content = model.output_to_pdb(output)[0]
            
        plddt = output["plddt"].cpu().numpy()[0]
        mean_plddt = float(plddt.mean())
        
    except torch.cuda.OutOfMemoryError:
        torch.cuda.empty_cache()
        raise HTTPException(
            status_code=507,
            detail="GPU 内存不足。尝试更短的序列或减小分块大小。"
        )
    
    inference_time = time.time() - start_time
    
    return PredictionResponse(
        name=request.name,
        sequence_length=len(sequence),
        pdb_content=pdb_content,
        mean_plddt=mean_plddt,
        inference_time_seconds=round(inference_time, 2)
    )

@app.get("/health")
def health():
    gpu_mem = torch.cuda.memory_allocated() / 1024**3 if torch.cuda.is_available() else 0
    return {
        "status": "ok",
        "model": "ESMFold v1",
        "device": str(next(model.parameters()).device),
        "gpu_memory_gb": round(gpu_mem, 2)
    }

@app.get("/")
def root():
    return {"message": "ESMFold API — /predict 用于预测结构，/docs 为 Swagger UI"}
```

```bash
# 运行 API
pip install fastapi uvicorn
uvicorn api_server:app --host 0.0.0.0 --port 8080 --workers 1
```

***

## API 使用示例

```bash
# 通过 API 预测结构
curl -X POST http://localhost:8080/predict \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ubiquitin",
    "sequence": "MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDYNIQKESTLHLVLRLRGG"
  }' | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"Name: {data['name']}\")
print(f\"Length: {data['sequence_length']} aa\")
print(f\"Mean pLDDT: {data['mean_plddt']:.1f}\")
print(f\"Time: {data['inference_time_seconds']}s\")
# 保存 PDB
open('ubiquitin.pdb', 'w').write(data['pdb_content'])
print('PDB 已保存！')
"
```

***

## 批处理脚本

```python
# batch_predict.py
import torch
import esm
import os
from pathlib import Path
from Bio import SeqIO  # pip install biopython

def predict_fasta(fasta_file: str, output_dir: str, chunk_size: int = 64):
    """为 FASTA 文件中的所有序列预测结构。"""
    
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    
    # 加载模型
    model = esm.pretrained.esmfold_v1()
    model = model.eval().cuda()
    model.set_chunk_size(chunk_size)
    
    # 读取 FASTA
    sequences = list(SeqIO.parse(fasta_file, "fasta"))
    print(f"正在为 {len(sequences)} 个蛋白质预测结构...")
    
    results = []
    for i, record in enumerate(sequences):
        seq = str(record.seq).upper()
        name = record.id
        
        print(f"[{i+1}/{len(sequences)}] 正在预测 {name}（{len(seq)} aa）...")
        
        try:
            with torch.no_grad():
                output = model.infer(seq)
                pdb = model.output_to_pdb(output)[0]
            
            plddt = output["plddt"].cpu().numpy()[0].mean()
            
            # 保存 PDB
            output_path = os.path.join(output_dir, f"{name}.pdb")
            with open(output_path, "w") as f:
                f.write(pdb)
            
            results.append({
                "name": name,
                "length": len(seq),
                "mean_plddt": round(float(plddt), 2),
                "output": output_path,
                "status": "success"
            })
            
        except Exception as e:
            print(f"  错误：{e}")
            results.append({"name": name, "status": f"error: {e}"})
    
    # 写入汇总
    import csv
    with open(os.path.join(output_dir, "summary.csv"), "w") as f:
        writer = csv.DictWriter(f, fieldnames=["name", "length", "mean_plddt", "output", "status"])
        writer.writeheader()
        writer.writerows(results)
    
    success = sum(1 for r in results if r.get("status") == "success")
    print(f"\n完成！{success}/{len(sequences)} 个结构预测成功")
    print(f"结果已保存到 {output_dir}/")

if __name__ == "__main__":
    predict_fasta(
        fasta_file="./proteins.fasta",
        output_dir="./predicted_structures",
        chunk_size=64
    )
```

***

## 结构可视化

### 使用 Py3Dmol（Jupyter / Python）

```python
import py3Dmol  # pip install py3Dmol

with open("protein.pdb") as f:
    pdb_data = f.read()

view = py3Dmol.view(width=800, height=600)
view.addModel(pdb_data, "pdb")
view.setStyle({"cartoon": {"colorscheme": "ssJmol"}})
view.zoomTo()
view.show()
```

### 使用 PyMOL

```bash
# 安装 PyMOL
apt-get install pymol

# 打开结构
pymol lysozyme.pdb
```

### 使用 Biotite 的编程化可视化

```python
import biotite.structure.io.pdb as pdb
import biotite.structure as struc
import numpy as np

# 加载预测结构
pdb_file = pdb.PDBFile.read("lysozyme.pdb")
structure = pdb.get_structure(pdb_file, model=1)

# 分析二级结构
sse = struc.annotate_sse(structure)

helix_frac = (sse == 'a').mean() * 100
sheet_frac = (sse == 'b').mean() * 100
coil_frac = (sse == 'c').mean() * 100

print(f"二级结构组成：")
print(f"  α 螺旋：  {helix_frac:.1f}%")
print(f"  β 折叠：   {sheet_frac:.1f}%")
print(f"  Coil/其他：   {coil_frac:.1f}%")
```

***

## 内存优化

### 分块大小指南

```python
# 更低的 chunk_size = 更少显存，预测更慢
# 更高的 chunk_size = 更多显存，预测更快

# 对于 8GB 显存（允许最多 ~400 aa）
model.set_chunk_size(32)

# 对于 16GB 显存（可达 ~700 aa）
model.set_chunk_size(64)

# 对于 40GB 显存（可达 ~2000 aa，无需分块）
model.set_chunk_size(None)  # 禁用分块
```

### 超长序列的 CPU 卸载

```python
# 在 CPU 上加载模型，每次推理时移动到 GPU
model = esm.pretrained.esmfold_v1()
model = model.eval()

# 在推理时移动到 GPU，之后移回 CPU
model = model.cuda()
with torch.no_grad():
    output = model.infer(sequence)
model = model.cpu()  # 释放 GPU 内存
torch.cuda.empty_cache()
```

***

## 故障排除

### CUDA 内存不足（Out of Memory）

```bash
# 减小分块大小
model.set_chunk_size(32)  # 或甚至 16

# 检查可用显存
nvidia-smi --query-gpu=memory.free --format=csv,noheader

# 对于超长蛋白，按结构域拆分
# 通常将 >1000 aa 的蛋白拆分为 300-500 aa 的结构域是安全的
```

### openfold 导入错误

```bash
# 使用特定提交重新安装
pip install "git+https://github.com/aqlaboratory/openfold.git@4b41059694619831a7db195b7e0988fc4ff3a307"

# 检查安装
python -c "import openfold; print('OpenFold OK')"
```

### 模型加载慢

```bash
# 第一次加载会下载 2.7GB 的模型权重 — 这是正常的
# 随后加载使用缓存的权重（约 30 秒加载时间）

# 检查缓存位置
python -c "import torch; print(torch.hub.get_dir())"
ls ~/.cache/torch/hub/
```

{% hint style="warning" %}
**内存说明：** ESMFold 的语言模型（ESM-2 15B 参数）需要大量显存。对于显存少于 16GB 的 GPU 服务器，请使用 `esm2_t33_650M_UR50D` 骨干变体或启用激进的分块。
{% endhint %}

{% hint style="info" %}
**pLDDT 解释：**

* **>90** = 非常高的置信度（AlphaFold 着色中的蓝色）
* **70–90** = 可信（青/浅蓝）
* **50–70** = 低置信度（黄色）— 请谨慎对待
* **<50** = 非常低置信度（橙/红）— 可能为无序区
  {% endhint %}

***

## Clore.ai 的 GPU 建议

ESMFold 的显存需求主要由 ESM-2 15B 参数语言模型决定。序列长度会增加额外的内存开销。

| GPU               | 显存（VRAM） | Clore.ai 价格 | 最大序列长度          | 预测时间（300 aa） |
| ----------------- | -------- | ----------- | --------------- | ------------ |
| RTX 3090          | 24 GB    | \~$0.12/小时  | \~400 aa（使用分块）  | 约 8 秒        |
| RTX 4090          | 24 GB    | \~$0.70/小时  | \~400 aa（使用分块）  | 约 5 秒        |
| A100 40GB         | 40 GB    | \~$1.20/小时  | \~800 aa 可舒适处理  | 约 3 秒        |
| 💡 本指南中的所有示例均可部署在 | 80 GB    | \~$2.00/小时  | \~1500+ aa，大型蛋白 | 约 4 秒        |

{% hint style="warning" %}
**最低显存：16GB。** 使用完整 ESM-2 骨干时，ESMFold 无法在 8GB GPU 上运行。RTX 3090/4090（24GB）可在不分块的情况下处理最多 \~400 个氨基酸的蛋白 —— 在 API 中启用 `chunk_size=64` 以处理更长序列。
{% endhint %}

**研究性价比最高：** RTX 3090 每小时约 $0.12，可处理绝大多数蛋白质结构预测任务（人类平均蛋白：约 300–400 aa）。在每次预测约 8 秒的情况下，你每小时可处理约 450 个结构，成本约 $0.12 —— 与需要进行每个结构数分钟 MSA 计算的 AlphaFold2 相比具有优势。

**高通量蛋白组学：** 对于筛选数千个序列，A100 40GB（约 $1.20/小时）结合批量推理每小时可处理 \~1200+ 次预测 —— 适用于蛋白组规模的研究。

***

## 资源

* 🐙 **GitHub：** [github.com/facebookresearch/esm](https://github.com/facebookresearch/esm)
* 🤗 **模型：** [huggingface.co/facebook/esmfold\_v1](https://huggingface.co/facebook/esmfold_v1)
* 📄 **论文：** [使用语言模型进行原子级蛋白质结构的进化尺度预测（Science, 2023）](https://www.science.org/doi/10.1126/science.ade2574)
* 🌐 **ESM 元基因组图谱：** [esmatlas.com](https://esmatlas.com) — 使用 ESMFold 预测了 7.72 亿 个结构
* 💻 **Meta AI 博客：** [ai.meta.com/blog/protein-folding-esmfold-metagenomics](https://ai.meta.com/blog/protein-folding-esmfold-metagenomics/)
* 🔬 **ESM 更新日志：** [github.com/facebookresearch/esm/blob/main/CHANGELOG.md](https://github.com/facebookresearch/esm/blob/main/CHANGELOG.md)
