GroupMoveWithHintStrategies
Move Type: Group Complexity: O(T × N × S × K) - efficient for million-object problems Primary Use: TorchRec shard placement, large-scale group-based placement
Move groups of objects using strategy hints per group. Designed for large-scale problems with multiple groups having different constraints.
Overview
GroupMoveWithHintStrategies (also known as GROUP_MOVE_WITH_HINT_STRATEGIES) enables applying different move strategies for different groups based on their unique constraints. Instead of trying all possible combinations (which would be impractical for million-object problems), this move type uses hints to guide the solver toward feasible moves for each group.
Use when:
- Large-scale problems (millions of objects)
- Different groups have different constraints
- Know which strategies work well for each group
- Have nested partition structure (primary + secondary)
- TorchRec table sharding scenarios
- Need efficient moves for heterogeneous groups
Avoid when:
- Simple homogeneous problems
- No group-specific constraints
- Don't have strategy hints
- Single partition is sufficient
Quick Example
- Python
- C++
# Apply different strategies for different shard types
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
GroupMoveWithHintStrategiesMoveTypeSpec(
primaryPartition="table",
secondaryPartition="shard_type",
moveStrategies=MoveStrategies(
groupToMoveStrategy={
"row_wise": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
),
),
),
"column_wise": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITHOUT_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
),
),
),
}
),
),
]
)
)
)
void quickExample() {
// Apply different strategies for different shard types
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("shard");
solver.setContainerName("rank");
// Setup problem
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"node1", {}},
{"node2", {"shard1", "shard2"}},
});
solver.addObjectDimension(
"memory",
std::map<std::string, double>{
{"shard1", 3.0},
{"shard2", 3.0},
});
// Define partitions
std::unordered_map<std::string, std::vector<std::string>> tables = {
{"table1", {"shard1", "shard2"}},
};
solver.addPartition("table", std::move(tables));
std::unordered_map<std::string, std::vector<std::string>> shardTypes = {
{"row_wise", {"shard1"}},
{"column_wise", {"shard2"}},
};
solver.addPartition("shard_type", std::move(shardTypes));
LocalSearchSolverSpec solverSpec;
// Create strategies for each shard type
MoveStrategy rowWiseStrategy;
rowWiseStrategy.type() = MoveStrategyType::RANDOM_SAMPLING_WITH_REPLACEMENT;
MoveToScopeItemsSpec rowWiseDest;
rowWiseDest.defaultScopeItems() = ScopeItemList();
rowWiseDest.defaultScopeItems()->scopeName() = "rank";
rowWiseDest.defaultScopeItems()->scopeItems() = {"node1", "node2"};
rowWiseStrategy.moveToScopeItems() = std::move(rowWiseDest);
MoveStrategy columnWiseStrategy;
columnWiseStrategy.type() =
MoveStrategyType::RANDOM_SAMPLING_WITHOUT_REPLACEMENT;
MoveToScopeItemsSpec columnWiseDest;
columnWiseDest.defaultScopeItems() = ScopeItemList();
columnWiseDest.defaultScopeItems()->scopeName() = "rank";
columnWiseDest.defaultScopeItems()->scopeItems() = {"node1", "node2"};
columnWiseStrategy.moveToScopeItems() = std::move(columnWiseDest);
MoveStrategies moveStrategies;
moveStrategies.groupToMoveStrategy() = {
{"row_wise", rowWiseStrategy},
{"column_wise", columnWiseStrategy},
};
GroupMoveWithHintStrategiesMoveTypeSpec groupMoveSpec;
groupMoveSpec.primaryPartition() = "table";
groupMoveSpec.secondaryPartition() = "shard_type";
groupMoveSpec.moveStrategies() = std::move(moveStrategies);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().groupMoveWithHintStrategiesMoveTypeSpec() =
groupMoveSpec;
solver.addSolver(solverSpec);
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-memory";
balanceSpec.scope() = "rank";
balanceSpec.dimension() = "memory";
solver.addGoal(std::move(balanceSpec), 1.0);
// Solve and print results
auto solution = solver.solve();
std::cout << " Initial objective: " << std::fixed << std::setprecision(4)
<< *solution.initialObjective()->value() << "\n";
std::cout << " Final objective: " << *solution.finalObjective()->value()
<< "\n";
std::cout << " Improvement: "
<< (*solution.initialObjective()->value() -
*solution.finalObjective()->value())
<< "\n";
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
primaryPartition | string | Yes | null | Outer partition (e.g., tables) |
secondaryPartition | string | Yes | null | Inner partition (e.g., shard types) |
moveStrategies | MoveStrategies | Yes | null | Map of group → strategy |
unassignedContainer | string | No | null | Container for unassigned objects |
secondaryGroupReplacementConfig | SecondaryGroupReplacementConfig | No | null | Allowed secondary group replacements |
Parameter Details
primaryPartition:
- The outer partition defining primary groups
- Example: "table" partition in TorchRec
- Each primary group processed independently
secondaryPartition:
- The inner partition splitting the primary partition
- Example: "shard_type" partition in TorchRec
- Defines subgroups within each primary group
moveStrategies:
- Map:
group_name → MoveStrategy - Each
MoveStrategycontains:type:RANDOM_SAMPLING_WITH_REPLACEMENTorRANDOM_SAMPLING_WITHOUT_REPLACEMENTmoveSetsGeneratedPerScopeItem: Number of move sets to generate (default: 1)moveToScopeItems: Destination scope items specificationtertiaryPartition: Optional third partition levelnumScopeItemsToExplorePerTertiaryGroup: Optional sampling for tertiary groups
unassignedContainer:
- Optional container for initially unassigned objects
- Enables group replacement when specified
secondaryGroupReplacementConfig:
- Controls which secondary groups can replace each other
- Only used when
unassignedContaineris set - Map:
secondary_group → allowed_replacement_groups
How It Works
For each primary group (e.g., table):
- Identify secondary groups: Find all secondary groups (e.g., shard types) within this primary group
- Apply strategy per group: For each secondary group:
- Look up the strategy hint for this group
- Generate move sets according to the strategy
- Sample with/without replacement as specified
- Evaluate move sets: Test all generated move sets in parallel
- Select best: Choose the move set that improves objective most
- Repeat: Process all primary groups
Visual Example
Primary Partition: Tables
Secondary Partition: Shard Types
Table1:
├─ row_wise shards → Strategy: Random sampling with replacement
├─ column_wise shards → Strategy: Random sampling without replacement
└─ data_parallel shard → Strategy: Random sampling with replacement
Table2:
├─ row_wise shards → Strategy: Random sampling with replacement
├─ column_wise shards → Strategy: Random sampling without replacement
└─ data_parallel shard → Strategy: Random sampling with replacement
Each table × shard type combination gets its own strategy
Complexity
Per primary group: O(N × S × K)
Where:
- N = average objects per secondary group
- S = number of secondary groups
- K = move sets generated per secondary group
Total: O(T × N × S × K)
Where:
- T = number of primary groups (tables)
Real-world TorchRec example:
- Tables (T): 2,500
- Shards per group (N): 10
- Shard types (S): 10
- Move sets per group (K): 1
- Total evaluations: 2,500 × 10 × 10 × 1 = 250,000
- At 100K eval/sec: ~2.5 seconds
Worst case (10M shards, 5K tables):
- Tables (T): 5,000
- Shards per group (N): 200
- Shard types (S): 10
- Move sets per group (K): 1
- Total evaluations: 10,000,000
- At 100K eval/sec: ~100 seconds
Usage Patterns
TorchRec Table Sharding
Different strategies for different shard types:
- Python
- C++
# TorchRec table sharding with different strategies per shard type
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
GroupMoveWithHintStrategiesMoveTypeSpec(
primaryPartition="table",
secondaryPartition="shard_type",
moveStrategies=MoveStrategies(
groupToMoveStrategy={
"row_wise": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2", "node3"]
),
),
),
"column_wise": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITHOUT_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2", "node3"]
),
),
),
"data_parallel": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2", "node3"]
),
),
),
}
),
),
]
)
)
)
void torchrec() {
// TorchRec table sharding with different strategies per shard type
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
solver.setObjectName("shard");
solver.setContainerName("rank");
// Setup imbalanced problem
solver.setAssignment(
std::map<std::string, std::vector<std::string>>{
{"node1", {"shard1", "shard2", "shard3"}},
{"node2", {}},
{"node3", {}},
});
solver.addObjectDimension(
"compute",
std::map<std::string, double>{
{"shard1", 2.0},
{"shard2", 2.0},
{"shard3", 2.0},
});
std::unordered_map<std::string, std::vector<std::string>> tables = {
{"embeddings", {}},
{"features", {}},
};
solver.addPartition("table", std::move(tables));
std::unordered_map<std::string, std::vector<std::string>> shardTypes = {
{"row_wise", {}},
{"column_wise", {}},
{"data_parallel", {}},
};
solver.addPartition("shard_type", std::move(shardTypes));
LocalSearchSolverSpec solverSpec;
MoveToScopeItemsSpec defaultDest;
defaultDest.defaultScopeItems() = ScopeItemList();
defaultDest.defaultScopeItems()->scopeName() = "rank";
defaultDest.defaultScopeItems()->scopeItems() = {"node1", "node2", "node3"};
MoveStrategy rowWiseStrategy;
rowWiseStrategy.type() = MoveStrategyType::RANDOM_SAMPLING_WITH_REPLACEMENT;
rowWiseStrategy.moveToScopeItems() = defaultDest;
MoveStrategy columnWiseStrategy;
columnWiseStrategy.type() =
MoveStrategyType::RANDOM_SAMPLING_WITHOUT_REPLACEMENT;
columnWiseStrategy.moveToScopeItems() = defaultDest;
MoveStrategy dataParallelStrategy;
dataParallelStrategy.type() =
MoveStrategyType::RANDOM_SAMPLING_WITH_REPLACEMENT;
dataParallelStrategy.moveToScopeItems() = std::move(defaultDest);
MoveStrategies moveStrategies;
moveStrategies.groupToMoveStrategy() = {
{"row_wise", rowWiseStrategy},
{"column_wise", columnWiseStrategy},
{"data_parallel", dataParallelStrategy},
};
GroupMoveWithHintStrategiesMoveTypeSpec groupMoveSpec;
groupMoveSpec.primaryPartition() = "table";
groupMoveSpec.secondaryPartition() = "shard_type";
groupMoveSpec.moveStrategies() = std::move(moveStrategies);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().groupMoveWithHintStrategiesMoveTypeSpec() =
groupMoveSpec;
solver.addSolver(solverSpec);
BalanceSpec balanceSpec;
balanceSpec.name() = "balance-compute";
balanceSpec.scope() = "rank";
balanceSpec.dimension() = "compute";
solver.addGoal(std::move(balanceSpec), 1.0);
// Solve and print results
auto solution = solver.solve();
std::cout << " Initial objective: " << std::fixed << std::setprecision(4)
<< *solution.initialObjective()->value() << "\n";
std::cout << " Final objective: " << *solution.finalObjective()->value()
<< "\n";
std::cout << " Improvement: "
<< (*solution.initialObjective()->value() -
*solution.finalObjective()->value())
<< "\n";
}
With Replacement vs Without
Different sampling strategies for different constraints:
- Python
- C++
# With replacement for flexible placement, without for exclusive
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
GroupMoveWithHintStrategiesMoveTypeSpec(
primaryPartition="service",
secondaryPartition="tier",
moveStrategies=MoveStrategies(
groupToMoveStrategy={
"primary": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITHOUT_REPLACEMENT, # Exclusive
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["rack1", "rack2"]
),
),
),
"replica": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT, # Flexible
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["rack1", "rack2"]
),
),
),
}
),
),
]
)
)
)
void samplingTypes() {
// With replacement for flexible placement, without for exclusive
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> services = {
{"web", {}},
{"db", {}},
};
solver.addPartition("service", std::move(services));
std::unordered_map<std::string, std::vector<std::string>> tiers = {
{"primary", {}},
{"replica", {}},
};
solver.addPartition("tier", std::move(tiers));
LocalSearchSolverSpec solverSpec;
MoveToScopeItemsSpec defaultDest;
defaultDest.defaultScopeItems() = ScopeItemList();
defaultDest.defaultScopeItems()->scopeItems() = {"rack1", "rack2"};
MoveStrategy primaryStrategy;
primaryStrategy.type() =
MoveStrategyType::RANDOM_SAMPLING_WITHOUT_REPLACEMENT; // Exclusive
primaryStrategy.moveToScopeItems() = defaultDest;
MoveStrategy replicaStrategy;
replicaStrategy.type() =
MoveStrategyType::RANDOM_SAMPLING_WITH_REPLACEMENT; // Flexible
replicaStrategy.moveToScopeItems() = std::move(defaultDest);
MoveStrategies moveStrategies;
moveStrategies.groupToMoveStrategy() = {
{"primary", primaryStrategy},
{"replica", replicaStrategy},
};
GroupMoveWithHintStrategiesMoveTypeSpec groupMoveSpec;
groupMoveSpec.primaryPartition() = "service";
groupMoveSpec.secondaryPartition() = "tier";
groupMoveSpec.moveStrategies() = std::move(moveStrategies);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().groupMoveWithHintStrategiesMoveTypeSpec() =
groupMoveSpec;
solver.addSolver(solverSpec);
}
Multiple Move Sets
Generate multiple move sets per group:
- Python
- C++
# Generate multiple move sets for better solution quality
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
GroupMoveWithHintStrategiesMoveTypeSpec(
primaryPartition="table",
secondaryPartition="shard_type",
moveStrategies=MoveStrategies(
groupToMoveStrategy={
"type_a": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveSetsGeneratedPerScopeItem=5, # Generate 5 move sets
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
),
),
),
"type_b": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITHOUT_REPLACEMENT,
moveSetsGeneratedPerScopeItem=3, # Generate 3 move sets
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
),
),
),
}
),
),
]
)
)
)
void multipleMovesets() {
// Generate multiple move sets for better solution quality
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> tables = {
{"table1", {}},
};
solver.addPartition("table", std::move(tables));
std::unordered_map<std::string, std::vector<std::string>> shardTypes = {
{"type_a", {}},
{"type_b", {}},
};
solver.addPartition("shard_type", std::move(shardTypes));
LocalSearchSolverSpec solverSpec;
MoveToScopeItemsSpec defaultDest;
defaultDest.defaultScopeItems() = ScopeItemList();
defaultDest.defaultScopeItems()->scopeItems() = {"node1", "node2"};
MoveStrategy typeAStrategy;
typeAStrategy.type() = MoveStrategyType::RANDOM_SAMPLING_WITH_REPLACEMENT;
typeAStrategy.moveSetsGeneratedPerScopeItem() = 5; // Generate 5 move sets
typeAStrategy.moveToScopeItems() = defaultDest;
MoveStrategy typeBStrategy;
typeBStrategy.type() = MoveStrategyType::RANDOM_SAMPLING_WITHOUT_REPLACEMENT;
typeBStrategy.moveSetsGeneratedPerScopeItem() = 3; // Generate 3 move sets
typeBStrategy.moveToScopeItems() = std::move(defaultDest);
MoveStrategies moveStrategies;
moveStrategies.groupToMoveStrategy() = {
{"type_a", typeAStrategy},
{"type_b", typeBStrategy},
};
GroupMoveWithHintStrategiesMoveTypeSpec groupMoveSpec;
groupMoveSpec.primaryPartition() = "table";
groupMoveSpec.secondaryPartition() = "shard_type";
groupMoveSpec.moveStrategies() = std::move(moveStrategies);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().groupMoveWithHintStrategiesMoveTypeSpec() =
groupMoveSpec;
solver.addSolver(solverSpec);
}
With Unassigned Container
Enable group replacement via unassigned container:
- Python
- C++
# Use unassigned container to enable group replacement
from rebalancer.interface.thrift.v2.SolverSpecs.thrift_types import (
MoveStrategies,
MoveStrategy,
MoveStrategyType,
MoveToScopeItemsSpec,
SecondaryGroupReplacementConfig,
)
solver.addSolver(
SolverSpecs(
localSearchSolverSpec=LocalSearchSolverSpec(
moveTypeList=[
GroupMoveWithHintStrategiesMoveTypeSpec(
primaryPartition="table",
secondaryPartition="shard_type",
unassignedContainer="unassigned", # Enable replacement
secondaryGroupReplacementConfig=SecondaryGroupReplacementConfig(
secondaryGroupToAllowedReplacements={
"type_a": [
"type_b",
"type_c",
], # type_a can be replaced by type_b or type_c
"type_b": [
"type_a"
], # type_b can be replaced by type_a
# type_c has no entry, can be replaced by any
}
),
moveStrategies=MoveStrategies(
groupToMoveStrategy={
"type_a": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
)
),
),
"type_b": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
)
),
),
"type_c": MoveStrategy(
type=MoveStrategyType.RANDOM_SAMPLING_WITH_REPLACEMENT,
moveToScopeItems=MoveToScopeItemsSpec(
defaultScopeItems=ScopeItemList(
itemList=["node1", "node2"]
)
),
),
}
),
),
]
)
)
)
void unassigned() {
// Use unassigned container to enable group replacement
auto executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
ProblemSolver solver(executor, "example", "test");
std::unordered_map<std::string, std::vector<std::string>> tables = {
{"table1", {}},
};
solver.addPartition("table", std::move(tables));
std::unordered_map<std::string, std::vector<std::string>> shardTypes = {
{"type_a", {}},
{"type_b", {}},
{"type_c", {}},
};
solver.addPartition("shard_type", std::move(shardTypes));
LocalSearchSolverSpec solverSpec;
MoveToScopeItemsSpec defaultDest;
defaultDest.defaultScopeItems() = ScopeItemList();
defaultDest.defaultScopeItems()->scopeItems() = {"node1", "node2"};
MoveStrategy sharedStrategy;
sharedStrategy.type() = MoveStrategyType::RANDOM_SAMPLING_WITH_REPLACEMENT;
sharedStrategy.moveToScopeItems() = std::move(defaultDest);
MoveStrategies moveStrategies;
moveStrategies.groupToMoveStrategy() = {
{"type_a", sharedStrategy},
{"type_b", sharedStrategy},
{"type_c", sharedStrategy},
};
SecondaryGroupReplacementConfig replacementConfig;
replacementConfig.secondaryGroupToAllowedReplacements() = {
{"type_a",
{"type_b", "type_c"}}, // type_a can be replaced by type_b or type_c
{"type_b", {"type_a"}}, // type_b can be replaced by type_a
// type_c has no entry, can be replaced by any
};
GroupMoveWithHintStrategiesMoveTypeSpec groupMoveSpec;
groupMoveSpec.primaryPartition() = "table";
groupMoveSpec.secondaryPartition() = "shard_type";
groupMoveSpec.unassignedContainer() = "unassigned"; // Enable replacement
groupMoveSpec.secondaryGroupReplacementConfig() =
std::move(replacementConfig);
groupMoveSpec.moveStrategies() = std::move(moveStrategies);
solverSpec.moveTypeList()->emplace_back();
solverSpec.moveTypeList()->back().groupMoveWithHintStrategiesMoveTypeSpec() =
groupMoveSpec;
solver.addSolver(solverSpec);
}
Performance Characteristics
Strategy Comparison
| Strategy | Replacement | Use Case |
|---|---|---|
RANDOM_SAMPLING_WITH_REPLACEMENT | Yes | Can place multiple objects on same container |
RANDOM_SAMPLING_WITHOUT_REPLACEMENT | No | Each container used at most once |
Scalability
| Objects | Tables | Shard Types | K | Evaluations | Time @100K/s |
|---|---|---|---|---|---|
| 250K | 2.5K | 10 | 1 | 250K | 2.5s |
| 250K | 2.5K | 10 | 5 | 1.25M | 12.5s |
| 10M | 5K | 10 | 1 | 10M | 100s |
Key insight: Even with 10 million objects, this approach is tractable due to strategy hints
When Does It Help?
GroupMoveWithHintStrategies helps when:
- Large scale: Millions of objects to place
- Heterogeneous groups: Different groups have different constraints
- Known strategies: You know which approach works for each group
- Nested structure: Primary + secondary partition hierarchy
- TorchRec workloads: Table sharding scenarios
GroupMoveWithHintStrategies does NOT help when:
- Small problems: Overhead not worth it
- Homogeneous groups: All groups have same constraints
- No hints available: Don't know which strategies to use
- Simple structure: Single partition sufficient
Comparison with Alternatives
| Move Type | Approach | Scale | Use Case |
|---|---|---|---|
| Single | Explore all | Small | Independent objects |
| ColocateGroups | Colocation | Medium | Related groups together |
| GroupMoveWithHintStrategies | Strategy hints | Large | Million+ objects with hints |
Troubleshooting
Problem: Too slow even with hints
Diagnosis: Too many move sets being generated
Solutions:
- Reduce
moveSetsGeneratedPerScopeItem(keep at 1 initially) - Ensure strategies are appropriate for each group
- Check if tertiary partition is needed
- Review number of secondary groups
Problem: Poor solution quality
Diagnosis: Strategy hints not optimal for groups
Solutions:
- Review which strategy type works best for each group
- Try
RANDOM_SAMPLING_WITHOUT_REPLACEMENTfor exclusive placement - Try
RANDOM_SAMPLING_WITH_REPLACEMENTfor flexible placement - Increase
moveSetsGeneratedPerScopeItem(e.g., 3-5) - May need different
moveToScopeItemsper group
Problem: Groups not moving as expected
Diagnosis: Partition or strategy configuration issue
Solutions:
- Verify primary and secondary partitions are correct
- Check that all secondary groups have strategies
- Review
moveToScopeItemsdestinations - Ensure unassignedContainer is set if using replacement
Problem: Strategy not defined for group
Diagnosis: Missing strategy hint in moveStrategies map
Solutions:
- Add strategy for every secondary group
- Check group names match partition exactly
- Review partition definition
When to Use GroupMoveWithHintStrategies
DO use when:
- Large-scale problems (100K+ objects)
- Have nested partition structure (primary + secondary)
- Know which strategies work for each group
- Different groups have different constraints
- TorchRec or similar workloads
DO NOT use when:
- Small problems (<10K objects)
- Single partition sufficient
- All groups homogeneous
- Don't have strategy hints
- Need exploratory approach
Related Move Types
Group-based alternatives:
- ColocateGroups - Collocate related groups
- GroupRouting - Group-aware routing
- GreedyGroupToScopeItem - Greedy group placement
Simpler alternatives:
- Single - For independent objects
- SingleGreedy - Greedy single moves
Source Code
- Thrift definition:
interface/thrift/SolverSpecs.thrift:666 - Implementation:
solver/moves/GroupMoveWithHintStrategiesMoveType.h - Tests:
solver/moves/tests/
Next Steps
- Learn about ColocateGroups for group colocation
- Try GroupRouting for routing-based placement
- Review Move Types Overview for choosing move types
- See TorchRec documentation for real-world usage examples