【AI系统】Ascend C 编程范式

Ascend C 编程范式

AI 的发展日新月异,AI 系统相关软件的更新迭代也是应接不暇,作为一本讲授理论的作品,我们将尽可能地讨论编程范式背后的原理和思考,而少体现代码实现,以期让读者理解 Ascend C 为何这样设计,进而随时轻松理解最新的 Ascend C 算子的编写思路。

本文将针对 Ascend C 的编程范式进行详细讲解,重点讲授向量计算编程范式。

向量编程范式

基于 Ascend C 编程范式的方式实现自定义向量算子的流程如下图所示,由三个步骤组成:算子分析是进行编程的前置任务,负责明确自定义算子的各项需求,如输入输出、使用 API 接口等;核函数的定义和封装是编程的第一步,负责声明核函数的名称,并提供进入核函数运算逻辑的接口;基于算子需求实现算子类是整个核函数的核心计算逻辑,其由被分为内存初始化、数据搬入、算子计算逻辑实现、数据搬出四个部分,后三者被又被称为算子的实现流程。

在这里插入图片描述

自定义向量算子核心部分一般由两个函数组成,分别是 Init() 函数(初始化函数)与 Process() 函数(执行函数)。Init() 函数完成板外数据定位以及板上内存初始化工作;Process() 函数完成向量算子的实现,分成三个流水任务:CopyIn、Compute、CopyOut。CopyIn 负责板外数据搬入,Compute 负责向量计算,CopyOut 负责板上数据搬出。

流水线任务之间存在数据依赖,需要进行数据传递。Ascend C 中使用 TQue 队列完成任务之间的数据通信和同步,提供 EnQue、DeQue 等基础 API;TQue 队列管理不同层级的物理内存时,用一种抽象的逻辑位置(TPosition)来表达各级别的存储,代替了片上物理存储的概念,开发者无需感知硬件架构。另外,Ascend C 使用 GlobalTensorLocalTensor 作为数据的基本操作单元,它是各种指令 API 直接调用的对象,也是数据的载体。在向量编程模型中,使用到的 TQue 类型如下:搬入数据的存放位置 VECIN、搬出数据的存放位置 VECOUT。

在本节中,我们将从 add_custom 这一基本的向量算子着手,根据自定义算子的开发流程,逐步介绍如何根据向量编程范式逐步编写自定义向量算子,最后会介绍 Ascend C 向量编程如何进行数据切分。

算子分析

在开发算子代码之前需要分析算子的数学表达式、输入、输出以及计算逻辑的实现,明确需要调用的 Ascend C 接口。

  1. 明确算子的数学表达式

Ascend C 提供的向量计算接口的操作元素都为 LocalTensor,输入数据需要先搬运进片上存储,以 Add 算子为例,数学表达式为:z=x+y,使用计算接口完成两个输入参数相加,得到最终结果,再搬出到外部存储上。

  1. 明确输入和输出

Add 算子有两个输入:x 与 y,输出为 z。

本样例中算子的输入支持的数据类型为 half(float16),算子输出的数据类型与输入数据类型相同。

算子输入支持 shape(8,2048),输出 shape 与输入 shape 相同。算子输入支持的数据格式(shape)为:ND。

  1. 确定算子实现所需接口

使用 DataCopy 来实现数据搬移;由于向量计算实现较为简单,使用基础 API 完成计算逻辑的实现,在加法算子中使用双目指令接口 Add 实现 x+y;使用 EnQue、DeQue 等接口对 TQue 队列进行管理。

核函数定义与封装

在完成算子分析后,可以正式开始开发算子代码,其第一步应该完成对于核函数的定义和封装。在本小节将介绍如何对函数原型进行定义,并介绍核函数定义中应该遵循的规则;随后将介绍函数原型中所需实现的内容;最后本小节将完成核函数的封装,便于后续对于核函数的调用。

  1. 函数原型定义

本样例中,函数原型名为 add_custom,根据算子分析中对算子输入输出的分析,确定有 3 个参数 x,y,z,其中 x,y 为输入内存,z 为输出内存。

根据核函数定义的规则,使用__global__函数类型限定符来标识它是一个核函数,可以被<<<...>>>调用;使用__aicore__函数类型限定符来标识该核函数在设备端 AI Core 上执行;为方便起见,统一使用 GM_ADDR 宏修饰入参,表示其为入参在内存中的位置。add_custom 函数原型的定义见下方程序第 1 行所示。

  1. 调用算子类的 Init 和 Process 函数

