Skip to content

Commit 6f41247

Browse files
committed
Auto merge of #82 - autumnai:feat/new_features, r=hobofan
change meaning of framework features Changes the default feature flags to only build in support for the Native backend, since that is what most people will have available on their development machines. It also changes the meaning of the framework feature flags (`native`,`cuda`,`opencl`), so that only the capabilities that are shared between the frameworks will be included in the compiled version. See #81 for a possible long term solution. Example: - feature flags are `native cuda` -> `Convolution` Layer **is not available** since the native backend does not provide the required traits, not even for the CUDA backend. - feature flags are `cuda` -> `Convolution` Layer **is available** since the CUDA backend provides the required traits and there is no native backend it has to be compatible with. - feature flags are `native` -> `Convolution` Layer **is not available** since the native backend does not provide the required traits and there are no other frameworks present. WIP: I still nedd to finish a top-level FEATURE-FLAGS guide that explains this a bit more in depth.
2 parents c36149e + 642a4d4 commit 6f41247

File tree

11 files changed

+293
-32
lines changed

11 files changed

+293
-32
lines changed

Cargo.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ keywords = ["deep-learning", "neural-networks", "machine-learning", "framework"]
1414
license = "MIT OR Apache-2.0"
1515

1616
[dependencies]
17-
collenchyma = { version = "0.0.8", default-features = false }
18-
collenchyma-blas = { version = "0.2.0", default-features = false }
17+
collenchyma = { version = "0.0.8", default-features = false, features = ["native"] } # native feature to read/write data into tensors
18+
collenchyma-blas = { version = "0.2.0", default-features = false, features = ["native"] } # only compiles with native feature
1919
collenchyma-nn = { version = "0.3.2", default-features = false }
2020

2121
log = "0.3.2"
@@ -30,8 +30,8 @@ timeit = "0.1.2"
3030
env_logger = "0.3"
3131

3232
[features]
33-
default = ["native", "cuda", "opencl"]
34-
native = ["collenchyma/native", "collenchyma-blas/native", "collenchyma-nn/native"]
33+
default = ["native"]
34+
native = ["collenchyma-blas/native", "collenchyma-nn/native"]
3535
cuda = ["collenchyma/cuda", "collenchyma-blas/cuda", "collenchyma-nn/cuda"]
3636
opencl = ["collenchyma/opencl", "collenchyma-blas/opencl", "collenchyma-nn/opencl"]
3737

