#deep-learning #tensor #neural-network #compile-time #automatic-differentiation #auto-diff #gpu-accelerated

无 std dfdx

在 Rust 中提供符合人体工程学的自动微分,具有类似 PyTorch 的 API

17 个版本 (11 个破坏性更新)

0.13.0 2023 年 7 月 27 日
0.11.2 2023 年 4 月 27 日
0.11.0 2023 年 3 月 16 日
0.10.0 2022 年 10 月 30 日
0.8.0 2022 年 7 月 13 日

#64机器学习 分类中

Download history 305/week @ 2024-04-22 367/week @ 2024-04-29 228/week @ 2024-05-06 118/week @ 2024-05-13 79/week @ 2024-05-20 78/week @ 2024-05-27 62/week @ 2024-06-03 52/week @ 2024-06-10 64/week @ 2024-06-17 64/week @ 2024-06-24 16/week @ 2024-07-01 176/week @ 2024-07-08 55/week @ 2024-07-15 51/week @ 2024-07-22 49/week @ 2024-07-29 60/week @ 2024-08-05

每月 223 次下载
4 crates 中使用

MIT/Apache 许可

1.5MB
32K SLoC

dfdx:Rust 中的形状检查深度学习

crates.io docs.rs

关注人体工程学与安全性的 Rust 深度学习。

目前仍处于预 alpha 状态。接下来的几个版本计划为破坏性更新。

主要特性

  1. 🔥 支持高达 6d 形状的 GPU 加速张量库!
  2. 具有编译时和运行时尺寸维度的形状。(例如 Tensor<(usize, Const<10>)>Tensor<Rank2<5, 10>>)
  3. 一个庞大的张量运算库(包括 matmulconv2d 等)。
    1. 所有张量运算在编译时进行形状和类型检查!!
  4. 符合人体工程学的神经网络构建块(如 LinearConv2DTransformer)。
  5. 标准深度学习优化器,如 SgdAdamAdamWRMSprop 等。

dfdxcrates.io!通过在您的 Cargo.toml 中添加以下内容来使用

dfdx = "0.13.0"

docs.rs/dfdx 中查看文档。

[1] https://en.wikipedia.org/wiki/Automatic_differentiation#Reverse_accumulation

设计目标

  1. 从头到尾的人体工程学(包括前端界面和内部实现)。
  2. 尽可能在编译时进行检查(即,如果某些内容不正确则不编译)。
  3. 最大化性能。
  4. 最小化不安全代码[1]
  5. 最小化在内部代码中使用的Rc<RefCell>[2]

[1] 目前唯一的不安全调用是矩阵乘法。

[2] 使用 Arc 的只有用于存储数据的张量。使用 Arc 而不是 Box 来减少克隆张量时的分配。

CUDA加速

启用cuda特性以开始使用Cuda设备!需要安装nvidia的cuda工具包。有关更多信息,请参阅功能标志文档

API预览

有关更多详细信息,请查看examples/

  1. 👌 简单神经网络API,完全在编译时进行形状检查。
type Mlp = (
    (Linear<10, 32>, ReLU),
    (Linear<32, 32>, ReLU),
    (Linear<32, 2>, Tanh),
);

fn main() {
    let dev: Cuda = Default::default(); // or `Cpu`
    let mlp = dev.build_module::<Mlp, f32>();
    let x: Tensor<Rank1<10>, f32, Cpu> = dev.zeros();
    let y: Tensor<Rank1<2>, f32, Cpu> = mlp.forward(x);
    mlp.save("checkpoint.npz")?;
}
  1. 📈 人体工程学优化器API
type Model = ...
let mut model = dev.build_module::<Model, f32>();
let mut grads = model.alloc_grads();
let mut sgd = Sgd::new(&model, SgdConfig {
    lr: 1e-2,
    momentum: Some(Momentum::Nesterov(0.9))
});

let loss = ...
grads = loss.backward();

sgd.update(&mut model, &grads);
  1. 💡 常量张量可以转换为Rust数组,反之亦然
let t0: Tensor<Rank0, f32, _> = dev.tensor(0.0);
assert_eq!(t0.array(), &0.0);

let t1 /*: Tensor<Rank1<3>, f32, _>*/ = dev.tensor([1.0, 2.0, 3.0]);
assert_eq!(t1.array(), [1.0, 2.0, 3.0]);

let t2: Tensor<Rank2<2, 3>, f32, _> = dev.sample_normal();
assert_ne!(t2.array(), [[0.0; 3]; 2]);

有趣/值得注意的实现细节

模块

pub trait Module<Input> {
    type Output;
    fn forward(&self, input: Input) -> Self::Output;
}

从这个灵活的特质中,我们得到

  1. 单个和批量输入(只需有多个实现!)
  2. 多个输入/输出(多头部模块或RNN)
  3. 当存在或不存在tape时,行为不同(不是其他库中存在的.train()/.eval()行为!)

元组表示前馈(即顺序)模块

由于我们可以为元组实现特质,这在其他语言中是不可能的,因此它们为顺序执行模块提供了一个非常好的前端。

// no idea why you would do this, but you could!
type Model = (ReLU, Sigmoid, Tanh);
let model = dev.build_module::<Model, f32>();
type Model = (Linear<10, 5>, Tanh)
let model = dev.build_module::<Model, f32>();

如何实现2元组的Module

impl<Input, A, B> Module<Input> for (A, B)
where
    Input: Tensor,
    A: Module<Input>,        // A is a module that takes Input
    B: Module<A::Output>,    // B is a module that takes A's Output
{
    type Output = B::Output; // the output of this is B's Output
    fn forward(&self, x: Input) -> Self::Output {
        let x = self.0.forward(x);
        let x = self.1.forward(x);
        x
    }
}

已为元组实现模块,最多可达6个元素,但你可以随意嵌套它们

没有使用Rc<RefCells<T>> - 梯度带没有被放在一个单元后面!

其他实现可能直接在张量上存储梯度带的引用,这需要修改张量或到处使用Rc/Refcells。

我们已经找到了一种优雅的方法来避免这种情况,将引用和动态借用检查减少到0!

由于所有操作都产生正好1个子项,我们总是可以将梯度带移动到最后一个操作的子项。此外,由于所有模型参数(所有张量)永远不会拥有梯度带,因为它们永远不会是任何操作的输出。这意味着我们知道哪个张量拥有梯度带,并且拥有它的张量将始终是中间结果,不需要在梯度计算过程中维护。

所有这些都使用户对记录在梯度带上的张量有了前所未有的控制/精度!

一个高级用例需要张量在计算图中多次重复使用。这可以通过克隆张量并手动移动梯度带来处理。

类型检查反向传播

tl;dr:如果你忘记包含对trace()traced()的调用,程序将无法编译!

-let pred = module.forward(x);
+let pred = module.forward(x.traced(grads));
let loss = (y - pred).square().mean();
let gradients = loss.backward();

由于我们知道哪些张量拥有梯度带,我们可以要求传递给.backward()的张量拥有梯度带!更进一步,我们可以要求它移动到.backward(),以便它可以析构带并构建梯度!

所有这些都可以在编译时进行检查 🎉

📄 与pytorch验证

所有函数和操作都针对与pytorch中类似代码所示的行为进行了测试。

许可证

双授权,兼容Rust项目。

根据您的选择,许可协议为Apache License,版本2.0 http://www.apache.org/licenses/LICENSE2.0 或MIT许可 http://opensource.org/licenses/MIT。除非符合这些条款,否则不得复制、修改或分发此文件。

依赖项

~2–8MB
~199K SLoC