news 2026/9/18 22:37:22

ATVOSS DeviceAdapter 详解:host/device 桥接层的设计原理与 Run 运行流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ATVOSS DeviceAdapter 详解:host/device 桥接层的设计原理与 Run 运行流程

ATVOSS DeviceAdapter 详解:host/device 桥接层的设计原理与 Run 运行流程

【免费下载链接】atvossATVOSS(Ascend C Templates for Vector Operator Subroutines)是一套基于Ascend C开发的Vector算子库,致力于为昇腾硬件上的Vector类融合算子提供极简、高效、高性能、高拓展的编程方式。项目地址: https://gitcode.com/cann/atvoss

DeviceAdapter 是 ATVOSS 中位于 host 侧的 device 适配层类,它把用户以表达式方式书写的算子配置(KernelOp)封装为可直接在昇腾硬件上运行的实体,内部统一完成 ACL 资源管理、参数解析、Tiling 计算与 kernel 启动。本文以 DeviceAdapter.md 与 DeviceAdapter_Run.md 为骨架,结合 device_adapter.h、tiling.h、arguments.h 等源码,深入讲解其类设计、Run接口参数语义、底层五步执行流水线,并给出可直接落地的完整代码示例,帮助读者掌握 ATVOSS 算子从“表达式配置”到“device 上执行”的完整通路。

一、DeviceAdapter 的功能定位

ATVOSS(Ascend C Templates for Vector Operator Subroutines)将 Vector 融合算子的开发抽象为三个层次:

  1. Block 层:由BlockBuilder承担,把单个核的任务切分为多个 tile,完成每次数据搬运与计算;
  2. Kernel 层:由KernelBuilder承担,负责计算 Tiling 信息、依据 block ID 确定当前核需要处理的 GM 数据,并下发给 Block;
  3. Device 层:由DeviceAdapter承担,构造 device 适配层对象,桥接 host 与 device

按照 device_adapter.h 中的类注释,DeviceAdapter 是一个通用适配器(generic adapter),为不同的算子调用提供统一的 host 侧接口,内部封装了 Acl 相关的资源管理并自动处理 kernel 调用。也就是说,用户只需要面向表达式描述“算什么、怎么算”,DeviceAdapter 负责把计算描述落到真实的昇腾执行环境中。

其头文件为 device_adapter.h,文档中给出的类原型如下:

template <typename KernelOp> class DeviceAdapter{ DeviceAdapter() {}; }

类模板的唯一模板参数KernelOp是用户算子配置中kernel层的静态配置与调度策略,即由Atvoss::Ele::KernelBuilder<BlockOp>实例化得到的类型。

二、模板参数 KernelOp 说明

参数名称参数类型输入/输出数据类型参数说明默认值
KernelOp模板参数输入NAkernel 层的用户静态配置和调度策略NA

从 device_adapter.h 可以看到,KernelOp上还派生出一组关键类型别名,它们决定了 DeviceAdapter 内部如何使用该配置:

using ExprMaker = typename KernelOp::ScheduleClz::ExprMaker; using BlockOp = typename KernelOp::ScheduleClz::BlockTemplate; using OpParam = typename KernelOp::ScheduleCfgClz; template <typename T> using Tensor = DeviceTensor<T>;
  • ExprMaker:表达式构造器,用于在 host 侧重新实例化用户的计算表达式(Compute());
  • BlockOp:kernel 层携带的 Block 模板类型,供 Tiling 计算时逐级调用;
  • OpParam:kernel/block 两级调度配置(Tiling 信息)的组合类型;
  • Tensor:device 侧的张量视图,即 DeviceTensor,它持有T* ptr_指针并提供GetPtr()operator[]等访问方式,IsTensor标记用于模板特化识别。

在 examples/abs/abs.cpp 中可以看到真实的组装顺序:先由BlockBuilder生成BlockOp,再由KernelBuilder包装成KernelOp,最终交给DeviceAdapter

using ArchTag = Atvoss::Arch::DAV_3510; using BlockOp = Atvoss::Ele::BlockBuilder<AbsCompute, ArchTag, blockPolicy, Atvoss::Ele::DefaultBlockConfig>; using KernelOp = Atvoss::Ele::KernelBuilder<BlockOp, kernelPolicy>; using DeviceOp = Atvoss::DeviceAdapter<KernelOp>;

