I have learnt, that if you want a graph safe way of using conditionals inside of a PyTorch model you should use torch.cond. It will reduce the painful memcpy (
) events between the host and device (CUDA) which while algorithmically not a problem, performance wise makes inference very slow.
The way I have used torch.cond should be correct (right?) yet also it is a bit “ugly”. I was wondering if there are any best practices that come with using torch.cond to make the model description concise yet performative? Searching on the pytorch/examples repo, I could not find any examples.
Or is this as good as it gets? This would be a good conclusion also ![]()
Minimal example ![]()
import torch
import torch.nn as nn
class EarlyExitMLP(nn.Module):
def __init__(self, in_features=32, hidden=64, num_classes=10):
super().__init__()
# backbone with three FC layers
self.backbone = nn.ModuleList([
nn.Linear(in_features, hidden),
nn.Linear(hidden, hidden),
nn.Linear(hidden, hidden),
])
# 3 early exits
self.exit1 = nn.Linear(hidden, num_classes)
self.exit2 = nn.Linear(hidden, num_classes)
self.exit3 = nn.Linear(hidden, num_classes)
# same as self.exit3
self.final = nn.Linear(hidden, num_classes)
# threshold met exit check
def should_exit(self, logits, threshold):
probs = torch.softmax(logits, dim=-1)
return probs.max(dim=-1).values > threshold
def result(self, logits, exit_id):
return {"exit_id": exit_id, "logits": logits}
def forward_early_exit(self, x, t1, t2, t3):
# we consider batch size of one, so no variable input tensor shape complex stuff
if x.shape[0] != 1:
raise ValueError("Early-exit inference requires a batch size of 1.")
x = x.flatten(1)
h1 = torch.relu(self.backbone[0](x))
logits1 = self.exit1(h1)
# this part "works", but how to rewrite it?
def after_exit1(h1, logits1):
h2 = torch.relu(self.backbone[1](h1))
logits2 = self.exit2(h2)
def after_exit2(h2, logits2):
h3 = torch.relu(self.backbone[2](h2))
logits3 = self.exit3(h3)
def after_exit3(h3, logits3):
final_logits = self.final(h3)
return self.result(final_logits, 4)
return torch.cond(
self.should_exit(logits3, t3),
lambda h3, logits3: self.result(logits3, 3),
after_exit3,
(h3, logits3),
)
return torch.cond(
self.should_exit(logits2, t2),
lambda h2, logits2: self.result(logits2, 2),
after_exit2,
(h2, logits2),
)
return torch.cond(
self.should_exit(logits1, t1),
lambda h1, logits1: self.result(logits1, 1),
after_exit1,
(h1, logits1),
)
# Example
model = EarlyExitMLP()
x = torch.randn(1, 32)
out = model.forward_early_exit(x, t1=0.9, t2=0.8, t3=0.7) # dummy threshold values
print(out)
I know there are some complexities when we have dynamic shapes (e.g. if some samples in a batch exit while others remain) or having multiple tensor output but here I am mainly concerned with a batch size of 1. I also understand that torch.cond is a prototype feature, yet I simply cannot find a better alternative for conditional graphs.
