SAMTok-self-contained / modeling_samtok.py
godx7's picture
Upload folder using huggingface_hub
3805eff verified
Raw
History Blame Contribute Delete
10.5 kB
import json
import importlib
import re
import sys
from pathlib import Path
import numpy as np
import torch
from PIL import Image
from transformers import AutoConfig, AutoProcessor, PreTrainedModel, Qwen3VLForConditionalGeneration
from transformers.utils import cached_file
try:
from .configuration_samtok import SAMTokCompositeConfig
except ImportError:
from configuration_samtok import SAMTokCompositeConfig
class SAMTokCompositeModel(PreTrainedModel):
config_class = SAMTokCompositeConfig
main_input_name = "input_ids"
_no_split_modules = ["Qwen3VLDecoderLayer", "MultiScaleBlock", "TwoWayAttentionBlock"]
def __init__(self, config):
super().__init__(config)
self.vlm = None
self.processor = None
self.vq_sam2 = None
self.sam2_image_processor = None
self._samtok_models = None
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
config = kwargs.pop("config", None)
trust_remote_code = kwargs.pop("trust_remote_code", True)
dtype = kwargs.pop("dtype", kwargs.pop("torch_dtype", "auto"))
device_map = kwargs.pop("device_map", None)
vlm_attn_implementation = kwargs.pop("vlm_attn_implementation", None)
hub_kwargs = {
key: kwargs.pop(key)
for key in list(kwargs.keys())
if key in {"cache_dir", "force_download", "local_files_only", "revision", "token"}
}
if config is None:
config = SAMTokCompositeConfig.from_pretrained(
pretrained_model_name_or_path,
trust_remote_code=trust_remote_code,
**hub_kwargs,
)
model = cls(config)
model._load_samtok_package(pretrained_model_name_or_path)
qwen_config_path = cached_file(pretrained_model_name_or_path, config.qwen_config_file, **hub_kwargs)
with open(qwen_config_path, "r", encoding="utf-8") as f:
qwen_config_dict = json.load(f)
qwen_config = AutoConfig.for_model(qwen_config_dict.pop("model_type"), **qwen_config_dict)
vlm_kwargs = dict(kwargs)
vlm_kwargs["config"] = qwen_config
vlm_kwargs["dtype"] = dtype
if device_map is not None:
vlm_kwargs["device_map"] = device_map
if vlm_attn_implementation is not None:
vlm_kwargs["attn_implementation"] = vlm_attn_implementation
model.vlm = Qwen3VLForConditionalGeneration.from_pretrained(
pretrained_model_name_or_path,
*model_args,
**vlm_kwargs,
).eval()
model.processor = AutoProcessor.from_pretrained(
pretrained_model_name_or_path,
trust_remote_code=trust_remote_code,
**hub_kwargs,
)
model.vq_sam2 = model._load_vq_sam2(pretrained_model_name_or_path, hub_kwargs).eval()
return model
def _load_samtok_package(self, pretrained_model_name_or_path):
model_path = Path(str(pretrained_model_name_or_path))
if model_path.exists():
model_dir = model_path.resolve()
if str(model_dir) not in sys.path:
sys.path.insert(0, str(model_dir))
self._samtok_models = importlib.import_module("samtok.models")
self.sam2_image_processor = self._samtok_models.DirectResize(self.config.sam2_image_size)
@property
def device(self):
if self.vlm is not None:
return self.vlm.device
return super().device
@property
def dtype(self):
if self.vlm is not None:
return self.vlm.dtype
return super().dtype
def to(self, *args, **kwargs):
super().to(*args, **kwargs)
if self.vlm is not None:
self.vlm.to(*args, **kwargs)
if self.vq_sam2 is not None:
self.vq_sam2.to(*args, **kwargs)
return self
def eval(self):
super().eval()
if self.vlm is not None:
self.vlm.eval()
if self.vq_sam2 is not None:
self.vq_sam2.eval()
return self
def forward(self, *args, **kwargs):
return self.vlm(*args, **kwargs)
def generate(self, *args, **kwargs):
return self.vlm.generate(*args, **kwargs)
def _load_vq_sam2(self, pretrained_model_name_or_path, hub_kwargs):
if self._samtok_models is None:
self._load_samtok_package(pretrained_model_name_or_path)
sam2_ckpt_path = cached_file(
pretrained_model_name_or_path,
self.config.sam2_ckpt_file,
**hub_kwargs,
)
vq_sam2_path = cached_file(
pretrained_model_name_or_path,
self.config.vq_sam2_file,
**hub_kwargs,
)
sam2_config = self._samtok_models.SAM2Config(ckpt_path=sam2_ckpt_path)
vq_sam2_config = self._samtok_models.VQ_SAM2Config(
sam2_config=sam2_config,
codebook_size=self.config.codebook_size,
codebook_depth=self.config.codebook_depth,
shared_codebook=self.config.shared_codebook,
latent_dim=self.config.latent_dim,
)
vq_sam2 = self._samtok_models.VQ_SAM2(vq_sam2_config)
state = torch.load(vq_sam2_path, map_location="cpu", weights_only=False)
vq_sam2.load_state_dict(state)
if self.vlm is not None:
vq_sam2.to(self.vlm.device)
return vq_sam2
def generate_with_masks(
self,
image,
question,
max_new_tokens=512,
do_sample=False,
top_p=1.0,
**generate_kwargs,
):
image_obj = _load_image(image)
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": question},
],
}
]
inputs = self.processor.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",
)
inputs = inputs.to(self.device)
generated_ids = self.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=do_sample,
top_p=top_p,
**generate_kwargs,
)
generated_ids_trimmed = [
out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
text = self.processor.batch_decode(
generated_ids_trimmed,
skip_special_tokens=True,
clean_up_tokenization_spaces=False,
)[0]
masks, tags = self.decode_masks(text, image_obj)
return {"text": text, "masks": masks, "tags": tags}
def decode_masks(self, output_text, image):
image_obj = _load_image(image)
ori_width, ori_height = image_obj.size
quant_ids = _extract_mt_token_ids_v1(output_text)
if len(quant_ids) % self.config.codebook_depth != 0:
output_text = _fix_mt_format_comprehensive(output_text)
quant_ids = _extract_mt_token_ids_v2(output_text)
if len(quant_ids) == 0 or len(quant_ids) % self.config.codebook_depth != 0:
return None, []
remap_quant_ids = []
tags = []
for idx in range(len(quant_ids) // self.config.codebook_depth):
chunk = quant_ids[
idx * self.config.codebook_depth:(idx + 1) * self.config.codebook_depth
]
normalized = [
token_id - book_id * self.config.codebook_size
for book_id, token_id in enumerate(chunk)
]
normalized = [
code if 0 <= code < self.config.codebook_size else -1
for code in normalized
]
if all(code == -1 for code in normalized):
continue
tags.append("-".join(str(x) for x in chunk))
remap_quant_ids.append(normalized)
if not remap_quant_ids:
return None, []
remap_quant_ids = remap_quant_ids[: self.config.max_samtok_masks]
tags = tags[: self.config.max_samtok_masks]
sam2_image = self.sam2_image_processor.apply_image(np.array(image_obj))
pixel_values = torch.from_numpy(sam2_image).permute(2, 0, 1).contiguous()
pixel_values = pixel_values.unsqueeze(0).to(self.vq_sam2.dtype).to(self.vq_sam2.device)
quant_ids_tensor = torch.LongTensor(remap_quant_ids).to(self.vq_sam2.device)
pred_masks = self._forward_masks_in_chunks(pixel_values, quant_ids_tensor)
pred_masks = torch.nn.functional.interpolate(
pred_masks,
size=(ori_height, ori_width),
mode="bilinear",
)
pred_masks = pred_masks > 0.5
pred_masks = pred_masks[:, 0, :, :].cpu().numpy().astype(np.uint8)
return pred_masks, tags
def _forward_masks_in_chunks(self, pixel_values, quant_ids):
chunks = []
chunk_size = self.config.decode_chunk_size
for start in range(0, len(quant_ids), chunk_size):
quant_chunk = quant_ids[start:start + chunk_size]
pixel_chunk = pixel_values.repeat(len(quant_chunk), 1, 1, 1)
with torch.no_grad():
chunks.append(self.vq_sam2.forward_with_codes(pixel_chunk, quant_chunk))
return torch.cat(chunks, dim=0)
def _load_image(image):
if isinstance(image, Image.Image):
return image.convert("RGB")
return Image.open(Path(image)).convert("RGB")
def _extract_mt_token_ids_v1(text):
return [int(x) for x in re.findall(r"<\|mt_(\d{4})\|>", text)]
def _extract_mt_token_ids_v2(text):
pattern = re.compile(r"<\|mt_start\|><\|mt_(\d{4})\|><\|mt_(\d{4})\|><\|mt_end\|>")
values = []
for first, second in pattern.findall(text):
values.extend([int(first), int(second)])
return values
def _fix_mt_format_comprehensive(text):
text = re.sub(
r"(<\|mt_start\|>)(<\|mt_\d+\|>)(<\|mt_\d+\|>)(?:<\|mt_\d+\|>)+<\|mt_end\|>",
r"\1\2\3<|mt_end|>",
text,
)
text = re.sub(
r"(<\|mt_start\|>)(<\|mt_\d+\|>)(<\|mt_end\|>)",
r"\1\2<|mt_9999|><|mt_end|>",
text,
)
text = re.sub(
r"(<\|mt_start\|>)(<\|mt_\d+\|>)(?!<\|mt_)",
r"\1\2<|mt_9999|><|mt_end|>",
text,
)
return text