Feature Extraction
Transformers
Safetensors
English
phagen
biology
dna
genomics
bacteriophage
diffusion
masked-diffusion
custom-code
custom_code
Instructions to use YusenWithR/PhaGen with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use YusenWithR/PhaGen with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="YusenWithR/PhaGen", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("YusenWithR/PhaGen", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 22,664 Bytes
264f262 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | # Adapted from https://github.com/pengzhangzhi/Open-dLLM
# Licensed under the Apache License, Version 2.0
import warnings
import copy
from dataclasses import dataclass
from typing import Any, Dict, Optional, Tuple, Union
import torch
import torch.distributions as dists
from torch.nn import functional as F
from transformers import __version__
from transformers.generation.configuration_utils import GenerationConfig
from transformers.utils import ModelOutput, is_torchdynamo_compiling, logging
logger = logging.get_logger(__name__)
# origin reveals randomly selected masked positions.
# entropy, topk_margin and maskgit_plus rank masked positions by confidence.
# p2 can re-mask generated positions; supplied prompt tokens remain fixed.
def top_p_logits(logits, top_p=None):
if top_p is None or top_p >= 1.0:
return logits
if not (0.0 < top_p <= 1.0):
raise ValueError(f"`top_p` must be in (0, 1], got {top_p}")
# top-p sampling
## Keep the smallest set of tokens whose cumulative probability reaches top_p and send the rest to negative infinity.
# 1. Sort the logits.
sorted_logits, sorted_indices = torch.sort(logits, dim=-1, descending=True)
# 2. Take softmax probabilities, then their cumulative sum.
cumulative_probs = torch.cumsum(F.softmax(sorted_logits.float(), dim=-1), dim=-1)
# 3. Find where the cumulative probability exceeds top_p.
sorted_indices_to_remove = cumulative_probs > top_p
# 4. Shift the mask so at least one token always survives.
# Shifting the cumulative probability right by one guarantees a sampleable token for any top_p.
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
sorted_indices_to_remove[..., 0] = False
# 5. Build the mask and apply it to the original logits.
mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device)
mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove)
logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min)
return logits
def top_k_logits(logits, top_k=None):
if top_k is None or top_k <= 0:
return logits
top_k = min(int(top_k), logits.size(-1))
# torch.topk(logits, top_k)[0] gives the values of the top_k elements.
# torch.topk(logits, top_k)[0][..., -1, None] gives the smallest of those top_k values.
# logits < that value then marks every position below the top_k threshold.
indices_to_remove = logits < torch.topk(logits, top_k, dim=-1).values[..., -1, None]
# Apply the mask.
logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min)
return logits
def restrict_logits_to_allowed_tokens(logits, allowed_token_ids=None):
if allowed_token_ids is None:
return logits
allowed_token_ids = sorted({int(token_id) for token_id in allowed_token_ids})
if not allowed_token_ids:
raise ValueError("`allowed_token_ids` must not be empty when provided.")
if allowed_token_ids[0] < 0 or allowed_token_ids[-1] >= logits.size(-1):
raise ValueError(
"`allowed_token_ids` contains an ID outside the model vocabulary: "
f"ids={allowed_token_ids}, vocab_size={logits.size(-1)}"
)
blocked = torch.ones(logits.size(-1), dtype=torch.bool, device=logits.device)
blocked[torch.tensor(allowed_token_ids, dtype=torch.long, device=logits.device)] = False
return logits.masked_fill(blocked, float("-inf"))
def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, alg="origin"):
if logits.size(-1) == 0:
raise ValueError("`logits` must have a non-empty vocabulary dimension.")
if temperature > 0:
logits = logits / temperature
logits = top_p_logits(logits, top_p)
if top_k is not None and top_k > 0:
logits = top_k_logits(logits, top_k)
probs = torch.softmax(logits.float(), dim=-1)
if temperature > 0:
# Sample from the distribution.
x0 = dists.Categorical(probs=probs).sample()
else:
# Greedy selection.
_, x0 = probs.max(dim=-1)
# Gather the softmax probability of the sampled token index and use it as the model's confidence at that position.
confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1)
if alg == "topk_margin":
if probs.size(-1) < 2:
raise ValueError("`topk_margin` requires vocabulary size >= 2.")
# Probability gap between the most likely and the second most likely token.
sorted_probs, _ = torch.sort(probs, dim=-1, descending=True)
top1_probs = sorted_probs[..., 0]
top2_probs = sorted_probs[..., 1]
confidence = top1_probs - top2_probs
elif alg == "entropy":
# Negative Shannon entropy: higher values indicate greater confidence.
log_probs = torch.log(probs.clamp(min=1e-10))
confidence = (probs * log_probs).sum(dim=-1)
elif alg in ["maskgit_plus", "origin", "p2"]:
pass
else:
raise NotImplementedError(f"Algorithm {alg} not implemented.")
return confidence, x0
@dataclass
class MDMModelOutput(ModelOutput):
sequences: torch.LongTensor = None
history: Optional[Tuple[torch.LongTensor, ...]] = None
class MDMGenerationConfig(GenerationConfig):
"""
Parameters (MDM-specific, popped before forwarding to GenerationConfig):
mask_token_id, steps, alg, alg_temp, block_pad_token,
allowed_token_ids,
output_history, pad_attention_mode, eps
Parameters (inherited from GenerationConfig, passed through via super()):
pad_token_id, max_new_tokens, temperature, top_p, top_k,
num_return_sequences, return_dict_in_generate, ...
"""
def __init__(self, **kwargs):
if 'do_sample' not in kwargs:
kwargs['do_sample'] = True
# --- MDM-specific (not recognized by GenerationConfig) ---
self.mask_token_id = kwargs.pop("mask_token_id", None)
self.steps: int = kwargs.pop("steps", 512)
self.alg: str = kwargs.pop("alg", "entropy")
self.alg_temp: Optional[float] = kwargs.pop("alg_temp", 0.0)
self.block_pad_token: bool = kwargs.pop("block_pad_token", False)
self.allowed_token_ids: Optional[list[int]] = kwargs.pop("allowed_token_ids", None)
self.output_history: bool = kwargs.pop("output_history", False)
self.pad_attention_mode: str = kwargs.pop("pad_attention_mode", "attend_pad")
self.eps: float = kwargs.pop("eps", 1e-3)
# --- Inherited (handled by GenerationConfig.__init__) ---
# pad_token_id: int | None (default: None)
# max_new_tokens: int | None (default: None)
# temperature: float (default: 1.0)
# top_p: float | None (default: None)
# top_k: int | None (default: None)
# num_return_sequences: int (default: 1)
# return_dict_in_generate: bool (default: False)
super().__init__(**kwargs)
class MDMGenerationMixin:
"""
Mixin class for Masked Diffusion Model generation, adapted from the **Dream** model's generation utils.
"""
@torch.no_grad()
def diffusion_generate(
self,
input_ids: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
generation_config: Optional[MDMGenerationConfig] = None,
**kwargs,
) -> Union[MDMModelOutput, torch.LongTensor]:
# 1. Prepare the generation config.
generation_config = self._mdm_prepare_generation_config(generation_config, **kwargs)
if input_ids is None:
raise ValueError("`input_ids` must be provided for diffusion generation.")
if generation_config.max_new_tokens is not None:
generation_config.max_length = input_ids.shape[-1] + generation_config.max_new_tokens
elif generation_config.max_length is None:
generation_config.max_length = input_ids.shape[-1]
if generation_config.max_length < input_ids.shape[-1]:
raise ValueError(f"`max_length` ({generation_config.max_length}) must be >= input length ({input_ids.shape[-1]}).")
if generation_config.steps <= 0:
raise ValueError(f"`steps` must be > 0, got {generation_config.steps}")
if not (0.0 < generation_config.eps <= 1.0):
raise ValueError(f"`eps` must be in (0, 1], got {generation_config.eps}")
# 3. Expand the input for multi-sequence generation.
input_ids, attention_mask = self._expand_inputs_for_generation(
expand_size=generation_config.num_return_sequences,
input_ids=input_ids,
attention_mask=attention_mask
)
if attention_mask is not None:
if attention_mask.ndim != 2:
raise ValueError(f"`attention_mask` must be 2D [batch, seq], got shape={tuple(attention_mask.shape)}")
if attention_mask.shape != input_ids.shape:
raise ValueError(f"`attention_mask` shape {tuple(attention_mask.shape)} must match `input_ids` shape {tuple(input_ids.shape)} before canvas padding.")
attention_mask = attention_mask.to(device=input_ids.device, dtype=torch.long)
if generation_config.mask_token_id is None:
raise ValueError("`mask_token_id` must be set in the generation config.")
pad_len = generation_config.max_length - input_ids.shape[-1]
input_ids = F.pad(input_ids, (0, pad_len), value=generation_config.mask_token_id)
if attention_mask is not None and pad_len > 0:
# New canvas region corresponds to generation region; keep it valid by default.
attention_mask = F.pad(attention_mask, (0, pad_len), value=1)
# 4. Run the sampling loop.
return self._mdm_sample(
x=input_ids,
attention_mask=attention_mask,
generation_config=generation_config
)
def _mdm_prepare_generation_config(
self,
generation_config: Optional[GenerationConfig],
**kwargs
) -> MDMGenerationConfig:
# Build the MDM generation config from the incoming generation_config.
if generation_config is None:
generation_config = self.generation_config
# Use MDMGenerationConfig as the target class.
if not isinstance(generation_config, MDMGenerationConfig):
generation_config = MDMGenerationConfig.from_dict(generation_config.to_dict())
# Update kwargs.
generation_config.update(**kwargs)
return generation_config
@staticmethod
def _expand_inputs_for_generation(
expand_size: int = 1,
input_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.LongTensor] = None
) -> Tuple[Optional[torch.LongTensor], Optional[torch.LongTensor]]:
if expand_size == 1:
return input_ids, attention_mask
if input_ids is not None:
input_ids = input_ids.repeat_interleave(expand_size, dim=0)
if attention_mask is not None:
attention_mask = attention_mask.repeat_interleave(expand_size, dim=0)
return input_ids, attention_mask
def _mdm_sample(
self,
x: torch.LongTensor,
attention_mask: Optional[torch.LongTensor],
generation_config: MDMGenerationConfig
) -> Union[MDMModelOutput, torch.LongTensor]:
# Extract params from config
mask_token_id = generation_config.mask_token_id
steps = generation_config.steps
eps = generation_config.eps
alg = generation_config.alg
alg_temp = generation_config.alg_temp
temperature = generation_config.temperature
top_p = generation_config.top_p
top_k = generation_config.top_k
histories = [] if generation_config.output_history else None
# Fixed tokens = input context (should never be remasked in p2)
fix_mask = (x != mask_token_id)
pad_attention_mode = generation_config.pad_attention_mode
if pad_attention_mode not in ("attend_pad", "mask_pad"):
raise ValueError(f"`pad_attention_mode` must be one of ['attend_pad', 'mask_pad'], got {pad_attention_mode}")
use_custom_attention_mask = attention_mask is not None
if use_custom_attention_mask:
if attention_mask.ndim != 2:
raise ValueError(f"`attention_mask` must be 2D [batch, seq], got shape={tuple(attention_mask.shape)}")
if attention_mask.shape != x.shape:
raise ValueError(f"`attention_mask` shape {tuple(attention_mask.shape)} must match generation canvas shape {tuple(x.shape)}.")
attention_mask = attention_mask.to(device=x.device, dtype=torch.long)
timesteps = torch.linspace(1, eps, steps + 1, device=x.device)
for i in range(steps):
mask_index = (x == mask_token_id)
if not mask_index.any(): # Stop if no tokens are masked
break
if use_custom_attention_mask:
# Caller-provided mask has highest priority.
gen_attention_mask = attention_mask
else:
# attend_pad lets PAD positions participate in attention;
# mask_pad excludes them using the configured PAD token ID.
if pad_attention_mode == "attend_pad":
gen_attention_mask = torch.ones_like(x, dtype=torch.long, device=x.device)
else:
if generation_config.pad_token_id is None:
gen_attention_mask = None
else:
gen_attention_mask = (x != generation_config.pad_token_id).long()
# Bidirectional attention is used throughout denoising.
outputs = self(input_ids=x, attention_mask=gen_attention_mask, is_causal=False)
logits = outputs.logits
# Optional: block PAD sampling if requested.
if generation_config.pad_token_id is not None and generation_config.block_pad_token:
logits[..., generation_config.pad_token_id] = float('-inf')
logits = restrict_logits_to_allowed_tokens(
logits, generation_config.allowed_token_ids
)
mask_logits = logits[mask_index] # select the masked positions
t = timesteps[i]
s = timesteps[i + 1]
# Start sampling.
if alg == "origin":
# s / t is the fraction of masked positions that stay masked.
# 1 - s / t is the probability that a masked token is revealed in this step, meaning replaced by a freshly sampled token.
# s / t falls as i grows, so p_transfer rises and later steps reveal more tokens at once.
p_transfer = 1 - s / t if i < steps - 1 else 1 # at i == steps - 1 every remaining masked token is revealed
# Tensor of mask_token_id with one entry per masked token.
x0 = torch.full_like(x[mask_index], fill_value=mask_token_id, device=x.device, dtype=torch.long)
# Random tensor shaped like x0, recording which positions fall below p_transfer.
transfer_index_t_s = torch.rand(*x0.shape, device=x.device) < p_transfer
# Sample the predicted tokens and write them into the matching entries of x0.
if transfer_index_t_s.any():
_, sampled_tokens = sample_tokens(mask_logits[transfer_index_t_s], temperature=temperature, top_p=top_p, top_k=top_k, alg=alg)
x0[transfer_index_t_s] = sampled_tokens
# Write x0 back into x, which holds the user prompt and the denoised output.
x[mask_index] = x0
elif alg == "p2":
# Compute confidence and sampled tokens for the entire sequence:
# conf_full: [B, L], confidence of the sampled token at each position
# x0_full: [B, L], sampled token IDs for each position
conf_full, x0_full = sample_tokens(logits, temperature=temperature, top_p=top_p, top_k=top_k, alg=alg)
# Construct full_conf matrix and mask out fixed positions
# Only positions in (~fix_mask) are candidates for masking/unmasking
full_conf = conf_full.clone()
full_conf[fix_mask] = float("inf")
# Prevent NaNs or extreme values from interfering
full_conf = torch.where(torch.isfinite(full_conf), full_conf, torch.full_like(full_conf, float("inf")))
# Increase the fraction of unmasked positions linearly over the steps.
kappa_t = (i + 1) / steps
# Calculate how many positions to re-mask per sample
# = number of variable positions * (1 - kappa_t)
num_positions = (~fix_mask).sum(dim=1) # [B]
num_to_mask = (num_positions.float() * (1.0 - kappa_t)).floor().to(torch.long)
# Boundaries: at least 0, at most total number of variable positions
num_to_mask = num_to_mask.clamp_min(0)
num_to_mask = torch.minimum(num_to_mask, num_positions)
# Select the lowest-confidence positions within (~fix_mask) for re-masking
sorted_idx = torch.argsort(full_conf, dim=1, descending=False) # [B, L]
max_k = int(num_to_mask.max().item())
if max_k > 0:
topk_idx = sorted_idx[:, :max_k] # [B, max_k]
row_mask = torch.arange(max_k, device=x.device).unsqueeze(0) < num_to_mask.unsqueeze(1) # [B, max_k]
to_mask = torch.zeros_like(x, dtype=torch.bool)
batch_arange = torch.arange(x.size(0), device=x.device).unsqueeze(1).expand_as(topk_idx) # [B, max_k]
valid_batch = batch_arange[row_mask] # [sum_k]
valid_col = topk_idx[row_mask] # [sum_k]
to_mask[valid_batch, valid_col] = True
else:
to_mask = torch.zeros_like(x, dtype=torch.bool)
# Apply re-masking: set selected positions back to mask_token_id
x[to_mask] = mask_token_id
# For positions that started as mask and were not re-masked, unmask them with sampled tokens
keep_unmask = mask_index & (~to_mask)
x[keep_unmask] = x0_full[keep_unmask]
elif alg in ["maskgit_plus", "entropy", "topk_margin"]:
# Confidence-based sampling (maskgit, entropy, etc.)
confidence, x0 = sample_tokens(mask_logits, temperature=temperature, top_p=top_p, top_k=top_k, alg=alg)
confidence = confidence.to(mask_logits.dtype)
# Calculate number of mask tokens per sample
num_mask_tokens_per_sample = mask_index.sum(dim=1) # [batch_size]
# Calculate transfer tokens per sample
if i < steps - 1:
number_transfer_tokens_per_sample = (num_mask_tokens_per_sample.float() * (1 - s / t)).long()
else:
number_transfer_tokens_per_sample = num_mask_tokens_per_sample
number_transfer_tokens_per_sample = torch.minimum(number_transfer_tokens_per_sample, num_mask_tokens_per_sample)
# Build full confidence matrix
full_confidence = torch.full_like(x, -torch.inf, device=x.device, dtype=logits.dtype)
full_confidence[mask_index] = confidence
# Get maximum transfer tokens for efficient batching
max_transfer_tokens = number_transfer_tokens_per_sample.max().item()
if max_transfer_tokens > 0:
if alg_temp is None or alg_temp <= 0:
# Use topk for each sample
_, all_transfer_indices = torch.topk(full_confidence, max_transfer_tokens, dim=1) # [batch_size, max_transfer_tokens]
else:
# Robust vectorized sampling via Gumbel-TopK (no replacement)
# Handles rows with fewer valid positions than requested and rows with no valid positions
# full_confidence has -inf for invalid positions; keep them -inf so they won't be selected
scaled_logits = full_confidence / alg_temp
# Uniform in (0,1) to avoid log(0)
uniform = torch.rand_like(scaled_logits).clamp_(min=1e-20, max=1 - 1e-20)
gumbel_noise = -torch.log(-torch.log(uniform))
scores = scaled_logits + gumbel_noise
_, all_transfer_indices = torch.topk(scores, max_transfer_tokens, dim=1) # [batch_size, max_transfer_tokens]
# Create mask for valid transfers (handle variable number of transfers per sample)
batch_size = x.size(0)
valid_mask = torch.arange(max_transfer_tokens, device=x.device).unsqueeze(0) < number_transfer_tokens_per_sample.unsqueeze(1) # [batch_size, max_transfer_tokens]
# Get valid transfer indices and corresponding batch indices
valid_transfer_indices = all_transfer_indices[valid_mask] # [total_valid_transfers]
valid_batch_indices = torch.arange(batch_size, device=x.device).unsqueeze(1).expand_as(all_transfer_indices)[valid_mask] # [total_valid_transfers]
# Prepare the transfer data
x_ = torch.zeros_like(x, device=x.device, dtype=torch.long) + mask_token_id
x_[mask_index] = x0.clone()
# Batch update using advanced indexing
x[valid_batch_indices, valid_transfer_indices] = x_[valid_batch_indices, valid_transfer_indices]
else:
raise NotImplementedError(f"Algorithm {alg} not implemented.")
if histories is not None:
histories.append(x.clone())
if generation_config.return_dict_in_generate:
return MDMModelOutput(sequences=x, history=None if histories is None else tuple(histories))
else:
return x
|