FEATURE-FLAGS.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Feature flags in Leaf
2+
3+
## The problem(s)
4+
5+
Supporting different backends is an important concept in Leaf.
6+
7+
Optimally we would like to always have to choice of running Leaf on all backends.
8+
However in reality there are some tradeoffs that have to be made.
9+
10+
One problem is that certain backends require the presence of special hardware to
11+
run (CUDA needs NVIDIA GPUs), or the libraries to address them are not present on
12+
the developers machine which is necessary for compilation.
13+
14+
Another challenge is that not all backends have support for the same operations,
15+
which constrains neural networks with special requirements to the backends that
16+
provide those operations. Due to some limitations in the current version of Rust
17+
(1.7) allowing differently featured backends can not be that easily supported.
18+
See [Issue #81](https://github.com/autumnai/leaf/issues/81).
19+
20+
## The solution
21+
22+
Feature flags are a well known concept to add opt-in functionality that is
23+
not necessary for every use-case of a library and are a good solution to the first
24+
problem.
25+
Luckily, Cargo, Rust's package manager has built-in support for feature flags.
26+
27+
A simple dependency with additional features enabled in a `Cargo.toml` looks like this:
28+
```toml
29+
[dependencies]
30+
leaf = { version = "0.2.0", features = ["cuda"] }
31+
```
32+
33+
Feature flags are usually used in an additive way, but **some configurations
34+
of features for Leaf might actually take away some functionality**.
35+
We do this because we want the models to be portable across different backends,
36+
which is not possible if e.g. the CUDA backend supports Convolution layers while
37+
the Native backend doesn't. To make it possible we deactivate those features that
38+
are only available on a single backend, effectively "dumbing down" the backends.
39+
40+
Example:
41+
- feature flags are `cuda` -> `Convolution` Layer **is available** since the CUDA backend provides the required traits and there is no native backend it has to be compatible with.
42+
- feature flags are `native` -> `Convolution` Layer **is not available** since the native backend does not provide the required traits and there are no other frameworks present.
43+
- feature flags are `native cuda` -> `Convolution` Layer **is not available** since the native backend does not provide the required traits, and the CUDA backend has been dumbed down.
44+
45+
## Using the feature flags
46+
47+
One thing we have ignored until now are default feature flags. Cargo allows to
48+
define a set of features that should be included in a package by default .
49+
One of the default feature flags of Leaf is the `native` flag. When looking at
50+
the above example you might notice that the only way we can unleash the full
51+
power of the CUDA backend is by deactivating the default `native` flag.
52+
Cargo allows us to do that either via the `--no-default-features` on the CLI or
53+
by specifying `default-feature = false` for a dependency in `Cargo.toml`.
54+
55+
#### In your project
56+
57+
The simple `Cargo.toml` example above works in simple cases but if you want
58+
to provide the same flexibility of backends in your project, you can reexport
59+
the feature flags.
60+
61+
A typical example (including collenchyma) would look like this:
62+
```toml
63+
[dependencies]
64+
leaf = { version = "0.2.0", default-features = false }
65+
# the native collenchyma feature is neccesary to read/write tensors
66+
collenchyma = { version = "0.0.8", default-features = false, features = ["native"] }
67+
68+
[features]
69+
default = ["native"]
70+
native = ["leaf/native"]
71+
opencl = ["leaf/opencl", "collenchyma/opencl"]
72+
cuda = ["leaf/cuda", "collenchyma/cuda"]
73+
74+
```
75+
76+
Building your project would then look like this:
77+
```sh
78+
# having both native and CUDA backends
79+
# `native` is provided by default, and `cuda` explicitly specified by `--features cuda`
80+
cargo build --features cuda
81+
# unleashing CUDA
82+
# `native` default not included because of `--no-default-features`, and `cuda` explicitly specified by `--features cuda`
83+
cargo build --no-default-features --features cuda
84+
```

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ cuda = ["leaf/cuda"]
8686
opencl = ["leaf/opencl"]
8787
```
8888

89+
> More information on the use of feature flags in Leaf can be found in [FEATURE-FLAGS.md](./FEATURE-FLAGS.md)
90+
8991

9092
## Examples
9193

@@ -100,7 +102,7 @@ the install guide, clone this repoistory and then run
100102

101103
```bash
102104
# The examples currently require CUDA support.
103-
cargo run --release --example benchmarks
105+
cargo run --release --no-default-features --features cuda --example benchmarks alexnet
104106
```
105107

106108
[leaf-examples]: https://github.com/autumnai/leaf-examples

examples/benchmarks.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,12 @@ fn get_time_scale<'a>(sec: f64) -> (f64, &'a str) {
112112
}
113113
}
114114

115-
#[cfg(not(feature = "cuda"))]
115+
#[cfg(feature="native")]
116116
fn bench_alexnet() {
117117
println!("Examples run only with CUDA support at the moment, because of missing native convolution implementation for the Collenchyma NN Plugin.");
118-
println!("Try compiling with the \"cuda\" feature flag.");
118+
println!("Try running with `cargo run --release --no-default-features --features cuda --example benchmarks alexnet`.");
119119
}
120-
#[cfg(feature = "cuda")]
120+
#[cfg(all(feature="cuda", not(feature="native")))]
121121
fn bench_alexnet() {
122122
let mut cfg = SequentialConfig::default();
123123
cfg.add_input("data", &vec![128, 3, 224, 224]);
@@ -194,12 +194,12 @@ fn bench_alexnet() {
194194
}
195195
}
196196

197-
#[cfg(not(feature = "cuda"))]
197+
#[cfg(feature="native")]
198198
fn bench_overfeat() {
199199
println!("Examples run only with CUDA support at the moment, because of missing native convolution implementation for the Collenchyma NN Plugin.");
200-
println!("Try compiling with the \"cuda\" feature flag.");
200+
println!("Try running with `cargo run --release --no-default-features --features cuda --example benchmarks overfeat`.");
201201
}
202-
#[cfg(feature = "cuda")]
202+
#[cfg(all(feature="cuda", not(feature="native")))]
203203
fn bench_overfeat() {
204204
let mut cfg = SequentialConfig::default();
205205
cfg.add_input("data", &vec![128, 3, 231, 231]);
@@ -276,12 +276,12 @@ fn bench_overfeat() {
276276
}
277277
}
278278

279-
#[cfg(not(feature = "cuda"))]
279+
#[cfg(feature="native")]
280280
fn bench_vgg_a() {
281281
println!("Examples run only with CUDA support at the moment, because of missing native convolution implementation for the Collenchyma NN Plugin.");
282-
println!("Try compiling with the \"cuda\" feature flag.");
282+
println!("Try running with `cargo run --release --no-default-features --features cuda --example benchmarks vgg`.");
283283
}
284-
#[cfg(feature = "cuda")]
284+
#[cfg(all(feature="cuda", not(feature="native")))]
285285
fn bench_vgg_a() {
286286
let mut cfg = SequentialConfig::default();
287287
cfg.add_input("data", &vec![64, 3, 224, 224]);

src/layer.rs

+12
Original file line numberDiff line numberDiff line change
@@ -687,9 +687,11 @@ impl<B: IBackend + LayerOps<f32> + 'static> Layer<B> {
687687
/// [3]: ../layers/index.html
688688
fn worker_from_config(backend: Rc<B>, config: &LayerConfig) -> Box<ILayer<B>> {
689689
match config.layer_type.clone() {
690+
#[cfg(all(feature="cuda", not(feature="native")))]
690691
LayerType::Convolution(layer_config) => Box::new(Convolution::from_config(&layer_config)),
691692
LayerType::Linear(layer_config) => Box::new(Linear::from_config(&layer_config)),
692693
LayerType::LogSoftmax => Box::new(LogSoftmax::default()),
694+
#[cfg(all(feature="cuda", not(feature="native")))]
693695
LayerType::Pooling(layer_config) => Box::new(Pooling::from_config(&layer_config)),
694696
LayerType::Sequential(layer_config) => Box::new(Sequential::from_config(backend, &layer_config)),
695697
LayerType::Softmax => Box::new(Softmax::default()),
@@ -1103,12 +1105,14 @@ pub struct LayerConfig {
11031105
pub enum LayerType {
11041106
// Common layers
11051107
/// Convolution Layer
1108+
#[cfg(all(feature="cuda", not(feature="native")))]
11061109
Convolution(ConvolutionConfig),
11071110
/// Linear Layer
11081111
Linear(LinearConfig),
11091112
/// LogSoftmax Layer
11101113
LogSoftmax,
11111114
/// Pooling Layer
1115+
#[cfg(all(feature="cuda", not(feature="native")))]
11121116
Pooling(PoolingConfig),
11131117
/// Sequential Layer
11141118
Sequential(SequentialConfig),
@@ -1131,14 +1135,22 @@ impl LayerType {
11311135
/// Returns wether the LayerType supports in-place operations.
11321136
pub fn supports_in_place(&self) -> bool {
11331137
match *self {
1138+
#[cfg(all(feature="cuda", not(feature="native")))]
11341139
LayerType::Convolution(_) => false,
11351140
LayerType::Linear(_) => false,
11361141
LayerType::LogSoftmax => false,
1142+
#[cfg(all(feature="cuda", not(feature="native")))]
11371143
LayerType::Pooling(_) => false,
11381144
LayerType::Sequential(_) => false,
11391145
LayerType::Softmax => false,
1146+
#[cfg(all(feature="cuda", not(feature="native")))]
11401147
LayerType::ReLU => true,
1148+
#[cfg(feature="native")]
1149+
LayerType::ReLU => false,
1150+
#[cfg(all(feature="cuda", not(feature="native")))]
11411151
LayerType::Sigmoid => true,
1152+
#[cfg(feature="native")]
1153+
LayerType::Sigmoid => false,
11421154
LayerType::NegativeLogLikelihood(_) => false,
11431155
LayerType::Reshape(_) => true,
11441156
}

src/layers/activation/relu.rs

+70-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
//! needed in a Sigmoid layer.
88
99
use co::{IBackend,SharedTensor};
10-
use conn::{Relu, ReluPointwise};
10+
use conn::Relu;
11+
#[cfg(all(feature="cuda", not(feature="native")))]
12+
use conn::ReluPointwise;
1113
use layer::*;
1214
use util::ArcLock;
1315

@@ -16,6 +18,11 @@ use util::ArcLock;
1618
/// ReLU Activation Layer
1719
pub struct ReLU;
1820

21+
//
22+
// ReLU + ReLUPointwise
23+
// Only on CUDA
24+
//
25+
#[cfg(all(feature="cuda", not(feature="native")))]
1926
impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ILayer<B> for ReLU {
2027
impl_ilayer_activation!();
2128

@@ -41,6 +48,7 @@ impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ILayer<B> for ReLU {
4148
}
4249
}
4350

51+
#[cfg(all(feature="cuda", not(feature="native")))]
4452
impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ComputeOutput<f32, B> for ReLU {
4553
fn compute_output(&self,
4654
backend: &B,
@@ -54,6 +62,7 @@ impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ComputeOutput<f32, B> for ReL
5462
}
5563
}
5664

65+
#[cfg(all(feature="cuda", not(feature="native")))]
5766
impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ComputeInputGradient<f32, B> for ReLU {
5867
fn compute_input_gradient(&self,
5968
backend: &B,
@@ -69,4 +78,64 @@ impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ComputeInputGradient<f32, B>
6978
}
7079
}
7180

81+
#[cfg(all(feature="cuda", not(feature="native")))]
7282
impl<B: IBackend + Relu<f32> + ReluPointwise<f32>> ComputeParametersGradient<f32, B> for ReLU {}
83+
84+
//
85+
// ReLU without ReLUPointwise
86+
// Only on CUDA
87+
//
88+
#[cfg(feature="native")]
89+
impl<B: IBackend + Relu<f32>> ILayer<B> for ReLU {
90+
impl_ilayer_activation!();
91+
92+
fn reshape(&mut self,
93+
backend: ::std::rc::Rc<B>,
94+
input_data: &mut Vec<ArcLock<SharedTensor<f32>>>,
95+
input_gradient: &mut Vec<ArcLock<SharedTensor<f32>>>,
96+
weights_data: &mut Vec<ArcLock<SharedTensor<f32>>>,
97+
weights_gradient: &mut Vec<ArcLock<SharedTensor<f32>>>,
98+
output_data: &mut Vec<ArcLock<SharedTensor<f32>>>,
99+
output_gradient: &mut Vec<ArcLock<SharedTensor<f32>>>) {
100+
if let Some(inp) = input_data.get(0) {
101+
let read_inp = inp.read().unwrap();
102+
let input_desc = read_inp.desc();
103+
input_gradient[0].write().unwrap().resize(input_desc).unwrap();
104+
output_data[0].write().unwrap().resize(input_desc).unwrap();
105+
output_gradient[0].write().unwrap().resize(input_desc).unwrap();
106+
}
107+
}
108+
}
109+
110+
#[cfg(feature="native")]
111+
impl<B: IBackend + Relu<f32>> ComputeOutput<f32, B> for ReLU {
112+
fn compute_output(&self,
113+
backend: &B,
114+
_weights: &[&SharedTensor<f32>],
115+
input_data: &[&SharedTensor<f32>],
116+
output_data: &mut [&mut SharedTensor<f32>]) {
117+
match input_data.get(0) {
118+
Some(input) => backend.relu_plain(input, output_data[0]).unwrap(),
119+
None => panic!("No input provided for ReLU layer."),
120+
}
121+
}
122+
}
123+
124+
#[cfg(feature="native")]
125+
impl<B: IBackend + Relu<f32>> ComputeInputGradient<f32, B> for ReLU {
126+
fn compute_input_gradient(&self,
127+
backend: &B,
128+
weights_data: &[&SharedTensor<f32>],
129+
output_data: &[&SharedTensor<f32>],
130+
output_gradients: &[&SharedTensor<f32>],
131+
input_data: &[&SharedTensor<f32>],
132+
input_gradients: &mut [&mut SharedTensor<f32>]) {
133+
match output_data.get(0) {
134+
Some(_) => backend.relu_grad_plain(output_data[0], output_gradients[0], input_data[0], input_gradients[0]).unwrap(),
135+
None => panic!("No output_data provided for ReLU layer backward."),
136+
}
137+
}
138+
}
139+
140+
#[cfg(feature="native")]
141+
impl<B: IBackend + Relu<f32>> ComputeParametersGradient<f32, B> for ReLU {}

0 commit comments

Comments
 (0)