0

0

c++扩展算子开发③:CUDA算子的开发

P粉084495128

P粉084495128

发布时间:2025-07-17 17:02:07

|

824人浏览过

|

来源于php中文网

原创

本文介绍了使用C++进行CUDA算子开发的流程,以tanh算子为例,包含编写.cu文件实现运算、.cpp文件实现Python调用绑定、.py文件实现安装。展示了前向输出和回传梯度与官方实现一致,还详细拆分了各文件代码及作用。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

c++扩展算子开发③:cuda算子的开发 - php中文网

c++扩展算子开发③:CUDA算子的开发

项目说明

  在使用c++进行CUDA算子开发

开发流程

  • 编写.cu文件
    • 实现该算子的运算部分,在使用setup.py对算子进行安装时,nvcc程序针对.cu文件进行编译,并最终包含进动态链接库
  • 编写.cpp文件
    • 使得可以在python中调用CUDA kernel函数,.cpp调用上面.cu文件中启动函数,绑定到python中使用
  • 编写.py文件
    • 实现该算子安装

项目展示

  在GPU上面运行tanh算子,可以看到官方实现的算子和我们自己实现的CUDA算子的前向输出和回传梯度都一致   安装自己实现的tanh算子,运行后请刷新下环境!!!

In [ ]
!python setup.py install
In [1]
import numpy as np
x = np.random.random((4, 10)).astype("float32")print(x)
[[0.8485352  0.82548    0.6914224  0.33665353 0.5060949  0.12096553
  0.93415546 0.66898936 0.36616254 0.61785257]
 [0.9686086  0.8368737  0.87306726 0.5306038  0.35964754 0.09533529
  0.6159888  0.5113984  0.3554379  0.92584795]
 [0.5851171  0.87855285 0.8729009  0.16328739 0.06106287 0.03119349
  0.6431769  0.46255094 0.39092144 0.6841152 ]
 [0.41889587 0.85792965 0.48324853 0.8920178  0.7228439  0.2088154
  0.18290831 0.74242246 0.770023   0.89185   ]]

tanh(Offical)

In [2]
import paddle
paddle_x = paddle.to_tensor(x, place=paddle.CUDAPlace(0))
paddle_x.stop_gradient = Falsepaddle_y = paddle.tanh(paddle_x)
paddle_y.backward()
grad = paddle_x.gradient()print("==========================================================")print("前向传播:")print(paddle_y)print("==========================================================")print("检测是否在GPU上:")print(paddle_y.place)print("==========================================================")print("梯度:")print(grad)
W0112 18:06:20.751464  7652 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1
W0112 18:06:20.756742  7652 device_context.cc:465] device: 0, cuDNN Version: 7.6.
==========================================================
前向传播:
Tensor(shape=[4, 10], dtype=float32, place=CUDAPlace(0), stop_gradient=False,
       [[0.69030344, 0.67804146, 0.59889495, 0.32448652, 0.46689692, 0.12037896,
         0.73252547, 0.58431470, 0.35063058, 0.54963106],
        [0.74809217, 0.68414962, 0.70292914, 0.48584250, 0.34490353, 0.09504751,
         0.54832906, 0.47103402, 0.34118930, 0.72865224],
        [0.52637470, 0.70569360, 0.70284498, 0.16185147, 0.06098709, 0.03118338,
         0.56705880, 0.43216103, 0.37215433, 0.59418815],
        [0.39599988, 0.69518942, 0.44884148, 0.71238893, 0.61866784, 0.20583236,
         0.18089549, 0.63060653, 0.64694285, 0.71230626]])
==========================================================
检测是否在GPU上:
CUDAPlace(0)
==========================================================
梯度:
[[0.52348113 0.5402598  0.6413248  0.8947085  0.7820073  0.9855089
  0.46340644 0.6585763  0.8770582  0.6979057 ]
 [0.4403581  0.53193927 0.5058906  0.7639571  0.8810415  0.99096596
  0.6993352  0.77812696 0.88358986 0.4690659 ]
 [0.72292966 0.5019965  0.5060089  0.9738041  0.99628055 0.9990276
  0.6784443  0.81323683 0.86150116 0.6469404 ]
 [0.8431841  0.51671165 0.7985413  0.492502   0.6172501  0.957633
  0.9672768  0.6023354  0.58146495 0.49261978]]

tanh(Ours)

1、安装tanh算子,运行后请刷新下环境!!!(前面已经安装了)

In [4]
!python setup.py install

2、开始测试

立即学习C++免费学习笔记(深入)”;

In [3]
import paddlefrom custom_ops import tanh_op
custom_ops_x = paddle.to_tensor(x, place=paddle.CUDAPlace(0))
custom_ops_x.stop_gradient = Falsecustom_ops_y = tanh_op(custom_ops_x)
custom_ops_y.backward()
grad = custom_ops_x.gradient()print("==========================================================")print("前向传播:")print(custom_ops_y)print("==========================================================")print("检测是否在GPU上:")print(custom_ops_y.place)print("==========================================================")print("梯度:")print(grad)
==========================================================
前向传播:
Tensor(shape=[4, 10], dtype=float32, place=CUDAPlace(0), stop_gradient=False,
       [[0.69030344, 0.67804146, 0.59889495, 0.32448652, 0.46689692, 0.12037896,
         0.73252547, 0.58431470, 0.35063058, 0.54963106],
        [0.74809217, 0.68414962, 0.70292914, 0.48584250, 0.34490353, 0.09504751,
         0.54832906, 0.47103402, 0.34118930, 0.72865224],
        [0.52637470, 0.70569360, 0.70284498, 0.16185147, 0.06098709, 0.03118338,
         0.56705880, 0.43216103, 0.37215433, 0.59418815],
        [0.39599988, 0.69518942, 0.44884148, 0.71238893, 0.61866784, 0.20583236,
         0.18089549, 0.63060653, 0.64694285, 0.71230626]])
==========================================================
检测是否在GPU上:
CUDAPlace(0)
==========================================================
梯度:
[[0.52348113 0.5402598  0.6413248  0.8947085  0.7820073  0.9855089
  0.46340644 0.6585763  0.8770582  0.6979057 ]
 [0.4403581  0.53193927 0.5058906  0.7639571  0.8810415  0.99096596
  0.6993352  0.77812696 0.88358986 0.4690659 ]
 [0.72292966 0.5019965  0.5060089  0.9738041  0.99628055 0.9990276
  0.6784443  0.81323683 0.86150116 0.6469404 ]
 [0.8431841  0.51671165 0.7985413  0.492502   0.6172501  0.957633
  0.9672768  0.6023354  0.58146495 0.49261978]]

项目主体

.cu文件

  .cu文件主要是实现该算子的运算部分,在使用setup.py对算子进行安装时,nvcc程序针对.cu文件进行编译,并最终包含进动态链接库

代码拆分

  1、引入头文件,以及定义一个block含有的thread数目

In [ ]
#include <paddle/extension.h>#include <vector>#include <cuda.h>#include <cuda_runtime.h>#define BLOCK 512

  2、定义前向传播运算函数
  该函数是一个CUDA特有声明为__global__的模板函数,负责具体执行运算部分
  这里的blockIdx,blockDim,threadIdx分别表示block索引,block维度,thread索引,GPU上有多个并发的线程同时负责以上计算,用gid=blockIdx.x * blockDim.x + threadIdx.x这一语句用来计算绝对索引,负责返回数据中某个位置处值,这样就只需要关注于单个线程计算过程

In [ ]
template<typename data_t>
__global__ void tanh_forward_cuda_kernel(const data_t* input_data,
                                    data_t* output_data,                                    int input_numel){    int gid = blockIdx.x * blockDim.x + threadIdx.x;    for(int i=gid; i<input_numel; i+=blockDim.x*gridDim.x){
        output_data[i] = std::tanh(input_data[i]);
    }
}

  3、定义前向传播启动函数
  该函数是一个返回paddle::Tensor类型的函数,负责对输入进行一些转换,数据初始化以及返回前向传播运算成果
  这里的PD_DISPATCH_FLOATING_TYPES这个宏,实现了动态分发机制(dynamic dispatch),即它会在运行时,根据输入具体的数值类型,去决定之前CUDA kernel模块函数需要实例化为哪种函数吗,这也是之前用模板类data_t的原因。
  PD_DISPATCH_FLOATING_TYPES这个宏函数,传入的参数有三个:数据类型,用来报错的函数名、一个Lambda函数
  ①数据类型可以通过.type()获取
  ②用来报错的函数名可以自己命名,一般与该算子作用相关
  ③Lambda函数部分([&]表示该Lambda表达式中用到的外部变量是传引用的)包括前面2中实现的运算函数tanh_forward_cuda_kernel;运算函数后面用到了<<< >>>这一写法启动kernel,其中需要根据输出大小分配grid数(用grid = (input_numel + BLOCK - 1) / BLOCK算出来),并设置每一block中的thread数(宏定义中的BLOCK),还有传入tensor目前所在的stream;接着就是( )里面传递参数进运算函数tanh_forward_cuda_kernel

In [ ]
std::vector<paddle::Tensor> tanh_forward_cuda(const paddle::Tensor &input){
    auto output = paddle::Tensor(paddle::PlaceType::kGPU, input.shape());    int input_numel = input.size();    int grid = (input_numel + BLOCK - 1) / BLOCK;

    PD_DISPATCH_FLOATING_TYPES(        input.type(), "tanh_forward_cuda_kernel", ([&] {
            tanh_forward_cuda_kernel<data_t><<<grid, BLOCK, 0, input.stream()>>>(                input.data<data_t>(), 
                output.mutable_data<data_t>(input.place()), 
                input_numel
            );
        })
    );    return {output};
}

  4、同理,定义反向回传的运算函数和启动函数

In [ ]
template<typename data_t>
__global__ void tanh_backward_cuda_kernel(const data_t* input_data,
                                    const data_t* output_grad_data,
                                    data_t* input_grad_data,                                    int output_numel){    int gid = blockIdx.x * blockDim.x + threadIdx.x;    for(int i=gid; i<output_numel; i+=blockDim.x*gridDim.x){
        input_grad_data[i] = output_grad_data[i] * (1 - std::pow(std::tanh(input_data[i]), 2));
    }
}

std::vector<paddle::Tensor> tanh_backward_cuda(const paddle::Tensor &input,
                                               const paddle::Tensor &output,
                                               const paddle::Tensor &output_grad){
    auto input_grad = paddle::Tensor(paddle::PlaceType::kGPU, input.shape());    int output_numel = output.size();    int grid = (output_numel + BLOCK - 1) / BLOCK;

    PD_DISPATCH_FLOATING_TYPES(        input.type(), "tanh_backward_cuda_kernel", ([&] {
            tanh_backward_cuda_kernel<data_t><<<grid, BLOCK, 0, input.stream()>>>(                input.data<data_t>(), 
                output_grad.data<data_t>(), 
                input_grad.mutable_data<data_t>(input.place()), 
                output_numel
            );
        })
    );    return {input_grad};
}

完整代码

In [ ]
#include <paddle/extension.h>#include <vector>#include <cuda.h>#include <cuda_runtime.h>#define BLOCK 512template<typename data_t>
__global__ void tanh_forward_cuda_kernel(const data_t* input_data,
                                    data_t* output_data,                                    int input_numel){    int gid = blockIdx.x * blockDim.x + threadIdx.x;    for(int i=gid; i<input_numel; i+=blockDim.x*gridDim.x){
        output_data[i] = std::tanh(input_data[i]);
    }
}

template<typename data_t>
__global__ void tanh_backward_cuda_kernel(const data_t* input_data,
                                    const data_t* output_grad_data,
                                    data_t* input_grad_data,                                    int output_numel){    int gid = blockIdx.x * blockDim.x + threadIdx.x;    for(int i=gid; i<output_numel; i+=blockDim.x*gridDim.x){
        input_grad_data[i] = output_grad_data[i] * (1 - std::pow(std::tanh(input_data[i]), 2));
    }
}

std::vector<paddle::Tensor> tanh_forward_cuda(const paddle::Tensor &input){
    auto output = paddle::Tensor(paddle::PlaceType::kGPU, input.shape());    int input_numel = input.size();    int grid = (input_numel + BLOCK - 1) / BLOCK;

    PD_DISPATCH_FLOATING_TYPES(        input.type(), "tanh_forward_cuda_kernel", ([&] {
            tanh_forward_cuda_kernel<data_t><<<grid, BLOCK, 0, input.stream()>>>(                input.data<data_t>(), 
                output.mutable_data<data_t>(input.place()), 
                input_numel
            );
        })
    );    return {output};
}

std::vector<paddle::Tensor> tanh_backward_cuda(const paddle::Tensor &input,
                                               const paddle::Tensor &output,
                                               const paddle::Tensor &output_grad){
    auto input_grad = paddle::Tensor(paddle::PlaceType::kGPU, input.shape());    int output_numel = output.size();    int grid = (output_numel + BLOCK - 1) / BLOCK;

    PD_DISPATCH_FLOATING_TYPES(        input.type(), "tanh_backward_cuda_kernel", ([&] {
            tanh_backward_cuda_kernel<data_t><<<grid, BLOCK, 0, input.stream()>>>(                input.data<data_t>(), 
                output_grad.data<data_t>(), 
                input_grad.mutable_data<data_t>(input.place()), 
                output_numel
            );
        })
    );    return {input_grad};
}

.cpp文件

  .cpp文件是为了使得可以在python中调用CUDA kernel函数,它调用上面.cu文件中启动函数,绑定到python中使用

代码拆分

  1、引入头文件,以及定义PADDLE_WITH_CUDA和CHECK_INPUT(x)
  ①PADDLE_WITH_CUDA是用来能够获取Tensor.steam(),详细可看官方定义下的代码

#if defined(PADDLE_WITH_CUDA)
  /// \bref Get current stream of Tensor
  cudaStream_t stream() const;#elif defined(PADDLE_WITH_HIP)
  hipStream_t stream() const;#endif

  ②CHECK_INPUT(x)用来查验Tensor是否在GPU上面或者数据类型是否出错
