Implement LRU eviction policy for LoRA adapters (#11041)
This commit is contained in:
190
test/srt/lora/test_lora_eviction_policy.py
Normal file
190
test/srt/lora/test_lora_eviction_policy.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
|
||||
"""
|
||||
Unit tests for LoRA eviction policies.
|
||||
Tests LRU and FIFO eviction behavior.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from sglang.srt.lora.eviction_policy import get_eviction_policy
|
||||
|
||||
|
||||
class TestLoRAEvictionPolicy(unittest.TestCase):
|
||||
"""Unit tests for LoRA eviction policies."""
|
||||
|
||||
def _test_eviction_policy(
|
||||
self, policy_name, access_sequence, candidates, expected_victim
|
||||
):
|
||||
"""
|
||||
Helper to test eviction policy with given access pattern.
|
||||
|
||||
Args:
|
||||
policy_name: Name of eviction policy ("lru" or "fifo")
|
||||
access_sequence: List of adapter IDs in access order
|
||||
candidates: Set of adapter IDs that can be evicted
|
||||
expected_victim: Expected adapter ID to be evicted
|
||||
"""
|
||||
policy = get_eviction_policy(policy_name)
|
||||
|
||||
# Simulate access pattern
|
||||
for adapter_id in access_sequence:
|
||||
policy.mark_used(adapter_id)
|
||||
|
||||
# Select victim from candidates
|
||||
victim = policy.select_victim(candidates)
|
||||
self.assertEqual(
|
||||
victim,
|
||||
expected_victim,
|
||||
f"{policy_name.upper()}: Expected {expected_victim}, got {victim}",
|
||||
)
|
||||
|
||||
def test_lru_basic(self):
|
||||
"""Test LRU selects least recently used adapter."""
|
||||
self._test_eviction_policy(
|
||||
"lru",
|
||||
access_sequence=["lora1", "lora2", "lora3", "lora4"],
|
||||
candidates={"lora1", "lora2", "lora3", "lora4"},
|
||||
expected_victim="lora1",
|
||||
)
|
||||
|
||||
def test_lru_with_reuse(self):
|
||||
"""Test LRU updates order on reuse."""
|
||||
self._test_eviction_policy(
|
||||
"lru",
|
||||
access_sequence=["lora1", "lora2", "lora3", "lora4", "lora1"],
|
||||
candidates={"lora1", "lora2", "lora3", "lora4"},
|
||||
expected_victim="lora2",
|
||||
)
|
||||
|
||||
def test_lru_multiple_reuse(self):
|
||||
"""Test LRU with multiple reuses."""
|
||||
self._test_eviction_policy(
|
||||
"lru",
|
||||
access_sequence=["lora1", "lora2", "lora3", "lora1", "lora2"],
|
||||
candidates={"lora1", "lora2", "lora3"},
|
||||
expected_victim="lora3",
|
||||
)
|
||||
|
||||
def test_lru_with_subset_candidates(self):
|
||||
"""Test LRU with subset of candidates."""
|
||||
self._test_eviction_policy(
|
||||
"lru",
|
||||
access_sequence=["lora1", "lora2", "lora3", "lora4"],
|
||||
candidates={"lora2", "lora3", "lora4"},
|
||||
expected_victim="lora2",
|
||||
)
|
||||
|
||||
def test_lru_base_model_priority(self):
|
||||
"""Test LRU prioritizes base model for eviction."""
|
||||
self._test_eviction_policy(
|
||||
"lru",
|
||||
access_sequence=["lora1", "lora2", "lora3"],
|
||||
candidates={None, "lora1", "lora2", "lora3"},
|
||||
expected_victim=None,
|
||||
)
|
||||
|
||||
def test_fifo_basic(self):
|
||||
"""Test FIFO selects first inserted adapter."""
|
||||
self._test_eviction_policy(
|
||||
"fifo",
|
||||
access_sequence=["lora1", "lora2", "lora3", "lora4"],
|
||||
candidates={"lora1", "lora2", "lora3", "lora4"},
|
||||
expected_victim="lora1",
|
||||
)
|
||||
|
||||
def test_fifo_ignores_reuse(self):
|
||||
"""Test FIFO ignores reuse."""
|
||||
self._test_eviction_policy(
|
||||
"fifo",
|
||||
access_sequence=[
|
||||
"lora1",
|
||||
"lora2",
|
||||
"lora3",
|
||||
"lora4",
|
||||
"lora4",
|
||||
"lora3",
|
||||
"lora2",
|
||||
"lora1",
|
||||
],
|
||||
candidates={"lora1", "lora2", "lora3", "lora4"},
|
||||
expected_victim="lora1",
|
||||
)
|
||||
|
||||
def test_fifo_with_subset_candidates(self):
|
||||
"""Test FIFO with subset of candidates."""
|
||||
self._test_eviction_policy(
|
||||
"fifo",
|
||||
access_sequence=["lora1", "lora2", "lora3", "lora4"],
|
||||
candidates={"lora2", "lora3", "lora4"},
|
||||
expected_victim="lora2",
|
||||
)
|
||||
|
||||
def test_fifo_base_model_priority(self):
|
||||
"""Test FIFO prioritizes base model for eviction."""
|
||||
self._test_eviction_policy(
|
||||
"fifo",
|
||||
access_sequence=["lora1", "lora2", "lora3"],
|
||||
candidates={None, "lora1", "lora2", "lora3"},
|
||||
expected_victim=None,
|
||||
)
|
||||
|
||||
def test_policy_remove(self):
|
||||
"""Test that remove() correctly updates internal state."""
|
||||
lru = get_eviction_policy("lru")
|
||||
lru.mark_used("lora1")
|
||||
lru.mark_used("lora2")
|
||||
lru.mark_used("lora3")
|
||||
|
||||
# Remove lora1, so lora2 becomes LRU
|
||||
lru.remove("lora1")
|
||||
victim = lru.select_victim({"lora1", "lora2", "lora3"})
|
||||
self.assertEqual(victim, "lora2")
|
||||
|
||||
def test_eviction_policy_factory(self):
|
||||
"""Test eviction policy factory function."""
|
||||
# Test valid policies
|
||||
lru = get_eviction_policy("lru")
|
||||
fifo = get_eviction_policy("fifo")
|
||||
|
||||
self.assertIsNotNone(lru)
|
||||
self.assertIsNotNone(fifo)
|
||||
|
||||
# Test invalid policy
|
||||
with self.assertRaises(ValueError):
|
||||
get_eviction_policy("invalid_policy")
|
||||
|
||||
def test_lru_vs_fifo_behavior(self):
|
||||
"""Test that LRU and FIFO behave differently."""
|
||||
access_sequence = ["lora1", "lora2", "lora3", "lora1"]
|
||||
candidates = {"lora1", "lora2", "lora3"}
|
||||
|
||||
lru = get_eviction_policy("lru")
|
||||
for adapter_id in access_sequence:
|
||||
lru.mark_used(adapter_id)
|
||||
lru_victim = lru.select_victim(candidates)
|
||||
|
||||
fifo = get_eviction_policy("fifo")
|
||||
for adapter_id in access_sequence:
|
||||
fifo.mark_used(adapter_id)
|
||||
fifo_victim = fifo.select_victim(candidates)
|
||||
|
||||
self.assertNotEqual(lru_victim, fifo_victim)
|
||||
self.assertEqual(lru_victim, "lora2")
|
||||
self.assertEqual(fifo_victim, "lora1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -20,6 +20,8 @@ suites = {
|
||||
TestFile("hicache/test_hicache_mla.py", 127),
|
||||
TestFile("hicache/test_hicache_storage.py", 127),
|
||||
TestFile("lora/test_lora.py", 200),
|
||||
TestFile("lora/test_lora_eviction.py", 200),
|
||||
TestFile("lora/test_lora_eviction_policy.py", 200),
|
||||
TestFile("lora/test_lora_backend.py", 99),
|
||||
TestFile("lora/test_lora_eviction.py", 200),
|
||||
TestFile("lora/test_lora_qwen3.py", 97),
|
||||
|
||||
Reference in New Issue
Block a user