Skip to content

Commit 8794e01

Browse files
committed
Add more test cases
Signed-off-by: Hollow Man <hollowman@opensuse.org>
1 parent 19f77c9 commit 8794e01

3 files changed

Lines changed: 180 additions & 1 deletion

File tree

nemo_rl/utils/weight_transfer_delta_tracker.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __init__(self, config: Mapping[str, Any]) -> None:
9494
self._baseline_ready_events: dict[str, torch.cuda.Event] = {}
9595

9696
def _baseline_dtype(self, dtype: torch.dtype) -> torch.dtype:
97-
return self.delta_dtype if dtype.is_floating_point else dtype
97+
return dtype
9898

9999
def should_prewarm_baseline(self) -> bool:
100100
return (
@@ -272,6 +272,7 @@ def _make_per_tensor_deltas(self, tensors: TensorBatch) -> TensorBatch:
272272
)
273273
baseline = self.baseline[name].to(
274274
device=tensor.device,
275+
dtype=self.delta_dtype,
275276
non_blocking=tensor.is_cuda,
276277
copy=True,
277278
)

tests/unit/models/generation/test_registry.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import importlib
1616
import sys
1717

18+
import pytest
19+
1820
from nemo_rl.models.generation import registry as registry_mod
1921
from nemo_rl.models.generation.constants import SGLANG_BACKEND, VLLM_BACKEND
2022

@@ -26,3 +28,31 @@ def test_builtin_registration_does_not_import_sglang():
2628

2729
assert set(registry.get_registered_backends()) == {VLLM_BACKEND, SGLANG_BACKEND}
2830
assert "nemo_rl.models.generation.sglang" not in sys.modules
31+
32+
33+
def test_custom_generation_backend_factory_is_used():
34+
registry = importlib.reload(registry_mod)
35+
created = object()
36+
37+
def custom_factory(cluster, config):
38+
assert cluster == "cluster"
39+
assert config == {"name": "custom"}
40+
return created
41+
42+
registry.register_generation_backend("custom", custom_factory)
43+
44+
assert (
45+
registry.create_generation(
46+
"custom",
47+
cluster="cluster",
48+
config={"name": "custom"},
49+
)
50+
is created
51+
)
52+
53+
54+
def test_unknown_generation_backend_lists_registered_backends():
55+
registry = importlib.reload(registry_mod)
56+
57+
with pytest.raises(ValueError, match="Registered backends"):
58+
registry.create_generation("missing", cluster=None, config={})

tests/unit/utils/test_weight_transfer.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,21 @@ def test_empty_transfer_header_uses_single_control_broadcast():
305305
assert len(group.broadcasted_tensors) == 1
306306

307307

