omnimalloc 0.3.0__cp310-cp310-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. omnimalloc/__init__.py +19 -0
  2. omnimalloc/_cpp.cp310-win32.pyd +0 -0
  3. omnimalloc/_cpp.pyi +85 -0
  4. omnimalloc/allocate.py +49 -0
  5. omnimalloc/allocators/__init__.py +25 -0
  6. omnimalloc/allocators/base.py +26 -0
  7. omnimalloc/allocators/genetic.py +187 -0
  8. omnimalloc/allocators/greedy.py +82 -0
  9. omnimalloc/allocators/greedy_cpp.py +65 -0
  10. omnimalloc/allocators/hillclimb.py +215 -0
  11. omnimalloc/allocators/minimalloc.py +109 -0
  12. omnimalloc/allocators/naive.py +23 -0
  13. omnimalloc/allocators/random.py +52 -0
  14. omnimalloc/allocators/utils.py +26 -0
  15. omnimalloc/benchmark/__init__.py +21 -0
  16. omnimalloc/benchmark/benchmark.py +245 -0
  17. omnimalloc/benchmark/converters/__init__.py +11 -0
  18. omnimalloc/benchmark/converters/model.py +164 -0
  19. omnimalloc/benchmark/converters/onnx.py +142 -0
  20. omnimalloc/benchmark/results/__init__.py +9 -0
  21. omnimalloc/benchmark/results/campaign.py +135 -0
  22. omnimalloc/benchmark/results/export.py +232 -0
  23. omnimalloc/benchmark/results/report.py +123 -0
  24. omnimalloc/benchmark/results/result.py +49 -0
  25. omnimalloc/benchmark/results/utils.py +68 -0
  26. omnimalloc/benchmark/results/visualize.py +309 -0
  27. omnimalloc/benchmark/sources/__init__.py +17 -0
  28. omnimalloc/benchmark/sources/base.py +176 -0
  29. omnimalloc/benchmark/sources/generator.py +302 -0
  30. omnimalloc/benchmark/sources/huggingface.py +261 -0
  31. omnimalloc/benchmark/sources/minimalloc.py +167 -0
  32. omnimalloc/benchmark/sources/utils.py +26 -0
  33. omnimalloc/benchmark/timer.py +142 -0
  34. omnimalloc/benchmark/utils.py +3 -0
  35. omnimalloc/common/__init__.py +8 -0
  36. omnimalloc/common/directories.py +13 -0
  37. omnimalloc/common/optional.py +30 -0
  38. omnimalloc/common/registry.py +72 -0
  39. omnimalloc/common/units.py +18 -0
  40. omnimalloc/primitives/__init__.py +10 -0
  41. omnimalloc/primitives/allocation.py +11 -0
  42. omnimalloc/primitives/memory.py +61 -0
  43. omnimalloc/primitives/pool.py +86 -0
  44. omnimalloc/primitives/system.py +38 -0
  45. omnimalloc/primitives/utils.py +39 -0
  46. omnimalloc/py.typed +1 -0
  47. omnimalloc/validate.py +96 -0
  48. omnimalloc/visualize.py +390 -0
  49. omnimalloc-0.3.0.dist-info/METADATA +272 -0
  50. omnimalloc-0.3.0.dist-info/RECORD +52 -0
  51. omnimalloc-0.3.0.dist-info/WHEEL +5 -0
  52. omnimalloc-0.3.0.dist-info/licenses/LICENSE +201 -0