In [ ]
#include <paddle/extension.h>#include <vector>#define PADDLE_WITH_CUDA#define CHECK_INPUT(x) PD_CHECK(x.place() == paddle::PlaceType::kGPU, #x " must be a GPU Tensor.")

  2、声明.cu里的启动函数,以便后面编程时进行联想以及让编译器知道这么一个函数

In [ ]
std::vector<paddle::Tensor> tanh_forward_cuda(const paddle::Tensor &input);

std::vector<paddle::Tensor> tanh_backward_cuda(const paddle::Tensor &input,
                                               const paddle::Tensor &output,
                                               const paddle::Tensor &output_grad);

  3、编写前向传播函数,主要实现调用.cu里的前向传播启动函数

In [ ]
std::vector<paddle::Tensor> tanh_forward(const paddle::Tensor& input) {
  CHECK_INPUT(input);  return tanh_forward_cuda(input);
}

  4、编写反向传播函数,主要实现调用.cu里的反向回传启动函数

In [ ]
std::vector<paddle::Tensor> tanh_backward(const paddle::Tensor& input,
                                          const paddle::Tensor& output,
                                          const paddle::Tensor& output_grad) {
  CHECK_INPUT(input);
  CHECK_INPUT(output);
  CHECK_INPUT(output_grad);  return tanh_backward_cuda(input, output, output_grad);
}

  5、使用PD_BUILD_OP系列宏,构建算子的描述信息,实现python与c++算子的绑定,作用有点类似PYBIND11_MODULE
  PD_BUILD_OP:用于构建前向算子
  PD_BUILD_GRAD_OP:用于构建前向算子对应的反向算子
  注意:构建同一个算子的前向、反向实现,宏后面使用的算子名需要保持一致(此例中的tanh_op)
  注意:PD_BUILD_OP与PD_BUILD_GRAD_OP中的Inputs与Outputs的name有强关联,对于前向算子的某个输入,如果反向算子仍然要复用,那么其name一定要保持一致(此例中的Inputs({"input"}和Outputs({"output"}),因为内部执行时,会以name作为key去查找对应的变量,比如这里前向算子的input与反向算子的input指代同一个Tensor

In [ ]
PD_BUILD_OP(tanh_op)
    .Inputs({"input"})
    .Outputs({"output"})
    .SetKernelFn(PD_KERNEL(tanh_forward));

PD_BUILD_GRAD_OP(tanh_op)
    .Inputs({"input", "output", paddle::Grad("output")})
    .Outputs({paddle::Grad("input")})
    .SetKernelFn(PD_KERNEL(tanh_backward));

完整代码

In [ ]
#include <paddle/extension.h>#include <vector>#define PADDLE_WITH_CUDA#define CHECK_INPUT(x) PD_CHECK(x.place() == paddle::PlaceType::kGPU, #x " must be a GPU Tensor.")std::vector<paddle::Tensor> tanh_forward_cuda(const paddle::Tensor &input);

std::vector<paddle::Tensor> tanh_backward_cuda(const paddle::Tensor &input,
                                               const paddle::Tensor &output,
                                               const paddle::Tensor &output_grad);

std::vector<paddle::Tensor> tanh_forward(const paddle::Tensor& input) {
  CHECK_INPUT(input);  return tanh_forward_cuda(input);
}

std::vector<paddle::Tensor> tanh_backward(const paddle::Tensor& input,
                                          const paddle::Tensor& output,
                                          const paddle::Tensor& output_grad) {
  CHECK_INPUT(input);
  CHECK_INPUT(output);
  CHECK_INPUT(output_grad);  return tanh_backward_cuda(input, output, output_grad);
}

PD_BUILD_OP(tanh_op)
    .Inputs({"input"})
    .Outputs({"output"})
    .SetKernelFn(PD_KERNEL(tanh_forward));

PD_BUILD_GRAD_OP(tanh_op)
    .Inputs({"input", "output", paddle::Grad("output")})
    .Outputs({paddle::Grad("input")})
    .SetKernelFn(PD_KERNEL(tanh_backward));

.py文件

  .py文件主要是实现该算子安装

  在安装后引用该算子,以此为例,是通过from custom_ops import tanh_op来引用的

  其中custom_ops来自setup.py部分的name里

  c++扩展算子开发③:CUDA算子的开发 - php中文网

  其中tan_op来自.cpp部分的PD_BUILD_OP里

  c++扩展算子开发③:CUDA算子的开发 - php中文网

In [ ]
from paddle.utils.cpp_extension import CUDAExtension, setup

setup(
    name='custom_ops',
    ext_modules=CUDAExtension(
        sources=['tanh.cpp', 'tanh.cu']
    )
)

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号