308+
def test_protocol_rejects_invalid_header_dtype_and_env(monkeypatch):
309+
with pytest.raises(ValueError, match="header control"):
310+
protocol_mod.decode_header_control(torch.tensor([99, 0, 0, 0]))
311+
312+
with pytest.raises(ValueError, match="header control"):
313+
protocol_mod.decode_header_control(torch.tensor([1, 99, 0, 0]))
314+
315+
with pytest.raises(ValueError, match="Unsupported tensor dtype"):
316+
protocol_mod.dtype_from_name("complex64")
317+
318+
monkeypatch.setenv("NRL_REFIT_BASELINE_PREWARM_CHUNK_BYTES", "not-an-int")
319+
with pytest.raises(ValueError, match="Expected integer value"):
320+
protocol_mod.env_int("NRL_REFIT_BASELINE_PREWARM_CHUNK_BYTES", default=1)
321+
322+
308323
def test_sparse_indices_encoder_coalesces_small_tensors(monkeypatch):
309324
monkeypatch.setenv("NRL_REFIT_SPARSE_ENCODE_COALESCE_BYTES", "1024")
310325
tensors = [
@@ -604,6 +619,42 @@ def test_packed_weight_transfer_non_floating_chunk_uses_full_update():
604619
assert torch.equal(receiver_state[name], tensor)
605620

606621

622+
def test_packed_weight_transfer_sparse_encoding_unavailable_uses_full_update(
623+
monkeypatch,
624+
):
625+
tracker = DeltaCompressionTracker(_tracker_config())
626+
receiver_state = {}
627+
counters = {"full": 0, "delta": 0}
628+
initial = [("linear.weight", torch.zeros(4))]
629+
updated = [("linear.weight", torch.ones(4))]
630+
631+
load_full, load_delta = _state_loaders(receiver_state, counters=counters)
632+
633+
with _cpu_transfer_env():
634+
_run_transfer(
635+
initial,
636+
tracker=tracker,
637+
load_full=load_full,
638+
load_delta=load_delta,
639+
delta_load_batch_size_bytes=1024,
640+
)
641+
monkeypatch.setattr(
642+
sparse_codec_mod,
643+
"encode_sparse_indices",
644+
Mock(side_effect=sparse_codec_mod.SparseEncodingUnavailable),
645+
)
646+
_run_transfer(
647+
updated,
648+
tracker=tracker,
649+
load_full=load_full,
650+
load_delta=load_delta,
651+
delta_load_batch_size_bytes=1024,
652+
)
653+
654+
assert counters == {"full": 2, "delta": 0}
655+
assert torch.equal(receiver_state["linear.weight"], updated[0][1])
656+
657+
607658
def test_consumer_flushes_async_sparse_decode_before_full_update():
608659
group = RecordingGroup()
609660
sparse_delta = torch.zeros(1024)
@@ -732,6 +783,17 @@ def test_full_sync_interval_one_does_not_create_baseline():
732783
assert tracker.baseline == {}
733784

734785

786+
def test_delta_tracker_rejects_invalid_config():
787+
with pytest.raises(ValueError, match="Unsupported delta compression dtype"):
788+
DeltaCompressionTracker({**_tracker_config(), "dtype": "float64"})
789+
790+
with pytest.raises(ValueError, match="full_sync_interval"):
791+
DeltaCompressionTracker(_tracker_config(full_sync_interval=0))
792+
793+
with pytest.raises(ValueError, match="sparse_bucket_size_bytes"):
794+
DeltaCompressionTracker(_tracker_config(sparse_bucket_size_bytes=0))
795+
796+
735797
def test_delta_baseline_prewarm_reuses_allocations(monkeypatch):
736798
monkeypatch.setenv("NRL_REFIT_BASELINE_PREWARM_CHUNK_BYTES", "32")
737799
tracker = DeltaCompressionTracker(_tracker_config())
@@ -748,6 +810,8 @@ def test_delta_baseline_prewarm_reuses_allocations(monkeypatch):
748810
assert tracker.baseline["linear.weight"].shape == torch.Size([2, 4])
749811
assert tracker.baseline["linear.bias"].dtype == torch.float32
750812
assert tracker.baseline["embed.weight"].dtype == torch.float16
813+
assert tracker._baseline_entries["linear.weight"].arena.dtype == torch.float32
814+
assert tracker._baseline_entries["embed.weight"].arena.dtype == torch.float16
751815

752816
tensors = [
753817
(name, torch.ones(tuple(shape), dtype=dtype))
@@ -762,6 +826,43 @@ def test_delta_baseline_prewarm_reuses_allocations(monkeypatch):
762826
)
763827

764828

829+
def test_delta_baseline_prewarm_can_be_disabled(monkeypatch):
830+
monkeypatch.setenv("NRL_REFIT_PREWARM_DELTA_BASELINE", "0")
831+
tracker = DeltaCompressionTracker(_tracker_config())
832+
833+
with _cpu_transfer_env():
834+
tracker.prewarm_baseline_from_metadata(
835+
{"linear.weight": (torch.Size([4]), torch.float32)}
836+
)
837+
838+
assert tracker.baseline == {}
839+
assert tracker._baseline_entries == {}
840+
841+
842+
def test_delta_baseline_keeps_source_dtype_but_emits_delta_dtype():
843+
tracker = DeltaCompressionTracker(_tracker_config())
844+
initial = [
845+
("linear.weight", torch.tensor([1.0, 2.0], dtype=torch.float16)),
846+
]
847+
updated = [
848+
("linear.weight", torch.tensor([1.5, 1.0], dtype=torch.float16)),
849+
]
850+
851+
with _cpu_transfer_env():
852+
first_is_delta, _ = tracker.prepare_chunk(initial)
853+
tracker.on_sync_succeeded()
854+
is_delta, deltas = tracker.prepare_chunk(updated)
855+
856+
assert not first_is_delta
857+
assert tracker.baseline["linear.weight"].dtype == torch.float16
858+
assert is_delta
859+
assert deltas[0][1].dtype == torch.float32
860+
torch.testing.assert_close(
861+
deltas[0][1],
862+
torch.tensor([0.5, -1.0], dtype=torch.float32),
863+
)
864+
865+
765866
def test_delta_baseline_records_contiguous_entries(monkeypatch):
766867
monkeypatch.setenv("NRL_REFIT_BASELINE_PREWARM_CHUNK_BYTES", "1024")
767868
tracker = DeltaCompressionTracker(_tracker_config())
@@ -1093,6 +1194,41 @@ def test_vllm_delta_transfer_config_rejects_colocated_mode():
10931194
create_vllm_delta_transfer_tracker(generation_config)
10941195

10951196

1197+
def test_vllm_delta_transfer_config_returns_none_when_disabled():
1198+
assert create_vllm_delta_transfer_tracker(None) is None
1199+
assert create_vllm_delta_transfer_tracker({}) is None
1200+
assert (
1201+
create_vllm_delta_transfer_tracker({"delta_compression": {"enabled": False}})
1202+
is None
1203+
)
1204+
1205+
1206+
def test_vllm_delta_transfer_config_rejects_non_vllm_backend():
1207+
generation_config = {
1208+
"backend": "sglang",
1209+
"vllm_cfg": {"precision": "bfloat16"},
1210+
"colocated": {"enabled": False},
1211+
"delta_compression": _tracker_config(),
1212+
}
1213+
1214+
with pytest.raises(ValueError, match="vLLM"):
1215+
create_vllm_delta_transfer_tracker(generation_config)
1216+
1217+
1218+
def test_vllm_delta_transfer_config_creates_tracker_when_enabled():
1219+
generation_config = {
1220+
"backend": "vllm",
1221+
"vllm_cfg": {"precision": "bfloat16"},
1222+
"colocated": {"enabled": False},
1223+
"delta_compression": _tracker_config(),
1224+
}
1225+
1226+
tracker = create_vllm_delta_transfer_tracker(generation_config)
1227+
1228+
assert isinstance(tracker, DeltaCompressionTracker)
1229+
assert tracker.delta_dtype == torch.float32
1230+
1231+
10961232
def test_vllm_delta_transfer_config_rejects_quantized_modelopt_path():
10971233
generation_config = {
10981234
"backend": "vllm",
@@ -1104,3 +1240,15 @@ def test_vllm_delta_transfer_config_rejects_quantized_modelopt_path():
11041240

11051241
with pytest.raises(NotImplementedError, match="ModelOpt"):
11061242
create_vllm_delta_transfer_tracker(generation_config)
1243+
1244+
1245+
def test_vllm_delta_transfer_config_rejects_fp8_precision():
1246+
generation_config = {
1247+
"backend": "vllm",
1248+
"vllm_cfg": {"precision": "fp8"},
1249+
"colocated": {"enabled": False},
1250+
"delta_compression": _tracker_config(),
1251+
}
1252+
1253+
with pytest.raises(NotImplementedError, match="FP8"):
1254+
create_vllm_delta_transfer_tracker(generation_config)

0 commit comments

Comments
 (0)