diff --git a/lib/core.ts b/lib/core.ts index d334dc5..b4c55bd 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -335,6 +335,18 @@ export const getTinyHyperGraphSolverOptions = ( const compareCandidatesByF = (left: Candidate, right: Candidate) => left.f - right.f +export const DEFAULT_SPARSE_CANDIDATE_STORAGE_HOP_THRESHOLD = 200_000_000 + +export const getCandidateStorageHopCount = ( + topology: Pick, +) => topology.portCount * topology.regionCount + +export const shouldUseSparseCandidateStorageForTopology = ( + topology: Pick, +) => + getCandidateStorageHopCount(topology) > + DEFAULT_SPARSE_CANDIDATE_STORAGE_HOP_THRESHOLD + interface SegmentGeometryScratch { lesserAngle: number greaterAngle: number @@ -383,6 +395,20 @@ export class TinyHyperGraphSolver extends BaseSolver { ) { super() applyTinyHyperGraphSolverOptions(this, options) + const candidateStorageHopCount = getCandidateStorageHopCount(topology) + const shouldUseSparseCandidateStorage = + options?.USE_SPARSE_CANDIDATE_STORAGE ?? + shouldUseSparseCandidateStorageForTopology(topology) + this.USE_SPARSE_CANDIDATE_STORAGE = shouldUseSparseCandidateStorage + if (shouldUseSparseCandidateStorage) { + this.stats = { + ...this.stats, + candidateStorageHopCount, + candidateStorageMode: "sparse", + autoSparseCandidateStorage: + options?.USE_SPARSE_CANDIDATE_STORAGE === undefined, + } + } this.state = { portAssignment: new Int32Array(topology.portCount).fill(-1), regionSegments: Array.from({ length: topology.regionCount }, () => []), diff --git a/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts b/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts index 9ccd4f9..d23c893 100644 --- a/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts +++ b/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts @@ -48,7 +48,7 @@ const DEFAULT_SOLVE_GRAPH_OPTIONS: TinyHyperGraphSolverOptions = { } const DEFAULT_SECTION_SOLVER_MAX_ITERATIONS = 50_000 -const DEFAULT_SECTION_PIPELINE_MAX_ITERATIONS = 200_000 +const DEFAULT_SECTION_PIPELINE_MAX_ITERATIONS = 1_250_000 const DEFAULT_SECTION_SOLVER_OPTIONS: TinyHyperGraphSectionSolverOptions = { DISTANCE_TO_COST: 0.05, diff --git a/lib/section-solver/index.ts b/lib/section-solver/index.ts index e66f4a1..540ecd3 100644 --- a/lib/section-solver/index.ts +++ b/lib/section-solver/index.ts @@ -593,6 +593,7 @@ class TinyHyperGraphSectionSearchSolver extends TinyHyperGraphSolver { baselineBeatRipCount?: number previousBestMaxRegionCost = Number.POSITIVE_INFINITY ripsSinceBestMaxRegionCostImprovement = 0 + private prioritizeRouteIdForNextRip?: RouteId MAX_RIPS = Number.POSITIVE_INFINITY MAX_RIPS_WITHOUT_MAX_REGION_COST_IMPROVEMENT = Number.POSITIVE_INFINITY @@ -672,6 +673,27 @@ class TinyHyperGraphSectionSearchSolver extends TinyHyperGraphSolver { this.state.goalPortId = -1 } + getNextActiveRouteOrder(): RouteId[] { + const shuffledRouteIds = shuffle( + [...this.activeRouteIds], + this.state.ripCount, + ) + const prioritizedRouteId = this.prioritizeRouteIdForNextRip + this.prioritizeRouteIdForNextRip = undefined + + if ( + prioritizedRouteId === undefined || + !this.activeRouteIds.includes(prioritizedRouteId) + ) { + return shuffledRouteIds + } + + return [ + prioritizedRouteId, + ...shuffledRouteIds.filter((routeId) => routeId !== prioritizedRouteId), + ] + } + override getStartingNextRegionId( routeId: RouteId, startingPortId: PortId, @@ -687,10 +709,7 @@ class TinyHyperGraphSectionSearchSolver extends TinyHyperGraphSolver { override resetRoutingStateForRerip() { if (!this.fixedSnapshot) { super.resetRoutingStateForRerip() - this.state.unroutedRoutes = shuffle( - [...this.activeRouteIds], - this.state.ripCount, - ) + this.state.unroutedRoutes = this.getNextActiveRouteOrder() this.applyFixedSegments() return } @@ -698,10 +717,7 @@ class TinyHyperGraphSectionSearchSolver extends TinyHyperGraphSolver { restoreSolvedStateSnapshot(this, this.fixedSnapshot) this.state.currentRouteId = undefined this.state.currentRouteNetId = undefined - this.state.unroutedRoutes = shuffle( - [...this.activeRouteIds], - this.state.ripCount, - ) + this.state.unroutedRoutes = this.getNextActiveRouteOrder() this.state.candidateQueue.clear() this.resetCandidateBestCosts() this.state.goalPortId = -1 @@ -820,6 +836,26 @@ class TinyHyperGraphSectionSearchSolver extends TinyHyperGraphSolver { override onOutOfCandidates() { const { state } = this + const currentRouteId = state.currentRouteId + const failedOnFirstActiveRoute = + currentRouteId !== undefined && + state.unroutedRoutes.length === this.activeRouteIds.length - 1 + + if (failedOnFirstActiveRoute) { + this.stats = { + ...this.stats, + activeRouteCount: this.activeRouteIds.length, + sectionSearchRejectedInfeasibleFirstRoute: true, + sectionSearchInfeasibleRouteId: currentRouteId, + ripCount: state.ripCount, + } + this.error = `Route ${currentRouteId} has no legal path through the section from the fixed routing state` + this.failed = true + return + } + if (currentRouteId !== undefined) { + this.prioritizeRouteIdForNextRip = currentRouteId + } for (const regionId of this.mutableRegionIds) { const regionCost = @@ -1013,6 +1049,21 @@ export class TinyHyperGraphSectionSolver extends BaseSolver { return } + if (this.sectionSolver.stats.acceptedFixedSectionStateOnTimeout) { + this.optimizedSolver = this.baselineSolver + this.stats = { + ...this.stats, + initialMaxRegionCost: this.baselineSummary.maxRegionCost, + initialTotalRegionCost: this.baselineSummary.totalRegionCost, + finalMaxRegionCost: this.baselineSummary.maxRegionCost, + finalTotalRegionCost: this.baselineSummary.totalRegionCost, + optimized: false, + sectionSearchTimeoutFallbackToBaseline: true, + } + this.solved = true + return + } + const candidateSolver = createSolvedSolverFromRegionSegments( this.topology, this.problem, diff --git a/tests/solver/ddr5-dense-allocation-repro.test.ts b/tests/solver/ddr5-dense-allocation-repro.test.ts index 49288d2..0420402 100644 --- a/tests/solver/ddr5-dense-allocation-repro.test.ts +++ b/tests/solver/ddr5-dense-allocation-repro.test.ts @@ -4,6 +4,10 @@ import { getSinglePortPointPathingSolverParams, type SerializedHyperGraphPortPointPathingSolverInput, } from "lib/compat/convertPortPointPathingSolverInputToSerializedHyperGraph" +import { + DEFAULT_SPARSE_CANDIDATE_STORAGE_HOP_THRESHOLD, + TinyHyperGraphSolver, +} from "lib/core" import { loadSerializedHyperGraph } from "lib/compat/loadSerializedHyperGraph" import { ddr5Pipeline7PortPointPathingInput } from "tiny-hypergraph-repros" @@ -140,7 +144,7 @@ const addConnectionTerminalPorts = ( } } -test("repro: DDR5 pipeline7 port-point-pathing input implies multi-GB dense hop state", () => { +test("repro: DDR5 pipeline7 port-point-pathing input uses sparse hop state", () => { const input = getSinglePortPointPathingSolverParams( ddr5Pipeline7PortPointPathingInput as SerializedHyperGraphPortPointPathingSolverInput, ) @@ -176,7 +180,18 @@ test("repro: DDR5 pipeline7 port-point-pathing input implies multi-GB dense hop expect(topology.regionCount).toBeGreaterThan(12_000) expect(topology.portCount).toBeGreaterThan(27_000) expect(denseHopBytes).toBeGreaterThan(4_000_000_000) + expect(denseHopCount).toBeGreaterThan( + DEFAULT_SPARSE_CANDIDATE_STORAGE_HOP_THRESHOLD, + ) - // Do not construct TinyHyperGraphSolver in this repro: current construction - // attempts to allocate dense hop state at this size, which is the crash. + const solver = new TinyHyperGraphSolver(topology, problem) + + expect(solver.USE_SPARSE_CANDIDATE_STORAGE).toBe(true) + expect(solver.state.candidateBestCostByHopId).toBeInstanceOf(Map) + expect(solver.state.candidateBestCostGenerationByHopId).toBeInstanceOf(Map) + expect(solver.stats).toMatchObject({ + autoSparseCandidateStorage: true, + candidateStorageHopCount: denseHopCount, + candidateStorageMode: "sparse", + }) })