Protein sequence and inverse folding
Sequence-conditioned ProteinMPNN decoder stack
ProteinMPNNLigandMPNN
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Update each residue’s feature vector using its structural neighbors and only the amino-acid identities allowed by the supplied decoding-order mask. A visible neighbor contributes its current decoder state and sequence embedding; an unseen neighbor contributes only its original encoder state. Return the final features and every intermediate layer state.
Function signature
proteinmpnn_decoder(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
eps: float = 1e-05,
scale: float = 30.0,
dropout: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor]Worked example
What one neighbor is allowed to reveal
Consider the message-input stage with one feature channel. Let the current query state be H_d[i]=2, its edge feature be 1, and neighbor j have sequence embedding 7, original encoder state H_0[j]=5, and current decoder state H_d[j]=3. If the edge is active and backward_mask=True, concatenate [2,1,7,3]. If the edge is active but backward_mask=False, concatenate [2,1,0,5]. The latter retains structural information while withholding the unseen amino-acid embedding and decoder state. These vectors then enter the same supplied message MLP.
Parameters
| Parameter | Shape and meaning |
|---|---|
activationsdict | Supplied intermediate tensors. |
parametersdict | Supplied model parameters, keyed as shown below. |
masksdict | Supplied masks. Each field below defines what True includes or permits. |
structuredict | Fields supplied together in this dictionary. |
epsfloatDefault 1e-05 | Positive stabilizer inside sqrt(population channel variance + eps) in both LayerNorm operations. |
scalefloatDefault 30.0 | Positive divisor of the summed active neighbor messages at every layer; it is independent of the neighbor count. |
dropoutfloatDefault 0.0 | Probability used with supplied keep masks. Do not draw new random masks. |
Return
tuple[torch.Tensor, torch.Tensor]
Return these tensors in the stated order.
Required behavior
B,L,C,D,F \ge 1,K\ge 0,V\ge 2. Start\mathrm{H0}=\mathrm{encoder\_nodes}masked to zero on invalidnodes; keep it as the immutable structural context.E=\mathrm{encoder\_edges}[B,L,K,C];S=\mathrm{embedding}[\mathrm{tokens}]has shape [B,L,C]. All floating inputs are finite and share dtype/device.- weights have
[\mathrm{input},\mathrm{output}]orientation and a leadingDlayer axis. Effective edge validitya_{\mathrm{ijk}}=\mathrm{edge\_mask}AND both endpoint node masks.backward_maskis supplied from a valid strict decoding order (therefore no self/future edges); it is intersected with a, and is not recomputed here. - At layer
d, for neighborjofi, lets_{\mathrm{ij}}=S_{j}andn_{\mathrm{ij}}=H_{d},jon active backward edges; otherwises_{\mathrm{ij}}=0andn_{\mathrm{ij}}=\mathrm{H0\_j}. The message input isCONCAT[H_{d},i, E_{\mathrm{ij}}, s_{\mathrm{ij}}, n_{\mathrm{ij}}], exactly that4 Corder. - Apply affine
\mathrm{message\_w1}/\mathrm{b1} \to \mathrm{exact} \operatorname{GELU} \to \mathrm{affine} \mathrm{message\_w2}/\mathrm{b2} \to \mathrm{exact} \operatorname{GELU} \to \mathrm{affine} \mathrm{message\_w3}/\mathrm{b3}. Sum only active messages overKand divide by\mathrm{scale}>0(default30), not by degree. - Then
U=\mathrm{LayerNorm}(H_{d} + \mathrm{supplied\_dropout}(\mathrm{message\_update}), \mathrm{norm1\_scale}/\mathrm{bias}). - Let
f=\operatorname{GELU}(U\mathbin{@}\mathrm{ffn\_up}+\mathrm{ffn\_up\_bias})\mathbin{@}\mathrm{ffn\_down}+\mathrm{ffn\_down\_bias};H_{\mathrm{}}(d+1)=\mathrm{LayerNorm}(U+\mathrm{supplied\_dropout}(f),\mathrm{norm2\_scale}/\mathrm{bias}), zeroing invalidnodesafterwards. Exact GELU isx\cdot (1+\operatorname{erf}(x/\sqrt{2}))/2. - LayerNorm uses population channel variance with
\mathrm{eps}>0inside the square root (default10^{-5}). Config\mathrm{dropout}=pin[0,1), default0. - The two supplied keep masks [
D,B,L,C] define\mathrm{dropout}(x)=\operatorname{where}(\mathrm{keep},x/(1-p),0); they are applied even ifp=0, so source inference corresponds top=0and all keepsTrue. These are the onlykeys. - Return
\mathrm{nodes}=H_{D}and\mathrm{states}=\mathrm{stack}(\mathrm{H0},\ldots,H_{D})with shape [D+1,B,L,C]. No output projection, softmax, sampling, edge updates or residual outside the specified stack.K=0means zero message aggregation, but the residual/norm/FFN path still executes. F=4Cgives the upstream transition width; other positiveFis a stated teaching extension. Noncontiguousstates/weights and padding must work without mutation.- The message concatenation is
[H_d[i],E_{ij},s_{ij},n_{ij}]. A visible neighbor usesn_{ij}=H_d[j]; an unseen neighbor uses the unchangedH_0[j].
Why this matters
Known neighbors can supply their sequence embedding and current decoder state. Future neighbors supply only the original encoder state. This preserves structural context while enforcing the supplied decoding order.
Starter code
You write the body of this function in the BioTorch workspace. Your code runs in an isolated PyTorch sandbox; Run checks the public examples and Submit grades the full test suite, including gradients.
import torch
def proteinmpnn_decoder(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
eps: float = 1e-05,
scale: float = 30.0,
dropout: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Return tuple[torch.Tensor, torch.Tensor]:
nodes: (B, L, C).
states: (D+1, B, L, C)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement proteinmpnn_decoder")
Ready to implement it?
3 progressive hints, a solution walkthrough, and grading that checks values, shapes and gradients are waiting in the workspace.
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Sources and further reading
A primitive exercise, not an end-to-end reproduction of the named models. No affiliation or endorsement is implied.