简介:这份网络切片仿真资源包面向5G通信网络方向的研究人员、高校师生及工程技术人员,用于在共享物理基础设施上模拟多个独立逻辑网络的部署、资源分配与性能评估。内容围绕网络切片核心知识展开,涵盖NFV与SDN虚拟化技术、SLA服务等级协议设计、计算存储通信资源分配、切片安全隔离机制以及动态调整与自愈能力等关键议题,并涉及自动驾驶、远程医疗、智能工厂等5G典型应用场景的性能预测与优化。资源包共67个文件,约280KB,以m脚本、ot工程文件、c源码、obj目标文件为主,辅以exp、lib、dll、prj等工程配置与库文件,构成一套可运行的仿真工程结构,便于读者直接加载分析切片模型与调度流程。目前已有1029人学习下载,适合希望借助仿真手段深入理解网络切片架构、验证资源管理策略并探索未来通信网络演进的读者参考使用。
1. 网络切片仿真.rar:一个压缩包背后,5G 切片验证到底在仿什么
你拿到一个叫「网络切片仿真.rar」的压缩包,解压后大概率是一堆 MATLAB 脚本、Python 文件或者 NS-3 的 tcl 配置。别急着双击运行,先想清楚一件事:网络切片仿真的核心不是「跑起来」,而是「在单台机器上复现多租户共享物理网络时,每个切片的 SLA 能不能被单独验证」。这解决的是真实 5G 核心网和接入网设备太贵、切片隔离策略调一次要等一周的问题。适合谁?做 5G 切片算法验证的研究生、需要给客户演示切片隔离效果但拿不到现网资源的售前工程师、以及想从零理解「无线资源怎么按切片切」的开发者。这个压缩包里的东西,本质是一套可复现的沙盒,让你在本地把 eMBB、URLLC、mMTC 三种切片混跑,看谁抢了谁的资源。
2. 先搞懂切片仿真的三层建模:从业务流到资源块
2.1 为什么不能直接用真实核心网做切片验证
真实 5G 网络里,网络切片横跨终端、无线接入网、承载网、核心网四个域。你要验证一个 URLLC 切片在拥塞时能不能保住 1ms 时延,得同时改 RAN 的调度权重、核心网 UPF 的 QoS Flow 映射、传输网的 VLAN 优先级。现网里动任何一层都要走变更流程,而且三张切片同时跑,出了问题根本不知道是哪层先崩的。
仿真环境的价值在于:把四层压缩成三个可独立控制的模块——业务模型、资源模型、调度策略。业务模型决定每个切片产生什么样的包(大小、周期、突发性),资源模型决定物理层有多少 RB、多少带宽、多少 MEC 算力,调度策略决定这些资源怎么分。压缩包里不管用什么语言写的,骨架一定是这三块。
常见做法是:用 Python 写业务流生成和统计,用 MATLAB 写调度算法,用 NS-3 做包级仿真。但如果你拿到的包只有一种语言,大概率是纯 MATLAB 或者纯 Python 的简化模型,牺牲了包级精度换可读性。
2.2 业务流建模:eMBB、URLLC、mMTC 的参数差异在哪
三种切片的业务特征完全不同,仿真里必须用不同的随机过程描述。eMBB 是高速下载,包大、持续、对时延不敏感但对速率敏感;URLLC 是工业控制指令,包小、周期短、对时延和可靠性极度敏感;mMTC 是传感器上报,包极小、并发数巨大、对时延几乎无要求。
下面是一个业务流生成的最小 Python 示例,我一般用这个结构做快速验证:
import numpy as np def generate_embb_traffic(duration_ms, packet_size_bytes=1500, interval_ms=1): """eMBB: 大包、连续发送,模拟视频流或 FTP 下载""" num_packets = int(duration_ms / interval_ms) # 包大小加 10% 抖动,模拟编码速率变化 sizes = np.random.normal(packet_size_bytes, packet_size_bytes * 0.1, num_packets) timestamps = np.arange(0, duration_ms, interval_ms) return timestamps, sizes.astype(int) def generate_urllc_traffic(duration_ms, packet_size_bytes=64, period_ms=1): """URLLC: 小包、严格周期,模拟工业控制指令""" num_packets = int(duration_ms / period_ms) # 周期抖动控制在 5% 以内,否则时延统计会失真 jitter = np.random.uniform(-0.05, 0.05, num_packets) * period_ms timestamps = np.arange(0, duration_ms, period_ms) + jitter sizes = np.full(num_packets, packet_size_bytes) return timestamps, sizes def generate_mmtc_traffic(num_devices, duration_ms, report_interval_ms=1000): """mMTC: 海量设备、稀疏上报,模拟传感器""" all_timestamps = [] all_sizes = [] for _ in range(num_devices): # 每个设备的上报时刻加随机偏移,避免所有设备同时发 offset = np.random.uniform(0, report_interval_ms) ts = np.arange(offset, duration_ms, report_interval_ms) all_timestamps.extend(ts) all_sizes.extend(np.random.randint(20, 50, len(ts))) return np.array(all_timestamps), np.array(all_sizes)逻辑说明:eMBB 用正态分布加抖动,因为真实视频流的包大小随编码内容波动;URLLC 的周期抖动必须很小,否则你测出来的时延抖动其实是业务源自己产生的,不是网络调度导致的;mMTC 的关键是设备间的相位偏移,如果所有设备从 0 时刻开始周期上报,会在仿真开始瞬间产生一个巨大的突发,把资源模型冲垮。
参数怎么改:packet_size_bytes按你实际要模拟的业务改,eMBB 常见 1500,URLLC 常见 32 到 200,mMTC 常见 20 到 100。interval_ms或period_ms决定负载强度,改小就是加压。num_devices在 mMTC 里直接决定并发连接数,一般从 100 起步,逐步加到 1000 看资源模型什么时候崩。
2.3 资源模型:RB 怎么分给不同切片
无线侧的资源单位是 RB(Resource Block),时域 1ms、频域 180kHz。一个 20MHz 带宽的载波有 100 个 RB。仿真里你不需要真的去算 OFDM 符号,但必须把 RB 抽象成「时间-频率二维网格」,然后按切片分配。
常见做法有两种:静态预留和动态共享。静态预留是给每个切片固定分 RB,比如 eMBB 60 个、URLLC 20 个、mMTC 20 个。优点是隔离性好,缺点是资源利用率低。动态共享是所有切片抢同一个 RB 池,按调度算法实时分配。优点是利用率高,缺点是隔离性靠算法保证,算法翻车就全崩。
我一般先用静态预留跑通,确认业务模型和统计逻辑没问题,再切到动态共享调算法。下面是一个 RB 网格分配的简化实现:
class RBGrid: def __init__(self, num_rbs=100, num_ttis=1000): # 每个 TTI 是一个 1ms 的调度周期 self.grid = np.zeros((num_ttis, num_rbs)) # 0 表示空闲,1/2/3 表示切片 ID self.num_rbs = num_rbs self.num_ttis = num_ttis def allocate_static(self, slice_id, start_rb, num_allocated): """静态分配:从 start_rb 开始连续分配 num_allocated 个 RB""" self.grid[:, start_rb:start_rb + num_allocated] = slice_id def allocate_dynamic(self, tti, slice_priorities, demands): """动态分配:按优先级和需求在每个 TTI 重新分配""" remaining_rbs = self.num_rbs # 按优先级从高到低排序,URLLC 优先级最高 sorted_slices = sorted(slice_priorities.items(), key=lambda x: -x[1]) for slice_id, _ in sorted_slices: if remaining_rbs <= 0: break alloc = min(demands.get(slice_id, 0), remaining_rbs) # 从当前 TTI 的空闲 RB 里找连续块分配 free_rbs = np.where(self.grid[tti] == 0)[0] if len(free_rbs) >= alloc: self.grid[tti, free_rbs[:alloc]] = slice_id remaining_rbs -= alloc逻辑说明:grid的每一行是一个 TTI,每一列是一个 RB。静态分配直接切片,简单但浪费。动态分配每个 TTI 重新算,slice_priorities里 URLLC 给最高值(比如 3),eMBB 给 2,mMTC 给 1。demands是每个切片当前 TTI 需要的 RB 数,由业务模型推算。
参数怎么改:num_rbs按带宽改,20MHz 对应 100,10MHz 对应 50。num_ttis是仿真总时长,1000 就是 1 秒。动态分配里slice_priorities的数值决定抢占顺序,但注意如果 URLLC 一直占满,eMBB 会饿死,实际调的时候要加一个最小保障 RB 数。
3. 用 Python 把切片仿真跑起来:从环境搭建到出图
3.1 环境依赖和目录结构
压缩包解压后,不管原来是什么结构,我建议整理成下面这样,方便后续改参数和复现:
slice_sim/ ├── config/ │ └── slice_config.yaml # 切片参数、业务参数、资源参数 ├── src/ │ ├── traffic.py # 业务流生成 │ ├── resource.py # RB 网格和分配 │ ├── scheduler.py # 调度算法 │ └── metrics.py # 统计和出图 ├── run_sim.py # 主入口 └── requirements.txt依赖只需要 numpy、matplotlib、pyyaml。如果你拿到的包里有 NS-3 或 MATLAB 代码,Python 部分可以当预处理和后处理用,核心仿真还在原环境跑。
pip install numpy matplotlib pyyaml3.2 配置文件怎么写:把参数从代码里抽出来
把参数写死在代码里是复现的噩梦。我一般用 YAML 管所有可调参数:
slices: embb: priority: 2 packet_size: 1500 interval_ms: 1 static_rbs: 60 urllc: priority: 3 packet_size: 64 period_ms: 1 static_rbs: 20 mmtc: priority: 1 num_devices: 500 report_interval_ms: 1000 static_rbs: 20 simulation: duration_ms: 1000 num_rbs: 100 mode: "static" # static 或 dynamic逻辑说明:priority只在动态模式生效,static_rbs只在静态模式生效。duration_ms和num_rbs是全局参数。改模式只需要改一个字段,不用动代码。
参数怎么改:先跑静态模式确认三种切片的业务流都正常生成,再切动态模式看调度效果。num_devices从 500 开始,如果仿真太慢就降到 100,如果看不出拥塞就加到 1000。
3.3 主仿真循环和统计指标
主循环按 TTI 推进,每个 TTI 做三件事:生成业务包、分配 RB、记录统计。下面是一个最小可运行的主循环:
import yaml import numpy as np from src.traffic import generate_embb_traffic, generate_urllc_traffic, generate_mmtc_traffic from src.resource import RBGrid def run_simulation(config_path): with open(config_path) as f: cfg = yaml.safe_load(f) sim_cfg = cfg["simulation"] num_ttis = sim_cfg["duration_ms"] grid = RBGrid(num_rbs=sim_cfg["num_rbs"], num_ttis=num_ttis) # 生成各切片业务流 embb_ts, embb_sizes = generate_embb_traffic(num_ttis, cfg["slices"]["embb"]["packet_size"]) urllc_ts, urllc_sizes = generate_urllc_traffic(num_ttis, cfg["slices"]["urllc"]["packet_size"]) mmtc_ts, mmtc_sizes = generate_mmtc_traffic( cfg["slices"]["mmtc"]["num_devices"], num_ttis, cfg["slices"]["mmtc"]["report_interval_ms"] ) # 统计每个切片的成功传输包数和时延 stats = {"embb": {"tx": 0, "delay": []}, "urllc": {"tx": 0, "delay": []}, "mmtc": {"tx": 0, "delay": []}} for tti in range(num_ttis): # 静态模式:RB 已经预分配好,直接算每个切片能传多少包 if sim_cfg["mode"] == "static": embb_rbs = cfg["slices"]["embb"]["static_rbs"] urllc_rbs = cfg["slices"]["urllc"]["static_rbs"] mmtc_rbs = cfg["slices"]["mmtc"]["static_rbs"] else: # 动态模式:按优先级和需求分配,这里简化为按比例 total = sim_cfg["num_rbs"] embb_rbs = int(total * 0.6) urllc_rbs = int(total * 0.2) mmtc_rbs = int(total * 0.2) # 每个 RB 每个 TTI 能承载的字节数,简化按 1000 字节算 bytes_per_rb = 1000 for slice_name, rbs, ts, sizes in [ ("embb", embb_rbs, embb_ts, embb_sizes), ("urllc", urllc_rbs, urllc_ts, urllc_sizes), ("mmtc", mmtc_rbs, mmtc_ts, mmtc_sizes), ]: capacity = rbs * bytes_per_rb # 找当前 TTI 到达的包 mask = (ts >= tti) & (ts < tti + 1) arrived_sizes = sizes[mask] arrived_ts = ts[mask] for pkt_size, pkt_ts in zip(arrived_sizes, arrived_ts): if capacity >= pkt_size: capacity -= pkt_size stats[slice_name]["tx"] += 1 stats[slice_name]["delay"].append(tti - pkt_ts) else: break # 容量不够,后面的包排队或丢弃 return stats if __name__ == "__main__": stats = run_simulation("config/slice_config.yaml") for name, s in stats.items(): delays = s["delay"] if delays: print(f"{name}: tx={s['tx']}, avg_delay={np.mean(delays):.2f}ms, p99_delay={np.percentile(delays, 99):.2f}ms") else: print(f"{name}: tx=0")逻辑说明:bytes_per_rb是简化假设,真实值取决于 MCS 和调制方式,但做趋势验证够用。每个 TTI 检查哪些包到达,按容量依次传输。delay是当前 TTI 减去包生成时刻,单位 ms。p99 时延是 URLLC 切片最关键的指标,平均值会被大量小时延包拉低,看不出问题。
参数怎么改:bytes_per_rb改大就是信道条件好,改小就是信道差。动态模式里的比例分配可以换成真正的调度算法,比如按优先级排序后依次满足。num_ttis改大跑更长时间,但注意统计时延的数组会变长,内存不够就分批统计。
3.4 出图:把三个切片的时延 CDF 画在一张图上
import matplotlib.pyplot as plt def plot_cdf(stats): plt.figure(figsize=(8, 5)) for name, s in stats.items(): delays = np.sort(s["delay"]) if len(delays) == 0: continue cdf = np.arange(1, len(delays) + 1) / len(delays) plt.plot(delays, cdf, label=f"{name} (n={len(delays)})") plt.xlabel("Delay (ms)") plt.ylabel("CDF") plt.legend() plt.grid(True, alpha=0.3) plt.savefig("slice_delay_cdf.png", dpi=150) plt.show()逻辑说明:CDF 图能一眼看出 URLLC 的时延是不是集中在 1ms 以内,eMBB 是不是有长尾。如果 URLLC 的 CDF 在 1ms 处只有 80%,说明有 20% 的包超时,调度算法需要调。
参数怎么改:dpi改大出图更清晰,figsize按需调。如果三个切片的时延量级差太多,可以用对数坐标,但 URLLC 的 1ms 要求用线性坐标更直观。
4. 避坑:切片仿真里最容易翻车的 5 个地方
4.1 现象:URLLC 时延统计出来是 0.5ms,但实际业务要求 1ms 内,看起来达标了
原因:业务流生成时,URLLC 包的生成时刻和 TTI 边界对齐了。比如包在 t=0.0ms 生成,第一个 TTI 是 0 到 1ms,传输完成时刻算作 1ms,时延是 1ms。但如果包在 t=0.9ms 生成,同样在第一个 TTI 传完,时延只有 0.1ms。平均下来时延被拉低,但最坏情况可能超过 1ms。
解决:统计时延要用「包生成时刻到传输完成时刻」的差值,而不是「TTI 序号差」。并且必须看 p99 或最大值,不能只看平均。我一般会在业务流生成时加一个随机相位偏移,让包到达时刻均匀分布在 TTI 内,避免对齐带来的统计偏差。
4.2 现象:动态调度模式下,mMTC 切片几乎传不出去包
原因:mMTC 优先级最低,动态分配时 URLLC 和 eMBB 先把 RB 抢完,mMTC 只能捡剩下的。如果 URLLC 和 eMBB 的负载都很高,mMTC 会长期饿死。
解决:给每个切片设一个最小保障 RB 数,不管优先级多低,先扣掉保障部分再参与动态竞争。或者在调度算法里加一个老化机制,等待时间越长的包优先级越高。我一般用最小保障加简单轮询,先保证没有切片完全饿死,再调优先级。
4.3 现象:仿真跑完发现 eMBB 的吞吐量远低于理论值
原因:bytes_per_rb设得太保守,或者 RB 分配时没有考虑 MIMO 和载波聚合。真实 5G 一个 RB 在 20MHz 带宽、256QAM、4x4 MIMO 下能承载的字节数远大于 1000。
解决:bytes_per_rb按实际配置算。简化公式是:bytes_per_rb = 12 子载波 × 14 符号 × 调制阶数 × 码率 / 8。256QAM 调制阶数是 8,码率 0.9 左右,算下来一个 RB 约 1500 到 2000 字节。如果开了 MIMO,再乘层数。但注意这是峰值,实际调度时还要考虑控制信道开销和参考信号,打八折比较稳妥。
4.4 现象:改了配置文件里的参数,但仿真结果完全没变
原因:代码里硬编码了参数,YAML 文件只是摆设。或者主入口读的是另一个配置文件。
解决:在run_simulation开头加一行打印,把读到的配置输出出来,确认参数生效。我一般会在每个模块初始化时打印关键参数,比如print(f"URLLC period: {cfg['slices']['urllc']['period_ms']}ms")。如果打印值和配置文件不一致,说明读错了文件或者被代码覆盖了。
4.5 现象:仿真跑得特别慢,1000 个 TTI 要跑好几分钟
原因:每个 TTI 都在做全量数组扫描,或者业务流生成时用了 Python 循环而不是 numpy 向量化。
解决:把业务流生成全部向量化,generate_mmtc_traffic里的 for 循环改成 numpy 的np.repeat和np.tile。主循环里找当前 TTI 到达的包,不要每次扫全量数组,提前按时间排序后用指针推进。如果还是慢,把统计部分改成只在最后做一次,中间只记录原始数据。
5. 进阶:用置信区间判断仿真结果是不是「玄学」
仿真跑出来的数字,最怕的是「这次 URLLC 达标了,下次又不达标」。单次仿真的结果受随机种子影响很大,尤其是 URLLC 的 p99 时延,可能因为几个包的随机抖动就超标。我一般会跑 20 次不同随机种子的仿真,然后算均值和 95% 置信区间。
def run_multiple_seeds(config_path, num_runs=20): all_p99 = {"embb": [], "urllc": [], "mmtc": []} for seed in range(num_runs): np.random.seed(seed) stats = run_simulation(config_path) for name, s in stats.items(): if s["delay"]: all_p99[name].append(np.percentile(s["delay"], 99)) for name, values in all_p99.items(): values = np.array(values) mean = np.mean(values) ci_low = np.percentile(values, 2.5) ci_high = np.percentile(values, 97.5) print(f"{name}: p99 mean={mean:.2f}ms, 95% CI=[{ci_low:.2f}, {ci_high:.2f}]") return all_p99逻辑说明:np.random.seed(seed)保证每次运行的随机序列不同但可复现。np.percentile(values, 2.5)和97.5给出 95% 置信区间。如果 URLLC 的 p99 置信区间上限超过 1ms,说明你的调度算法在坏情况下保不住时延要求,需要加冗余或改优先级。
参数怎么改:num_runs至少 20,条件允许跑 50 次。如果置信区间太宽,说明随机性太大,要么增加仿真时长,要么检查业务流模型是不是有周期性突发没被平滑掉。
我自己的习惯是:任何切片仿真结果,只要 p99 的置信区间宽度超过均值的 20%,就不下结论,先加仿真次数或者查业务流模型。这个习惯帮我省了很多「以为算法有效其实只是随机种子好」的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取