Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
942b641
feat: add standalone time series encoder
CUHKSZzxy May 26, 2026
2381f30
refactor: replace time series encoder ops
CUHKSZzxy May 26, 2026
35e954d
refactor: replace mrqformer bert model
CUHKSZzxy May 26, 2026
5ef78f2
refactor: split time series bert module
CUHKSZzxy May 26, 2026
ca7e088
refactor: simplify time series inference interfaces
CUHKSZzxy May 26, 2026
536ea63
refactor: trim time series encoder branches
CUHKSZzxy May 26, 2026
b3dee23
refactor: inline time series encoder helpers
CUHKSZzxy May 26, 2026
d3f07bb
refactor: reorder time series encoder modules
CUHKSZzxy May 26, 2026
ecae382
feat: add time series forecaster
CUHKSZzxy May 26, 2026
f59af06
style: align forecaster docstrings
CUHKSZzxy May 26, 2026
e55724c
style: clean forecaster comments
CUHKSZzxy May 26, 2026
4e964d3
refactor: simplify time series forecaster branches
CUHKSZzxy May 27, 2026
53f8876
refactor: trim redundant linear builder args
CUHKSZzxy May 27, 2026
bd3494c
refactor: simplify time series forecaster defaults
CUHKSZzxy May 28, 2026
76386b9
style: clarify time series encoder builder args
CUHKSZzxy May 28, 2026
01cdcef
feat: support interns2 preview time series
CUHKSZzxy Jun 4, 2026
a018bb4
Merge branch 'main' of https://github.com/CUHKSZzxy/lmdeploy into sup…
CUHKSZzxy Jun 4, 2026
b669908
fix: preserve time series channel metadata
CUHKSZzxy Jun 4, 2026
f2023fa
refactor: clean up time series model modules
CUHKSZzxy Jun 5, 2026
4bdc2c1
refactor: simplify time series model internals
CUHKSZzxy Jun 8, 2026
afafeb5
fix: align interns2 time-series encoder
CUHKSZzxy Jun 9, 2026
e10bf8e
fix: initialize whisper k projection bias
CUHKSZzxy Jun 10, 2026
563a620
feat: expose time-series forecast outputs
CUHKSZzxy Jun 10, 2026
376a817
Merge branch 'main' into interns2-ts
CUHKSZzxy Jun 10, 2026
7c75a3f
fix: validate time-series forecast inputs
CUHKSZzxy Jun 10, 2026
7558aba
fix: validate forecast horizon
CUHKSZzxy Jun 11, 2026
156b485
Merge remote-tracking branch 'origin/main' into interns2-ts
CUHKSZzxy Jun 23, 2026
4689fc8
feat: update interns2 time-series forecasting
CUHKSZzxy Jun 25, 2026
d2e15a5
fix: carry time-series forecast state across chunks
CUHKSZzxy Jun 25, 2026
b19d2d2
refactor: rename time-series forecast state
CUHKSZzxy Jun 25, 2026
aaecb04
fix: support variable-length batched time series
CUHKSZzxy Jun 25, 2026
dc3e0bd
fix: handle zero-token forecast metrics
CUHKSZzxy Jun 26, 2026
aa6c65a
docs: add InternS2 time series forecast examples
CUHKSZzxy Jun 26, 2026
f6667dc
refactor: isolate ts forecast output handling
CUHKSZzxy Jun 29, 2026
be54e01
chore: polish interns2 time series wiring
CUHKSZzxy Jun 29, 2026
7d67d57
fix: adapt interns2 preview ts forecaster to 0701
CUHKSZzxy Jul 1, 2026
0c94135
feat: support interns2 preview time series forecast
CUHKSZzxy Jul 1, 2026
ec9a31a
refactor: localize interns2 preview ts weight loading
CUHKSZzxy Jul 1, 2026
d264971
fix: handle explicit time-series forecast horizons
CUHKSZzxy Jul 3, 2026
8253d79
Merge branch 'main' into interns2-ts
CUHKSZzxy Jul 7, 2026
1f0b215
fix: log prompt metrics for zero-token forecasts
CUHKSZzxy Jul 7, 2026
187eb1c
fix: preserve predicted time series forecast horizon
CUHKSZzxy Jul 8, 2026
ac12202
docs: clarify time series forecast horizon behavior
CUHKSZzxy Jul 8, 2026
719da79
Merge branch 'main' into interns2-ts
CUHKSZzxy Jul 24, 2026
83624e6
fix: address time series review issues
CUHKSZzxy Jul 24, 2026
d89736c
style: normalize time series forecaster strings
CUHKSZzxy Jul 24, 2026
5bd734a
test: remove time series forecast postprocessor tests
CUHKSZzxy Jul 24, 2026
0d30386
Merge branch 'main' into interns2-ts
CUHKSZzxy Jul 27, 2026
fe2f069
chore: reorder interns2 preview module map
CUHKSZzxy Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 108 additions & 3 deletions docs/en/multi_modal/multimodal_inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,13 @@ ______________________________________________________________________

