Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
64 changes: 64 additions & 0 deletions tests/model_executor/model_loader/test_reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,70 @@ def test_online_processing_waits_for_late_registered_bias():
assert torch.equal(quant_method.bias_at_process, loaded_bias)


@pytest.mark.parametrize("num_heads", [96, 128])
@pytest.mark.parametrize("tp_rank", [0, 3])
@pytest.mark.parametrize("reverse", [False, True])
def test_kda_online_loading_releases_sources_and_zeros_padding(
monkeypatch, default_vllm_config, num_heads, tp_rank, reverse
):
"""Finish KDA's merged projection after six inputs, before model finalization."""
from vllm.models.kimi_k3.amd.kda import KimiK3DeltaAttention
from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig

monkeypatch.setattr(
"vllm.distributed.parallel_state.get_tp_group",
lambda: SimpleNamespace(world_size=8, rank_in_group=tp_rank),
)
default_vllm_config.model_config = SimpleNamespace(dtype=torch.float32)
config = KimiLinearConfig(
hidden_size=16,
linear_attn_config={
"kda_layers": [0],
"full_attn_layers": [],
"head_dim": 128,
"num_heads": num_heads,
"short_conv_kernel_size": 4,
"use_full_rank_gate": True,
},
)
attention = KimiK3DeltaAttention(
config, default_vllm_config, prefix="model.layers.0.self_attn"
)
projection = attention.in_proj_qkvgfab
# Exercise online loading on CPU without selecting a GPU quantization kernel.
projection.weight = to_meta_tensor(projection.weight)
projection.quant_method = None
initialize_online_processing(projection)

empty_strided = torch.empty_strided

def poisoned_empty(*args, **kwargs):
return empty_strided(*args, **kwargs).fill_(float("nan"))

monkeypatch.setattr(torch, "empty_strided", poisoned_empty)
widths = [128 * num_heads] * 4 + [128, num_heads]
expected = {}
sources = []
for shard in reversed(range(6)) if reverse else range(6):
source = torch.arange(widths[shard] * 16, dtype=torch.float32).reshape(-1, 16)
sources.append(ref(source))
local_width = widths[shard] if shard == 4 else widths[shard] // 8
start = 0 if shard == 4 else tp_rank * local_width
expected[shard] = source[start : start + local_width].clone()
assert projection.weight.is_meta
projection.weight.weight_loader(projection.weight, source, shard)
del source

assert not projection.weight.is_meta
assert all(source() is None for source in sources)
expected_weight = torch.cat([expected[shard] for shard in range(6)])
torch.testing.assert_close(
projection.weight[: len(expected_weight)], expected_weight
)
padding = projection.weight[len(expected_weight) :]
assert torch.equal(padding, torch.zeros_like(padding))


def test_layerwise_reload_skips_non_persistent_parameter_alias_buffers(monkeypatch):
layer = _AliasedBufferLayer()
model = torch.nn.Sequential(layer)
Expand Down
3 changes: 3 additions & 0 deletions vllm/model_executor/model_loader/reload/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ def materialize_meta_tensor(meta_tensor: torch.Tensor) -> torch.Tensor:
dtype=meta_tensor.dtype,
requires_grad=False,
)
# Initialization on meta does not preserve storage for unloaded padding.
if get_tensor_load_numel(meta_tensor) < meta_tensor.numel():
tensor.zero_()
tensor.__class__ = meta_tensor.__class__
tensor.__dict__ = meta_tensor.__dict__.copy()
return tensor
Expand Down
4 changes: 4 additions & 0 deletions vllm/models/kimi_k3/amd/kda.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ def __init__(
)
if self.in_proj_padding:
self.in_proj_qkvgfab.weight.data[-self.in_proj_padding :].zero_()
# Alignment rows are not part of the checkpoint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: can we instead fix this in the same way as nvidia, i.e. add a new _KimiGDNMergedColumnParallelLinear and add the padding/zeroing fix there? Alternatively just import their implementation directly

That way we don't have to touch any shared code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — moved the padding handling into an AMD-local _PaddedKimiGDNMergedColumnParallelLinear (subclassing the shared Kimi-Linear one), mirroring the NVIDIA class; meta.py is no longer touched. I didn't import the NVIDIA class directly since nvidia/kda.py pulls the NVIDIA backend's ops into the ROCm path, and its block-scale replication would change AMD loading behavior.

self.in_proj_qkvgfab.weight.weight_loader_numel = (
local_output_size * self.hidden_size
)

self.f_b_proj = ColumnParallelLinear(
self.head_dim,
Expand Down
Loading