DeviceAdapter的无参构造函数(DeviceAdapter() {})意味着该对象是轻量句柄,通常作为局部对象实例化后直接调用Run

三、DeviceAdapter::Run 主运行接口

3.1 函数原型

template <typename Args> int64_t Run(const Args& arguments, aclrtStream stream = nullptr)

Run是 device 适配层的主运行接口,负责完成host 侧的参数解析device 侧入参数据结构对象的准备,随后触发 kernel 启动。

3.2 参数说明

参数名称参数类型输入/输出数据类型参数说明默认值
Args模板参数输入NA用户的输入参数列表,类型根据用户传入的参数实例化NA
arguments函数形参输入Args用户传入的参数列表NA
stream函数形参输入aclrtStream用户创建的 stream 流对象nullptr

其中arguments通常由Atvoss::ArgumentsBuilder构建(见 arguments.h),它实际是一个二元组:(inputOutput 元组, attrs 元组)Run内部通过std::get<0>(arguments)取出输入输出参数元组继续处理。

3.3 返回值说明

返回值数据类型返回值说明
int64_tdevice 层执行的结果,0:成功,-1:失败

当 Tiling 计算失败时,Run会打印[ERROR]: [Atvoss][Device] CalcParam failed!并返回 -1(见 device_adapter.h)。因此调用方应检查返回值以确认算子是否成功下发。

四、Run 的源码级五步执行流水线

Run的实现(device_adapter.h)可以分解为五个阶段,下面结合源码逐步展开。

4.1 表达式线性化:还原用户计算语义

auto expr = ToLinearizerExpr(ExprMaker{}.template Compute<Tensor>()); using Expr = typename decltype(expr)::Type; using Params = Atvoss::Params_t<Expr>;

这里用KernelOp携带的ExprMaker在 host 侧重新执行用户定义的Compute(),得到计算表达式树后线性化(linearize),再通过Params_t<Expr>提取出全部PlaceHolder参数的静态描述(参数序号、数据类型、ParamUsage流向等)。这一步是后续参数准备与转换的“类型蓝图”。

4.2 参数准备:PrepareParams

auto argTuple = std::get<0>(arguments); auto params = PrepareParams<Params>(argTuple);

PrepareParams逐个把用户传入的实参按照PlaceHolder的序号构造为DeviceTensor或标量包装对象(ConstructParam,见 device_adapter.h):

  • 当参数声明为Tensor<InputDtype>而实参是Atvoss::Tensor时,构造DeviceTensor<T>,其内部直接引用用户传入的 device 指针(ptr_ = src.data(),见 device_tensor.h);
  • 当参数是标量(如示例中的in3)时,直接构造标量类型。

静态断言保证了Params的参数数量与用户实参元组大小严格一致,不一致会在编译期报错(见 device_adapter.h)。

4.3 动态参数计算:Tiling

OpParam opParam; if (!CalculateTiling<KernelOp>(arguments, opParam)) { ... return -1; }

CalculateTiling定义在 tiling.h,它分两级调用调度策略:

  1. 先调用KernelOp::ScheduleClz::MakeScheduleConfig(arguments, cfg.kernelParam)计算 kernel 层配置(如核数blockNum、每核处理单元数等,参见 DefaultKernelConfig);
  2. 再调用BlockOp::ScheduleClz::MakeScheduleConfig(arguments, cfg.kernelParam, cfg.blockParam)计算 block 层配置(如整 tile 数、尾 tile 元素数等,参见 DefaultBlockConfig)。

arguments中的 attr(如示例里的attr("dim", 5))在这里作为调度依据被消费。

4.4 参数转换:ConvertArgs 与 TransformArgs

auto convertArgs = ConvertArgs<Params>(params, argTuple);

ConvertArgs依据Params中每个参数的位置与 usage,把params与原始argTuple重新对齐成最终传给 kernel 的元组。随后LaunchKernelWithDataTuple会通过TransformArgs(见 device_adapter.h)完成最终形态转换:

  • 标量类型:原样转发(std::forward<T>(value));
  • Tensor 类型:取其 device 指针(value.GetPtr())。

