diff --git a/docs/source/tutorials/dlinear_v2_example.ipynb b/docs/source/tutorials/dlinear_v2_example.ipynb
new file mode 100644
index 000000000..46f54dc55
--- /dev/null
+++ b/docs/source/tutorials/dlinear_v2_example.ipynb
@@ -0,0 +1,499 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "9f56c2ab",
+ "metadata": {},
+ "source": [
+ "# DLinear v2 tutorial\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11ff8fe1",
+ "metadata": {},
+ "source": [
+ "
\n",
+ ":warning:
TimeSeries,
TslibDataModule, and
DLinear are part of the experimental v2 API. The API is unstable and may change without notice. This notebook is a demonstration, not a production recipe. Feedback is welcome in
issue 1736.\n",
+ "
\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d48d5124",
+ "metadata": {},
+ "source": [
+ "This notebook trains **DLinear** on toy data with the v2 package API.\n",
+ "\n",
+ "DLinear splits each series into a trend and a seasonal part with a moving average, applies a linear layer to each part, and adds the two forecasts. The current v2 implementation uses continuous features only.\n",
+ "\n",
+ "The notebook builds three objects:\n",
+ "\n",
+ "1. `TimeSeries`, the raw dataset.\n",
+ "2. `TslibDataModule`, which windows the series and exposes `metadata`.\n",
+ "3. `DLinear_pkg_v2`, which calls `fit` and `predict`.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e570a441",
+ "metadata": {},
+ "source": [
+ "## Toy data\n",
+ "\n",
+ "Ten short series, each a trend plus a 12-step seasonal wave. `x` is a lagged copy of the target and is unknown in the future. `future_known` is the same wave shifted into a cosine, so its future values are known at forecast time. There are no categorical columns.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "fbb16d2b",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-10-03T13:53:13.656460Z",
+ "iopub.status.busy": "2026-10-03T13:53:13.656161Z",
+ "iopub.status.idle": "2026-10-03T13:53:14.178794Z",
+ "shell.execute_reply": "2026-10-03T13:53:14.178158Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " series_id | \n",
+ " time_idx | \n",
+ " y | \n",
+ " x | \n",
+ " future_known | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 0.049671 | \n",
+ " 0.049671 | \n",
+ " 1.000000e+00 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 0 | \n",
+ " 1 | \n",
+ " 0.516174 | \n",
+ " 0.049671 | \n",
+ " 8.660254e-01 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 0 | \n",
+ " 2 | \n",
+ " 0.990794 | \n",
+ " 0.516174 | \n",
+ " 5.000000e-01 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 0 | \n",
+ " 3 | \n",
+ " 1.242303 | \n",
+ " 0.990794 | \n",
+ " 6.123234e-17 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 0 | \n",
+ " 4 | \n",
+ " 0.962610 | \n",
+ " 1.242303 | \n",
+ " -5.000000e-01 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " series_id time_idx y x future_known\n",
+ "0 0 0 0.049671 0.049671 1.000000e+00\n",
+ "1 0 1 0.516174 0.049671 8.660254e-01\n",
+ "2 0 2 0.990794 0.516174 5.000000e-01\n",
+ "3 0 3 1.242303 0.990794 6.123234e-17\n",
+ "4 0 4 0.962610 1.242303 -5.000000e-01"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "\n",
+ "np.random.seed(42)\n",
+ "\n",
+ "num_series = 10\n",
+ "seq_length = 48\n",
+ "rows = []\n",
+ "for series_id in range(num_series):\n",
+ " time_idx = np.arange(seq_length)\n",
+ " seasonal = np.sin(2 * np.pi * time_idx / 12)\n",
+ " trend = 0.03 * time_idx\n",
+ " y = trend + seasonal + np.random.normal(0, 0.1, seq_length)\n",
+ " x = np.roll(y, 1)\n",
+ " x[0] = y[0]\n",
+ " future_known = np.cos(2 * np.pi * time_idx / 12)\n",
+ " for t in range(seq_length):\n",
+ " rows.append(\n",
+ " {\n",
+ " \"series_id\": series_id,\n",
+ " \"time_idx\": int(time_idx[t]),\n",
+ " \"y\": float(y[t]),\n",
+ " \"x\": float(x[t]),\n",
+ " \"future_known\": float(future_known[t]),\n",
+ " }\n",
+ " )\n",
+ "\n",
+ "data_df = pd.DataFrame(rows)\n",
+ "data_df.head()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fbd5a14a",
+ "metadata": {},
+ "source": [
+ "## `TimeSeries`\n",
+ "\n",
+ "`TimeSeries` names the columns the rest of the pipeline uses:\n",
+ "\n",
+ "- `time`: integer time index within each series.\n",
+ "- `target`: column to forecast.\n",
+ "- `group`: columns that identify a series.\n",
+ "- `num`: continuous inputs.\n",
+ "- `known`: inputs whose future values are available.\n",
+ "- `unknown`: inputs available only in the history.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "61eec68f",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-10-03T13:53:14.180112Z",
+ "iopub.status.busy": "2026-10-03T13:53:14.179990Z",
+ "iopub.status.idle": "2026-10-03T13:53:17.423703Z",
+ "shell.execute_reply": "2026-10-03T13:53:17.423027Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "from pytorch_forecasting.data.timeseries import TimeSeries\n",
+ "\n",
+ "dataset = TimeSeries(\n",
+ " data=data_df,\n",
+ " time=\"time_idx\",\n",
+ " target=\"y\",\n",
+ " group=[\"series_id\"],\n",
+ " num=[\"x\", \"future_known\"],\n",
+ " known=[\"future_known\"],\n",
+ " unknown=[\"x\"],\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4de64fbb",
+ "metadata": {},
+ "source": [
+ "## `TslibDataModule`\n",
+ "\n",
+ "`TslibDataModule` is the data module linked to `DLinear_pkg_v2`. `context_length` is the history window and `prediction_length` is the forecast horizon. The split is by series, so each of train, validation, and test needs at least one series. `metadata` is what the model reads for input and output sizes.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "70773776",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-10-03T13:53:17.425215Z",
+ "iopub.status.busy": "2026-10-03T13:53:17.425048Z",
+ "iopub.status.idle": "2026-10-03T13:53:17.433445Z",
+ "shell.execute_reply": "2026-10-03T13:53:17.432867Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'feature_names': {'categorical': [],\n",
+ " 'continuous': ['x', 'future_known'],\n",
+ " 'static': [],\n",
+ " 'known': ['future_known'],\n",
+ " 'unknown': ['x'],\n",
+ " 'target': ['y'],\n",
+ " 'all': ['x', 'future_known'],\n",
+ " 'static_categorical': [],\n",
+ " 'static_continuous': []},\n",
+ " 'feature_indices': {'categorical': [],\n",
+ " 'continuous': [0, 1],\n",
+ " 'static': [],\n",
+ " 'known': [1],\n",
+ " 'unknown': [0],\n",
+ " 'target': [0]},\n",
+ " 'n_features': {'categorical': 0,\n",
+ " 'continuous': 2,\n",
+ " 'static': 0,\n",
+ " 'known': 1,\n",
+ " 'unknown': 1,\n",
+ " 'target': 1,\n",
+ " 'all': 2,\n",
+ " 'static_categorical': 0,\n",
+ " 'static_continuous': 0},\n",
+ " 'context_length': 16,\n",
+ " 'prediction_length': 4,\n",
+ " 'freq': 'h',\n",
+ " 'features': 'MS'}"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from sklearn.preprocessing import StandardScaler\n",
+ "\n",
+ "from pytorch_forecasting.data.data_module import TslibDataModule\n",
+ "from pytorch_forecasting.data.encoders import TorchNormalizer\n",
+ "\n",
+ "datamodule_cfg = dict(\n",
+ " context_length=16,\n",
+ " prediction_length=4,\n",
+ " batch_size=16,\n",
+ " train_val_test_split=(0.6, 0.2, 0.2),\n",
+ " target_normalizer=TorchNormalizer(),\n",
+ " scalers={\n",
+ " \"x\": StandardScaler(),\n",
+ " \"future_known\": StandardScaler(),\n",
+ " },\n",
+ ")\n",
+ "\n",
+ "data_module = TslibDataModule(time_series_dataset=dataset, **datamodule_cfg)\n",
+ "data_module.metadata\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c599e5d5",
+ "metadata": {},
+ "source": [
+ "## `DLinear_pkg_v2`\n",
+ "\n",
+ "`moving_avg` is the kernel width of the trend filter. `individual=False` shares one linear layer across features. Pass the same `datamodule_cfg` used above so a later `fit` on a `TimeSeries` rebuilds this module.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "ff6a5fe1",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-10-03T13:53:17.434644Z",
+ "iopub.status.busy": "2026-10-03T13:53:17.434537Z",
+ "iopub.status.idle": "2026-10-03T13:53:17.438970Z",
+ "shell.execute_reply": "2026-10-03T13:53:17.438436Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{'loss': MAE(), 'moving_avg': 5, 'individual': False, 'logging_metrics': [MAE(), SMAPE()], 'optimizer': 'adam', 'optimizer_params': {'lr': 0.001}}\n"
+ ]
+ }
+ ],
+ "source": [
+ "from pytorch_forecasting.metrics import MAE, SMAPE\n",
+ "from pytorch_forecasting.models.dlinear import DLinear_pkg_v2\n",
+ "\n",
+ "model_cfg = dict(\n",
+ " loss=MAE(),\n",
+ " moving_avg=5,\n",
+ " individual=False,\n",
+ " logging_metrics=[MAE(), SMAPE()],\n",
+ " optimizer=\"adam\",\n",
+ " optimizer_params={\"lr\": 1e-3},\n",
+ ")\n",
+ "\n",
+ "trainer_cfg = dict(\n",
+ " max_epochs=5,\n",
+ " accelerator=\"cpu\",\n",
+ " devices=1,\n",
+ " enable_progress_bar=False,\n",
+ " enable_model_summary=True,\n",
+ " enable_checkpointing=False,\n",
+ " logger=False,\n",
+ " log_every_n_steps=1,\n",
+ ")\n",
+ "\n",
+ "model_pkg = DLinear_pkg_v2(\n",
+ " model_cfg=model_cfg,\n",
+ " trainer_cfg=trainer_cfg,\n",
+ " datamodule_cfg=datamodule_cfg,\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ed0b12ef",
+ "metadata": {},
+ "source": [
+ "## Fit\n",
+ "\n",
+ "`fit` accepts the `TslibDataModule` built above. Passing the `TimeSeries` instead makes the package construct the module from `datamodule_cfg`. `save_ckpt=False` skips the package checkpoint. `enable_checkpointing=False` also turns off the trainer's own checkpoint. The warning filter hides Lightning's \"no logger configured\" message, which appears because the model still records metrics.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "c8a45b14",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-10-03T13:53:17.440094Z",
+ "iopub.status.busy": "2026-10-03T13:53:17.440014Z",
+ "iopub.status.idle": "2026-10-03T13:53:18.411199Z",
+ "shell.execute_reply": "2026-10-03T13:53:18.410612Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ " | Name | Type | Params | Mode | FLOPs\n",
+ "------------------------------------------------------------------------\n",
+ "0 | loss | MAE | 0 | train | 0 \n",
+ "1 | logging_metrics | ModuleList | 0 | train | 0 \n",
+ "2 | decomposition | SeriesDecomposition | 0 | train | 0 \n",
+ "3 | linear_seasonal | Linear | 68 | train | 0 \n",
+ "4 | linear_trend | Linear | 68 | train | 0 \n",
+ "------------------------------------------------------------------------\n",
+ "136 Trainable params\n",
+ "0 Non-trainable params\n",
+ "136 Total params\n",
+ "0.001 Total estimated model params size (MB)\n",
+ "9 Modules in train mode\n",
+ "0 Modules in eval mode\n",
+ "0 Total Flops\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "`Trainer.fit` stopped: `max_epochs=5` reached.\n"
+ ]
+ }
+ ],
+ "source": [
+ "import warnings\n",
+ "\n",
+ "warnings.filterwarnings(\"ignore\", message=\".*no logger configured.*\")\n",
+ "\n",
+ "model_pkg.fit(data_module, save_ckpt=False)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6aa6cd87",
+ "metadata": {},
+ "source": [
+ "## Predict\n",
+ "\n",
+ "`predict` returns a dict. With a point loss such as `MAE`, `prediction` has shape `(n_windows, prediction_length)`.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "f6773336",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-10-03T13:53:18.412583Z",
+ "iopub.status.busy": "2026-10-03T13:53:18.412488Z",
+ "iopub.status.idle": "2026-10-03T13:53:18.599661Z",
+ "shell.execute_reply": "2026-10-03T13:53:18.599146Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "prediction shape: (290, 4)\n",
+ "first forecast: [0.82, 0.623, 0.302, 0.105]\n"
+ ]
+ }
+ ],
+ "source": [
+ "preds = model_pkg.predict(\n",
+ " data_module,\n",
+ " trainer_kwargs=dict(\n",
+ " accelerator=\"cpu\",\n",
+ " logger=False,\n",
+ " enable_progress_bar=False,\n",
+ " enable_model_summary=False,\n",
+ " enable_checkpointing=False,\n",
+ " ),\n",
+ ")\n",
+ "\n",
+ "print(\"prediction shape:\", tuple(preds[\"prediction\"].shape))\n",
+ "print(\"first forecast:\", [round(v, 3) for v in preds[\"prediction\"][0].tolist()])\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/source/tutorials_v2.rst b/docs/source/tutorials_v2.rst
index bebab6f39..d05c7b63d 100644
--- a/docs/source/tutorials_v2.rst
+++ b/docs/source/tutorials_v2.rst
@@ -15,3 +15,4 @@ The following tutorials for version 2 can be also found as `notebooks on GitHub
tutorials/ptf_V2_example
tutorials/tslib_v2_example
+ tutorials/dlinear_v2_example