LocateAnything-3B-custom / generate_utils.py
Akfmsk's picture
Initial custom version
6fd1cee
Raw
History Blame Contribute Delete
7.26 kB
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
import torch
import torch.nn.functional as F
import torch.distributions as dists
from typing import Dict, Optional
def get_token_ids_from_config(config) -> Dict[str, int]:
"""Extract all token IDs from the configuration object.
Args:
config: Configuration object (LocateAnythingConfig or similar)
Returns:
Dictionary containing all token IDs
"""
token_ids = {}
# Get from main config
token_ids['box_start_token_id'] = getattr(config, 'box_start_token_id', 151668)
token_ids['box_end_token_id'] = getattr(config, 'box_end_token_id', 151669)
token_ids['coord_start_token_id'] = getattr(config, 'coord_start_token_id', 151677)
token_ids['coord_end_token_id'] = getattr(config, 'coord_end_token_id', 152677)
token_ids['ref_start_token_id'] = getattr(config, 'ref_start_token_id', 151672)
token_ids['ref_end_token_id'] = getattr(config, 'ref_end_token_id', 151673)
token_ids['none_token_id'] = getattr(config, 'none_token_id', 4064)
# Get from text_config
text_config = getattr(config, 'text_config', None)
if text_config is not None:
token_ids['null_token_id'] = getattr(text_config, 'null_token_id', 152678)
token_ids['im_end_token_id'] = getattr(text_config, 'eos_token_id', 151645)
token_ids['switch_token_id'] = getattr(text_config, 'switch_token_id', 152679)
token_ids['default_mask_token_id'] = getattr(text_config, 'text_mask_token_id', 151676)
else:
token_ids['null_token_id'] = 152678
token_ids['im_end_token_id'] = 151645
token_ids['switch_token_id'] = 152679
token_ids['default_mask_token_id'] = 151676
return token_ids
def top_p_logits(
logits: torch.Tensor,
top_p: float = None
) -> torch.Tensor:
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
sorted_indices_to_remove = cumulative_probs > top_p
# Shift the indices to the right to keep the first token above the threshold
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
sorted_indices_to_remove[..., 0] = 0
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: torch.Tensor,
top_k: int = None
) -> torch.Tensor:
top_k = min(top_k, logits.size(-1)) # Safety check
# Remove all tokens with a probability less than the last token of the top-k
indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min)
return logits
def apply_repetition_penalty(
logits: torch.Tensor,
input_ids: torch.Tensor,
repetition_penalty: float = 1.0
) -> torch.Tensor:
"""
Apply repetition penalty to logits.
Args:
logits: Shape [batch_size, seq_len, vocab_size] or [batch_size, vocab_size]
input_ids: Previously generated token ids, shape [batch_size, seq_len]
repetition_penalty: Penalty factor. > 1.0 penalizes repetition, < 1.0 encourages it.
Returns:
Modified logits with repetition penalty applied.
"""
if repetition_penalty == 1.0:
return logits
# Convert to 3D for vectorized computation
if logits.dim() == 2:
logits = logits.unsqueeze(1) # [B, 1, V]
squeeze_back = True
else:
squeeze_back = False
batch_size, seq_len, vocab_size = logits.shape
# Construct [B, V] bool mask marking tokens that have appeared in each batch
device = logits.device
token_mask = torch.zeros(batch_size, vocab_size, dtype=torch.bool, device=device)
for b in range(batch_size):
# Apply penalty only based on tokens already generated in this batch
unique_tokens = input_ids[b].unique()
# Prevent out-of-bounds: only keep IDs within vocab range
valid_tokens = unique_tokens[(unique_tokens >= 0) & (unique_tokens < vocab_size)]
if valid_tokens.numel() > 0:
token_mask[b, valid_tokens] = True
# Expand to [B, L, V] to align with logits
token_mask = token_mask.unsqueeze(1).expand(-1, seq_len, -1)
# Divide positive values by penalty, multiply negative values by penalty
positive = logits > 0
negative = ~positive
# Apply penalty only at mask positions
logits = torch.where(token_mask & positive, logits / repetition_penalty, logits)
logits = torch.where(token_mask & negative, logits * repetition_penalty, logits)
if squeeze_back:
logits = logits.squeeze(1)
return logits
def sample_tokens_ar(
logits: torch.Tensor,
generated: torch.Tensor,
token_ids: Dict[str, int],
**generate_kwargs,
):
"""
Lightweight sampling function for AR single-step sampling only.
Args:
logits: [batch_size, vocab_size] or [batch_size, 1, vocab_size]
generated: [batch_size, seq_len]
"""
# Convert to 3D for reusing repetition penalty and clipping logic
if logits.dim() == 2:
logits = logits.unsqueeze(1) # [B, 1, V]
batch_size, seq_len, vocab_size = logits.shape
assert seq_len == 1, "sample_tokens_ar only supports single-step AR sampling (seq_len == 1)"
repetition_penalty = generate_kwargs.get('repetition_penalty', 1.0)
temperature = generate_kwargs.get('temperature', 0)
top_p = generate_kwargs.get('top_p', None)
top_k = generate_kwargs.get('top_k', None)
# Apply repetition penalty only based on historically generated tokens
if repetition_penalty != 1.0:
logits = apply_repetition_penalty(logits, generated, repetition_penalty)
if temperature > 0:
logits = logits / temperature
if top_p is not None and top_p < 1:
logits = top_p_logits(logits, top_p)
if top_k is not None:
logits = top_k_logits(logits, top_k)
probs = torch.softmax(logits, dim=-1)
if temperature > 0:
try:
x0 = dists.Categorical(probs=probs).sample()
confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1)
except Exception:
confidence, x0 = probs.max(dim=-1)
else:
# For greedy: directly take the token with maximum probability
confidence, x0 = probs.max(dim=-1)
# Keep interface consistent with sample_tokens: return [B, 1, V] / [B, 1] shape
return probs, confidence, x0, None, None