Gem5模拟器,popnet模拟器源码记录(十五)

文章探讨了使用Gem5进行大规模Chiplet并行仿真的问题,指出Gem5由于在单核上运行导致并行效率低下,以及精度不足的原因。为解决这些问题,论文提出结合Popnet模拟器来模拟通信记录并计算延时和能耗。作者在Popnet中遇到的挑战包括选择路由策略、理解和验证输出结果的准确性。文章详细分析了不同交换技术和路由算法,并提供了对Popnet模拟器源码的理解。
摘要由CSDN通过智能技术生成

在使用王小航老师课题组开源的Gem5代码进行大规模的Chiplet并行仿真时,有个关键问题是:为什么原来的gem5不可以进行大规模仿真?为什么精度不够?也是论文提出的背景:

(20条消息) 傻白探索Chiplet,A Methodology for Simulating Multi-chiplet Systems UsingOpen-source Simulators(十)_好啊啊啊啊的博客-CSDN博客

 当时感受不是很清楚,以我现在的观点来看,无法进行大规模的并行仿真是由于Gem5虽然支持多核仿真,但是Gem5本身是在单核上运行的,无论你主机有多少个核只用得上一个,极大地限制了Gem5的运行,我测过,就跑一个很简单的256*256的矩阵乘法,用默认配置(开启L1Cache + 512MB 内存 + AtomicSimpleCPU)在64核上得半小时,如果你改用TimingSimpleCPU你还不一定能跑起来,因此大规模并行仿真确实不太行;

精度不够,考虑两个原因,精度要高可以考虑使用O3 CPU模型,但核多了会很卡,另一方面你在测算某个benchmark时,计算完成虽有stats.txt可以看统计结果,但是如果运行完成后你不及时结束,sim_tick和final_tick以及一些和时间相关的参数也会跟着变大;此外,在考虑Chiplet通信时,因为论文中使用的是文件读写来实现通信,但是实际中并不是这样,你可能要考虑实际传输的链路时延和能耗、路由器转发也需要时间和能耗、interposer是否存在等等,这些在gem5模拟器中是没有被考虑到的,因为gem5是一个通用CPU模拟器,这是我觉得论文说Gem5精度不够的原因。

综上,论文提出了用gem5来模拟系统结构并得到通信记录,再通过popnet模拟器来测算这个通信记录的延时和能耗,这个就给了你比较多的发挥空间,你可以选用不同的拓扑结构、不同的路由策略、是否考虑中介层等等。但是popnet模拟器的文档对我这样的小白来说还是不够清晰,它本身提供了多种路由策略,但是比较模棱两可的是:

  • 作者没有告诉我们如何选取路由策略?选取依据是什么?不同的路由策略有何差异
  • 选择的路由策略中,拓扑何如?需要怎样的输入才能让这个路由算法跑起来?
  • 如果跑出结果了,我如何判断这个输出是对的,是和我的输入文件能够对应起来的?

抱着以上疑问,我开启了阅读popnet源码的不归之路,我只想顺顺利利的做个靠谱的实验 (T_T)

获取popnet模拟器代码:

git clone https://gitee.com/hic_0757/popnet_modified.git

目录

一、相关知识:

二、Q&A

1. 如何选取路由策略?选取依据是什么?不同的路由策略有何差异 

2. 选择的路由策略中,拓扑何如?需要怎样的输入才能让这个路由算法跑起来?

3. 如果跑出结果了,我如何判断这个输出是对的,是和我的输入文件能够对应起来的?

三、源码记录


一、相关知识:

由于我是小白,之前并未接触过片上网络模拟器,查看论文之后才知道popnet模拟器中提到的微片flit是何意,常用的交换技术有:电路交换,存储转发,虚切入,虫孔等等,以下:

  • 电路交换属于 bufferless 的流控机制,即在源节点和目标节点之间没有任何缓冲空间,分配若干个顺次连续的通道构成从源节点到目标节点的路径,当传送完毕后电路就取消;如果消息传送不频繁并且消息较长,那么,电路交换是有优势的。由于在消息发送期间保留整个物理路径,电路交换可能阻塞其他消息。由于通信局部性,电路交换适用于专用 SoC 设计 ;

报文交换将消息划分成固定长度的报文,每个报文的前几个字节包含路由和控制信息。如果消息较短且发送频繁,那么,报文交换是有优势的。

  • 存储转发流控机制中,每个节点都要等到包全部到达后才能把它发送给下一个节点,这种方法的缺点是在每个节点上的时延太久;
  • 虚切入机制(VCT)也是 packet-buffer 流控机制,以包为单位申请 buffer 空间,它与存储转发机制的不同之处在于,一旦包头已经到达,就可以把它送给下一个节点,不用等待整个包都到达,延时较小,只是申请空间太多;
  • 虫孔是 flit-buffer流控机制(WH),将报文分割成多个flit,即以片( flit ) 为单位申请通道另一端的空间,只要输出通道另一端有一个片大小的 buffer 空间,就可以把输入端口的一片送给下一个节点,不用等整个包都到达,后面所有的片都会跟着包头片(包含路由和控制信息,其余微片只包含数据)走同样的路径。

