🌟 Why LowMind?
| Feature | 🔥 PyTorch | 🟢 TensorFlow | ⚡ LowMind |
|---|---|---|---|
| Install Size | ~2.5 GB | ~600 MB | ~3 MB ✅ |
| Dependencies | 50+ | 30+ | 2 only ✅ |
| Raspberry Pi Ready | ❌ Painful | ⚠️ Limited | ✅ Native |
| PyTorch-like API | ✅ | ❌ | ✅ |
| Reverse-mode Autograd | ✅ | ✅ | ✅ |
| Zero CUDA Required | ❌ | ❌ | ✅ |
| Embedded / IoT / Edge | ❌ | ❌ | ✅ |
| System Health Monitor | ❌ | ❌ | ✅ |
LowMind is a pure-NumPy deep learning framework built from scratch for Raspberry Pi, embedded systems, and any resource-constrained environment. Train real models on a $35 computer.
🎯 Feature Coverage
Core Capabilities Coverage
─────────────────────────────────────────────────────────
🧠 Autograd Engine ████████████████████ 100%
🏗️ Neural Layers ████████████████████ 100%
⚡ Activations ████████████████████ 100%
📉 Loss Functions ████████████████████ 100%
🚀 Optimizers (5 types) ████████████████████ 100%
📅 LR Schedulers (7) ████████████████████ 100%
📦 Data Pipeline ████████████████████ 100%
📊 Metrics Suite ████████████████████ 100%
🎯 High-level Trainer ████████████████████ 100%
🔔 Callbacks ████████████████████ 100%
🤖 Pre-built Models ████████████████████ 100%
🖥️ System Monitor ████████████████████ 100%
⚙️ Model I/O (gzip) ████████████████████ 100%
🔢 INT8 Quantization ████████████████████ 100%
🔄 LSTM / GRU ████████████████████ 100%
🔌 Embedded C++ Exporter ████████████████████ 100%
🌐 Distributed Pi Cluster ░░░░░░░░░░░░░░░░░░░░ Planned
🗺️ Architecture
graph LR
A[📂 Your Data<br/>numpy arrays] --> B
subgraph DATA ["📦 Data Pipeline"]
B[TensorDataset] --> C[DataLoader<br/>batch + shuffle]
end
subgraph MODEL ["🏗️ Model — Sequential / Custom Module"]
D[Linear / Conv2d] --> E[Activation<br/>ReLU · GELU · Softmax]
E --> F[BatchNorm / Dropout]
F --> G[Output Layer]
end
subgraph ENGINE ["⚡ Training Engine"]
H[Loss Function] --> I[loss.backward<br/>Autograd Graph]
I --> J[Optimizer.step<br/>SGD · Adam · AdamW]
J --> K[LR Scheduler]
end
subgraph CALLBACKS ["🔔 Callbacks"]
L[EarlyStopping]
M[ModelCheckpoint]
N[History Logger]
end
subgraph MONITOR ["🖥️ System Monitor"]
O[CPU · RAM · Temp]
P[health_score 0–100]
Q[memory_trace]
end
C --> D
G --> H
K --> CALLBACKS
CALLBACKS --> R[💾 model.lmz<br/>Compressed]
R --> S[🍓 Raspberry Pi<br/>Inference]
MODEL --- MONITOR
style DATA fill:#1a2a4a,color:#7dd3fc
style MODEL fill:#1a3a2a,color:#86efac
style ENGINE fill:#2a1a3a,color:#c4b5fd
style CALLBACKS fill:#3a2a1a,color:#fdba74
style MONITOR fill:#3a1a1a,color:#fca5a5
🚀 Quick Start
import lowmind as lm
import numpy as np
# ┌─────────────────────────────────────────────────────────┐
# │ 1. Build Model │
# └─────────────────────────────────────────────────────────┘
model = lm.Sequential(
lm.Linear(784, 256),
lm.ReLU(),
lm.BatchNorm1d(256),
lm.Dropout(0.3),
lm.Linear(256, 128),
lm.ReLU(),
lm.Linear(128, 10),
)
print(model) # prints architecture
model.num_parameters() # → total trainable params
# ┌─────────────────────────────────────────────────────────┐
# │ 2. Data │
# └─────────────────────────────────────────────────────────┘
X = np.random.randn(1000, 784).astype(np.float32)
y = np.random.randint(0, 10, 1000)
X_train, X_val, y_train, y_val = lm.train_test_split(X, y, test_size=0.2)
train_loader = lm.DataLoader(lm.TensorDataset(X_train, y_train), batch_size=64, shuffle=True)
val_loader = lm.DataLoader(lm.TensorDataset(X_val, y_val), batch_size=64)
# ┌─────────────────────────────────────────────────────────┐
# │ 3. Train — one line │
# └─────────────────────────────────────────────────────────┘
trainer = lm.Trainer(
model = model,
optimizer = lm.Adam(model.parameters(), lr=1e-3),
loss_fn = lm.cross_entropy_loss,
callbacks = [lm.EarlyStopping(patience=10), lm.ModelCheckpoint('/tmp/best.lmz')],
clip_grad = 1.0,
verbose = 1,
)
history = trainer.fit(train_loader, val_loader, epochs=100)
# ┌─────────────────────────────────────────────────────────┐
# │ 4. Evaluate & Save │
# └─────────────────────────────────────────────────────────┘
val_loss, val_acc = trainer.evaluate(val_loader)
print(f"Val Accuracy: {val_acc:.2%}")
model.save('/tmp/model.lmz') # compressed — ~70% smaller
📚 Full API Reference
🔢 Tensors & Autograd
lm.Tensor — N-dimensional array with automatic gradient tracking.
# ── Creating ────────────────────────────────────────────────
t = lm.Tensor([1., 2., 3.]) # from list
t = lm.Tensor(np.array([[1, 2],[3, 4]])) # from numpy
t = lm.Tensor(5.0, requires_grad=True) # scalar with grad
lm.zeros(3, 4); lm.ones(2, 2) # factory
lm.randn(10,10); lm.rand(5, 5) # random
lm.arange(0, 10, 2) # → [0, 2, 4, 6, 8]
# ── Arithmetic ──────────────────────────────────────────────
c = a + b; c = a - b; c = a * b # element-wise
c = a / b; c = a ** 2; c = a @ b # divide, power, matmul
# ── Reductions ──────────────────────────────────────────────
x.sum(axis=0); x.mean(axis=(2, 3)); x.max(axis=1)
# ── Activations ─────────────────────────────────────────────
x.relu(); x.sigmoid(); x.tanh(); x.gelu()
x.softmax(axis=-1); x.clip(-1, 1); x.leaky_relu(0.01)
# ── Shape Ops ───────────────────────────────────────────────
x.reshape(6, 4); x.flatten(start_dim=1)
x.transpose((0,2,1)); x.squeeze(1); x.unsqueeze(0)
# ── Autograd Example ────────────────────────────────────────
x = lm.Tensor(3.0, requires_grad=True)
y = x**2 + 2*x + 1
y.backward()
print(x.grad) # → 8.0 ✓ (dy/dx = 2x+2)
# Gradient clipping
lm.clip_grad_norm(model.parameters(), max_norm=1.0)
# ── Utilities ───────────────────────────────────────────────
t.item(); t.numpy(); t.detach(); t.copy()
t.shape; t.ndim; t.size; t.zero_grad()
🏗️ Layers & Modules
# Linear
lm.Linear(784, 256, bias=True) # (N,784)→(N,256)
# Convolution
lm.Conv2d(3, 32, kernel_size=3, stride=1, padding=1) # (N,3,H,W)→(N,32,H,W)
# Normalization
lm.BatchNorm1d(256) # for (N, features)
lm.BatchNorm2d(32) # for (N, C, H, W)
# Pooling
lm.MaxPool2d(2, 2) # halves spatial dims
lm.AvgPool2d(2)
# Utility
lm.Flatten(start_dim=1)
lm.Dropout(p=0.5) # auto-disabled at model.eval()
lm.Embedding(10000, 128)
# ── Custom Module ───────────────────────────────────────────
class ResBlock(lm.Module):
def __init__(self, d):
super().__init__()
self.fc1 = lm.Linear(d, d)
self.bn = lm.BatchNorm1d(d)
self.fc2 = lm.Linear(d, d)
def forward(self, x):
return (self.bn(self.fc2(self.fc1(x).relu())) + x).relu()
# ── Sequential ──────────────────────────────────────────────
model = lm.Sequential(
lm.Linear(784, 256), lm.ReLU(), lm.BatchNorm1d(256),
lm.Dropout(0.3), lm.Linear(256, 10),
)
model.num_parameters() # count params
model.summary() # architecture table
📉 Loss Functions
lm.cross_entropy_loss(logits, targets) # classification
lm.cross_entropy_loss(logits, targets, reduction='sum')
lm.binary_cross_entropy_loss(probs, targets) # binary
lm.binary_cross_entropy_loss(logits, targets, from_logits=True)
lm.mse_loss(preds, targets) # regression
lm.mae_loss(preds, targets) # outlier-robust
lm.huber_loss(preds, targets, delta=1.0) # smooth L1
lm.nll_loss(log_probs, targets) # after log-softmax
🚀 Optimizers
# All share the same interface:
optimizer.zero_grad() → loss.backward() → optimizer.step()
lm.SGD(model.parameters(), lr=0.01, momentum=0.9,
weight_decay=1e-4, nesterov=True)
lm.Adam(model.parameters(), lr=1e-3, betas=(0.9,0.999),
eps=1e-8, amsgrad=False)
lm.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01) # ← preferred
lm.RMSprop(model.parameters(), lr=1e-3, alpha=0.99, momentum=0.0)
lm.AdaGrad(model.parameters(), lr=0.01)
Convergence (lower is better, epoch 10):
SGD ████████████████████░░░░░░░ 0.42
AdaGrad ████████████████░░░░░░░░░░░ 0.28
RMSprop █████████████░░░░░░░░░░░░░░ 0.31
Adam ████████░░░░░░░░░░░░░░░░░░░ 0.18 ⭐
AdamW ███████░░░░░░░░░░░░░░░░░░░░ 0.16 ⭐⭐
📅 LR Schedulers
lm.StepLR(optimizer, step_size=10, gamma=0.5)
lm.MultiStepLR(optimizer, milestones=[30,60,90], gamma=0.1)
lm.ExponentialLR(optimizer, gamma=0.95)
lm.CosineAnnealingLR(optimizer, T_max=50, eta_min=1e-6)
lm.ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.5)
lm.LinearWarmupLR(optimizer, warmup_steps=1000, target_lr=1e-3)
lm.CyclicLR(optimizer, base_lr=1e-4, max_lr=1e-1,
step_size=2000, mode='triangular') # step per batch!
📦 Data Utilities
# Datasets
ds = lm.TensorDataset(X_train, y_train)
class MyDataset(lm.Dataset):
def __init__(self, X, y): self.X, self.y = X, y
def __len__(self): return len(self.X)
def __getitem__(self, i): return self.X[i], self.y[i]
# DataLoader
loader = lm.DataLoader(ds, batch_size=64, shuffle=True, drop_last=False)
for X_batch, y_batch in loader: ...
# Split
X_tr, X_val, y_tr, y_val = lm.train_test_split(
X, y, test_size=0.2, shuffle=True, seed=42)
📊 Metrics
# Classification
lm.accuracy(preds, targets) # 0-1 float
lm.top_k_accuracy(logits, targets, k=5)
lm.precision(logits, targets, num_classes=10) # macro
lm.recall(logits, targets, num_classes=10)
lm.f1_score(logits, targets, num_classes=10)
lm.f1_score(logits, targets, num_classes=10, average='none') # per-class
lm.confusion_matrix(logits, targets) # (C,C) array
# Regression
lm.r2_score(preds, targets)
lm.mean_squared_error(preds, targets)
lm.mean_absolute_error(preds, targets)
🤖 Pre-built Models
# Tabular / flat data
lm.MicroMLP(input_size=784, hidden_sizes=[256,128], output_size=10, dropout=0.3)
# Small images (N, 3, 32, 32) → (N, 10)
lm.MicroCNN(in_channels=3, num_classes=10, input_size=32, dropout=0.2)
# Residual connections — more capacity
lm.TinyResNet(in_channels=3, num_classes=10, input_size=32, base_filters=16)
# ── Model I/O ───────────────────────────────────────────────
model.save('/path/model.lmz') # compressed gzip
model.save('/path/model.lm', compress=False)
model.load('/path/model.lmz')
sd = model.state_dict()
model.load_state_dict(sd, strict=False)
🖥️ System Monitor
lm.configure_memory(max_mb=128) # set budget
monitor = lm.SystemMonitor()
monitor.print_status() # CPU%, RAM, temp
score = monitor.health_score() # 0–100
stats = monitor.get_stats()
with lm.memory_trace("Forward Pass"):
out = model(X)
lm.memory_manager.optimize_for_inference()
lm.memory_manager.get_memory_info()
# {'allocated_mb': 12.3, 'max_mb': 128.0, 'usage_percent': 9.6}
🔌 Embedded C++ Inference Engine Exporter
Export your trained LowMind Sequential models directly into standard, highly-efficient, standalone C++ header files ready to compile and run on microcontrollers (Arduino, ESP32, STM32) without Python!
import lowmind as lm
# 1. Define input shape (C, H, W) or flat features
input_shape = (1, 8, 8)
# 2. Export model weights, biases, and layers to a self-contained header file
lm.export_to_cpp(model, input_shape, "embedded_model.h", namespace="my_embedded_model")
Key Advantages:
- Ping-Pong Static Buffer Architecture: Avoids dynamic memory allocation (
malloc/new) completely. Keeps memory consumption perfectly predictable and constant on small microcontrollers. - Pure Self-Contained C++: Generated with standard
<cmath>and arrays. Zero external dependencies required. - Extensive Layer Support: Supports Linear, Conv2d, BatchNorm1d/BatchNorm2d, MaxPool2d, AvgPool2d, Flatten, ReLU, LeakyReLU, Sigmoid, Tanh, and Softmax layers.
✂️ Weight Pruning & Sparsity
Magnitude-based weight pruning API to zero out low-magnitude weights and calculate overall model sparsity.
import lowmind as lm
# Create a pruner for your model
pruner = lm.Pruner(model)
# Prune the entire model (skip biases by default) to a target sparsity ratio (0.0 to 1.0)
pruner.prune_model(sparsity_ratio=0.5)
# Prune specific weight parameters of a layer
pruner.prune_module_weight("fc.weight", sparsity_ratio=0.5)
# Re-apply pruning masks (vital to call after optimizer.step() during training)
optimizer.step()
pruner.apply_masks()
# Calculate current sparsity percentage of the model
sparsity_pct = pruner.calculate_sparsity()
print(f"Model Sparsity: {sparsity_pct:.2f}%")
🎯 INT8 Model Quantization
Post-Training Integer (INT8) Quantization helper to convert float32 weights to simulated 8-bit integer weights.
import lowmind as lm
# In-place quantization of model weights to simulate 8-bit integers
model.quantize()
# Alternatively, extract integer weights and scale factor of a specific tensor
q_data, scale = lm.quantize_weight(model[0].weight)
# Wrap quantized data in a container
quantized_tensor = lm.QuantizedTensor(q_data, scale)
# Convert back to float32 representation
float_data = quantized_tensor.dequantize()
🏋️ Quantization Aware Training (QAT)
Simulate the effects of 8-bit integer quantization during training using Straight-Through Estimators (STE). This allows the model's weights to adapt and learn quantization robust features, resulting in almost 0% accuracy drop when finally quantized to INT8!
import lowmind as lm
# 1. Enable QAT (Straight-Through Estimators) on all layers of a model
lm.prepare_qat(model, enabled=True)
# 2. Train the model normally using any trainer or custom loop
# Standard SGD, Adam, and backpropagation are fully supported
trainer.fit(loader, epochs=5)
# 3. Toggle QAT off after training
lm.prepare_qat(model, enabled=False)
# 4. Perform final INT8 quantization
model.quantize()
🎓 Knowledge Distillation
Knowledge Distillation Trainer to transfer knowledge from a heavy, pre-trained Teacher model to a lightweight Student model.
import lowmind as lm
# Setup DistillationTrainer (combines hard label loss and soft temperature-scaled loss)
trainer = lm.DistillationTrainer(
student_model=student_model,
teacher_model=teacher_model,
optimizer=optimizer,
loss_fn=lm.cross_entropy_loss,
temperature=3.0, # Soft target scaling temperature (default 3.0)
alpha=0.5, # Coefficient weight for soft loss vs hard loss (default 0.5)
clip_grad=1.0,
grad_accum_steps=1,
verbose=1
)
# Train the student model
history = trainer.fit(train_loader, val_loader, epochs=10)
🗜️ Gradient Accumulation
Simulate large batch sizes on low-memory edge devices by accumulating gradients over multiple steps before performing an optimizer update.
import lowmind as lm
# Pass grad_accum_steps parameter to Trainer
trainer = lm.Trainer(
model=model,
optimizer=optimizer,
loss_fn=lm.cross_entropy_loss,
grad_accum_steps=4 # Accumulate over 4 steps (effectively 4x batch size)
)
🛡️ Gradient Checkpointing
Trade compute for massive memory savings on edge devices. Only save activations at checkpoints and recompute the rest during the backward pass on-the-fly.
import lowmind as lm
# Wrap Sequential block or any sub-module function in checkpoint
out = lm.checkpoint(model_block, input_tensor)
🚀 Hardware Bottleneck Accelerator
Check if hardware acceleration is active. Incorporates blazingly fast memory stride tricks and optional Numba Just-In-Time (JIT) compiler fallback to accelerate k-D convolutions at assembly-level speed (10x - 50x speedup!).
import lowmind as lm
# Check if hardware JIT/stride acceleration is active on this system
print("JIT Accelerated:", lm.is_jit_accelerated())
🔄 ONNX Model Export
Exports a LowMind model to standard ONNX format for cross-platform deployment on PyTorch, TensorFlow, ONNX Runtime, TensorRT, or Android/iOS accelerators.
import lowmind as lm
import numpy as np
# Define dummy input
dummy_input = np.random.randn(1, 3, 32, 32).astype(np.float32)
# Export and verify to standard .onnx file
onnx_model = lm.export_to_onnx(model, dummy_input, "model.onnx")
💡 10 Complete Examples
| # | Script | Topic |
|---|---|---|
01 |
01_basic_tensors.py |
Tensor creation, arithmetic, autograd from scratch |
02 |
02_linear_regression.py |
Linear regression · SGD · custom loop |
03 |
03_mlp_classification.py |
XOR classification · Adam · DataLoader |
04 |
04_mnist_like.py |
Full pipeline · MicroMLP · EarlyStopping · Checkpointing |
05 |
05_cnn_image.py |
MicroCNN · BatchNorm · MaxPool |
06 |
06_optimizers_comparison.py |
SGD vs Adam vs RMSprop vs AdaGrad benchmark |
07 |
07_custom_layer.py |
Attention layer · LayerNorm · Transformer block |
08 |
08_save_load_model.py |
Save / load · state_dict · transfer learning |
09 |
09_lr_schedulers.py |
Compare all 7 scheduler strategies |
10 |
10_raspberry_pi_monitor.py |
System monitoring · memory tracing · health score |
git clone https://github.com/dhaval-vedra/lowmind.git && cd lowmind
python examples/01_basic_tensors.py
python examples/04_mnist_like.py
📂 Project Structure
lowmind/
├── 📦 lowmind/ ← Main package
│ ├── __init__.py ← Public API (all exports here)
│ ├── core/
│ │ ├── tensor.py ← 🧠 Tensor + autograd engine
│ │ ├── memory.py ← 💾 MemoryManager (LRU, GC)
│ │ └── module.py ← 🏗️ Module base class
│ ├── nn/
│ │ ├── layers.py ← Linear, Conv2d, BatchNorm, Pool…
│ │ ├── activation.py ← ReLU, GELU, Sigmoid, Softmax…
│ │ ├── loss.py ← cross_entropy, bce, mse, huber…
│ │ └── sequential.py ← Sequential container
│ ├── optim/
│ │ ├── sgd.py ← SGD + Nesterov
│ │ ├── adam.py ← Adam, AdamW, RMSprop, AdaGrad
│ │ └── scheduler.py ← 7 LR schedulers
│ ├── data/
│ │ └── dataloader.py ← Dataset, DataLoader, split
│ ├── utils/
│ │ ├── metrics.py ← accuracy, f1, r2, confusion…
│ │ ├── trainer.py ← High-level Trainer
│ │ ├── callbacks.py ← EarlyStopping, Checkpoint, History
│ │ └── monitor.py ← SystemMonitor, memory_trace
│ └── models/
│ └── micro_cnn.py ← MicroMLP, MicroCNN, TinyResNet
├── 📁 examples/ ← 10 complete runnable examples
├── 🧪 tests/ ← pytest test suite
├── 📖 docs/ ← Extended documentation
├── setup.py
├── requirements.txt
└── README.md
🍓 Raspberry Pi — Deployment Guide
┌──────────────────┬────────────┬────────────┬─────────────────┬──────────────┐
│ Device │ Memory │ max_mb │ batch_size │ Best Model │
├──────────────────┼────────────┼────────────┼─────────────────┼──────────────┤
│ Pi Zero W │ 512 MB │ 64 │ 4–8 │ MicroMLP │
│ Pi 3 Model B │ 1 GB │ 128 │ 16 │ MicroCNN │
│ Pi 4 (2 GB) │ 2 GB │ 256 │ 32 │ TinyResNet │
│ Pi 4 (4 GB+) │ 4–8 GB │ 512 │ 64 │ TinyResNet │
└──────────────────┴────────────┴────────────┴─────────────────┴──────────────┘
import lowmind as lm
# ① Set memory limit for your Pi
lm.configure_memory(max_mb=128) # Pi 3
# ② Small batch sizes
loader = lm.DataLoader(ds, batch_size=16)
# ③ Pi-optimized architectures
model = lm.MicroCNN(in_channels=1, num_classes=10, input_size=28)
# ④ Monitor health during training
monitor = lm.SystemMonitor()
if monitor.health_score() < 40:
print("⚠️ System stressed — reduce batch size or lr")
# ⑤ Free memory after training
lm.memory_manager.optimize_for_inference()
import gc; gc.collect()
# ⑥ Save compressed for deployment (~70% smaller)
model.save('/tmp/model.lmz', compress=True)
🤝 Contributing
Contributions are very welcome! Priority areas:
| Area | Difficulty | Impact |
|---|---|---|
| 📊 Pi benchmark suite | Easy | High |
| 🔄 LSTM / GRU layers | Medium | High |
| ⚡ INT8 Quantization | Hard | Very High |
| 🌐 Multi-Pi distributed | Hard | Very High |
# Fork → Branch → Code → Test → PR
git clone https://github.com/<you>/lowmind && cd lowmind
git checkout -b feature/my-awesome-feature
pip install pytest && pytest tests/ -v
# then open a PR 🎉
🧪 Running Tests
pip install pytest
pytest tests/ -v
📄 License
MIT License — free to use, modify, and distribute. See LICENSE.
Built with ❤️ in India 🇮🇳 by Dhaval Vedra
Empowering AI at the edge — from data centers down to $35 computers
⭐ Star this repo if LowMind helped you — it keeps the project alive! ⭐
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lowmind-2.2.0.tar.gz.
File metadata
- Download URL: lowmind-2.2.0.tar.gz
- Upload date:
- Size: 109.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88b23a6857b7c090fcacf0b93de7ba2aa5aab391f473b9c7dcd40dd87301c4f0
|
|
| MD5 |
0a6f800baf24938162cd858194e61ff2
|
|
| BLAKE2b-256 |
03771e6d3131f538b58b34e5e9d64ff7cc8ff8a64bd3bf9e5fe0ff7f547d8c22
|
File details
Details for the file lowmind-2.2.0-py3-none-any.whl.
File metadata
- Download URL: lowmind-2.2.0-py3-none-any.whl
- Upload date:
- Size: 78.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd8ac0bd1f6d59e70f5b00e1a8b0858d6d5314fd1616dcd2d9d200fa696958d9
|
|
| MD5 |
75b1e424c3084b4788e3a0aa307c5322
|
|
| BLAKE2b-256 |
6820ab1f1738284d843855268991b8e71a9e674c3f8e43352b23fa279dd2ac01
|