mirror of
https://github.com/OpenVGLab/OmniLottie.git
synced 2026-09-16 23:26:26 +00:00
180 lines
6.3 KiB
Python
180 lines
6.3 KiB
Python
import torch
|
|||
|
|
import torch.nn as nn
|
||
|
|
from transformers import Qwen2_5_VLForConditionalGeneration, AutoConfig, PreTrainedModel
|
||
|
|
from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import Qwen2_5_VLCausalLMOutputWithPast
|
||
|
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||
|
|
import os
|
||
|
|
|
||
|
|
import transformers.models.qwen2_5_vl.modeling_qwen2_5_vl as qwen_modeling
|
||
|
|
|
||
|
|
from configuration_lottie_decoder import LottieDecoderConfig
|
||
|
|
|
||
|
|
|
||
|
|
class LottieDecoder(PreTrainedModel):
|
||
|
|
"""
|
||
|
|
Autoregressive generative model for OmniLottie
|
||
|
|
|
||
|
|
Lottie animation generation model based on Qwen2.5-VL,
|
||
|
|
supports generating Lottie JSON code from videos.
|
||
|
|
"""
|
||
|
|
|
||
|
|
config_class = LottieDecoderConfig
|
||
|
|
base_model_prefix = "lottie_decoder"
|
||
|
|
supports_gradient_checkpointing = True
|
||
|
|
|
||
|
|
def __init__(self, config: LottieDecoderConfig):
|
||
|
|
"""
|
||
|
|
Initialize LottieDecoder model
|
||
|
|
|
||
|
|
Args:
|
||
|
|
config (LottieDecoderConfig): Model configuration object
|
||
|
|
"""
|
||
|
|
super().__init__(config)
|
||
|
|
|
||
|
|
self.config = config
|
||
|
|
self.pix_len = config.pix_len
|
||
|
|
self.text_len = config.text_len
|
||
|
|
self.vocab_size = config.vocab_size
|
||
|
|
self.bos_token_id = config.bos_token_id
|
||
|
|
self.eos_token_id = config.eos_token_id
|
||
|
|
self.pad_token_id = config.pad_token_id
|
||
|
|
|
||
|
|
print(f"Initializing LottieDecoder with base model: {config.base_model_path}")
|
||
|
|
|
||
|
|
# Create base model configuration
|
||
|
|
qwen_config = AutoConfig.from_pretrained(
|
||
|
|
config.base_model_path,
|
||
|
|
vocab_size=self.vocab_size,
|
||
|
|
bos_token_id=self.bos_token_id,
|
||
|
|
eos_token_id=self.eos_token_id,
|
||
|
|
pad_token_id=self.pad_token_id,
|
||
|
|
trust_remote_code=True
|
||
|
|
)
|
||
|
|
|
||
|
|
# Load base Qwen2.5-VL model
|
||
|
|
self.transformer = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
||
|
|
config.base_model_path,
|
||
|
|
config=qwen_config,
|
||
|
|
torch_dtype=getattr(torch, config.torch_dtype) if isinstance(config.torch_dtype, str) else config.torch_dtype,
|
||
|
|
attn_implementation=config.attn_implementation,
|
||
|
|
ignore_mismatched_sizes=True
|
||
|
|
)
|
||
|
|
|
||
|
|
# Extend vocabulary to support Lottie tokens
|
||
|
|
self.transformer.resize_token_embeddings(self.vocab_size)
|
||
|
|
|
||
|
|
# Set to training mode initially (same as original decoder)
|
||
|
|
self.train()
|
||
|
|
|
||
|
|
print(f"LottieDecoder initialized successfully. Vocab size: {self.vocab_size}")
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
|
||
|
|
"""
|
||
|
|
Load LottieDecoder from pretrained model path
|
||
|
|
|
||
|
|
Supports two loading methods:
|
||
|
|
1. Load from Hugging Face standard format (recommended)
|
||
|
|
2. Load from old format pytorch_model.bin (backward compatible)
|
||
|
|
"""
|
||
|
|
# Check if it's old format (contains pytorch_model.bin)
|
||
|
|
if os.path.isdir(pretrained_model_name_or_path):
|
||
|
|
old_format_path = os.path.join(pretrained_model_name_or_path, 'pytorch_model.bin')
|
||
|
|
if os.path.exists(old_format_path) and not os.path.exists(os.path.join(pretrained_model_name_or_path, 'config.json')):
|
||
|
|
print(f"Detected old format model, loading from {old_format_path}...")
|
||
|
|
return cls._from_old_format(pretrained_model_name_or_path, **kwargs)
|
||
|
|
|
||
|
|
# Use standard Hugging Face loading process
|
||
|
|
return super().from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def _from_old_format(cls, checkpoint_path, **kwargs):
|
||
|
|
"""
|
||
|
|
Load model from old format (pytorch_model.bin)
|
||
|
|
|
||
|
|
Args:
|
||
|
|
checkpoint_path: Directory path containing pytorch_model.bin
|
||
|
|
"""
|
||
|
|
# Extract configuration parameters
|
||
|
|
pix_len = kwargs.pop('pix_len', 4560)
|
||
|
|
text_len = kwargs.pop('text_len', 1500)
|
||
|
|
base_model_path = kwargs.pop('base_model_path', 'Qwen/Qwen2.5-VL-3B-Instruct')
|
||
|
|
|
||
|
|
# Create configuration
|
||
|
|
config = LottieDecoderConfig(
|
||
|
|
pix_len=pix_len,
|
||
|
|
text_len=text_len,
|
||
|
|
base_model_path=base_model_path
|
||
|
|
)
|
||
|
|
|
||
|
|
# Initialize model
|
||
|
|
model = cls(config)
|
||
|
|
|
||
|
|
# Load weights
|
||
|
|
model_file = os.path.join(checkpoint_path, 'pytorch_model.bin')
|
||
|
|
if os.path.exists(model_file):
|
||
|
|
state_dict = torch.load(model_file, map_location='cpu')
|
||
|
|
model.load_state_dict(state_dict, strict=False)
|
||
|
|
print(f"Successfully loaded weights from {model_file}")
|
||
|
|
else:
|
||
|
|
print(f"Warning: Model file not found {model_file}")
|
||
|
|
|
||
|
|
return model
|
||
|
|
|
||
|
|
def forward(
|
||
|
|
self,
|
||
|
|
input_ids=None,
|
||
|
|
attention_mask=None,
|
||
|
|
pixel_values=None,
|
||
|
|
image_grid_thw=None,
|
||
|
|
pixel_values_videos=None,
|
||
|
|
video_grid_thw=None,
|
||
|
|
labels=None,
|
||
|
|
past_key_values=None,
|
||
|
|
use_cache=False,
|
||
|
|
**kwargs
|
||
|
|
):
|
||
|
|
"""
|
||
|
|
Forward pass - currently for inference only, needs implementation for training
|
||
|
|
"""
|
||
|
|
return self.transformer(
|
||
|
|
input_ids=input_ids,
|
||
|
|
attention_mask=attention_mask,
|
||
|
|
pixel_values=pixel_values,
|
||
|
|
image_grid_thw=image_grid_thw,
|
||
|
|
pixel_values_videos=pixel_values_videos,
|
||
|
|
video_grid_thw=video_grid_thw,
|
||
|
|
labels=labels,
|
||
|
|
past_key_values=past_key_values,
|
||
|
|
use_cache=use_cache,
|
||
|
|
**kwargs
|
||
|
|
)
|
||
|
|
|
||
|
|
def generate(self, *args, **kwargs):
|
||
|
|
"""
|
||
|
|
Generate Lottie tokens
|
||
|
|
|
||
|
|
Directly calls the underlying transformer's generate method
|
||
|
|
"""
|
||
|
|
return self.transformer.generate(*args, **kwargs)
|
||
|
|
|
||
|
|
def get_input_embeddings(self):
|
||
|
|
"""Get input embeddings"""
|
||
|
|
return self.transformer.get_input_embeddings()
|
||
|
|
|
||
|
|
def set_input_embeddings(self, value):
|
||
|
|
"""Set input embeddings"""
|
||
|
|
self.transformer.set_input_embeddings(value)
|
||
|
|
|
||
|
|
def get_output_embeddings(self):
|
||
|
|
"""Get output embeddings"""
|
||
|
|
return self.transformer.get_output_embeddings()
|
||
|
|
|
||
|
|
def set_output_embeddings(self, new_embeddings):
|
||
|
|
"""Set output embeddings"""
|
||
|
|
self.transformer.set_output_embeddings(new_embeddings)
|
||
|
|
|
||
|
|
def resize_token_embeddings(self, new_num_tokens: Optional[int] = None):
|
||
|
|
"""Resize token embeddings"""
|
||
|
|
return self.transformer.resize_token_embeddings(new_num_tokens)
|