Uh oh!
There was an error while loading. Please reload this page.
C++20 placer: 0.0000 overlap, 0.2654 wirelength, 36.10s on tests 1-10 - #64
Open
lgoyal6 wants to merge 2 commits into
Open
C++20 placer: 0.0000 overlap, 0.2654 wirelength, 36.10s on tests 1-10#64lgoyal6 wants to merge 2 commits into
lgoyal6 wants to merge 2 commits into
Conversation
This adds cpp_placer, a placement solver written as a single C++20 translation unit and driven from placement.py through a ctypes shim. It carries no dependencies beyond a C++20 compiler, and build.sh compiles the shared library on first import, so the existing Python entry points keep working exactly as they did and nobody has to run a separate build step before scoring. The pipeline itself is the conventional one rather than anything exotic. Analytic global placement runs Adam on a smoothed wirelength objective with an auto-scaled overlap-area penalty and a uniform grid for neighbour search, and that is followed by macro placement, row legalization and detailed placement. The whole sequence is wrapped in a multi-start over die aspect ratio and macro arrangement, because the objective has enough local minima that a single start leaves a meaningful amount on the table. Two observations about the scoring are worth recording for whoever works on this next, since neither is documented and both change how a solver should be tuned. The objective the scorer actually computes is Chebyshev distance rather than the Manhattan distance described in the docstring, so anyone optimising against the documented metric is improving a different function from the one being measured. Separately, intra-cell edges contribute a fixed 0.0330 to every score regardless of where anything is placed, which means the range genuinely available to a solver is a good deal narrower than the raw leaderboard numbers make it look. Measured on tests 1 through 10 using the repository's own scorer from a clean checkout: 0.0000 overlap, 0.2654 wirelength, 36.10 seconds. That does not beat the current top row of 0.2611. It places third on the wirelength column while running faster than both entries above it. run_first10.py reproduces the run end to end.
This records the result from the previous commit in the README table: 0.0000 overlap, 0.2654 wirelength and 36.10 seconds on tests 1 through 10, all measured with the repository's own scorer from a clean checkout rather than from a working tree with local modifications. The row lands third on the wirelength column. I am submitting it rather than holding it back until it wins, because the approach differs from the entries above it, the accompanying write-up is explicit about where it loses as well as where it does well, and the two scoring observations recorded in the previous commit are useful to the project independently of where this entry finishes in the ranking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This does not beat the top row. #1 is 0.2611 and I reached 0.2654, so this is
3rd on the wirelength column, at 36.10s against #1's 50.51s. Posting it anyway
because the approach is different from the entries above it and the writeup
includes the parts where it loses.
What it is
A mixed-size placer in C++20, one translation unit, called from placement.py
through a ctypes shim. No dependencies beyond a C++20 compiler; the library
builds itself on first import. Pipeline is the classic flow: analytic global
placement (Adam on the smoothed objective plus an auto-scaled overlap-area
penalty, uniform-grid neighbour search), then macro placement, then row
legalization, then detailed placement, wrapped in a multi-start over die aspect
ratio and macro arrangement.
The part I think is worth your time
Zero overlap here is structural, not tuned. Every standard cell in the generator
has height exactly 1.0, so they tile a unit-pitch row grid perfectly. Rows cannot
overlap in y, and intervals within a row are kept disjoint by construction, so
the placement cannot produce an overlap regardless of hyperparameters. Before
returning, positions are rounded to float32 (the dtype the harness stores them
in) and re-checked with a sweep-line version of calculate_cells_with_overlaps's
exact predicate; if anything overlaps, the shim raises rather than returning a
bad placement.
Two things I found reading the scoring code, neither of which I patched
The scored objective is Chebyshev, not Manhattan. wirelength_attraction_loss
costs each edge as alphalog(exp(|dx|/alpha) + exp(|dy|/alpha)) with
alpha=0.1. The docstring calls it a smooth approximation of Manhattan
distance, but alphalogsumexp(dx/alpha, dy/alpha) approximates max, within
alpha*ln2 = 0.069 of max(|dx|,|dy|) everywhere. This solver optimises the
L-inf function you actually score.
Pin offsets are corner-relative while the overlap check is centre-relative.
Pins land at cell_pos + offset with the offset in [0,w] x [0,h], but the
overlap check treats cell_pos as the centre, so a cell's pin cloud is its body
translated by (+w/2, +h/2), and the shift scales with the cell. That is
exploitable: a small cell can park so its pin cloud lands inside a large
neighbour's.
I deliberately left both alone. Changing either moves the metric and makes every
existing leaderboard row incomparable. Flagging rather than fixing.
How much of this metric is reachable
Edges are generated between pins, so when both endpoints sit on the same cell the
term is identical under every legal placement. Measured, that floor is 0.0330
averaged over tests 1-10, and it is very uneven: 26.2% of test 1's edges are
intra-cell, giving it a floor of 0.0902, versus 0.0004 for test 10. Against the
reachable part of the score, #1 is at 0.2281 and I am at 0.2326.
Where it loses
Runtime is a dial, not a result: the solver is anytime, so the honest form is the
curve. 0.2849 at 6.96s, 0.2699 at 12.53s, 0.2654 at 36.10s, 0.2653 at 48.16s. It
saturates, so the 0.0045 gap to #1 is algorithmic and not something more time
fixes. My weakest cases are the small macro-dominated ones, tests 1 to 4, at
0.285 to 0.334; my strongest is test 10 at 0.1665. Peak RSS for the whole run is
225 MB, most of it the harness importing torch.
I also implemented overlap_repulsion_loss properly and left the original Adam
loop reachable with PARTCL_SOLVER=torch. With the shipped hyperparameters it
scores 0.4742 overlap and 0.4801 wirelength, so a correct loss function on its
own does not get to zero overlap. The legalizer is what does.
Not attempted: extra credit tests 11 and 12. Test 12 has 100,010 cells and
calculate_cells_with_overlaps is a Python double loop over all pairs, so scoring
it is on the order of 5e9 Python iterations no matter how fast the placer is. A
grid or sweep-line version of that check would make the extra credit cases
practical to score; happy to send one if it is useful.
Reproduce
python run_first10.py
test.py as shipped runs all 12 cases and averages over 12, while the README asks
for the first 10, so run_first10.py reuses test.py's own run_placement_test and
TEST_CASES verbatim and only changes which slice is averaged. Full numbers,
including every axis where this loses, are in cpp_placer/README.md.