Skip to content

Commit 687ed5c

Browse files
authored
[FEA] Allow setting *_pool_size with human-readable string (#1670)
Closes #173 Authors: - Matthew Murray (https://github.com/Matt711) - Lawrence Mitchell (https://github.com/wence-) Approvers: - Lawrence Mitchell (https://github.com/wence-) - Mark Harris (https://github.com/harrism) URL: #1670
1 parent 1e5fa03 commit 687ed5c

File tree

8 files changed

+143
-22
lines changed

8 files changed

+143
-22
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,8 +771,8 @@ of 1 GiB and a maximum size of 4 GiB. The pool uses
771771
>>> import rmm
772772
>>> pool = rmm.mr.PoolMemoryResource(
773773
... rmm.mr.CudaMemoryResource(),
774-
... initial_pool_size=2**30,
775-
... maximum_pool_size=2**32
774+
... initial_pool_size="1GiB", # equivalent to initial_pool_size=2**30
775+
... maximum_pool_size="4GiB"
776776
... )
777777
>>> rmm.mr.set_current_device_resource(pool)
778778
```

python/rmm/docs/guide.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ of 1 GiB and a maximum size of 4 GiB. The pool uses
139139
>>> import rmm
140140
>>> pool = rmm.mr.PoolMemoryResource(
141141
... rmm.mr.CudaMemoryResource(),
142-
... initial_pool_size=2**30,
143-
... maximum_pool_size=2**32
142+
... initial_pool_size="1GiB", # equivalent to initial_pool_size=2**30
143+
... maximum_pool_size="4GiB"
144144
... )
145145
>>> rmm.mr.set_current_device_resource(pool)
146146
```
@@ -151,8 +151,8 @@ Similarly, to use a pool of managed memory:
151151
>>> import rmm
152152
>>> pool = rmm.mr.PoolMemoryResource(
153153
... rmm.mr.ManagedMemoryResource(),
154-
... initial_pool_size=2**30,
155-
... maximum_pool_size=2**32
154+
... initial_pool_size="1GiB",
155+
... maximum_pool_size="4GiB"
156156
... )
157157
>>> rmm.mr.set_current_device_resource(pool)
158158
```

python/rmm/rmm/_lib/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
# the License.
1313
# =============================================================================
1414

15-
set(cython_sources device_buffer.pyx lib.pyx logger.pyx memory_resource.pyx cuda_stream.pyx)
15+
set(cython_sources device_buffer.pyx lib.pyx logger.pyx memory_resource.pyx cuda_stream.pyx
16+
helper.pyx)
1617
set(linked_libraries rmm::rmm)
1718

1819
# Build all of the Cython targets

python/rmm/rmm/_lib/helper.pxd

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2024, NVIDIA CORPORATION.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
cdef object parse_bytes(object s) except *

python/rmm/rmm/_lib/helper.pyx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright (c) 2024, NVIDIA CORPORATION.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Helper functions for rmm"""
16+
17+
import re
18+
19+
20+
cdef dict BYTE_SIZES = {
21+
'b': 1,
22+
'': 1,
23+
'kb': 1000,
24+
'mb': 1000**2,
25+
'gb': 1000**3,
26+
'tb': 1000**4,
27+
'pb': 1000**5,
28+
'kib': 1024,
29+
'mib': 1024**2,
30+
'gib': 1024**3,
31+
'tib': 1024**4,
32+
'pib': 1024**5,
33+
}
34+
35+
36+
pattern = re.compile(r"^([0-9]+(?:\.[0-9]*)?)[\t ]*((?i:(?:[kmgtp]i?)?b))?$")
37+
38+
cdef object parse_bytes(object s):
39+
"""Parse a string or integer into a number of bytes.
40+
41+
Parameters
42+
----------
43+
s : int | str
44+
Size in bytes. If an integer is provided, it is returned as-is.
45+
A string is parsed as a floating point number with an (optional,
46+
case-insensitive) byte-specifier, both SI prefixes (kb, mb, ..., pb)
47+
and binary prefixes (kib, mib, ..., pib) are supported.
48+
49+
Returns
50+
-------
51+
Requested size in bytes as an integer.
52+
53+
Raises
54+
------
55+
ValueError
56+
If it is not possible to parse the input as a byte specification.
57+
"""
58+
cdef str suffix
59+
cdef double n
60+
cdef int multiplier
61+
62+
if isinstance(s, int):
63+
return s
64+
65+
match = pattern.match(s)
66+
67+
if match is None:
68+
raise ValueError(f"Could not parse {s} as a byte specification")
69+
70+
n = float(match.group(1))
71+
72+
suffix = match.group(2)
73+
if suffix is None:
74+
suffix = ""
75+
76+
multiplier = BYTE_SIZES[suffix.lower()]
77+
78+
return int(n*multiplier)

python/rmm/rmm/_lib/memory_resource.pyx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ from libcpp.string cimport string
3232
from cuda.cudart import cudaError_t
3333

3434
from rmm._cuda.gpu import CUDARuntimeError, getDevice, setDevice
35+
3536
from rmm._cuda.stream cimport Stream
37+
3638
from rmm._cuda.stream import DEFAULT_STREAM
3739

3840
from rmm._lib.cuda_stream_view cimport cuda_stream_view
41+
from rmm._lib.helper cimport parse_bytes
3942
from rmm._lib.memory_resource cimport (
4043
available_device_memory as c_available_device_memory,
4144
percent_of_free_device_memory as c_percent_of_free_device_memory,
@@ -44,6 +47,7 @@ from rmm._lib.per_device_resource cimport (
4447
cuda_device_id,
4548
set_per_device_resource as cpp_set_per_device_resource,
4649
)
50+
4751
from rmm.statistics import Statistics
4852

4953
# Transparent handle of a C++ exception
@@ -314,9 +318,9 @@ cdef class CudaAsyncMemoryResource(DeviceMemoryResource):
314318
315319
Parameters
316320
----------
317-
initial_pool_size : int, optional
321+
initial_pool_size : int | str, optional
318322
Initial pool size in bytes. By default, half the available memory
319-
on the device is used.
323+
on the device is used. A string argument is parsed using `parse_bytes`.
320324
release_threshold: int, optional
321325
Release threshold in bytes. If the pool size grows beyond this
322326
value, unused memory held by the pool will be released at the
@@ -334,7 +338,7 @@ cdef class CudaAsyncMemoryResource(DeviceMemoryResource):
334338
cdef optional[size_t] c_initial_pool_size = (
335339
optional[size_t]()
336340
if initial_pool_size is None
337-
else optional[size_t](<size_t> initial_pool_size)
341+
else optional[size_t](<size_t> parse_bytes(initial_pool_size))
338342
)
339343

340344
cdef optional[size_t] c_release_threshold = (
@@ -426,12 +430,12 @@ cdef class PoolMemoryResource(UpstreamResourceAdaptor):
426430
c_initial_pool_size = (
427431
c_percent_of_free_device_memory(50) if
428432
initial_pool_size is None
429-
else initial_pool_size
433+
else parse_bytes(initial_pool_size)
430434
)
431435
c_maximum_pool_size = (
432436
optional[size_t]() if
433437
maximum_pool_size is None
434-
else optional[size_t](<size_t> maximum_pool_size)
438+
else optional[size_t](<size_t> parse_bytes(maximum_pool_size))
435439
)
436440
self.c_obj.reset(
437441
new pool_memory_resource[device_memory_resource](
@@ -456,10 +460,10 @@ cdef class PoolMemoryResource(UpstreamResourceAdaptor):
456460
upstream_mr : DeviceMemoryResource
457461
The DeviceMemoryResource from which to allocate blocks for the
458462
pool.
459-
initial_pool_size : int, optional
463+
initial_pool_size : int | str, optional
460464
Initial pool size in bytes. By default, half the available memory
461465
on the device is used.
462-
maximum_pool_size : int, optional
466+
maximum_pool_size : int | str, optional
463467
Maximum size in bytes, that the pool can grow to.
464468
"""
465469
pass
@@ -1091,8 +1095,10 @@ cpdef void _initialize(
10911095
typ = PoolMemoryResource
10921096
args = (upstream(),)
10931097
kwargs = dict(
1094-
initial_pool_size=initial_pool_size,
1095-
maximum_pool_size=maximum_pool_size
1098+
initial_pool_size=None if initial_pool_size is None
1099+
else parse_bytes(initial_pool_size),
1100+
maximum_pool_size=None if maximum_pool_size is None
1101+
else parse_bytes(maximum_pool_size)
10961102
)
10971103
else:
10981104
typ = upstream

python/rmm/rmm/rmm.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2019, NVIDIA CORPORATION.
1+
# Copyright (c) 2019-2024, NVIDIA CORPORATION.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -45,14 +45,16 @@ def reinitialize(
4545
performance.
4646
managed_memory : bool, default False
4747
If True, use managed memory for device memory allocation
48-
initial_pool_size : int, default None
48+
initial_pool_size : int | str, default None
4949
When `pool_allocator` is True, this indicates the initial pool size in
5050
bytes. By default, 1/2 of the total GPU memory is used.
5151
When `pool_allocator` is False, this argument is ignored if provided.
52-
maximum_pool_size : int, default None
52+
A string argument is parsed using `parse_bytes`.
53+
maximum_pool_size : int | str, default None
5354
When `pool_allocator` is True, this indicates the maximum pool size in
5455
bytes. By default, the total available memory on the GPU is used.
5556
When `pool_allocator` is False, this argument is ignored if provided.
57+
A string argument is parsed using `parse_bytes`.
5658
devices : int or List[int], default 0
5759
GPU device IDs to register. By default registers only GPU 0.
5860
logging : bool, default False

python/rmm/rmm/tests/test_rmm.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,8 @@ def test_rmm_pool_cupy_allocator_stream_lifetime():
432432
def test_pool_memory_resource(dtype, nelem, alloc):
433433
mr = rmm.mr.PoolMemoryResource(
434434
rmm.mr.CudaMemoryResource(),
435-
initial_pool_size=1 << 22,
436-
maximum_pool_size=1 << 23,
435+
initial_pool_size="4MiB",
436+
maximum_pool_size="8MiB",
437437
)
438438
rmm.mr.set_current_device_resource(mr)
439439
assert rmm.mr.get_current_device_resource_type() is type(mr)
@@ -507,7 +507,7 @@ def test_binning_memory_resource(dtype, nelem, alloc, upstream_mr):
507507

508508
def test_reinitialize_max_pool_size():
509509
rmm.reinitialize(
510-
pool_allocator=True, initial_pool_size=0, maximum_pool_size=1 << 23
510+
pool_allocator=True, initial_pool_size=0, maximum_pool_size="8MiB"
511511
)
512512
rmm.DeviceBuffer().resize((1 << 23) - 1)
513513

@@ -530,6 +530,24 @@ def test_reinitialize_initial_pool_size_gt_max():
530530
assert "Initial pool size exceeds the maximum pool size" in str(e.value)
531531

532532

533+
def test_reinitialize_with_valid_str_arg_pool_size():
534+
rmm.reinitialize(
535+
pool_allocator=True,
536+
initial_pool_size="2kib",
537+
maximum_pool_size="8kib",
538+
)
539+
540+
541+
def test_reinitialize_with_invalid_str_arg_pool_size():
542+
with pytest.raises(ValueError) as e:
543+
rmm.reinitialize(
544+
pool_allocator=True,
545+
initial_pool_size="2k", # 2kb valid, not 2k
546+
maximum_pool_size="8k",
547+
)
548+
assert "Could not parse" in str(e.value)
549+
550+
533551
@pytest.mark.parametrize("dtype", _dtypes)
534552
@pytest.mark.parametrize("nelem", _nelems)
535553
@pytest.mark.parametrize("alloc", _allocs)

0 commit comments

Comments
 (0)