| # coding=utf-8 | |
| # Adapted from Qwen2.5-VL SGLang implementation | |
| import logging | |
| from typing import Iterable, List, Optional, Tuple | |
| import torch | |
| import torch.nn as nn | |
| from sglang.srt.configs import DotsOCRConfig | |
| from sglang.srt.layers.logits_processor import LogitsProcessor | |
| from sglang.srt.layers.quantization.base_config import QuantizationConfig | |
| from sglang.srt.layers.vocab_parallel_embedding import ParallelLMHead | |
| from sglang.srt.managers.mm_utils import ( | |
| MultiModalityDataPaddingPatternMultimodalTokens, | |
| general_mm_embed_routine, | |
| ) | |
| from sglang.srt.managers.schedule_batch import MultimodalDataItem, MultimodalInputs | |
| from sglang.srt.model_executor.forward_batch_info import ForwardBatch | |
| from sglang.srt.model_loader.weight_utils import default_weight_loader | |
| from sglang.srt.models.dots_vlm_vit import DotsVisionTransformer | |
| from sglang.srt.models.qwen2 import Qwen2ForCausalLM | |
| from sglang.srt.utils import add_prefix | |
| logger = logging.getLogger(__name__) | |
| class DotsOCRForCausalLM(nn.Module): | |
| def __init__( | |
| self, | |
| config: DotsOCRConfig, | |
| quant_config: Optional[QuantizationConfig] = None, | |
| prefix: str = "", | |
| ) -> None: | |
| super().__init__() | |
| self.config = config | |
| # Initialize vision transformer | |
| self.visual = DotsVisionTransformer( | |
| config.vision_config, | |
| ) | |
| # Initialize language model | |
| self.model = Qwen2ForCausalLM(config, quant_config) | |
| # Initialize LM head | |
| if config.tie_word_embeddings: | |
| self.lm_head = self.model.embed_tokens | |
| else: | |
| self.lm_head = ParallelLMHead( | |
| config.vocab_size, | |
| config.hidden_size, | |
| quant_config=quant_config, | |
| prefix=add_prefix("lm_head", prefix), | |
| ) | |
| self.logits_processor = LogitsProcessor(config) | |
| def pad_input_ids(self, input_ids: List[int], mm_inputs: MultimodalInputs): | |
| pattern = MultiModalityDataPaddingPatternMultimodalTokens() | |
| return pattern.pad_input_tokens(input_ids, mm_inputs) | |
| def get_image_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor: | |
| # Extract pixel values and grid information (following reference pattern) | |
| pixel_values = torch.cat([item.feature for item in items], dim=0).type( | |
| self.visual.dtype | |
| ) | |
| image_grid_thw = torch.concat( | |
| [item.image_grid_thw for item in items], dim=0 | |
| ).to(self.visual.device) | |
| # Add dimension checks like in reference code | |
| assert pixel_values.dim() == 2, f"{pixel_values.dim()=}" | |
| assert image_grid_thw.dim() == 2, f"{image_grid_thw.dim()=}" | |
| # Process through vision tower | |
| image_embeds = self.visual(pixel_values, image_grid_thw) | |
| # Ensure consistent dtype for FlashInfer compatibility | |
| # Force bfloat16 to match model's expected dtype | |
| if hasattr(self.model, "embed_tokens"): | |
| target_dtype = self.model.embed_tokens.weight.dtype | |
| if image_embeds.dtype != target_dtype: | |
| image_embeds = image_embeds.to(target_dtype) | |
| return image_embeds | |
| def _pad_vit_attn_dummy_heads(self, name: str, loaded_weight: torch.Tensor): | |
| """pad attn qkv weights for dummy heads""" | |
| num_dummy_heads = self.config.vision_config.num_dummy_heads | |
| if num_dummy_heads == 0: | |
| return loaded_weight | |
| head_dim = self.config.vision_config.head_dim | |
| if "attn.qkv_proj" in name: | |
| wq, wk, wv = loaded_weight.chunk(3, dim=0) | |
| if name.endswith(".weight"): | |
| dummy_shape = [num_dummy_heads, head_dim, wq.shape[-1]] | |
| elif name.endswith(".bias"): | |
| dummy_shape = [num_dummy_heads, head_dim] | |
| else: | |
| raise RuntimeError(f"Unsupported weight with name={name}") | |
| pad_func = lambda x: torch.cat( | |
| [x.unflatten(0, (-1, head_dim)), x.new_zeros(dummy_shape)], dim=0 | |
| ).flatten(0, 1) | |
| wq, wk, wv = pad_func(wq), pad_func(wk), pad_func(wv) | |
| loaded_weight = torch.cat([wq, wk, wv], dim=0) | |
| if "attn.proj.weight" in name: | |
| padded_weight = loaded_weight.new_zeros( | |
| loaded_weight.shape[0], head_dim * num_dummy_heads | |
| ) | |
| loaded_weight = torch.cat([loaded_weight, padded_weight], dim=-1) | |
| if "attn.q_norm.weight" in name or "attn.k_norm.weight" in name: | |
| padded_weight = loaded_weight.new_zeros(head_dim * num_dummy_heads) | |
| loaded_weight = torch.cat([loaded_weight, padded_weight], dim=0) | |
| return loaded_weight | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor, | |
| positions: torch.Tensor, | |
| forward_batch: ForwardBatch, | |
| **kwargs: object, | |
| ) -> torch.Tensor: | |
| hidden_states = general_mm_embed_routine( | |
| input_ids=input_ids, | |
| positions=positions, | |
| forward_batch=forward_batch, | |
| multimodal_model=self, | |
| language_model=self.model, | |
| ) | |
| return hidden_states | |
| def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): | |
| """Load weights for the model, separating vision and language weights""" | |
| weights = list(weights) | |
| # Separate vision tower weights and language model weights | |
| vision_weights = [] | |
| language_weights = [] | |
| for name, loaded_weight in weights: | |
| if name.startswith("vision_tower."): | |
| vision_name = name.replace(r"attn.qkv.", r"attn.qkv_proj.") | |
| vision_weights.append((vision_name, loaded_weight)) | |
| else: | |
| # All other weights go to language model | |
| language_weights.append((name, loaded_weight)) | |
| # Load vision tower weights | |
| vision_state_dict = dict(vision_weights) | |
| params_dict = dict(self.named_parameters(remove_duplicate=False)) | |
| for name, loaded_weight in vision_state_dict.items(): | |
| name = name.replace("vision_tower", "visual") | |
| if name not in params_dict: | |
| raise ValueError(f"Weight {name} not found in params_dict") | |
| param = params_dict[name] | |
| weight_loader = getattr(param, "weight_loader", default_weight_loader) | |
| loaded_weight = self._pad_vit_attn_dummy_heads(name, loaded_weight) | |
| weight_loader(param, loaded_weight) | |
| if language_weights: | |
| self.model.load_weights(language_weights) | |
| def get_embed_and_head(self): | |
| return self.model.embed_tokens.weight, self.lm_head.weight | |
| EntryClass = [DotsOCRForCausalLM] | |
Xet Storage Details
- Size:
- 6.68 kB
- Xet hash:
- 72a945b909729204ef911ad4565c6ee374d6e038ab74c3561770d0f8235a48db
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.