Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
not current_platform.is_cuda_alike(),
reason="fastsafetensors requires NVIDIA/AMD GPUs",
)
def test_fastsafetensors_model_loader():
@pytest.mark.parametrize("queue_size", [0, 1])
def test_fastsafetensors_model_loader(monkeypatch, queue_size):
monkeypatch.setenv("VLLM_FASTSAFETENSORS_QUEUE_SIZE", str(queue_size))
with tempfile.TemporaryDirectory() as tmpdir:
huggingface_hub.constants.HF_HUB_OFFLINE = False
download_weights_from_hf(
Expand All @@ -45,7 +47,3 @@ def test_fastsafetensors_model_loader():
assert fastsafetensors_tensor.dtype == hf_safetensors_tensors[name].dtype
assert fastsafetensors_tensor.shape == hf_safetensors_tensors[name].shape
assert torch.all(fastsafetensors_tensor.eq(hf_safetensors_tensors[name]))


if __name__ == "__main__":
test_fastsafetensors_model_loader()
16 changes: 16 additions & 0 deletions vllm/envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
VLLM_USE_TRITON_AWQ_GEMV: bool = True
VLLM_ALLOW_UNFUSED_AWQ_GEMM: bool = True
VLLM_AWQ_USE_TN_GEMM: bool = False
VLLM_FASTSAFETENSORS_QUEUE_SIZE: int = 0
VLLM_ALLOW_RUNTIME_LORA_UPDATING: bool = False
VLLM_SKIP_P2P_CHECK: bool = False
VLLM_DISABLED_KERNELS: list[str] = []
Expand Down Expand Up @@ -1027,6 +1028,21 @@ def _resolve_rust_frontend_path() -> str | None:
"VLLM_TEST_FORCE_LOAD_FORMAT": lambda: os.getenv(
"VLLM_TEST_FORCE_LOAD_FORMAT", "dummy"
),
# Queue size for fastsafetensors ParallelLoader pipelined weight
# loading. Peak load-time VRAM is roughly
# model_weights + (1 + queue_size) * shard_size.
# Default 0 preserves the non-pipelined memory footprint so this
# change does not shrink the loadable-model envelope. Set to 1
# (or higher) to overlap producing the next shard's device buffer
# with the consumer copying the current shard into model params,
# at the cost of `queue_size` extra shard-sized buffers resident
# at peak during loading.
"VLLM_FASTSAFETENSORS_QUEUE_SIZE": lambda: int(
os.getenv("VLLM_FASTSAFETENSORS_QUEUE_SIZE", "0")
),
# Time in ms for the zmq client to wait for a response from the backend
# server for simple data operations
"VLLM_RPC_TIMEOUT": lambda: int(os.getenv("VLLM_RPC_TIMEOUT", "10000")),
# Timeout in seconds for keeping HTTP connections alive in API server
"VLLM_HTTP_TIMEOUT_KEEP_ALIVE": lambda: int(
os.environ.get("VLLM_HTTP_TIMEOUT_KEEP_ALIVE", "5")
Expand Down
100 changes: 49 additions & 51 deletions vllm/model_executor/model_loader/weight_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@
SafetensorsStreamer = runai_model_streamer.placeholder_attr("SafetensorsStreamer")

try:
from fastsafetensors import SafeTensorsFileLoader, SingleGroup
from fastsafetensors import SingleGroup
except ImportError:
fastsafetensors = PlaceholderModule("fastsafetensors")
SafeTensorsFileLoader = fastsafetensors.placeholder_attr("SafeTensorsFileLoader")
SingleGroup = fastsafetensors.placeholder_attr("SingleGroup")

from vllm.model_executor.layers.quantization.torchao import torchao_version_at_least
Expand Down Expand Up @@ -1022,74 +1021,73 @@ def runai_safetensors_weights_iterator(
yield name, tensor.clone()


def _init_fastsafetensors_loader(
pg: "torch.distributed.ProcessGroup",
device: torch.device,
f_list: list[str],
*,
nogds: bool = False,
):
loader = SafeTensorsFileLoader(pg, device, nogds=nogds)
rank_file_map = {i: [f] for i, f in enumerate(f_list)}
loader.add_filenames(rank_file_map)
return loader


def fastsafetensors_weights_iterator(
hf_weights_files: list[str],
use_tqdm_on_load: bool,
) -> Generator[tuple[str, torch.Tensor], None, None]:
"""Iterate over the weights in the model safetensor files
using fastsafetensor library."""
using fastsafetensor library.

Uses ParallelLoader for pipelined loading: the producer thread
prepares metadata for the next shard while the consumer yields
tensors from the current shard.
"""
from fastsafetensors.parallel_loader import ParallelLoader

if torch.distributed.is_initialized():
pg = torch.distributed.group.WORLD
else:
pg = SingleGroup()

device = torch.device(f"cuda:{current_platform.current_device()}")
hf_weights_files = sorted(hf_weights_files, key=_natural_sort_key)
weight_files_sub_lists = [
hf_weights_files[i : i + pg.size()]
for i in range(0, len(hf_weights_files), pg.size())
]

# Use nogds=True for TP > 1 to avoid cuFileDriverOpen() which
# initializes the GDS DMA subsystem for all visible GPUs, creating
# unwanted CUDA contexts on every device.
nogds = pg.size() > 1

for f_list in tqdm(
weight_files_sub_lists,
desc="Loading safetensors using Fastsafetensor loader",
disable=not enable_tqdm(use_tqdm_on_load),
bar_format=_BAR_FORMAT,
):
loader = _init_fastsafetensors_loader(pg, device, f_list, nogds=nogds)
try:
try:
fb = loader.copy_files_to_device()
except RuntimeError as e:
if "gds" not in str(e):
raise

loader.close()
nogds = True
logger.warning_once(
"GDS not enabled, setting `nogds=True`.\n"
"For more information, see: https://github.com/foundation-model-stack/fastsafetensors?tab=readme-ov-file#basic-api-usages"
)
loader = _init_fastsafetensors_loader(pg, device, f_list, nogds=nogds)
fb = loader.copy_files_to_device()
queue_size = envs.VLLM_FASTSAFETENSORS_QUEUE_SIZE
tqdm_enabled = enable_tqdm(use_tqdm_on_load)

def _make_loader(nogds: bool) -> "ParallelLoader":
return ParallelLoader(
pg=pg,
hf_weights_files=hf_weights_files,
queue_size=queue_size,
use_tqdm_on_load=tqdm_enabled,
device=str(device),
nogds=nogds,
)

try:
keys = list(fb.key_to_rank_lidx.keys())
for k in keys:
t = fb.get_tensor(k)
yield k, t
finally:
fb.close()
finally:
loader.close()
# GDS can fail either at construction or lazily inside the producer
# thread during iteration (e.g. cuFileHandleRegister returning
# CU_FILE_HANDLE_NOT_REGISTERED on a filesystem without GDS support).
# Catch both and fall back to nogds, but only before yielding any
# tensor -- restarting mid-stream would reload earlier shards.
pl = None
yielded = False
try:
try:
pl = _make_loader(nogds)
for name, tensor in pl.iterate_weights():
yielded = True
yield name, tensor
except RuntimeError as e:
if nogds or yielded or "gds" not in str(e):
raise
logger.warning_once(
"GDS not enabled, setting `nogds=True`.\n"
"For more information, see: https://github.com/foundation-model-stack/"
"fastsafetensors?tab=readme-ov-file#basic-api-usages"
)
if pl is not None:
pl.close()
pl = _make_loader(nogds=True)
yield from pl.iterate_weights()
finally:
if pl is not None:
pl.close()


def instanttensor_weights_iterator(
Expand Down
Loading