多后端张量库 Tensorvia 的 CPU 后端,移植到纯 C++23 模块化设计,使用 mcpp 构建。
原项目用 CMake + 传统头文件,本仓库是它的"模块化 CPU-only 分支",目标是验证 mcpp + C++23 模块在数值计算库上的可行性,并顺便做一些性能优化。
- 纯 C++23 模块——
import tensorvia;拿到完整 API,告别#include链 - mcpp 一键构建——
mcpp build完事,工具链自动管理 - OpenMP 并行 + AVX2/FMA/F16C 向量化——kernel 启用硬件加速
- 软件模拟的 float16/bfloat16——不依赖
<stdfloat>,跨工具链(GCC / Clang / MSVC)一致 - per-thread 内存池——循环里反复构造/析构 tensor 走 freelist,避免 malloc/free
Tensor::reset()——热循环里复用 tensor 容器,固定 shape+dtype 时零分配- 跨平台 CPUContext——自动检测并打印 CPU 型号、核数、内存、指令集支持
- debug 时自动打印硬件信息,release 时静默
不需要 CMake、不需要 vcpkg/Conan,所有依赖由 mcpp 自带的隔离工具链沙盒提供。
xlings install mcpp -y
# 或
curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bashmcpp build # 默认 release(-O2 + NDEBUG)
mcpp build --profile debug # 调试版(-O0 -g),会打印 CPU 硬件信息
mcpp test # 跑所有 tests/*.cpp
mcpp run # 构建 + 运行 src/main.cpp切换 profile 时 BMI 缓存会自动重建,首次切换慢一些。
Tensorvia-cpu/
├── mcpp.toml ← 工程配置
├── src/
│ ├── tensorvia.cppm ← 主模块接口,re-export 所有公共分区
│ ├── types.cppm ← :types 基础类型(Device/DataType/...)
│ ├── context.cppm ← :context ContextImpl 抽象基类
│ ├── tensor.cppm ← :tensor Tensor/TensorImpl/Metadata
│ ├── factory.cppm ← :factory 后端工厂注册机制
│ ├── cpu_context.cppm ← :cpu_context CPUContext(硬件信息)
│ ├── cpu_tensor.cppm ← :cpu_tensor CPUTensor(内部,不导出)
│ ├── cpu_ops.cppm ← :cpu_ops CPU 算子模板特化(内部)
│ ├── ops.cppm ← :ops_api ops:: 公共 API
│ ├── float16.hpp ← 软件模拟 IEEE 754 binary16
│ ├── bfloat16.hpp ← 软件模拟 BF16
│ ├── _platform.h ← RESTRICT 宏(跨编译器)
│ ├── core/
│ │ ├── tensor.cpp ← Tensor 实现
│ │ ├── factory.cpp ← 工厂实现
│ │ └── ops.cpp ← ops:: 调度层(输入校验 + 后端分发)
│ ├── init.cpp ← CPU 后端注册器
│ └── backend/cpu/
│ ├── cpu_tensor.cpp ← CPUTensor 实现(含内存池)
│ ├── cpu_context.cpp ← CPUContext 实现(跨平台硬件检测)
│ └── ops/
│ ├── arithmetic.cpp ← 加减乘除(AVX2 向量化)
│ ├── activate.cpp ← ReLU/SiLU/Sigmoid/Tanh/Softmax
│ ├── reduce.cpp ← Sum/Min/Max/Mean/Argmax/...
│ ├── mul.cpp ← 矩阵乘法
│ ├── logical.cpp ← ==/!=/>/</>=/<=
│ ├── transpose.cpp ← 2D / N-D 转置(分块并行)
│ ├── slice.cpp ← 切片
│ ├── concat.cpp ← 拼接
│ ├── typecast.cpp ← dtype 转换
│ ├── initializer.cpp ← Zeros/Ones/Fill/Random
│ ├── println.cpp ← tensor 打印
│ ├── export_csv.cpp ← CSV 导出
│ └── temp.cpp ← 实验性算子
└── tests/
└── test.cpp ← 烟雾测试
模块组织原则:
.cppm是模块接口单元(声明 + 短模板实现).cpp是模块实现单元(非模板的算法实现):types/:tensor/:factory/:cpu_context/:ops_api是公共分区,通过主模块tensorviare-export 给消费者:cpu_tensor/:cpu_ops是内部分区,只在模块内部使用,不对消费者可见
import std;
import tensorvia;
int main() {
// 创建张量
auto a = Tensor::Ones({2, 3}, DataType::FLOAT32);
auto b = Tensor::Fill({2, 3}, 2.0f, DataType::FLOAT32);
// 算术运算(运算符重载 + 显式 ops:: 调用都行)
auto c = a + b;
ops::println(c);
// 矩阵乘法
auto d = ops::Mul(a.view({3, 2}), b.view({2, 3}));
// 归约
float s = ops::Sum(c);
auto max_per_row = ops::Max(c, /*axis=*/1);
// 类型转换
auto f16 = ops::Typecast(c, DataType::FLOAT16);
// 转置 / 切片 / 拼接
auto t = c.t();
auto sliced = c.slice({{0, 1}, {0, 2}});
auto cat = ops::Concat({a, b}, 1);
}// ❌ 每次循环重新构造(开销 ~100 ns/op)
for (int i = 0; i < N; ++i) {
Tensor buf = Tensor::Zeros({256, 256}, DataType::FLOAT32);
// ...
}
// ✅ 复用 tensor 容器(~18 ns/op,5× 加速)
Tensor buf;
for (int i = 0; i < N; ++i) {
buf.reset({256, 256}, DataType::FLOAT32); // shape+dtype 不变时零分配
ops::Zeros(buf); // in-place 填零
// ...
}CPUTensor 的数据 buffer 走 std::pmr::unsynchronized_pool_resource,每个 OpenMP 线程独享一个 pool:
- 同尺寸 tensor buffer 进同 size-class freelist,O(1) 复用
- 零锁争用(per-thread pool)
- 64 字节对齐,AVX-512 友好
实测单线程 ~10M tensors/sec(每次构造+析构 ~95 ns)。
reset(shape, dtype) 在 shape+dtype 不变时复用整个 TensorImpl + 元数据 vector 容量,零分配。比反复构造快 5×。
跟静态工厂 Tensor::Zeros(...) 对应的 in-place 版本,配合 reset 用最省。
主要算术(add/sub/mul)的 float32 / float64 路径有 AVX2 特化(_mm256_add_ps 等),其余 dtype 走模板通用版本。OpenMP 在 N > 4096 时自动并行化。
mcpp build 默认 release(-O2 + -DNDEBUG)。
debug profile(mcpp build --profile debug)会在 CPUContext 构造时打印:
***************************CPU Device Info******************
CPU Model: Intel(R) Core(TM) i5-14600KF
Logical cores: 20
Total memory: 31909 MB
AVX2: yes | FMA: yes | F16C: yes | AVX512F: no | SSE4.2: yes
************************************************************
通过 #ifndef NDEBUG 门控,跟 assert() 同一开关。
<stdfloat> 不是所有工具链都有:
| 工具链 | <stdfloat> |
float16/bfloat16 类型选择 |
|---|---|---|
| GCC | ✅ | std::float16_t/std::bfloat16_t |
| Clang | ❌ | 强制软件模拟 |
| MSVC | ❌ | 强制软件模拟 |
| ICPX | ✅ | sycl::half/sycl::bfloat16(本项目不体现) |
| NVCC | ✅ | __half/__nv_bfloat16(本项目不体现) |
| 平台 | 型号 | 内存 | 指令集 |
|---|---|---|---|
| Linux | /proc/cpuinfo |
sysconf(_SC_PHYS_PAGES) |
__builtin_cpu_supports |
| macOS | sysctlbyname |
sysctlbyname("hw.memsize") |
同上 |
| Windows | 注册表 | GlobalMemoryStatusEx |
IsProcessorFeaturePresent |
mcpp.toml 当前固定 -mavx2 -mfma -mf16c,要求 x86 host。迁到 ARM(Apple Silicon 等)需要:
- 删掉这三个标志
- 把
<immintrin.h>相关 AVX intrinsic 改成 NEON / SVE - 调整
cpu_context.cpp的 ISA 检测分支
未来可以用 [features] 把这些标志做成可选,按平台 / 编译时能力自动开关。
| 维度 | 原 Tensorvia | Tensorvia-cpu |
|---|---|---|
| 构建系统 | CMake | mcpp |
| 源码组织 | .h + .cpp(传统) |
.cppm + .cpp(C++23 模块) |
| 后端 | CPU + CUDA + SYCL + Vulkan | 仅 CPU |
| 浮点类型 | 跟随编译器(条件编译) | 自动分流(原生优先,缺则软件) |
| 内存管理 | malloc/free | per-thread PMR pool |
| 命名空间 | via:: / ops:: |
全局 + ops:: |
| 算子 API | 一致 | 一致 + in-place 系列 + reset() |
API 基本兼容,可以比较容易地反向合并回原项目(增量添加内存池、reset、in-place ops 等优化)。
mcpp clean && mcpp buildmcpp build 末尾的 Finished release [optimized] 是误报,真实标志看:
mcpp build --verbose 2>&1 | grep "TENSORVIA\|-O\|-D"mcpp 自动生成 compile_commands.json,clangd / ccls 可以直接消费。但模块单元(.cppm)的 IDE 诊断可能不准确,以 mcpp build 输出为准。
放 tests/test_*.cpp,mcpp test 自动发现并每个文件编译成一个独立二进制。
改 mcpp.toml 里的 version = "" 顺手更新 README 顶部 / CHANGELOG(如有)
git add mcpp.toml git -c user.email="aimol@local" -c user.name="Aimol-l" commit -m "release: v"
git tag -a v -m "" git push origin main git push origin v
mcpp publish --dry-run 产物在 target/dist/: Tensorvia-cpu-.tar.gz Tensorvia-cpu.lua sha256sum target/dist/Tensorvia-cpu-.tar.gz # 确认 sha
gh release create v target/dist/Tensorvia-cpu-.tar.gz
--title "v"
--notes "Release v. See README for changes."
cd /tmp/mcpp-index
拉最新 upstream(防止落后于其它新增包) git checkout main git pull upstream main git push origin main # 同步 fork
新建分支 git checkout -b update-tensorvia-cpu-
拷贝新版 lua(覆盖旧文件)
cp /home/aimol/Documents/C++/CppWorkspace/Tensorvia-cpu/target/dist/Tensorvia-cpu.lua
pkgs/t/Tensorvia-cpu.lua
手动编辑:保留 namespace/maintainers/Form A 注释 只替换 url 和 sha256(步骤 3 的产物),其它字段不要改 $EDITOR pkgs/t/Tensorvia-cpu.lua
git diff pkgs/t/Tensorvia-cpu.lua
- x86 专用编译标志:
-mavx2 -mfma -mf16c写死,ARM 上需要手动调整 - OpenMP 锁定到 GCC/Clang:MSVC 的
/openmp不兼容,需要平台分流 - Tensor 拷贝是深拷贝:未实现 view 共享语义(这是后续大改方向)
- shared_ptr 原子计数:在 OpenMP 高并发下仍是瓶颈,要去掉得改 ownership 模型
MIT,见 mcpp.toml 中的 license 字段。