资讯动态

x86 与 AVX SIMD 编程实战:从 AVX2 到 Intel AMX 的 CPU 性能优化指南(Maths, CS AI Compendium)

发布时间:2026/9/16 18:24:23 来源:尧图企业网站定制
x86 与 AVX SIMD 编程实战从 AVX2 到 Intel AMX 的 CPU 性能优化指南Maths, CS AI Compendium【免费下载链接】maths-cs-ai-compendiumBecome a cracked AI/ML researcher/engineer with this unconventional textbook covering maths, computing, and ML with intuition.项目地址: https://gitcode.com/GitHub_Trending/mat/maths-cs-ai-compendium无论你的训练跑在 AWS、GCP 还是 Azure 的云 VM 上它几乎必然运行在 x86 CPU 上即使 GPU 密集的训练数据加载、预处理、梯度聚合与 checkpoint 也全部在 CPU 上完成。本文基于 Maths, CS AI Compendium 第 16 章 03. x86 and AVX.md 展开系统讲解 x86 SIMD 的演进、AVX/AVX2 intrinsics 编程、AVX-512 掩码寄存器、Intel AMX 矩阵乘法硬件、内存对齐、性能陷阱与 profiling 工具。读完你将掌握用 256-bit/512-bit 向量指令优化 CPU 热点内核的完整技能并用可运行的 benchmark 验证加速效果。为什么 ML 工程师必须关心 x86 SIMD在讨论 intrinsics 之前先厘清一个背景你写的每一行 Python 最终都在执行 C/SIMD 代码。正如本仓库第 16 章 00. why C and how ML frameworks work.md 所述PyTorch、JAX、NumPy 都是Python 前端 C/CUDA 后端的双语言架构Python 负责描述计算图ATen、XLA、BLAS 等 C 内核库负责实际计算最终落到 CPU SIMD 单元或 GPU 核心上。一个 4096×4096 的jnp.matmul约执行 1370 亿次浮点运算纯 Python 嵌套循环需要约 30 分钟而经 AVX-512 SIMD 与多线程优化的 C 只需约 10 毫秒——差距约 180,000 倍。对 ML 训练而言x86 SIMD 的意义不仅在于矩阵乘法本身。GPU 训练链路中有大量无法卸载到 GPU 的 CPU 工作数据加载与预处理、梯度聚合、checkpoint 序列化。这些环节的性能瓶颈往往就是 CPU 的标量代码。用 SIMD 优化它们可以实打实地缩短端到端训练时间。x86 SIMD 的演进从 MMX 到 AMXx86 的 SIMD 能力沿着寄存器越来越宽的路线演进。下表完整罗列了六代主要扩展代际年份寄存器宽度寄存器数量关键特性MMX199764-bit8mm0-7仅整数与 FPU 共用寄存器SSE1999128-bit8xmm0-74 个 float专用寄存器SSE22001128-bit8/162 个 double整数运算AVX2011256-bit16ymm0-158 个 float三操作数指令AVX22013256-bit16256-bit 整数FMAgatherAVX-5122017512-bit32zmm0-3116 个 float掩码寄存器scatterAMX2023Tile 寄存器8 个 tile矩阵乘法BF16、INT8每一代都让向量化代码的吞吐翻倍。用 SSE intrinsics 写的代码能在 2001 年以来所有 x86 CPU 上运行AVX2 需要 2013 年后的 CPUAVX-512 主要出现在 Intel Xeon 与部分消费级芯片AMX 是最新的Sapphire Rapids 及之后。向后兼容性是 x86 SIMD 生态的重要特性SSE 的 xmm 寄存器是 AVX ymm 寄存器的低 128 位而 ymm 又是 AVX-512 zmm 的低 256 位。因此老代码无需修改即可在新 CPU 上运行——这也解释了为什么现代 CPU 需要引入 vzeroupper 一类的清理指令详见性能陷阱一节。从本仓库 01. hardware fundamentals.md 的寄存器对照表可以更宏观地理解128-bit 寄存器容纳 4 个 floatSSE/NEON256-bit 容纳 8 个AVX/AVX2512-bit 容纳 16 个AVX-512而 ARM 的 SVE 是可伸缩的 128-2048 bit。寄存器越宽意味着单条指令处理的并行度越高——这是 2005 年时钟频率撞上功耗墙之后CPU 获取性能的主要途径。AVX2 编程入门AVX2 基于 256-bit 的 YMM 寄存器一次处理 8 个 float 或 4 个 double。它是可移植高性能代码的甜蜜点几乎覆盖所有 2013 年后的现代 x86 CPUIntel 与 AMD 均支持这也是本仓库选择重点讲解它的原因。Intrinsics 命名约定所有 x86 intrinsics 遵循统一模式_mm[width]_[operation]_[type]。_mm MMX/SSE128-bit_mm256 AVX256-bit_mm512 AVX-512512-bit操作部分add、mul、fmadd、load、store、set等类型部分ps packed singlefloat32pd packed doublefloat64epi32 packed int32si256 256-bit 整数对应三种核心数据类型#include immintrin.h // 所有 x86 SIMD intrinsics 的头文件 // 数据类型 __m256 a; // 256-bit 寄存器容纳 8 个 float32 __m256d b; // 256-bit 寄存器容纳 4 个 float64 __m256i c; // 256-bit 寄存器容纳整数8×32、16×16 或 32×8加载与存储// 从内存加载 8 个 float __m256 v _mm256_loadu_ps(ptr); // 非对齐加载任意地址可用 __m256 v _mm256_load_ps(ptr); // 对齐加载ptr 必须 32 字节对齐更快 // 将 8 个 float 写回内存 _mm256_storeu_ps(out_ptr, v); // 非对齐存储 _mm256_store_ps(out_ptr, v); // 对齐存储 // 将单个值广播到全部 8 个通道 __m256 ones _mm256_set1_ps(1.0f); // [1, 1, 1, 1, 1, 1, 1, 1] // 逐元素设置很少需要 __m256 v _mm256_set_ps(7,6,5,4,3,2,1,0); // 注意参数顺序是反的 // 清零寄存器 __m256 z _mm256_setzero_ps();set1广播在 ML 内核中极其常用——例如 ReLU 需要把 0 广播到所有通道作为比较基准。set_ps的参数顺序是反向的第一个参数对应最高位通道这是 x86 intrinsics 最常见的初学坑之一。算术运算__m256 c _mm256_add_ps(a, b); // c[i] a[i] b[i] __m256 d _mm256_mul_ps(a, b); // d[i] a[i] * b[i] __m256 e _mm256_sub_ps(a, b); // e[i] a[i] - b[i] __m256 f _mm256_div_ps(a, b); // f[i] a[i] / b[i]比 mul 慢得多 // Fused Multiply-Addr a * b c单条指令单次舍入 __m256 r _mm256_fmadd_ps(a, b, c); // ML 中最重要的一条指令 // 最小值与最大值 __m256 mn _mm256_min_ps(a, b); // min(a[i], b[i]) —— 可用于 clipping __m256 mx _mm256_max_ps(a, b); // max(a[i], b[i]) —— 可用于 ReLUFMA融合乘加是 ML 内核的基石点积、矩阵乘法、卷积的内层循环都由它构成。它把乘法和加法合并为一条指令且只做一次舍入比先乘后加更精确。min/max则是实现激活函数的关键——_mm256_max_ps(x, zero)就是一条指令级的 ReLU完全无需分支。实战一AVX2 点积点积是矩阵乘法的内层循环。先给出完整可编译的 AVX2 实现#include immintrin.h float dot_avx2(const float* a, const float* b, int n) { __m256 sum _mm256_setzero_ps(); // 8 个累加器初始化为 0 int i 0; for (; i 8 n; i 8) { __m256 va _mm256_loadu_ps(a i); __m256 vb _mm256_loadu_ps(b i); sum _mm256_fmadd_ps(va, vb, sum); // sum va * vb } // 水平归约把 sum 中的 8 个元素加起来 // 第 1 步将高 128 位加到低 128 位 __m128 hi _mm256_extractf128_ps(sum, 1); __m128 lo _mm256_castps256_ps128(sum); __m128 sum128 _mm_add_ps(hi, lo); // 得到 4 个部分和 // 第 2 步在 128 位寄存器内做水平相加 sum128 _mm_hadd_ps(sum128, sum128); // [ab, cd, ab, cd] sum128 _mm_hadd_ps(sum128, sum128); // [abcd, ...] float result _mm_cvtss_f32(sum128); // 提取标量 // 标量收尾处理不足 8 个的剩余元素 for (; i n; i) { result a[i] * b[i]; } return result; }为什么水平归约这么难看SIMD 天生为垂直运算设计通道 0 对通道 0、通道 1 对通道 1跨通道的水平求和是在跟硬件作对。这正是点积代码末尾总有那段别扭归约代码的原因向量化主循环很干净归约则是固定模板。理解这一点你就不会在写 SIMD 时被归约代码吓到。与 NEON 的对比同章 02. ARM and NEON.md 中的 NEON 版本每迭代处理 4 个 float而 AVX2 每迭代处理 8 个。对长向量而言忽略内存带宽限制这是 NEON 的 2 倍提速。实战二AVX2 Softmax 的求最大值步骤Softmax 的完整流程是找最大值 → 减去它 → 指数化 → 求和 → 归一化。下面是其中找最大值的向量化实现float vector_max_avx2(const float* data, int n) { __m256 max_vec _mm256_set1_ps(-INFINITY); int i 0; for (; i 8 n; i 8) { __m256 v _mm256_loadu_ps(data i); max_vec _mm256_max_ps(max_vec, v); } // 把 8 个最大值归约到 1 个 __m128 hi _mm256_extractf128_ps(max_vec, 1); __m128 lo _mm256_castps256_ps128(max_vec); __m128 max128 _mm_max_ps(hi, lo); // 通过 shuffle 与 max 找出唯一最大值 max128 _mm_max_ps(max128, _mm_shuffle_ps(max128, max128, 0b01001110)); max128 _mm_max_ps(max128, _mm_shuffle_ps(max128, max128, 0b10110001)); float result _mm_cvtss_f32(max128); for (; i n; i) { result result data[i] ? result : data[i]; } return result; }_mm_shuffle_ps在寄存器内部重排元素二进制常量0b01001110决定每个元素的去向。这本质上是一种置换permutation——与本仓库第 2 章的置换矩阵直接对应SIMD 通道重排就是硬件层面的乘以置换矩阵。这是本教材反复强调的跨章节联系线性代数概念在硬件指令里有真实对应物。AVX-512掩码寄存器与频率节流AVX-512 将宽度再次翻倍512-bit 的 ZMM 寄存器一次处理 16 个 float。__m512 a _mm512_loadu_ps(ptr); // 加载 16 个 float __m512 c _mm512_fmadd_ps(a, b, c); // 一次 16 个 FMA float sum _mm512_reduce_add_ps(a); // 内置水平求和无需手动归约 // 掩码操作只作用于部分通道 __mmask16 mask _mm512_cmpgt_ps_mask(a, zero); // 哪些通道 0 __m512 relu _mm512_maskz_mov_ps(mask, a); // 负通道清零 ReLU掩码寄存器__mmask16是 AVX-512 最强大的特性每个 bit 控制一个通道是否参与运算。这取代了标量收尾循环——最后一轮迭代用掩码只激活有效通道任意向量长度都无需单独的标量循环。上面 4 行代码就是一个完整的向量化 ReLU比较 → 掩码 → 选择性移动。频率节流是必须了解的代价在许多 Intel CPU 上使用 AVX-512 指令会让 CPU 暂时降低时钟频率为控制功耗与发热。因此对短促的突发性负载AVX-512 并不总是快于 AVX2——频率损失可能抵消更宽向量带来的收益。对持续型工作负载如矩阵乘法AVX-512 胜出对混合型代码部分 SIMD、部分标量频繁的频率切换反而有害。选择指令集时先测一下你的负载属于哪一类。Intel AMX为矩阵乘法定制的硬件AMXAdvanced Matrix eXtensions引入了专门的矩阵乘法单元。与 SIMD 向量不同AMX 操作的是tile2D 数据块最多 16 行 × 64 字节/行。#include immintrin.h // AMX tile 乘法C A * BBF16 // A 是 16x32 BF16B 是 32x16 BF16C 是 16x16 FP32 _tile_loadd(0, a_ptr, stride_a); // 从 A 加载 tile 0 _tile_loadd(1, b_ptr, stride_b); // 从 B 加载 tile 1 _tile_dpbf16ps(2, 0, 1); // tile 2 tile 0 * tile 1BF16 矩阵乘FP32 累加 _tile_stored(2, c_ptr, stride_c); // 将 tile 2 存储到 CAMX 用一条指令完成 16×32 × 32×16 的矩阵乘法——这是数百次 FMA 同时执行专门为 Transformer 推理中占主导地位的小型矩阵乘法注意力得分计算、MLP 层设计。AMX 支持 BF16bfloat16与 INT8恰好匹配 ML 推理常用的低精度。与负责其他运算的 AVX-512 配合配备 AMX 的 CPUIntel Sapphire Rapids、Emerald Rapids可以把 Transformer 推理跑到接近入门级 GPU 的水平。这与本仓库第 17 章量化中的低精度推理思路一脉相承。内存对齐理论与实践对齐内存访问指数据地址是向量寄存器宽度的整数倍SSE 为 16 字节、AVX 为 32 字节、AVX-512 为 64 字节。对齐访问在部分 CPU 上更快且是_mm256_load_ps的硬性要求_mm256_loadu_ps则没有此要求。// 分配对齐内存 float* data (float*)aligned_alloc(32, n * sizeof(float)); // 为 AVX 做 32 字节对齐 // C 对齐分配 #include new float* data new (std::align_val_t(32)) float[n]; // 或使用编译器属性 alignas(32) float data[1024];实际情况在现代 CPUHaswell 及之后上只要数据不跨缓存行边界非对齐加载loadu几乎与对齐加载一样快——非对齐的惩罚已基本消失。但缓存行分裂数据横跨两个 64 字节缓存行仍会让该次加载慢约 2 倍。对齐分配可以彻底规避这个问题。这是理论上要对齐、实测可量化的典型例子用后文 task3 的基准测试亲自验证。性能陷阱清单AVX-SSE 转换惩罚在较旧的 Intel CPUSkylake 之前上在 AVX256-bit与 SSE128-bit指令之间切换会有约 70 个周期的惩罚。因此使用 AVX 的函数在返回前应调用_mm256_zeroupper()或vzeroupper指令清除 YMM 寄存器的高 128 位。Skylake 及以后的 CPU 已无此惩罚。寄存器压力AVX2 只有 16 个 YMM 寄存器。如果内核使用过多变量编译器会把寄存器溢出spill到栈上内存性能被摧毁。保持内层循环简单、活跃变量少。数据依赖多累加器隐藏延迟sum _mm256_fmadd_ps(a, b, sum)对sum存在依赖每次迭代必须等上一次 FMA 完成约 4-5 周期延迟。修复方法是使用多个独立累加器最后再合并// 单一累加器受 FMA 延迟限制4-5 周期 __m256 sum _mm256_setzero_ps(); for (...) { sum _mm256_fmadd_ps(a, b, sum); // 每次依赖上一次 } // 四个累加器4 倍吞吐隐藏延迟 __m256 sum0 _mm256_setzero_ps(); __m256 sum1 _mm256_setzero_ps(); __m256 sum2 _mm256_setzero_ps(); __m256 sum3 _mm256_setzero_ps(); for (...) { sum0 _mm256_fmadd_ps(a0, b0, sum0); // 相互独立 sum1 _mm256_fmadd_ps(a1, b1, sum1); // 相互独立 sum2 _mm256_fmadd_ps(a2, b2, sum2); // 相互独立 sum3 _mm256_fmadd_ps(a3, b3, sum3); // 相互独立 } sum0 _mm256_add_ps(sum0, sum1); sum2 _mm256_add_ps(sum2, sum3); sum0 _mm256_add_ps(sum0, sum2);这就是**循环展开loop unrolling**隐藏延迟CPU 可以背靠背发射这些 FMA因为它们写入不同寄存器。这是数值代码中影响最大的微优化之一——多累加器技巧同时适用于 CPU SIMD 和 GPU kernelTriton/CUDA 中同理。性能分析用硬件计数器指导优化perf 与 VTuneLinux 下最直接的硬件级测量工具是 perf需内核支持# Linux perf需内核支持 perf stat ./my_program # 基本计数器cycles、instructions、IPC perf stat -e cache-misses,cache-references ./my_program # 缓存行为 perf record -g ./my_program perf report # 调用图性能分析 # Intel VTune更详细的 x86 剖析 vtune -collect hotspots -- ./my_program vtune -collect memory-access -- ./my_program # 内存带宽分析看什么指标IPC每周期指令数衡量 CPU 利用效率。IPC 2 为良好IPC 1 通常意味着内存停顿或分支预测失败。缓存未命中率L1/L2 高未命中率说明数据局部性差需要重构数据访问模式可参考第 16 章 01 文件的硬件基础中关于内存层次的内容。分支误预测率超过 5% 说明存在不可预测分支应改为无分支代码SIMD 比较 混合/掩码。实测 FLOPS vs roofline将实测 FLOPS 与 01. hardware fundamentals.md 的 roofline 模型对比。如果远低于 roofline说明还有优化空间——注意区分你的负载是计算密集型峰值 FLOPS 主导还是内存密集型带宽 × 算术强度主导。像 ReLU 这样的逐元素运算算术强度只有 1是典型的内存密集型此时换更宽的 SIMD 帮助有限瓶颈在内存带宽。编码任务可编译可运行的验证实验以下三个任务与主文档一致在 x86Intel/AMD机器上用 g 或 clang 编译运行即可验证全部概念。任务 1标量 vs AVX2 点积基准// task1_avx_dot.cpp // Compile: g -O3 -mavx2 -mfma -o task1 task1_avx_dot.cpp #include iostream #include chrono #include vector #include immintrin.h float dot_scalar(const float* a, const float* b, int n) { float sum 0.0f; for (int i 0; i n; i) sum a[i] * b[i]; return sum; } float dot_avx2(const float* a, const float* b, int n) { __m256 sum _mm256_setzero_ps(); int i 0; for (; i 8 n; i 8) { __m256 va _mm256_loadu_ps(a i); __m256 vb _mm256_loadu_ps(b i); sum _mm256_fmadd_ps(va, vb, sum); } // Reduce: add upper 128 to lower 128, then horizontal add __m128 hi _mm256_extractf128_ps(sum, 1); __m128 lo _mm256_castps256_ps128(sum); __m128 r _mm_add_ps(hi, lo); r _mm_hadd_ps(r, r); r _mm_hadd_ps(r, r); float result _mm_cvtss_f32(r); for (; i n; i) result a[i] * b[i]; return result; } int main() { const int N 10000000; std::vectorfloat a(N, 1.0f), b(N, 2.0f); volatile float s1 dot_scalar(a.data(), b.data(), N); volatile float s2 dot_avx2(a.data(), b.data(), N); auto bench { auto start std::chrono::high_resolution_clock::now(); volatile float s; for (int t 0; t 100; t) s fn(a.data(), b.data(), N); auto end std::chrono::high_resolution_clock::now(); double ms std::chrono::durationdouble, std::milli(end - start).count() / 100; std::cout name : ms ms (result: s )\n; return ms; }; double t1 bench(dot_scalar, Scalar); double t2 bench(dot_avx2, AVX2 ); std::cout Speedup: t1 / t2 x\n; return 0; }编译参数中-O3开启编译器最高优化-mavx2启用 AVX2 指令-mfma启用 FMA。注意volatile防止编译器把结果优化掉。长向量场景下 AVX2 版本相对标量应有接近 8 倍的理想上限加速实测通常会受限于内存带宽。任务 2AVX2 ReLU 与循环展开// task2_avx_relu.cpp // Compile: g -O3 -mavx2 -o task2 task2_avx_relu.cpp #include iostream #include chrono #include vector #include immintrin.h void relu_scalar(const float* in, float* out, int n) { for (int i 0; i n; i) { out[i] in[i] 0.0f ? in[i] : 0.0f; } } void relu_avx2(const float* in, float* out, int n) { __m256 zero _mm256_setzero_ps(); int i 0; for (; i 8 n; i 8) { __m256 x _mm256_loadu_ps(in i); _mm256_storeu_ps(out i, _mm256_max_ps(x, zero)); } for (; i n; i) out[i] in[i] 0.0f ? in[i] : 0.0f; } // Unrolled: process 32 floats per iteration (4 x 8) void relu_avx2_unrolled(const float* in, float* out, int n) { __m256 zero _mm256_setzero_ps(); int i 0; for (; i 32 n; i 32) { __m256 x0 _mm256_loadu_ps(in i); __m256 x1 _mm256_loadu_ps(in i 8); __m256 x2 _mm256_loadu_ps(in i 16); __m256 x3 _mm256_loadu_ps(in i 24); _mm256_storeu_ps(out i, _mm256_max_ps(x0, zero)); _mm256_storeu_ps(out i 8, _mm256_max_ps(x1, zero)); _mm256_storeu_ps(out i 16, _mm256_max_ps(x2, zero)); _mm256_storeu_ps(out i 24, _mm256_max_ps(x3, zero)); } for (; i 8 n; i 8) { _mm256_storeu_ps(out i, _mm256_max_ps(_mm256_loadu_ps(in i), zero)); } for (; i n; i) out[i] in[i] 0.0f ? in[i] : 0.0f; } int main() { const int N 16000000; std::vectorfloat in(N), out(N); for (int i 0; i N; i) in[i] (float)(i % 200) - 100.0f; auto bench { fn(in.data(), out.data(), N); // warm up auto start std::chrono::high_resolution_clock::now(); for (int t 0; t 100; t) fn(in.data(), out.data(), N); auto end std::chrono::high_resolution_clock::now(); double ms std::chrono::durationdouble, std::milli(end - start).count() / 100; double bw 2.0 * N * sizeof(float) / ms / 1e6; // read write std::cout name : ms ms ( bw GB/s)\n; }; bench(relu_scalar, Scalar ); bench(relu_avx2, AVX2 ); bench(relu_avx2_unrolled, AVX2 unrolled ); return 0; }这个实验会揭示一个反直觉的事实ReLU 是内存密集型运算bw计算了读取 写入的总字节数。当 AVX2 版本把带宽推到接近硬件极限例如 DDR5 的约 50 GB/s时展开版本提升有限——因为瓶颈已经从计算变成内存。这正是 roofline 模型的实战印证。任务 3对齐 vs 非对齐加载// task3_alignment.cpp // Compile: g -O3 -mavx2 -o task3 task3_alignment.cpp #include iostream #include chrono #include cstdlib #include immintrin.h int main() { const int N 16000000; // Aligned allocation (32-byte for AVX2) float* aligned (float*)aligned_alloc(32, N * sizeof(float)); // Unaligned: offset by 4 bytes (1 float) from aligned boundary float* raw (float*)malloc((N 1) * sizeof(float)); float* unaligned raw 1; // guaranteed misaligned for (int i 0; i N; i) { aligned[i] 1.0f; unaligned[i] 1.0f; } auto bench { __m256 sum _mm256_setzero_ps(); // Warm up for (int i 0; i 8 N; i 8) { __m256 v use_aligned ? _mm256_load_ps(ptr i) : _mm256_loadu_ps(ptr i); sum _mm256_add_ps(sum, v); } auto start std::chrono::high_resolution_clock::now(); for (int t 0; t 100; t) { sum _mm256_setzero_ps(); for (int i 0; i 8 N; i 8) { __m256 v use_aligned ? _mm256_load_ps(ptr i) : _mm256_loadu_ps(ptr i); sum _mm256_add_ps(sum, v); } } auto end std::chrono::high_resolution_clock::now(); double ms std::chrono::durationdouble, std::milli(end - start).count() / 100; double bw (double)N * sizeof(float) / ms / 1e6; std::cout name : ms ms ( bw GB/s)\n; }; bench(aligned, true, Aligned load ); bench(unaligned, false, Unaligned load); free(aligned); free(raw); return 0; }注意这里非对齐指针 对齐指针 1 个 float4 字节偏移保证跨越缓存行边界。现代 CPU 上你可能看到两者几乎无差别而在更老的架构或缓存行分裂频繁的场景下对齐优势才会显现。这正是实测优于臆测的绝佳演示。与本章其他内容的衔接本文件是第 16 章SIMD 与 GPU 编程的 x86 分支。完整的知识链路是先用 00. why C and how ML frameworks work.md 理解 C 内核与 Python 前端的协作再通过 01. hardware fundamentals.md 掌握 roofline 模型与硬件边界然后对照 02. ARM and NEON.md 对比 ARM 侧的 NEON 实现。往后04. GPU architecture and CUDA.md 与 05. triton, TPUs and pallax.md 会把同样的思维并行化、隐藏延迟、roofline 分析迁移到 GPU。理解底层的 x86 SIMD会显著提升你使用高层框架时对性能的判断力——正如本仓库 MCP 服务器见 mcp/src/index.ts所服务的定位把这份教材当作可检索的知识库让每一个性能概念都能被查证、被复现。【免费下载链接】maths-cs-ai-compendiumBecome a cracked AI/ML researcher/engineer with this unconventional textbook covering maths, computing, and ML with intuition.项目地址: https://gitcode.com/GitHub_Trending/mat/maths-cs-ai-compendium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价