在函数原型中,首先实例化对应的算子类,并调用该算子类的 Init()Process() 函数,如下方程序第 2-4 行所示。其中,Init() 函数负责内存初始化相关工作,Process() 函数则负责算子实现的核心逻辑。

  1. 对核函数的调用进行封装

对核函数的调用进行封装,得到 add_custom_do 函数,便于主程序调用。下方程序第 6 行所示内容表示该封装函数仅在编译运行 NPU 侧的算子时会用到,编译运行 CPU 侧的算子时,可以直接调用 add_custom 函数。

调用核函数时,除了需要传入参数 x,y,z,还需要使用<<<…>>>传入 blockDim(核函数执行的核数), l2ctrl(保留参数,设置为 nullptr), stream(应用程序中维护异步操作执行顺序的任务流对象)来规定核函数的执行配置,如下方程序第 10 行所示。

extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z){
    KernelAdd op;
    op.Init(x, y, z);
    op.Process();
}

#ifndef __CCE_KT_TEST__

// call of kernel function
void add_custom_do(uint32_t blockDim, void* l2ctrl, void* stream, uint8_t* x, uint8_t* y, uint8_t* z){
    add_custom<<<blockDim, l2ctrl, stream>>>(x, y, z);
}

#endif

算子数据通路

前文已经提到过在 Process() 函数中存在三个流水任务,分别是 CopyIn、Compute 和 CopyOut。本节将详细讲解数据在这三个任务之间的传递过程,并为后续使用 Ascend C 对其进行实现作铺垫。

向量算子三阶段任务流水的数据通路如下图所示。

在这里插入图片描述

上图纵向分为 2 部分,上部分为发生在外部存储(Global Memory)中的数据流通过程,下部分为发生在 AI Core 内(Local Memory)中的数据流通过程;横向分为 3 部分,指代 CopyIn、Compute 和 CopyOut 这三个阶段中的数据流通过程。发生在 AI Core 内的任务间数据传递统一由 TPipe 资源管理模块进行管理。

在 CopyIn 任务中,需要先将执行计算的数据 xGm、yGm 从外部存储通过 DataCopy 接口传入板上,存储为 xLocal、yLocal,并通过 EnQue 接口传入数据搬入队列 inQueueX、inQueueY 中,以便进行流水模块间的数据通信与同步。

在 Compute 任务中,需要先将 xLocal、yLocal 使用 DeQue 接口从数据搬入队列中取出,并使用相应的向量运算 API 执行计算操作得到结果 zLocal,并将 zLocal 通过 EnQue 接口传入数据搬出队列 outQueueZ 中。

在 CopyOut 任务中,需要先将结果数据 zLocal 使用 DeQue 接口从数据搬出队列中取出,并使用 DataCopy 接口将板上数据传出到外部存储 zGm 中。

上述为向量算子核心处理部分的数据通路,同时也作为一个程序设计思路,下面将介绍如何用 Ascend C 对其进行实现。

算子类实现

在对核函数的声明和定义中,我们会提到需要实例化算子类,并调用其中的两个函数来实现算子。在本节中,将首先展示算子类的成员,随后具体介绍 Init() 函数和 Process() 函数的作用与实现。

  1. 算子类成员定义

算子类的成员如下方程序所示。如第 4-5 行所示,在算子类中,需要声明对外开放的内存初始化函数 Init() 和核心处理函数 Process()。而为了实现适量算子核内计算流水操作,在向量算子中我们又将 Process()函数分为三个部分,即数据搬入阶段 CopyIn()、计算阶段 Compute()与数据搬出阶段 CopyOut()三个私有类成员,见第 6~9 行。

除了这些函数成员声明外,第 10-14 行还依次声明了内存管理对象 pipe、输入数据 TQue 队列管理对象 inQueueX 和 inQueueY、输出数据 TQue 队列管理对象 outQueueZ 以及管理输入输出 Global Memory 内存地址的对象 xGm,yGm 与 zGm,这些均作为私有成员在算子实现中被使用。

class KernelAdd {

public:
    __aicore__ inline KernelAdd() {} 
    __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z){} 
    __aicore__ inline void Process(){}

private:
    __aicore__ inline void CopyIn(int32_t progress){}
    __aicore__ inline void Compute(int32_t progress){}
    __aicore__ inline void CopyOut(int32_t progress){}

