[diffusion] feat: avoid direct GPU parameter copies (#36832)
This commit is contained in:
@@ -427,8 +427,9 @@ class TransformerLoader(ComponentLoader):
|
||||
)
|
||||
if direct_gpu_weight_loading:
|
||||
logger.warning(
|
||||
"Direct GPU weight loading is enabled for %s; the complete checkpoint "
|
||||
"state dict and materialized model weights may coexist on GPU during startup",
|
||||
"Direct GPU weight loading is enabled for %s; compatible checkpoint "
|
||||
"tensors become model storage, while transformed tensors may still "
|
||||
"require temporary GPU allocations",
|
||||
component_name,
|
||||
)
|
||||
|
||||
|
||||
@@ -98,14 +98,12 @@ def _make_param_like(
|
||||
return new_param
|
||||
|
||||
|
||||
def _can_assign_cpu_tensor_without_copy(
|
||||
def _can_assign_tensor_without_copy(
|
||||
actual_param: torch.nn.Parameter,
|
||||
full_tensor: torch.Tensor,
|
||||
target_param: torch.Tensor,
|
||||
) -> bool:
|
||||
"""Return whether a TP=1 linear loader would only copy this CPU tensor."""
|
||||
if full_tensor.device.type != "cpu":
|
||||
return False
|
||||
"""Return whether a TP=1 linear loader would only copy this tensor."""
|
||||
weight_loader = actual_param.__dict__.get("weight_loader")
|
||||
if not isinstance(weight_loader, MethodType):
|
||||
return False
|
||||
@@ -134,6 +132,8 @@ def _can_assign_cpu_tensor_without_copy(
|
||||
return (
|
||||
full_tensor.shape == target_param.shape
|
||||
and full_tensor.dtype == target_param.dtype
|
||||
and full_tensor.layout == target_param.layout
|
||||
and full_tensor.stride() == target_param.stride()
|
||||
)
|
||||
|
||||
|
||||
@@ -432,6 +432,9 @@ def maybe_load_fsdp_model(
|
||||
cpu_offload=load_on_cpu,
|
||||
param_names_mapping=param_names_mapping_fn,
|
||||
keep_checkpoint_mapping=keep_checkpoint_mapping,
|
||||
allow_device_tensor_assignment=(
|
||||
weight_load_plan.load_full_state_dict_on_device
|
||||
),
|
||||
preconverted_state_dict=preconverted_state_dict,
|
||||
)
|
||||
if bnb_quant_states:
|
||||
@@ -568,6 +571,7 @@ def load_model_from_full_model_state_dict(
|
||||
]
|
||||
| None
|
||||
) = None,
|
||||
allow_device_tensor_assignment: bool = False,
|
||||
) -> _IncompatibleKeys:
|
||||
"""
|
||||
Converting full state dict into a sharded state dict
|
||||
@@ -581,6 +585,10 @@ def load_model_from_full_model_state_dict(
|
||||
cpu_offload (bool): flag to check if FSDP offload is enabled
|
||||
param_names_mapping (Optional[Callable[[str], str]]): a function that maps full param name to sharded param name
|
||||
keep_checkpoint_mapping (bool): retain compatible CPU checkpoint tensors instead of copying them
|
||||
allow_device_tensor_assignment (bool): adopt compatible checkpoint tensors
|
||||
already materialized on the target device. This is reserved for an
|
||||
explicit full-state direct-device load; ordinary loading keeps its
|
||||
established parameter materialization path.
|
||||
Returns:
|
||||
``NamedTuple`` with ``missing_keys`` and ``unexpected_keys`` fields:
|
||||
* **missing_keys** is a list of str containing the missing keys
|
||||
@@ -734,10 +742,10 @@ def load_model_from_full_model_state_dict(
|
||||
sharded_tensor = full_tensor
|
||||
elif weight_loader is not None:
|
||||
assert actual_param is not None
|
||||
if _can_assign_cpu_tensor_without_copy(
|
||||
actual_param,
|
||||
full_tensor,
|
||||
meta_sharded_param,
|
||||
if (
|
||||
full_tensor.device.type == "cpu" or allow_device_tensor_assignment
|
||||
) and _can_assign_tensor_without_copy(
|
||||
actual_param, full_tensor, meta_sharded_param
|
||||
):
|
||||
sharded_tensor = full_tensor
|
||||
else:
|
||||
|
||||
@@ -9,7 +9,11 @@ import torch
|
||||
from safetensors.torch import safe_open, save_file
|
||||
from torch import nn
|
||||
|
||||
from sglang.multimodal_gen.runtime.layers.linear import ReplicatedLinear
|
||||
from sglang.multimodal_gen.runtime.layers.linear import (
|
||||
ColumnParallelLinear,
|
||||
ReplicatedLinear,
|
||||
UnquantizedLinearMethod,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.quantization.bitsandbytes import (
|
||||
BitsAndBytesConfig,
|
||||
)
|
||||
@@ -180,6 +184,26 @@ class TestFSDPEntrypointRegistration(unittest.TestCase):
|
||||
|
||||
|
||||
class TestOrdinaryWeightLoading(unittest.TestCase):
|
||||
def _load_replicated_weight(
|
||||
self, device: torch.device, *, allow_device_tensor_assignment: bool = False
|
||||
) -> tuple[torch.nn.Parameter, torch.Tensor]:
|
||||
with torch.device("meta"):
|
||||
model = _ReplicatedLinearModel()
|
||||
checkpoint_weight = torch.arange(
|
||||
16, dtype=torch.float32, device=device
|
||||
).reshape(4, 4)
|
||||
|
||||
fsdp_load.load_model_from_full_model_state_dict(
|
||||
model,
|
||||
iter((("proj.weight", checkpoint_weight),)),
|
||||
checkpoint_load_device=device,
|
||||
param_dtype=torch.float32,
|
||||
strict=True,
|
||||
param_names_mapping=fsdp_load.get_param_names_mapping({}),
|
||||
allow_device_tensor_assignment=allow_device_tensor_assignment,
|
||||
)
|
||||
return model.proj.weight, checkpoint_weight
|
||||
|
||||
def test_direct_device_loading_skips_rank_local_cpu_checkpoint(self):
|
||||
load_plan = WeightLoadPlan(
|
||||
checkpoint_load_device=torch.device("cuda:0"),
|
||||
@@ -217,22 +241,54 @@ class TestOrdinaryWeightLoading(unittest.TestCase):
|
||||
weight_load_plan=load_plan,
|
||||
)
|
||||
|
||||
def test_tp1_unquantized_linear_assigns_checkpoint_tensor_without_copy(self):
|
||||
with torch.device("meta"):
|
||||
model = _ReplicatedLinearModel()
|
||||
checkpoint_weight = torch.arange(16, dtype=torch.float32).reshape(4, 4)
|
||||
|
||||
fsdp_load.load_model_from_full_model_state_dict(
|
||||
model,
|
||||
iter((("proj.weight", checkpoint_weight),)),
|
||||
checkpoint_load_device=torch.device("cpu"),
|
||||
param_dtype=torch.float32,
|
||||
strict=True,
|
||||
param_names_mapping=fsdp_load.get_param_names_mapping({}),
|
||||
def test_tp1_unquantized_linear_adopts_cpu_checkpoint_storage(self):
|
||||
model_weight, checkpoint_weight = self._load_replicated_weight(
|
||||
torch.device("cpu")
|
||||
)
|
||||
|
||||
self.assertEqual(model.proj.weight.data_ptr(), checkpoint_weight.data_ptr())
|
||||
torch.testing.assert_close(model.proj.weight, checkpoint_weight)
|
||||
self.assertEqual(model_weight.data_ptr(), checkpoint_weight.data_ptr())
|
||||
torch.testing.assert_close(model_weight, checkpoint_weight)
|
||||
|
||||
@unittest.skipUnless(torch.cuda.is_available(), "CUDA is required")
|
||||
def test_ordinary_cuda_loading_preserves_materialization_path(self):
|
||||
model_weight, checkpoint_weight = self._load_replicated_weight(
|
||||
torch.device("cuda:0")
|
||||
)
|
||||
|
||||
self.assertNotEqual(model_weight.data_ptr(), checkpoint_weight.data_ptr())
|
||||
torch.testing.assert_close(model_weight, checkpoint_weight)
|
||||
|
||||
@unittest.skipUnless(torch.cuda.is_available(), "CUDA is required")
|
||||
def test_direct_cuda_loading_adopts_checkpoint_storage(self):
|
||||
model_weight, checkpoint_weight = self._load_replicated_weight(
|
||||
torch.device("cuda:0"), allow_device_tensor_assignment=True
|
||||
)
|
||||
|
||||
self.assertEqual(model_weight.data_ptr(), checkpoint_weight.data_ptr())
|
||||
torch.testing.assert_close(model_weight, checkpoint_weight)
|
||||
|
||||
def test_zero_copy_assignment_rejects_incompatible_layout_or_tp_weights(self):
|
||||
with torch.device("meta"):
|
||||
model = _ReplicatedLinearModel()
|
||||
param = model.proj.weight
|
||||
tensor = torch.empty(4, 4)
|
||||
|
||||
self.assertFalse(
|
||||
fsdp_load._can_assign_tensor_without_copy(
|
||||
param, tensor.as_strided((4, 4), (1, 4)), param
|
||||
)
|
||||
)
|
||||
|
||||
tp_owner = ColumnParallelLinear.__new__(ColumnParallelLinear)
|
||||
nn.Module.__init__(tp_owner)
|
||||
tp_owner.quant_method = UnquantizedLinearMethod()
|
||||
tp_owner.tp_size = 1
|
||||
tp_param = nn.Parameter(tensor)
|
||||
tp_param.weight_loader = tp_owner.weight_loader
|
||||
tp_owner.tp_size = 2
|
||||
self.assertFalse(
|
||||
fsdp_load._can_assign_tensor_without_copy(tp_param, tensor, tp_param)
|
||||
)
|
||||
|
||||
|
||||
class TestDevicePostprocessMove(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user