from __future__ import annotations
import json
import logging
import os
import struct
import time
import uuid
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Iterator
import torch
from .accelerators import AcceleratorBackend
from .gds_transfer import GdsTransferManager, is_gds_available
logger = logging.getLogger("modelexpress.gds_loader")
SAFETENSORS_DTYPE_MAP: dict[str, torch.dtype] = {
"F64": torch.float64,
"F32": torch.float32,
"F16": torch.float16,
"BF16": torch.bfloat16,
"F8_E4M3": torch.float8_e4m3fn,
"F8_E5M2": torch.float8_e5m2,
"I64": torch.int64,
"I32": torch.int32,
"I16": torch.int16,
"I8": torch.int8,
"U8": torch.uint8,
"BOOL": torch.bool,
}
class MxGdsLoader:
def __init__(self, accelerator_backend: AcceleratorBackend):
self._gds_manager: GdsTransferManager | None = None
self._device_id: int | None = None
self._accelerator_backend = accelerator_backend
def load(self, model_path: str) -> dict[str, torch.Tensor]:
result: dict[str, torch.Tensor] = {}
for name, tensor in self.load_iter(model_path):
result[name] = tensor
return result
def load_iter(
self,
model_path: str,
*,
use_tqdm: bool = True,
revision: str | None = None,
) -> Iterator[tuple[str, torch.Tensor]]:
load_start = time.perf_counter()
model_path = self._resolve_model_path(model_path, revision=revision)
if not is_gds_available():
raise RuntimeError(
"GDS is not available. Check nvidia_fs module and libcufile."
)
self._device_id = self._accelerator_backend.current_device()
self._ensure_gds_manager()
file_tensor_map = self._resolve_safetensors_files(model_path)
file_jobs = []
for file_path, tensor_names in file_tensor_map.items():
header_info = self._parse_safetensors_header(file_path)
file_tensors = {
name: header_info[name]
for name in tensor_names
if name in header_info
}
if file_tensors:
file_jobs.append((file_path, file_tensors))
if not file_jobs:
return
total_files = len(file_jobs)
pbar = None
if use_tqdm:
from tqdm import tqdm
pbar = tqdm(
total=total_files,
desc="Loading safetensors via GDS",
unit="file",
)
pool = ThreadPoolExecutor(max_workers=1)
try:
pending = pool.submit(self._load_file_tensors, *file_jobs[0])
for i in range(total_files):
loaded = pending.result()
if pbar is not None:
pbar.update(1)
if i + 1 < total_files:
pending = pool.submit(
self._load_file_tensors, *file_jobs[i + 1]
)
for name, tensor in loaded.items():
yield name, tensor
logger.info("GDS load complete in %.2fs", time.perf_counter() - load_start)
finally:
if pbar is not None:
pbar.close()
pool.shutdown(wait=True)
@staticmethod
def _resolve_model_path(
model_path: str, revision: str | None = None
) -> str:
p = Path(model_path)
if p.is_dir():
return str(p.resolve())
from huggingface_hub import snapshot_download
local_dir = snapshot_download(model_path, revision=revision)
logger.info("Resolved HF model '%s' -> %s", model_path, local_dir)
return local_dir
def _ensure_gds_manager(self) -> None:
if self._gds_manager is not None:
return
agent_name = f"mx-gds-{self._device_id}-{uuid.uuid4().hex[:8]}"
self._gds_manager = GdsTransferManager(
agent_name=agent_name,
accelerator_backend=self._accelerator_backend,
)
self._gds_manager.initialize()
logger.info("GDS manager initialized for device %d", self._device_id)
def _resolve_safetensors_files(
self, model_path: str
) -> dict[str, list[str]]:
model_dir = Path(model_path)
index_path = model_dir / "model.safetensors.index.json"
if index_path.exists():
with open(index_path, "r") as f:
index = json.load(f)
weight_map: dict[str, str] = index.get("weight_map", {})
if not weight_map:
raise RuntimeError(f"Empty weight_map in {index_path}")
file_tensors: dict[str, list[str]] = defaultdict(list)
for tensor_name, filename in weight_map.items():
abs_path = str(model_dir / filename)
file_tensors[abs_path].append(tensor_name)
logger.info(
"Found sharded model: %d files, %d tensors",
len(file_tensors), len(weight_map),
)
return dict(file_tensors)
single_path = model_dir / "model.safetensors"
if single_path.exists():
header_info = self._parse_safetensors_header(str(single_path))
tensor_names = list(header_info.keys())
logger.info("Found single safetensors file: %d tensors", len(tensor_names))
return {str(single_path): tensor_names}
st_files = sorted(model_dir.glob("*.safetensors"))
if not st_files:
raise FileNotFoundError(f"No .safetensors files found in {model_path}")
file_tensors_map: dict[str, list[str]] = {}
for st_file in st_files:
header_info = self._parse_safetensors_header(str(st_file))
file_tensors_map[str(st_file)] = list(header_info.keys())
total = sum(len(v) for v in file_tensors_map.values())
logger.info(
"Found %d safetensors files via glob: %d tensors",
len(file_tensors_map), total,
)
return file_tensors_map
def _parse_safetensors_header(self, file_path: str) -> dict[str, dict]:
with open(file_path, "rb") as f:
raw = f.read(8)
if len(raw) < 8:
raise RuntimeError(f"Invalid safetensors file: {file_path}")
header_size = struct.unpack("<Q", raw)[0]
if header_size > 100 * 1024 * 1024:
raise RuntimeError(
f"Safetensors header too large ({header_size} bytes): {file_path}"
)
header_bytes = f.read(header_size)
header = json.loads(header_bytes)
data_start = 8 + header_size
result: dict[str, dict] = {}
for name, info in header.items():
if name == "__metadata__":
continue
offsets = info["data_offsets"]
result[name] = {
"file_offset": data_start + offsets[0],
"size": offsets[1] - offsets[0],
"dtype": info["dtype"],
"shape": info["shape"],
}
return result
def _load_file_tensors(
self,
file_path: str,
tensor_infos: dict[str, dict],
) -> dict[str, torch.Tensor]:
device = self._accelerator_backend.torch_device(self._device_id)
sorted_names = sorted(
tensor_infos.keys(),
key=lambda n: tensor_infos[n]["file_offset"],
)
tensor_list = []
tensor_meta = []
for name in sorted_names:
info = tensor_infos[name]
st_dtype = info["dtype"]
torch_dtype = SAFETENSORS_DTYPE_MAP.get(st_dtype)
if torch_dtype is None:
raise RuntimeError(
f"Unsupported safetensors dtype '{st_dtype}' "
f"for tensor '{name}'"
)
tensor_list.append((info["file_offset"], info["size"]))
tensor_meta.append((name, torch_dtype, info["shape"]))
fd = os.open(file_path, os.O_RDONLY)
file_size = os.fstat(fd).st_size
try:
raw_tensors = self._gds_manager.batch_load_file(
fd, file_size, tensor_list, device,
)
finally:
os.close(fd)
result: dict[str, torch.Tensor] = {}
for raw, (name, torch_dtype, shape) in zip(raw_tensors, tensor_meta, strict=True):
result[name] = raw.view(torch_dtype).reshape(shape)
logger.info("Loaded %s", Path(file_path).name)
return result
def shutdown(self) -> None:
if self._gds_manager is not None:
self._gds_manager.shutdown()
self._gds_manager = None