File size: 7,682 Bytes
60b21d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
231
232
233
234
235
236
#!/usr/bin/env python3

# SPDX-FileCopyrightText: 2025 Stanford University, ETH Zurich, and the project authors (see CONTRIBUTORS.md)
# SPDX-FileCopyrightText: 2025 This source file is part of the OpenTSLM open-source project.
#
# SPDX-License-Identifier: MIT

"""
Plot memory usage on simulation datasets from memory_simulation.csv.

- Only uses datasets starting with 'Simulation-'.
- Extracts time series length (L) and number of series (N).
- Computes total_length = N * L.
- Plots memory vs total_length per base model, comparing SoftPrompt vs Flamingo.
- OOM runs (> 180GB) are shown with a dashed line, red X, and "OOM" label.
- Always shows panels in order: gemma-270m, gemma-1b, llama-1b, llama-3b.
"""

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
import re

OOM_THRESHOLD = 180  # GB


def parse_model_name(llm_id, model_type):
    """Return base_model, config (SoftPrompt or Flamingo)."""
    if llm_id.startswith("meta-llama/"):
        base_name = llm_id.replace("meta-llama/", "")
    elif llm_id.startswith("google/"):
        base_name = llm_id.replace("google/", "")
    else:
        base_name = llm_id

    # Normalize base model names to match expected order
    if "Llama-3.2-1B" in base_name:
        base_name = "Llama-3.2-1B"
    elif "Llama-3.2-3B" in base_name:
        base_name = "Llama-3.2-3B"
    elif "gemma-3-270m" in base_name:
        base_name = "Gemma-3-270M"
    elif "gemma-3-1b-pt" in base_name:
        base_name = "Gemma-3-1B-pt"

    if model_type == "OpenTSLMSP":
        type_name = "SoftPrompt"
    elif model_type == "OpenTSLMFlamingo":
        type_name = "Flamingo"
    else:
        type_name = model_type

    return base_name, type_name


def parse_simulation_dataset(name):
    """Parse Simulation dataset name like 'Simulation-L10-N5' → (L=10, N=5)."""
    match = re.match(r"Simulation-L(\d+)-N(\d+)", name)
    if match:
        return int(match.group(1)), int(match.group(2))
    return None, None


