mirror of
https://github.com/PaddlePaddle/FastDeploy.git
synced 2026-04-23 00:17:25 +08:00
edd31e8849
* add * [tests] Add Paddle attention determinism tests and refactor resource manager Add comprehensive determinism tests for Paddle attention layer and refactor resource manager for deterministic mode support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * add * add * add * add * add more * add more * fixsome * fixsome * fix bugs * fix bugs * only in gpu * add docs * fix comments * fix some * fix some * fix comments * add more * fix potential problem * remove not need * remove not need * remove no need * fix bug * fix bugs * fix comments * fix comments * Update tests/ce/deterministic/test_determinism_verification.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/inter_communicator/test_ipc_signal.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/layers/test_paddle_attention_determinism.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/engine/test_sampling_params_determinism.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/layers/test_paddle_attention_determinism.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/layers/test_paddle_attention_determinism_standalone.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix comments * fix import error * fix a bug * fix bugs * fix bugs * fix coverage * refine codes * refine code * fix comments * fix comments * fix comments * rm not need * fix allreduce large tensor bug * mv log files * mv log files * add files --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
346 lines
12 KiB
Python
346 lines
12 KiB
Python
"""
|
|
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# 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.
|
|
"""
|
|
|
|
import time
|
|
import unittest
|
|
from multiprocessing.shared_memory import SharedMemory
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from fastdeploy.inter_communicator.ipc_signal import IPCSignal, shared_memory_exists
|
|
|
|
|
|
class TestSharedMemoryExists(unittest.TestCase):
|
|
"""Test cases for shared_memory_exists function."""
|
|
|
|
def test_returns_false_for_nonexistent_memory(self):
|
|
"""Test that shared_memory_exists returns False for non-existent shared memory."""
|
|
result = shared_memory_exists(f"nonexistent_shm_{time.time()}")
|
|
self.assertFalse(result)
|
|
|
|
def test_returns_true_for_existing_memory(self):
|
|
"""Test that shared_memory_exists returns True for existing shared memory."""
|
|
name = f"test_shm_{time.time()}"
|
|
shm = SharedMemory(name=name, create=True, size=1024)
|
|
try:
|
|
result = shared_memory_exists(name)
|
|
self.assertTrue(result)
|
|
finally:
|
|
try:
|
|
shm.close()
|
|
shm.unlink()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"dtype,shape,initial_value",
|
|
[
|
|
(np.int32, (10,), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
|
(np.float32, (5,), [0.0, 1.5, 2.5, 3.5, 4.5]),
|
|
(np.int64, (3, 3), [[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
|
|
(np.uint8, (4,), [0, 127, 200, 255]),
|
|
],
|
|
)
|
|
def test_ipc_signal_create_with_array(dtype, shape, initial_value):
|
|
"""Test IPCSignal creation with numpy array."""
|
|
name = f"test_ipc_signal_{time.time()}"
|
|
array = np.array(initial_value, dtype=dtype)
|
|
|
|
signal = IPCSignal(name=name, array=array, dtype=dtype, create=True)
|
|
try:
|
|
# Verify value is initialized correctly
|
|
np.testing.assert_array_equal(signal.value, array)
|
|
np.testing.assert_equal(signal.value.dtype, dtype)
|
|
|
|
# Verify shared memory exists
|
|
assert shared_memory_exists(name)
|
|
finally:
|
|
try:
|
|
signal.clear()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
class TestIPCSignal(unittest.TestCase):
|
|
"""Test cases for IPCSignal class."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.test_name_base = f"test_ipc_signal_{time.time()}"
|
|
self._signals_to_clean = []
|
|
|
|
def tearDown(self):
|
|
"""Clean up all tracked signals."""
|
|
for signal in self._signals_to_clean:
|
|
try:
|
|
signal.clear()
|
|
except Exception:
|
|
pass
|
|
|
|
def _track(self, signal):
|
|
"""Register a signal for automatic cleanup in tearDown."""
|
|
self._signals_to_clean.append(signal)
|
|
return signal
|
|
|
|
def test_create_with_suffix(self):
|
|
"""Test IPCSignal creation with suffix."""
|
|
name = self.test_name_base
|
|
suffix = 123
|
|
|
|
array = np.array([1, 2, 3], dtype=np.int32)
|
|
signal = self._track(IPCSignal(name=name, array=array, dtype=np.int32, suffix=suffix, create=True))
|
|
|
|
expected_name = f"{name}.{suffix}"
|
|
self.assertTrue(shared_memory_exists(expected_name))
|
|
np.testing.assert_array_equal(signal.value, array)
|
|
|
|
def test_attach_to_existing(self):
|
|
"""Test IPCSignal attaching to existing shared memory."""
|
|
name = f"{self.test_name_base}_attach"
|
|
array = np.array([10, 20, 30], dtype=np.int64)
|
|
|
|
# Create shared memory
|
|
signal1 = self._track(IPCSignal(name=name, array=array, dtype=np.int64, create=True))
|
|
signal1.value[0] = 99 # Modify value
|
|
|
|
# Attach to existing
|
|
signal2 = IPCSignal(name=name, array=array, dtype=np.int64, create=False)
|
|
|
|
# Verify value is shared
|
|
self.assertEqual(signal2.value[0], 99)
|
|
np.testing.assert_array_equal(signal2.value, signal1.value)
|
|
|
|
def test_dtype_mismatch_raises_assertion(self):
|
|
"""Test that dtype mismatch raises AssertionError."""
|
|
name = f"{self.test_name_base}_mismatch"
|
|
array = np.array([1, 2, 3], dtype=np.int32)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
IPCSignal(name=name, array=array, dtype=np.float32, create=True)
|
|
|
|
def test_non_numpy_array_raises_assertion(self):
|
|
"""Test that non-numpy array raises AssertionError."""
|
|
name = f"{self.test_name_base}_non_array"
|
|
|
|
with self.assertRaises(AssertionError):
|
|
IPCSignal(name=name, array=[1, 2, 3], dtype=np.int32, create=True)
|
|
|
|
def test_create_with_shm_size(self):
|
|
"""Test IPCSignal creation with shm_size (no array)."""
|
|
name = f"{self.test_name_base}_size"
|
|
|
|
signal = self._track(IPCSignal(name=name, shm_size=1024, create=True))
|
|
|
|
# Verify signal is created but value is None (no array template)
|
|
self.assertTrue(shared_memory_exists(name))
|
|
self.assertIsNone(signal.value)
|
|
|
|
def test_attach_with_shm_size(self):
|
|
"""Test IPCSignal attach with shm_size (no array)."""
|
|
name = f"{self.test_name_base}_attach_size"
|
|
|
|
# Create
|
|
self._track(IPCSignal(name=name, shm_size=512, create=True))
|
|
|
|
# Attach
|
|
signal2 = IPCSignal(name=name, shm_size=512, create=False)
|
|
|
|
self.assertTrue(shared_memory_exists(name))
|
|
self.assertIsNone(signal2.value)
|
|
|
|
def test_shm_size_required_without_array_and_dtype(self):
|
|
"""Test that shm_size is required when array and dtype are None."""
|
|
name = f"{self.test_name_base}_no_size"
|
|
|
|
with self.assertRaises(AssertionError):
|
|
IPCSignal(name=name, create=True)
|
|
|
|
def test_clear_removes_shared_memory(self):
|
|
"""Test that clear() properly removes shared memory."""
|
|
name = f"{self.test_name_base}_clear"
|
|
array = np.array([1, 2, 3], dtype=np.int32)
|
|
|
|
signal = IPCSignal(name=name, array=array, dtype=np.int32, create=True)
|
|
self.assertTrue(shared_memory_exists(name))
|
|
|
|
signal.clear()
|
|
self.assertFalse(shared_memory_exists(name))
|
|
|
|
def test_clear_idempotent(self):
|
|
"""Test that clear() can be called multiple times safely."""
|
|
name = f"{self.test_name_base}_idempotent"
|
|
array = np.array([1, 2, 3], dtype=np.int32)
|
|
|
|
signal = IPCSignal(name=name, array=array, dtype=np.int32, create=True)
|
|
|
|
# Should not raise exception
|
|
signal.clear()
|
|
signal.clear() # Call again
|
|
|
|
def test_value_sharing_between_processes_mock(self):
|
|
"""Test that value is shared (mocked for unit test)."""
|
|
name = f"{self.test_name_base}_shared"
|
|
array = np.array([100, 200, 300], dtype=np.int64)
|
|
|
|
signal1 = self._track(IPCSignal(name=name, array=array, dtype=np.int64, create=True))
|
|
signal2 = IPCSignal(name=name, array=array, dtype=np.int64, create=False)
|
|
|
|
# Modify through signal1
|
|
signal1.value[0] = 999
|
|
signal1.value[1] = 888
|
|
signal1.value[2] = 777
|
|
|
|
# Verify signal2 sees changes
|
|
self.assertEqual(signal2.value[0], 999)
|
|
self.assertEqual(signal2.value[1], 888)
|
|
self.assertEqual(signal2.value[2], 777)
|
|
|
|
def test_multiple_array_creation_replaces_existing(self):
|
|
"""Test that creating with same name replaces existing shared memory."""
|
|
name = f"{self.test_name_base}_replace"
|
|
array1 = np.array([1, 2, 3], dtype=np.int32)
|
|
array2 = np.array([4, 5, 6], dtype=np.int32)
|
|
|
|
signal1 = IPCSignal(name=name, array=array1, dtype=np.int32, create=True)
|
|
signal1.clear()
|
|
|
|
signal2 = self._track(IPCSignal(name=name, array=array2, dtype=np.int32, create=True))
|
|
|
|
np.testing.assert_array_equal(signal2.value, array2)
|
|
|
|
def test_clear_closes_and_unlinks(self):
|
|
"""Test that clear() both closes and unlinks the shared memory."""
|
|
name = f"{self.test_name_base}_unlink"
|
|
array = np.array([1, 2, 3], dtype=np.int32)
|
|
|
|
signal = IPCSignal(name=name, array=array, dtype=np.int32, create=True)
|
|
|
|
# After clear, the shared memory should be removed
|
|
signal.clear()
|
|
self.assertFalse(shared_memory_exists(name))
|
|
|
|
# Attempting to attach should fail
|
|
try:
|
|
_ = SharedMemory(name=name, create=False)
|
|
self.fail("Should have raised FileNotFoundError")
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
def test_raw_buffer_read_write_with_shm_size(self):
|
|
"""Test raw buffer read/write in shm_size mode."""
|
|
name = f"{self.test_name_base}_raw_buf"
|
|
data = b"hello ipc signal"
|
|
|
|
signal1 = self._track(IPCSignal(name=name, shm_size=1024, create=True))
|
|
signal1.shm.buf[: len(data)] = data
|
|
|
|
signal2 = IPCSignal(name=name, shm_size=1024, create=False)
|
|
self.assertEqual(bytes(signal2.shm.buf[: len(data)]), data)
|
|
|
|
def test_create_overwrites_existing_without_clear(self):
|
|
"""Test that create=True on existing name auto-unlinks and recreates."""
|
|
name = f"{self.test_name_base}_overwrite"
|
|
array1 = np.array([1, 2, 3], dtype=np.int32)
|
|
array2 = np.array([7, 8, 9], dtype=np.int32)
|
|
|
|
# Create first signal, do NOT clear it
|
|
IPCSignal(name=name, array=array1, dtype=np.int32, create=True)
|
|
|
|
# Create again with same name — should auto-unlink old and recreate
|
|
signal2 = self._track(IPCSignal(name=name, array=array2, dtype=np.int32, create=True))
|
|
np.testing.assert_array_equal(signal2.value, array2)
|
|
|
|
def test_attach_nonexistent_raises_error(self):
|
|
"""Test that create=False on non-existent shm raises FileNotFoundError."""
|
|
name = f"nonexistent_signal_{time.time()}"
|
|
array = np.array([1, 2, 3], dtype=np.int32)
|
|
|
|
with self.assertRaises(FileNotFoundError):
|
|
IPCSignal(name=name, array=array, dtype=np.int32, create=False)
|
|
|
|
|
|
class TestIPCSignalEdgeCases(unittest.TestCase):
|
|
"""Test edge cases for IPCSignal."""
|
|
|
|
def test_empty_array_raises_error(self):
|
|
"""Test IPCSignal with empty array raises ValueError due to nbytes=0."""
|
|
name = f"test_empty_array_{time.time()}"
|
|
array = np.array([], dtype=np.int32)
|
|
|
|
with self.assertRaises(ValueError):
|
|
IPCSignal(name=name, array=array, dtype=np.int32, create=True)
|
|
|
|
def test_large_array(self):
|
|
"""Test IPCSignal with large array."""
|
|
name = f"test_large_array_{time.time()}"
|
|
size = 10000
|
|
array = np.arange(size, dtype=np.int64)
|
|
|
|
signal = IPCSignal(name=name, array=array, dtype=np.int64, create=True)
|
|
try:
|
|
np.testing.assert_array_equal(signal.value, array)
|
|
finally:
|
|
try:
|
|
signal.clear()
|
|
except Exception:
|
|
pass
|
|
|
|
def test_multidimensional_array(self):
|
|
"""Test IPCSignal with multidimensional array."""
|
|
name = f"test_multi_array_{time.time()}"
|
|
array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.int32)
|
|
|
|
signal = IPCSignal(name=name, array=array, dtype=np.int32, create=True)
|
|
try:
|
|
self.assertEqual(signal.value.shape, (3, 3))
|
|
np.testing.assert_array_equal(signal.value, array)
|
|
finally:
|
|
try:
|
|
signal.clear()
|
|
except Exception:
|
|
pass
|
|
|
|
def test_different_numeric_types(self):
|
|
"""Test IPCSignal with different numeric types."""
|
|
name_base = f"test_types_{time.time()}"
|
|
|
|
test_cases = [
|
|
(np.int8, [1, 2, 3]),
|
|
(np.int16, [1000, 2000, 3000]),
|
|
(np.int32, [100000, 200000, 300000]),
|
|
(np.int64, [1000000000, 2000000000, 3000000000]),
|
|
(np.float32, [1.5, 2.5, 3.5]),
|
|
(np.float64, [1.123456789, 2.987654321, 3.5]),
|
|
]
|
|
|
|
for i, (dtype, values) in enumerate(test_cases):
|
|
name = f"{name_base}_{i}"
|
|
array = np.array(values, dtype=dtype)
|
|
signal = IPCSignal(name=name, array=array, dtype=dtype, create=True)
|
|
try:
|
|
np.testing.assert_array_equal(signal.value, array)
|
|
finally:
|
|
try:
|
|
signal.clear()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|