private:
    TPipe pipe; 
    TQue<TPosition::VECIN, BUFFER_NUM> inQueueX, inQueueY;  
    TQue<TPosition::VECOUT, BUFFER_NUM> outQueueZ;  
    GlobalTensor<half> xGm, yGm, zGm;  
};
  1. 初始化函数 Init()函数实现

在多核并行计算中,每个核计算的数据是全部数据的一部分。Ascend C 核函数是单个核的处理函数,所以我们需要获取每个核负责的对应位置的数据。此外,我们还需要对于声明的输入输出 TQue 队列分配相应的内存空间。

Init() 函数实现见下方程序。第 2~5 行通过计算得到该核所负责的数据所在位置,其中 x、y、z 表示 3 个入参在片外的起始地址;BLOCK_LENGTH 表示单个核负责的数据长度,为数据全长与参与计算核数的商;GetBlockIdx()是与硬件感知相关的 API 接口,可以得到核所对应的编号,在该样例中为 0-7。通过这种方式可以得到该核函数需要处理的输入输出在 Global Memory 上的内存偏移地址,并将该偏移地址设置在 Global Tensor 中。

第 6~8 行通过 TPipe 内存管理对象为输入输出 TQue 分配内存。其调用 API 接口 InitBuffer(),接口入参依次为 TQue 队列名、是否启动 double buffer 机制以及单个数据块的大小(而非长度)。

1   __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z)
2   {
3       xGm.SetGlobalBuffer((__gm__ half*)x + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH);
4       yGm.SetGlobalBuffer((__gm__ half*)y + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH);
5       zGm.SetGlobalBuffer((__gm__ half*)z + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH);
6       pipe.InitBuffer(inQueueX, BUFFER_NUM, TILE_LENGTH * sizeof(half));
7       pipe.InitBuffer(inQueueY, BUFFER_NUM, TILE_LENGTH * sizeof(half));
8       pipe.InitBuffer(outQueueZ, BUFFER_NUM, TILE_LENGTH * sizeof(half));
9   }

  1. 核心处理函数 Process()函数实现

基于向量编程范式,将核函数的实现分为 3 个基本任务:CopyIn,Compute,CopyOut,Process() 函数通过调用顺序调用这三个基本任务完成核心计算任务。然而考虑到每个核内的数据仍然被进一步切分成小块,需要循环执行上述步骤,从而得到最终结果。Process() 函数的实现如下方程序所示。

1   public:
2       __aicore__ inline void Process()
3       {
4           constexpr int32_t loopCount = TILE_NUM * BUFFER_NUM;
5           for (int32_t i = 0; i < loopCount; i++) {
6               CopyIn(i);
7               Compute(i);
8               CopyOut(i);
9           }
10      }
11  private:
12      __aicore__ inline void CopyIn(int32_t progress)
13      {
14          LocalTensor<half> xLocal = inQueueX.AllocTensor<half>();
15          LocalTensor<half> yLocal = inQueueY.AllocTensor<half>();

16          DataCopy(xLocal, xGm[progress * TILE_LENGTH], TILE_LENGTH);
17          DataCopy(yLocal, yGm[progress * TILE_LENGTH], TILE_LENGTH);

18          inQueueX.EnQue(xLocal);
19          inQueueY.EnQue(yLocal);
20      }
21      __aicore__ inline void Compute(int32_t progress)
22      {
23          LocalTensor<half> xLocal = inQueueX.DeQue<half>();
24          LocalTensor<half> yLocal = inQueueY.DeQue<half>();
25          LocalTensor<half> zLocal = outQueueZ.AllocTensor<half>();

26          Add(zLocal, xLocal, yLocal, TILE_LENGTH);
27          outQueueZ.EnQue<half>(zLocal);

28          inQueueX.FreeTensor(xLocal);
29          inQueueY.FreeTensor(yLocal);
30      }
31      __aicore__ inline void CopyOut(int32_t progress)
32      {
33          LocalTensor<half> zLocal = outQueueZ.DeQue<half>();
34          DataCopy(zGm[progress * TILE_LENGTH], zLocal, TILE_LENGTH);
35          outQueueZ.FreeTensor(zLocal);
36      }