TransformArgs有编译期static_assert,只接受标量或IsTensor特化类型,从类型系统上杜绝了非法入参。

4.5 Kernel 启动:LaunchKernelWithDataTuple

LaunchKernelWithDataTuple<KernelOp>(opParam.kernelParam.blockNum, stream, opParam, convertArgs);

启动入口是KernelCustom(device_adapter.h),它是一个__global__ __aicore__核函数,声明了KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY)(即 AIV 核执行),通过AscendC::Std::make_index_sequence展开参数元组并调用KernelWrapper,最终执行KernelOp op; op.Run(cfg, args...)进入 kernel 层调度(kernel/builder.h)。

此外,源码中提供ATVOSS_DEBUG_MODE == 2的性能剖析分支:此时 kernel 会连续启动 200 次(代码注释标注为 profiling run times),便于观测算子耗时;默认分支只启动一次。

五、完整使用示例

5.1 算子配置中的 DeviceAdapter

以下示例完整来自 DeviceAdapter.md 与 DeviceAdapter_Run.md,展示了一个 AddSub(out = in1 + in2 - in3)算子从 Compute 到 DeviceOp 的完整组装:

template <typename InputDtype, typename OutputDtype> struct AddSubConfig { struct AddSubCompute { template <template <typename> class Tensor> __host_aicore__ constexpr auto Compute() const { auto in1 = Atvoss::PlaceHolder<1, Tensor<InputDtype>, Atvoss::ParamUsage::IN>(); auto in2 = Atvoss::PlaceHolder<2, Tensor<InputDtype>, Atvoss::ParamUsage::IN>(); auto in3 = Atvoss::PlaceHolder<3, InputDtype, Atvoss::ParamUsage::IN>(); auto out = Atvoss::PlaceHolder<4, Tensor<OutputDtype>, Atvoss::ParamUsage::OUT>(); return (out = in1 + in2 - in3); }; }; using ArchTag = Atvoss::Arch::DAV_3510; using BlockOp = Atvoss::Ele::BlockBuilder<AddSubCompute, ArchTag>; using KernelOp = Atvoss::Ele::KernelBuilder<BlockOp>; using DeviceOp = Atvoss::DeviceAdapter<KernelOp>; };

要点说明:

  • PlaceHolder<N, T, ParamUsage>中的序号N决定参数在 kernel 实参列表中的位置,ParamUsage::IN/OUT/IN_OUT声明数据流向(参见 ParamUsage.md);
  • ArchTag = Atvoss::Arch::DAV_3510指定目标昇腾芯片架构。

5.2 host 侧调用 DeviceAdapter::Run

template <typename InputDtype, typename OutputDtype> static void Run() { /* ACL init and stream create */ ... Atvoss::Tensor<InputDtype> in1(deviceIn1, {{3, 4, 0, 0, 0, 0, 0, 0}}, 2); Atvoss::Tensor<InputDtype> in2(deviceIn2, {{3, 4, 0, 0, 0, 0, 0, 0}}, 2); InputDtype in3 = 5.0; Atvoss::Tensor<OutputDtype> out(deviceOut, {{3, 4, 0, 0, 0, 0, 0, 0}}, 2); auto arguments = Atvoss::ArgumentsBuilder{}.inputOutput(in1, in2, in3, out).attr("dim", 5).build(); using DeviceOp = typename AddSubConfig<InputDtype, OutputDtype>::DeviceOp; DeviceOp deviceOp; deviceOp.Run(arguments, stream); } int main(int argc, char const* argv[]) { Run<float, float>(); return 0; }

几点实战说明:

  • Atvoss::Tensor的构造函数为Tensor(T* dataPtr, uint64_t* inputShape, size_t dims),shape 数组固定按 8 维描述,未用到的维度补 0(见 tensor.h);示例中{{3, 4, 0, 0, 0, 0, 0, 0}}表示一个 3×4 的二维张量;
  • ArgumentsBuilderinputOutput只接受Atvoss::Tensor与标量类型,attr添加的键值对(如"dim")会被 Tiling 计算消费;不允许传入裸指针(有static_assert保护,见 arguments.h);
  • stream可省略(默认nullptr),但实际使用时建议传入通过aclrtCreateStream创建的 stream 流对象,以便与其他 ACL 任务同步调度。