## 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. Time series
> forecasting is supported for **Intern-S2-Preview** models with a time-series forecaster only.

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.

<details>
<summary>Complete example</summary>
<summary>Time series understanding example</summary>

```python
from openai import OpenAI
Expand Down Expand Up @@ -389,6 +390,110 @@ print(response.choices[0].message.content)

</details>

<details>
<summary>Intern-S2-Preview time series forecast example</summary>

```python
from openai import OpenAI

client = OpenAI(api_key='EMPTY', base_url='http://localhost:23333/v1')
model_name = client.models.list().data[0].id

response = client.chat.completions.create(
model=model_name,
messages=[{
'role': 'user',
'content': [
{
'type': 'time_series_url',
'time_series_url': {
'url': 'file:///path/to/history.npy',
# Optional when the Intern-S2-Preview processor can infer a default.
'sampling_rate': 100,
},
},
{
'type': 'text',
'text': 'Forecast the next 48 time points for this time series.',
},
],
}],
temperature=0.0,
max_completion_tokens=32,
extra_body={
# Intern-S2-Preview forecast checkpoints route supported TS requests to forecast output.
'enable_forecasting': True,
# Optional output-length override. Horizon prediction still runs when supported.
'forecast_horizon': 48,
},
)

message = response.model_dump()['choices'][0]['message']
forecast = message['ts_forecast']
print('predicted_horizon:', forecast['predicted_horizon'])
print('point_forecast:', forecast['point_forecast'])
print('quantile_forecast:', forecast['quantile_forecast'])
```

The forecast payload contains:

- `predicted_horizon`: the horizon predicted by Intern-S2-Preview. It is computed when the checkpoint has a horizon head,
including when `forecast_horizon` is set.
- `forecast_horizon`: optional request field that overrides the forecast array length only. It does not suppress horizon
prediction.
- `point_forecast`: the point forecast array.
- `quantile_forecast`: quantile forecasts in `[median, 0.1, 0.2, ..., 0.9]` order.

</details>

<details>
<summary>Intern-S2-Preview streaming forecast example</summary>

```python
from openai import OpenAI

client = OpenAI(api_key='EMPTY', base_url='http://localhost:23333/v1')
model_name = client.models.list().data[0].id

stream = client.chat.completions.create(
model=model_name,
messages=[{
'role': 'user',
'content': [
{
'type': 'time_series_url',
'time_series_url': {
'url': 'file:///path/to/history.npy',
'sampling_rate': 100,
},
},
{'type': 'text', 'text': 'Forecast the next 48 time points.'},
],
}],
temperature=0.0,
max_completion_tokens=32,
stream=True,
extra_body={
'enable_forecasting': True,
'forecast_horizon': 48,
},
)

forecast = None
for chunk in stream:
delta = chunk.model_dump()['choices'][0].get('delta', {})
if delta.get('content'):
print(delta['content'], end='', flush=True)
if delta.get('ts_forecast') is not None:
forecast = delta['ts_forecast']

print('\nforecast:', forecast)
```

For streaming responses, `ts_forecast` is attached to the final assistant delta.

</details>

______________________________________________________________________

## Local Files and Base64
Expand Down
110 changes: 107 additions & 3 deletions docs/zh_cn/multi_modal/multimodal_inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,13 @@ ______________________________________________________________________

## 时序数据

> **注意:** 时序数据输入目前仅支持 **InternS1-Pro** 模型。
> **注意:** 时序理解目前支持 **InternS1-Pro** 和 **Intern-S2-Preview** 模型。时序预测仅支持带有时序预测器的
> **Intern-S2-Preview** 模型。

`time_series_url` 内容项需要在 URL 之外额外提供 `sampling_rate` 字段(单位:Hz)。
`time_series_url` 内容项需要提供 URL。已知采样率时,可以通过 `sampling_rate` 字段传入,单位为 Hz。

<details>
<summary>完整示例</summary>
<summary>时序理解示例</summary>

```python
from openai import OpenAI
Expand Down Expand Up @@ -388,6 +389,109 @@ print(response.choices[0].message.content)