如上方程序第 4-9 行所示,Process() 函数需要首先计算每个核内的分块数量,从而确定循环执行三段流水任务的次数,随后依此循环顺序执行数据搬入任务 CopyIn()、向量计算任务 Compute() 和数据搬出任务 CopyOut()。一个简化的数据通路图如下图所示。根据此图,可以完成各个任务的程序设计。

在这里插入图片描述

  • CopyIn()私有类函数实现

使用 AllocTensor 接口为参与计算的输入分配板上存储空间,如上方程序第 14~15 行代码所示,由于定义的入参数据类型是 half 类型的,所以此处分配的空间大小也为 half。

使用 DataCopy 接口将 GlobalTensor 数据拷贝到 LocalTensor,如第 16~17 行所示,xGm、yGm 存储的是该核所需处理的所有输入,因此根据该分块对应编号找到相关的分块数据拷贝至板上。

使用 EnQue 将 LocalTensor 放入 VecIn 的 TQue 中,如第 18~19 行所示。

  • Compute()私有类函数实现

使用 DeQue 从 VecIn 中取出输入 x 和 y,如上方程序第 23-24 行所示。

使用 AllocTensor 接口为输出分配板上存储空间,如第 25 行所示。

使用 Ascend C 接口 Add 完成向量计算,如第 26 行所示。该接口是一个双目指令 2 级接口,入参分别为目的操作数、源操作数 1、源操作数 2 和输入元素个数。

使用 EnQue 将计算结果 LocalTensor 放入到 VecOut 的 TQue 中,如第 27 行所示。

使用 FreeTensor 释放不再使用的 LocalTensor,即两个用于存储输入的 LocalTensor,如第 28~29 行所示。

  • CopyOut 私有类函数实现

使用 DeQue 接口从 VecOut 的 TQue 中取出目标结果 z,如上方程序第 33 行所示。

使用 DataCopy 接口将 LocalTensor 数据拷贝到 GlobalTensor 上,如第 34 行所示。

使用 FreeTensor 将不再使用的 LocalTensor 进行回收,如第 35 行所示。

算子切分策略

正如前文所述,Ascend C 算子编程是 SPMD 编程,其使用多个核进行并行计算,在单个核内还将数据根据需求切分成若干份,降低每次计算负荷,从而起到加快计算效率的作用。这里需要注意,Ascend C 中涉及到的核数其实并不是指实际执行的硬件中所拥有的处理器核数,而是“逻辑核”的数量,即同时运行了多少个算子的实例,是同时执行此算子的进程数量。一般的,建议使用的逻辑核数量是实际处理器核数的整数倍。此外,如果条件允许,还可以进一步将每个待处理数据一分为二,开启 double buffer 机制(一种性能优化方法),实现流水线间并行,进一步减少计算单元的闲置问题。

在本 add_custom 算子样例中,设置数据整体长度 TOTAL_LENGTH 为 8* 2048,平均分配到 8 个核上运行,单核上处理的数据大小 BLOCK_LENGTH 为 2048;对于单核上的处理数据,进行数据切块,将数据切分成 8 块(并不意味着 8 块就是性能最优);切分后的每个数据块再次切分成 2 块,即可开启 double buffer。此时每个数据块的长度 TILE_LENGTH 为 128 个数据。

具体数据切分示意图下图所示,在确定一个数据的起始内存位置后,将数据整体平均分配到各个核中,随后针对单核上的数据再次进行切分,将数据切分为 8 块,并启动 double buffer 机制再次将每个数据块一分为二,得到单个数据块的长度 TILE_LENGTH。

在这里插入图片描述

数据切分中所使用的各参数定义如下程序所示:第 1 行定义了数据全长 TOTAL_LENGTH,约束了输入数据的长度;第 2 行声明了参与计算任务的核数 USE_CORE_NUM;第 3 行计算得到了单个核负责计算的数据长度 BLOCK_LENGTH;第 4 行定义了单个核中数据的切分块数 TILE_NUM;第 5 行决定了是否开启 double buffer 机制,如果不开启则规定 BUFFER_NUM = 1;第六行计算得到单个数据块的数据长度 TILE_LENGTH。

1   constexpr int32_t TOTAL_LENGTH = 8 * 2048;  
2   constexpr int32_t USE_CORE_NUM = 8;
3   constexpr int32_t BLOCK_LENGTH = TOTAL_LENGTH / USE_CORE_NUM;
4   constexpr int32_t TILE_NUM = 8; 
5   constexpr int32_t BUFFER_NUM = 2;
6   constexpr int32_t TILE_LENGTH = BLOCK_LENGTH / TILE_NUM / BUFFER_NUM;