5.3 与 ACL 初始化流程的完整衔接

以 examples/abs/abs.cpp 为参照,一次完整的DeviceAdapter::Run调用需要包裹在标准 ACL 生命周期中:

  1. aclInit(nullptr)初始化 ACL,结束时aclFinalize()
  2. aclrtSetDevice(deviceId)设置 device;
  3. aclrtCreateContext(&context, deviceId)创建 Context;
  4. aclrtCreateStream(&stream)创建 Stream;
  5. aclrtMalloc为输入/输出分配 device 内存(ACL_MEM_MALLOC_HUGE_FIRST);
  6. aclrtMemcpy完成 Host→Device 数据拷贝;
  7. Atvoss::Tensor包装 device 指针并构建arguments
  8. 实例化DeviceOp deviceOp;并调用deviceOp.Run(arguments, stream);
  9. aclrtSynchronizeStream(stream)同步,再aclrtMemcpy拷回结果验证。

该样例同时展示了返回值检查的实践:Run返回 0 表示 kernel 成功下发,结合 stream 同步即可安全读取输出。

六、约束说明与易错点

  • 约束说明:文档标注NA,即无额外硬件/软件约束;但使用时需保证:
    • Atvoss::Tensor包装的是已经通过aclrtMalloc分配的 device 内存,而非 host 内存;
    • ArgumentsBuilder::inputOutput的实参类型必须为Atvoss::Tensor或标量(禁止裸指针);
    • 实参数量与Compute()PlaceHolder的数量保持一致,否则编译期static_assert会失败;
    • shape 维度数在 1~8 之间(MAX_DIMS = 8,见 tensor.h)。
  • 返回值检查:务必检查Runint64_t返回值,0 成功、-1 失败(Tiling 计算失败时返回 -1)。
  • 性能剖析:编译期定义ATVOSS_DEBUG_MODE == 2会令Run连续启动 200 次 kernel 用于性能采样,正式发布请勿开启。

七、从文档到源码的进阶阅读路径

  • 类定义与 Run 实现:device_adapter.h
  • Tiling 计算:tiling.h
  • Device 侧张量视图:device_tensor.h
  • host 侧参数构建:arguments.h
  • Kernel 层构建器:include/elewise/kernel/builder.h
  • Block 层构建器:include/elewise/block/builder.h
  • 端到端样例:examples/abs/abs.cpp、examples/muls/muls.cpp、examples/rms_norm/rms_norm.cpp
  • 相关 API 文档:DeviceAdapter_Run.md、ArgumentsBuilder_inputOutput.md、ParamUsage.md

通过本文可以完整理解:用户在Compute()中书写表达式 →BlockBuilder/KernelBuilder逐级封装 →DeviceAdapter::Run完成表达式还原、参数准备、Tiling 计算、参数转换与 kernel 启动。掌握这条通路后,即可基于 ATVOSS 快速开发并调试自己的 Vector 融合算子。

【免费下载链接】atvossATVOSS(Ascend C Templates for Vector Operator Subroutines)是一套基于Ascend C开发的Vector算子库,致力于为昇腾硬件上的Vector类融合算子提供极简、高效、高性能、高拓展的编程方式。项目地址: https://gitcode.com/cann/atvoss

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 22:35:31

电商图片批量采集实战:DOM+内存双源提取方案

1. 这不是“爬虫教程”&#xff0c;而是一份电商图片批量采集的实战手记我第一次接到这个需求&#xff0c;是帮一个做跨境选品的朋友整理竞品图库。他每天要翻200个淘宝、京东、亚马逊、ASOS的商品页&#xff0c;手动右键保存主图、细节图、场景图、白底图……平均每个页面耗时…

作者头像 李华
网站建设 2026/9/18 22:34:56

无网也能写 AI 会议纪要:anarlog 离线模式完整指南

无网也能写 AI 会议纪要&#xff1a;anarlog 离线模式完整指南 【免费下载链接】anarlog Open source Granola AI Alternative 项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog anarlog 的离线模式&#xff1a;一款开源 AI 会议笔记应用&#xff0c;监听你的…

作者头像 李华