</details>

<details>
<summary>Intern-S2-Preview 时序预测示例</summary>

```python
from openai import OpenAI

client = OpenAI(api_key='EMPTY', base_url='http://localhost:23333/v1')
model_name = client.models.list().data[0].id

response = client.chat.completions.create(
model=model_name,
messages=[{
'role': 'user',
'content': [
{
'type': 'time_series_url',
'time_series_url': {
'url': 'file:///path/to/history.npy',
# 当 Intern-S2-Preview processor 可以推断默认值时,该字段可选。
'sampling_rate': 100,
},
},
{
'type': 'text',
'text': '请预测该时序接下来的 48 个时间点。',
},
],
}],
temperature=0.0,
max_completion_tokens=32,
extra_body={
# Intern-S2-Preview 预测 checkpoint 会将支持的时序请求路由到预测输出。
'enable_forecasting': True,
# 可选输出长度覆盖项。支持时仍会执行 horizon 预测。
'forecast_horizon': 48,
},
)

message = response.model_dump()['choices'][0]['message']
forecast = message['ts_forecast']
print('predicted_horizon:', forecast['predicted_horizon'])
print('point_forecast:', forecast['point_forecast'])
print('quantile_forecast:', forecast['quantile_forecast'])
```

预测结果包含:

- `predicted_horizon`: Intern-S2-Preview 预测出的 horizon。当 checkpoint 带有 horizon head 时会计算该字段,
即使请求中设置了 `forecast_horizon`。
- `forecast_horizon`: 可选请求字段,仅用于覆盖预测数组长度。它不会抑制 horizon 预测。
- `point_forecast`: 点预测数组。
- `quantile_forecast`: 分位数预测,顺序为 `[median, 0.1, 0.2, ..., 0.9]`。

</details>

<details>
<summary>Intern-S2-Preview 流式预测示例</summary>

```python
from openai import OpenAI

client = OpenAI(api_key='EMPTY', base_url='http://localhost:23333/v1')
model_name = client.models.list().data[0].id

stream = client.chat.completions.create(
model=model_name,
messages=[{
'role': 'user',
'content': [
{
'type': 'time_series_url',
'time_series_url': {
'url': 'file:///path/to/history.npy',
'sampling_rate': 100,
},
},
{'type': 'text', 'text': '请预测接下来的 48 个时间点。'},
],
}],
temperature=0.0,
max_completion_tokens=32,
stream=True,
extra_body={
'enable_forecasting': True,
'forecast_horizon': 48,
},
)

forecast = None
for chunk in stream:
delta = chunk.model_dump()['choices'][0].get('delta', {})
if delta.get('content'):
print(delta['content'], end='', flush=True)
if delta.get('ts_forecast') is not None:
forecast = delta['ts_forecast']

print('\nforecast:', forecast)
```

对于流式响应,`ts_forecast` 会附加在最后一个 assistant delta 上。

</details>

______________________________________________________________________

