From 9efe7080480e605c8ed24b91da1e68d2e3dba420 Mon Sep 17 00:00:00 2001 From: zxy Date: Fri, 31 Jul 2026 12:40:36 +0000 Subject: [PATCH] feat: support interns2 preview time-series encoder --- docs/en/multi_modal/multimodal_inputs.md | 6 +- docs/zh_cn/multi_modal/multimodal_inputs.md | 6 +- lmdeploy/pytorch/models/bert.py | 312 +++++++ lmdeploy/pytorch/models/interns1_pro.py | 4 +- ...e_series.py => interns1_pro_ts_encoder.py} | 0 lmdeploy/pytorch/models/interns2_preview.py | 421 ++++++++++ .../models/interns2_preview_ts_encoder.py | 762 ++++++++++++++++++ lmdeploy/pytorch/models/module_map.py | 16 +- lmdeploy/pytorch/models/qwen3_5.py | 55 +- lmdeploy/pytorch/models/qwen3_5_moe.py | 6 +- lmdeploy/pytorch/models/qwen3_vl.py | 19 +- lmdeploy/pytorch/models/whisper.py | 16 +- lmdeploy/vl/model/base.py | 6 +- lmdeploy/vl/model/builder.py | 1 + lmdeploy/vl/model/interns2_preview.py | 149 ++++ lmdeploy/vl/model/preprocess_utils.py | 2 + lmdeploy/vl/model/qwen3_5.py | 112 +-- .../test_vl/test_preprocess_utils.py | 21 + 18 files changed, 1726 insertions(+), 188 deletions(-) create mode 100644 lmdeploy/pytorch/models/bert.py rename lmdeploy/pytorch/models/{interns1_pro_time_series.py => interns1_pro_ts_encoder.py} (100%) create mode 100644 lmdeploy/pytorch/models/interns2_preview.py create mode 100644 lmdeploy/pytorch/models/interns2_preview_ts_encoder.py create mode 100644 lmdeploy/vl/model/interns2_preview.py diff --git a/docs/en/multi_modal/multimodal_inputs.md b/docs/en/multi_modal/multimodal_inputs.md index e19205cf95..1ff647cb2d 100644 --- a/docs/en/multi_modal/multimodal_inputs.md +++ b/docs/en/multi_modal/multimodal_inputs.md @@ -348,12 +348,12 @@ ______________________________________________________________________ ## Time Series -> **Note:** Time series input is currently supported for the **InternS1-Pro** model only. +> **Note:** Time series understanding is supported for **InternS1-Pro** and **Intern-S2-Preview** models. -The `time_series_url` content item requires a `sampling_rate` field (in Hz) alongside the URL. +The `time_series_url` content item requires a URL. Include `sampling_rate` in Hz when it is known.
-Complete example +Time series understanding example ```python from openai import OpenAI diff --git a/docs/zh_cn/multi_modal/multimodal_inputs.md b/docs/zh_cn/multi_modal/multimodal_inputs.md index c25a4196fb..8058a09ea8 100644 --- a/docs/zh_cn/multi_modal/multimodal_inputs.md +++ b/docs/zh_cn/multi_modal/multimodal_inputs.md @@ -348,12 +348,12 @@ ______________________________________________________________________ ## 时序数据 -> **注意:** 时序数据输入目前仅支持 **InternS1-Pro** 模型。 +> **注意:** 时序理解目前支持 **InternS1-Pro** 和 **Intern-S2-Preview** 模型。 -`time_series_url` 内容项需要在 URL 之外额外提供 `sampling_rate` 字段(单位:Hz)。 +`time_series_url` 内容项需要提供 URL。已知采样率时,可以通过 `sampling_rate` 字段传入,单位为 Hz。
-完整示例 +时序理解示例 ```python from openai import OpenAI diff --git a/lmdeploy/pytorch/models/bert.py b/lmdeploy/pytorch/models/bert.py new file mode 100644 index 0000000000..cc9ba07fb8 --- /dev/null +++ b/lmdeploy/pytorch/models/bert.py @@ -0,0 +1,312 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Adapted from HuggingFace Transformers' BERT modeling code for LMDeploy inference. + +import torch +import torch.nn.functional as F +from torch import nn +from transformers.activations import ACT2FN + +from lmdeploy.pytorch.nn import LayerNorm +from lmdeploy.pytorch.nn.linear import build_colwise_linear, build_rowwise_linear + + +class BertConfig: + + def __init__(self, + vocab_size: int, + hidden_size: int, + num_hidden_layers: int, + num_attention_heads: int, + intermediate_size: int, + max_position_embeddings: int, + add_cross_attention: bool = True, + is_decoder: bool = True, + cross_attention_freq: int = 2, + hidden_act: str = 'gelu', + layer_norm_eps: float = 1e-12, + type_vocab_size: int = 2, + pad_token_id: int = 0, + initializer_range: float = 0.02): + self.vocab_size = vocab_size + self.hidden_size = hidden_size + self.num_hidden_layers = num_hidden_layers + self.num_attention_heads = num_attention_heads + self.intermediate_size = intermediate_size + self.max_position_embeddings = max_position_embeddings + self.add_cross_attention = add_cross_attention + self.is_decoder = is_decoder + self.cross_attention_freq = cross_attention_freq + self.hidden_act = hidden_act + self.layer_norm_eps = layer_norm_eps + self.type_vocab_size = type_vocab_size + self.pad_token_id = pad_token_id + self.initializer_range = initializer_range + + +def _make_causal_mask(input_shape: torch.Size, dtype: torch.dtype, device: torch.device) -> torch.Tensor: + batch_size, tgt_len = input_shape + mask = torch.full((tgt_len, tgt_len), torch.finfo(dtype).min, device=device) + mask_cond = torch.arange(mask.size(-1), device=device) + mask.masked_fill_(mask_cond < (mask_cond + 1).view(mask.size(-1), 1), 0) + mask = mask.to(dtype) + return mask[None, None, :, :].expand(batch_size, 1, tgt_len, tgt_len) + + +def _prepare_cross_attention_mask(attention_mask: torch.Tensor | None, inputs_embeds: torch.Tensor, + encoder_hidden_states: torch.Tensor | None): + if attention_mask is None: + return None + if attention_mask.dim() == 4: + return attention_mask.to(dtype=inputs_embeds.dtype, device=inputs_embeds.device) + + batch_size, query_length = inputs_embeds.shape[:2] + key_length = encoder_hidden_states.shape[1] if encoder_hidden_states is not None else attention_mask.shape[-1] + expanded_mask = attention_mask[:, None, None, :].expand(batch_size, 1, query_length, key_length) + expanded_mask = expanded_mask.to(dtype=inputs_embeds.dtype, device=inputs_embeds.device) + inverted_mask = 1.0 - expanded_mask + return inverted_mask.masked_fill(inverted_mask.to(torch.bool), torch.finfo(inputs_embeds.dtype).min) + + +class BertEmbeddings(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.word_embeddings = nn.Embedding( + config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id, dtype=dtype, device=device) + self.position_embeddings = nn.Embedding( + config.max_position_embeddings, config.hidden_size, dtype=dtype, device=device) + self.token_type_embeddings = nn.Embedding( + config.type_vocab_size, config.hidden_size, dtype=dtype, device=device) + self.LayerNorm = LayerNorm(config.hidden_size, eps=config.layer_norm_eps, dtype=dtype, device=device) + self.register_buffer( + 'position_ids', torch.arange(config.max_position_embeddings, device=device).expand((1, -1)), + persistent=False) + self.register_buffer('token_type_ids', torch.zeros(self.position_ids.size(), dtype=torch.long, device=device), + persistent=False) + + def forward(self, + input_ids: torch.Tensor | None = None, + token_type_ids: torch.Tensor | None = None, + position_ids: torch.Tensor | None = None, + inputs_embeds: torch.Tensor | None = None) -> torch.Tensor: + if input_ids is not None: + input_shape = input_ids.size() + else: + input_shape = inputs_embeds.size()[:-1] + + batch_size, seq_length = input_shape + if position_ids is None: + position_ids = self.position_ids[:, :seq_length] + + if token_type_ids is None: + token_type_ids = self.token_type_ids[:, :seq_length].expand(batch_size, seq_length) + + if inputs_embeds is None: + inputs_embeds = self.word_embeddings(input_ids) + + token_type_embeddings = self.token_type_embeddings(token_type_ids) + position_embeddings = self.position_embeddings(position_ids) + embeddings = inputs_embeds + token_type_embeddings + position_embeddings + return self.LayerNorm(embeddings) + + +class BertSelfAttention(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + if config.hidden_size % config.num_attention_heads != 0: + raise ValueError( + f'hidden_size must be divisible by num_attention_heads, got {config.hidden_size} and ' + f'{config.num_attention_heads}.') + + self.num_attention_heads = config.num_attention_heads + self.attention_head_size = config.hidden_size // config.num_attention_heads + self.all_head_size = self.num_attention_heads * self.attention_head_size + self.scaling = self.attention_head_size**-0.5 + + self.query = build_colwise_linear( + config.hidden_size, self.all_head_size, bias=True, dtype=dtype, device=device) + self.key = build_colwise_linear( + config.hidden_size, self.all_head_size, bias=True, dtype=dtype, device=device) + self.value = build_colwise_linear( + config.hidden_size, self.all_head_size, bias=True, dtype=dtype, device=device) + + def forward(self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor | None = None, + encoder_hidden_states: torch.Tensor | None = None): + key_value_states = hidden_states if encoder_hidden_states is None else encoder_hidden_states + + batch_size = hidden_states.shape[0] + hidden_shape = (batch_size, -1, self.num_attention_heads, self.attention_head_size) + + query_layer = self.query(hidden_states).view(*hidden_shape).transpose(1, 2) + key_layer = self.key(key_value_states).view(*hidden_shape).transpose(1, 2) + value_layer = self.value(key_value_states).view(*hidden_shape).transpose(1, 2) + + attn_output = F.scaled_dot_product_attention( + query_layer, + key_layer, + value_layer, + attn_mask=attention_mask, + dropout_p=0.0, + scale=self.scaling, + ) + attn_output = attn_output.transpose(1, 2).contiguous() + return attn_output.reshape(hidden_states.shape[0], hidden_states.shape[1], self.all_head_size) + + +class BertSelfOutput(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.dense = build_rowwise_linear( + config.hidden_size, config.hidden_size, bias=True, dtype=dtype, device=device) + self.LayerNorm = LayerNorm(config.hidden_size, eps=config.layer_norm_eps, dtype=dtype, device=device) + + def forward(self, hidden_states: torch.Tensor, input_tensor: torch.Tensor) -> torch.Tensor: + hidden_states = self.dense(hidden_states) + return self.LayerNorm(hidden_states + input_tensor) + + +class BertAttention(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.self = BertSelfAttention(config, dtype=dtype, device=device) + self.output = BertSelfOutput(config, dtype=dtype, device=device) + + def forward(self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor | None = None, + encoder_hidden_states: torch.Tensor | None = None): + attention_output = self.self( + hidden_states, attention_mask=attention_mask, encoder_hidden_states=encoder_hidden_states) + return self.output(attention_output, hidden_states) + + +class BertIntermediate(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.dense = build_colwise_linear( + config.hidden_size, config.intermediate_size, bias=True, dtype=dtype, device=device) + self.intermediate_act_fn = ACT2FN[config.hidden_act] + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + return self.intermediate_act_fn(self.dense(hidden_states)) + + +class BertOutput(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.dense = build_rowwise_linear( + config.intermediate_size, config.hidden_size, bias=True, dtype=dtype, device=device) + self.LayerNorm = LayerNorm(config.hidden_size, eps=config.layer_norm_eps, dtype=dtype, device=device) + + def forward(self, hidden_states: torch.Tensor, input_tensor: torch.Tensor) -> torch.Tensor: + hidden_states = self.dense(hidden_states) + return self.LayerNorm(hidden_states + input_tensor) + + +class BertLayer(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.attention = BertAttention(config, dtype=dtype, device=device) + self.add_cross_attention = config.add_cross_attention + if self.add_cross_attention: + self.crossattention = BertAttention(config, dtype=dtype, device=device) + self.intermediate = BertIntermediate(config, dtype=dtype, device=device) + self.output = BertOutput(config, dtype=dtype, device=device) + + def forward(self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor | None = None, + encoder_hidden_states: torch.Tensor | None = None, + encoder_attention_mask: torch.Tensor | None = None) -> torch.Tensor: + attention_output = self.attention(hidden_states, attention_mask=attention_mask) + + if self.add_cross_attention and encoder_hidden_states is not None: + attention_output = self.crossattention( + attention_output, attention_mask=encoder_attention_mask, encoder_hidden_states=encoder_hidden_states) + + intermediate_output = self.intermediate(attention_output) + return self.output(intermediate_output, attention_output) + + +class BertEncoder(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.layer = nn.ModuleList( + [BertLayer(config, dtype=dtype, device=device) for _ in range(config.num_hidden_layers)]) + + def forward(self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor | None = None, + encoder_hidden_states: torch.Tensor | None = None, + encoder_attention_mask: torch.Tensor | None = None) -> torch.Tensor: + for layer_module in self.layer: + hidden_states = layer_module( + hidden_states, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + ) + return hidden_states + + +class BertPooler(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.dense = build_colwise_linear( + config.hidden_size, config.hidden_size, bias=True, dtype=dtype, device=device) + self.activation = nn.Tanh() + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + return self.activation(self.dense(hidden_states[:, 0])) + + +class BertModel(nn.Module): + + def __init__(self, config: BertConfig, dtype: torch.dtype, device: torch.device): + super().__init__() + self.config = config + self.embeddings = BertEmbeddings(config, dtype=dtype, device=device) + self.encoder = BertEncoder(config, dtype=dtype, device=device) + self.pooler = BertPooler(config, dtype=dtype, device=device) + + def forward(self, + input_ids: torch.Tensor | None = None, + token_type_ids: torch.Tensor | None = None, + position_ids: torch.Tensor | None = None, + inputs_embeds: torch.Tensor | None = None, + encoder_hidden_states: torch.Tensor | None = None, + encoder_attention_mask: torch.Tensor | None = None): + if (input_ids is None) == (inputs_embeds is None): + raise ValueError('You must specify exactly one of input_ids or inputs_embeds.') + + embedding_output = self.embeddings( + input_ids=input_ids, + position_ids=position_ids, + token_type_ids=token_type_ids, + inputs_embeds=inputs_embeds, + ) + + self_attention_mask = _make_causal_mask( + embedding_output.shape[:2], embedding_output.dtype, embedding_output.device) + cross_attention_mask = _prepare_cross_attention_mask( + encoder_attention_mask, embedding_output, encoder_hidden_states) + + sequence_output = self.encoder( + embedding_output, + attention_mask=self_attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=cross_attention_mask, + ) + pooled_output = self.pooler(sequence_output) + + return sequence_output, pooled_output diff --git a/lmdeploy/pytorch/models/interns1_pro.py b/lmdeploy/pytorch/models/interns1_pro.py index 9a03f30640..5e5c4bb0f1 100644 --- a/lmdeploy/pytorch/models/interns1_pro.py +++ b/lmdeploy/pytorch/models/interns1_pro.py @@ -12,12 +12,13 @@ from lmdeploy.pytorch.weight_loader.model_weight_loader import load_weight from lmdeploy.vl.constants import Modality -from .interns1_pro_time_series import InternS1ProTimeSeriesModel +from .interns1_pro_ts_encoder import InternS1ProTimeSeriesModel from .patch import add_prefix, get_build_model_context from .qwen3_moe import Qwen3MoeModel from .qwen3_vl import Qwen3VLVisionModel from .utils.cudagraph import CudaGraphMixin from .utils.model import DeployModelMixinV1 +from .whisper import _create_fake_bias_for_whisper_k_proj class InternS1ProForConditionalGeneration(nn.Module, DeployModelMixinV1, CudaGraphMixin): @@ -306,6 +307,7 @@ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]): params_dict = dict(self.named_parameters()) buffers_dict = dict(self.named_buffers()) + weights = _create_fake_bias_for_whisper_k_proj(weights, '.self_attn.k_proj.weight') for name, loaded_weight in weights: if 'rotary_emb.inv_freq' in name: continue diff --git a/lmdeploy/pytorch/models/interns1_pro_time_series.py b/lmdeploy/pytorch/models/interns1_pro_ts_encoder.py similarity index 100% rename from lmdeploy/pytorch/models/interns1_pro_time_series.py rename to lmdeploy/pytorch/models/interns1_pro_ts_encoder.py diff --git a/lmdeploy/pytorch/models/interns2_preview.py b/lmdeploy/pytorch/models/interns2_preview.py new file mode 100644 index 0000000000..71a461b9ec --- /dev/null +++ b/lmdeploy/pytorch/models/interns2_preview.py @@ -0,0 +1,421 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections.abc import Iterable +from typing import Any + +import torch +from torch import nn +from transformers.configuration_utils import PretrainedConfig + +from lmdeploy.pytorch.engine.input_process import PreprocessInputResult +from lmdeploy.pytorch.model_inputs import StepContext, StepContextManager +from lmdeploy.pytorch.multimodal.data_type import MultiModalData +from lmdeploy.vl.constants import Modality + +from .interns1_pro_ts_encoder import InternS1ProTimeSeriesModel +from .interns2_preview_ts_encoder import InternS2PreviewTimeSeriesModel +from .patch import add_prefix, get_build_model_context +from .qwen3_5_moe import ( + Qwen3_5MoeForConditionalGeneration, + Qwen3_5MoeInputProcessor, + Qwen3_5MoeModel, + Qwen3_5MoeTextModel, + Qwen3_5MoeVisionModel, +) +from .whisper import _create_fake_bias_for_whisper_k_proj + + +class InternS2PreviewModel(Qwen3_5MoeModel): + + def __init__(self, + config: PretrainedConfig, + dtype: torch.dtype | None = None, + device: torch.device | None = None, + prefix: str = ''): + nn.Module.__init__(self) + self.config = config + + self.visual = Qwen3_5MoeVisionModel(config.vision_config, + dtype=dtype, + device=device, + prefix=add_prefix('visual', prefix)) + self.language_model = Qwen3_5MoeTextModel(config.text_config, + dtype=dtype, + device=device, + prefix=add_prefix('language_model', prefix)) + + self.use_preview_ts_encoder = getattr(config, 'ts_forecaster_config', None) is not None + if self.use_preview_ts_encoder: + self.time_series = InternS2PreviewTimeSeriesModel(config.ts_config, dtype=dtype, device=device) + else: + self.time_series = InternS1ProTimeSeriesModel(config.ts_config, dtype=dtype, device=device) + + def forward( + self, + input_ids: torch.Tensor, + position_ids: torch.Tensor, + past_key_values: list[list[torch.Tensor]], + attn_metadata: Any, + state_ids: torch.Tensor, + inputs_embeds: torch.Tensor | None = None, + mrope_position_ids: torch.Tensor | None = None, + pixel_values: torch.Tensor | None = None, + vis_cu_seqlens: torch.Tensor | None = None, + vis_pos_emb: torch.Tensor | None = None, + multimodal_mask: torch.Tensor | None = None, + pos_embeds: torch.Tensor | None = None, + grid_thw: torch.Tensor | None = None, + all_routed_experts: torch.Tensor | None = None, + return_input_embeds: bool = False, + ts_values: torch.Tensor | None = None, + ts_lens: torch.Tensor | None = None, + ts_sr: torch.Tensor | None = None, + ts_channels: torch.Tensor | None = None, + ): + output_inputs_embeds = None + if inputs_embeds is None: + inputs_embeds = self.get_input_embeddings()(input_ids) + + if pixel_values is not None: + dtype = inputs_embeds.dtype + pixel_values = pixel_values.to(dtype) + vis_pos_emb = (vis_pos_emb[0].to(dtype), vis_pos_emb[1].to(dtype)) + + image_embeds = self.visual(pixel_values, + cu_seqlens=vis_cu_seqlens, + rotary_pos_emb=vis_pos_emb, + pos_embeds=pos_embeds) + + split_sizes = (grid_thw.prod(-1) // self.visual.spatial_merge_size**2).tolist() + image_embeds = torch.split(image_embeds, split_sizes) + image_embeds = torch.cat(image_embeds, dim=0).to(inputs_embeds.device, dtype) + + multimodal_mask = multimodal_mask.unsqueeze(-1).expand_as(inputs_embeds) + inputs_embeds = inputs_embeds.masked_scatter(multimodal_mask, image_embeds) + elif ts_values is not None: + if self.use_preview_ts_encoder: + ts_embeds, ts_pad_mask, _ = self.time_series(ts_values, ts_lens, ts_sr, ts_channels) + ts_valid_mask = ~ts_pad_mask + ts_features = ts_embeds[ts_valid_mask].to(inputs_embeds.device, inputs_embeds.dtype) + + ts_placeholder = input_ids == self.config.ts_token_id + n_ts_placeholders = ts_placeholder.sum().item() + n_ts_tokens = ts_features.size(0) + assert n_ts_placeholders == n_ts_tokens, ( + f'Mismatch: tokens={n_ts_placeholders}, ts_embeds_valid={n_ts_tokens}') + + flat_embeds = inputs_embeds.reshape(-1, inputs_embeds.size(-1)) + flat_embeds[ts_placeholder.reshape(-1)] = ts_features + inputs_embeds = flat_embeds.reshape_as(inputs_embeds) + else: + ts_embeds = self.time_series(ts_values, ts_lens, ts_sr).to(inputs_embeds.device, + inputs_embeds.dtype) + inputs_embeds = inputs_embeds.masked_scatter(multimodal_mask[..., None], ts_embeds) + + output_inputs_embeds = inputs_embeds if return_input_embeds else None + + hidden_states = self.language_model( + input_ids=input_ids, + position_ids=position_ids, + past_key_values=past_key_values, + attn_metadata=attn_metadata, + state_ids=state_ids, + inputs_embeds=inputs_embeds, + mrope_position_ids=mrope_position_ids, + all_routed_experts=all_routed_experts, + ) + return hidden_states, output_inputs_embeds + + +class InternS2PreviewForConditionalGeneration(Qwen3_5MoeForConditionalGeneration): + """ModelForCausalLM.""" + + def __init__(self, + config: PretrainedConfig, + ctx_mgr: StepContextManager, + dtype: torch.dtype | None = None, + device: torch.device | None = None, + prefix: str = ''): + nn.Module.__init__(self) + self.config = config + self.ctx_mgr = ctx_mgr + + self.input_processor = InternS2PreviewInputProcessor(self.config, dtype) + + # build model + self.model = InternS2PreviewModel(config, dtype=dtype, device=device, prefix=add_prefix('model', prefix)) + + # build lm_head + self.lm_head = self.build_lm_head(config.text_config.hidden_size, + config.text_config.vocab_size, + bias=False, + dtype=dtype, + device=device) + + # for router replay + bm_ctx = get_build_model_context() + self.enable_return_routed_experts = bm_ctx.enable_return_routed_experts + self.is_spec_decoding = get_build_model_context().num_spec_tokens > 0 + + def forward( + self, + input_ids: torch.Tensor, + position_ids: torch.Tensor, + past_key_values: list[list[torch.Tensor]], + attn_metadata: Any, + state_ids: torch.Tensor, + inputs_embeds: torch.Tensor | None = None, + mrope_position_ids: torch.Tensor | None = None, + pixel_values: torch.Tensor | None = None, + vis_cu_seqlens: torch.Tensor | None = None, + vis_pos_emb: torch.Tensor | None = None, + multimodal_mask: torch.Tensor | None = None, + pos_embeds: torch.Tensor | None = None, + grid_thw: torch.Tensor | None = None, + return_input_embeds: bool = False, + ts_values: torch.Tensor | None = None, + ts_lens: torch.Tensor | None = None, + ts_sr: torch.Tensor | None = None, + ts_channels: torch.Tensor | None = None, + **kwargs, + ): + all_routed_experts = None + if self.enable_return_routed_experts: + config = self.config.text_config + num_tokens = input_ids.size(1) + all_routed_experts = position_ids.new_empty( + (num_tokens, config.num_hidden_layers, config.num_experts_per_tok), dtype=torch.uint16) + + hidden_states, target_inputs_embeds = self.model( + input_ids=input_ids, + position_ids=position_ids, + past_key_values=past_key_values, + attn_metadata=attn_metadata, + state_ids=state_ids, + inputs_embeds=inputs_embeds, + mrope_position_ids=mrope_position_ids, + pixel_values=pixel_values, + vis_cu_seqlens=vis_cu_seqlens, + vis_pos_emb=vis_pos_emb, + multimodal_mask=multimodal_mask, + pos_embeds=pos_embeds, + grid_thw=grid_thw, + all_routed_experts=all_routed_experts, + return_input_embeds=return_input_embeds, + ts_values=ts_values, + ts_lens=ts_lens, + ts_sr=ts_sr, + ts_channels=ts_channels, + ) + + output = dict(hidden_states=hidden_states, + all_routed_experts=all_routed_experts, + target_inputs_embeds=target_inputs_embeds) + return output + + @staticmethod + def _collate_time_series_values(mm_inputs): + """Pad variable-length TS inputs before batching.""" + max_len = max(inp.data.size(1) for inp in mm_inputs) + max_channels = max(inp.data.size(2) for inp in mm_inputs) + padded_values = [] + for inp in mm_inputs: + data = inp.data + if data.size(1) == max_len and data.size(2) == max_channels: + padded_values.append(data) + continue + padded = data.new_zeros((data.size(0), max_len, max_channels)) + padded[:, :data.size(1), :data.size(2)] = data + padded_values.append(padded) + return torch.cat(padded_values) + + @staticmethod + def _split_multimodal_inputs(context: StepContext): + vision_mm_inputs = [] + ts_mm_inputs = [] + if context.input_multimodals is None: + return vision_mm_inputs, ts_mm_inputs + + for input_mm in context.input_multimodals: + if input_mm is None: + continue + for item in input_mm.get('mm_data', []): + if item.modality == Modality.TIME_SERIES: + ts_mm_inputs.append(item) + else: + vision_mm_inputs.append(item) + return vision_mm_inputs, ts_mm_inputs + + def _prepare_vision_inputs(self, input_ids: torch.Tensor, mm_inputs: list[MultiModalData]): + # same image/video preparation as Qwen3.5; TS is handled separately. + multimodal_mask = self.get_multimodal_mask(input_ids, mm_inputs) + pixel_values = torch.cat([inp.data for inp in mm_inputs]) + grid_thw = torch.stack([data.meta['grid_thw'] for data in mm_inputs]).cpu() + vis_pos_emb = self.model.visual.rot_pos_emb(grid_thw) + pos_embeds = self.model.visual.fast_pos_embed_interpolate(grid_thw) + vis_cu_seqlens = torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2], + grid_thw[:, 0]).to(pixel_values.device) + vis_cu_seqlens = vis_cu_seqlens.cumsum(dim=0, dtype=torch.int32) + vis_pos_emb = vis_pos_emb.repeat(1, 2) + vis_pos_emb = (vis_pos_emb.cos(), vis_pos_emb.sin()) + return dict( + pixel_values=pixel_values, + vis_cu_seqlens=vis_cu_seqlens, + vis_pos_emb=vis_pos_emb, + multimodal_mask=multimodal_mask, + grid_thw=grid_thw, + pos_embeds=pos_embeds, + ) + + def _prepare_ts_inputs(self, input_ids: torch.Tensor, ts_mm_inputs: list[MultiModalData]): + multimodal_mask = None + ts_values = None + ts_lens = None + ts_sr = None + ts_channels = None + + if ts_mm_inputs: + # time series samples can have different lengths or channel counts, so pad before batching. + ts_values = self._collate_time_series_values(ts_mm_inputs) + ts_lens = torch.cat([inp.meta['ts_lens'] for inp in ts_mm_inputs]) + ts_sr = torch.cat([inp.meta['ts_sr'] for inp in ts_mm_inputs]) + ts_channels = torch.cat([inp.meta['ts_channels'] for inp in ts_mm_inputs]) + multimodal_mask = self.get_multimodal_mask(input_ids, ts_mm_inputs) + + return dict( + multimodal_mask=multimodal_mask, + ts_values=ts_values, + ts_lens=ts_lens, + ts_sr=ts_sr, + ts_channels=ts_channels, + ) + + def prepare_inputs_for_generation( + self, + past_key_values: list[list[torch.Tensor]], + inputs_embeds: torch.Tensor | None = None, + context: StepContext | None = None, + ): + """Prepare input.""" + # get input_ids, position_ids and attention metadatas + input_ids = context.input_ids + position_ids = context.position_ids + attn_metadata = context.attn_metadata + + # make past_key_values + state_caches = list(cache.transpose(0, 1) for cache in context.state_caches) + state_caches = list(zip(state_caches[0], state_caches[1])) + past_key_values = list(past_key_values) + new_past_key_values = [] + for layer_type in self.config.text_config.layer_types: + if layer_type == 'linear_attention': + new_past_key_values.append(state_caches.pop(0)) + elif layer_type == 'full_attention': + new_past_key_values.append(past_key_values.pop(0)) + + vision_mm_inputs, ts_mm_inputs = self._split_multimodal_inputs(context) + # one forward can insert either vision embeddings or TS embeddings, not both. + if vision_mm_inputs and ts_mm_inputs: + raise ValueError('InternS2Preview does not support vision and time-series inputs in the same batch.') + + # vlm inputs + vision_inputs = dict( + pixel_values=None, + vis_cu_seqlens=None, + vis_pos_emb=None, + multimodal_mask=None, + grid_thw=None, + pos_embeds=None, + ) + if vision_mm_inputs: + vision_inputs = self._prepare_vision_inputs(input_ids, vision_mm_inputs) + + mrope_position_ids = getattr(context, 'mrope_position_ids', None) + + # process vision embeddings + vision_embeddings = context.input_embeddings + vision_embedding_indexing = context.input_embedding_indexing + if vision_embeddings is not None and len(vision_embeddings) > 0: + if inputs_embeds is None: + inputs_embeds = self.get_input_embeddings()(input_ids) + inputs_embeds[:, vision_embedding_indexing, :] = vision_embeddings.to(inputs_embeds) + + # return input embeds for spec decoding + return_input_embeds = self.is_spec_decoding and (vision_inputs['pixel_values'] is not None + or context.is_chunk_multimodal) + + # time series inputs + ts_inputs = self._prepare_ts_inputs(input_ids, ts_mm_inputs) + ts_multimodal_mask = ts_inputs.pop('multimodal_mask') + vision_multimodal_mask = vision_inputs.pop('multimodal_mask') + multimodal_mask = ts_multimodal_mask if ts_multimodal_mask is not None else vision_multimodal_mask + + # inputs of forward + return dict( + input_ids=input_ids, + position_ids=position_ids, + past_key_values=new_past_key_values, + attn_metadata=attn_metadata, + inputs_embeds=inputs_embeds, + state_ids=context.state_offsets, + # mm inputs + mrope_position_ids=mrope_position_ids, + multimodal_mask=multimodal_mask, + return_input_embeds=return_input_embeds, + **vision_inputs, + **ts_inputs, + ) + + def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]): + def remaining_weights(): + for name, loaded_weight in weights: + if name.startswith('time_series_forecaster.'): + continue + yield name, loaded_weight + + # InternS2Preview TS encoders use Whisper layers; HF omits k_proj bias. + remaining_weights_with_bias = _create_fake_bias_for_whisper_k_proj(remaining_weights(), + '.self_attn.k_proj.weight') + super().load_weights(remaining_weights_with_bias) + + +class InternS2PreviewInputProcessor(Qwen3_5MoeInputProcessor): + """InternS2Preview input processor with time-series support.""" + + def _make_time_series_mm_data(self, input_mm: dict[str, Any]) -> MultiModalData: + ts_values = input_mm['ts_values'].to(self.dtype) + offset = input_mm['offset'] + ts_token_id = input_mm['ts_token_id'] + ts_lens = input_mm['ts_lens'] + ts_sr = input_mm['ts_sr'] + ts_channels = input_mm.get('ts_channels') + + meta = dict(ts_lens=ts_lens, ts_sr=ts_sr, ts_token_id=ts_token_id) + if ts_channels is not None: + meta['ts_channels'] = ts_channels + return MultiModalData(modality=Modality.TIME_SERIES, + data=ts_values, + start=offset[0], + end=offset[1], + meta=meta) + + def preprocess_input(self, + input_ids: list[int], + input_multimodals: list[dict[str, Any]] = None, + **kwargs) -> PreprocessInputResult: + if input_multimodals is None or len(input_multimodals) == 0: + return input_ids, input_multimodals + + input_mm_data = [] + for input_mm in input_multimodals: + modality = input_mm.get('modality') + if modality == Modality.IMAGE: + mm_data = self._make_image_mm_data(input_mm) + elif modality == Modality.VIDEO: + mm_data = self._make_video_mm_data(input_mm) + elif modality == Modality.TIME_SERIES: + mm_data = self._make_time_series_mm_data(input_mm) + else: + raise ValueError(f'unsupported modality {modality}') + input_mm_data.append(mm_data) + + return PreprocessInputResult(input_ids=input_ids, input_multimodals=dict(mm_data=input_mm_data)) diff --git a/lmdeploy/pytorch/models/interns2_preview_ts_encoder.py b/lmdeploy/pytorch/models/interns2_preview_ts_encoder.py new file mode 100644 index 0000000000..ae6fb82588 --- /dev/null +++ b/lmdeploy/pytorch/models/interns2_preview_ts_encoder.py @@ -0,0 +1,762 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +import math + +import torch +import torch.nn.functional as F +from torch import nn +from torch.nn import TransformerEncoder, TransformerEncoderLayer +from torch.nn.utils.rnn import pad_sequence +from transformers import WhisperConfig, WhisperPreTrainedModel +from transformers.activations import ACT2FN + +from lmdeploy.pytorch.nn import LayerNorm +from lmdeploy.pytorch.nn.linear import build_colwise_linear, build_rowwise_linear + +from .bert import BertConfig, BertModel +from .whisper import WhisperEncoderLayer + + +class FixPositionalEncoding(nn.Module): + + def __init__(self, + d_model: int, + max_len: int = 200000, + dtype: torch.dtype | None = None, + device: torch.device | None = None): + super().__init__() + pe = torch.zeros(max_len, 1, d_model, dtype=torch.float) + position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) + div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) + pe[:, 0, 0::2] = torch.sin(position * div_term) + pe[:, 0, 1::2] = torch.cos(position * div_term) + pe = pe.to(dtype=dtype, device=device) + self.register_buffer('pe', pe) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = x + self.pe[:x.size(0), :, :] + return x + + +class CustomWhisperEncoder(WhisperPreTrainedModel): + """Whisper encoder with time-series input/output adapters.""" + + def __init__(self, config: WhisperConfig, dtype: torch.dtype | None = None, device: torch.device | None = None): + super().__init__(config) + + self.embed_dim = config.d_model + self.num_mel_bins = config.num_mel_bins + self.max_source_positions = config.max_source_positions + self.embed_scale = math.sqrt(self.embed_dim) if config.scale_embedding else 1.0 + + self.conv1 = nn.Conv1d( + self.num_mel_bins, self.embed_dim, kernel_size=3, padding=1, dtype=dtype, device=device) + self.conv2 = nn.Conv1d( + self.embed_dim, self.embed_dim, kernel_size=3, stride=2, padding=1, dtype=dtype, device=device) + self.embed_positions = nn.Embedding(self.max_source_positions, self.embed_dim, dtype=dtype, device=device) + + self.layers = nn.ModuleList( + [WhisperEncoderLayer(config, dtype=dtype, device=device) for _ in range(config.encoder_layers)]) + self.layer_norm = LayerNorm(config.d_model, eps=1e-5, dtype=dtype, device=device) + + self.adapt_in = build_colwise_linear( + in_features=config.ts_adapt_in_dim, + out_features=80, + bias=True, + dtype=dtype, + device=device, + ) + self.adapt_out = build_rowwise_linear( + in_features=self.embed_dim, + out_features=config.ts_adapt_out_dim, + bias=True, + dtype=dtype, + device=device, + ) + + self.mask_type = None + self.chunk_length = None + + def get_input_embeddings(self) -> nn.Module: + return self.conv1 + + def set_input_embeddings(self, value: nn.Module): + self.conv1 = value + + def _make_causal_mask(self, + input_ids_shape: torch.Size, + dtype: torch.dtype, + device: torch.device, + past_key_values_length: int = 0) -> torch.Tensor: + """Create a causal attention mask in HF Whisper's expected shape.""" + bsz, tgt_len = input_ids_shape + mask = torch.full((tgt_len, tgt_len), torch.finfo(dtype).min, device=device) + mask_cond = torch.arange(mask.size(-1), device=device) + mask.masked_fill_(mask_cond < (mask_cond + 1).view(mask.size(-1), 1), 0) + mask = mask.to(dtype) + + if past_key_values_length > 0: + mask = torch.cat([torch.zeros(tgt_len, past_key_values_length, dtype=dtype, device=device), mask], dim=-1) + return mask[None, None, :, :].expand(bsz, 1, tgt_len, tgt_len + past_key_values_length) + + def _prepare_decoder_attention_mask(self, input_shape, inputs_embeds, past_key_values_length): + # create causal mask + # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len] + combined_attention_mask = None + + if input_shape[-1] > 1: + combined_attention_mask = self._make_causal_mask( + input_shape, + inputs_embeds.dtype, + device=inputs_embeds.device, + past_key_values_length=past_key_values_length, + ) + + return combined_attention_mask + + def prepare_chunk_attention_mask(self, input_shape, inputs_embeds): + block_size = round(self.chunk_length / 4 * 2) + matrix_size = input_shape[1] + + matrix = torch.ones(matrix_size, matrix_size) + + num_full_blocks = round(matrix_size // block_size) + remainder = matrix_size % block_size + for i in range(num_full_blocks): + row_start = i * block_size + col_start = i * block_size + matrix[row_start:row_start + block_size, col_start:col_start + block_size] = torch.zeros( + block_size, block_size) + + if remainder > 0: + last_row_start = num_full_blocks * block_size + last_col_start = num_full_blocks * block_size + matrix[last_row_start:last_row_start + remainder, + last_col_start:last_col_start + remainder] = torch.zeros(remainder, remainder) + + matrix = matrix * -65504 + matrix = matrix.unsqueeze(0).unsqueeze(0).repeat(input_shape[0], 1, 1, 1) + attention_mask = matrix.to(inputs_embeds.device) + return attention_mask + + def prepare_padding_mask(self, input_shape, inputs_embeds, input_lens): + matrix_size = input_shape[1] + matrix_list = [] + + for i in range(input_shape[0]): + padding_matrix = torch.ones(matrix_size, matrix_size) * -65504 + padding_matrix[:input_lens[i], :input_lens[i]] = 0 + matrix_list.append(padding_matrix) + + attention_mask = torch.stack(matrix_list).unsqueeze(1).to(inputs_embeds.dtype).to(inputs_embeds.device) + + return attention_mask + + def forward( + self, + input_features, + input_length=None, + causal=True, + ): + input_features = self.adapt_in(input_features) + input_features = input_features.permute(1, 2, 0) + + inputs_embeds = F.gelu(self.conv1(input_features)) + inputs_embeds = F.gelu(self.conv2(inputs_embeds)) + + inputs_embeds = inputs_embeds.permute(0, 2, 1) + embed_pos = self.embed_positions.weight + + if inputs_embeds.shape[1] > embed_pos.shape[0]: + target_len = inputs_embeds.shape[1] + padding = [0, 0, 0, target_len - embed_pos.shape[0]] + + embed_pos = F.pad(embed_pos, pad=padding, mode='constant', value=0) + hidden_states = inputs_embeds[:, :embed_pos.shape[0], :] + embed_pos + else: + hidden_states = inputs_embeds + embed_pos[:inputs_embeds.shape[1], :] + + input_shape = inputs_embeds.size()[:-1] + past_key_values_length = 0 + if causal: + if self.mask_type == 'chunk': + attention_mask = self.prepare_chunk_attention_mask(input_shape, inputs_embeds) + else: + attention_mask = self._prepare_decoder_attention_mask(input_shape, inputs_embeds, + past_key_values_length) + else: + attention_mask = self.prepare_padding_mask(input_shape, inputs_embeds, (input_length + 1) // 2) + + for encoder_layer in self.layers: + layer_outputs = encoder_layer(hidden_states, attention_mask) + if isinstance(layer_outputs, tuple): + hidden_states = layer_outputs[0] + else: + hidden_states = layer_outputs + + hidden_states = hidden_states.permute(1, 0, 2) + hidden_states = self.layer_norm(hidden_states) + hidden_states = self.adapt_out(hidden_states) + + lengths = (input_length + 1) // 2 + + return hidden_states, lengths + + +class MRQFormer(nn.Module): + + def __init__(self, + num_query_list: list[int], + hidden_size: int = 1024, + num_layers: int = 6, + cross_attention_freq: int = 2, + encoder_hidden_size: int | None = None, + output_size: int | None = None, + dtype: torch.dtype | None = None, + device: torch.device | None = None): + super().__init__() + + self.num_query_list = num_query_list + self.hidden_size = hidden_size + + self.query_token_list = nn.ParameterList() + for num_queries in self.num_query_list: + self.query_token_list.append( + nn.Parameter(torch.randn(1, num_queries, hidden_size, dtype=dtype, device=device))) + + bert_config = BertConfig( + vocab_size=1, + hidden_size=hidden_size, + num_hidden_layers=num_layers, + num_attention_heads=hidden_size // 16, + intermediate_size=hidden_size * 4, + max_position_embeddings=max(self.num_query_list), + add_cross_attention=True, + is_decoder=True, + cross_attention_freq=cross_attention_freq, + ) + self.transformer = BertModel(bert_config, dtype=dtype, device=device) + + if encoder_hidden_size is not None and encoder_hidden_size != hidden_size: + self.encoder_proj = build_colwise_linear( + in_features=encoder_hidden_size, + out_features=hidden_size, + bias=True, + dtype=dtype, + device=device, + ) + else: + self.encoder_proj = nn.Identity() + + if output_size is None: + output_size = hidden_size + + self.proj_out = nn.Sequential( + build_colwise_linear( + in_features=hidden_size, + out_features=4 * hidden_size, + bias=True, + dtype=dtype, + device=device, + ), + nn.ReLU(), + build_rowwise_linear( + in_features=4 * hidden_size, + out_features=output_size, + bias=True, + dtype=dtype, + device=device, + ), + ) + + def forward(self, encoder_features: torch.Tensor, attention_mask=None, res_idx: int = 0): + batch_size = encoder_features.shape[0] + + encoder_features = self.encoder_proj(encoder_features) + + query_tokens = self.query_token_list[res_idx] + query_tokens = query_tokens.expand(batch_size, -1, -1) + + encoder_attention_mask = None + if attention_mask is not None: + encoder_attention_mask = attention_mask + + outputs, _ = self.transformer( + inputs_embeds=query_tokens, + encoder_hidden_states=encoder_features, + encoder_attention_mask=encoder_attention_mask + ) + + outputs = self.proj_out(outputs) + + return outputs + + +class SingleResChunkQformerSubsampling(nn.Module): + + def __init__(self, + hidden_dim: int = 128, + alpha: float = 0.5, + patch: int = 800, + num_query: int = 32, + num_conv_layers: int = 0, + dtype: torch.dtype | None = None, + device: torch.device | None = None): + super().__init__() + + self.patch_list = [patch] + self.num_query_list = [num_query] + + self.num_conv_layers = num_conv_layers + if self.num_conv_layers == 0: + self.conv = nn.Conv1d( + in_channels=1, + out_channels=hidden_dim - 2, + kernel_size=5, + stride=1, + padding=2, + dtype=dtype, + device=device) + self.mask_pool = None + else: + conv_layers = [] + pool_layers = [] + output_channels = hidden_dim - 2 + + for i in range(self.num_conv_layers): + in_channels = 1 if i == 0 else output_channels + conv_layers.append( + nn.Conv1d(in_channels, + output_channels, + kernel_size=5, + stride=2, + padding=2, + dtype=dtype, + device=device)) + conv_layers.append(nn.ReLU(inplace=True)) + pool_layers.append(nn.MaxPool1d(kernel_size=5, stride=2, padding=2)) + + self.conv = nn.Sequential(*conv_layers) + self.mask_pool = nn.Sequential(*pool_layers) + + self.mrqformer = MRQFormer( + num_query_list=self.num_query_list, + hidden_size=hidden_dim, + num_layers=6, + cross_attention_freq=2, + output_size=hidden_dim, + dtype=dtype, + device=device) + + self.pos_encoder = FixPositionalEncoding(d_model=hidden_dim, dtype=dtype, device=device) + + self.alpha = alpha + channel_encoder_layers = TransformerEncoderLayer(d_model=hidden_dim, nhead=32, dtype=dtype, device=device) + self.channel_encoder = TransformerEncoder(channel_encoder_layers, num_layers=1) + self.fuze_proj = build_rowwise_linear( + in_features=hidden_dim, + out_features=256, + bias=True, + dtype=dtype, + device=device, + ) + + def forward(self, + inputs: torch.Tensor, + input_lens: torch.Tensor | None = None, + sr=None, + force_strides=None, + mask: torch.Tensor | None = None, + subrate: torch.Tensor | None = None): + if mask is None: + mask = torch.ones(inputs.shape).to(inputs.device) + features, feature_lens, learnable_lens, strides, raw_outputs = self.forward_patch( + inputs, input_lens, sr, force_strides, mask, subrate) + + return features, feature_lens, learnable_lens, strides, raw_outputs + + def forward_patch(self, + inputs: torch.Tensor, + input_lens: torch.Tensor, + sr, + force_strides, + mask: torch.Tensor | None = None, + subrate: torch.Tensor | None = None): + pred_strides = None + + output_list = [] + + seq = inputs + + mean = seq.mean(dim=1, keepdim=True) + std = seq.std(dim=1, keepdim=True) + + abs_mean_plus_1 = torch.abs(mean) + 1.0 + log_abs_plus_1 = torch.log(abs_mean_plus_1) + sign_mean = torch.sign(mean) + mean_feat = sign_mean * log_abs_plus_1 + + std_feat = torch.log(std + 1e-7) + + seq = (seq - mean) / (std + 1e-7) + + max_patch = torch.tensor(max(self.patch_list), dtype=torch.float32, device=seq.device) + max_num_query = torch.tensor(max(self.num_query_list), dtype=torch.float32, device=seq.device) + if subrate is None: + subrate = max_patch / max_num_query + else: + subrate = subrate.to(device=seq.device, dtype=torch.float32) + step = subrate * max_num_query + patch_size = torch.ceil(step) + seq_len = torch.tensor(inputs.shape[1], dtype=torch.float32, device=seq.device) + + output_len = torch.ceil((seq_len - patch_size) / step + 1) + pad_len = (torch.ceil((output_len - 1) * step + patch_size - seq_len)).long().item() + + if seq.ndim == 2: + seq = seq.unsqueeze(-1) + seq = F.pad(seq, (0, 0, 0, pad_len, 0, 0), 'constant', 0) + + padded_mask = F.pad(mask, (0, 0, 0, pad_len, 0, 0), 'constant', 0) + + batch_size, seq_len, channels = seq.shape + seq = seq.permute(0, 2, 1) + seq = seq.reshape(batch_size * channels, 1, seq_len) + if self.num_conv_layers == 0: + seq = F.relu(self.conv(seq)) + else: + seq = self.conv(seq) + seq_len = seq.shape[-1] + seq = seq.reshape(batch_size, channels, -1, seq_len) + seq = seq.permute(0, 3, 1, 2) + + mean_feat = mean_feat.expand([batch_size, seq_len, channels]).unsqueeze(-1) + std_feat = std_feat.expand([batch_size, seq_len, channels]).unsqueeze(-1) + + seq = torch.cat([seq, mean_feat, std_feat], dim=-1) + hidden_dim = seq.shape[-1] + + if self.mask_pool is not None: + padded_mask = padded_mask.permute(0, 2, 1) + padded_mask = self.mask_pool(padded_mask) + padded_mask = padded_mask.permute(0, 2, 1) + + for res_idx, _ in enumerate(self.patch_list): + num_query = torch.tensor(self.num_query_list[res_idx], dtype=torch.float32, device=seq.device) + stride = subrate * num_query + patch = torch.ceil(stride) + patch_output_len = torch.floor(output_len * step / (stride * (2**self.num_conv_layers))) + + indices = (torch.floor(torch.arange(0, patch_output_len.item(), device=seq.device) * stride).unsqueeze(1) + + torch.arange(patch, device=seq.device)).long() + patched = seq[:, indices, :, :] + _, num_patch, patch_len, _, _ = patched.shape + patched = patched.permute(1, 2, 0, 3, 4) + patched = patched.reshape(num_patch, patch_len, -1) + patched = patched.reshape(num_patch, patch_len, batch_size, channels, hidden_dim) + + patched = patched.permute(2, 0, 1, 3, 4) + patched = patched.reshape(batch_size * num_patch, patch_len, channels, hidden_dim) + + patched_mask = padded_mask[:, indices, :] + patched_mask = patched_mask.reshape(batch_size * num_patch, patch_len, channels) + + raw_output = self.forward_encoder(patched, patched_mask, res_idx) + output = raw_output.reshape(-1, hidden_dim) + output_list.append(output) + + outputs = torch.cat(output_list, dim=1) + outputs = self.fuze_proj(outputs) + out_hidden_dim = outputs.shape[1] + + outputs = outputs.reshape(batch_size, -1, out_hidden_dim) + + output_lens = torch.ceil((padded_mask[:, :, 0].sum(dim=-1) - patch_size) / step + 1) * max_num_query + + learnable_lens = None + raw_outputs = None + + return outputs, output_lens, learnable_lens, pred_strides, raw_outputs + + def forward_encoder(self, inputs: torch.Tensor, mask: torch.Tensor, res_idx: int = 0) -> torch.Tensor: + x = inputs + num_patch, patch_len, channels, hidden_dim = x.shape + x = x.permute(1, 0, 2, 3) + x = x.reshape(patch_len, num_patch * channels, -1) + + x = self.pos_encoder(x) + + mask = mask.permute(0, 2, 1) + mask = mask.reshape(num_patch * channels, patch_len) + + chunk_size = 4096 * 8 + all_outputs = [] + + for i in range(0, x.size(1), chunk_size): + x_chunk = x[:, i:i + chunk_size, :].contiguous() + chunk_mask = mask[i:i + chunk_size] + x_chunk = x_chunk.permute(1, 0, 2) + x_chunk = self.mrqformer(x_chunk, attention_mask=chunk_mask, res_idx=res_idx) + x_chunk = x_chunk.permute(1, 0, 2) + all_outputs.append(x_chunk) + + x = torch.cat(all_outputs, dim=1) + + x = x.reshape(-1, num_patch, channels, hidden_dim) + x = x.permute(2, 1, 0, 3) + x = x.reshape(channels, -1, hidden_dim) + + all_outputs = [] + + for i in range(0, x.size(1), chunk_size): + x_chunk = x[:, i:i + chunk_size, :].contiguous() + x_chunk = self.channel_encoder(x_chunk) + x_chunk = x_chunk.mean(0) + all_outputs.append(x_chunk) + + x = torch.cat(all_outputs, dim=0) + x = x.reshape(num_patch, -1, hidden_dim) + + return x + + +class InternS2PreviewTimeSeriesProjector(nn.Module): + + def __init__(self, config, dtype: torch.dtype | None = None, device: torch.device | None = None): + super().__init__() + self.layer_norm = LayerNorm(config.ts_hidden_dim, eps=1e-5, dtype=dtype, device=device) + self.linear_1 = build_colwise_linear( + in_features=config.ts_hidden_dim, + out_features=config.out_hidden_size, + bias=True, + dtype=dtype, + device=device, + ) + self.act = ACT2FN[config.activation_function] + self.linear_2 = build_rowwise_linear( + in_features=config.out_hidden_size, + out_features=config.out_hidden_size, + bias=True, + dtype=dtype, + device=device, + ) + + def forward(self, ts_features: torch.Tensor) -> torch.Tensor: + hidden_states = self.layer_norm(ts_features) + hidden_states = self.linear_1(hidden_states) + hidden_states = self.act(hidden_states) + hidden_states = self.linear_2(hidden_states) + return hidden_states + + +class ChunkModel(nn.Module): + + def __init__(self, encoder_embed: nn.Module, encoder: nn.Module, chunk_size: int = 6400, step: int = 6400): + super().__init__() + + self.chunk_size = chunk_size + self.step = step + assert self.step <= self.chunk_size or self.chunk_size < 0 + + self.encoder_embed = encoder_embed + self.encoder = encoder + + def chunk_tensor(self, tensor: torch.Tensor): + seq_len = tensor.shape[0] + chunk_size = self.chunk_size + step = self.step + + chunks = [] + masks = [] + + if chunk_size > 0: + start = 0 + while start < seq_len: + end = min(start + chunk_size, seq_len) + chunk = tensor[start:end, :] + chunks.append(chunk) + mask = torch.zeros(chunk.shape) + mask[:end - start, :] = 1 + masks.append(mask) + + if end >= seq_len: + break + + start += step + + output = pad_sequence(chunks, batch_first=True) + output_mask = pad_sequence(masks, batch_first=True).to(output.device) + else: + output = tensor.unsqueeze(0) + output_mask = torch.ones(output.shape).to(tensor.device) + + return output, output_mask + + def concat_chunks_simple(self, + chunks: torch.Tensor, + chunk_lengths: torch.Tensor | None = None) -> tuple[torch.Tensor, int]: + """Concatenate chunks without overlap.""" + num_chunks, chunk_len, channels = chunks.shape + + if chunk_lengths is None: + signal = chunks.reshape(-1, channels) + signal_len = num_chunks * chunk_len + else: + signal_list = [] + signal_len = 0 + for i in range(num_chunks): + valid_len = int(chunk_lengths[i].item()) + signal_list.append(chunks[i, :valid_len, :]) + signal_len += valid_len + signal = torch.cat(signal_list, dim=0) + + return signal, signal_len + + def forward_encoder(self, + x: torch.Tensor, + x_lens: torch.Tensor, + x_channels: torch.Tensor, + sr=None, + force_strides=None) -> tuple[torch.Tensor, torch.Tensor]: + """Compute encoder outputs.""" + assert x.shape[0] == len(x_lens) and x.shape[0] == len(x_channels) + batch_size = x.shape[0] + + outputs = [] + output_lens = [] + output_chunks = [] + for b in range(batch_size): + seq = x[b, :x_lens[b], :x_channels[b]] + chunks, mask = self.chunk_tensor(seq) + subrate = torch.clamp(x_lens[b].to(device=chunks.device, dtype=torch.float32) / 500, min=1.0) + + chunk_output, chunk_lens, learnable_lens, _, _ = self.encoder_embed( + chunks, x_lens[b], sr, force_strides, mask=mask, subrate=subrate) + + outputs.append(chunk_output) + output_lens.append(chunk_lens) + output_chunks.append(chunk_output.shape[0]) + + x_lens = torch.cat(output_lens, dim=0) + max_len = x_lens.max().item() + + # Preserve reference behavior: padded chunk embeddings are constructed + # here but the original input tensor is fed into the final encoder. + padded_list = [] + for tensor in outputs: + pad_len = int(max_len - tensor.shape[1]) + padded_list.append(F.pad(tensor, (0, 0, 0, pad_len, 0, 0), mode='constant', value=0)) + + if x_lens.ndim == 0: + x_lens = x_lens.unsqueeze(-1) + + x = x.permute(1, 0, 2) + encoder_out, encoder_out_lens = self.encoder(x, x_lens) + encoder_out = encoder_out.permute(1, 0, 2) + + if learnable_lens is not None: + learnable_lens = learnable_lens / 2 + + total_chunks = encoder_out.shape[0] + assert sum(output_chunks) == total_chunks, f'chunk count mismatch: {sum(output_chunks)} vs {total_chunks}' + + features = [] + feature_lens = [] + start = 0 + + for count in output_chunks: + end = start + count + signal_chunks = encoder_out[start:end] + + feature, feature_len = self.concat_chunks_simple(signal_chunks, encoder_out_lens[start:end]) + + features.append(feature) + start = end + feature_lens.append(feature_len) + + encoder_out = pad_sequence(features, batch_first=True) + encoder_out_lens = torch.tensor(feature_lens).to(encoder_out.device) + + return encoder_out, encoder_out_lens + + +class InternS2PreviewTimeSeriesModel(ChunkModel): + + def __init__(self, config, dtype: torch.dtype | None = None, device: torch.device | None = None): + encoder_embed = SingleResChunkQformerSubsampling( + hidden_dim=config.subsampling_hidden_dim, + patch=config.subsampling_patch, + num_query=config.subsampling_num_query, + num_conv_layers=config.subsampling_num_conv_layers, + dtype=dtype, + device=device, + ) + encoder = CustomWhisperEncoder(config, dtype=dtype, device=device) + chunk_size = getattr(config, 'chunk_size', 12800) + step = getattr(config, 'chunk_step', chunk_size) + super().__init__(encoder_embed=encoder_embed, encoder=encoder, chunk_size=chunk_size, step=step) + self.config = config + self.projector = InternS2PreviewTimeSeriesProjector(config, dtype=dtype, device=device) + + @staticmethod + def make_pad_mask(lengths: torch.Tensor) -> torch.Tensor: + assert lengths.ndim == 1, lengths.ndim + max_len = int(lengths.max().item()) + seq_range = torch.arange(0, max_len, device=lengths.device) + expanded_lengths = seq_range.unsqueeze(0).expand(lengths.size(0), max_len) + return expanded_lengths >= lengths.unsqueeze(-1) + + def forward( + self, + time_series_signals: torch.Tensor, + ts_lens: torch.Tensor, + sr: torch.Tensor | None = None, + channels: torch.Tensor | None = None, + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + if channels is None: + raise ValueError('channels must be specified') + if time_series_signals.ndim != 3: + raise ValueError(f'wrong time_series_signals size: {time_series_signals.shape}') + + outputs = [] + output_lens = [] + output_chunks = [] + for b in range(time_series_signals.shape[0]): + seq = time_series_signals[b, :ts_lens[b], :channels[b]] + chunks, mask = self.chunk_tensor(seq) + subrate = torch.clamp(ts_lens[b].to(device=chunks.device, dtype=torch.float32) / 500, min=1.0) + + chunk_output, chunk_lens, _, _, _ = self.encoder_embed(chunks, mask=mask, subrate=subrate) + outputs.append(chunk_output) + output_lens.append(chunk_lens.long()) + output_chunks.append(chunk_output.shape[0]) + + x_lens = torch.cat(output_lens, dim=0).long() + max_len = int(x_lens.max().item()) + + padded_list = [] + for tensor in outputs: + pad_len = int(max_len - tensor.shape[1]) + padded_list.append(F.pad(tensor, (0, 0, 0, pad_len, 0, 0), mode='constant', value=0)) + + x = torch.cat(padded_list, dim=0).permute(1, 0, 2) + encoder_out, encoder_out_lens = self.encoder(x, x_lens, causal=True) + encoder_out = encoder_out.permute(1, 0, 2) + encoder_out_lens = (x_lens + 1) // 2 + + features = [] + feature_lens = [] + start = 0 + for count in output_chunks: + end = start + count + feature, feature_len = self.concat_chunks_simple(encoder_out[start:end], encoder_out_lens[start:end]) + features.append(feature) + feature_lens.append(feature_len) + start = end + + encoder_out = pad_sequence(features, batch_first=True) + encoder_out_lens = torch.tensor(feature_lens, device=encoder_out.device) + + ts_pad_mask = self.make_pad_mask(encoder_out_lens) + ts_embeds = self.projector(encoder_out) + return ts_embeds, ts_pad_mask, encoder_out diff --git a/lmdeploy/pytorch/models/module_map.py b/lmdeploy/pytorch/models/module_map.py index 7a2010b762..358c60247e 100644 --- a/lmdeploy/pytorch/models/module_map.py +++ b/lmdeploy/pytorch/models/module_map.py @@ -184,14 +184,6 @@ f'{LMDEPLOY_PYTORCH_MODEL_PATH}.qwen3_5_moe.Qwen3_5MoeForConditionalGeneration', }) -# interns2preview -MODULE_MAP.update({ - 'InternS2PreviewForConditionalGeneration': - f'{LMDEPLOY_PYTORCH_MODEL_PATH}.qwen3_5_moe.Qwen3_5MoeForConditionalGeneration', - 'InternS2PreviewForCausalLM': - f'{LMDEPLOY_PYTORCH_MODEL_PATH}.qwen3_5_moe.Qwen3_5MoeForConditionalGeneration', -}) - MODULE_MAP.update({ 'Qwen3_5MTPModel': f'{LMDEPLOY_PYTORCH_MODEL_PATH}.qwen3_5_mtp.Qwen3_5MTPModel', }) @@ -203,6 +195,14 @@ f'{LMDEPLOY_PYTORCH_MODEL_PATH}.qwen3_omni_moe_thinker.Qwen3OmniMoeThinkerForConditionalGeneration', }) +# interns2-preview +MODULE_MAP.update({ + 'InternS2PreviewForConditionalGeneration': + f'{LMDEPLOY_PYTORCH_MODEL_PATH}.interns2_preview.InternS2PreviewForConditionalGeneration', + 'InternS2PreviewForCausalLM': + f'{LMDEPLOY_PYTORCH_MODEL_PATH}.interns2_preview.InternS2PreviewForConditionalGeneration', +}) + # phi-3 MODULE_MAP.update({ 'Phi3ForCausalLM': f'{LMDEPLOY_PYTORCH_MODEL_PATH}.phi3.Phi3ForCausalLM', diff --git a/lmdeploy/pytorch/models/qwen3_5.py b/lmdeploy/pytorch/models/qwen3_5.py index 2544e38ef7..9defa43f44 100644 --- a/lmdeploy/pytorch/models/qwen3_5.py +++ b/lmdeploy/pytorch/models/qwen3_5.py @@ -30,7 +30,6 @@ build_rowwise_linear, ) from lmdeploy.pytorch.weight_loader.model_weight_loader import default_weight_loader, load_weight -from lmdeploy.vl.constants import Modality from .patch import add_prefix, get_build_model_context from .qwen2_5_vl import Qwen2_5_VisionRotaryEmbedding as Qwen3_5VisionRotaryEmbedding @@ -934,10 +933,6 @@ def forward( grid_thw: torch.Tensor | None = None, all_routed_experts: torch.Tensor | None = None, return_input_embeds: bool = False, - # for time series - ts_values: torch.Tensor = None, - ts_lens: torch.Tensor = None, - ts_sr: torch.Tensor = None, ): """Model forward, return logits.""" @@ -964,11 +959,6 @@ def forward( # mask and scatter to create final input embeddings multimodal_mask = multimodal_mask.unsqueeze(-1).expand_as(inputs_embeds) inputs_embeds = inputs_embeds.masked_scatter(multimodal_mask, image_embeds) - elif ts_values is not None: - if not hasattr(self, 'time_series'): - raise RuntimeError('Time-series inputs require a time_series module.') - ts_embeds = self.time_series(ts_values, ts_lens, ts_sr) # [B, T, C] - inputs_embeds = inputs_embeds.masked_scatter(multimodal_mask[..., None], ts_embeds) output_inputs_embeds = inputs_embeds if return_input_embeds else None @@ -1029,7 +1019,6 @@ def __init__(self, self.enable_return_routed_experts = False self.is_spec_decoding = get_build_model_context().num_spec_tokens > 0 - def forward( self, input_ids: torch.Tensor, @@ -1046,10 +1035,6 @@ def forward( pos_embeds: torch.Tensor | None = None, grid_thw: torch.Tensor | None = None, return_input_embeds: bool = False, - # for time series - ts_values: torch.Tensor = None, - ts_lens: torch.Tensor = None, - ts_sr: torch.Tensor = None, **kwargs, ): """Model forward, return logits.""" @@ -1076,10 +1061,6 @@ def forward( grid_thw=grid_thw, all_routed_experts=all_routed_experts, return_input_embeds=return_input_embeds, - # for time series - ts_values=ts_values, - ts_lens=ts_lens, - ts_sr=ts_sr, ) return dict(hidden_states=hidden_states, all_routed_experts=all_routed_experts, @@ -1119,33 +1100,25 @@ def prepare_inputs_for_generation( multimodal_mask = None grid_thw = None pos_embeds = None - # for time series - ts_values = None - ts_lens = None - ts_sr = None if context.input_multimodals is not None: - mm_inputs = [input_mm.get('mm_data', []) for input_mm in context.input_multimodals] + mm_inputs = [ + input_mm.get('mm_data', []) for input_mm in context.input_multimodals if input_mm is not None + ] # flatten batch mm_inputs = [item for sublist in mm_inputs for item in sublist] if len(mm_inputs) > 0: - modality = mm_inputs[0].modality multimodal_mask = self.get_multimodal_mask(input_ids, mm_inputs) - if modality == Modality.TIME_SERIES: - ts_values = torch.cat([inp.data for inp in mm_inputs]) - ts_lens = torch.cat([inp.meta['ts_lens'] for inp in mm_inputs]) - ts_sr = torch.cat([inp.meta['ts_sr'] for inp in mm_inputs]) - else: - pixel_values = torch.cat([inp.data for inp in mm_inputs]) - grid_thw = torch.stack([data.meta['grid_thw'] for data in mm_inputs]).cpu() - vis_pos_emb = self.model.visual.rot_pos_emb(grid_thw) - pos_embeds = self.model.visual.fast_pos_embed_interpolate(grid_thw) - vis_cu_seqlens = torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2], - grid_thw[:, 0]).to(pixel_values.device) - vis_cu_seqlens = vis_cu_seqlens.cumsum(dim=0, dtype=torch.int32) - vis_pos_emb = vis_pos_emb.repeat(1, 2) - vis_pos_emb = (vis_pos_emb.cos(), vis_pos_emb.sin()) + pixel_values = torch.cat([inp.data for inp in mm_inputs]) + grid_thw = torch.stack([data.meta['grid_thw'] for data in mm_inputs]).cpu() + vis_pos_emb = self.model.visual.rot_pos_emb(grid_thw) + pos_embeds = self.model.visual.fast_pos_embed_interpolate(grid_thw) + vis_cu_seqlens = torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2], + grid_thw[:, 0]).to(pixel_values.device) + vis_cu_seqlens = vis_cu_seqlens.cumsum(dim=0, dtype=torch.int32) + vis_pos_emb = vis_pos_emb.repeat(1, 2) + vis_pos_emb = (vis_pos_emb.cos(), vis_pos_emb.sin()) mrope_position_ids = getattr(context, 'mrope_position_ids', None) @@ -1177,10 +1150,6 @@ def prepare_inputs_for_generation( grid_thw=grid_thw, pos_embeds=pos_embeds, return_input_embeds=return_input_embeds, - # for time series - ts_values=ts_values, - ts_lens=ts_lens, - ts_sr=ts_sr, ) def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]): diff --git a/lmdeploy/pytorch/models/qwen3_5_moe.py b/lmdeploy/pytorch/models/qwen3_5_moe.py index efbb972c5c..a4dae9d994 100644 --- a/lmdeploy/pytorch/models/qwen3_5_moe.py +++ b/lmdeploy/pytorch/models/qwen3_5_moe.py @@ -13,7 +13,6 @@ from lmdeploy.pytorch.nn.moe import build_fused_moe from lmdeploy.pytorch.weight_loader.model_weight_loader import load_weight -from .interns1_pro_time_series import InternS1ProTimeSeriesModel from .patch import add_prefix, get_build_model_context from .qwen3_5 import ( Qwen3_5Attention, @@ -232,9 +231,6 @@ def __init__(self, device=device, prefix=add_prefix('language_model', prefix)) - # build time series model - if hasattr(config, 'ts_config'): - self.time_series = InternS1ProTimeSeriesModel(config.ts_config, dtype=dtype, device=device) class Qwen3_5MoeForConditionalGeneration(Qwen3_5ForConditionalGeneration): """ModelForCausalLM.""" @@ -362,7 +358,7 @@ def __skip_layers(name): if 'mtp.' in name: continue - if name.startswith(('model.time_series.', 'time_series_forecaster.')): + if name.startswith('time_series_forecaster.'): continue if 'rotary_emb.inv_freq' in name: continue diff --git a/lmdeploy/pytorch/models/qwen3_vl.py b/lmdeploy/pytorch/models/qwen3_vl.py index db3f69af55..a07cb2eea0 100644 --- a/lmdeploy/pytorch/models/qwen3_vl.py +++ b/lmdeploy/pytorch/models/qwen3_vl.py @@ -705,21 +705,6 @@ def _make_video_mm_data(self, input_mm: dict[str, Any]) -> MultiModalData: )) return mm_data - def _make_time_series_mm_data(self, input_mm: dict[str, Any]) -> MultiModalData: - """Make time series MultiModalData.""" - ts_values = input_mm['ts_values'].to(self.dtype) - offset = input_mm['offset'] - ts_token_id = input_mm['ts_token_id'] - ts_lens = input_mm['ts_lens'] - ts_sr = input_mm['ts_sr'] - - mm_data = MultiModalData(modality=Modality.TIME_SERIES, - data=ts_values, - start=offset[0], - end=offset[1], - meta=dict(ts_lens=ts_lens, ts_sr=ts_sr, ts_token_id=ts_token_id)) - return mm_data - def preprocess_input(self, input_ids: list[int], input_multimodals: list[dict[str, Any]] = None, @@ -735,8 +720,8 @@ def preprocess_input(self, mm_data = self._make_image_mm_data(input_mm) elif modality == Modality.VIDEO: mm_data = self._make_video_mm_data(input_mm) - elif modality == Modality.TIME_SERIES: - mm_data = self._make_time_series_mm_data(input_mm) + else: + raise ValueError(f'unsupported modality {modality}') input_mm_data.append(mm_data) result = PreprocessInputResult(input_ids=input_ids, input_multimodals=dict(mm_data=input_mm_data)) diff --git a/lmdeploy/pytorch/models/whisper.py b/lmdeploy/pytorch/models/whisper.py index 4b5ba9cbfb..b9f0361ca9 100644 --- a/lmdeploy/pytorch/models/whisper.py +++ b/lmdeploy/pytorch/models/whisper.py @@ -1,6 +1,8 @@ # Copyright (c) OpenMMLab. All rights reserved. # adpated from https://github.com/huggingface/transformers/blob/main/src/transformers/models/whisper/modeling_whisper.py +from collections.abc import Iterable + import torch from torch import nn from transformers.activations import ACT2FN @@ -10,6 +12,19 @@ from lmdeploy.pytorch.nn.linear import build_colwise_linear, build_qkv_proj, build_rowwise_linear +def _create_fake_bias_for_whisper_k_proj(weights: Iterable[tuple[str, torch.Tensor]], + fake_bias_key_name: str) -> Iterable[tuple[str, torch.Tensor]]: + """Create zero K bias for Whisper QKV packed loading. + + Transformers Whisper has Q/V projection bias but no K projection bias. We synthesize a zero K bias to keep + LMDeploy's packed QKV behavior aligned. + """ + for name, loaded_weight in weights: + yield name, loaded_weight + if 'time_series.' in name and name.endswith(fake_bias_key_name): + yield name.replace('weight', 'bias'), loaded_weight.new_zeros(loaded_weight.size(0)) + + class WhisperAttention(nn.Module): """Multi-headed attention from 'Attention Is All You Need' paper.""" @@ -36,7 +51,6 @@ def __init__( self.scaling = self.head_dim**-0.5 # packed qkv - # TODO, zhouxinyu, hf whisper hard-code k_proj bias = False, may double check self.qkv_proj = build_qkv_proj(self.embed_dim, num_q_heads=self.num_heads, num_kv_heads=self.num_heads, diff --git a/lmdeploy/vl/model/base.py b/lmdeploy/vl/model/base.py index 1eab9a9a0b..deca71383f 100644 --- a/lmdeploy/vl/model/base.py +++ b/lmdeploy/vl/model/base.py @@ -42,6 +42,7 @@ class VisionModel(ABC): 'ts_values': Modality.TIME_SERIES, 'ts_sr': Modality.TIME_SERIES, 'ts_lens': Modality.TIME_SERIES, + 'ts_channels': Modality.TIME_SERIES, } # processor output attributes that carry the main feature tensor @@ -191,6 +192,8 @@ def preprocess(self, 'time series processor is not defined for time series input' assert not raw_images and not raw_videos and not raw_audios, \ 'time series is not compatible with image/video/audio input' + if len(raw_time_series) != 1: + raise ValueError('Only one time-series input is supported per request.') self.tokenizer = self.processor.tokenizer time_series_processor = self.time_series_processor kwargs['time_series'] = raw_time_series @@ -234,8 +237,7 @@ def preprocess(self, # expand bundled hf processor outputs into per-image/video entry for lmdeploy to consume expanded_mm_items = get_expanded_mm_items(collected_mm_items, self.mm_tokens) - result = dict(input_ids=input_ids.tolist(), multimodal=expanded_mm_items) - return result + return dict(input_ids=input_ids.tolist(), multimodal=expanded_mm_items) @staticmethod def has_input_ids(messages: list[dict]) -> bool: diff --git a/lmdeploy/vl/model/builder.py b/lmdeploy/vl/model/builder.py index 6b2152bc09..44b7ca69b6 100644 --- a/lmdeploy/vl/model/builder.py +++ b/lmdeploy/vl/model/builder.py @@ -15,6 +15,7 @@ from .glm4_1v import GLM4_1_VisionModel # noqa F401 from .glm4_v import GLM4VisionModel # noqa F401 from .interns1_pro import InternS1ProVisionModel # noqa F401 +from .interns2_preview import InternS2PreviewVisionModel # noqa F401 from .internvl import InternVLVisionModel # noqa F401 from .internvl3_hf import InternVL3VisionModel # noqa F401 from .llama4 import LLama4VisionModel # noqa F401 diff --git a/lmdeploy/vl/model/interns2_preview.py b/lmdeploy/vl/model/interns2_preview.py new file mode 100644 index 0000000000..f15cfd7ec3 --- /dev/null +++ b/lmdeploy/vl/model/interns2_preview.py @@ -0,0 +1,149 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Any + +import numpy as np +import torch + +from lmdeploy.utils import get_logger +from lmdeploy.vl.model.base import VISION_MODELS, MultimodalSpecialTokens +from lmdeploy.vl.model.qwen3_5 import Qwen3_5Model, check_transformers +from lmdeploy.vl.model.utils import disable_logging + +logger = get_logger('lmdeploy') + + +@VISION_MODELS.register_module() +class InternS2PreviewVisionModel(Qwen3_5Model): + """InternS2Preview vision model with time-series preprocessing.""" + + _arch = [ + 'InternS2PreviewForConditionalGeneration', + 'InternS2PreviewForCausalLM', + ] + _turbomind_native_vision = True + + def build_preprocessor(self, trust_remote_code: bool = False): + super().build_preprocessor(trust_remote_code=trust_remote_code) + + self.ts_token = getattr(self.processor, 'ts_token', None) + self.ts_token_id = getattr(self.processor, 'ts_token_id', None) + self.ts_start_token = getattr(self.processor, 'ts_start_token', None) + self.ts_end_token = getattr(self.processor, 'ts_end_token', None) + + self.mm_tokens = MultimodalSpecialTokens(image_token=self.image_token, + video_token=self.video_token, + ts_token=self.ts_token, + image_token_id=self.image_token_id, + video_token_id=self.video_token_id, + ts_token_id=self.ts_token_id) + + self.ts_signals_do_normalize = getattr(self.processor, 'ts_signals_do_normalize', True) + self.ts_signals_do_truncate = getattr(self.processor, 'ts_signals_do_truncate', True) + + def time_series_processor(self, + text: list[str], + time_series: list[Any], + sampling_rate: float | None = None, + **kwargs): + ts_input = time_series[0] if isinstance(time_series, list) else time_series + sampling_rate = sampling_rate[0] if isinstance(sampling_rate, list) else sampling_rate + + if not isinstance(ts_input, np.ndarray): + ts_input = np.array(ts_input, dtype=np.float32) + + if self.ts_signals_do_normalize: + mean = ts_input.mean(axis=0, keepdims=True) + std = ts_input.std(axis=0, keepdims=True) + ts_input = (ts_input - mean) / (std + 1e-8) + + max_ts_len = 240000 + if self.ts_signals_do_truncate and len(ts_input) > max_ts_len: + ts_input = ts_input[:max_ts_len] + + if ts_input.ndim == 1: + ts_input = ts_input[:, None] + + ts_len = ts_input.shape[0] + ts_channel = ts_input.shape[1] + + if sampling_rate is None or sampling_rate <= 0: + sampling_rate = max(ts_len / 4, 1.0) + + # newer InternS2 Preview checkpoints compute TS tokens differently + if getattr(self.hf_config, 'ts_forecaster_config', None) is not None: + chunk_size = getattr(self.processor, 'chunk_size', 12800) + num_query = getattr(self.processor, 'num_query', 2) + subrate = max(ts_len / 500, 1.0) + stride = subrate * num_query + patch_size = np.ceil(stride) + chunk_num = ts_len // chunk_size + tail_len = ts_len - chunk_size * chunk_num + full_chunk_tokens = (np.ceil((chunk_size - patch_size) / stride + 1) * num_query + 1) // 2 + tail_tokens = (np.ceil((tail_len - patch_size) / stride + 1) * num_query + 1) // 2 + ts_tokens = int(chunk_num * full_chunk_tokens + tail_tokens) + else: + stride = np.floor(160 / ((1 + np.exp(-sampling_rate / 100))**6)) + patch_size = stride * 2 + embed_length = (np.ceil((ts_len - patch_size) / stride) + 1) + ts_tokens = int((embed_length // 2 + 1) // 2) + + for i in range(len(text)): + ts_placeholder = f'{self.ts_start_token}{self.ts_token}{self.ts_end_token}' + if ts_placeholder in text[i]: + expanded_placeholder = self.ts_start_token + self.ts_token * ts_tokens + self.ts_end_token + text[i] = text[i].replace(ts_placeholder, expanded_placeholder, 1) + elif self.ts_token in text[i]: + text[i] = text[i].replace(self.ts_token, self.ts_token * ts_tokens) + + input_ids = self.tokenizer(text, add_special_tokens=False, **kwargs)['input_ids'] + + ts_input = torch.from_numpy(np.array([ts_input])).to(dtype=torch.bfloat16) + ts_sr = torch.tensor([sampling_rate]) + ts_lens = torch.tensor([ts_len]) + ts_channels = torch.tensor([ts_channel]) + return dict(input_ids=input_ids, + ts_values=ts_input, + ts_sr=ts_sr, + ts_lens=ts_lens, + ts_channels=ts_channels, + ts_token_id=self.ts_token_id) + + def build_model(self, trust_remote_code: bool = False): + check_transformers() + arch = self.hf_config.architectures[0] + if arch in self._arch: + from transformers import AutoModelForImageTextToText as AutoModelCls + else: + raise ValueError(f'Unsupported arch={arch}') + + if self.with_llm: + self.vl_model = AutoModelCls.from_pretrained(self.model_path, + device_map='cpu', + trust_remote_code=trust_remote_code) + else: + from accelerate import init_empty_weights + with init_empty_weights(): + config = self.hf_config + config.tie_word_embeddings = False + if hasattr(config, 'text_config'): + config.text_config.tie_word_embeddings = False + + model = AutoModelCls.from_config(config, trust_remote_code=trust_remote_code) + model.visual = model.model.visual + model.time_series = model.model.time_series + del model.model + del model.lm_head + model.half() + + from accelerate import load_checkpoint_and_dispatch + with disable_logging(): + load_checkpoint_and_dispatch(model=model, + checkpoint=self.model_path, + device_map='auto' if not self.with_llm else {'': 'cpu'}, + max_memory=self.max_memory, + no_split_module_classes=[ + 'InternS2PreviewDecoderLayer', + 'InternS2PreviewVisionBlock', + ], + dtype=torch.half) + self.model = model.eval() diff --git a/lmdeploy/vl/model/preprocess_utils.py b/lmdeploy/vl/model/preprocess_utils.py index f267722ff7..89f8a09618 100644 --- a/lmdeploy/vl/model/preprocess_utils.py +++ b/lmdeploy/vl/model/preprocess_utils.py @@ -251,12 +251,14 @@ def get_expanded_mm_items(collected_mm_items, mm_tokens: 'MultimodalSpecialToken audio_token_id=token_id, )) elif modality == Modality.TIME_SERIES: + ts_channels = item.get('ts_channels') expanded_mm_items.append( dict( modality=modality, ts_values=item['feature'], ts_sr=item['ts_sr'], ts_lens=item['ts_lens'], + ts_channels=ts_channels, offset=item['offset'][0], ts_token_id=token_id, )) diff --git a/lmdeploy/vl/model/qwen3_5.py b/lmdeploy/vl/model/qwen3_5.py index fe0b13ff5a..7182aae535 100644 --- a/lmdeploy/vl/model/qwen3_5.py +++ b/lmdeploy/vl/model/qwen3_5.py @@ -1,21 +1,13 @@ # Copyright (c) OpenMMLab. All rights reserved. -from typing import Any - -import numpy as np import torch from lmdeploy.utils import get_logger -from lmdeploy.vl.model.base import VISION_MODELS, MultimodalSpecialTokens +from lmdeploy.vl.model.base import VISION_MODELS from lmdeploy.vl.model.qwen3 import Qwen3VLModel from lmdeploy.vl.model.utils import disable_logging logger = get_logger('lmdeploy') -_INTERN_S2_ARCHS = [ - 'InternS2PreviewForConditionalGeneration', - 'InternS2PreviewForCausalLM', -] - def check_transformers(): try: @@ -34,7 +26,6 @@ class Qwen3_5Model(Qwen3VLModel): _arch = [ 'Qwen3_5ForConditionalGeneration', 'Qwen3_5MoeForConditionalGeneration', - *_INTERN_S2_ARCHS, ] _turbomind_native_vision = True @@ -42,79 +33,6 @@ def build_preprocessor(self, trust_remote_code: bool = False): check_transformers() super().build_preprocessor(trust_remote_code=trust_remote_code) - # time series tokens - self.ts_token = getattr(self.processor, 'ts_token', None) - self.ts_token_id = getattr(self.processor, 'ts_token_id', None) - self.ts_start_token = getattr(self.processor, 'ts_start_token', None) - self.ts_end_token = getattr(self.processor, 'ts_end_token', None) - - # special tokens - self.mm_tokens = MultimodalSpecialTokens( - image_token=self.image_token, - video_token=self.video_token, - ts_token=self.ts_token, - image_token_id=self.image_token_id, - video_token_id=self.video_token_id, - ts_token_id=self.ts_token_id - ) - - def time_series_processor(self, - text: list[str], - time_series: list[Any], - sampling_rate: float | None = None, - **kwargs): - - ts_input = time_series[0] if isinstance(time_series, list) else time_series - sampling_rate = sampling_rate[0] if isinstance(sampling_rate, list) else sampling_rate - - if not isinstance(ts_input, np.ndarray): - ts_input = np.array(ts_input, dtype=np.float32) - - mean = ts_input.mean(axis=0, keepdims=True) - std = ts_input.std(axis=0, keepdims=True) - ts_input = (ts_input - mean) / (std + 1e-8) - - # truncate to 240k to avoid OOM - max_ts_len = 240000 - if len(ts_input) > max_ts_len: - ts_input = ts_input[:max_ts_len] - - if ts_input.ndim == 1: - ts_input = ts_input[:, None] # [T,C] - - ts_len = ts_input.shape[0] - - # set the default value to ts_len / 4 if sr is not provided or invalid - if sampling_rate is None or sampling_rate <= 0: - sampling_rate = max(ts_len / 4, 1.0) - - # compute num ts tokens - stride = np.floor(160 / ((1 + np.exp(-sampling_rate / 100))**6)) - patch_size = stride * 2 - embed_length = (np.ceil((ts_len - patch_size) / stride) + 1) - ts_tokens = int((embed_length // 2 + 1) // 2) - - # generate text with ts tokens - for i in range(len(text)): - if f'{self.ts_start_token}{self.ts_token}{self.ts_end_token}' in text[i]: - ts_placeholder = self.ts_start_token + self.ts_token * ts_tokens + self.ts_end_token - text[i] = text[i].replace( - f'{self.ts_start_token}{self.ts_token}{self.ts_end_token}', ts_placeholder, 1 - ) - elif self.ts_token in text[i]: - text[i] = text[i].replace(self.ts_token, self.ts_token * ts_tokens) - - input_ids = self.tokenizer(text, add_special_tokens=False, **kwargs)['input_ids'] - - ts_input = torch.from_numpy(np.array([ts_input])).to(dtype=torch.bfloat16) - ts_sr = torch.tensor([sampling_rate]) - ts_lens = torch.tensor([ts_len]) - return dict(input_ids=input_ids, - ts_values=ts_input, - ts_sr=ts_sr, - ts_lens=ts_lens, - ts_token_id=self.ts_token_id) - def build_model(self, trust_remote_code: bool = False): check_transformers() arch = self.hf_config.architectures[0] @@ -122,18 +40,11 @@ def build_model(self, trust_remote_code: bool = False): from transformers import Qwen3_5ForConditionalGeneration as AutoModelCls elif arch == 'Qwen3_5MoeForConditionalGeneration': from transformers import Qwen3_5MoeForConditionalGeneration as AutoModelCls - elif arch in _INTERN_S2_ARCHS: - from transformers import AutoModelForImageTextToText as AutoModelCls else: raise ValueError(f'Unsupported arch={arch}') if self.with_llm: - if arch in ['Qwen3_5ForConditionalGeneration', 'Qwen3_5MoeForConditionalGeneration']: - self.vl_model = AutoModelCls.from_pretrained(self.model_path, device_map='cpu') - else: - self.vl_model = AutoModelCls.from_pretrained(self.model_path, - device_map='cpu', - trust_remote_code=trust_remote_code) + self.vl_model = AutoModelCls.from_pretrained(self.model_path, device_map='cpu') else: from accelerate import init_empty_weights with init_empty_weights(): @@ -142,17 +53,10 @@ def build_model(self, trust_remote_code: bool = False): if hasattr(config, 'text_config'): config.text_config.tie_word_embeddings = False - if arch in ['Qwen3_5ForConditionalGeneration', 'Qwen3_5MoeForConditionalGeneration']: - model = AutoModelCls._from_config(config) - model.visual = model.model.visual - del model.model - del model.lm_head - elif arch in _INTERN_S2_ARCHS: - model = AutoModelCls.from_config(config, trust_remote_code=trust_remote_code) - model.visual = model.model.visual - model.time_series = model.model.time_series - del model.model - del model.lm_head + model = AutoModelCls._from_config(config) + model.visual = model.model.visual + del model.model + del model.lm_head model.half() from accelerate import load_checkpoint_and_dispatch @@ -164,8 +68,6 @@ def build_model(self, trust_remote_code: bool = False): no_split_module_classes=[ 'Qwen3_5VisionBlock', 'Qwen3_5MoeVisionBlock', - 'InternS2PreviewDecoderLayer', - 'InternS2PreviewVisionBlock', - ], + ], dtype=torch.half) self.model = model.eval() diff --git a/tests/test_lmdeploy/test_vl/test_preprocess_utils.py b/tests/test_lmdeploy/test_vl/test_preprocess_utils.py index 22084f0e88..c6c3c35485 100644 --- a/tests/test_lmdeploy/test_vl/test_preprocess_utils.py +++ b/tests/test_lmdeploy/test_vl/test_preprocess_utils.py @@ -11,6 +11,7 @@ class _Tokens: image_token_id = 42 video_token_id = 43 audio_token_id = 44 + ts_token_id = 45 def get_token_id_by_modality(self, modality): if modality == Modality.IMAGE: @@ -19,6 +20,8 @@ def get_token_id_by_modality(self, modality): return self.video_token_id if modality == Modality.AUDIO: return self.audio_token_id + if modality == Modality.TIME_SERIES: + return self.ts_token_id raise AssertionError(f'unexpected modality: {modality}') @@ -97,3 +100,21 @@ def test_expand_audio_items_use_compact_tensor_storage(): for entry in expanded: _assert_compact_storage(entry['input_features']) _assert_compact_storage(entry['feature_attention_mask']) + + +def test_expand_time_series_item_preserves_channels(): + items = { + Modality.TIME_SERIES: { + 'feature': torch.zeros(1, 10, 3, dtype=torch.float32), + 'ts_sr': torch.tensor([100]), + 'ts_lens': torch.tensor([10]), + 'ts_channels': torch.tensor([3]), + 'offset': [(0, 4)], + } + } + + expanded = get_expanded_mm_items(items, _Tokens()) + + assert len(expanded) == 1 + assert expanded[0]['ts_channels'].tolist() == [3] + assert expanded[0]['ts_token_id'] == _Tokens.ts_token_id