Skip to content

Commit b62bb30

Browse files
authored
[ML] Prevent the trained model deployment memory estimation from double-counting allocations. (#131990) (#131998)
This reverts commit 971cfb9. The refactoring in 971cfb9 introduced a bug that could potentially lead to double-counting of the number of allocations in the trained model memory estimation.
1 parent ebc1016 commit b62bb30

File tree

6 files changed

+33
-14
lines changed

6 files changed

+33
-14
lines changed

docs/changelog/131990.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 131990
2+
summary: Prevent the trained model deployment memory estimation from double-counting
3+
allocations
4+
area: Machine Learning
5+
type: bug
6+
issues: []

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/TrainedModelAssignmentRebalancer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,13 @@ private static void copyAssignments(
144144
for (Map.Entry<AssignmentPlan.Node, Integer> assignment : nodeAssignments.entrySet()) {
145145
AssignmentPlan.Node originalNode = originalNodeById.get(assignment.getKey().id());
146146
dest.assignModelToNode(m, originalNode, assignment.getValue());
147-
// As the node has all its available memory we need to manually account memory of models with
148-
// current allocations.
149-
dest.accountMemory(m, originalNode);
147+
if (m.currentAllocationsByNodeId().containsKey(originalNode.id())) {
148+
// TODO (#101612) requiredMemory should be calculated by the AssignmentPlan.Builder
149+
// As the node has all its available memory we need to manually account memory of models with
150+
// current allocations.
151+
long requiredMemory = m.estimateMemoryUsageBytes(m.currentAllocationsByNodeId().get(originalNode.id()));
152+
dest.accountMemory(m, originalNode, requiredMemory);
153+
}
150154
}
151155
}
152156
}

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/planning/AbstractPreserveAllocations.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Deployment modifyModelPreservingPreviousAssignments(Deployment m) {
6969
AssignmentPlan mergePreservedAllocations(AssignmentPlan assignmentPlan) {
7070
// As the model/node objects the assignment plan are the modified ones,
7171
// they will not match the models/nodes members we have in this class.
72-
// Therefore, we build a lookup table based on the ids, so we can merge the plan
72+
// Therefore, we build a lookup table based on the ids so we can merge the plan
7373
// with its preserved allocations.
7474
final Map<Tuple<String, String>, Integer> plannedAssignmentsByModelNodeIdPair = new HashMap<>();
7575
for (Deployment m : assignmentPlan.models()) {
@@ -81,6 +81,7 @@ AssignmentPlan mergePreservedAllocations(AssignmentPlan assignmentPlan) {
8181

8282
AssignmentPlan.Builder mergedPlanBuilder = AssignmentPlan.builder(nodes, deployments);
8383
for (Node n : nodes) {
84+
// TODO (#101612) Should the first loop happen in the builder constructor?
8485
for (Deployment deploymentAllocationsToPreserve : deployments) {
8586

8687
// if the model m is already allocated on the node n and I want to preserve this allocation

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/planning/AssignmentPlan.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,8 @@ public Builder assignModelToNode(Deployment deployment, Node node, int allocatio
408408
if (allocations <= 0) {
409409
return this;
410410
}
411-
if (requiredMemory > remainingNodeMemory.get(node)) {
411+
if (/*isAlreadyAssigned(deployment, node) == false
412+
&&*/ requiredMemory > remainingNodeMemory.get(node)) {
412413
throw new IllegalArgumentException(
413414
"not enough memory on node ["
414415
+ node.id()
@@ -454,14 +455,13 @@ private static int getCurrentAllocations(Deployment m, Node n) {
454455
}
455456

456457
public void accountMemory(Deployment m, Node n) {
457-
if (m.currentAllocationsByNodeId().containsKey(n.id())) {
458-
int allocations = m.currentAllocationsByNodeId().get(n.id());
459-
long requiredMemory = m.estimateMemoryUsageBytes(allocations);
460-
accountMemory(m, n, requiredMemory);
461-
}
458+
// TODO (#101612) remove or refactor unused method
459+
long requiredMemory = getDeploymentMemoryRequirement(m, n, getCurrentAllocations(m, n));
460+
accountMemory(m, n, requiredMemory);
462461
}
463462

464-
private void accountMemory(Deployment m, Node n, long requiredMemory) {
463+
public void accountMemory(Deployment m, Node n, long requiredMemory) {
464+
// TODO (#101612) computation of required memory should be done internally
465465
remainingNodeMemory.computeIfPresent(n, (k, v) -> v - requiredMemory);
466466
if (remainingNodeMemory.containsKey(n) && remainingNodeMemory.get(n) < 0) {
467467
throw new IllegalArgumentException("not enough memory on node [" + n.id() + "] to assign model [" + m.id() + "]");

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/planning/RandomizedAssignmentRounding.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ private void unassignOversizedModels(Node n) {
310310
private AssignmentPlan toPlan() {
311311
AssignmentPlan.Builder builder = AssignmentPlan.builder(nodes, deployments);
312312
for (Map.Entry<Tuple<AssignmentPlan.Deployment, Node>, Integer> assignment : tryAssigningRemainingCores().entrySet()) {
313+
// TODO (#101612) The model should be assigned to the node only when it is possible. This means, that canAssign should be
314+
// integrated into the assignModelToNode.
313315
if (builder.canAssign(assignment.getKey().v1(), assignment.getKey().v2(), assignment.getValue())) {
314316
builder.assignModelToNode(assignment.getKey().v1(), assignment.getKey().v2(), assignment.getValue());
315317
}

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/planning/ZoneAwareAssignmentPlanner.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,15 @@ private AssignmentPlan swapOriginalModelsInPlan(
185185
for (Map.Entry<Node, Integer> assignment : nodeAssignments.entrySet()) {
186186
Node originalNode = originalNodeById.get(assignment.getKey().id());
187187
planBuilder.assignModelToNode(originalDeployment, originalNode, assignment.getValue());
188-
// As the node has all its available memory we need to manually account memory of models with
189-
// current allocations.
190-
planBuilder.accountMemory(originalDeployment, originalNode);
188+
if (originalDeployment.currentAllocationsByNodeId().containsKey(originalNode.id())) {
189+
// TODO (#101612) requiredMemory should be calculated by the AssignmentPlan.Builder
190+
// As the node has all its available memory we need to manually account memory of models with
191+
// current allocations.
192+
long requiredMemory = originalDeployment.estimateMemoryUsageBytes(
193+
originalDeployment.currentAllocationsByNodeId().get(originalNode.id())
194+
);
195+
planBuilder.accountMemory(m, originalNode, requiredMemory);
196+
}
191197
}
192198
}
193199
return planBuilder.build();

0 commit comments

Comments
 (0)