Ascend基于自定义算子工程的算子开发

news2025/4/15 5:04:12

环境准备

见https://gitee.com/zaj1414904389/ascend-tutorial.git

工程创建

CANN软件包中提供了工程创建工具msopgen,开发者可以输入算子原型定义文件生成Ascend C算子开发工程

[
  {
    "op": "AddCustom",
    "input_desc": [
      {
        "name": "x",
        "param_type": "required",
        "format": [
          "ND"
        ],
        "type": [
          "fp16"
        ]
      },
      {
        "name": "y",
        "param_type": "required",
        "format": [
          "ND"
        ],
        "type": [
          "fp16"
        ]
      }
    ],
    "output_desc": [
      {
        "name": "z",
        "param_type": "required",
        "format": [
          "ND"
        ],
        "type": [
          "fp16"
        ]
      }
    ]
  }
]

使用msopgen工具生成AddCustom算子的开发工程。
执行以下命令

/usr/local/Ascend/ascend-toolkit/8.0.RC1.alpha002/python/site-packages/bin/msopgen gen -i /home/ma-user/add_custom.json -c ai_core-Ascend910A -lan cpp -out  /home/ma-user/AddCustom

生成代码目录

(MindSpore) [root@edbdd54b26c74c17b9ddfb1308c88382-task0-0 AddCustom]# tree -L 2
.
AddCustom
├── build.sh         // 编译入口脚本
├── cmake 
│   ├── config.cmake
│   ├── util        // 算子工程编译所需脚本及公共编译文件存放目录
├── CMakeLists.txt   // 算子工程的CMakeLists.txt
├── CMakePresets.json // 编译配置项
├── framework        // 算子插件实现文件目录,单算子模型文件的生成不依赖算子适配插件,无需关注
├── op_host                      // host侧实现文件
│   ├── add_custom_tiling.h    // 算子tiling定义文件
│   ├── add_custom.cpp         // 算子原型注册、shape推导、信息库、tiling实现等内容文件
│   ├── CMakeLists.txt
├── op_kernel                   // kernel侧实现文件
│   ├── CMakeLists.txt   
│   ├── add_custom.cpp        // 算子核函数实现文件 
├── scripts                     // 自定义算子工程打包相关脚本所在目录

算子核函数实现

算子核函数实现代码的内部调用关系
在这里插入图片描述
AddCustom/op_kernel/add_custom.cpp完整代码

#include "kernel_operator.h"
using namespace AscendC;
constexpr int32_t BUFFER_NUM = 2;
class KernelAdd {
public:
__aicore__ inline KernelAdd() {}
// 初始化函数,完成内存初始化相关操作
__aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z, uint32_t totalLength, uint32_t tileNum)
{
    // 使用获取到的TilingData计算得到singleCoreSize(每个核上总计算数据大小)、tileNum(每个核上分块个数)、singleTileLength(每个分块大小)等变量
    ASSERT(GetBlockNum() != 0 && "block dim can not be zero!");
    this->blockLength = totalLength / GetBlockNum();
    this->tileNum = tileNum;
    ASSERT(tileNum != 0 && "tile num can not be zero!");
    this->tileLength = this->blockLength / tileNum / BUFFER_NUM;

    // 获取当前核的起始索引
    xGm.SetGlobalBuffer((__gm__ DTYPE_X*)x + this->blockLength * GetBlockIdx(), this->blockLength);
    yGm.SetGlobalBuffer((__gm__ DTYPE_Y*)y + this->blockLength * GetBlockIdx(), this->blockLength);
    zGm.SetGlobalBuffer((__gm__ DTYPE_Z*)z + this->blockLength * GetBlockIdx(), this->blockLength);
    // 通过Pipe内存管理对象为输入输出Queue分配内存
    pipe.InitBuffer(inQueueX, BUFFER_NUM, this->tileLength * sizeof(DTYPE_X));
    pipe.InitBuffer(inQueueY, BUFFER_NUM, this->tileLength * sizeof(DTYPE_Y));
    pipe.InitBuffer(outQueueZ, BUFFER_NUM, this->tileLength * sizeof(DTYPE_Z));
}
// 核心处理函数,实现算子逻辑,调用私有成员函数CopyIn、Compute、CopyOut完成矢量算子的三级流水操作
__aicore__ inline void Process()
{
    int32_t loopCount = this->tileNum * BUFFER_NUM;
    for (int32_t i = 0; i < loopCount; i++) {
        CopyIn(i);
        Compute(i);
        CopyOut(i);
    }
}


