Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
---

<!-- COVERAGE_START -->
![English Coverage](https://img.shields.io/badge/en_coverage-98%25-green.svg) 602/614 docs translated
![English Coverage](https://img.shields.io/badge/en_coverage-98%25-green.svg) 611/624 docs translated
<!-- COVERAGE_END -->

## 这是什么项目
Expand Down
49 changes: 49 additions & 0 deletions code/examples/vol8/cache_layout.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 行主序 vs 列主序的 cache 实测
// 配套 documents/vol8-domains/ai/tiny_ml/stage2/02-weight-shape.md
// 点"运行"在 Compiler Explorer 云端执行,绝对数字因机器而异,
// 但"行序远快于列序"的趋势一定成立。

#include <algorithm>
#include <chrono>
#include <cstdio>
#include <vector>

int main() {
constexpr int N = 1024;
std::vector<float> m((size_t)N * N);
for (int i = 0; i < N * N; ++i)
m[i] = float(i % 7);
volatile float sink = 0; // 防止累加循环被优化掉

double row_t[5], col_t[5];
for (int it = 0; it < 5; ++it) {
float acc;
// 行序遍历:内层走 c,顺着一行连续读 —— cache 命中
auto a = std::chrono::high_resolution_clock::now();
acc = 0;
for (int r = 0; r < N; ++r)
for (int c = 0; c < N; ++c)
acc += m[(size_t)r * N + c];
auto b = std::chrono::high_resolution_clock::now();
sink = acc;
row_t[it] = std::chrono::duration<double, std::milli>(b - a).count();

// 列序遍历:内层走 r,跨行读同一列 —— cache miss
auto c1 = std::chrono::high_resolution_clock::now();
acc = 0;
for (int c = 0; c < N; ++c)
for (int r = 0; r < N; ++r)
acc += m[(size_t)r * N + c];
auto d = std::chrono::high_resolution_clock::now();
sink = acc;
col_t[it] = std::chrono::duration<double, std::milli>(d - c1).count();
}

std::sort(row_t, row_t + 5);
std::sort(col_t, col_t + 5);
std::printf("N=%d, -O2, 5 次取中位数:\n", N);
std::printf(" 行序(row-major): %.2f ms\n", row_t[2]);
std::printf(" 列序(col-major): %.2f ms\n", col_t[2]);
std::printf(" 列序 / 行序 = %.1f 倍\n", col_t[2] / row_t[2]);
return 0;
}
110 changes: 110 additions & 0 deletions code/volumn_codes/vol8-labs/ai/tiny_ml/stage2/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# .clang-format 配置文件
# 用于 clangd 格式化和代码检查
# 生成日期: 2026-02-15

# 基础风格: LLVM (简洁现代)
BasedOnStyle: LLVM

# 缩进设置
IndentWidth: 4
UseTab: Never
ColumnLimit: 100

# 大括号风格: 附加式 { 在语句末尾
BreakBeforeBraces: Attach

# 指针/引用星号位置: 左侧 (int* a)
PointerAlignment: Left
DerivePointerAlignment: false

# 命名风格 (仅供参考,不影响已有代码)
# 可以让 clangd 警告不符合命名规范的代码
# 需要配合 clang-tidy 使用

# 函数声明: 返回类型和函数名在同一行
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes

# 结构体初始化: 按位置赋值
# C++11UnifiedBraceList: false 使用传统风格

# 预处理器缩进: 与代码对齐
IndentPPDirectives: AfterHash

# 其他实用设置

# 在二元运算符后换行
BreakBeforeBinaryOperators: None

# 在三元运算符后换行
BreakBeforeTernaryOperators: true

# 连续赋值时的对齐
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false

# 转换类型周围的空格
SpaceAfterCStyleCast: false

# 逻辑运算符周围的空格
SpaceAfterLogicalNot: false

# 模板列表中的空格
SpaceAfterTemplateKeyword: true

# 控制语句括号内的空格: if (x) 而非 if (x )
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false

# 圆括号内的空格
SpacesInParentheses: false
SpacesInSquareBrackets: false

# 容器类型周围的空格
SpacesInContainerLiterals: false

# lambda 相关
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false

# 对齐
AlignAfterOpenBracket: Align
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true

# 允许函数参数在同一行
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true

# 允许短函数在一行
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false

# 自动对齐注释
ReflowComments: true

# 最大连续空行数
MaxEmptyLinesToKeep: 1

# 命名空间缩进
NamespaceIndentation: None

# 访问修饰符缩进
IndentAccessModifiers: false
IndentCaseLabels: true

# 换行符设置
LineEnding: LF

# C/C++ 语言设置
Language: Cpp
Standard: Latest

# 包含块排序
SortIncludes: true
IncludeBlocks: Preserve
1 change: 1 addition & 0 deletions code/volumn_codes/vol8-labs/ai/tiny_ml/stage2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build*/
41 changes: 41 additions & 0 deletions code/volumn_codes/vol8-labs/ai/tiny_ml/stage2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.20)

project(tamcpp_mlinfra LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 不调这个,CMake 不生成 CTestTestfile.cmake,catch_discover_tests 注册的
# add_test 全悬空,ctest 永远 No tests found。
enable_testing()

message(STATUS "Ready to fetch the necessary test framework")

include(FetchContent)

FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.0
)

FetchContent_MakeAvailable(Catch2)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)

