Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEA] Allow setting *_pool_size with human-readable string #1670

Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -771,8 +771,8 @@ of 1 GiB and a maximum size of 4 GiB. The pool uses
>>> import rmm
>>> pool = rmm.mr.PoolMemoryResource(
... rmm.mr.CudaMemoryResource(),
... initial_pool_size=2**30,
... maximum_pool_size=2**32
... initial_pool_size="1GiB", # equivalent to initial_pool_size=2**30
... maximum_pool_size="4GiB"
... )
>>> rmm.mr.set_current_device_resource(pool)
```
Expand Down
8 changes: 4 additions & 4 deletions python/rmm/docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ of 1 GiB and a maximum size of 4 GiB. The pool uses
>>> import rmm
>>> pool = rmm.mr.PoolMemoryResource(
... rmm.mr.CudaMemoryResource(),
... initial_pool_size=2**30,
... maximum_pool_size=2**32
... initial_pool_size="1GiB", # equivalent to initial_pool_size=2**30
... maximum_pool_size="4GiB"
... )
>>> rmm.mr.set_current_device_resource(pool)
```
Expand All @@ -151,8 +151,8 @@ Similarly, to use a pool of managed memory:
>>> import rmm
>>> pool = rmm.mr.PoolMemoryResource(
... rmm.mr.ManagedMemoryResource(),
... initial_pool_size=2**30,
... maximum_pool_size=2**32
... initial_pool_size="1GiB",
... maximum_pool_size="4GiB"
... )
>>> rmm.mr.set_current_device_resource(pool)
```
Expand Down
106 changes: 98 additions & 8 deletions python/rmm/rmm/_lib/memory_resource.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cimport cython
from cython.operator cimport dereference as deref
from libc.stddef cimport size_t
from libc.stdint cimport int8_t, int64_t, uintptr_t
from libc.stdlib cimport atof
from libcpp cimport bool
from libcpp.memory cimport make_unique, unique_ptr
from libcpp.optional cimport optional
Expand All @@ -32,7 +33,9 @@ from libcpp.string cimport string
from cuda.cudart import cudaError_t

from rmm._cuda.gpu import CUDARuntimeError, getDevice, setDevice

from rmm._cuda.stream cimport Stream

from rmm._cuda.stream import DEFAULT_STREAM

from rmm._lib.cuda_stream_view cimport cuda_stream_view
Expand All @@ -44,6 +47,7 @@ from rmm._lib.per_device_resource cimport (
cuda_device_id,
set_per_device_resource as cpp_set_per_device_resource,
)

from rmm.statistics import Statistics

# Transparent handle of a C++ exception
Expand Down Expand Up @@ -314,7 +318,7 @@ cdef class CudaAsyncMemoryResource(DeviceMemoryResource):

Parameters
----------
initial_pool_size : int, optional
initial_pool_size : int | str, optional
Initial pool size in bytes. By default, half the available memory
on the device is used.
Matt711 marked this conversation as resolved.
Show resolved Hide resolved
release_threshold: int, optional
Expand All @@ -334,7 +338,7 @@ cdef class CudaAsyncMemoryResource(DeviceMemoryResource):
cdef optional[size_t] c_initial_pool_size = (
optional[size_t]()
if initial_pool_size is None
else optional[size_t](<size_t> initial_pool_size)
else optional[size_t](<size_t> parse_bytes(initial_pool_size))
)

cdef optional[size_t] c_release_threshold = (
Expand Down Expand Up @@ -426,12 +430,12 @@ cdef class PoolMemoryResource(UpstreamResourceAdaptor):
c_initial_pool_size = (
c_percent_of_free_device_memory(50) if
initial_pool_size is None
else initial_pool_size
else parse_bytes(initial_pool_size)
)
c_maximum_pool_size = (
optional[size_t]() if
maximum_pool_size is None
else optional[size_t](<size_t> maximum_pool_size)
else optional[size_t](<size_t> parse_bytes(maximum_pool_size))
)
self.c_obj.reset(
new pool_memory_resource[device_memory_resource](
Expand All @@ -456,10 +460,10 @@ cdef class PoolMemoryResource(UpstreamResourceAdaptor):
upstream_mr : DeviceMemoryResource
The DeviceMemoryResource from which to allocate blocks for the
pool.
initial_pool_size : int, optional
initial_pool_size : int | str, optional
Initial pool size in bytes. By default, half the available memory
on the device is used.
maximum_pool_size : int, optional
maximum_pool_size : int | str, optional
Maximum size in bytes, that the pool can grow to.
"""
pass
Expand Down Expand Up @@ -1091,8 +1095,10 @@ cpdef void _initialize(
typ = PoolMemoryResource
args = (upstream(),)
kwargs = dict(
initial_pool_size=initial_pool_size,
maximum_pool_size=maximum_pool_size
initial_pool_size=None if initial_pool_size is None
else parse_bytes(initial_pool_size),
maximum_pool_size=None if maximum_pool_size is None
else parse_bytes(maximum_pool_size)
)
else:
typ = upstream
Expand Down Expand Up @@ -1324,3 +1330,87 @@ def available_device_memory():
cdef pair[size_t, size_t] res
res = c_available_device_memory()
return (res.first, res.second)


cdef dict byte_sizes = {
"kB": 10 ** 3,
"MB": 10 ** 6,
"GB": 10 ** 9,
"TB": 10 ** 12,
"PB": 10 ** 15,
"KiB": 2 ** 10,
"MiB": 2 ** 20,
"GiB": 2 ** 30,
"TiB": 2 ** 40,
"PiB": 2 ** 50,
"B": 1,
"": 1,
}

byte_sizes.update({k.lower(): v for k, v in byte_sizes.items()})
byte_sizes.update({k[0]: v for k, v in byte_sizes.items() if k and "i" not in k})
byte_sizes.update({k[:-1]: v for k, v in byte_sizes.items() if k and "i" in k})
Matt711 marked this conversation as resolved.
Show resolved Hide resolved
Matt711 marked this conversation as resolved.
Show resolved Hide resolved

cdef int parse_bytes(object s):
Matt711 marked this conversation as resolved.
Show resolved Hide resolved
""" Parse byte string to numbers

Parameters
----------
s : int | str
Size of the pool in bytes (shorthand)
Matt711 marked this conversation as resolved.
Show resolved Hide resolved

>>> parse_bytes('100')
100
>>> parse_bytes('100 MB')
100000000
>>> parse_bytes('100M')
100000000
>>> parse_bytes('5kB')
5000
>>> parse_bytes('5.4 kB')
5400
>>> parse_bytes('1kiB')
1024
>>> parse_bytes('1e6')
1000000
>>> parse_bytes('1e6 kB')
1000000000
>>> parse_bytes('MB')
1000000
>>> parse_bytes(123)
123
>>> parse_bytes('5 foos') # doctest: +SKIP
ValueError: Could not interpret 'foos' as a byte unit
"""
cdef double n
cdef int multiplier
cdef int result

if isinstance(s, (int, float)):
return int(s)
Matt711 marked this conversation as resolved.
Show resolved Hide resolved

s = str(s).replace(" ", "")
Matt711 marked this conversation as resolved.
Show resolved Hide resolved
if not s[0].isdigit():
s = "1" + s
Matt711 marked this conversation as resolved.
Show resolved Hide resolved

cdef int i
for i in range(len(s) - 1, -1, -1):
if not s[i].isalpha():
break
index = i + 1

prefix = s[:index]
suffix = s[index:]
Matt711 marked this conversation as resolved.
Show resolved Hide resolved

try:
n = atof(prefix.encode())
except ValueError:
raise ValueError(f"Could not interpret '{prefix}' as a number")

try:
multiplier = byte_sizes[suffix.lower()]
except KeyError:
raise ValueError(f"Could not interpret '{suffix}' as a byte unit")

result = int(n * multiplier)
return result
6 changes: 3 additions & 3 deletions python/rmm/rmm/rmm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019, NVIDIA CORPORATION.
# Copyright (c) 2019-2024, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,11 +45,11 @@ def reinitialize(
performance.
managed_memory : bool, default False
If True, use managed memory for device memory allocation
initial_pool_size : int, default None
initial_pool_size : int | str, default None
When `pool_allocator` is True, this indicates the initial pool size in
bytes. By default, 1/2 of the total GPU memory is used.
When `pool_allocator` is False, this argument is ignored if provided.
maximum_pool_size : int, default None
Matt711 marked this conversation as resolved.
Show resolved Hide resolved
maximum_pool_size : int | str, default None
When `pool_allocator` is True, this indicates the maximum pool size in
bytes. By default, the total available memory on the GPU is used.
When `pool_allocator` is False, this argument is ignored if provided.
Expand Down
8 changes: 4 additions & 4 deletions python/rmm/rmm/tests/test_rmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,8 @@ def test_rmm_pool_cupy_allocator_stream_lifetime():
def test_pool_memory_resource(dtype, nelem, alloc):
mr = rmm.mr.PoolMemoryResource(
rmm.mr.CudaMemoryResource(),
initial_pool_size=1 << 22,
maximum_pool_size=1 << 23,
initial_pool_size="4MiB",
maximum_pool_size="8MiB",
)
rmm.mr.set_current_device_resource(mr)
assert rmm.mr.get_current_device_resource_type() is type(mr)
Expand Down Expand Up @@ -507,7 +507,7 @@ def test_binning_memory_resource(dtype, nelem, alloc, upstream_mr):

def test_reinitialize_max_pool_size():
rmm.reinitialize(
pool_allocator=True, initial_pool_size=0, maximum_pool_size=1 << 23
pool_allocator=True, initial_pool_size=0, maximum_pool_size="8MiB"
)
rmm.DeviceBuffer().resize((1 << 23) - 1)

Expand All @@ -524,7 +524,7 @@ def test_reinitialize_initial_pool_size_gt_max():
with pytest.raises(RuntimeError) as e:
rmm.reinitialize(
pool_allocator=True,
initial_pool_size=1 << 11,
initial_pool_size="2KiB",
maximum_pool_size=1 << 10,
)
assert "Initial pool size exceeds the maximum pool size" in str(e.value)
wence- marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading