首页
/ PyTorch C++ 前端张量创建完全指南:Factory 函数、TensorOptions 与数据导入

PyTorch C++ 前端张量创建完全指南:Factory 函数、TensorOptions 与数据导入

2026-09-07 11:42:07作者:农烁颖Land

本篇指南以 PyTorch C++ 前端(torch:: API,也即 LibTorch)为核心,系统讲解如何通过 factory 工厂函数创建各种初始化形态的张量:从 zerosonesrandnarange 到零维张量与 Scalar;同时深入剖析统一创建范式、尺寸(shape)表达方式、TensorOptions 的四个配置轴(dtype/layout/device/requires_grad)以及用 from_blob 包装外部内存等关键能力。读完本篇,你将能够在 C++ 环境中完全等效地完成 Python 侧常见的张量创建与转换操作,并能结合 PyTorch 仓库源码理解其底层实现。

本文主体依据仓库文档 docs/cpp/source/api/aten/creation.md 整理扩充。该文档位于 C++ 文档体系的 ATen API 分类下(见 docs/cpp/source/api/aten/index.md)。为便于绝大多数用户使用,示例统一使用更友好的 torch:: 命名空间;ATen/ATen.h 提供了功能对等的 at:: 底层命名空间,本文的绝大部分接口均可直接照搬到 at:: 下使用。

统一创建范式:所有 factory 函数都遵循同一套 schema

Factory(工厂)函数指那些"从零创建一个全新张量"的函数,与从已有张量派生的操作(如 viewpermute)不同,它们不依赖输入数据,只依赖你传入的配置参数。

所有 factory 函数都遵循一个统一调用范式:

torch::<function-name>(<function-specific-options>, <sizes>, <tensor-options>)

即调用由三部分构成:

  1. 函数特定参数(可选):某些函数独有,如 randint 的取值范围、full 的填充值;
  2. sizes(尺寸):沿每个维度的长度;
  3. tensor-options(张量选项):统一控制 dtype、layout、device 与 requires_grad,同样可选,省略时使用默认值。

也就是说,只有函数名 + sizes 是必有的;大部分调用只需要一行代码即可完成。这一统一约定在仓库的自动生成代码中也有体现:PyTorch 的 torch:: 张量创建入口由 tools/autograd/templates/variable_factories.h 模板在构建期自动生成,其中每个 factory 的重载都围绕 at::IntArrayRef sizesconst at::TensorOptions& options 展开。

可用 Factory 函数总览

原文档列出的核心 factory 函数如下表所示,覆盖了日常创建张量的绝大多数场景:

函数 初始化行为
torch::zeros 全 0 填充的张量
torch::ones 全 1 填充的张量
torch::empty 未初始化的张量(内存内容不确定,分配速度最快)
torch::full 用单一标量值填充的张量
torch::rand [0, 1) 区间上的均匀分布随机张量
torch::randn 标准正态分布(均值 0、方差 1)随机张量
torch::randint 指定范围内的随机整型张量
torch::arange 等差数列序列(整数或浮点)
torch::linspace 线性等间隔的数值序列
torch::logspace 对数等间隔的数值序列
torch::eye 单位矩阵(对角线为 1、其余为 0)
torch::randperm 0 到 n-1 的一个随机排列

其中随机类的 randrandnrandintrandperm 依赖 PyTorch 的全局随机数生成器(RNG),其 CPU 端内核实现位于 aten/src/ATen/native/TensorFactories.cpp,属于 ATen native 工厂实现的一个集中入口文件,与本篇所有 factory 函数一一对应。

指定尺寸:int64_t 与 IntArrayRef

单一向量尺寸:不需要额外参数的函数可以直接用一个整数指定长度。例如下面的语句创建一个含 5 个分量的向量:

torch::Tensor tensor = torch::ones(5);

多维尺寸:当需要表达多维形状时,通过在花括号中逐个列出每个维度的长度来构造一个 IntArrayRef。例如 {2, 3} 表示 2 行 3 列的矩阵,{3, 4, 5} 表示一个三维张量:

torch::Tensor tensor = torch::randn({3, 4, 5});
assert(tensor.sizes() == std::vector<int64_t>{3, 4, 5});

除了花括号初始化列表,也可以直接传入 std::vector<int64_t>

std::vector<int64_t> shape = {3, 4, 5};
torch::Tensor tensor = torch::randn(shape);
  • tensor.sizes() 可一次性取出完整的形状(返回 IntArrayRef);
  • tensor.size(i) 访问单个维度 i 的长度;
  • 所有尺寸底层都是 int64_t,这也是 PyTorch 全栈(Python 端 torch.Size 同样基于 64 位整数)的统一约定。

函数特定参数:注意"sizes 永远跟在函数特定参数之后"

部分 factory 函数带独有的业务参数。例如 randint 需要指定随机整数的取值范围。只给上界时写法如下:

// 在 [0, 10) 内均匀随机生成整数,形状为 5x5
torch::Tensor tensor = torch::randint(/*high=*/10, {5, 5});

同时给出下界与上界:

// 在 [3, 10) 内均匀随机生成整数,形状为 5x5
torch::Tensor tensor = torch::randint(/*low=*/3, /*high=*/10, {5, 5});

提示: 无论函数特定参数有多少个,sizes 始终排在它们之后、TensorOptions 之前,即统一 schema 中 <sizes><tensor-options> 的相对位置是固定的。

注意: 也有不需要 sizes 的特例。比如 arangelinspacelogspaceeyerandperm 这类函数,其输出形状完全由函数特定参数(区间边界、端点数量、矩阵阶数等)推导得出,因此调用时不要再传尺寸参数。例如 arange 只需要起止边界即可确定序列长度:

// 从 0 到 9 的等差序列,等价于 Python 的 torch.arange(10)
torch::Tensor seq = torch::arange(10);

TensorOptions:四个配置轴与合法取值

TensorOptions 是 PyTorch C++ 中"张量属性配置"的统一载体,用来指定新建张量的 dtype(元素数据类型)、layout(内存布局)、device(计算设备)与 requires_grad(是否需要梯度跟踪)。其类型定义位于 aten/src/ATen/TensorOptions.h,在 C++ 前端头文件 torch/csrc/api/include/torch/types.h 中被引入并直接可用。

四个配置轴的说明与允许取值如下:

  • dtype:元素数据类型。文档明确给出的取值包括 kUInt8kInt8kInt16kInt32kInt64kFloat32kFloat64
  • layout:张量内存布局,取 kStrided(稠密、带步长)或 kSparse(稀疏);
  • device:计算设备,取 kCPUkCUDA(CUDA 可附带设备索引);
  • requires_grad:是否记录梯度,取 truefalse

dtype 的 Rust 风格简写

除上述"完整名称"外,还存在一套 Rust 风格简写。仓库源码 torch/csrc/api/include/torch/types.h 中可看到两组别名的完整定义:既有 kFloat32 = at::kFloat 这种 ATen 名称到 torch:: 名称的映射,也定义了 kF32 = kFloat32kF16kF64kI8kI16kI32kI64kU8kU16kU32kU64 等简写别名。因此写 kF32 与写 kFloat32 完全等价。完整取值清单可在该头文件中直接查阅。

提示: 若需要完整的 dtype 别名列表(包括 kFloat16kUInt16 等扩展类型),请直接查看仓库中的 torch/csrc/api/include/torch/types.h,该文件即原文档所指的 torch/types.h

组合式构建与链式调用

下面是文档中的完整示例:构造一个 TensorOptions 对象,将四个轴全部显式指定——kFloat64 双精度、kStrided 布局、编号为 1 的 CUDA 设备、需要梯度:

auto options =
  torch::TensorOptions()
    .dtype(torch::kFloat64)
    .layout(torch::kStrided)
    .device(torch::kCUDA, 1)
    .requires_grad(true);

torch::Tensor tensor = torch::full({3, 4}, /*value=*/123, options);

// 逐项校验配置确实生效
assert(tensor.dtype() == torch::kFloat64);
assert(tensor.layout() == torch::kStrided);
assert(tensor.device().type() == torch::kCUDA);
assert(tensor.device().index() == 1);
assert(tensor.requires_grad());

注意上面 device(torch::kCUDA, 1) 中第二个参数 1 即 CUDA 设备索引;full 的函数特定参数是填充值 123

默认值与"全默认"写法

