Skip to content

feat: NPU (Ascend 910B3) support for RFdiffusion inference - #459

Open
xuejiakn wants to merge 1 commit into
RosettaCommons:mainfrom
xuejiakn:feat/npu-ascend-support
Open

feat: NPU (Ascend 910B3) support for RFdiffusion inference#459
xuejiakn wants to merge 1 commit into
RosettaCommons:mainfrom
xuejiakn:feat/npu-ascend-support

Conversation

@xuejiakn

Copy link
Copy Markdown

Description

This PR adapts RFdiffusion to run on Huawei Ascend NPU (Ascend 910B3) with torch_npu, enabling protein structure generation on NPU hardware.

Background

RFdiffusion depends on PyTorch + DGL + e3nn + SE3Transformer, all originally CUDA-only. This PR replaces CUDA-specific calls with NPU-compatible alternatives and patches operators not supported on NPU.

Changes (8 files, +114 -16 lines)

File Change
rfdiffusion/__init__.py allow_internal_format=False + torch.cdist NPU patch (manual bmm + sqrt)
rfdiffusion/inference/model_runners.py Device: torch.cudatorch.npu
rfdiffusion/Track_module.py autocast: torch.cuda.amp.autocasttorch.amp.autocast(device_type="npu")
scripts/run_inference.py NPU detection (NPU > CUDA > CPU), torch.npu.empty_cache(), device metadata
SE3Transformer/model/basis.py nvtx three-tier fallback: CUDA → record_function → no-op
SE3Transformer/model/layers/attention.py nvtx fallback + e_dot_v manual implementation
SE3Transformer/model/layers/convolution.py nvtx fallback
SE3Transformer/model/layers/norm.py nvtx fallback

Key Adaptations

  1. Device migration: All torch.cuda.* calls replaced with torch.npu.* equivalents
  2. Internal format disabled: torch.npu.config.allow_internal_format = False to fix torch.cat tensor format conflicts in SE3Transformer
  3. torch.cdist patch: NPU does not support torch.cdist natively; patched with ||x1-x2|| = sqrt(||x1||² + ||x2||² - 2*x1·x2^T)
  4. nvtx fallback: torch.cuda.nvtx.range import succeeds but throws at runtime on NPU; three-tier fallback with torch.autograd.profiler.record_function for NPU profiling support
  5. DGL e_dot_v: Ascend DGL SDDMM only supports lhs_target=0; replaced with manual (key * query[dst]).sum(dim=-1)

Prerequisites

  • DGL Ascend version with SpMM copy_rhs fix (see dgl-ascend PR #24)
  • PyTorch 2.7.1+cpu + torch_npu 2.7.1.post4
  • CANN 8.2.RC1, Ascend 910B3

Verification

Metric NPU CPU Diff
pLDDT (20-step deterministic) 0.9885 0.9885 0.000005
Inference time (20 steps) 43.3s 664.7s 15.35x speedup
  • All 6 checkpoints verified (Base, ActiveSite, Complex, InpaintSeq, etc.)
  • pLDDT > 0.95 for 50-residue designs (normal random mode)
  • 20-step inference quality matches 200-step (consistent with paper)

Type

  • New feature (NPU hardware support)
  • Bug fix
  • Performance optimization
  • Documentation update
  • Other

Adapt RFdiffusion to run on Huawei Ascend NPU (Ascend 910B3) with
torch_npu, enabling protein structure generation on NPU hardware.

Changes (8 files, +114 -16 lines):

1. rfdiffusion/__init__.py:
   - Global torch.npu.config.allow_internal_format = False
   - Patch torch.cdist for NPU (manual bmm + sqrt implementation)

2. rfdiffusion/inference/model_runners.py:
   - Device selection: torch.cuda -> torch.npu

3. rfdiffusion/Track_module.py:
   - autocast: torch.cuda.amp.autocast -> torch.amp.autocast(device_type='npu')

4. scripts/run_inference.py:
   - NPU device detection (priority NPU > CUDA > CPU)
   - torch.npu.empty_cache() support
   - TRB metadata records actual device name

5-8. SE3Transformer (basis.py, attention.py, convolution.py, norm.py):
   - nvtx range three-tier fallback: CUDA nvtx -> NPU record_function -> no-op
   - dgl.ops.e_dot_v replaced with manual implementation (Ascend SDDMM
     only supports lhs_target=0)

Verified: RFdiffusion NPU pLDDT=0.9885, CPU pLDDT=0.9885, diff=0.000005
NPU speedup: 15.35x over CPU (43.3s vs 664.7s for 20-step inference)

Environment: PyTorch 2.7.1+cpu, torch_npu 2.7.1.post4, CANN 8.2.RC1,
DGL 2.5 (Ascend), Ascend 910B3
@rclune

rclune commented Sep 8, 2026

Copy link
Copy Markdown
Member

Thank you for this PR.

There are newer version of RFdiffusion (most notably RFD3) whose dependencies are more up to date. Do you run into similar issues trying to RFD3 on the NPU resources you have access to?

@rclune rclune left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR. I have some concerns around ensuring that the changes made will still allow run_inference to work with GPU/CUDA systems. Please make sure that any NPU-specific additions are wrapped in conditionals or try/except clauses to ensure that the GPU functionality is not changed.

Comment thread scripts/run_inference.py
import re
import os, time, pickle
import torch
import torch_npu

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many users of RFD only have access to GPU/CPU resources. Adding this import statement not in a try/except will make it impossible to run on GPU systems. I suggest wrapping it like you do in __init__.py.

self._log = logging.getLogger(__name__)
if torch.cuda.is_available():
self.device = torch.device("cuda")
if torch.npu.is_available():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please back in the check for torch.cuda.is_available() as a fallback. Right now only NPU is checked, so GPU users will be silently placed on CPUs. You can do something similar to what you have on lines 46-50 of run_inference.py.

nn.init.zeros_(self.embed_e2.bias)

@torch.cuda.amp.autocast(enabled=False)
@torch.amp.autocast(device_type="npu", enabled=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hardcodes device_type="npu", so on CUDA systems this decorator no longer disables autocast, meaning torch.cuda.amp.autocast(enabled=False)'s effect is lost. Should be conditional on the active device.


with nvtx_range('attention dot product + softmax'):
# Compute attention weights (softmax of inner product between key and query)
edge_weights = dgl.ops.e_dot_v(graph, key, query).squeeze(-1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of this for a manual implementation should be conditioned on the device type, similar to how the nvtx changes are.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants