Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
187 changes: 149 additions & 38 deletions lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,27 @@ type AutomaticSectionSearchResult = {
candidateInitMs: number
candidateSolveMs: number
candidateReplayScoreMs: number
winningCandidateLabel?: string
winningCandidateFamily?: TinyHyperGraphSectionCandidateFamily
winningCandidateLabels: string[]
winningCandidateFamilies: TinyHyperGraphSectionCandidateFamily[]
}

type CandidateEvaluationResult = {
label: string
family: TinyHyperGraphSectionCandidateFamily
regionIds: RegionId[]
portSectionMask: Int8Array
finalMaxRegionCost: number
improvement: number
eligibilityMs: number
initMs: number
solveMs: number
replayScoreMs: number
}

const isCandidateEvaluationResult = (
value: CandidateEvaluationResult | null,
): value is CandidateEvaluationResult => value !== null

const DEFAULT_SOLVE_GRAPH_OPTIONS: TinyHyperGraphSolverOptions = {
RIP_THRESHOLD_RAMP_ATTEMPTS: 5,
}
Expand All @@ -81,6 +98,7 @@ const DEFAULT_CANDIDATE_FAMILIES: TinyHyperGraphSectionCandidateFamily[] = [
const DEFAULT_MAX_HOT_REGIONS = 2

const IMPROVEMENT_EPSILON = 1e-9
const DEFAULT_PARALLEL_CANDIDATE_SOLVE_LIMIT = 4

const getMaxRegionCost = (solver: TinyHyperGraphSolver) =>
solver.state.regionIntersectionCaches.reduce(
Expand Down Expand Up @@ -248,8 +266,8 @@ const findBestAutomaticSectionMask = (

let bestFinalMaxRegionCost = baselineMaxRegionCost
let bestPortSectionMask = new Int8Array(topology.portCount)
let winningCandidateLabel: string | undefined
let winningCandidateFamily: TinyHyperGraphSectionCandidateFamily | undefined
const winningCandidateLabels: string[] = []
const winningCandidateFamilies: TinyHyperGraphSectionCandidateFamily[] = []
let generatedCandidateCount = 0
let candidateCount = 0
let duplicateCandidateCount = 0
Expand All @@ -263,6 +281,11 @@ const findBestAutomaticSectionMask = (
sectionSolverOptions.MAX_HOT_REGIONS ??
DEFAULT_MAX_HOT_REGIONS

const candidatesToEvaluate: Array<{
candidate: SectionMaskCandidate
candidateProblem: TinyHyperGraphProblem
}> = []

for (const candidate of getSectionMaskCandidates(
solvedSolver,
topology,
Expand All @@ -286,64 +309,152 @@ const findBestAutomaticSectionMask = (
}

seenPortSectionMasks.add(portSectionMaskKey)
candidatesToEvaluate.push({ candidate, candidateProblem })
}

const evaluateCandidate = ({
candidate,
candidateProblem,
}: {
candidate: SectionMaskCandidate
candidateProblem: TinyHyperGraphProblem
}): CandidateEvaluationResult | null => {
try {
const eligibilityStartTime = performance.now()
const activeRouteIds = getActiveSectionRouteIds(
topology,
candidateProblem,
solution,
)
candidateEligibilityMs += performance.now() - eligibilityStartTime
const eligibilityMs = performance.now() - eligibilityStartTime

if (activeRouteIds.length === 0) {
continue
candidateEligibilityMs += eligibilityMs
return null
}

candidateCount += 1

const candidateInitStartTime = performance.now()
const sectionSolver = new TinyHyperGraphSectionSolver(
topology,
candidateProblem,
solution,
sectionSolverOptions,
)
candidateInitMs += performance.now() - candidateInitStartTime
const initMs = performance.now() - candidateInitStartTime

const candidateSolveStartTime = performance.now()
sectionSolver.solve()
candidateSolveMs += performance.now() - candidateSolveStartTime
const solveMs = performance.now() - candidateSolveStartTime

if (sectionSolver.failed || !sectionSolver.solved) {
continue
candidateEligibilityMs += eligibilityMs
candidateInitMs += initMs
candidateSolveMs += solveMs
return null
}

const finalMaxRegionCost = Number(
sectionSolver.stats.finalMaxRegionCost ??
getMaxRegionCost(sectionSolver.getSolvedSolver()),
const candidateReplayScoreStartTime = performance.now()
const replayedFinalMaxRegionCost = getSerializedOutputMaxRegionCost(
sectionSolver.getOutput(),
)

if (finalMaxRegionCost < bestFinalMaxRegionCost - IMPROVEMENT_EPSILON) {
const candidateReplayScoreStartTime = performance.now()
const replayedFinalMaxRegionCost = getSerializedOutputMaxRegionCost(
sectionSolver.getOutput(),
)
candidateReplayScoreMs +=
performance.now() - candidateReplayScoreStartTime

if (
replayedFinalMaxRegionCost <
bestFinalMaxRegionCost - IMPROVEMENT_EPSILON
) {
bestFinalMaxRegionCost = replayedFinalMaxRegionCost
bestPortSectionMask = new Int8Array(candidateProblem.portSectionMask)
winningCandidateLabel = candidate.label
winningCandidateFamily = candidate.family
}
const replayScoreMs = performance.now() - candidateReplayScoreStartTime

candidateEligibilityMs += eligibilityMs
candidateInitMs += initMs
candidateSolveMs += solveMs
candidateReplayScoreMs += replayScoreMs

return {
label: candidate.label,
family: candidate.family,
regionIds: candidate.regionIds,
portSectionMask: new Int8Array(candidateProblem.portSectionMask),
finalMaxRegionCost: replayedFinalMaxRegionCost,
improvement: baselineMaxRegionCost - replayedFinalMaxRegionCost,
eligibilityMs,
initMs,
solveMs,
replayScoreMs,
}
} catch {
// Skip invalid section masks that split a route into multiple spans.
return null
}
}

const parallelCandidateSolveLimit = Math.max(
1,
sectionSolverOptions.PARALLEL_CANDIDATE_SOLVE_LIMIT ??
DEFAULT_PARALLEL_CANDIDATE_SOLVE_LIMIT,
)
const evaluatedCandidates: CandidateEvaluationResult[] = []
for (let i = 0; i < candidatesToEvaluate.length; i += parallelCandidateSolveLimit) {
const chunk = candidatesToEvaluate.slice(i, i + parallelCandidateSolveLimit)
const chunkResults = chunk
.map(evaluateCandidate)
.filter(isCandidateEvaluationResult)
evaluatedCandidates.push(...chunkResults)
}

candidateCount = evaluatedCandidates.length

const sortedByImprovement = [...evaluatedCandidates].sort((left, right) => {
if (left.improvement !== right.improvement) {
return right.improvement - left.improvement
}
return left.finalMaxRegionCost - right.finalMaxRegionCost
})

const chosenCandidates: CandidateEvaluationResult[] = []
const chosenRegionIds = new Set<RegionId>()
for (const evaluatedCandidate of sortedByImprovement) {
if (evaluatedCandidate.improvement <= IMPROVEMENT_EPSILON) {
continue
}
const overlapsChosenRegion = evaluatedCandidate.regionIds.some((regionId) =>
chosenRegionIds.has(regionId),
)
if (overlapsChosenRegion) {
continue
}
chosenCandidates.push(evaluatedCandidate)
for (const regionId of evaluatedCandidate.regionIds) {
chosenRegionIds.add(regionId)
}
}

if (chosenCandidates.length > 0) {
const mergedMask = new Int8Array(topology.portCount)
for (const candidate of chosenCandidates) {
for (let portId = 0; portId < topology.portCount; portId++) {
if (candidate.portSectionMask[portId] === 1) {
mergedMask[portId] = 1
}
}
winningCandidateLabels.push(candidate.label)
winningCandidateFamilies.push(candidate.family)
}

const mergedProblem = createProblemWithPortSectionMask(problem, mergedMask)
const mergedSectionSolver = new TinyHyperGraphSectionSolver(
topology,
mergedProblem,
solution,
sectionSolverOptions,
)
mergedSectionSolver.solve()

if (mergedSectionSolver.solved && !mergedSectionSolver.failed) {
const mergedReplayedFinalMaxRegionCost = getSerializedOutputMaxRegionCost(
mergedSectionSolver.getOutput(),
)
if (
mergedReplayedFinalMaxRegionCost <
bestFinalMaxRegionCost - IMPROVEMENT_EPSILON
) {
bestFinalMaxRegionCost = mergedReplayedFinalMaxRegionCost
bestPortSectionMask = mergedMask
}
}
}

Expand All @@ -360,8 +471,8 @@ const findBestAutomaticSectionMask = (
candidateInitMs,
candidateSolveMs,
candidateReplayScoreMs,
winningCandidateLabel,
winningCandidateFamily,
winningCandidateLabels,
winningCandidateFamilies,
}
}

Expand Down Expand Up @@ -469,9 +580,9 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver<Tiny
)

this.selectedSectionCandidateLabel =
searchResult.winningCandidateLabel
searchResult.winningCandidateLabels.join(", ")
this.selectedSectionCandidateFamily =
searchResult.winningCandidateFamily
searchResult.winningCandidateFamilies[0]
this.stats = {
...this.stats,
sectionSearchGeneratedCandidateCount:
Expand All @@ -486,9 +597,9 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver<Tiny
searchResult.baselineMaxRegionCost -
searchResult.finalMaxRegionCost,
selectedSectionCandidateLabel:
searchResult.winningCandidateLabel ?? null,
searchResult.winningCandidateLabels.join(", ") || null,
selectedSectionCandidateFamily:
searchResult.winningCandidateFamily ?? null,
searchResult.winningCandidateFamilies[0] ?? null,
sectionSearchMs: searchResult.totalMs,
sectionSearchBaselineEvaluationMs:
searchResult.baselineEvaluationMs,
Expand Down
13 changes: 13 additions & 0 deletions lib/section-solver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export interface TinyHyperGraphSectionSolverOptions
* falls back to this value before using its built-in default.
*/
MAX_HOT_REGIONS?: number
/**
* Number of section candidates evaluated per chunk during automatic
* section-mask search. Higher values increase concurrency pressure and may
* improve throughput on larger datasets.
*/
PARALLEL_CANDIDATE_SOLVE_LIMIT?: number
}

const applyTinyHyperGraphSectionSolverOptions = (
Expand All @@ -74,6 +80,10 @@ const applyTinyHyperGraphSectionSolverOptions = (
solver.EXTRA_RIPS_AFTER_BEATING_BASELINE_MAX_REGION_COST =
options.EXTRA_RIPS_AFTER_BEATING_BASELINE_MAX_REGION_COST
}
if (options.PARALLEL_CANDIDATE_SOLVE_LIMIT !== undefined) {
solver.PARALLEL_CANDIDATE_SOLVE_LIMIT =
options.PARALLEL_CANDIDATE_SOLVE_LIMIT
}
}

const getTinyHyperGraphSectionSolverOptions = (
Expand All @@ -85,6 +95,7 @@ const getTinyHyperGraphSectionSolverOptions = (
solver.MAX_RIPS_WITHOUT_MAX_REGION_COST_IMPROVEMENT,
EXTRA_RIPS_AFTER_BEATING_BASELINE_MAX_REGION_COST:
solver.EXTRA_RIPS_AFTER_BEATING_BASELINE_MAX_REGION_COST,
PARALLEL_CANDIDATE_SOLVE_LIMIT: solver.PARALLEL_CANDIDATE_SOLVE_LIMIT,
})

const cloneRegionSegments = (
Expand Down Expand Up @@ -592,6 +603,7 @@ class TinyHyperGraphSectionSearchSolver extends TinyHyperGraphSolver {
MAX_RIPS = Number.POSITIVE_INFINITY
MAX_RIPS_WITHOUT_MAX_REGION_COST_IMPROVEMENT = Number.POSITIVE_INFINITY
EXTRA_RIPS_AFTER_BEATING_BASELINE_MAX_REGION_COST = Number.POSITIVE_INFINITY
PARALLEL_CANDIDATE_SOLVE_LIMIT = 4

constructor(
topology: TinyHyperGraphTopology,
Expand Down Expand Up @@ -865,6 +877,7 @@ export class TinyHyperGraphSectionSolver extends BaseSolver {
MAX_RIPS = Number.POSITIVE_INFINITY
MAX_RIPS_WITHOUT_MAX_REGION_COST_IMPROVEMENT = 10
EXTRA_RIPS_AFTER_BEATING_BASELINE_MAX_REGION_COST = 10
PARALLEL_CANDIDATE_SOLVE_LIMIT = 4

RIP_CONGESTION_REGION_COST_FACTOR = 0.1

Expand Down
Loading