如果您想了解更多AI知识,与AI专业人士交流,请立即访问昇腾社区官方网站https://www.hiascend.com/或者深入研读《AI系统:原理与架构》一书,这里汇聚了海量的AI学习资源和实践课程,为您的AI技术成长提供强劲动力。不仅如此,您还有机会投身于全国昇腾AI创新大赛和昇腾AI开发者创享日等盛事,发现AI世界的无限奥秘~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928193.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

hadoop环境配置-创建hadoop用户+更新apt+安装SSH+配置Java环境

一、创建hadoop用户(在vm安装的ubantu上打开控制台) 1、sudo useradd -m hadoop -s /bin/bash &#xff08;创建hadoop用户&#xff09; 2、sudo passwd hadoop (设置密码) 3、sudo adduser hadoop sudo&#xff08;将新建的hadoop用户设置为管理员&#xff09; 执行如下图 将…

嵌入式系统应用-LVGL的应用-平衡球游戏 part1

平衡球游戏 part1 1 平衡球游戏的界面设计2 界面设计2.1 背景设计2.2 球的设计2.3 移动球的坐标2.4 用鼠标移动这个球2.5 增加边框规则2.6 效果图 3 为小球增加增加动画效果3.1 增加移动效果代码3.2 具体效果图片 平衡球游戏 part2 第二部分文章在这里 1 平衡球游戏的界面设计…

从被动响应到主动帮助,ProActive Agent开启人机交互新篇章

在人工智能领域&#xff0c;我们正见证着一场革命性的变革。传统的AI助手&#xff0c;如ChatGPT&#xff0c;需要明确的指令才能执行任务。但现在&#xff0c;清华大学联合面壁智能等团队提出了一种全新的主动式Agent交互范式——ProActive Agent&#xff0c;它能够主动观察环境…

2.mysql 中一条更新语句的执行流程是怎样的呢?

前面我们系统了解了一个查询语句的执行流程&#xff0c;并介绍了执行过程中涉及的处理模块。 相信你还记得&#xff0c;一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块&#xff0c;最后到达存储引擎。 那么&#xff0c;一条更新语句的执行流程又…

NaviveUI框架的使用 ——安装与引入(图标安装与引入)

文章目录 概述安装直接引入引入图标样式库 概述 &#x1f349;Naive UI 是一个轻量、现代化且易于使用的 Vue 3 UI 组件库&#xff0c;它提供了一组简洁、易用且功能强大的组件&#xff0c;旨在为开发者提供更高效的开发体验&#xff0c;特别是对于构建现代化的 web 应用程序。…

web vue 滑动选择 n宫格选中 九宫格选中

页面动态布局经常性要交给客户来操作&#xff0c;他们按时他们的习惯在同一个屏幕内显示若干个子视图&#xff0c;尤其是在医学影像领域对于影像的同屏显示目视对比显的更为重要。 来看看如下的用户体验&#xff1a; 设计为最多支持5行6列页面展示后&#xff0c;右侧的布局则动…

ELK的Filebeat

目录 传送门前言一、概念1. 主要功能2. 架构3. 使用场景4. 模块5. 监控与管理 二、下载地址三、Linux下7.6.2版本安装filebeat.yml配置文件参考&#xff08;不要直接拷贝用&#xff09;多行匹配配置过滤配置最终配置&#xff08;一、多行匹配、直接读取日志文件、EFK方案&#…

C#调用c++创建的动态链接库dll文件

在C#中调用外部DLL文件是一种常见的编程实践&#xff0c;它具有以下几个重要意义&#xff1a;1.代码重用&#xff1b;2.模块化&#xff1b;3.性能优化&#xff1b;4.安全性&#xff1b;5.跨平台兼容性&#xff1b;6.方便更新和维护&#xff1b;7.利用特定技术或框架&#xff1b…

重建大师重建的模型坐标有偏差怎么解决?

第一遍自由网空三&#xff0c;跑完之后刺点&#xff0c;然后控制点平差增强参数解算&#xff0c;方法如下&#xff1a; &#xff08;1&#xff09;跑完自由网空三后&#xff0c;选择编辑控制点&#xff0c;出现刺点窗口后&#xff0c;导入控制点参数 &#xff08;2&#xff09…