def plot_memory_usage_sim(csv_file="memory_simulation.csv"):
    # --- Paper-style settings ---
    plt.style.use("seaborn-v0_8-white")
    matplotlib.rcParams.update(
        {
            "font.family": "serif",
            "font.serif": ["Palatino", "Times New Roman", "DejaVu Serif"],
            "font.size": 18,
            "axes.labelsize": 20,
            "axes.titlesize": 20,
            "legend.fontsize": 17,
            "xtick.labelsize": 17,
            "ytick.labelsize": 17,
            "axes.linewidth": 0.6,
            "axes.edgecolor": "0.15",
        }
    )

    df = pd.read_csv(csv_file)

    # Replace -1 with NaN (ignore failed runs)
    df["peak_cuda_reserved_gb"] = df["peak_cuda_reserved_gb"].replace(-1, pd.NA)

    # Keep only simulation datasets
    df = df[df["dataset"].str.startswith("Simulation-")]

    # Parse model name and dataset details
    df[["base_model", "config"]] = df.apply(
        lambda row: pd.Series(parse_model_name(row["llm_id"], row["model"])), axis=1
    )
    df[["L", "N"]] = df["dataset"].apply(
        lambda s: pd.Series(parse_simulation_dataset(s))
    )
    df = df.dropna(subset=["L", "N"])
    df["L"] = df["L"].astype(int)
    df["N"] = df["N"].astype(int)

    # Compute total sequence length
    df["total_length"] = df["L"] * df["N"]

    # Sort
    df = df.sort_values(by=["base_model", "config", "total_length"])

    # Fixed base_model order
    base_model_order = ["Gemma-3-270M", "Gemma-3-1B-pt", "Llama-3.2-1B", "Llama-3.2-3B"]

    # One subplot per model (always 4)
    n_models = len(base_model_order)
    fig, axes = plt.subplots(1, n_models, figsize=(3.2 * n_models, 3.2), sharey=True)

    if n_models == 1:
        axes = [axes]

    # Muted palette for configs - order matters for legend
    palette = {"SoftPrompt": "#4477AA", "Flamingo": "#CC6677"}
    config_order = ["SoftPrompt", "Flamingo"]

    for ax, base_model in zip(axes, base_model_order):
        subdf = df[df["base_model"] == base_model]

        if subdf.empty:
            ax.set_title(base_model, fontsize=13, fontweight="bold")
            ax.set_facecolor("#F8F9FA")
            ax.text(
                0.5, 0.5, "No data", ha="center", va="center", fontsize=10, color="gray"
            )
            ax.set_xticks([])
            ax.set_yticks([])
            continue

        for cfg in config_order:
            cfg_df = subdf[subdf["config"] == cfg]
            if cfg_df.empty:
                continue
            cfg_df = cfg_df.sort_values("total_length")
            color = palette[cfg]

            # Successful runs (≤ threshold)
            ok_df = cfg_df[cfg_df["peak_cuda_reserved_gb"] <= OOM_THRESHOLD]
            ax.plot(
                ok_df["total_length"],
                ok_df["peak_cuda_reserved_gb"],
                label=cfg,
                color=color,
                linewidth=4.0,
                alpha=0.9,
            )

            # First OOM run (if any)
            oom_df = cfg_df[cfg_df["peak_cuda_reserved_gb"] > OOM_THRESHOLD]
            if not oom_df.empty and not ok_df.empty:
                first_oom = oom_df.iloc[0]
                last_ok = ok_df.iloc[-1]

                # dashed line up to OOM
                ax.plot(
                    [last_ok["total_length"], first_oom["total_length"]],
                    [last_ok["peak_cuda_reserved_gb"], OOM_THRESHOLD * 1.05],
                    color=color,
                    linestyle="--",
                    linewidth=2.5,
                    alpha=0.8,
                )

                # red X marker
                ax.scatter(
                    first_oom["total_length"],
                    OOM_THRESHOLD * 1.05,
                    color="red",
                    marker="x",
                    s=80,
                    linewidth=3,
                    zorder=5,
                )
                ax.text(
                    first_oom["total_length"],
                    OOM_THRESHOLD * 1.05,
                    "OOM",
                    color="red",
                    fontsize=9,
                    fontweight="bold",
                    ha="center",
                    va="bottom",
                )

        # Titles & labels
        ax.set_title(base_model, fontsize=19, fontweight="bold")

        # Only show axis labels on specific subplots
        if ax == axes[0]:  # Leftmost subplot
            ax.set_ylabel("Peak VRAM Usage (GB)", fontsize=18, fontweight="bold")
            ax.set_xlabel(
                "Total Sequence Length (N × L)", fontsize=18, fontweight="bold"
            )
        else:
            ax.set_ylabel("")
            ax.set_xlabel("")
        ax.set_facecolor("#F8F9FA")
        ax.grid(True, which="major", linestyle="-", linewidth=0.4, alpha=0.5)
        ax.grid(True, which="minor", linestyle=":", linewidth=0.3, alpha=0.3)
        ax.minorticks_on()
        ax.tick_params(axis="both", labelsize=17)

        # Legend only in first subplot
        if ax == axes[0]:
            leg = ax.legend(
                title=None,
                fontsize=17,
                loc="best",
                frameon=True,
                framealpha=0.95,
                edgecolor="0.3",
            )
            for text in leg.get_texts():
                text.set_fontweight("bold")

    plt.tight_layout(pad=0.5)
    for fmt in ["png", "pdf"]:
        plt.savefig(
            f"memory_usage_simulation.{fmt}",
            dpi=300 if fmt == "png" else None,
            bbox_inches="tight",
            pad_inches=0,
            facecolor="white",
            format=fmt,
        )
    plt.show()


if __name__ == "__main__":
    plot_memory_usage_sim()