File size: 12,252 Bytes
2465521 48c817e bb66e5f 48c817e 2465521 bb66e5f 2465521 f3f4077 bb66e5f 48c817e f3f4077 bb66e5f f3f4077 8000401 bb66e5f 49fc4a2 274dbe7 49fc4a2 274dbe7 49fc4a2 274dbe7 49fc4a2 274dbe7 49fc4a2 bb66e5f a53abfe bb66e5f a53abfe bb66e5f a53abfe bb66e5f 5469158 49fc4a2 bb66e5f b445266 f8d526c b445266 d910137 f8d526c b445266 bb66e5f e718a25 bb66e5f 48c817e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | ---
base_model:
- google/gemma-3-270m
- google/gemma-3-1b-pt
- Qwen/Qwen3-0.6B
license: mit
pipeline_tag: text-generation
tags:
- time-series
- time-series-language-model
- forecast-grounding
- lora
---
# LOTUS: Learning to Understand Time-series by Learning to Forecast
<p align="center"><img src="LOTUS.png" alt="LOTUS" width="420"></p>
**LOTUS** is a time-series language model pretrained under **forecast grounding** — a pretraining
principle that requires language-aligned time-series representations to *remain predictive of the
future evolution of the signal* before the model is adapted to downstream tasks. Time-series
language models typically align numerical signals with language using labels, captions, or
question–answer pairs; such supervision encourages linguistic representations but does not
explicitly require them to preserve information about how the underlying signal evolves. Forecast
grounding closes that gap.
LOTUS divides each time-series channel into non-overlapping patches and maps them into the LLM
embedding space. It then predicts the next patch from the LLM hidden states of the time-series
tokens, encouraging the learned representation to retain temporal information. To capture both
temporal changes and periodic patterns, each raw patch is combined with its spectral representation
(**temporal–spectral patch representation**) before mapping into the LLM embedding space.
Across three language-model backbones and six downstream evaluation datasets, LOTUS achieves the
best average performance in every matched-backbone group with minimal trainable parameters —
**59.91%** average accuracy with the Qwen3-0.6B backbone, outperforming the strongest matched
baseline by **3.36%** — and its 270M-parameter model is the top system on the TSRBench zero-shot
reasoning benchmark ahead of backbones 25–50× larger.
Paper: [arXiv:2610.18877](https://arxiv.org/abs/2610.18877) · [Hugging Face Paper page](https://huggingface.co/papers/2610.18877) · Code: [DA2I2-SLM/Lotus](https://github.com/DA2I2-SLM/Lotus)
· Phase-1 (pretraining) checkpoints: [hiepnh137/LangTimeFoundationModel](https://huggingface.co/hiepnh137/LangTimeFoundationModel)
## Training
* **Phase 1 — forecast grounding (pretraining).** The encoder, projector, next-patch decoder and
LLM LoRA adapters are trained jointly on a mixture of forecasting (next-patch prediction from the
LLM hidden states) and language (TSQA multiple-choice, M4 captioning) corpora.
* **Phase 2 — downstream adaptation.** The next-patch decoder is removed; the time-series
interface and LLM LoRA adapters are trained with the language objective alone, jointly across
HAR, Sleep-EDF, ECG-QA, PAMAP2 and Time-MQA.
All systems are evaluated under greedy decoding: the model writes a rationale followed by an
explicit answer, which is parsed and compared with the reference. Classification accuracy is
reported on HAR, PAMAP2, Sleep-EDF and Time-MQA (MCQ and true/false splits), and generative
accuracy under normalization on ECG-QA.
## Training data (as specified in the paper)
**Phase 1 — forecast-grounded pretraining.** Four corpora: Monash half-hourly electricity
demand (Godahewala et al., 2021) and a synthetic multivariate corpus for the forecasting
objective, and two language corpora — TSQA multiple-choice questions on trend, volatility,
seasonality and outliers (Kong et al., 2025;
[HF: ChengsenWang/TSQA](https://huggingface.co/datasets/ChengsenWang/TSQA)) and M4 series with
generated captions (Makridakis et al., 2020).
The synthetic corpus contains **844,275 series** from four generator families, none contributing
more than 37% (paper Table 5):
| Generator family | Series | Share | Variates | Length |
|---|---|---|---|---|
| Chronos-2 multivariatizer (re-implementation) | 310,000 | 36.7% | 3–10 | 512–8,192 |
| KernelSynth, univariate (Ansari et al., 2024) | 299,999 | 35.5% | 1 | 1,024–4,096 |
| TimePFN LMC (Taga et al., 2025) | 215,000 | 25.5% | 3–10 | 512–2,048 |
| SymTime S²Generator (Wang et al., 2025) | 19,276 | 2.3% | 3–10 | 1,024–4,096 |
**Phase 2 — downstream adaptation (the checkpoints in this repository).** Five datasets,
fine-tuned jointly:
| Dataset | Task | Source |
|---|---|---|
| HAR | activity recognition from 3-axis accelerometer windows, with rationales (8 activities) | OpenTSLM release (Langer et al., 2026) |
| PAMAP2 | activity recognition (18 activities) | Reiss & Stricker, 2012 |
| Sleep-EDF | sleep staging from 30-second EEG segments (6 stages) | Kemp et al., 2000 |
| ECG-QA | question answering over 12-lead ECG records | Oh et al., 2023, on PTB-XL (Wagner et al., 2020) |
| Time-MQA | open-ended time-series QA; MCQ and T/F splits scored separately | Kong et al., 2025 ([HF: Time-MQA/TSQA](https://huggingface.co/datasets/Time-MQA/TSQA), gated) |
Generation recipes for the synthetic corpus (upstream repositories, commits and driver scripts)
are in [`scripts/synthetic_corpus/`](https://github.com/DA2I2-SLM/Lotus/tree/main/scripts/synthetic_corpus)
of the code repository; the downstream corpora are downloaded automatically by the trainer.
## Checkpoints in this repository (Phase 2, joint five-task adaptation)
| File | Backbone | Trainable params |
|---|---|---|
| `lotus_qwen3-0.6b_joint-5task-finetune.pt` | Qwen3-0.6B | 9.3M |
| `lotus_gemma-3-1b-pt_joint-5task-finetune.pt` | Gemma3-1B | 11.1M |
| `lotus_gemma-3-270m_joint-5task-finetune.pt` | Gemma3-270M | 1.9M |
The repository also contains the matched **OpenTSLM-SP** and **OpenTSLM-Flamingo** baseline
checkpoints used in the paper (joint five-task and sequential-curriculum variants).
## Results (six downstream splits)
| Backbone | Method | Params | HAR | Sleep | ECG | PAMAP2 | MCQ | T/F | **Avg.** |
|---|---|---|---|---|---|---|---|---|---|
| Gemma3-270M | OpenTSLM-SP | 3.1M | 54.38 | 65.12 | 9.66 | 52.14 | 38.59 | 68.39 | 48.05 |
| Gemma3-270M | OpenTSLM-Flamingo | 317M | 62.55 | 68.01 | 30.10 | 54.70 | 12.37 | 3.04 | 38.46 |
| Gemma3-270M | **LOTUS** | 1.9M | 58.38 | 70.54 | 33.16 | 55.15 | 39.41 | 74.47 | **55.19** |
| Gemma3-1B | OpenTSLM-SP | 5.5M | 55.46 | 72.58 | 21.66 | 47.44 | 41.36 | 77.51 | 52.67 |
| Gemma3-1B | **LOTUS** | 11.1M | 62.67 | 77.10 | 37.66 | 52.56 | 44.98 | 78.42 | **58.90** |
| Qwen3-0.6B | OpenTSLM-SP | 27.8M | 63.25 | 70.54 | 32.20 | 47.44 | 54.64 | 71.20 | 56.55 |
| Qwen3-0.6B | **LOTUS** | 9.3M | 63.66 | 77.20 | 31.58 | 53.42 | 56.72 | 76.90 | **59.91** |
ECG-QA accuracy is computed on the first 5,000 test items (manifest:
`eval_manifests/ecg_test_first5000.json` in the code repository).
## Use it
Everything needed to run the model — including the LOTUS code — is inside this repository.
`pip install torch transformers peft numpy` (Python ≥ 3.10), then one setup cell:
```python
from huggingface_hub import snapshot_download
import sys
sys.path.insert(0, snapshot_download("daidv1112/LOTUS",
allow_patterns=["load_lotus.py", "src/*"]))
from load_lotus import load_lotus
lotus = load_lotus() # downloads the checkpoint, picks GPU + dtype automatically
```
**Case 1 — type the series in** (any list of numbers, raw scale, in time order):
```python
series = [12.4, 13.1, 12.8, 13.9, 14.5, 14.2, 15.3, 16.1, 15.8, 16.9,
17.4, 17.1, 18.2, 19.0, 18.6, 19.5, 20.3, 20.0, 21.1, 21.8,
21.5, 22.6, 23.4, 23.0, 24.1, 24.8, 24.5, 25.6, 26.3, 26.0,
27.1, 27.9, 27.5, 28.6, 29.3, 29.0, 30.1, 30.8, 30.5, 31.6,
32.3, 32.0, 33.1, 33.8, 33.5, 34.6, 35.2, 35.0]
lotus.ask("Is the time series showing an upward trend?", series)
# -> "True. The series shows an upward trend as it starts at 15.0 and increases to 34.0,
# indicating a consistent increase over time."
```
**Case 2 — read the series from a CSV file** (name the column, or let it pick the first
numeric one; non-numeric and missing values are dropped):
```python
lotus.ask_csv("Does the time series show a clear seasonal pattern?", "your_data.csv", column="OT")
# -> "[LOTUS] using column 'OT' of your_data.csv: 651 points, range 7.744e+04 to 2.943e+05"
# "No. Reasoning: The data shows fluctuations and patterns but does not exhibit a clear
# seasonal pattern. While there are noticeable peaks and troughs, these do not occur at
# regular intervals..."
```
Both methods take any natural-language question and an optional `domain=` context hint
(`Traffic`, `Energy`, `Healthcare`, ...), and return the model's verdict plus its rationale.
<details>
<summary>Low-level quickstart (explicit model construction)</summary>
A checkpoint is a `torch.save` dictionary holding the time-series encoder, projector, next-patch
decoder and LoRA adapters; the base LLM is loaded from its Hugging Face id. The model class is
`LotusModel` (vendored in this repository under `src/`; equivalently available in the
[Lotus repository](https://github.com/DA2I2-SLM/Lotus), Python ≥ 3.12, `pip install -e .`).
```python
import sys
from types import SimpleNamespace
import torch
from huggingface_hub import hf_hub_download
sys.path.insert(0, "Lotus/src") # git clone https://github.com/DA2I2-SLM/Lotus.git
from opentslm.model.llm.lotus_model import LotusModel
from opentslm.time_series_datasets.util import extend_time_series_to_match_patch_size_and_aggregate
cfg = SimpleNamespace(
is_pretraining=True, patch_size=32, pe_type="sincos",
use_only_time_pe=False, use_only_channel_pe=False,
use_freq_embed=True, enc_dec_type="transformer",
trans_enc_dim=192, trans_enc_num_layers=6, trans_enc_num_heads=8, trans_enc_ff_dim=896,
trans_dec_dim=192, trans_dec_num_layers=4, trans_dec_num_heads=8, trans_dec_ff_dim=896,
llm_mv_position_ids=True, llm_dtype="fp32", # "bf16" on GPUs with bf16 support
)
model = LotusModel(cfg, llm_id="Qwen/Qwen3-0.6B", device="cuda")
model.enable_lora(lora_r=8, lora_alpha=16)
model.load_from_file(hf_hub_download("daidv1112/LOTUS", "lotus_qwen3-0.6b_joint-5task-finetune.pt"))
model.eval()
# activity recognition with rationale: 3-axis accelerometer window (raw m/s^2 values)
axes = [...] # x, y, z lists of equal length
texts = []
series = torch.tensor(axes, dtype=torch.float32)
means, stds = series.mean(1, keepdim=True), series.std(1, keepdim=True).clamp(min=1e-6)
for ax_, m_, s_ in zip("xyz", means.flatten().tolist(), stds.flatten().tolist()):
texts.append(f"The following is the accelerometer data on the {ax_}-axis, "
f"it has mean {m_:.4f} and std {s_:.4f}:")
sample = {"pre_prompt": HAR_PROMPT, # verbatim template: see docs/prompts.md
"time_series": ((series - means) / stds).tolist(),
"time_series_text": texts, "post_prompt": "Rationale:", "answer": ""}
batch = extend_time_series_to_match_patch_size_and_aggregate([sample], patch_size=32)
text, _ = model.generate(batch, max_new_tokens=200)
print(text[0]) # rationale, ends with "Answer: walking."
```
</details>
The exact prompts for every task are in [`docs/prompts.md`](https://github.com/DA2I2-SLM/Lotus/blob/main/docs/prompts.md),
the evaluation harnesses in [`evaluation/`](https://github.com/DA2I2-SLM/Lotus/tree/main/evaluation),
and a runnable tutorial (loading, QA, captioning, forecasting and this checkpoint) in the
[Hands-on 2 notebook](https://colab.research.google.com/github/Davido111200/lotus-hands-on-2-fixed/blob/main/Hands-on-2_Lotus_fixed.ipynb).
## Try it
The quickest way to run this checkpoint is the step-by-step tutorial notebook — it loads LOTUS
(Qwen3-0.6B) on a free Colab T4 and evaluates it on real accelerometer windows from its HAR test
split, printing accuracy next to the paper's number together with the model's verbatim rationale:
[](https://colab.research.google.com/github/Davido111200/lotus-hands-on-2-fixed/blob/main/Hands-on-2_Lotus_fixed.ipynb)
## Citation
```bibtex
@article{do2026lotus,
title = {LOTUS: Learning to Understand Time-series by Learning to Forecast},
author = {Do, Van Dai and Nguyen, Huu Hiep and Nguyen, Minh Hoang and Nguyen, Manh and Le, Hung},
journal = {arXiv preprint arXiv:2610.18877},
year = {2026}
}
``` |