omnimalloc/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ from importlib.metadata import version
6
+
7
+ __version__ = version("omnimalloc")
8
+
9
+ from .allocate import run_allocation as run_allocation
10
+ from .allocators import get_available_allocators as get_available_allocators
11
+ from .allocators import get_default_allocator as get_default_allocator
12
+ from .primitives import Allocation as Allocation
13
+ from .primitives import BufferKind as BufferKind
14
+ from .primitives import IdType as IdType
15
+ from .primitives import Memory as Memory
16
+ from .primitives import Pool as Pool
17
+ from .primitives import System as System
18
+ from .validate import validate_allocation as validate_allocation
19
+ from .visualize import plot_allocation as plot_allocation
Binary file
omnimalloc/_cpp.pyi ADDED
@@ -0,0 +1,85 @@
1
+ from collections.abc import Sequence
2
+ import enum
3
+
4
+
5
+ class BufferKind(enum.Enum):
6
+ def __str__(self) -> str: ...
7
+
8
+ def __repr__(self) -> str: ...
9
+
10
+ WORKSPACE = 0
11
+
12
+ CONSTANT = 1
13
+
14
+ INPUT = 2
15
+
16
+ OUTPUT = 3
17
+
18
+ @property
19
+ def is_io(self) -> bool: ...
20
+
21
+ def __hash__(self) -> int: ...
22
+
23
+ class Allocation:
24
+ def __init__(self, id: int | str, size: int, start: int, end: int, offset: int | None = None, kind: BufferKind | None = None) -> None: ...
25
+
26
+ @property
27
+ def id(self) -> int | str: ...
28
+
29
+ @property
30
+ def size(self) -> int: ...
31
+
32
+ @property
33
+ def start(self) -> int: ...
34
+
35
+ @property
36
+ def end(self) -> int: ...
37
+
38
+ @property
39
+ def offset(self) -> int | None: ...
40
+
41
+ @property
42
+ def kind(self) -> BufferKind | None: ...
43
+
44
+ @property
45
+ def is_allocated(self) -> bool: ...
46
+
47
+ @property
48
+ def duration(self) -> int: ...
49
+
50
+ @property
51
+ def height(self) -> int | None: ...
52
+
53
+ @property
54
+ def area(self) -> int: ...
55
+
56
+ def overlaps_temporally(self, other: Allocation) -> bool: ...
57
+
58
+ def overlaps_spatially(self, other: Allocation) -> bool: ...
59
+
60
+ def overlaps(self, other: Allocation) -> bool: ...
61
+
62
+ def with_offset(self, offset: int) -> Allocation: ...
63
+
64
+ def with_kind(self, kind: BufferKind) -> Allocation: ...
65
+
66
+ def __str__(self) -> str: ...
67
+
68
+ def __repr__(self) -> str: ...
69
+
70
+ def __eq__(self, arg: Allocation, /) -> bool: ...
71
+
72
+ def __hash__(self) -> int: ...
73
+
74
+ class GreedyAllocatorCpp:
75
+ def __init__(self) -> None: ...
76
+
77
+ def allocate(self, allocations: Sequence[Allocation]) -> list[Allocation]: ...
78
+
79
+ def __str__(self) -> str: ...
80
+
81
+ def __repr__(self) -> str: ...
82
+
83
+ def __eq__(self, arg: GreedyAllocatorCpp, /) -> bool: ...
84
+
85
+ def __hash__(self) -> int: ...
omnimalloc/allocate.py ADDED
@@ -0,0 +1,49 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+
6
+ from typing import TypeVar, cast
7
+
8
+ from .allocators import BaseAllocator, get_default_allocator
9
+ from .primitives import Memory, Pool, System
10
+ from .validate import validate_allocation
11
+
12
+ T = TypeVar("T", System, Memory, Pool)
13
+
14
+
15
+ def run_allocation(
16
+ entity: T,
17
+ allocator: BaseAllocator | type[BaseAllocator] | str | None = None,
18
+ validate: bool = False,
19
+ ) -> T:
20
+ """Run allocation on the given entity using the provided allocator.
21
+
22
+ Args:
23
+ entity: The entity to allocate (System, Memory, or Pool).
24
+ allocator: The allocator to use (instance, class, or name).
25
+ validate: Whether to validate the allocated entity.
26
+
27
+ Returns:
28
+ The allocated entity with the same type as the input.
29
+ """
30
+
31
+ if allocator is None:
32
+ allocator = get_default_allocator()
33
+
34
+ if isinstance(allocator, str):
35
+ allocator = BaseAllocator.get(allocator)
36
+
37
+ if isinstance(allocator, type):
38
+ allocator = allocator()
39
+
40
+ # At this point, allocator is guaranteed to be a BaseAllocator instance
41
+ assert isinstance(allocator, BaseAllocator)
42
+
43
+ # ty doesn't understand that TypeVar T (System|Memory|Pool) all have allocate method
44
+ allocated = entity.allocate(allocator) # type: ignore[invalid-argument-type]
45
+
46
+ if validate:
47
+ validate_allocation(allocated)
48
+
49
+ return cast("T", allocated)
@@ -0,0 +1,25 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ from .base import BaseAllocator as BaseAllocator
6
+ from .genetic import GeneticAllocator as GeneticAllocator
7
+ from .greedy import GreedyAllocator as GreedyAllocator
8
+ from .greedy import GreedyByAreaAllocator as GreedyByAreaAllocator
9
+ from .greedy import GreedyByConflictAllocator as GreedyByConflictAllocator
10
+ from .greedy import GreedyByDurationAllocator as GreedyByDurationAllocator
11
+ from .greedy import GreedyBySizeAllocator as GreedyBySizeAllocator
12
+ from .greedy_cpp import GreedyAllocatorCpp as GreedyAllocatorCpp
13
+ from .greedy_cpp import GreedyByAreaAllocatorCpp as GreedyByAreaAllocatorCpp
14
+ from .greedy_cpp import GreedyByConflictAllocatorCpp as GreedyByConflictAllocatorCpp
15
+ from .greedy_cpp import GreedyByDurationAllocatorCpp as GreedyByDurationAllocatorCpp
16
+ from .greedy_cpp import GreedyBySizeAllocatorCpp as GreedyBySizeAllocatorCpp
17
+ from .hillclimb import HillClimbAllocator as HillClimbAllocator
18
+ from .minimalloc import MinimallocAllocator as MinimallocAllocator
19
+ from .naive import NaiveAllocator as NaiveAllocator
20
+ from .random import RandomAllocator as RandomAllocator
21
+ from .utils import AVAILABLE_ALLOCATORS as AVAILABLE_ALLOCATORS
22
+ from .utils import DEFAULT_ALLOCATOR as DEFAULT_ALLOCATOR
23
+ from .utils import get_allocator_by_name as get_allocator_by_name
24
+ from .utils import get_available_allocators as get_available_allocators
25
+ from .utils import get_default_allocator as get_default_allocator
@@ -0,0 +1,26 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ from abc import abstractmethod
6
+ from typing import TYPE_CHECKING
7
+
8
+ from omnimalloc.common.registry import Registered
9
+
10
+ if TYPE_CHECKING:
11
+ from omnimalloc.primitives import Allocation
12
+
13
+
14
+ class BaseAllocator(Registered):
15
+ """Base class for allocators with automatic registry."""
16
+
17
+ @abstractmethod
18
+ def allocate(
19
+ self, allocations: tuple["Allocation", ...]
20
+ ) -> tuple["Allocation", ...]:
21
+ """Run the allocator on the given allocations."""
22
+ ...
23
+
24
+ def reset(self) -> None:
25
+ """Optional: reset allocator state/config. Override if needed."""
26
+ ...
@@ -0,0 +1,187 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ import random
6
+
7
+ import numpy as np
8
+
9
+ from omnimalloc.common.optional import require_optional
10
+ from omnimalloc.primitives import Allocation
11
+
12
+ from .greedy import GreedyAllocator
13
+
14
+ try:
15
+ from deap import algorithms, base, creator, tools
16
+
17
+ HAS_DEAP = True
18
+
19
+ except ImportError:
20
+ from types import SimpleNamespace
21
+ from typing import Any
22
+
23
+ HAS_DEAP = False
24
+
25
+ algorithms: Any = SimpleNamespace(
26
+ eaSimple=None,
27
+ )
28
+ base: Any = SimpleNamespace(
29
+ Fitness=None,
30
+ Toolbox=None,
31
+ )
32
+ creator: Any = SimpleNamespace(
33
+ create=None,
34
+ )
35
+ tools: Any = SimpleNamespace(
36
+ initIterate=None,
37
+ selTournament=None,
38
+ cxOrdered=None,
39
+ mutShuffleIndexes=None,
40
+ )
41
+
42
+
43
+ class GeneticAllocator(GreedyAllocator):
44
+ """Genetic algorithm allocator that evolves permutation orders."""
45
+
46
+ def __init__(
47
+ self,
48
+ seed: int = 42,
49
+ population_size: int = 100,
50
+ num_generations: int = 50,
51
+ crossover_prob: float = 0.7,
52
+ mutation_prob: float = 0.2,
53
+ tournament_size: int = 3,
54
+ ) -> None:
55
+ """Initialize the genetic allocator."""
56
+ if not HAS_DEAP:
57
+ require_optional("deap", "GeneticAllocator")
58
+
59
+ self.seed = seed
60
+ self.population_size = population_size
61
+ self.num_generations = num_generations
62
+ self.crossover_prob = crossover_prob
63
+ self.mutation_prob = mutation_prob
64
+ self.tournament_size = tournament_size
65
+
66
+ # Setup DEAP creators (only once per class)
67
+ if not hasattr(creator, "FitnessMin"):
68
+ creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
69
+ if not hasattr(creator, "Individual"):
70
+ # FitnessMin is dynamically created by DEAP
71
+ creator.create("Individual", list, fitness=creator.FitnessMin) # type: ignore[possibly-missing-attribute]
72
+
73
+ def _evaluate_permutation(
74
+ self, permutation: list[int], allocations: tuple[Allocation, ...]
75
+ ) -> tuple[float]:
76
+ """Evaluate a permutation by computing peak memory usage."""
77
+
78
+ # Apply permutation
79
+ permuted_allocs = tuple(allocations[i] for i in permutation)
80
+
81
+ # Run greedy allocation
82
+ result = super().allocate(permuted_allocs)
83
+
84
+ # Calculate peak memory usage
85
+ if not result:
86
+ return (0.0,)
87
+
88
+ peak_memory = max(alloc.height for alloc in result if alloc.height is not None)
89
+ return (float(peak_memory),)
90
+
91
+ def _create_heuristic_permutations(
92
+ self, allocations: tuple[Allocation, ...]
93
+ ) -> list[list[int]]:
94
+ """Create permutations based on greedy heuristics."""
95
+
96
+ permutations = []
97
+
98
+ # Create index mapping for original order
99
+ indexed_allocs = list(enumerate(allocations))
100
+
101
+ # 1. Greedy by size (largest first)
102
+ sorted_by_size = sorted(indexed_allocs, key=lambda x: x[1].size, reverse=True)
103
+ permutations.append([idx for idx, _ in sorted_by_size])
104
+
105
+ return permutations
106
+
107
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
108
+ """Evolve permutations using genetic algorithm to find best allocation."""
109
+
110
+ # Set random seeds for deterministic behavior
111
+ random.seed(self.seed)
112
+ np.random.default_rng(self.seed)
113
+
114
+ if not allocations:
115
+ return allocations
116
+
117
+ if len(allocations) == 1:
118
+ return super().allocate(allocations)
119
+
120
+ n = len(allocations)
121
+
122
+ # Setup toolbox
123
+ toolbox = base.Toolbox()
124
+
125
+ # Register individual and population generators
126
+ toolbox.register("indices", random.sample, range(n), n) # Random permutation
127
+ # Individual and indices are dynamically created by DEAP
128
+ toolbox.register(
129
+ "individual",
130
+ tools.initIterate,
131
+ creator.Individual, # type: ignore[possibly-missing-attribute]
132
+ toolbox.indices, # type: ignore[possibly-missing-attribute]
133
+ )
134
+
135
+ # Register genetic operators
136
+ toolbox.register(
137
+ "evaluate", self._evaluate_permutation, allocations=allocations
138
+ )
139
+ toolbox.register("mate", tools.cxOrdered)
140
+ toolbox.register("mutate", tools.mutShuffleIndexes, indpb=0.05)
141
+ # TODO(fpedd): Try larger tournsize and selNSGA2
142
+ toolbox.register("select", tools.selTournament, tournsize=self.tournament_size)
143
+
144
+ # Create initial population with heuristic seeding
145
+ population = []
146
+
147
+ # Add heuristic-based individuals (5 heuristics)
148
+ heuristic_perms = self._create_heuristic_permutations(allocations)
149
+ for perm in heuristic_perms:
150
+ # Individual is dynamically created by DEAP
151
+ individual = creator.Individual(perm) # type: ignore[possibly-missing-attribute]
152
+ population.append(individual)
153
+
154
+ # Fill rest with random individuals
155
+ for _ in range(self.population_size - len(heuristic_perms)):
156
+ # individual() is dynamically registered on toolbox
157
+ individual = toolbox.individual() # type: ignore[possibly-missing-attribute]
158
+ population.append(individual)
159
+
160
+ # Track best individual
161
+ hall_of_fame = tools.HallOfFame(maxsize=1)
162
+
163
+ # Setup statistics
164
+ stats = tools.Statistics(key=lambda ind: ind.fitness.values)
165
+ stats.register("min", np.min)
166
+ stats.register("avg", np.mean)
167
+
168
+ # Run genetic algorithm
169
+ # TODO(fpedd): Try eaMuPlusLambda and eaMuCommaLambda
170
+ population, _ = algorithms.eaSimple(
171
+ population=population,
172
+ toolbox=toolbox,
173
+ cxpb=self.crossover_prob,
174
+ mutpb=self.mutation_prob,
175
+ ngen=self.num_generations,
176
+ stats=stats,
177
+ halloffame=hall_of_fame,
178
+ verbose=False,
179
+ )
180
+
181
+ # Get best permutation
182
+ best_individual = hall_of_fame[0]
183
+ best_permutation = list(best_individual)
184
+
185
+ # Apply best permutation and allocate
186
+ permuted_allocs = tuple(allocations[i] for i in best_permutation)
187
+ return super().allocate(permuted_allocs)
@@ -0,0 +1,82 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ from omnimalloc.primitives import Allocation
6
+
7
+ from .base import BaseAllocator
8
+
9
+
10
+ class GreedyAllocator(BaseAllocator):
11
+ """Base greedy allocator using first-fit strategy."""
12
+
13
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
14
+ placed_allocations: list[Allocation] = []
15
+
16
+ for current_alloc in allocations:
17
+ # Collect overlapping allocations sorted by offset
18
+ overlapping = [
19
+ placed
20
+ for placed in placed_allocations
21
+ if current_alloc.overlaps_temporally(placed)
22
+ ]
23
+ overlapping.sort(key=lambda a: a.offset or 0, reverse=False)
24
+
25
+ # Find offset using first-fit (outperforms best-fit in practice)
26
+ best_offset = 0
27
+ for placed in overlapping:
28
+ assert placed.offset is not None
29
+ gap = placed.offset - best_offset
30
+ if gap >= current_alloc.size:
31
+ break
32
+ best_offset = max(best_offset, placed.offset + placed.size)
33
+
34
+ new_alloc = current_alloc.with_offset(best_offset)
35
+ placed_allocations.append(new_alloc)
36
+
37
+ return tuple(placed_allocations)
38
+
39
+
40
+ class GreedyByDurationAllocator(GreedyAllocator):
41
+ """Greedy allocator sorting by duration (longest first)."""
42
+
43
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
44
+ sorted_allocs = sorted(allocations, key=lambda a: a.duration, reverse=True)
45
+ return super().allocate(tuple(sorted_allocs))
46
+
47
+
48
+ class GreedyByConflictAllocator(GreedyAllocator):
49
+ """Greedy allocator sorting by conflict degree (most conflicted first)."""
50
+
51
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
52
+ conflict_degrees = {}
53
+ for alloc in allocations:
54
+ conflicts = sum(
55
+ 1
56
+ for other in allocations
57
+ if other != alloc and alloc.overlaps_temporally(other)
58
+ )
59
+ conflict_degrees[alloc] = conflicts
60
+
61
+ sorted_allocs = sorted(
62
+ allocations, key=lambda a: (conflict_degrees[a], a.size), reverse=True
63
+ )
64
+ return super().allocate(tuple(sorted_allocs))
65
+
66
+
67
+ class GreedyByAreaAllocator(GreedyAllocator):
68
+ """Greedy allocator sorting by area (size * duration, largest first)."""
69
+
70
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
71
+ sorted_allocs = sorted(
72
+ allocations, key=lambda a: a.size * a.duration, reverse=True
73
+ )
74
+ return super().allocate(tuple(sorted_allocs))
75
+
76
+
77
+ class GreedyBySizeAllocator(GreedyAllocator):
78
+ """Greedy allocator sorting by size (largest first)."""
79
+
80
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
81
+ sorted_allocs = sorted(allocations, key=lambda a: a.size, reverse=True)
82
+ return super().allocate(tuple(sorted_allocs))
@@ -0,0 +1,65 @@
1
+ #
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+
5
+ from omnimalloc._cpp import GreedyAllocatorCpp as _GreedyAllocatorCpp
6
+ from omnimalloc.primitives import Allocation
7
+
8
+ from .base import BaseAllocator
9
+
10
+
11
+ class GreedyAllocatorCpp(BaseAllocator):
12
+ """C++ implementation of the base greedy allocator using first-fit strategy."""
13
+
14
+ def __init__(self) -> None:
15
+ self._cpp_allocator = _GreedyAllocatorCpp()
16
+
17
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
18
+ allocs_list = list(allocations)
19
+ result = self._cpp_allocator.allocate(allocs_list)
20
+ return tuple(result)
21
+
22
+
23
+ class GreedyByDurationAllocatorCpp(GreedyAllocatorCpp):
24
+ """C++ greedy allocator sorting by duration (longest first)."""
25
+
26
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
27
+ sorted_allocs = sorted(allocations, key=lambda a: a.duration, reverse=True)
28
+ return super().allocate(tuple(sorted_allocs))
29
+
30
+
31
+ class GreedyByConflictAllocatorCpp(GreedyAllocatorCpp):
32
+ """C++ greedy allocator sorting by conflict degree (most conflicted first)."""
33
+
34
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
35
+ conflict_degrees = {}
36
+ for alloc in allocations:
37
+ conflicts = sum(
38
+ 1
39
+ for other in allocations
40
+ if other != alloc and alloc.overlaps_temporally(other)
41
+ )
42
+ conflict_degrees[alloc] = conflicts
43
+
44
+ sorted_allocs = sorted(
45
+ allocations, key=lambda a: (conflict_degrees[a], a.size), reverse=True
46
+ )
47
+ return super().allocate(tuple(sorted_allocs))
48
+
49
+
50
+ class GreedyByAreaAllocatorCpp(GreedyAllocatorCpp):
51
+ """C++ greedy allocator sorting by area (size * duration, largest first)."""
52
+
53
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
54
+ sorted_allocs = sorted(
55
+ allocations, key=lambda a: a.size * a.duration, reverse=True
56
+ )
57
+ return super().allocate(tuple(sorted_allocs))
58
+
59
+
60
+ class GreedyBySizeAllocatorCpp(GreedyAllocatorCpp):
61
+ """C++ greedy allocator sorting by size (largest first)."""
62
+
63
+ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ...]:
64
+ sorted_allocs = sorted(allocations, key=lambda a: a.size, reverse=True)
65
+ return super().allocate(tuple(sorted_allocs))