|
33 | 33 | DeviceMemoryResource, |
34 | 34 | DeviceMemoryResourceOptions, |
35 | 35 | GraphMemoryResource, |
| 36 | + LegacyPinnedMemoryResource, |
36 | 37 | ManagedMemoryResource, |
37 | 38 | ManagedMemoryResourceOptions, |
38 | 39 | MemoryResource, |
@@ -1760,3 +1761,136 @@ def test_top_level_namespace_excludes_known_leaks(): |
1760 | 1761 | public = {n for n in dir(cuda.core) if not n.startswith("_")} |
1761 | 1762 | leaked = {"StridedMemoryView", "args_viewable_as_strided_memory"} |
1762 | 1763 | assert not (public & leaked) |
| 1764 | + |
| 1765 | + |
| 1766 | +def test_legacy_pinned_allocate_zero_size(init_cuda): |
| 1767 | + """LegacyPinnedMemoryResource.allocate(0) skips the driver call and uses ptr=0.""" |
| 1768 | + mr = LegacyPinnedMemoryResource() |
| 1769 | + buf = mr.allocate(0) |
| 1770 | + assert buf.size == 0 |
| 1771 | + # No driver call was made; handle is the sentinel 0. |
| 1772 | + assert int(buf.handle) == 0 |
| 1773 | + |
| 1774 | + |
| 1775 | +def test_legacy_pinned_device_id_raises(): |
| 1776 | + """LegacyPinnedMemoryResource.device_id raises; pinned memory is not bound to a GPU.""" |
| 1777 | + mr = LegacyPinnedMemoryResource() |
| 1778 | + with pytest.raises(RuntimeError, match="not bound to any GPU"): |
| 1779 | + _ = mr.device_id |
| 1780 | + |
| 1781 | + |
| 1782 | +def test_synchronous_memory_resource_basic(init_cuda): |
| 1783 | + """_SynchronousMemoryResource exercises properties and allocate paths (zero, non-zero, with-stream).""" |
| 1784 | + from cuda.core._memory._legacy import _SynchronousMemoryResource |
| 1785 | + |
| 1786 | + dev = Device() |
| 1787 | + mr = _SynchronousMemoryResource(dev.device_id) |
| 1788 | + assert mr.is_device_accessible is True |
| 1789 | + assert mr.is_host_accessible is False |
| 1790 | + assert mr.device_id == dev.device_id |
| 1791 | + |
| 1792 | + # Zero-size allocation takes the ptr=0 fast path. |
| 1793 | + zero_buf = mr.allocate(0) |
| 1794 | + assert zero_buf.size == 0 |
| 1795 | + assert int(zero_buf.handle) == 0 |
| 1796 | + zero_buf.close(stream=None) |
| 1797 | + |
| 1798 | + # Non-zero allocation goes through cuMemAlloc; close with stream=None for |
| 1799 | + # the simple path. The explicit-stream close path is covered separately. |
| 1800 | + buf = mr.allocate(64) |
| 1801 | + assert buf.size == 64 |
| 1802 | + assert int(buf.handle) != 0 |
| 1803 | + buf.close(stream=None) |
| 1804 | + |
| 1805 | + # allocate(size, stream=stream) exercises Stream_accept validation on the |
| 1806 | + # allocate side (cuMemAlloc is synchronous so the stream is accepted but unused). |
| 1807 | + stream = dev.create_stream() |
| 1808 | + buf2 = mr.allocate(32, stream=stream) |
| 1809 | + assert buf2.size == 32 |
| 1810 | + assert int(buf2.handle) != 0 |
| 1811 | + buf2.close(stream=None) |
| 1812 | + stream.close() |
| 1813 | + |
| 1814 | + |
| 1815 | +def test_synchronous_memory_resource_deallocate_accepts_stream(init_cuda): |
| 1816 | + """_SynchronousMemoryResource.deallocate accepts an explicit stream.""" |
| 1817 | + from cuda.core._memory._legacy import _SynchronousMemoryResource |
| 1818 | + |
| 1819 | + dev = Device() |
| 1820 | + mr = _SynchronousMemoryResource(dev.device_id) |
| 1821 | + buf = mr.allocate(64) |
| 1822 | + stream = dev.create_stream() |
| 1823 | + buf.close(stream=stream) |
| 1824 | + stream.close() |
| 1825 | + |
| 1826 | + |
| 1827 | +@pytest.mark.parametrize( |
| 1828 | + ("method", "spec", "match"), |
| 1829 | + [ |
| 1830 | + ("_access_to_flags", "bogus", "Unknown access spec"), |
| 1831 | + ("_allocation_type_to_driver", "bogus", "Unsupported allocation_type"), |
| 1832 | + ("_location_type_to_driver", "bogus", "Unsupported location_type"), |
| 1833 | + ("_handle_type_to_driver", "bogus", "Unsupported handle_type"), |
| 1834 | + ("_granularity_to_driver", "bogus", "Unsupported granularity"), |
| 1835 | + ], |
| 1836 | +) |
| 1837 | +def test_vmm_options_spec_validators_raise(method, spec, match): |
| 1838 | + """Every VMM spec validator static method rejects unknown strings with ValueError.""" |
| 1839 | + fn = getattr(VirtualMemoryResourceOptions, method) |
| 1840 | + with pytest.raises(ValueError, match=match): |
| 1841 | + fn(spec) |
| 1842 | + |
| 1843 | + |
| 1844 | +def test_vmm_options_handle_type_win32_raises(): |
| 1845 | + """_handle_type_to_driver raises NotImplementedError for 'win32'.""" |
| 1846 | + with pytest.raises(NotImplementedError, match="win32 is currently not supported"): |
| 1847 | + VirtualMemoryResourceOptions._handle_type_to_driver("win32") |
| 1848 | + |
| 1849 | + |
| 1850 | +def test_device_memory_resource_peer_accessible_by_non_owned(mempool_device): |
| 1851 | + """peer_accessible_by on a non-owned (default) DMR queries the driver live.""" |
| 1852 | + dev = mempool_device |
| 1853 | + # The default DeviceMemoryResource(device) wraps the current device's |
| 1854 | + # default pool, i.e. _mempool_owned is False, so accessing |
| 1855 | + # peer_accessible_by exercises the live _DMR_query_peer_access path. |
| 1856 | + mr = DeviceMemoryResource(dev) |
| 1857 | + peers = mr.peer_accessible_by |
| 1858 | + assert all(isinstance(p, Device) for p in peers) |
| 1859 | + # __contains__ accepts int dev_ids; the owning device is never a peer. |
| 1860 | + assert dev.device_id not in peers |
| 1861 | + |
| 1862 | + |
| 1863 | +def test_dmr_mempool_get_access_self(mempool_device): |
| 1864 | + """DMR_mempool_get_access returns 'rw' when querying the owning device itself.""" |
| 1865 | + from cuda.core._memory._device_memory_resource import DMR_mempool_get_access |
| 1866 | + |
| 1867 | + mr = DeviceMemoryResource(mempool_device) |
| 1868 | + # The owning device always has read-write access to its own pool. |
| 1869 | + assert DMR_mempool_get_access(mr, mempool_device.device_id) == "rw" |
| 1870 | + |
| 1871 | + |
| 1872 | +def test_dmr_mempool_get_access_peer(mempool_device_x2): |
| 1873 | + """DMR_mempool_get_access reflects peer access state for a different device.""" |
| 1874 | + from cuda.core._memory._device_memory_resource import DMR_mempool_get_access |
| 1875 | + |
| 1876 | + dev, peer = mempool_device_x2 |
| 1877 | + # Use an owned pool so peer-access state isn't contaminated by other tests |
| 1878 | + # that may have set access on the device's default pool. |
| 1879 | + mr = DeviceMemoryResource(dev, DeviceMemoryResourceOptions()) |
| 1880 | + |
| 1881 | + # Fresh owned pool: peer has no access. |
| 1882 | + assert DMR_mempool_get_access(mr, peer.device_id) == "" |
| 1883 | + # After granting access, peer has read-write. |
| 1884 | + mr.peer_accessible_by = [peer] |
| 1885 | + assert DMR_mempool_get_access(mr, peer.device_id) == "rw" |
| 1886 | + # After revoking, peer is back to no access. |
| 1887 | + mr.peer_accessible_by = [] |
| 1888 | + assert DMR_mempool_get_access(mr, peer.device_id) == "" |
| 1889 | + |
| 1890 | + |
| 1891 | +def test_dmr_peer_accessible_by_setter_empty(mempool_device): |
| 1892 | + """Assigning an empty peer-access set to a fresh owned pool is a no-op.""" |
| 1893 | + mr = DeviceMemoryResource(mempool_device, options=DeviceMemoryResourceOptions()) |
| 1894 | + assert set(mr.peer_accessible_by) == set() |
| 1895 | + mr.peer_accessible_by = [] |
| 1896 | + assert set(mr.peer_accessible_by) == set() |
0 commit comments