fix: use dispatch_model and strip _is_hf_initialized for bnb multi-GPU hooks
Two bugs found during testing: 1. accelerate's set_module_tensor_to_device passes param.__dict__ as kwargs to Params4bit(), but HF Transformers adds _is_hf_initialized to that dict. Params4bit.__new__() does not accept it, causing TypeError. Fix: strip the key before dispatching, restore after. 2. attach_align_device_hook_on_blocks with a block-level device map only installs coarse-grained hooks. Sub-modules (e.g. RMSNorm) inherit the wrong execution device, causing cross-device errors at forward time. Fix: use dispatch_model which installs hooks at every sub-module level, matching HF's own from_pretrained behavior. Tested with 28 tests across unit, single-GPU, multi-GPU (gemma-4-31B 4bit on 2x B200), and edge case suites -- all passing.
This commit is contained in:
parent
bed825e04a
commit
8603233829
1 changed files with 28 additions and 39 deletions
|
|
@ -200,7 +200,7 @@ def _attach_bnb_multidevice_hooks(
|
|||
return
|
||||
|
||||
try:
|
||||
from accelerate.hooks import attach_align_device_hook_on_blocks
|
||||
from accelerate import dispatch_model
|
||||
from accelerate.utils import find_tied_parameters, retie_parameters
|
||||
except ImportError:
|
||||
return # accelerate not available
|
||||
|
|
@ -210,51 +210,40 @@ def _attach_bnb_multidevice_hooks(
|
|||
if not inferred_map:
|
||||
return
|
||||
|
||||
# Determine the "main" device (first CUDA device encountered).
|
||||
cuda_device_vals = [
|
||||
v
|
||||
for v in inferred_map.values()
|
||||
if isinstance(v, torch.device) and v.type == "cuda"
|
||||
]
|
||||
main_device = cuda_device_vals[0] if cuda_device_vals else next(iter(cuda_devs))
|
||||
|
||||
# Preserve tied-parameter references before installing hooks.
|
||||
# Preserve tied-parameter references before dispatching.
|
||||
tied_params = find_tied_parameters(model)
|
||||
|
||||
if len(cuda_devs) > 1:
|
||||
# Multi-device: each block's hook routes inputs to its own device.
|
||||
execution_device = dict(inferred_map)
|
||||
execution_device[""] = main_device
|
||||
offload_dict = {k: False for k in execution_device}
|
||||
attach_align_device_hook_on_blocks(
|
||||
model,
|
||||
execution_device = execution_device,
|
||||
offload = offload_dict,
|
||||
weights_map = None,
|
||||
offload_buffers = False,
|
||||
)
|
||||
# accelerate's set_module_tensor_to_device grabs param.__dict__ and
|
||||
# passes it as **kwargs to the parameter class constructor. HF
|
||||
# Transformers adds _is_hf_initialized to parameter __dict__ which
|
||||
# bitsandbytes Params4bit/Int8Params do not accept, causing TypeError.
|
||||
# Strip these extra keys before dispatching.
|
||||
_extra_keys = ("_is_hf_initialized",)
|
||||
_stripped = []
|
||||
for _, param in model.named_parameters():
|
||||
for key in _extra_keys:
|
||||
if key in param.__dict__:
|
||||
_stripped.append((param, key, param.__dict__.pop(key)))
|
||||
|
||||
try:
|
||||
# Convert device_map values from torch.device to int (HF convention)
|
||||
device_map_int = {
|
||||
k: v.index if isinstance(v, torch.device) and v.type == "cuda" else v
|
||||
for k, v in inferred_map.items()
|
||||
}
|
||||
|
||||
# dispatch_model installs AlignDevicesHook on every module and
|
||||
# sub-module, exactly matching HF's own loading behavior.
|
||||
dispatch_model(model, device_map = device_map_int)
|
||||
desc = f"{len(inferred_map)} block(s) across {len(cuda_devs)} device(s)"
|
||||
else:
|
||||
# Single non-default device: one root hook sends all inputs to it.
|
||||
attach_align_device_hook_on_blocks(
|
||||
model,
|
||||
execution_device = main_device,
|
||||
offload = False,
|
||||
weights_map = None,
|
||||
offload_buffers = False,
|
||||
)
|
||||
desc = f"root hook -> {main_device} (single non-default device)"
|
||||
finally:
|
||||
# Restore stripped keys
|
||||
for param, key, val in _stripped:
|
||||
param.__dict__[key] = val
|
||||
|
||||
# Retie parameters that hook installation may have unlinked.
|
||||
retie_parameters(model, tied_params)
|
||||
|
||||
# Expose hf_device_map so downstream generation helpers work.
|
||||
# Use the device index (int) as the value, matching HF's convention.
|
||||
model.hf_device_map = {
|
||||
k: v.index if isinstance(v, torch.device) else v
|
||||
for k, v in inferred_map.items()
|
||||
}
|
||||
|
||||
print(
|
||||
f"Unsloth: Attached accelerate AlignDevicesHook ({desc}) "
|
||||
f"for bnb multi-GPU inference."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue