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 在 机器学习 分类中
每月 223 次下载
在 4 crates 中使用
1.5MB
32K SLoC
dfdx:Rust 中的形状检查深度学习
关注人体工程学与安全性的 Rust 深度学习。
目前仍处于预 alpha 状态。接下来的几个版本计划为破坏性更新。
主要特性
- 🔥 支持高达 6d 形状的 GPU 加速张量库!
- 具有编译时和运行时尺寸维度的形状。(例如
Tensor<(usize, Const<10>)>
和Tensor<Rank2<5, 10>>
) - 一个庞大的张量运算库(包括
matmul
、conv2d 等)。
- 所有张量运算在编译时进行形状和类型检查!!
- 符合人体工程学的神经网络构建块(如
Linear
、Conv2D 和
Transformer
)。 - 标准深度学习优化器,如
Sgd
、Adam
、AdamW
、RMSprop 等。
dfdx
在 crates.io!通过在您的 Cargo.toml
中添加以下内容来使用
dfdx = "0.13.0"
在 docs.rs/dfdx 中查看文档。
[1] https://en.wikipedia.org/wiki/Automatic_differentiation#Reverse_accumulation
设计目标
- 从头到尾的人体工程学(包括前端界面和内部实现)。
- 尽可能在编译时进行检查(即,如果某些内容不正确则不编译)。
- 最大化性能。
- 最小化不安全代码[1]
- 最小化在内部代码中使用的Rc<RefCell>[2]
[1] 目前唯一的不安全调用是矩阵乘法。
[2] 使用 Arc
的只有用于存储数据的张量。使用 Arc
而不是 Box
来减少克隆张量时的分配。
CUDA加速
启用cuda
特性以开始使用Cuda
设备!需要安装nvidia的cuda工具包。有关更多信息,请参阅功能标志文档。
API预览
有关更多详细信息,请查看examples/。
- 👌 简单神经网络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")?;
}
- 📈 人体工程学优化器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);
- 💡 常量张量可以转换为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;
}
从这个灵活的特质中,我们得到
- 单个和批量输入(只需有多个实现!)
- 多个输入/输出(多头部模块或RNN)
- 当存在或不存在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