Apache Airflow 快速入门教程

Apache Airflow已经成为Python生态系统中管道编排的事实上的库。与类似的解决方案相反&#xff0c;由于它的简单性和可扩展性&#xff0c;它已经获得了普及。在本文中&#xff0c;我将尝试概述它的主要概念&#xff0c;并让您清楚地了解何时以及如何使用它。 Airflow应用场景 …

GEE Download Data——气温数据的下载

GEE数据下载第二弹!今天我们来分享气温数据的下载。 一、数据介绍 气温数据我们要用到的是MODIS数据产品,MOD11A2 V6.1 产品提供 1200 x 1200 公里网格内 8 天平均陆地表面温度 (LST)。 MOD11A2 中的每个像素值都是该 8 天内收集的所有相应 MOD11A1 LST 像素的简单平均值。…

分布式推理框架 xDit

1. xDiT 简介 xDiT 是一个为大规模多 GPU 集群上的 Diffusion Transformers&#xff08;DiTs&#xff09;设计的可扩展推理引擎。它提供了一套高效的并行方法和 GPU 内核加速技术&#xff0c;以满足实时推理需求。 1.1 DiT 和 LLM DiT&#xff08;Diffusion Transformers&am…

uniapp 自定义导航栏增加首页按钮,仿微信小程序操作胶囊

实现效果如图 抽成组件navbar.vue&#xff0c;放入分包 <template><view class"header-nav-box":style"{height:Props.imgShow?:statusBarHeightpx,background:Props.imgShow?:Props.bgColor||#ffffff;}"><!-- 是否使用图片背景 false…

张伟楠动手学强化学习笔记|第一讲(上)

张伟楠动手学强化学习笔记|第一讲&#xff08;上&#xff09; 人工智能的两种任务类型 预测型任务 有监督学习无监督学习 决策型任务 强化学习 序贯决策(Sequential Decision Making) 智能体序贯地做出一个个决策&#xff0c;并接续看到新的观测&#xff0c;知道最终任务结…

《只狼》运行时提示“mfc140u.dll文件缺失”是什么原因?“找不到mfc140u.dll文件”要怎么解决?教你几招轻松搞定

《只狼》运行时提示“mfc140u.dll文件缺失”的科普与解决方案 作为一名软件开发从业者&#xff0c;在游戏开发和维护过程中&#xff0c;我们经常会遇到各种运行时错误和系统报错。今天&#xff0c;我们就来探讨一下《只狼》这款游戏在运行时提示“mfc140u.dll文件缺失”的原因…

MacOS 命令行详解使用教程

本章讲述MacOs命令行详解的使用教程&#xff0c;感谢大家观看。 本人博客:如烟花般绚烂却又稍纵即逝的主页 MacOs命令行前言&#xff1a; 在 macOS 上,Terminal&#xff08;终端) 是一个功能强大的工具&#xff0c;它允许用户通过命令行直接与系统交互。本教程将详细介绍 macOS…

【计算机网络】实验6:IPV4地址的构造超网及IP数据报

实验 6&#xff1a;IPV4地址的构造超网及IP数据报 一、 实验目的 加深对IPV4地址的构造超网&#xff08;无分类编制&#xff09;的了解。 加深对IP数据包的发送和转发流程的了解。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、了解IPV4地址的构造超网…

Java Web 1HTML快速入门

目录 一、Web开发介绍 1.什么是Web&#xff1f; 2.初识Web前端 二、HTML快速入门 1.什么是HTML、CSS&#xff1f; 2、案例练习 3.小结 三、VS Code开发工具 四、基础标签&样式&#xff08;HTML&#xff09; 2、实现标题--样式1&#xff08;新闻标题的颜色&#xff0…

【流程图】各元素形状和含义

判定、文档、数据、数据库、流程处理节点 矩形 - 动词 平行四边形 - 图像 下波浪 - 数据 图片来源http://baike.cu12.com/bkss/62449.shtml

利用机器学习预测离婚:从数据分析到模型构建(含方案和源码)

背景介绍 在当今社会&#xff0c;婚姻关系的稳定性受到了多方面因素的影响&#xff0c;包括经济压力、沟通问题、个人价值观差异等。离婚不仅对夫妻双方产生深远的影响&#xff0c;还可能对子女的成长环境和社会稳定造成不利影响。因此&#xff0c;理解并预测可能导致离婚的因素…