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
26 changes: 26 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TinyHyperGraphTopology, "portCount" | "regionCount">,
) => topology.portCount * topology.regionCount

export const shouldUseSparseCandidateStorageForTopology = (
topology: Pick<TinyHyperGraphTopology, "portCount" | "regionCount">,
) =>
getCandidateStorageHopCount(topology) >
DEFAULT_SPARSE_CANDIDATE_STORAGE_HOP_THRESHOLD

interface SegmentGeometryScratch {
lesserAngle: number
greaterAngle: number
Expand Down Expand Up @@ -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 }, () => []),
Expand Down
2 changes: 1 addition & 1 deletion lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
67 changes: 59 additions & 8 deletions lib/section-solver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -687,21 +709,15 @@ 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
}

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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 18 additions & 3 deletions tests/solver/ddr5-dense-allocation-repro.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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",
})
})
Loading