private:
// 搬入函数,完成CopyIn阶段的处理,被核心Process函数调用
__aicore__ inline void CopyIn(int32_t progress)
{
    // 从Queue中分配输入Tensor
    LocalTensor<DTYPE_X> xLocal = inQueueX.AllocTensor<DTYPE_X>();
    LocalTensor<DTYPE_Y> yLocal = inQueueY.AllocTensor<DTYPE_Y>();
    // 将GlobalTensor数据拷贝到LocalTensor
    DataCopy(xLocal, xGm[progress * this->tileLength], this->tileLength);
    DataCopy(yLocal, yGm[progress * this->tileLength], this->tileLength);
    // 将LocalTesor放入VECIN(代表矢量编程中搬入数据的逻辑存放位置)的Queue中
    inQueueX.EnQue(xLocal);
    inQueueY.EnQue(yLocal);
}
// 计算函数,完成Compute阶段的处理,被核心Process函数调用
__aicore__ inline void Compute(int32_t progress)
{
    // 将Tensor从队列中取出,用于后续计算
    LocalTensor<DTYPE_X> xLocal = inQueueX.DeQue<DTYPE_X>();
    LocalTensor<DTYPE_Y> yLocal = inQueueY.DeQue<DTYPE_Y>();
    // 从Queue中分配输出Tensor
    LocalTensor<DTYPE_Z> zLocal = outQueueZ.AllocTensor<DTYPE_Z>();
    // 调用Add接口进行计算
    Add(zLocal, xLocal, yLocal, this->tileLength);
    // 将计算结果LocalTensor放入到VecOut的Queue中
    outQueueZ.EnQue<DTYPE_Z>(zLocal);
    // 释放输入Tensor
    inQueueX.FreeTensor(xLocal);
    inQueueY.FreeTensor(yLocal);
}
// 搬出函数,完成CopyOut阶段的处理,被核心Process函数调用
__aicore__ inline void CopyOut(int32_t progress)
{
// 从VecOut的Queue中取出输出Tensor
LocalTensor<DTYPE_Z> zLocal = outQueueZ.DeQue<DTYPE_Z>();
        // 将输出Tensor拷贝到GlobalTensor中
        DataCopy(zGm[progress * this->tileLength], zLocal, this->tileLength);
        // 将不再使用的LocalTensor释放
        outQueueZ.FreeTensor(zLocal);
    }


private:
    //Pipe内存管理对象
    TPipe pipe;
    //输入数据Queue队列管理对象,QuePosition为VECIN
    TQue<QuePosition::VECIN, BUFFER_NUM> inQueueX, inQueueY; 
    //输出数据Queue队列管理对象,QuePosition为VECOUT
    TQue<QuePosition::VECOUT, BUFFER_NUM> outQueueZ;
    //管理输入输出Global Memory内存地址的对象,其中xGm, yGm为输入,zGm为输出
    GlobalTensor<DTYPE_X> xGm;
    GlobalTensor<DTYPE_Y> yGm;
    GlobalTensor<DTYPE_Z> zGm;
    // 每个核上总计算数据大小
    uint32_t blockLength;
    // 每个核上总计算数据分块个数
    uint32_t tileNum;
    // 每个分块大小
    uint32_t tileLength;
};


extern "C" __global__ __aicore__ void add_custom(GM_ADDR x, GM_ADDR y, GM_ADDR z, GM_ADDR workspace, GM_ADDR tiling)
{
    // 获取Host侧传入的Tiling参数
    GET_TILING_DATA(tilingData, tiling);
    // 初始化算子类
    KernelAdd op;
    // 算子类的初始化函数,完成内存初始化相关工作
    op.Init(x, y, z, tilingData.totalLength, tilingData.tileNum);
    if (TILING_KEY_IS(1)) {
        // 完成算子实现的核心逻辑
        op.Process();
    }
}

Host侧算子实现

