413 lines
16 KiB
Python
413 lines
16 KiB
Python
|
|
import argparse
|
||
|
|
import pickle
|
||
|
|
from collections import namedtuple
|
||
|
|
|
||
|
|
import os
|
||
|
|
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
||
|
|
import numpy as np
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
|
||
|
|
import gym
|
||
|
|
import torch
|
||
|
|
import torch.nn as nn
|
||
|
|
import torch.nn.functional as F
|
||
|
|
import torch.optim as optim
|
||
|
|
from torch.distributions import Normal
|
||
|
|
from torch.distributions import Categorical
|
||
|
|
from torch.utils.data.sampler import BatchSampler, SubsetRandomSampler
|
||
|
|
|
||
|
|
import place_env
|
||
|
|
import torchvision
|
||
|
|
from place_db import PlaceDB
|
||
|
|
import time
|
||
|
|
from tqdm import tqdm
|
||
|
|
import random
|
||
|
|
from comp_res import comp_res
|
||
|
|
from torch.utils.tensorboard import SummaryWriter
|
||
|
|
|
||
|
|
# set device to cpu or cuda
|
||
|
|
device = torch.device('cuda')
|
||
|
|
|
||
|
|
if(torch.cuda.is_available()):
|
||
|
|
device = torch.device('cuda:0')
|
||
|
|
torch.cuda.empty_cache()
|
||
|
|
print("Device set to : " + str(torch.cuda.get_device_name(device)))
|
||
|
|
else:
|
||
|
|
print("Device set to : cpu")
|
||
|
|
|
||
|
|
# Parameters
|
||
|
|
parser = argparse.ArgumentParser(description='Solve the Pendulum-v0 with PPO')
|
||
|
|
parser.add_argument(
|
||
|
|
'--gamma', type=float, default=0.95, metavar='G', help='discount factor (default: 0.9)')
|
||
|
|
parser.add_argument('--seed', type=int, default=42, metavar='N', help='random seed (default: 0)')
|
||
|
|
parser.add_argument('--disable_tqdm', type=int, default=1)
|
||
|
|
parser.add_argument('--lr', type=float, default=2.5e-3)
|
||
|
|
parser.add_argument(
|
||
|
|
'--log-interval',
|
||
|
|
type=int,
|
||
|
|
default=10,
|
||
|
|
metavar='N',
|
||
|
|
help='interval between training status logs (default: 10)')
|
||
|
|
parser.add_argument('--pnm', type=int, default=128)
|
||
|
|
parser.add_argument('--benchmark', type=str, default='adaptec1')
|
||
|
|
parser.add_argument('--soft_coefficient', type=float, default = 1)
|
||
|
|
parser.add_argument('--batch_size', type=int, default=64)
|
||
|
|
parser.add_argument('--is_test', action='store_true', default=False)
|
||
|
|
parser.add_argument('--save_fig', action='store_true', default=False)
|
||
|
|
args = parser.parse_args()
|
||
|
|
writer = SummaryWriter('./tb_log')
|
||
|
|
|
||
|
|
benchmark = args.benchmark
|
||
|
|
placedb = PlaceDB(benchmark)
|
||
|
|
grid = 224
|
||
|
|
placed_num_macro = args.pnm
|
||
|
|
if args.pnm > placedb.node_cnt:
|
||
|
|
placed_num_macro = placedb.node_cnt
|
||
|
|
args.pnm = placed_num_macro
|
||
|
|
env = gym.make('place_env-v0', placedb = placedb, placed_num_macro = placed_num_macro, grid = grid).unwrapped
|
||
|
|
|
||
|
|
num_emb_state = 64 + 2 + 1
|
||
|
|
num_state = 1 + grid*grid*5 + 2
|
||
|
|
|
||
|
|
def seed_torch(seed=0):
|
||
|
|
random.seed(seed)
|
||
|
|
np.random.seed(seed)
|
||
|
|
os.environ['PYTHONHASHSEED'] = str(seed)
|
||
|
|
torch.manual_seed(seed)
|
||
|
|
torch.cuda.manual_seed(seed)
|
||
|
|
torch.manual_seed(seed)
|
||
|
|
env.seed(seed)
|
||
|
|
torch.backends.cudnn.deterministic = True
|
||
|
|
torch.backends.cudnn.benchmark = False
|
||
|
|
|
||
|
|
num_action = env.action_space.shape
|
||
|
|
seed_torch(args.seed)
|
||
|
|
|
||
|
|
Transition = namedtuple('Transition',['state', 'action', 'reward', 'a_log_prob', 'next_state', 'reward_intrinsic'])
|
||
|
|
TrainingRecord = namedtuple('TrainRecord',['episode', 'reward'])
|
||
|
|
print("seed = {}".format(args.seed))
|
||
|
|
print("lr = {}".format(args.lr))
|
||
|
|
print("placed_num_macro = {}".format(args.pnm))
|
||
|
|
|
||
|
|
|
||
|
|
class MyCNN(nn.Module):
|
||
|
|
def __init__(self):
|
||
|
|
super(MyCNN, self).__init__()
|
||
|
|
self.cnn = nn.Sequential(
|
||
|
|
nn.Conv2d(4, 8, 1),
|
||
|
|
nn.ReLU(),
|
||
|
|
nn.Conv2d(8, 8, 1),
|
||
|
|
nn.ReLU(),
|
||
|
|
nn.Conv2d(8, 1, 1),
|
||
|
|
)
|
||
|
|
def forward(self, x):
|
||
|
|
return self.cnn(x)
|
||
|
|
|
||
|
|
|
||
|
|
class MyCNNCoarse(nn.Module):
|
||
|
|
def __init__(self, res_net):
|
||
|
|
super(MyCNNCoarse, self).__init__()
|
||
|
|
self.cnn = res_net.to(device)
|
||
|
|
self.cnn.fc = torch.nn.Linear(512, 16*7*7)
|
||
|
|
self.deconv = nn.Sequential(
|
||
|
|
nn.ConvTranspose2d(16, 8, 3, stride=2, padding=1, output_padding = 1), #14
|
||
|
|
nn.ReLU(),
|
||
|
|
nn.ConvTranspose2d(8, 4, 3, stride=2, padding=1, output_padding = 1), #28
|
||
|
|
nn.ReLU(),
|
||
|
|
nn.ConvTranspose2d(4, 2, 3, stride=2, padding=1, output_padding = 1), #56
|
||
|
|
nn.ReLU(),
|
||
|
|
nn.ConvTranspose2d(2, 1, 3, stride=2, padding=1, output_padding = 1), #112
|
||
|
|
nn.ReLU(),
|
||
|
|
nn.ConvTranspose2d(1, 1, 3, stride=2, padding=1, output_padding = 1), #224
|
||
|
|
)
|
||
|
|
def forward(self, x):
|
||
|
|
x = self.cnn(x).reshape(-1, 16, 7, 7)
|
||
|
|
return self.deconv(x)
|
||
|
|
|
||
|
|
|
||
|
|
class Actor(nn.Module):
|
||
|
|
def __init__(self, cnn, gcn, cnn_coarse):
|
||
|
|
super(Actor, self).__init__()
|
||
|
|
self.fc1 = nn.Linear(num_emb_state, 512)
|
||
|
|
self.fc2 = nn.Linear(512, 64)
|
||
|
|
self.fc3 = nn.Linear(64, grid * grid)
|
||
|
|
self.cnn = cnn
|
||
|
|
self.cnn_coarse = cnn_coarse
|
||
|
|
self.gcn = None
|
||
|
|
self.softmax = nn.Softmax(dim=-1)
|
||
|
|
self.merge = nn.Conv2d(2, 1, 1)
|
||
|
|
|
||
|
|
def forward(self, x, graph = None, cnn_res = None, gcn_res = None, graph_node = None):
|
||
|
|
if not cnn_res:
|
||
|
|
cnn_input = x[:, 1+grid*grid*1: 1+grid*grid*5].reshape(-1, 4, grid, grid)
|
||
|
|
mask = x[:, 1+grid*grid*2: 1+grid*grid*3].reshape(-1, grid, grid)
|
||
|
|
mask = mask.flatten(start_dim=1, end_dim=2)
|
||
|
|
cnn_res = self.cnn(cnn_input)
|
||
|
|
coarse_input = torch.cat((x[:, 1: 1+grid*grid*2].reshape(-1, 2, grid, grid),
|
||
|
|
x[:, 1+grid*grid*3: 1+grid*grid*4].reshape(-1, 1, grid, grid)
|
||
|
|
),dim= 1).reshape(-1, 3, grid, grid)
|
||
|
|
cnn_coarse_res = self.cnn_coarse(coarse_input)
|
||
|
|
cnn_res = self.merge(torch.cat((cnn_res, cnn_coarse_res), dim=1))
|
||
|
|
net_img = x[:, 1+grid*grid: 1+grid*grid*2]
|
||
|
|
net_img = net_img + x[:, 1+grid*grid*2: 1+grid*grid*3] * 10
|
||
|
|
net_img_min = net_img.min() + args.soft_coefficient
|
||
|
|
mask2 = net_img.le(net_img_min).logical_not().float()
|
||
|
|
|
||
|
|
x = cnn_res
|
||
|
|
x = x.reshape(-1, grid * grid)
|
||
|
|
x = torch.where(mask + mask2 >=1.0, -1.0e10, x.double())
|
||
|
|
x = self.softmax(x)
|
||
|
|
|
||
|
|
return x, cnn_res, gcn_res
|
||
|
|
|
||
|
|
|
||
|
|
class Critic(nn.Module):
|
||
|
|
def __init__(self, cnn, gcn, cnn_coarse, res_net):
|
||
|
|
super(Critic, self).__init__()
|
||
|
|
self.fc1 = nn.Linear(64, 64)
|
||
|
|
self.fc2 = nn.Linear(64, 64)
|
||
|
|
self.state_value = nn.Linear(64, 1)
|
||
|
|
self.pos_emb = nn.Embedding(1400, 64)
|
||
|
|
self.cnn = cnn
|
||
|
|
self.gcn = gcn
|
||
|
|
def forward(self, x, graph = None, cnn_res = None, gcn_res = None, graph_node = None):
|
||
|
|
x1 = F.relu(self.fc1(self.pos_emb(x[:, 0].long())))
|
||
|
|
x2 = F.relu(self.fc2(x1))
|
||
|
|
value = self.state_value(x2)
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
class PPO():
|
||
|
|
clip_param = 0.2
|
||
|
|
max_grad_norm = 0.5
|
||
|
|
ppo_epoch = 10
|
||
|
|
if placed_num_macro:
|
||
|
|
buffer_capacity = 10 * (placed_num_macro)
|
||
|
|
else:
|
||
|
|
buffer_capacity = 5120
|
||
|
|
batch_size = args.batch_size
|
||
|
|
print("batch_size = {}".format(batch_size))
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
super(PPO, self).__init__()
|
||
|
|
self.gcn = None
|
||
|
|
self.resnet = torchvision.models.resnet18(pretrained=True)
|
||
|
|
self.cnn = MyCNN().to(device)
|
||
|
|
self.cnn_coarse = MyCNNCoarse(self.resnet).to(device)
|
||
|
|
self.actor_net = Actor(cnn = self.cnn, gcn = self.gcn, cnn_coarse = self.cnn_coarse).float().to(device)
|
||
|
|
self.critic_net = Critic(cnn = self.cnn, gcn = self.gcn, cnn_coarse = None, res_net = self.resnet).float().to(device)
|
||
|
|
self.buffer = []
|
||
|
|
self.counter = 0
|
||
|
|
self.training_step = 0
|
||
|
|
self.actor_optimizer = optim.Adam(self.actor_net.parameters(), args.lr)
|
||
|
|
self.critic_net_optimizer = optim.Adam(self.critic_net.parameters(), args.lr)
|
||
|
|
|
||
|
|
def load_param(self, path):
|
||
|
|
checkpoint = torch.load(path, map_location=torch.device(device))
|
||
|
|
self.actor_net.load_state_dict(checkpoint['actor_net_dict'])
|
||
|
|
self.critic_net.load_state_dict(checkpoint['critic_net_dict'])
|
||
|
|
|
||
|
|
def select_action(self, state):
|
||
|
|
state = torch.from_numpy(state).float().to(device).unsqueeze(0)
|
||
|
|
with torch.no_grad():
|
||
|
|
action_probs, _, _ = self.actor_net(state)
|
||
|
|
dist = Categorical(action_probs)
|
||
|
|
action = dist.sample()
|
||
|
|
action_log_prob = dist.log_prob(action)
|
||
|
|
return action.item(), action_log_prob.item()
|
||
|
|
|
||
|
|
def get_value(self, state):
|
||
|
|
state = torch.from_numpy(state)
|
||
|
|
with torch.no_grad():
|
||
|
|
value = self.critic_net(state)
|
||
|
|
return value.item()
|
||
|
|
|
||
|
|
def save_param(self, running_reward):
|
||
|
|
strftime = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
|
||
|
|
if not os.path.exists("save_models"):
|
||
|
|
os.mkdir("save_models")
|
||
|
|
torch.save({"actor_net_dict": self.actor_net.state_dict(),
|
||
|
|
"critic_net_dict": self.critic_net.state_dict()},
|
||
|
|
"./save_models/net_dict-{}-{}-".format(benchmark, placed_num_macro)+strftime+"{}".format(int(running_reward))+".pkl")
|
||
|
|
|
||
|
|
def store_transition(self, transition):
|
||
|
|
self.buffer.append(transition)
|
||
|
|
self.counter+=1
|
||
|
|
return self.counter % self.buffer_capacity == 0
|
||
|
|
|
||
|
|
def update(self):
|
||
|
|
state = torch.tensor(np.array([t.state for t in self.buffer]), dtype=torch.float)
|
||
|
|
action = torch.tensor(np.array([t.action for t in self.buffer]), dtype=torch.float).view(-1, 1).to(device)
|
||
|
|
reward = torch.tensor(np.array([t.reward for t in self.buffer]), dtype=torch.float).view(-1, 1).to(device)
|
||
|
|
old_action_log_prob = torch.tensor(np.array([t.a_log_prob for t in self.buffer]), dtype=torch.float).view(-1, 1).to(device)
|
||
|
|
del self.buffer[:]
|
||
|
|
target_list = []
|
||
|
|
target = 0
|
||
|
|
for i in range(reward.shape[0]-1, -1, -1):
|
||
|
|
if state[i, 0] >= placed_num_macro - 1:
|
||
|
|
target = 0
|
||
|
|
r = reward[i, 0].item()
|
||
|
|
target = r + args.gamma * target
|
||
|
|
target_list.append(target)
|
||
|
|
target_list.reverse()
|
||
|
|
target_v_all = torch.tensor(np.array([t for t in target_list]), dtype=torch.float).view(-1, 1).to(device)
|
||
|
|
|
||
|
|
for _ in range(self.ppo_epoch): # iteration ppo_epoch
|
||
|
|
for index in tqdm(BatchSampler(SubsetRandomSampler(range(self.buffer_capacity)), self.batch_size, True),
|
||
|
|
disable = args.disable_tqdm):
|
||
|
|
self.training_step +=1
|
||
|
|
|
||
|
|
action_probs, _, _ = self.actor_net(state[index].to(device))
|
||
|
|
dist = Categorical(action_probs)
|
||
|
|
action_log_prob = dist.log_prob(action[index].squeeze())
|
||
|
|
ratio = torch.exp(action_log_prob - old_action_log_prob[index].squeeze())
|
||
|
|
target_v = target_v_all[index]
|
||
|
|
critic_net_output = self.critic_net(state[index].to(device))
|
||
|
|
advantage = (target_v - critic_net_output).detach()
|
||
|
|
|
||
|
|
L1 = ratio * advantage.squeeze()
|
||
|
|
L2 = torch.clamp(ratio, 1-self.clip_param, 1+self.clip_param) * advantage.squeeze()
|
||
|
|
action_loss = -torch.min(L1, L2).mean() # MAX->MIN desent
|
||
|
|
|
||
|
|
self.actor_optimizer.zero_grad()
|
||
|
|
action_loss.backward()
|
||
|
|
nn.utils.clip_grad_norm_(self.actor_net.parameters(), self.max_grad_norm)
|
||
|
|
self.actor_optimizer.step()
|
||
|
|
|
||
|
|
value_loss = F.smooth_l1_loss(self.critic_net(state[index].to(device)), target_v)
|
||
|
|
self.critic_net_optimizer.zero_grad()
|
||
|
|
value_loss.backward()
|
||
|
|
nn.utils.clip_grad_norm_(self.critic_net.parameters(), self.max_grad_norm)
|
||
|
|
self.critic_net_optimizer.step()
|
||
|
|
|
||
|
|
writer.add_scalar('action_loss', action_loss, self.training_step)
|
||
|
|
writer.add_scalar('value_loss', value_loss, self.training_step)
|
||
|
|
|
||
|
|
|
||
|
|
def save_placement(file_path, node_pos, ratio):
|
||
|
|
fwrite = open(file_path, 'w')
|
||
|
|
node_place = {}
|
||
|
|
for node_name in node_pos:
|
||
|
|
|
||
|
|
x, y,_ , _ = node_pos[node_name]
|
||
|
|
x = round(x * ratio + ratio)
|
||
|
|
y = round(y * ratio + ratio)
|
||
|
|
node_place[node_name] = (x, y)
|
||
|
|
print("len node_place", len(node_place))
|
||
|
|
for node_name in placedb.node_info:
|
||
|
|
if node_name not in node_place:
|
||
|
|
continue
|
||
|
|
x, y = node_place[node_name]
|
||
|
|
fwrite.write('{}\t{}\t{}\t:\tN /FIXED\n'.format(node_name, x, y))
|
||
|
|
print(".pl has been saved to {}.".format(file_path))
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
|
||
|
|
agent = PPO()
|
||
|
|
strftime = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
|
||
|
|
|
||
|
|
training_records = []
|
||
|
|
running_reward = -1000000
|
||
|
|
|
||
|
|
|
||
|
|
log_file_name = "logs/log_"+ benchmark + "_" + strftime + "_seed_"+ str(args.seed) + "_pnm_" + str(args.pnm) + ".csv"
|
||
|
|
if not os.path.exists("logs"):
|
||
|
|
os.mkdir("logs")
|
||
|
|
fwrite = open(log_file_name, "w")
|
||
|
|
load_model_path = None
|
||
|
|
|
||
|
|
if load_model_path:
|
||
|
|
agent.load_param(load_model_path)
|
||
|
|
|
||
|
|
best_reward = running_reward
|
||
|
|
if args.is_test:
|
||
|
|
torch.inference_mode()
|
||
|
|
|
||
|
|
for i_epoch in range(100000):
|
||
|
|
score = 0
|
||
|
|
raw_score = 0
|
||
|
|
start = time.time()
|
||
|
|
state = env.reset()
|
||
|
|
|
||
|
|
done = False
|
||
|
|
while done is False:
|
||
|
|
state_tmp = state.copy()
|
||
|
|
action, action_log_prob = agent.select_action(state)
|
||
|
|
|
||
|
|
next_state, reward, done, info = env.step(action)
|
||
|
|
assert next_state.shape == (num_state, )
|
||
|
|
reward_intrinsic = 0
|
||
|
|
if not args.is_test:
|
||
|
|
trans = Transition(state_tmp, action, reward / 200.0, action_log_prob, next_state, reward_intrinsic)
|
||
|
|
if not args.is_test and agent.store_transition(trans):
|
||
|
|
assert done == True
|
||
|
|
agent.update()
|
||
|
|
score += reward
|
||
|
|
raw_score += info["raw_reward"]
|
||
|
|
state = next_state
|
||
|
|
end = time.time()
|
||
|
|
|
||
|
|
if i_epoch == 0:
|
||
|
|
running_reward = score
|
||
|
|
running_reward = running_reward * 0.9 + score * 0.1
|
||
|
|
print("score = {}, raw_score = {}".format(score, raw_score))
|
||
|
|
|
||
|
|
if running_reward > best_reward * 0.975:
|
||
|
|
best_reward = running_reward
|
||
|
|
if i_epoch >= 10:
|
||
|
|
agent.save_param(running_reward)
|
||
|
|
if args.save_fig:
|
||
|
|
strftime_now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
|
||
|
|
if not os.path.exists("figures"):
|
||
|
|
os.mkdir("figures")
|
||
|
|
env.save_fig("./figures/{}{}.png".format(strftime_now,int(raw_score)))
|
||
|
|
print("save_figure: figures/{}{}.png".format(strftime_now,int(raw_score)))
|
||
|
|
try:
|
||
|
|
print("start try")
|
||
|
|
# cost is the routing estimation based on the MST algorithm
|
||
|
|
hpwl, cost = comp_res(placedb, env.node_pos, env.ratio)
|
||
|
|
print("hpwl = {:.2f}\tcost = {:.2f}".format(hpwl, cost))
|
||
|
|
except:
|
||
|
|
assert False
|
||
|
|
|
||
|
|
if args.is_test:
|
||
|
|
print("save node_pos")
|
||
|
|
hpwl, cost = comp_res(placedb, env.node_pos, env.ratio)
|
||
|
|
print("hpwl = {:.2f}\tcost = {:.2f}".format(hpwl, cost))
|
||
|
|
print("time = {}s".format(end-start))
|
||
|
|
pl_file_path = "{}-{}-{}.pl".format(benchmark, int(hpwl), time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) )
|
||
|
|
save_placement(pl_file_path, env.node_pos, env.ratio)
|
||
|
|
strftime_now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
|
||
|
|
pl_path = 'gg_place_new/{}-{}-{}-{}.pl'.format(benchmark, strftime_now, int(hpwl), int(cost))
|
||
|
|
fwrite_pl = open(pl_path, 'w')
|
||
|
|
for node_name in env.node_pos:
|
||
|
|
if node_name == "V":
|
||
|
|
continue
|
||
|
|
x, y, size_x, size_y = env.node_pos[node_name]
|
||
|
|
x = x * env.ratio + placedb.node_info[node_name]['x'] /2.0
|
||
|
|
y = y * env.ratio + placedb.node_info[node_name]['y'] /2.0
|
||
|
|
fwrite_pl.write("{}\t{:.4f}\t{:.4f}\n".format(node_name, x, y))
|
||
|
|
fwrite_pl.close()
|
||
|
|
strftime_now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
|
||
|
|
env.save_fig("./figures/{}-{}-{}-{}.png".format(benchmark, strftime_now, int(hpwl), int(cost)))
|
||
|
|
|
||
|
|
training_records.append(TrainingRecord(i_epoch, running_reward))
|
||
|
|
if i_epoch % 1 ==0:
|
||
|
|
print("Epoch {}, Moving average score is: {:.2f} ".format(i_epoch, running_reward))
|
||
|
|
fwrite.write("{},{},{:.2f},{}\n".format(i_epoch, score, running_reward, agent.training_step))
|
||
|
|
fwrite.flush()
|
||
|
|
writer.add_scalar('reward', running_reward, i_epoch)
|
||
|
|
if running_reward > -100:
|
||
|
|
print("Solved! Moving average score is now {}!".format(running_reward))
|
||
|
|
env.close()
|
||
|
|
agent.save_param()
|
||
|
|
break
|
||
|
|
if i_epoch % 100 == 0:
|
||
|
|
if placed_num_macro is None:
|
||
|
|
env.write_gl_file("./gl/{}{}.gl".format(strftime, int(score)))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|