## 本地文件与 Base64
Expand Down
15 changes: 15 additions & 0 deletions lmdeploy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ class GenerationConfig:
Must be non-negative; values below 0 are treated as 0.
repetition_ngram_threshold: The number of times an n-gram must be repeated to trigger early stop.
Must be non-negative; values below 0 are treated as 0.
forecast_horizon: Optional horizon used by time series forecast outputs.
Forecast routing is controlled by ``enable_forecasting``.
enable_forecasting: Whether to route supported time-series requests to forecast output.
"""

n: int = 1
Expand Down Expand Up @@ -151,6 +154,10 @@ class GenerationConfig:
repetition_ngram_size: int = 0
repetition_ngram_threshold: int = 0

# time series forecasting
forecast_horizon: int | None = None
enable_forecasting: bool | None = None

def convert_stop_bad_words_to_ids(self, tokenizer: Tokenizer):
"""Convert stop_words/bad_sords to ids and append the ids to
stop_token_ids/bad_token_ids."""
Expand Down Expand Up @@ -200,6 +207,9 @@ def __post_init__(self):
assert self.temperature >= 0 and self.temperature <= 2 # [0,2]
assert 0 <= self.min_p <= 1, \
f'min_p should be in range [0, 1], but found {self.min_p}'
assert (self.forecast_horizon is None
or (type(self.forecast_horizon) is int and self.forecast_horizon > 0)), \
'forecast_horizon must be a positive integer'
if self.repetition_ngram_size <= 0 or self.repetition_ngram_threshold <= 0:
self.repetition_ngram_size = 0
self.repetition_ngram_threshold = 0
Expand Down Expand Up @@ -597,6 +607,7 @@ class Response:
last_hidden_state: torch.Tensor = None
index: int = 0
routed_experts: Any = None
multimodal_outputs: dict[str, Any] | None = None
cached_tokens: int = 0

def __str__(self):
Expand Down Expand Up @@ -627,6 +638,7 @@ def _format_tensor(name: str, tensor: torch.Tensor | None) -> list[str]:
fields.extend(_format_tensor('logits', self.logits))
fields.extend(_format_tensor('last_hidden_state', self.last_hidden_state))
fields.extend(_format_tensor('routed_experts', self.routed_experts))
fields.append(f'multimodal_outputs={self.multimodal_outputs}')
return '\n'.join(fields)

def extend(self, other: 'Response') -> 'Response':
Expand All @@ -653,6 +665,8 @@ def extend(self, other: 'Response') -> 'Response':
self.logprobs = self.logprobs or []
self.logprobs += other.logprobs
self.routed_experts = other.routed_experts
self.multimodal_outputs = other.multimodal_outputs
self.cached_tokens = other.cached_tokens
return self


Expand Down Expand Up @@ -739,6 +753,7 @@ class EngineOutput:
cache_block_ids: list[int] | None = None
req_metrics: RequestMetrics | None = None
routed_experts: torch.Tensor = None
multimodal_outputs: dict[str, Any] | None = None
ce_loss: float = None


Expand Down
8 changes: 8 additions & 0 deletions lmdeploy/metrics/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ def update_from_output(self, outputs: EngineOutput, req_stats: RequestStats):

new_generation_tokens = len(outputs.token_ids)
if new_generation_tokens == 0:
if outputs.status != ResponseType.SUCCESS:
# time series forecast-only responses can finish without emitting text tokens.
req_stats.finish_reason = outputs.status
req_stats.finish_time = self.iteration_timestamp
if req_stats.first_token_time == 0:
req_stats.first_token_time = self.iteration_timestamp
req_stats.lastest_token_time = self.iteration_timestamp
self.prompt_tokens = req_stats.prompt_tokens
return

self.new_generation_tokens = new_generation_tokens
Expand Down
5 changes: 4 additions & 1 deletion lmdeploy/pytorch/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ class InferOutput:
# when Prefill Engine is Done.
cache_block_ids: list[int] = None

# for logging
# logging
req_metrics: RequestMetrics = None

# expert ids
routed_experts: torch.Tensor = None

# multimodal outputs, e.g. time series predictions
multimodal_outputs: dict[str, Any] | None = None

# summed, unnormalized cross-entropy (NLL) of the input prompt
ce_loss: float = None

Expand Down
3 changes: 3 additions & 0 deletions lmdeploy/pytorch/engine/engine_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ async def async_stream_infer(self,

cache_block_ids = resp.data.get('cache_block_ids', None) if resp.data else None
req_metrics = resp.data.get('req_metrics', None) if resp.data else None
multimodal_outputs = resp.data.get('multimodal_outputs', None) if resp.data else None
logprobs = resp.data.pop('logprobs', None) if resp.data else None

if resp.type == ResponseType.SUCCESS:
Expand All @@ -238,6 +239,7 @@ async def async_stream_infer(self,
token_ids[output_offset:].tolist(),
cache_block_ids=cache_block_ids,
req_metrics=req_metrics,
multimodal_outputs=multimodal_outputs,
logprobs=logprobs)
output_offset = len(token_ids)
elif resp.type in (ResponseType.FINISH, ResponseType.CANCEL):
Expand Down Expand Up @@ -265,6 +267,7 @@ async def async_stream_infer(self,
cache_block_ids=cache_block_ids,
req_metrics=req_metrics,
routed_experts=routed_experts,
multimodal_outputs=multimodal_outputs,
logprobs=logprobs,
ce_loss=ce_loss)
break
Expand Down
Loading
Loading