核函数开发并验证完成后,下一步就是进行Host侧的实现,对应“AddCustom/op_host”目录下的add_custom_tiling.h文件与add_custom.cpp文件。
修改“add_custom_tiling.h”文件,在此文件中增加粗体部分的代码,进行Tiling参数的定义。

#ifndef ADD_CUSTOM_TILING_H
#define ADD_CUSTOM_TILING_H
#include "register/tilingdata_base.h"
namespace optiling {
BEGIN_TILING_DATA_DEF(TilingData)
  // AddCustom算子使用了2个tiling参数:totalLength与tileNum
  TILING_DATA_FIELD_DEF(uint32_t, totalLength);     // 总计算数据量
  TILING_DATA_FIELD_DEF(uint32_t, tileNum);         // 每个核上总计算数据分块个数
END_TILING_DATA_DEF;

// 注册tiling数据到对应的算子
REGISTER_TILING_DATA_CLASS(AddCustom, TilingData)
}
#endif // ADD_CUSTOM_TILING_H


修改“add_custom.cpp”文件,进行Tiling的实现。
修改“TilingFunc”函数,实现Tiling上下文的获取,并通过上下文获取输入输出shape信息,并根据shape信息设置TilingData、序列化保存TilingData,并设置TilingKey。

namespace optiling {
const uint32_t BLOCK_DIM = 8;
const uint32_t TILE_NUM = 8;
static ge::graphStatus TilingFunc(gert::TilingContext* context)
{
    TilingData tiling;
    uint32_t totalLength = context->GetInputTensor(0)->GetShapeSize();
    context->SetBlockDim(BLOCK_DIM);
    tiling.set_totalLength(totalLength);
    tiling.set_tileNum(TILE_NUM);
    tiling.SaveToBuffer(context->GetRawTilingData()->GetData(), context->GetRawTilingData()->GetCapacity());
    context->GetRawTilingData()->SetDataSize(tiling.GetDataSize());
    context->SetTilingKey(1);
    size_t *currentWorkspace = context->GetWorkspaceSizes(1);
    currentWorkspace[0] = 0;
    return ge::GRAPH_SUCCESS;
}
} // namespace optiling

修改“add_custom.cpp”文件中的算子原型注册,此函数为入口函数。

namespace ops {
class AddCustom : public OpDef {
public:
    explicit AddCustom(const char* name) : OpDef(name)
    { 
        // Add算子的第一个输入
        this->Input("x")
            .ParamType(REQUIRED)    // 代表输入必选
            .DataType({ ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32 })   // 输入支持的数据类型
            .Format({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND })    // 输入支持的数据格式
            .UnknownShapeFormat({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND });  // 未知Shape情况下的Format的默认值
        // Add算子的第二个输入
        this->Input("y")
            .ParamType(REQUIRED)
            .DataType({ ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32 })
            .Format({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND })
            .UnknownShapeFormat({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND });
        this->Output("z")
            .ParamType(REQUIRED)
            .DataType({ ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32 })
            .Format({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND })
            .UnknownShapeFormat({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND });
        // 关联InferShape函数
        this->SetInferShape(ge::InferShape);
        // 关联Tiling函数
        this->AICore()
            .SetTiling(optiling::TilingFunc);
        // 注册算子支持的AI处理器型号,请替换为实际支持的AI处理器型号
        this->AICore().AddConfig("ascend910");
    }
};
// 结束算子注册
OP_ADD(AddCustom);
} // namespace ops

算子工程编译部署

译AddCustom工程,生成自定义算子安装包,并将其安装到算子库中。
修改CMakePresets.json中ASCEND_CANN_PACKAGE_PATH为CANN软件包安装路径。

{
    ……
    "configurePresets": [
        {
                ……
                "ASCEND_CANN_PACKAGE_PATH": {
                    "type": "PATH",
                    "value": "/usr/local/Ascend/ascend-toolkit/latest"        //请替换为CANN软件包安装后的实际路径
                },
                ……
        }
    ]
}

在算子工程AddCustom目录下执行如下命令,进行算子工程编译。

./build.sh

编译成功

start compile Ascend C operator AddCustom. kernel name is AddCustom_402e355eb717124771cfc7dbebfe946c
start compile Ascend C operator AddCustom. kernel name is AddCustom_ccd748392d99d04b8205210970fde2b9
start compile Ascend C operator AddCustom. kernel name is AddCustom_1e04ee05ab491cc5ae9c3d5c9ee8950b
compile Ascend C operator: AddCustom success!
compile Ascend C operator: AddCustom success!
compile Ascend C operator: AddCustom success!
[Ascend910A] Generating AddCustom_402e355eb717124771cfc7dbebfe946c Done
/usr/bin/gmake
[100%] Built target ascendc_bin_ascend910_add_custom_2
[Ascend910A] Generating AddCustom_ccd748392d99d04b8205210970fde2b9 Done
/usr/bin/gmake
[100%] Built target ascendc_bin_ascend910_add_custom_1
[Ascend910A] Generating AddCustom_1e04ee05ab491cc5ae9c3d5c9ee8950b Done
/usr/bin/gmake
[100%] Built target ascendc_bin_ascend910_add_custom_0
[100%] Built target ascendc_bin_ascend910_gen_ops_config
[100%] Built target binary
[  7%] Built target modify_vendor
[ 15%] Built target ascendc_impl_gen
[ 38%] Built target cust_op_proto
[ 46%] Built target npu_supported_ops
[ 61%] Built target cust_tf_parsers
[ 76%] Built target cust_opapi
[ 84%] Built target ops_info_gen_ascend910
[100%] Built target cust_optiling
[100%] Built target gen_version_info
[100%] Built target optiling_compat
Run CPack packaging tool...
CPack: Create package using External
CPack: Install projects
CPack: - Run preinstall target for: opp
CPack: - Install project: opp []
CPack: Create package

定义算子安装包部署。
编译成功后,会在当前目录下创建build_out目录,并在build_out目录下生成自定义算子安装包custom_opp__.run,例如“custom_opp_ubuntu_x86_64.run”。

cd /home/ma-user/AddCustom/build_out
./custom_opp_euleros_aarch64.run

命令执行成功后,自定义算子包中的相关文件将部署至当前环境的OPP算子库的vendors/customize目录中。

(MindSpore) [root@edbdd54b26c74c17b9ddfb1308c88382-task0-0 AddCustom]# ll /home/ma-user/AddCustom/build_out/_CPack_Packages/Linux/External/custom_opp_euleros_aarch64.run/packages/vendors/customize/
total 20
drwxr-x--- 3 root root 4096 Jun 21 07:20 framework
drwxr-x--- 4 root root 4096 Jun 21 07:20 op_api
drwxr-x--- 3 root root 4096 Jun 21 07:20 op_impl
drwxr-x--- 4 root root 4096 Jun 21 07:20 op_proto
-rw-r--r-- 1 root root   42 Jun 21 07:20 version.info

算子ST测试

CANN开发套件包中提供了ST测试工具“msopst”,用于生成算子的ST测试用例并在硬件环境中执行。
创建算子ST测试用例定义文件“AddCustom_case.json”,例如存储到跟算子工程目录“AddCustom”同级别的“AddCustom_st”路径下。
“AddCustom_case.json”文件的样例如下,开发者可基于此文件定制修改。
/home/ma-user/AddCustom_st/AddCustom_case.json

[
  {
    "case_name": "Test_AddCustom_001", 
    "op": "AddCustom", 
    "input_desc": [ 
      {
        "format": [
          "ND"
        ],
        "type": [
          "float16"
        ],
        "shape": [8,2048],
        "data_distribute": [ 
          "uniform"
        ],
        "value_range": [ 
          [
            0.1,
            1.0
          ]
        ],
        "name": "x"
      },
      {
        "format": [
          "ND"
        ],
        "type": [
          "float16"
        ],
        "shape": [8,2048],
        "data_distribute": [
          "uniform"
        ],
        "value_range": [
          [
            0.1,
            1.0
          ]
        ],
        "name": "y"
      }
    ],
    "output_desc": [
      {
        "format": [
          "ND"
        ],
        "type": [
          "float16"
        ],
        "shape": [8,2048],
        "name": "z"
      }
    ]
  }
]

配置ST测试用例执行时依赖的环境变量。

export DDK_PATH=/usr/local/Ascend/ascend-toolkit/latest
export NPU_HOST_LIB=/usr/local/Ascend/ascend-toolkit/latest/runtime/lib64/stub

进入msopst工具所在目录,执行如下命令生成并执行测试用例。
● 进入msopst工具所在目录。

cd /usr/local/Ascend/ascend-toolkit/latest/python/site-packages/bin

● 生成测试用例文件并执行。

./msopst run -i /home/ma-user/AddCustom_st/AddCustom_case.json -soc Ascend910A -out /home/ma-user/AddCustom_st

输出结果

b'Result file append successfully.'
b'[      OK ] AddCustom.Test_AddCustom_001_case_001_ND_float16 ( 800.668 ms )'
b'[=========] Ran 1 tests. ( 800.73 ms total )'
b'[PASSED] 1 tests.'
b'[FAILED] 0 tests.'
2024-06-21 07:56:48 (99241) - [INFO] Testcase execute in Ascend910A, cost time: 1.817183 s.
2024-06-21 07:56:48 (99241) - [INFO] Finish to run /home/ma-user/AddCustom_st/20240621075616/AddCustom/run/out/main.
2024-06-21 07:56:48 (99241) - [INFO] Step:------>>>>>> Start to get result <<<<<<------ 
2024-06-21 07:56:48 (99241) - [INFO] Find result.txt in /home/ma-user/AddCustom_st/20240621075616/AddCustom/run/out/result_files/result.txt.
2024-06-21 07:56:48 (99241) - [INFO] Case 'Test_AddCustom_001_case_001_ND_float16' run successfully.
2024-06-21 07:56:48 (99241) - [INFO] Get result data in AiHost execute time: 0.000810 s
========================================================================
run command: ./msopst run -i /home/ma-user/AddCustom_st/AddCustom_case.json -soc Ascend910A -out /home/ma-user/AddCustom_st
------------------------------------------------------------------------
- test case count: 1
- success count: 1
- failed count: 0
------------------------------------------------------------------------
========================================================================

2024-06-21 07:56:48 (99241) - [INFO] Process finished!
2024-06-21 07:56:48 (99241) - [INFO] The st report saved in: /home/ma-user/AddCustom_st/20240621075616/st_report.json.

附录

AddCustom/op_host/add_custom.cpp完整代码

#include "add_custom_tiling.h"
#include "register/op_def_registry.h"


namespace optiling {
const uint32_t BLOCK_DIM = 8;
const uint32_t TILE_NUM = 8;
static ge::graphStatus TilingFunc(gert::TilingContext* context)
{
    TilingData tiling;
    uint32_t totalLength = context->GetInputTensor(0)->GetShapeSize();
    context->SetBlockDim(BLOCK_DIM);
    tiling.set_totalLength(totalLength);
    tiling.set_tileNum(TILE_NUM);
    tiling.SaveToBuffer(context->GetRawTilingData()->GetData(), context->GetRawTilingData()->GetCapacity());
    context->GetRawTilingData()->SetDataSize(tiling.GetDataSize());
    context->SetTilingKey(1);
    size_t *currentWorkspace = context->GetWorkspaceSizes(1);
    currentWorkspace[0] = 0;
    return ge::GRAPH_SUCCESS;
}
}


namespace ge {
static ge::graphStatus InferShape(gert::InferShapeContext* context)
{
    const gert::Shape* x1_shape = context->GetInputShape(0);
    gert::Shape* y_shape = context->GetOutputShape(0);
    *y_shape = *x1_shape;
    return GRAPH_SUCCESS;
}
}


namespace ops {
class AddCustom : public OpDef {
public:
    explicit AddCustom(const char* name) : OpDef(name)
    {
         // Add算子的第一个输入
        this->Input("x")
            .ParamType(REQUIRED)    // 代表输入必选
            .DataType({ ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32 })   // 输入支持的数据类型
            .Format({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND })    // 输入支持的数据格式
            .UnknownShapeFormat({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND });  // 未知Shape情况下的Format的默认值
        // Add算子的第二个输入
        this->Input("y")
            .ParamType(REQUIRED)
            .DataType({ ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32 })
            .Format({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND })
            .UnknownShapeFormat({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND });
        this->Output("z")
            .ParamType(REQUIRED)
            .DataType({ ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_INT32 })
            .Format({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND })
            .UnknownShapeFormat({ ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND });
        // 关联InferShape函数
        this->SetInferShape(ge::InferShape);
        // 关联Tiling函数
        this->AICore()
            .SetTiling(optiling::TilingFunc);
        // 注册算子支持的AI处理器型号,请替换为实际支持的AI处理器型号
        this->AICore().AddConfig("ascend910");

    }
};

OP_ADD(AddCustom);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024全网最全面及最新且最为详细的网络安全技巧四 之 sql注入以及mysql绕过技巧 (2)———— 作者:LJS

目录 4.5 DNS记录类型介绍(A记录、MX记录、NS记录等&#xff0c;TXT&#xff0c;CNAME&#xff0c;PTR) 4.5.1 DNS 4.5.2 A记录 4.5.3NS记录 4.5.4 MX记录 4.5.5 CNAME记录 4.5.6 TXT记录 4.5.7 泛域名与泛解析 4.5.8域名绑定 4.5.9 域名转向 4.6 Mysql报错注入之floor报错详解…

基于Java影院管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

若依框架自定义左侧导航栏图标,并变色

若依框架中&#xff0c;有时候设计图跟若依框架自带的图标不一样&#xff0c;需要替换掉&#xff0c;问题来了&#xff0c;怎么替换&#xff0c;并变色&#xff1f; 1、设计师给的图标的路径必须是合并的&#xff0c;非多个路径&#xff0c;这样我们下载下来的svg图才有只有一个…

msvcr110.dll丢失的解决方法,亲测有效的几种解决方法

最近&#xff0c;我在启动一个程序时&#xff0c;系统突然弹出一个错误提示&#xff0c;告诉我电脑缺失了一个名为msvcr110.dll的文件。这让我感到非常困惑&#xff0c;因为我之前从未遇到过这样的问题。经过一番搜索和尝试&#xff0c;我总结了5种靠谱的解决方法。下面分享给大…

平凉小果子,平凡中的惊艳味道

平凉美食小果子&#xff0c;这看似平凡的名字背后&#xff0c;藏着无数平凉人的美好回忆。它不仅仅是一种食物&#xff0c;更是一种情感的寄托&#xff0c;一种文化的传承。小果子的制作过程看似简单&#xff0c;实则蕴含着深厚的功夫。选用优质的面粉作为主要原料&#xff0c;…

vue3-openlayers 要素聚合(cluster)、icon聚合

本篇介绍一下使用vue3-openlayers 要素聚合&#xff08;cluster&#xff09;&#xff0c;icon聚合 1 需求 要素聚合&#xff08;cluster&#xff09;&#xff0c;icon聚合 2 分析 使用ol-source-cluster 4 实现 <template><ol-map:loadTilesWhileAnimating"…

Redis数据库(五):Redis数据库基本特性

这一节我们来介绍如何使用C语言的库来操作Redis数据库。 目录 一、hiredis的安装 1.1 下载源码 1.2 解压 1.3 进入hiredis路径下 1.4 利用makefile文件进行编译 二、接口介绍 三、C程序操作Redis代码 四、redis.conf配置文件详解 五、Redis的持久化 5.1 RDB &#x…

优思学院|精益生产3大特征、5个步骤、8大浪费、10大工具

前言 精益生产作为一种先进的生产管理理念&#xff0c;起源于丰田汽车公司的生产方式&#xff0c;其核心在于消除浪费、优化流程&#xff0c;以最少的投入获取最大的产出。本文将详细解析精益生产的三大特征、五个步骤、八大浪费和十大工具&#xff0c;帮助读者深入理解这一理…

【java计算机毕设】学生作业管理系统java MySQL ssm JSP maven项目源代码+文档

1项目功能 【java计算机毕设】学生作业管理系统java MySQL ssm JSP maven 项目设计源代码 文档 期末小组作业 2项目介绍 系统功能&#xff1a; 学生作业管理系统包括管理员、小管理员、教师、学生四种角色。 管理员功能包括个人中心模块用于修改个人信息和密码、管理员管理、学…

DWC USB2.0协议学习2--架构介绍

目录 1 系统级架构 1.1 DWC_otg PMU模块 1.2 DWC_otg层次结构框图 1.3 DWC_otg功能模块框图 1.4 USB Host体系结构 1.4.1 发送FIFO 1.4.2 接收FIFO 1.5 USB Device体系结构 1.5.1专用发送FIFO 1.5.2 单个接收FIFO 2 DWC_otg_core架构 2.1 AHB总线接口单元(BIU) 2.2…

vue封装原生table表格方法

适用场景&#xff1a;有若干个表格&#xff0c;前面几列格式不一致&#xff0c;但是后面几列格式皆为占一个单元格&#xff0c;所以需要封装表格&#xff0c;表格元素自动根据数据结构生成即可&#xff1b;并且用户可新增列数据。 分类&#xff1a; 固定数据部分 就是根据数据…

合合信息智能文档抽取:赋能不良资产管理行业的数字化转型

官.网地址&#xff1a;合合TextIn - 合合信息旗下OCR云服务产品 随着数字化浪潮的汹涌澎湃&#xff0c;全球各行各业正经历着前所未有的变革。人工智能技术的快速发展&#xff0c;以其独特的创新能力和应用潜力&#xff0c;正在深刻地改变着业务模式&#xff0c;推动产业效率的…

【AIGC】AI技术兴起,设计师要大量失业了吗?

一、前言 随着技术的不断迭代&#xff0c;AIGC 能力的可控性得到了进一步提升&#xff0c;可应用的场景也越来越多&#xff0c;在文本、图像、视频等多个领域都有了广泛应用。 用户已经可以用自然语言来与 AI 工具进行交互&#xff0c;革新传统办公方式&#xff0c;工作任务可…

【Python datetime模块精讲】:时间旅行者的日志,精准操控日期与时间

文章目录 前言一、datetime模块简介二、常用类和方法三、date类四、time类五、datetime类六、timedelta类七、常用的函数和属性八、代码及其演示 前言 Python的datetime模块提供了日期和时间的类&#xff0c;用于处理日期和时间的算术运算。这个模块包括date、time、datetime和…

通过命令行配置调整KVM的虚拟网络

正文共&#xff1a;1234 字 20 图&#xff0c;预估阅读时间&#xff1a;2 分钟 在上篇文章中&#xff08;最小化安装的CentOS7部署KVM虚拟机&#xff09;&#xff0c;我们介绍了如何在最小化安装的CentOS 7系统中部署KVM组件和相关软件包。因为没有GUI图形界面&#xff0c;我们…

【Java Web】XML格式文件

目录 一、XML是什么 二、常见配置文件类型 *.properties类型&#xff1a; *.xml类型&#xff1a; 三、DOM4J读取xml配置文件 3.1 DOM4J的使用步骤 3.2 DOM4J的API介绍 一、XML是什么 XML即可扩展的标记语言&#xff0c;由标记语言可知其基本语法和HTML一样都是由标签构成的文件…

深度之眼(二十六)——神经网络基础知识(一)

文章目录 一、前言二、神经网络与多层感知机2.1 人工神经元2.2 人工神经网络2.3 多层感知机2.4 激活函数 一、前言 看了下课程安排&#xff0c;自己还是没安排好&#xff0c;刚刚捋清了一下思路。 基础&#xff1a;python、数理 认识&#xff1a;神经网络基础、opencv基础、py…

一探究竟:板式家具生产线如何实现精细加工?

随着科技的进步&#xff0c;板式家具行业正经历着一场由传统手工加工向自动化、智能化生产的转变。板式家具生产线如何实现精细加工&#xff0c;已成为行业内关注的焦点。那么&#xff0c;一条完整的板式家具生产线如何实现精细加工的呢&#xff1f;本文将深入探讨。 板式家具生…

直流电机双闭环调速Simulink仿真

直流电机参数&#xff1a; 仿真模型算法介绍&#xff1a; 1&#xff09;三相整流桥&#xff0c;采用半控功率器件SCR晶闸管&#xff1b; 2&#xff09;采用转速环、电流环 双闭环控制算法&#xff1b; 3&#xff09;外环-转速环&#xff0c;采用PI 比例积分控制&#xff1b;…