Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tensorvia-cpu

多后端张量库 TensorviaCPU 后端,移植到纯 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 自带的隔离工具链沙盒提供。


快速开始

安装 mcpp

xlings install mcpp -y
#
curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash

构建 / 运行 / 测试

mcpp 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公共分区,通过主模块 tensorvia re-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 填零
    // ...
}

性能优化

1. Per-thread 内存池

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)。

2. Tensor::reset() API

reset(shape, dtype) 在 shape+dtype 不变时复用整个 TensorImpl + 元数据 vector 容量,零分配。比反复构造快 5×。

3. ops::Fill/Zeros/Ones(Tensor&, ...) in-place 算子

跟静态工厂 Tensor::Zeros(...) 对应的 in-place 版本,配合 reset 用最省。

4. AVX2 向量化 kernel

主要算术(add/sub/mul)的 float32 / float64 路径有 AVX2 特化(_mm256_add_ps 等),其余 dtype 走模板通用版本。OpenMP 在 N > 4096 时自动并行化。


Debug vs Release

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() 同一开关。


跨平台说明

C++ 工具链分流

<stdfloat> 不是所有工具链都有:

工具链 <stdfloat> float16/bfloat16 类型选择
GCC std::float16_t/std::bfloat16_t
Clang 强制软件模拟
MSVC 强制软件模拟
ICPX sycl::half/sycl::bfloat16(本项目不体现)
NVCC __half/__nv_bfloat16(本项目不体现)

CPU 硬件信息检测

平台 型号 内存 指令集
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 等)需要:

  1. 删掉这三个标志
  2. <immintrin.h> 相关 AVX intrinsic 改成 NEON / SVE
  3. 调整 cpu_context.cpp 的 ISA 检测分支

未来可以用 [features] 把这些标志做成可选,按平台 / 编译时能力自动开关。


跟原 Tensorvia 项目的关系

维度 原 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.toml 后强制重建

mcpp clean && mcpp build

查看实际编译标志

mcpp build 末尾的 Finished release [optimized] 是误报,真实标志看:

mcpp build --verbose 2>&1 | grep "TENSORVIA\|-O\|-D"

clangd 集成

mcpp 自动生成 compile_commands.json,clangd / ccls 可以直接消费。但模块单元(.cppm)的 IDE 诊断可能不准确,以 mcpp build 输出为准。

写新测试

tests/test_*.cppmcpp test 自动发现并每个文件编译成一个独立二进制。


打包发布

1. 升版本 + commit

改 mcpp.toml 里的 version = "" 顺手更新 README 顶部 / CHANGELOG(如有)

git add mcpp.toml git -c user.email="aimol@local" -c user.name="Aimol-l" commit -m "release: v"

2. 打 tag + 推 main

git tag -a v -m "" git push origin main git push origin v

3. mcpp 打包(生成 tarball + xpkg 描述符)

mcpp publish --dry-run 产物在 target/dist/: Tensorvia-cpu-.tar.gz Tensorvia-cpu.lua sha256sum target/dist/Tensorvia-cpu-.tar.gz # 确认 sha

4. 创建 GitHub Release + 上传 tarball

gh release create v target/dist/Tensorvia-cpu-.tar.gz
--title "v"
--notes "Release v. See README for changes."

5. 更新 mcpp-index fork 的 lua 描述符

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 字段。

About

多后端张量库Tensorvia的CPU 后端,移植到纯 C++23 模块化设计,使用mcpp构建。

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages