tinygrad/lib.rs
1//! # tinygrad
2//!
3//! `tinygrad` is a crate for building and training neural networks in Rust. It provides a simple interface for defining tensors,
4//! performing forward and backward passes, and implementing basic operations such as dot products and summation.
5//!
6//! ## Quick Start
7//!
8//! Get started with the `tinygrad` library by following these simple steps:
9//!
10//! 1. Install the `tinygrad` crate by adding the following line to your `Cargo.toml` file:
11//!
12//! ```toml
13//! [dependencies]
14//! tinygrad = "0.1.0"
15//! ```
16//!
17//! 2. Use the `Tensor` and `ForwardBackward` traits to create and work with tensors:
18//!
19//! ```rust
20//! use ndarray::{array, Array1};
21//! use tinygrad::{Tensor, Context, TensorTrait};
22//!
23//! // Create a tensor
24//! let value = array![1.0, 2.0, 3.0];
25//! let tensor = Tensor::new(value);
26//!
27//! // Perform forward and backward passes
28//! let mut ctx = Context::new();
29//! let result = tensor.forward(&mut ctx, vec![tensor.get_value()]);
30//! tensor.backward(&mut ctx, array![1.0, 1.0, 1.0].view());
31//! ```
32//!
33//! 3. Implement custom operations by defining structs that implement the `ForwardBackward` trait:
34//!
35//! ```rust
36//! use ndarray::ArrayView1;
37//! use tinygrad::{ForwardBackward, Context, TensorTrait};
38//!
39//! // Example operation: Dot product
40//! struct Dot;
41//!
42//! impl ForwardBackward for Dot {
43//! fn forward(&self, _ctx: &mut Context, inputs: Vec<ArrayView1<f64>>) -> f64 {
44//! let input = &inputs[0];
45//! let weight = &inputs[1];
46//! input.dot(weight)
47//! }
48//!
49//! fn backward(&self, ctx: &mut Context, grad_output: ArrayView1<f64>) {
50//! // Implement backward pass
51//! // ...
52//! }
53//! }
54//! ```
55//!
56//! ## GitHub Repository
57//!
58//! You can access the source code for the `tinygrad` crate on [GitHub](https://github.com/wiseaidev/tinygrad).
59//!
60//! ## Contributing
61//!
62//! Contributions and feedback are welcome! If you'd like to contribute, report an issue, or suggest an enhancement,
63//! please engage with the project on [GitHub](https://github.com/wiseaidev/tinygrad).
64//! Your contributions help improve this crate for the community.
65
66use ndarray::{array, Array1, ArrayView1};
67
68type Gradient = Array1<f64>;
69
70/// This trait defines the common interface for tensors in a computational graph.
71pub trait TensorTrait {
72 /// Computes the forward pass of the tensor.
73 ///
74 /// # Arguments
75 /// * `ctx` - A mutable reference to the computation context.
76 /// * `inputs` - A vector of input arrays for the forward pass.
77 ///
78 /// # Returns
79 /// (`f64`): The result of the forward pass.
80 fn forward(&self, ctx: &mut Context, inputs: Vec<ArrayView1<f64>>) -> f64;
81
82 /// Computes the backward pass of the tensor to calculate gradients.
83 ///
84 /// # Arguments
85 /// * `ctx` - A mutable reference to the computation context.
86 /// * `grad_output` - The gradient of the loss with respect to the output.
87 fn backward(&self, ctx: &mut Context, grad_output: ArrayView1<f64>);
88
89 /// Gets the value of the tensor.
90 ///
91 /// # Returns
92 /// (`ArrayView1<f64>`): The view of the tensor's value.
93 fn get_value(&self) -> ArrayView1<f64>;
94
95 /// Gets the gradient of the tensor.
96 ///
97 /// # Returns
98 /// (`Option<Gradient>`): The option containing the gradient if available.
99 fn get_grad(&self) -> Option<Gradient>;
100}
101
102/// Represents a basic implementation of a tensor.
103#[derive(Debug, Clone)]
104pub struct Tensor {
105 pub value: Array1<f64>,
106 pub grad: Option<Gradient>,
107}
108
109impl Tensor {
110 /// Creates a new Tensor with the given value.
111 ///
112 /// # Arguments
113 ///
114 /// * `value` - The array representing the value of the tensor.
115 ///
116 /// # Returns
117 ///
118 /// (`Tensor`): A new Tensor instance.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use ndarray::{array, Array1};
124 /// use tinygrad::Tensor;
125 ///
126 /// let value = array![1.0, 2.0, 3.0];
127 /// let tensor = Tensor::new(value);
128 /// ```
129 pub fn new(value: Array1<f64>) -> Tensor {
130 Tensor { value, grad: None }
131 }
132}
133
134impl TensorTrait for Tensor {
135 /// Implements the forward pass for the Tensor.
136 ///
137 /// # Arguments
138 ///
139 /// * `_ctx` - A mutable reference to the computation context (not used in this example).
140 /// * `_inputs` - A vector of ArrayView1<f64> representing the input values (not used in this example).
141 ///
142 /// # Returns
143 ///
144 /// (`f64`): The result of the forward pass (not implemented in this example, returns 0.0).
145 fn forward(&self, _ctx: &mut Context, _inputs: Vec<ArrayView1<f64>>) -> f64 {
146 // TODO: Implement forward function for Tensor
147 0.0
148 }
149
150 /// Implements the backward pass for the Tensor.
151 ///
152 /// # Arguments
153 ///
154 /// * `_ctx` - A mutable reference to the computation context (not used in this example).
155 /// * `_grad_output` - An ArrayView1<f64> representing the gradient of the output.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// use ndarray::{array, Array1};
161 /// use tinygrad::{Tensor, Context, TensorTrait};
162 ///
163 /// let mut ctx = Context::new();
164 /// let tensor = Tensor::new(array![1.0, 2.0, 3.0]);
165 /// tensor.backward(&mut ctx, array![1.0, 1.0, 1.0].view());
166 /// ```
167 fn backward(&self, _ctx: &mut Context, _grad_output: ArrayView1<f64>) {
168 // TODO: Implement backward function for Tensor
169 }
170
171 /// Returns the value of the Tensor.
172 ///
173 /// # Returns
174 ///
175 /// (`ArrayView1<f64>`): The view of the array representing the value of the tensor.
176 ///
177 /// # Examples
178 ///
179 /// ```
180 /// use ndarray::{array, Array1};
181 /// use tinygrad::{Tensor, TensorTrait};
182 ///
183 /// let tensor = Tensor::new(array![1.0, 2.0, 3.0]);
184 /// let value = tensor.get_value();
185 /// ```
186 fn get_value(&self) -> ArrayView1<f64> {
187 self.value.view()
188 }
189
190 /// Returns the gradient of the Tensor if available.
191 ///
192 /// # Returns
193 ///
194 /// (`Option<Gradient>`): The optional gradient of the tensor.
195 ///
196 /// # Examples
197 ///
198 /// ```
199 /// use ndarray::{array, Array1};
200 /// use tinygrad::{Tensor, TensorTrait};
201 ///
202 /// let tensor = Tensor::new(array![1.0, 2.0, 3.0]);
203 /// let grad = tensor.get_grad();
204 /// ```
205 fn get_grad(&self) -> Option<Gradient> {
206 self.grad.clone()
207 }
208}
209
210/// This trait defines the interface for operations that have both forward and backward passes.
211pub trait ForwardBackward {
212 /// Computes the forward pass of the operation.
213 ///
214 /// # Arguments
215 /// * `ctx` - A mutable reference to the computation context.
216 /// * `inputs` - A vector of input arrays for the forward pass.
217 ///
218 /// # Returns
219 /// (`f64`): The result of the forward pass.
220 fn forward(&self, ctx: &mut Context, inputs: Vec<ArrayView1<f64>>) -> f64;
221
222 /// Computes the backward pass of the operation to calculate gradients.
223 ///
224 /// # Arguments
225 /// * `ctx` - A mutable reference to the computation context.
226 /// * `grad_output` - The gradient of the loss with respect to the output.
227 fn backward(&self, ctx: &mut Context, grad_output: ArrayView1<f64>);
228}
229
230/// Represents the Dot operation.
231struct Dot;
232
233/// Represents the Sum operation.
234struct Sum;
235
236impl ForwardBackward for Dot {
237 fn forward(&self, _ctx: &mut Context, inputs: Vec<ArrayView1<f64>>) -> f64 {
238 let input = &inputs[0];
239 let weight = &inputs[1];
240
241 input.dot(weight)
242 }
243
244 fn backward(&self, ctx: &mut Context, grad_output: ArrayView1<f64>) {
245 if ctx.saved_tensors.is_empty() {
246 println!("Warning: saved_tensors is empty. Unable to compute gradients.");
247 return;
248 }
249
250 let mut input = ctx.saved_tensors[0].clone();
251 let mut weight = ctx.saved_tensors[1].clone();
252
253 let grad_input = grad_output.dot(&input.get_value().t());
254 let grad_weight = input.get_value().t().dot(&grad_output);
255
256 input.grad = Some(array![grad_input]);
257 weight.grad = Some(array![grad_weight]);
258
259 ctx.save_for_backward(vec![Box::new(*input), Box::new(*weight)]);
260 }
261}
262
263impl ForwardBackward for Sum {
264 fn forward(&self, _ctx: &mut Context, inputs: Vec<ArrayView1<f64>>) -> f64 {
265 let input = &inputs[0];
266
267 input.sum()
268 }
269
270 fn backward(&self, ctx: &mut Context, grad_output: ArrayView1<f64>) {
271 let mut input = ctx.saved_tensors[0].clone();
272
273 input.grad = Some(Array1::from(grad_output.map(|x| x * 1.0)));
274
275 ctx.save_for_backward(vec![Box::new(*input)]);
276 }
277}
278
279/// Represents the computation context, storing tensors for backward pass computations.
280pub struct Context {
281 pub saved_tensors: Vec<Box<Tensor>>,
282}
283
284impl Context {
285 /// Creates a new Context instance.
286 ///
287 /// # Returns
288 ///
289 /// (`Context`): A new Context instance.
290 ///
291 /// # Examples
292 ///
293 /// ```
294 /// use tinygrad::Context;
295 ///
296 /// let context = Context::new();
297 /// ```
298 pub fn new() -> Context {
299 Context {
300 saved_tensors: Vec::new(),
301 }
302 }
303
304 /// Saves tensors for backward pass.
305 ///
306 /// # Arguments
307 ///
308 /// * `tensors` - A vector of Boxed Tensors to be saved.
309 ///
310 /// # Examples
311 ///
312 /// ```
313 /// use ndarray::{array, Array1};
314 /// use tinygrad::{Tensor, Context};
315 ///
316 /// let mut ctx = Context::new();
317 /// let tensor = Tensor::new(array![1.0, 2.0, 3.0]);
318 /// ctx.save_for_backward(vec![Box::new(tensor)]);
319 /// ```
320 pub fn save_for_backward(&mut self, tensors: Vec<Box<Tensor>>) {
321 self.saved_tensors.extend(tensors);
322 }
323}
324
325impl Default for Context {
326 fn default() -> Self {
327 Self::new()
328 }
329}