Xplace_for_ICCAD/src/run_placement_nesterov.py
2025-05-08 01:39:08 +08:00

494 lines
23 KiB
Python

from utils import *
from src import *
from functools import partial
def get_trunc_node_pos_fn(mov_node_size, data):
node_pos_lb = mov_node_size / 2 + data.die_ll + 1e-4
node_pos_ub = data.die_ur - mov_node_size / 2 + data.die_ll - 1e-4
def trunc_node_pos_fn(x):
x.data.clamp_(min=node_pos_lb, max=node_pos_ub)
return x
return trunc_node_pos_fn
def global_placement_main(gpdb, rawdb, ps: ParamScheduler, data: PlaceData, args, logger, params, gputimer=None):
init_density_map = data.init_density_map
if not args.global_placement:
logger.info("Global placement is switched off. Please make sure the input "
"placement solution is already placed globally.")
node_pos, iteration = data.node_pos, 0
hpwl, overflow = evaluate_placement(node_pos, init_density_map, ps, data, args)
hpwl, overflow = hpwl.item(), overflow.item()
info = ("%d_gp" % (iteration + 1), hpwl, data.design_name)
if args.draw_placement:
draw_fig_with_cairo_cpp(node_pos, data.node_size, data, info, args)
logger.info("Input solution, exact HPWL: %.6E exact Overflow: %.4f" % (hpwl, overflow))
gp_hpwl, overflow, gp_time, gp_per_iter = hpwl, overflow, 0, -1
return node_pos, iteration, gp_hpwl, overflow, gp_time, gp_per_iter
device = data.device
mov_lhs, mov_rhs = data.movable_index
mov_node_pos, mov_node_size, expand_ratio = data.get_mov_node_info()
mov_node_pos = mov_node_pos.requires_grad_(True)
trunc_node_pos_fn = get_trunc_node_pos_fn(mov_node_size, data)
conn_fix_node_pos = data.node_pos.new_empty(0, 2)
if data.fixed_connected_index[0] < data.fixed_connected_index[1]:
lhs, rhs = data.fixed_connected_index
conn_fix_node_pos = data.node_pos[lhs:rhs, ...]
conn_fix_node_pos = conn_fix_node_pos.detach()
def overflow_fn(mov_density_map):
overflow_sum = ((mov_density_map - args.target_density) * data.bin_area).clamp_(min=0.0).sum()
return overflow_sum / data.total_mov_area_without_filler
overflow_helper = (mov_lhs, mov_rhs, overflow_fn)
density_map_layer = ElectronicDensityLayer(
unit_len=data.unit_len,
num_bin_x=data.num_bin_x,
num_bin_y=data.num_bin_y,
device=device,
overflow_helper=overflow_helper,
sorted_maps=data.sorted_maps,
expand_ratio=expand_ratio,
deterministic=args.deterministic,
).to(device)
# fix_lhs, fix_rhs = data.fixed_index
# info = (0, 0, data.design_name + "_fix")
# fix_node_pos = data.node_pos[fix_lhs:fix_rhs, ...]
# fix_node_size = data.node_size[fix_lhs:fix_rhs, ...]
# draw_fig_with_cairo(
# None, None, fix_node_pos, fix_node_size, None, None, data, info, args
# )
def calc_route_force(mov_node_pos, mov_node_size, expand_ratio, constraint_fn):
return get_route_force(
args, logger, data, rawdb, gpdb, ps, mov_node_pos, mov_node_size, expand_ratio,
constraint_fn=constraint_fn
)
obj_and_grad_fn = partial(
calc_obj_and_grad,
constraint_fn=trunc_node_pos_fn,
route_fn=calc_route_force,
mov_node_size=mov_node_size,
expand_ratio=expand_ratio,
init_density_map=init_density_map,
density_map_layer=density_map_layer,
conn_fix_node_pos=conn_fix_node_pos,
ps=ps,
data=data,
args=args,
)
evaluator_fn = partial(
fast_evaluator,
constraint_fn=trunc_node_pos_fn,
mov_node_size=mov_node_size,
init_density_map=init_density_map,
density_map_layer=density_map_layer,
conn_fix_node_pos=conn_fix_node_pos,
ps=ps,
data=data,
args=args,
)
optimizer = NesterovOptimizer([mov_node_pos], lr=0)
# initialization
init_params(
mov_node_pos, trunc_node_pos_fn, mov_lhs, mov_rhs, conn_fix_node_pos,
density_map_layer, mov_node_size, expand_ratio, init_density_map, optimizer,
ps, data, args, route_fn=calc_route_force
)
# init learnig rate
init_lr = estimate_initial_learning_rate(obj_and_grad_fn, trunc_node_pos_fn, mov_node_pos, args.lr)
for param_group in optimizer.param_groups:
param_group["lr"] = init_lr.item()
torch.cuda.synchronize(device)
gp_start_time = time.time()
logger.info("start gp")
# def trace_handler(prof):
# print(prof.key_averages().table(
# sort_by="self_cuda_time_total", row_limit=-1))
# prof.export_chrome_trace("test_trace_" + str(prof.step_num) + ".json")
# with torch.profiler.profile(
# activities=[
# torch.profiler.ProfilerActivity.CPU,
# torch.profiler.ProfilerActivity.CUDA,
# ], schedule=torch.profiler.schedule(
# wait=2,
# warmup=2,
# active=2),
# on_trace_ready=trace_handler
# ) as p:
# for iter in range(6):
# # optimizer.zero_grad()
# obj = optimizer.step(obj_and_grad_fn)
# hpwl, overflow = evaluator_fn(mov_node_pos)
# # update parameters
# ps.step(hpwl, overflow, mov_node_pos, data)
# if ps.need_to_early_stop():
# break
# p.step()
# exit(0)
terminate_signal = False
route_early_terminate_signal = False
log_info = False
timing_cali_thrs_overflow = 0.5
timing_calibration = False
for iteration in range(args.inner_iter):
# optimizer.zero_grad() # zero grad inside obj_and_grad_fn
obj = optimizer.step(obj_and_grad_fn)
hpwl, overflow = evaluator_fn(mov_node_pos)
# update parameters
ps.step(hpwl, overflow, mov_node_pos, data)
# Perform timing-opt.
if args.timing_opt and iteration > args.timing_start_iter and iteration % 1 == 0:
ps.enable_timing = True
node_pos = torch.cat([mov_node_pos[mov_lhs:mov_rhs].clone(), data.node_pos[mov_rhs:]], dim=0)
if args.calibration and ps.recorder.overflow[-1] < timing_cali_thrs_overflow:
gputimer.update_timing_calibrated(node_pos, record=True)
timing_cali_thrs_overflow -= args.calibration_step
timing_calibration = True
elif timing_calibration:
gputimer.update_timing_calibrated(node_pos)
else:
gputimer.update_timing(node_pos)
timing_metrics = gputimer.report_timing_slack()
wns_early, tns_early, wns_late, tns_late = timing_metrics
ps.push_timing_sol(timing_metrics, hpwl, overflow, mov_node_pos)
if iteration % args.timing_freq == 0:
gputimer.step(ps, node_pos, data)
if ps.need_to_early_stop():
terminate_signal = True
log_info = True
if ps.enable_mixed_size and not ps.zero_macro_grad and terminate_signal:
ps.zero_macro_grad = True
# Find best gp node_pos (including macros and std cells)
best_res = ps.get_best_solution()
if best_res[0] is not None:
best_sol, hpwl, overflow = best_res
# fillers are unused from now, we don't copy there data
mov_node_pos[mov_lhs:mov_rhs].data.copy_(best_sol[mov_lhs:mov_rhs])
node_pos = mov_node_pos[mov_lhs:mov_rhs]
node_pos = torch.cat([node_pos, data.node_pos[mov_rhs:]], dim=0)
# Evaluate the mixed placement solution
hpwl, overflow = evaluate_placement(node_pos, init_density_map, ps, data, args)
hpwl, overflow = hpwl.item(), overflow.item()
if args.draw_placement:
info = ("%d_mixed_gp" % (iteration + 1), hpwl, data.design_name)
draw_fig_with_cairo_cpp(node_pos, data.node_size, data, info, args)
logger.info("After Mixed-GP, best solution eval, exact HPWL: %.6E exact Overflow: %.4f" % (hpwl, overflow))
# Run macro legalization to change node_pos inplace
macro_legalization_main(node_pos, data, args, logger)
if args.draw_placement:
info = ("%d_mixed_gp_ml" % (iteration + 1), hpwl, data.design_name)
draw_fig_with_cairo_cpp(node_pos, data.node_size, data, info, args)
# Write node_pos into database to provide an initial solution for std cell placement
data.node_pos[mov_lhs:mov_rhs].data.copy_(node_pos[mov_lhs:mov_rhs])
# Prepare for std cell placement
init_density_map = get_init_density_map(rawdb, gpdb, data, args, logger, ps=ps)
data.__total_mov_area_without_filler__ = torch.sum(data.node_area[mov_lhs:mov_rhs][torch.logical_not(data.is_mov_macro[mov_lhs:mov_rhs])]).item()
mov_node_pos, mov_node_size, expand_ratio = data.get_mov_node_info(init_method="randn_center")
mov_macros_idx = data.is_mov_macro[mov_lhs:mov_rhs]
mov_node_pos[mov_lhs:mov_rhs][mov_macros_idx] = data.node_pos[mov_lhs:mov_rhs][mov_macros_idx]
mov_node_pos = mov_node_pos.requires_grad_(True)
trunc_node_pos_fn = get_trunc_node_pos_fn(mov_node_size, data)
density_map_layer.expand_ratio = expand_ratio
density_map_layer.sorted_maps = data.sorted_maps
# ignore the density and grad computation of macros by node_weight
density_map_layer.cache_node_weight[mov_lhs:mov_rhs][mov_macros_idx] = -1.0
# update partial function correspondingly
obj_and_grad_fn.keywords["constraint_fn"] = trunc_node_pos_fn
obj_and_grad_fn.keywords["mov_node_size"] = mov_node_size
obj_and_grad_fn.keywords["expand_ratio"] = expand_ratio
obj_and_grad_fn.keywords["init_density_map"] = init_density_map
evaluator_fn.keywords["constraint_fn"] = trunc_node_pos_fn
evaluator_fn.keywords["mov_node_size"] = mov_node_size
evaluator_fn.keywords["init_density_map"] = init_density_map
# reset nesterov optimizer
logger.info("Reset optimizer...")
optimizer = NesterovOptimizer([mov_node_pos], lr=0)
# initialization
init_params(
mov_node_pos, trunc_node_pos_fn, mov_lhs, mov_rhs, conn_fix_node_pos,
density_map_layer, mov_node_size, expand_ratio, init_density_map, optimizer,
ps, data, args, route_fn=calc_route_force
)
# init learnig rate
cur_lr = estimate_initial_learning_rate(obj_and_grad_fn, trunc_node_pos_fn, mov_node_pos, args.lr)
for param_group in optimizer.param_groups:
param_group["lr"] = cur_lr.item()
ps.reset_best_sol()
terminate_signal = False # reset signal
logger.info("Re-run std cell placement with fixed macros.")
if ps.use_cell_inflate and ps.curr_optimizer_cnt < ps.max_route_opt and terminate_signal:
terminate_signal = False # reset signal
ps.start_route_opt = True
ps.curr_optimizer_cnt += 1
best_res = ps.get_best_solution()
if best_res[0] is not None:
best_sol, hpwl, overflow = best_res
mov_node_pos.data.copy_(best_sol)
if ps.use_route_force:
if ps.iter > 100 and ps.enable_route:
if ps.recorder.overflow[-1] < 0.2 and ps.recorder.overflow[-2] >= 0.2 and not ps.start_route_opt:
ps.start_route_opt = True
ps.curr_optimizer_cnt += 1
# if ps.recorder.overflow[-2] < 0.2 and ps.recorder.overflow[-1] >= 0.2 and ps.start_route_opt:
# ps.start_route_opt = False
# ps.curr_optimizer_cnt += 1
# if ps.use_cell_inflate and ps.curr_optimizer_cnt < ps.max_route_opt:
# if ps.iter > 100 and ps.enable_route:
# if ps.recorder.overflow[-1] < 0.15 and ps.recorder.overflow[-2] >= 0.15 and not ps.start_route_opt:
# ps.start_route_opt = True
# ps.curr_optimizer_cnt += 1
# if ps.recorder.overflow[-2] < 0.15 and ps.recorder.overflow[-1] >= 0.15 and ps.start_route_opt:
# ps.start_route_opt = False
if ps.start_route_opt and ps.enable_route:
if (ps.iter % args.route_freq == 0 and ps.use_route_force) or \
(ps.curr_optimizer_cnt != ps.prev_optimizer_cnt and ps.curr_optimizer_cnt <= ps.max_route_opt):
ps.rerun_route = True
else:
ps.rerun_route = False
else:
ps.rerun_route = False
if ps.rerun_route:
log_info = True
new_mov_node_size, new_expand_ratio = None, None
if ps.use_cell_inflate:
output = route_inflation(
args, logger, data, rawdb, gpdb, ps, mov_node_pos, mov_node_size, expand_ratio,
constraint_fn=trunc_node_pos_fn, visualize=args.visualize_cgmap
) # ps.use_cell_inflate is updated in route_inflation
if not ps.use_cell_inflate:
route_early_terminate_signal = True
terminate_signal = True
logger.info("Early stop cell inflation...")
if output is not None:
gr_metrics, new_mov_node_size, new_expand_ratio = output
ps.push_gr_sol(gr_metrics, hpwl, overflow, mov_node_pos)
route_fn=None
if ps.use_route_force:
route_fn=calc_route_force
ps.prev_optimizer_cnt = ps.curr_optimizer_cnt
if ps.use_cell_inflate or ps.use_route_force:
logger.info("Reset optimizer...")
if new_mov_node_size is not None:
# remove some fillers, we should update the size the pos
mov_node_size = new_mov_node_size
mov_node_pos = mov_node_pos[:new_mov_node_size.shape[0]].detach().clone()
mov_node_pos = mov_node_pos.requires_grad_(True)
# update expand ratio and precondition relevant data
expand_ratio = new_expand_ratio # already update in route_inflation()
data.mov_node_to_num_pins = data.mov_node_to_num_pins[:new_mov_node_size.shape[0]]
data.mov_node_area = data.mov_node_area # already update in route_inflation()
# update partial function correspondingly
trunc_node_pos_fn = get_trunc_node_pos_fn(mov_node_size, data)
obj_and_grad_fn.keywords["constraint_fn"] = trunc_node_pos_fn
obj_and_grad_fn.keywords["mov_node_size"] = mov_node_size
obj_and_grad_fn.keywords["expand_ratio"] = expand_ratio
evaluator_fn.keywords["constraint_fn"] = trunc_node_pos_fn
evaluator_fn.keywords["mov_node_size"] = mov_node_size
density_map_layer.expand_ratio = expand_ratio
# reset nesterov optimizer
optimizer = NesterovOptimizer([mov_node_pos], lr=0)
init_params(
mov_node_pos, trunc_node_pos_fn, mov_lhs, mov_rhs, conn_fix_node_pos,
density_map_layer, mov_node_size, expand_ratio, init_density_map, optimizer,
ps, data, args, route_fn=route_fn
)
cur_lr = estimate_initial_learning_rate(obj_and_grad_fn, trunc_node_pos_fn, mov_node_pos, args.lr)
for param_group in optimizer.param_groups:
param_group["lr"] = cur_lr.item()
logger.info(
"Route Iter: %d | lr: %.2E density_weight: %.2E route_weight: %.2E "
"congest_weight: %.2E pseudo_weight: %.2E "
% (
ps.curr_optimizer_cnt - 1,
cur_lr.item(),
ps.density_weight,
ps.route_weight,
ps.congest_weight,
ps.pseudo_weight,
)
)
ps.reset_best_sol()
if iteration % args.log_freq == 0 or iteration == args.inner_iter - 1 or log_info:
log_info = False
log_str = (
"iter: %d | masked_hpwl: %.2E overflow: %.4f obj: %.4E "
"density_weight: %.4E wa_coeff: %.4E"
% (
iteration,
hpwl,
overflow,
obj,
ps.density_weight,
ps.wa_coeff,
)
)
if ps.enable_timing:
log_str += " | early WNS/TNS: %.4f %.4f (ns) | late WNS/TNS: %.4f %.4f (ns)" % (
wns_early, tns_early, wns_late, tns_late
)
logger.info(log_str)
if args.draw_placement:
info = (iteration, hpwl, data.design_name)
node_pos_to_draw = mov_node_pos[mov_lhs:mov_rhs, ...].clone()
node_size_to_draw = data.node_size[mov_lhs:mov_rhs, ...].clone()
node_pos_to_draw = torch.cat(
[node_pos_to_draw, data.node_pos[mov_rhs:, ...].clone()], dim=0
)
node_size_to_draw = torch.cat(
[node_size_to_draw, data.node_size[mov_rhs:, ...].clone()], dim=0
)
if args.use_filler:
node_pos_to_draw = torch.cat(
[node_pos_to_draw, mov_node_pos[mov_rhs:, ...].clone()], dim=0
)
node_size_filler_to_draw = data.filler_size[:(mov_node_pos.shape[0] - mov_rhs), ...]
node_size_to_draw = torch.cat(
[node_size_to_draw, node_size_filler_to_draw], dim=0
)
draw_fig_with_cairo_cpp(
node_pos_to_draw, node_size_to_draw, data, info, args
)
if terminate_signal:
break
# Save best solution
best_res = ps.get_best_solution()
if best_res[0] is not None:
best_sol, hpwl, overflow = best_res
# fillers are unused from now, we don't copy there data
mov_node_pos[mov_lhs:mov_rhs].data.copy_(best_sol[mov_lhs:mov_rhs])
if ps.enable_mixed_size and ps.zero_macro_grad:
# rollback macro_pos to previous legalized results since trunc_node_pos_fn may change them
mov_macros_idx = data.is_mov_macro[mov_lhs:mov_rhs]
mov_node_pos.data[mov_lhs:mov_rhs][mov_macros_idx] = data.node_pos[mov_lhs:mov_rhs][mov_macros_idx]
if ps.enable_route:
route_inflation_roll_back(args, logger, data, mov_node_size)
if not route_early_terminate_signal:
ps.rerun_route = True
gr_metrics = run_gr_and_fft_main(
args, logger, data, rawdb, gpdb, ps, mov_node_pos, constraint_fn=trunc_node_pos_fn,
skip_m1_route=True, report_gr_metrics_only=True, visualize=args.visualize_cgmap
)
ps.rerun_route = False
ps.push_gr_sol(gr_metrics, hpwl, overflow, mov_node_pos)
best_sol_gr = ps.get_best_gr_sol()
mov_node_pos[mov_lhs:mov_rhs].data.copy_(best_sol_gr[mov_lhs:mov_rhs])
if ps.enable_timing and not ps.enable_route:
best_sol_timing = ps.get_best_timing_sol()
if best_sol_timing is not None:
mov_node_pos[mov_lhs:mov_rhs].data.copy_(best_sol_timing[mov_lhs:mov_rhs])
node_pos = mov_node_pos[mov_lhs:mov_rhs]
node_pos = torch.cat([node_pos, data.node_pos[mov_rhs:]], dim=0)
torch.cuda.synchronize(device)
gp_end_time = time.time()
gp_time = gp_end_time - gp_start_time
gp_per_iter = gp_time / (iteration + 1)
logger.info("GP Stop! #Iters %d masked_hpwl: %.6E overflow: %.4f GP Time: %.4fs perIterTime: %.6fs" %
(iteration, hpwl, overflow, gp_time, gp_time / (iteration + 1))
)
# Eval
hpwl, overflow = evaluate_placement(node_pos, init_density_map, ps, data, args)
hpwl, overflow = hpwl.item(), overflow.item()
info = ("%d_gp" % (iteration + 1), hpwl, data.design_name)
if args.draw_placement:
draw_fig_with_cairo_cpp(node_pos, data.node_size, data, info, args)
logger.info("After GP, best solution eval, exact HPWL: %.6E exact Overflow: %.4f" % (hpwl, overflow))
ps.visualize(args, logger)
gp_hpwl = hpwl
gp_time = gp_end_time - gp_start_time
iteration += 1 # increase 1 For DP drawing
return node_pos, iteration, gp_hpwl, overflow, gp_time, gp_per_iter
def run_placement_main_nesterov(args, logger):
total_start = time.time()
params = find_design_params(args, logger)
data, rawdb, gpdb = load_dataset(args, logger, params)
device = torch.device(
"cuda:{}".format(args.gpu) if torch.cuda.is_available() else "cpu"
)
assert args.use_eplace_nesterov
logger.info("Start place %s/%s" % (args.dataset , args.design_name))
logger.info("Use Nesterov optimizer!")
data = data.to(device)
data = data.preprocess()
logger.info(data)
logger.info(data.node_type_indices)
# args.num_bin_x = args.num_bin_y = 2 ** math.ceil(math.log2(max(data.die_info).item() // 25))
get_init_density_map(rawdb, gpdb, data, args, logger)
data.init_filler()
ps = ParamScheduler(data, args, logger)
gputimer = None
if args.timing_opt:
gputimer = GPUTimer(data, rawdb, gpdb, params, args)
data.gputimer = gputimer
def timing_eval_func(node_pos):
gputimer.update_timing_eval(node_pos)
wns_early, tns_early, wns_late, tns_late = gputimer.report_timing_slack()
logger.info("early WNS/TNS: %.4f/%.4f (ns) | late WNS/TNS: %.4f/%.4f (ns)" % (wns_early, tns_early, wns_late, tns_late))
return wns_early, tns_early, wns_late, tns_late
# global placement
node_pos, iteration, gp_hpwl, overflow, gp_time, gp_per_iter = global_placement_main(
gpdb, rawdb, ps, data, args, logger, params, gputimer
)
if args.timing_opt:
wns_early_gp, tns_early_gp, wns_late_gp, tns_late_gp = timing_eval_func(node_pos)
# detail placement
node_pos, dp_hpwl, top5overflow, lg_time, dp_time = detail_placement_main(
node_pos, gpdb, rawdb, ps, data, args, logger
)
if args.timing_opt:
wns_early_dp, tns_early_dp, wns_late_dp, tns_late_dp = timing_eval_func(node_pos)
iteration += 1
route_metrics = None
if ps.enable_route and args.final_route_eval:
logger.info("Final routing evalution by GGR...")
route_metrics = run_gr_and_fft(
args, logger, data, rawdb, gpdb, ps,
report_gr_metrics_only=True,
skip_m1_route=True, given_gr_params={
"rrrIters": 1,
"route_guide": os.path.join(args.result_dir, args.exp_id, args.output_dir, "%s_%s.guide" %(args.output_prefix, args.design_name)),
}
)
if args.load_from_raw:
del gpdb, rawdb
del gputimer
place_time = time.time() - total_start
logger.info("GP Time: %.4f LG Time: %.4f DP Time: %.4f Total Place Time: %.4f" % (
gp_time, lg_time, dp_time, place_time))
place_metrics = (dp_hpwl, gp_hpwl, top5overflow, overflow, gp_time, dp_time + lg_time, gp_per_iter, place_time)
if args.timing_opt:
place_metrics += (wns_early_dp, tns_early_dp, wns_late_dp, tns_late_dp)
return place_metrics, route_metrics