任何一个省略的轴都会自动取默认值:dtype 默认 kFloat32,layout 默认 kStrided,device 默认 kCPUrequires_grad 默认 false。因此最简单的情形下可以完全省略 TensorOptions

// 等价于:32 位浮点、stride 布局、CPU、不追踪梯度
torch::Tensor tensor = torch::randn({3, 4});

简写自由函数:torch::dtype() / torch::layout() / torch::device() / torch::requires_grad()

TensorOptions 的四个轴对应,torch:: 命名空间下还提供了四个自由函数:torch::dtype()torch::device()torch::layout()torch::requires_grad()。每个都返回一个可继续用 builder 方法链式细化的 TensorOptions 对象。下面三组写法彼此等价:

// 写法 1:显式构造 TensorOptions
torch::ones(10, torch::TensorOptions().dtype(torch::kFloat32))

// 写法 2:使用自由函数简写
torch::ones(10, torch::dtype(torch::kFloat32))

// 写法 3:自由函数 + 链式追加其他轴
torch::ones(10, torch::dtype(torch::kFloat32).layout(torch::kStrided))

隐式构造:只改一个轴时的极简写法

TensorOptions 可以从单个数支持隐式构造。因此当只有某一个轴与默认值不同时,可以直接把该值作为第三参传入,省略其余所有轴。例如只把 dtype 改成 kFloat32 时:

torch::ones(10, torch::kFloat32)

与 Python 的完整对照

把上述机制组合起来,C++ 的张量创建调用与 Python 侧几乎一一对应。原文档给出如下对照:

# Python
torch.randn(3, 4, dtype=torch.float32, device=torch.device('cuda', 1), requires_grad=True)
// C++
torch::randn({3, 4}, torch::dtype(torch::kFloat32).device(torch::kCUDA, 1).requires_grad(true))

两者语义完全一致:形状 3x4、float32、CUDA 设备 1 号、追踪梯度。差异仅在于 C++ 的命名空间常量(torch::kFloat32/torch::kCUDA)与 Python 的对象写法(torch.float32/torch.device('cuda', 1))。

导入外部数据:from_blob 包装既有内存

如果你已经拥有了一块自行分配的内存(位于 CPU 或 CUDA 上,例如来自 C 数组、图像解码缓冲或其它框架的输出),可以用 from_blob 直接把它"看作"一个 Tensor,而无需任何拷贝:

float data[] = {1, 2, 3, 4, 5, 6};
torch::Tensor tensor = torch::from_blob(data, {2, 3});  // 2x3 的视图

from_blob 的 C++ 前端声明由模板 tools/autograd/templates/variable_factories.h 生成,从源码可见它提供多组重载,便于按需选择:

  • 基础形态:from_blob(void* data, at::IntArrayRef sizes, const at::TensorOptions& options = {})
  • 带 strides 的形态:from_blob(void* data, at::IntArrayRef sizes, at::IntArrayRef strides, ...),用于内存中本身带步长(非连续)的数据;
  • 带自定义 deleter 的形态:传入 std::function<void(void*)>,当张量释放时会回调该 deleter 去释放原始内存。

从实现细节看,from_blob 会先通过 at::from_blob(...) 构建底层张量,并在构造 Variable 时把 TensorOptions 中的 requires_grad 显式取出单独处理(见 tools/autograd/templates/variable_factories.h),因此 from_blob 同样可以指定 dtype 来解释原始字节。

注意:from_blob 创建的张量不能 resize。原因在于 ATen 并不拥有这块内存——它只是一个轻量视图。如果你需要改变形状,应通过 view/reshape 这类不搬移数据的操作,或先拷贝出属于自己的数据。

补充:data 来自需要手动管理的分配器,务必传入对应的 deleter 重载,否则张量释放时不会回收外部内存,可能造成泄漏;反之如果你不需要接管释放,就不要传 deleter,由外部继续管理生命周期。

另外,若想从 C++ 的字面量数据容器(如嵌套花括号 {{1,2},{3,4}}std::vectorat::ArrayRef)构造张量,可改用 torch::tensor(...)。仓库源码 torch/csrc/api/include/torch/detail/TensorDataContainer.h 中的注释明确了其类型推断规则:整型数据默认生成 at::kLong(即 int64_t),浮点数据默认生成 torch::get_default_dtype(),这与 Python 端 torch.tensor 的默认行为保持一致。

