timeagent / code /OpenTSLM /scripts /get_memory_use.py
roh8exe's picture
Upload folder using huggingface_hub
60b21d3 verified
Raw
History Blame Contribute Delete
14 kB
# 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
import argparse
import csv
import os
import time
from datetime import datetime
from typing import Dict, List, Tuple
import torch
from tqdm.auto import tqdm
from torch.utils.data import DataLoader
import os as _os
import pynvml # type: ignore
_NVML_AVAILABLE = True
# Models
from opentslm.model.llm.OpenTSLMFlamingo import OpenTSLMFlamingo
from opentslm.model.llm.OpenTSLMSP import OpenTSLMSP
# Datasets
from opentslm.time_series_datasets.TSQADataset import TSQADataset
from opentslm.time_series_datasets.har_cot.HARCoTQADataset import HARCoTQADataset
from opentslm.time_series_datasets.sleep.SleepEDFCoTQADataset import SleepEDFCoTQADataset
from opentslm.time_series_datasets.ecg_qa.ECGQACoTQADataset import ECGQACoTQADataset
from opentslm.time_series_datasets.simulation.SimulationQADataset import SimulationQADataset
from opentslm.time_series_datasets.util import (
extend_time_series_to_match_patch_size_and_aggregate,
)
def get_device(device_arg: str | None) -> str:
if device_arg:
return device_arg
return "cuda" if torch.cuda.is_available() else "cpu"
def measure_peak_cuda_bytes() -> int:
if not torch.cuda.is_available():
return -1
torch.cuda.synchronize()
return int(torch.cuda.max_memory_allocated())
def measure_peak_cuda_reserved_bytes() -> int:
if not torch.cuda.is_available():
return -1
torch.cuda.synchronize()
return int(torch.cuda.max_memory_reserved())
def nvml_current_process_bytes() -> int:
if not _NVML_AVAILABLE or not torch.cuda.is_available():
return -1
try:
pynvml.nvmlInit()
pid = _os.getpid()
total_bytes = 0
found = False
device_count = pynvml.nvmlDeviceGetCount()
for device_index in range(device_count):
handle = pynvml.nvmlDeviceGetHandleByIndex(device_index)
# Try compute procs first
try:
procs = pynvml.nvmlDeviceGetComputeRunningProcesses_v3(handle)
except Exception:
procs = []
# Fallback to graphics procs
try:
procs_gfx = pynvml.nvmlDeviceGetGraphicsRunningProcesses_v3(handle)
except Exception:
procs_gfx = []
for p in list(procs) + list(procs_gfx):
if (
int(p.pid) == pid
and p.usedGpuMemory is not None
and p.usedGpuMemory >= 0
):
total_bytes += int(p.usedGpuMemory)
found = True
return total_bytes if found else -1
except Exception:
return -1
def get_first_batch(dataset, batch_size: int = 1) -> List[Dict[str, any]]:
# QADataset returns dict samples compatible with model.compute_loss
batch: List[Dict[str, any]] = []
for i in range(min(batch_size, len(dataset))):
batch.append(dataset[i])
# Ensure time series tensors are padded and converted
batch = extend_time_series_to_match_patch_size_and_aggregate(batch)
return batch
def build_optimizer(model, model_type: str, base_lr: float = 2e-4):
if model_type == "OpenTSLMSP":
enc_params = [
p for p in getattr(model, "encoder").parameters() if p.requires_grad
]
proj_params = [
p for p in getattr(model, "projector").parameters() if p.requires_grad
]
param_groups = []
if len(enc_params) > 0:
param_groups.append({"params": enc_params, "weight_decay": 0.1})
if len(proj_params) > 0:
param_groups.append({"params": proj_params, "weight_decay": 0.1})
return (
torch.optim.AdamW(param_groups, lr=base_lr)
if len(param_groups) > 0
else None
)
# Flamingo-like
named_params = list(model.named_parameters())
trainable = list(
filter(
lambda np: np[1].requires_grad
and not getattr(np[1], "exclude_from_optimizer", False),
named_params,
)
)
params_with_wd, params_without_wd = [], []
for name, p in trainable:
if "gated_cross_attn" in name:
params_with_wd.append(p)
else:
params_without_wd.append(p)
if len(params_with_wd) + len(params_without_wd) == 0:
return None
return torch.optim.AdamW(
[
{"params": params_with_wd, "weight_decay": 0.1},
{"params": params_without_wd, "weight_decay": 0.0},
],
lr=2e-4,
)
def train_for_steps(
model, model_type: str, dataset, steps: int
) -> Tuple[float, int, int, int]:
model.train()
optimizer = build_optimizer(model, model_type)
if torch.cuda.is_available():
torch.cuda.reset_peak_memory_stats()
torch.cuda.synchronize()
last_loss = 0.0
# DataLoader with shuffle and collate that pads series
loader = DataLoader(
dataset,
batch_size=1,
shuffle=True,
collate_fn=lambda b: extend_time_series_to_match_patch_size_and_aggregate(b),
drop_last=False,
)
pbar = tqdm(total=steps, desc="Training", leave=False)
max_peak_bytes = -1
max_reserved_bytes = -1
max_nvml_bytes = -1
step = 0
# Initialize postfix
pbar.set_postfix(
{
"alloc_gb": 0.0,
"res_gb": 0.0,
"nvml_gb": 0.0,
}
)
for batch in loader:
if optimizer:
optimizer.zero_grad(set_to_none=True)
loss = model.compute_loss(batch)
if optimizer and loss.requires_grad:
print(f"Backpropagating loss of {loss.item()} for step {step}")
loss.backward()
optimizer.step()
last_loss = float(loss.detach().item())
# Track peak memory across steps
if torch.cuda.is_available():
torch.cuda.synchronize()
current_peak = int(torch.cuda.max_memory_allocated())
current_reserved = int(torch.cuda.max_memory_reserved())
if current_peak > max_peak_bytes:
max_peak_bytes = current_peak
if current_reserved > max_reserved_bytes:
max_reserved_bytes = current_reserved
nvml_bytes = nvml_current_process_bytes()
if nvml_bytes > max_nvml_bytes:
max_nvml_bytes = nvml_bytes
# Update progress bar postfix in GB
def _to_gb(val: int) -> float:
return (
float(val) / (1024.0**3)
if isinstance(val, (int, float)) and val >= 0
else 0.0
)
pbar.set_postfix(
{
"alloc_gb": f"{_to_gb(max_peak_bytes):.2f}",
"res_gb": f"{_to_gb(max_reserved_bytes):.2f}",
"nvml_gb": f"{_to_gb(max_nvml_bytes):.2f}",
}
)
step += 1
pbar.update(1)
if step >= steps:
break
pbar.close()
if torch.cuda.is_available():
peak_bytes = max_peak_bytes
peak_reserved_bytes = max_reserved_bytes
nvml_peak_bytes = max_nvml_bytes
else:
peak_bytes = -1
peak_reserved_bytes = -1
nvml_peak_bytes = -1
return last_loss, peak_bytes, peak_reserved_bytes, nvml_peak_bytes
def ensure_csv(path: str, header: List[str]):
exists = os.path.exists(path)
if not exists:
with open(path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(header)
def append_row(path: str, row: List[any]):
with open(path, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow(row)
def run_for_dataset(
model_name: str, model, dataset_name: str, dataset_obj
) -> Dict[str, any]:
result: Dict[str, any] = {
"model": model_name,
"dataset": dataset_name,
"loss": None,
"peak_cuda_bytes": None,
"status": "ok",
"error": "",
}
try:
# Train for half an epoch, capped at 10000 steps
steps = max(1, min(len(dataset_obj), 10000))
loss, peak, peak_reserved, nvml_peak = train_for_steps(
model, model_name, dataset_obj, steps
)
result["loss"] = loss
result["peak_cuda_bytes"] = peak
result["peak_cuda_reserved_bytes"] = peak_reserved
result["nvml_peak_bytes"] = nvml_peak
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
return result
def main():
parser = argparse.ArgumentParser(
description="Measure memory use for a single training iteration for a chosen model and dataset."
)
parser.add_argument(
"-llm_id", required=True, help="HuggingFace model id for the language model"
)
parser.add_argument(
"--model",
required=True,
choices=["OpenTSLMFlamingo", "OpenTSLMSP"],
help="Model to instantiate",
)
parser.add_argument(
"--dataset",
required=True,
choices=[
"TSQADataset",
"HARCoTQADataset",
"SleepEDFCoTQADataset",
"ECGQACoTQADataset",
"SimulationQADataset",
],
help="Dataset to use",
)
parser.add_argument(
"--device", default="cuda", help="Device to run on (e.g., cuda, cuda:0, cpu)"
)
parser.add_argument(
"--length",
type=int,
default=100,
help="Length of time series for SimulationQADataset (default: 100)",
)
parser.add_argument(
"--num_series",
type=int,
default=1,
help="Number of time series for SimulationQADataset (default: 1)",
)
parser.add_argument(
"--results_csv",
default=os.path.join(REPO_DIR, "memory_use.csv"),
help="Path to CSV file to append results",
)
args = parser.parse_args()
device = get_device(args.device)
# CSV header and file
header = [
"timestamp",
"llm_id",
"device",
"model",
"dataset",
"loss",
"peak_cuda_bytes",
"peak_cuda_gb",
"peak_cuda_reserved_bytes",
"peak_cuda_reserved_gb",
"nvml_peak_bytes",
"nvml_peak_gb",
"status",
"error",
]
ensure_csv(args.results_csv, header)
# Instantiate selected model
if args.model == "OpenTSLMFlamingo":
model = OpenTSLMFlamingo(
device=device,
llm_id=args.llm_id,
cross_attn_every_n_layers=1,
gradient_checkpointing=True,
)
eos = model.get_eos_token()
elif args.model == "OpenTSLMSP":
model = OpenTSLMSP(llm_id=args.llm_id, device=device)
eos = model.get_eos_token()
else:
raise ValueError(f"Unknown model: {args.model}")
# Make absolutely sure parameters are on the requested device
model.to(device)
# Instantiate selected dataset
if args.dataset == "TSQADataset":
dataset = TSQADataset(split="train", EOS_TOKEN=eos)
dataset_name = "TSQA"
elif args.dataset == "HARCoTQADataset":
dataset = HARCoTQADataset(split="train", EOS_TOKEN=eos)
dataset_name = "HAR-CoT"
elif args.dataset == "SleepEDFCoTQADataset":
dataset = SleepEDFCoTQADataset(split="train", EOS_TOKEN=eos)
dataset_name = "SleepEDF-CoT"
elif args.dataset == "ECGQACoTQADataset":
dataset = ECGQACoTQADataset(
split="train", EOS_TOKEN=eos, max_samples=1, preload_processed_data=False
)
dataset_name = "ECG-QA-CoT"
elif args.dataset == "SimulationQADataset":
dataset = SimulationQADataset(
split="train", EOS_TOKEN=eos, length=args.length, num_series=args.num_series
)
dataset_name = f"Simulation-L{args.length}-N{args.num_series}"
else:
raise ValueError(f"Unknown dataset: {args.dataset}")
# Run one iteration and append results
res = run_for_dataset(args.model, model, dataset_name, dataset)
peak_bytes = res["peak_cuda_bytes"]
peak_gb = (
(float(peak_bytes) / (1024.0**3))
if isinstance(peak_bytes, (int, float)) and peak_bytes >= 0
else -1
)
peak_reserved_bytes = res.get("peak_cuda_reserved_bytes", -1)
peak_reserved_gb = (
(float(peak_reserved_bytes) / (1024.0**3))
if isinstance(peak_reserved_bytes, (int, float)) and peak_reserved_bytes >= 0
else -1
)
nvml_peak_bytes = res.get("nvml_peak_bytes", -1)
nvml_peak_gb = (
(float(nvml_peak_bytes) / (1024.0**3))
if isinstance(nvml_peak_bytes, (int, float)) and nvml_peak_bytes >= 0
else -1
)
append_row(
args.results_csv,
[
datetime.utcnow().isoformat(),
args.llm_id,
device,
res["model"],
res["dataset"],
res["loss"],
res["peak_cuda_bytes"],
(
f"{peak_gb:.4f}"
if isinstance(peak_gb, float) and peak_gb >= 0
else peak_gb
),
res.get("peak_cuda_reserved_bytes", -1),
(
f"{peak_reserved_gb:.4f}"
if isinstance(peak_reserved_gb, float) and peak_reserved_gb >= 0
else peak_reserved_gb
),
res.get("nvml_peak_bytes", -1),
(
f"{nvml_peak_gb:.4f}"
if isinstance(nvml_peak_gb, float) and nvml_peak_gb >= 0
else nvml_peak_gb
),
res["status"],
res["error"],
],
)
print(f"Done. Results appended to: {args.results_csv}")
if __name__ == "__main__":
main()