add_library(TAMCPP_TinyML INTERFACE include/tinyml/tensor.hpp include/tinyml/dense.hpp include/tinyml/debug.hpp)
target_include_directories(TAMCPP_TinyML INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)

# 跨编译器的 warning 选项封进一个函数,后续每个 target 一行调用就行,
# 不用在每个 CMakeLists.txt 里反复抄一遍 generator-expression。
# 比较坑的点:这个 Lab 要同时吃 MSVC 和 GCC/Clang,两边选项根本不通用,
# 只能用 genex 按编译器分流——还得是跑到 Windows 下编译一轮才吃到这个亏!
function(tamcpp_target_warnings target)
target_compile_options(${target} PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4;/permissive-;/Zi>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall;-Wextra;-Wpedantic;-g>
)
endfunction()

add_subdirectory(tests)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <cstddef>
#include <cstdio>
#include <format>
#include <string>

#include "tinyml/tensor.hpp"

namespace tamcpp::tinyml {
template <std::size_t Rows, std::size_t Cols, typename StorageType = float>
inline void debug_tensor(const Tensor<Rows, Cols, StorageType>& tensor) {
std::string buffer;
buffer.reserve(Rows * Cols * 16);

for (std::size_t row = 0; row < Rows; ++row) {
buffer += "[ ";

for (std::size_t col = 0; col < Cols; ++col) {
if constexpr (std::is_floating_point_v<StorageType>) {
std::format_to(std::back_inserter(buffer), "{:.6g}", tensor(row, col));
} else {
std::format_to(std::back_inserter(buffer), "{}", tensor(row, col));
}

if (col + 1 != Cols) {
buffer += ", ";
}
}

buffer += " ]\n";
}

std::fwrite(buffer.data(), 1, buffer.size(), stdout);
}
} // namespace tamcpp::tinyml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include <cstddef>
#include <span>

#include "tinyml/tensor.hpp"

namespace tamcpp::tinyml {

template <std::size_t In, std::size_t Out, typename StorageType = float>
struct Dense {
static constexpr std::size_t kIn = In;
static constexpr std::size_t kOut = Out;

static_assert(In > 0 && Out > 0, "We haven't seen a tensor with 0 size");

using Bias_t = Vector<Out, StorageType>;
using BiasView_t = std::span<const StorageType, Out>;
using Weight_t = Tensor<Out, In, StorageType>;
using WeightView_t = std::span<const StorageType, Out * In>;

constexpr Dense(const Weight_t& weight, const Bias_t& bias) noexcept
: weight_(weight.view()), bias_(bias.view()) {}

constexpr Vector<Out, StorageType>
forward(const Vector<In, StorageType>& vec_in) const noexcept {
Vector<Out, StorageType> result{};
// Vector = Tensor<1, N>,只有第 0 行;行下标必须是 0,
// 否则 internals_[1*N + j] 越过 std::array 边界(运行期 UB,
// 编译期还会让 forward 无法 constexpr 求值)。
for (std::size_t vec_out_index = 0; vec_out_index < Out; ++vec_out_index) {
StorageType& acc = result(0, vec_out_index); // 取引用,省掉每次 += 的下标重算
acc = bias_[vec_out_index];
for (std::size_t vec_in_index = 0; vec_in_index < In; ++vec_in_index) {
acc += vec_in(0, vec_in_index) * weight_[vec_out_index * In + vec_in_index];
}
}
return result;
}

constexpr std::span<const StorageType, Out * In> weight() const noexcept { return weight_; }
constexpr std::span<const StorageType, Out> bias() const noexcept { return bias_; }

private:
WeightView_t weight_;
BiasView_t bias_;
};

} // namespace tamcpp::tinyml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once
#include <array>
#include <cstddef>
#include <expected>
#include <span>

namespace tamcpp::tinyml {

template <std::size_t Rows, std::size_t Cols, typename StorageType = float>
class Tensor {
public:
enum class Error {
kShapeMismatch,
kOutOfRange,
};

constexpr std::expected<StorageType, Error> at(std::size_t i, std::size_t j) noexcept {
if (i >= Rows || j >= Cols) {
return std::unexpected{Error::kOutOfRange};
}

return internals_[i * Cols + j];
}

constexpr std::expected<const StorageType, Error> at(std::size_t i,
std::size_t j) const noexcept {
if (i >= Rows || j >= Cols) {
return std::unexpected{Error::kOutOfRange};
}
return internals_[i * Cols + j];
}

static_assert(Rows > 0 && Cols > 0, "We haven't seen a tensor with 0 size");

// Q: why not noexcept?
constexpr Tensor() = default; // Yeah, a default tensor
constexpr Tensor(std::array<StorageType, Rows * Cols> internals)
: internals_(std::move(internals)) {}

/* These APIs are burden, yet we need this when we need to query the tensor metas */
constexpr std::size_t row() const noexcept { return Rows; }
constexpr std::size_t col() const noexcept { return Cols; }

// noted by Charliechen114514, Actually array::size_type :_, but I got myself lazy
constexpr std::size_t size() const noexcept { return internals_.size(); }

// Hey! Get me!
constexpr StorageType& operator()(std::size_t i, std::size_t j) noexcept {
return internals_[i * Cols + j];
}

// Mark:
// functions overload differs in type params and const decorators
constexpr const StorageType& operator()(std::size_t i, std::size_t j) const noexcept {
return internals_[i * Cols + j];
}

// span view
constexpr std::span<const StorageType, Rows * Cols> view() const noexcept { return internals_; }
constexpr std::span<StorageType, Rows * Cols> view() noexcept { return internals_; }

constexpr std::array<StorageType, Rows * Cols>& storage() noexcept { return internals_; }
constexpr std::array<const StorageType, Rows * Cols>& storage() const noexcept {
return internals_;
}

private:
/**
* @brief Reason why we select one dimension is easy, cache friendly
*
*/
std::array<StorageType, Rows * Cols> internals_{}; // internal_arrays, one dimension
};

template <std::size_t Cols, typename StorageType = float>
using Vector = Tensor<1, Cols, StorageType>;

} // namespace tamcpp::tinyml
17 changes: 17 additions & 0 deletions code/volumn_codes/vol8-labs/ai/tiny_ml/stage2/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 顶层只把 Catch2 的 extras 加进了 MODULE_PATH,要用 catch_discover_tests
# 还得自己 include(Catch) 把命令加载进来。
include(Catch)

# 这个 Lab 会有好几个测试 target,依赖集合都一样
# (Catch2 + TAMCPP_TinyML)+ 同一套跨编译器 warning + 都要注册到 ctest。
# 封一层糖,每个测试一行起,不用反复抄,也避免手抄抄出 bug。
function(tamcpp_add_test name source)
add_executable(${name} ${source})
target_link_libraries(${name} PRIVATE Catch2::Catch2WithMain TAMCPP_TinyML)
tamcpp_target_warnings(${name})
catch_discover_tests(${name})
endfunction()

tamcpp_add_test(smoke_catch2 smoke.cpp)
tamcpp_add_test(tensor_api tensor_api.cpp)
tamcpp_add_test(dense_api dense_api.cpp)
Loading
Loading