张量转换:to() 在 dtype 与 device 之间迁移

创建之后经常需要把张量从一个 dtype 或设备迁移到另一个。此时使用 to()转换会创建一块全新内存上的新张量,绝不会原地修改原张量

torch::Tensor source = torch::randn({2, 3}, torch::kInt64);

// 只转换 dtype:int64 转为 float32
torch::Tensor float_tensor = source.to(torch::kFloat32);

// 迁移到 GPU(默认 CUDA 0 号设备)
torch::Tensor gpu_tensor = float_tensor.to(torch::kCUDA);

// 迁移到指定 CUDA 设备
torch::Tensor gpu1_tensor = float_tensor.to(torch::Device(torch::kCUDA, 1));

// 异步拷贝:从 GPU 拷回 CPU,non_blocking=true 允许不阻塞调用线程
torch::Tensor async_tensor = gpu_tensor.to(torch::kCPU, /*non_blocking=*/true);

要点总结:

  • to(torch::kFloat32) 这类写法依赖 TensorOptions隐式构造机制,把单值直接解释为 dtype 目标;
  • 设备目标可用 torch::Device(torch::kCUDA, 1) 精确到具体卡;
  • non_blocking 参数主要对 GPU 与固定内存(pinned memory)主机之间的拷贝有意义,用于隐藏拷贝延迟。

注意: 转换结果是指向新内存的全新张量,与原张量之间没有任何共享存储关系,对其中一个的写入不会影响另一个(详见 ATen 张量转换相关实现)。

Scalar 与零维张量:单值如何参与运算

Scalar:动态类型的单一数值

Scalar 用于表示单个"动态类型"的数值。和 Tensor 类似,它也是动态类型的,可以容纳 ATen 支持的任何数值类型,并且可以从 C++ 内建数值类型隐式构造。这意味着在编写算子时,可以把它设计成"既能接整数又能接浮点"的通用入口。原文档以 addmmsum 的函数签名为例:

namespace torch {
// beta、alpha 都是 Scalar,因此调用方可以直接传 1.0、0.5 这类字面量
Tensor addmm(Scalar beta, const Tensor & self,
             Scalar alpha, const Tensor & mat1,
             const Tensor & mat2);
// 对张量求和返回一个 Scalar
Scalar sum(const Tensor & self);
} // namespace torch

实际用法中无需显式构造 Scalar,直接传入 C++ 数值即可,字面量会被隐式转换为 Scalar

torch::Tensor a = ...;
torch::Tensor b = ...;
torch::Tensor c = ...;
// 1.0 与 .5 分别隐式转换成 beta 与 alpha 两个 Scalar
torch::Tensor r = torch::addmm(1.0, a, .5, b, c);

Scalar 的底层定义位于 ATen 的公共头文件 ATen/Scalar.h(该头文件在 docs/cpp/source/api/aten/index.md 的 ATen 公共 API 头文件清单中列出)。当需要对结果做进一步数值处理时,可调用 Scalar::toDouble()Scalar::toLong() 等方法把它转回具体 C++ 类型。

零维张量:单值张量与下标引用

零维(0-dim)张量同样只含一个值。它有两种常见来源:一类是 factory 或归约运算返回的标量张量,另一类是通过下标访问更大张量中的元素——此时取出的正是引用该元素的零维张量,如下例所示:

torch::Tensor matrix = torch::rand({10, 20});
matrix[1][2] = 4;  // matrix[1][2] 是一个零维张量

注意这里 matrix[1][2] 之所以能出现在赋值左侧,是因为它是大张量内部存储的一个视图(零维张量),对它的写入会直接作用到 matrix 的对应元素上。这与 Scalar(值语义、独立存储)形成鲜明对比,是理解 C++ 前端索引与赋值行为的关键。更深入的索引写法(高级索引、index_put 等)可继续阅读同目录文档 indexing.md

源码佐证与进一步阅读

综上,PyTorch C++ 前端的张量创建围绕"factory 函数 + sizes + TensorOptions"这一极简 schema 展开,覆盖从全零/全一、随机分布到等差数列、外部内存包装的全部初始化形态,足以在 C++ 侧完整复刻 Python 端的张量构造体验。

登录后查看全文
热门项目推荐
相关项目推荐