在poopnet模拟器中,A Methodology for Simulating Multi-chiplet Systems UsingOpen-source Simulators论文中使用的应该是虫孔机制。从性能的角度,VCT 优于 WH;从代价的角度,WH 优于 VCT。


二、Q&A

1. 如何选取路由策略?选取依据是什么?不同的路由策略有何差异 

  • 我是懒王,想断更,看明白了个五五六六,XY算法是比较经典的路由算法,XY算法和TXY算法本质上是相似的,它们都是基于DOR( dimension order routing )的路由算法,只是TXY算法在XY算法的基础上加入了Torus结构的特殊处理,使得在2D-Torus结构中表现更为优秀;
  • CHPLET_STAR_TOPO_ROUTING算法和CHIPLET_STAR_TOPO_ROUTING算法没仔细研究,因为跑不出来结果,会段错误;
  • GRAPH_TOPO算法和RECONFIGURABLE_GRAPH_TOPO算法都支持任意拓扑的路由,区别在于后者还需要一个额外的配置文件(好像是考虑了中介层,所以需要额外表明路由节点的连接情况),具体看完了忘记了🐟。

选取依据:看你想要啥样的拓扑结构,前两个是我觉得路由节点是mesh的(哦买噶,怎么和论文说的Torus是悖论,之后再看看确定一下);你想要自定义托拓扑的话就第五个,可以自己写一个mesh、crossbar或者star的网络。

2. 选择的路由策略中,拓扑何如?需要怎样的输入才能让这个路由算法跑起来?

拓扑勉强也算在上个问题说了,有个问题就是,作者应该是为了图方便,在源节点向目的节点发送数据时,固定写“0 x 0 y”,因此关于ary_number_就是指每个维度上的路由器个数,比如一个二维的9*9网络的此参数就是9。即使我们只有9个节点,为了不超过这个拓扑的9*9大小,我们仍然需要指定ary_number_=9。

输入:如果是1和2的话,无需拓扑结构,它内部自己应该有实现;如果是第5个需要自定义的拓扑结构(源码中有教如何构建)和通信的trace文件;第6个除了需要拓扑结构、通信的trace还有一份额外的配置文件。

3. 如果跑出结果了,我如何判断这个输出是对的,是和我的输入文件能够对应起来的?

这个popnet模拟器数据注入是根据trace文件的时钟周期来的,到那个时钟周期就会有数据注入(触发EVG_事件),继而调用ROUTER_事件,在相应触发CREDI_和WIRE_。如此,如果你的时钟越大,你路由的时间就越长,最终查看你总共完成的packet数目是否等于trace文件中的消息记录,相等就代表已完成。

 注意:整个路由过程本来就是一个死循环,时刻在检测是否有新数据进入。


三、源码记录

以下是我阅读源码时的一些记录,仅供参考,有错误还请各位大佬批评指正,我只是个弱小的菜菜--\(˙<>˙)/--:

main.py: 

int main(int argc, char *argv[])
{
	try {
		SRGen random_gen;
		configuration c_par(argc, argv);  //使用命令行参数 argc 和 argv 创建一个 configuration 类的实例 c_par,用于读取程序的配置参数。
		cout<<c_par;
		//changed at 2020-5-8
		//mess_queue network_mess_queue(0.0);
		//创建一个 mess_queue 类的实例 network_mess_queue(初始化配置参数),用于存储网络中的消息。
		mess_queue network_mess_queue(TIME_0);
		//创建一个 sim_foundation 类的实例 sim_net,用于模拟网络。
		sim_foundation sim_net;
		//调用 mess_queue 类实例的 simulator 函数,模拟消息队列中的消息。
		network_mess_queue.simulator();
		//调用 sim_foundation 类实例的 simulation_results 函数,输出模拟结果。
		sim_net.simulation_results();
	} catch (exception & e) {
		cerr << e.what();
	}
}

mess_queue.h和mess_queue.cc:主要是创建优先级队列(重载<运算符,以时钟周期作为优先级,始终越小越靠前),以及消息的入队、出队操作,通过构造函数进行初始化。以及simulator()函数负责整个过程的模拟调用,setInitEvent()函数根据不同的路由算法,将其包装成不同的路由事件,放到消息队列中。

mess_queue(time_type start_time=TIME_0);  //TIME_0=0或无限接近于0
void mess_queue::simulator() {
	//changed at 2020-5-23
	//static uint64_t wireMsgCnt=0,creditMsgCnt=0;
	//time_type report_t = 0;
	time_type report_t=TIME_0;  // 0
	long total_incoming = 0;
	//根据不同的路由算法,将其包装成不同的路由事件,放到消息队列中
	setInitEvent();
	//修改此循环
	//when mess queue is not empty and simulation deadline has not reach
	// 事件队列不为空,时间还没有达到指定的最大周期
	while(m_q_.size() > 0 && (current_time_ <= (configuration
					::ap().sim_length()))) {
		//changed at 2021-10-26
		//mess_event current_message = * get_message();
		// 返回消息队列中优先级最高的事件(按照时间排序,路由事件都用0作为其优先级,意味着路由事件是最先得到处理的)
		mess_event current_message=get_message();
		//changed at 2020-5-23
		//输出日志
		//logfile<<current_message;
		if(current_message.event_type()==WIRE_||current_message.event_type()==CREDIT_){
			logfile<<"\tFrom Router "<<current_message.src()
				<<" to Router "<<current_message.des()
				<<" Port "<<current_message.pc()
				<<" Virtual Channel "<<current_message.vc()<<endl;
		}
		// 移除优先级最高的队列
		remove_top_message();
		//保证当前时间小于或等于事件开始时间
		Sassert(static_cast<bool>(current_time_ <= ((current_message.
					event_start()) + S_ELPS_)));
		//if(current_time_>current_message.event_start()+S_ELPS_)continue;
		// 返回事件的开始时间
		current_time_ = current_message.event_start();

		if(current_time_ > report_t) {
		   cout<<"Current time: "<<current_time_<<" Incoming packets: "
			   <<total_incoming<<" Finished packets: "<<total_finished_<<endl;
			//changed at 2020-5-23
			//cout<<"Wire Message Count: "<<wireMsgCnt<<" Credit Message Count: "<<creditMsgCnt<<endl;
		   sim_foundation::wsf().simulation_results();
		   //changed at 2022-4-13
		   //自定义报告周期
		   //report_t += REPORT_PERIOD_;
		   report_t+=configuration::ap().getReportPeriod();
		}
		
		switch(current_message.event_type()) {

			case EVG_ ://读取轨迹文件的下一条记录
				//TODO: 改为读取外部输入
				// 因为sim_foundation是一个单例对象,使用wsf可以返回这个对象并对其进行访问;可以方便地访问sim_foundation类的成员,无需创建多个对象
				// 接收一个EVG类型的消息,并从输入trace中读取下一个数据包,并注入到下一跳路由器中,将一个新的EVG类型的消息加入到消息队列中
				sim_foundation::wsf().receive_EVG_message(current_message);
				total_incoming ++;
			break;

			case ROUTER_ :
				// 将一个新的ROUTER类型的消息加入到消息队列中, 并执行流水线路由,同时计算出多个路由器的下一跳,会唤起WIRE_和CREDIT_事件
				sim_foundation::wsf().receive_ROUTER_message(current_message);
			break;

			case WIRE_ :
				// 根据当前信息的目的地址,投递到目的路由器的输入缓冲区
				sim_foundation::wsf().receive_WIRE_message(current_message);
				//changed at 2020-5-23
				//wireMsgCnt++;
			break;

			case CREDIT_ :
				// 将消息的目的路由器对应端口的虚拟通道的值+1
				sim_foundation::wsf().receive_CREDIT_message(current_message);
				//changed at 2020-5-23
				//creditMsgCnt++;
			break;
			
			case RECONFIGURATION:
				// 根据该消息中包含的事件时间和当前路由器的重新配置周期,将路由器的拓扑结构进行重新配置
				sim_foundation::wsf().receive_RECONFIGURATION_message(current_message);
				break;

			default:
				throw pro_error(mess_error_);
			break;
		} 
	} 
}

sim_foundation.h和sim_foundation.cc:

构造函数:

  • 初始化各种参数和数据结构,包括网络拓扑结构的大小、缓存的大小、flit的大小、路由算法等等;
  • 根据不同的路由算法生成不同的路由表、 能耗表、延迟表、端口映射表(路由器下一跳端口/代价/地址) 、下一跳端口映射表(路由器连接的端口);
  • 根据网络拓扑结构的大小和ary_number计算出网络中路由器的数量,然后逐个创建路由器对象并记录其拓扑位置(在创建每个路由器对象时,需要为其指定一些属性,包括物理端口数量、虚拟通道数量、缓存大小、输出缓存大小、flit大小等);
  • 最后,调用init_file函数进行文件的初始化,读入所有轨迹文件,并将第一条消息记录包装成EVG_ (注入)事件

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值