feat: update workspace paths and enhance gitignore
- Updated stablediffusion crate path from "../stable-diffusion-burn" to "./crates/stable-diffusion-burn" for proper workspace resolution - Enhanced .gitignore to include generated model files (.mpk, .pt, .bin, .safetensors, .ckpt) and user_data directory - Added Cargo.lock to gitignore with appropriate comment - Reorganized IDE files section in gitignore for better clarity - Added newline at end of file for proper formatting
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
authors = [
|
||||
"nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"Dilshod Tadjibaev (@antimora)",
|
||||
]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "burn-no-std-tests"
|
||||
readme.workspace = true
|
||||
repository = "https://github.com/tracel-ai/burn/tree/main/crates/burn-no-std-tests"
|
||||
version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
tracing = [
|
||||
"burn/tracing",
|
||||
"burn-ndarray/tracing",
|
||||
"burn-store/tracing",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# ** Please make sure all dependencies support no_std **
|
||||
|
||||
burn = { path = "../burn", version = "=0.21.0-pre.2", default-features = false }
|
||||
burn-ndarray = { path = "../burn-ndarray", version = "=0.21.0-pre.2", default-features = false }
|
||||
|
||||
burn-store = { path = "../burn-store", version = "=0.21.0-pre.2", default-features = false, features = ["safetensors", "burnpack"]}
|
||||
@@ -0,0 +1,29 @@
|
||||
The `burn-no-std-tests` contains integration tests aimed to check `no_std` compatibility of `burn`, `burn-core`, `burn-tensor` and `burn-ndarray` packages.
|
||||
|
||||
Currently there is only a minimal test that checks if mnist model can be built with `no_std`. More tests should be added to check completeness.
|
||||
|
||||
The continuous integration (CI) should build with additional targets:
|
||||
|
||||
* `wasm32-unknown-unknown` - WebAssembly
|
||||
* `thumbv7m-none-eabi` - ARM Cortex-M3
|
||||
* `thumbv6m-none-eabi` - ARM Cortex-M0+
|
||||
|
||||
Shell commands to build and test the package:
|
||||
|
||||
```sh
|
||||
|
||||
# install the new targets if not installed previously
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add wasm32-unknown-unknown
|
||||
|
||||
# build for various targets
|
||||
cargo build # regular build
|
||||
cargo build --target thumbv7m-none-eabi
|
||||
cargo build --target wasm32-unknown-unknown
|
||||
RUSTFLAGS="--cfg portable_atomic_unsafe_assume_single_core" cargo build --target thumbv6m-none-eabi
|
||||
|
||||
# test
|
||||
cargo test
|
||||
|
||||
```
|
||||
@@ -0,0 +1,158 @@
|
||||
// Test Burnpack storage in no-std environment
|
||||
|
||||
use burn::{
|
||||
module::Module,
|
||||
nn,
|
||||
tensor::{Tensor, backend::Backend},
|
||||
};
|
||||
|
||||
use burn_store::{BurnpackStore, ModuleSnapshot, PathFilter};
|
||||
|
||||
/// Simple model for testing Burnpack storage
|
||||
#[derive(Module, Debug)]
|
||||
pub struct TestModel<B: Backend> {
|
||||
linear1: nn::Linear<B>,
|
||||
linear2: nn::Linear<B>,
|
||||
batch_norm: nn::BatchNorm<B>,
|
||||
}
|
||||
|
||||
impl<B: Backend> TestModel<B> {
|
||||
pub fn new(device: &B::Device) -> Self {
|
||||
Self {
|
||||
linear1: nn::LinearConfig::new(10, 20).init(device),
|
||||
linear2: nn::LinearConfig::new(20, 10).init(device),
|
||||
batch_norm: nn::BatchNormConfig::new(10).init(device),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward(&self, x: Tensor<B, 2>) -> Tensor<B, 2> {
|
||||
let x = self.linear1.forward(x);
|
||||
let x = self.linear2.forward(x);
|
||||
// Apply batch norm (expand to 3D, apply, then squeeze back)
|
||||
let x: Tensor<B, 3> = x.unsqueeze_dim(2);
|
||||
let x = self.batch_norm.forward(x);
|
||||
x.squeeze_dim(2)
|
||||
}
|
||||
}
|
||||
|
||||
/// Test basic Burnpack save and load in no-std
|
||||
pub fn test_burnpack_basic<B: Backend>(device: &B::Device) {
|
||||
// Create a model
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save to bytes (no file I/O in no-std)
|
||||
let mut save_store = BurnpackStore::from_bytes(None);
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save model");
|
||||
|
||||
// Get the serialized bytes
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load from bytes
|
||||
let mut load_store = BurnpackStore::from_bytes(Some(bytes));
|
||||
let mut loaded_model = TestModel::<B>::new(device);
|
||||
let result = loaded_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load model");
|
||||
|
||||
// Verify all tensors were loaded
|
||||
assert!(result.is_success(), "Should have no errors");
|
||||
assert!(!result.applied.is_empty(), "Should have loaded tensors");
|
||||
|
||||
// Test that the model still works
|
||||
let input = Tensor::<B, 2>::ones([2, 10], device);
|
||||
let _output = loaded_model.forward(input);
|
||||
}
|
||||
|
||||
/// Test Burnpack with filtering in no-std
|
||||
pub fn test_burnpack_filtering<B: Backend>(device: &B::Device) {
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save only linear1 weights
|
||||
let filter = PathFilter::new()
|
||||
.with_full_path("linear1.weight")
|
||||
.with_full_path("linear1.bias");
|
||||
let mut save_store = BurnpackStore::from_bytes(None).with_filter(filter);
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save filtered model");
|
||||
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load with partial loading allowed
|
||||
let mut load_store = BurnpackStore::from_bytes(Some(bytes)).allow_partial(true);
|
||||
let mut partial_model = TestModel::<B>::new(device);
|
||||
let result = partial_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load partial model");
|
||||
|
||||
// Verify that only linear1 was loaded
|
||||
assert_eq!(result.applied.len(), 2, "Should have loaded 2 tensors");
|
||||
assert!(!result.missing.is_empty(), "Should have missing tensors");
|
||||
}
|
||||
|
||||
/// Test Burnpack with metadata in no-std
|
||||
pub fn test_burnpack_metadata<B: Backend>(device: &B::Device) {
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save with metadata
|
||||
let mut save_store = BurnpackStore::from_bytes(None)
|
||||
.metadata("version", "1.0.0")
|
||||
.metadata("environment", "no-std")
|
||||
.metadata("model_type", "test");
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save model with metadata");
|
||||
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load and verify it works
|
||||
let mut load_store = BurnpackStore::from_bytes(Some(bytes));
|
||||
let mut loaded_model = TestModel::<B>::new(device);
|
||||
let result = loaded_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load model with metadata");
|
||||
|
||||
assert!(result.is_success(), "Should load successfully");
|
||||
}
|
||||
|
||||
// Note: Key remapping test is omitted as KeyRemapper requires std feature
|
||||
|
||||
// Note: Regex filtering test is omitted as with_regex requires std feature
|
||||
|
||||
/// Test Burnpack with match_all in no-std
|
||||
pub fn test_burnpack_match_all<B: Backend>(device: &B::Device) {
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save with match_all (should save everything)
|
||||
let mut save_store = BurnpackStore::from_bytes(None).match_all();
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save model");
|
||||
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load everything
|
||||
let mut load_store = BurnpackStore::from_bytes(Some(bytes));
|
||||
let mut loaded_model = TestModel::<B>::new(device);
|
||||
let result = loaded_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load model");
|
||||
|
||||
assert!(result.is_success(), "Should load successfully");
|
||||
// linear1 (weight, bias) + linear2 (weight, bias) + batch_norm (4 params)
|
||||
assert_eq!(result.applied.len(), 8, "Should load all 8 tensors");
|
||||
assert!(result.missing.is_empty(), "Should have no missing tensors");
|
||||
assert!(result.unused.is_empty(), "Should have no unused tensors");
|
||||
}
|
||||
|
||||
/// Run all Burnpack no-std tests
|
||||
pub fn run_all_tests<B: Backend>(device: &B::Device) {
|
||||
test_burnpack_basic::<B>(device);
|
||||
test_burnpack_filtering::<B>(device);
|
||||
test_burnpack_metadata::<B>(device);
|
||||
// test_burnpack_remapping requires KeyRemapper which needs std
|
||||
// test_burnpack_regex_filter requires with_regex which needs std
|
||||
test_burnpack_match_all::<B>(device);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Originally copied from the burn/examples/mnist package
|
||||
|
||||
use burn::{
|
||||
config::Config,
|
||||
module::Module,
|
||||
nn,
|
||||
tensor::{Tensor, backend::Backend},
|
||||
};
|
||||
|
||||
#[derive(Module, Debug)]
|
||||
pub struct ConvBlock<B: Backend> {
|
||||
conv: nn::conv::Conv2d<B>,
|
||||
pool: nn::pool::MaxPool2d,
|
||||
activation: nn::Gelu,
|
||||
}
|
||||
|
||||
#[derive(Config, Debug)]
|
||||
pub struct ConvBlockConfig {
|
||||
channels: [usize; 2],
|
||||
#[config(default = "[3, 3]")]
|
||||
kernel_size: [usize; 2],
|
||||
}
|
||||
|
||||
impl<B: Backend> ConvBlock<B> {
|
||||
pub fn new(config: &ConvBlockConfig, device: &B::Device) -> Self {
|
||||
let conv = nn::conv::Conv2dConfig::new(config.channels, config.kernel_size)
|
||||
.with_padding(nn::PaddingConfig2d::Same)
|
||||
.init(device);
|
||||
let pool = nn::pool::MaxPool2dConfig::new(config.kernel_size)
|
||||
.with_strides([1, 1])
|
||||
.with_padding(nn::PaddingConfig2d::Same)
|
||||
.init();
|
||||
let activation = nn::Gelu::new();
|
||||
|
||||
Self {
|
||||
conv,
|
||||
pool,
|
||||
activation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward(&self, input: Tensor<B, 4>) -> Tensor<B, 4> {
|
||||
let x = self.conv.forward(input.clone());
|
||||
let x = self.pool.forward(x);
|
||||
let x = self.activation.forward(x);
|
||||
|
||||
(x + input) / 2.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod burnpack;
|
||||
pub mod conv;
|
||||
pub mod mlp;
|
||||
pub mod model;
|
||||
pub mod safetensors;
|
||||
|
||||
extern crate alloc;
|
||||
@@ -0,0 +1,67 @@
|
||||
// Originally copied from the burn/examples/mnist package
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use burn::{
|
||||
config::Config,
|
||||
module::Module,
|
||||
nn,
|
||||
tensor::{Tensor, backend::Backend},
|
||||
};
|
||||
|
||||
/// Configuration to create a [Multilayer Perceptron](Mlp) layer.
|
||||
#[derive(Config, Debug)]
|
||||
pub struct MlpConfig {
|
||||
/// The number of layers.
|
||||
#[config(default = 3)]
|
||||
pub num_layers: usize,
|
||||
/// The dropout rate.
|
||||
#[config(default = 0.5)]
|
||||
pub dropout: f64,
|
||||
/// The size of each layer.
|
||||
#[config(default = 256)]
|
||||
pub d_model: usize,
|
||||
}
|
||||
|
||||
/// Multilayer Perceptron module.
|
||||
#[derive(Module, Debug)]
|
||||
pub struct Mlp<B: Backend> {
|
||||
linears: Vec<nn::Linear<B>>,
|
||||
dropout: nn::Dropout,
|
||||
activation: nn::Relu,
|
||||
}
|
||||
|
||||
impl<B: Backend> Mlp<B> {
|
||||
/// Create the module from the given configuration.
|
||||
pub fn new(config: &MlpConfig, device: &B::Device) -> Self {
|
||||
let mut linears = Vec::with_capacity(config.num_layers);
|
||||
|
||||
for _ in 0..config.num_layers {
|
||||
linears.push(nn::LinearConfig::new(config.d_model, config.d_model).init(device));
|
||||
}
|
||||
|
||||
Self {
|
||||
linears,
|
||||
dropout: nn::DropoutConfig::new(0.3).init(),
|
||||
activation: nn::Relu::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the forward pass on the input tensor.
|
||||
///
|
||||
/// # Shapes
|
||||
///
|
||||
/// - input: `[batch_size, d_model]`
|
||||
/// - output: `[batch_size, d_model]`
|
||||
pub fn forward(&self, input: Tensor<B, 2>) -> Tensor<B, 2> {
|
||||
let mut x = input;
|
||||
|
||||
for linear in self.linears.iter() {
|
||||
x = linear.forward(x);
|
||||
x = self.dropout.forward(x);
|
||||
x = self.activation.forward(x);
|
||||
}
|
||||
|
||||
x
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Originally copied from the burn/examples/mnist package
|
||||
|
||||
use crate::{
|
||||
conv::{ConvBlock, ConvBlockConfig},
|
||||
mlp::{Mlp, MlpConfig},
|
||||
};
|
||||
|
||||
use burn::{
|
||||
config::Config,
|
||||
module::Module,
|
||||
nn,
|
||||
tensor::{Tensor, backend::Backend},
|
||||
};
|
||||
|
||||
#[derive(Config, Debug)]
|
||||
pub struct MnistConfig {
|
||||
#[config(default = 42)]
|
||||
pub seed: u64,
|
||||
|
||||
pub mlp: MlpConfig,
|
||||
|
||||
#[config(default = 784)]
|
||||
pub input_size: usize,
|
||||
|
||||
#[config(default = 10)]
|
||||
pub output_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Module, Debug)]
|
||||
pub struct Model<B: Backend> {
|
||||
mlp: Mlp<B>,
|
||||
conv: ConvBlock<B>,
|
||||
input: nn::Linear<B>,
|
||||
output: nn::Linear<B>,
|
||||
num_classes: usize,
|
||||
}
|
||||
|
||||
impl<B: Backend> Model<B> {
|
||||
pub fn new(config: &MnistConfig, device: &B::Device) -> Self {
|
||||
let mlp = Mlp::new(&config.mlp, device);
|
||||
let input = nn::LinearConfig::new(config.input_size, config.mlp.d_model).init(device);
|
||||
let output = nn::LinearConfig::new(config.mlp.d_model, config.output_size).init(device);
|
||||
let conv = ConvBlock::new(&ConvBlockConfig::new([1, 1]), device);
|
||||
|
||||
Self {
|
||||
mlp,
|
||||
conv,
|
||||
output,
|
||||
input,
|
||||
num_classes: config.output_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward(&self, input: Tensor<B, 3>) -> Tensor<B, 2> {
|
||||
let [batch_size, height, width] = input.dims();
|
||||
|
||||
let x = input.reshape([batch_size, 1, height, width]).detach();
|
||||
let x = self.conv.forward(x);
|
||||
let x = x.reshape([batch_size, height * width]);
|
||||
|
||||
let x = self.input.forward(x);
|
||||
let x = self.mlp.forward(x);
|
||||
|
||||
self.output.forward(x)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Test SafeTensors storage in no-std environment
|
||||
|
||||
use burn::{
|
||||
module::Module,
|
||||
nn,
|
||||
tensor::{Tensor, backend::Backend},
|
||||
};
|
||||
|
||||
use burn_store::{ModuleSnapshot, SafetensorsStore};
|
||||
|
||||
/// Simple model for testing SafeTensors storage
|
||||
#[derive(Module, Debug)]
|
||||
pub struct TestModel<B: Backend> {
|
||||
linear1: nn::Linear<B>,
|
||||
linear2: nn::Linear<B>,
|
||||
}
|
||||
|
||||
impl<B: Backend> TestModel<B> {
|
||||
pub fn new(device: &B::Device) -> Self {
|
||||
Self {
|
||||
linear1: nn::LinearConfig::new(10, 20).init(device),
|
||||
linear2: nn::LinearConfig::new(20, 10).init(device),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward(&self, x: Tensor<B, 2>) -> Tensor<B, 2> {
|
||||
let x = self.linear1.forward(x);
|
||||
self.linear2.forward(x)
|
||||
}
|
||||
}
|
||||
|
||||
/// Test basic SafeTensors save and load in no-std
|
||||
pub fn test_safetensors_basic<B: Backend>(device: &B::Device) {
|
||||
// Create a model
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save to bytes (no file I/O in no-std)
|
||||
let mut save_store = SafetensorsStore::from_bytes(None);
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save model");
|
||||
|
||||
// Get the serialized bytes
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load from bytes
|
||||
let mut load_store = SafetensorsStore::from_bytes(Some(bytes));
|
||||
let mut loaded_model = TestModel::<B>::new(device);
|
||||
loaded_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load model");
|
||||
|
||||
// Test that the model still works
|
||||
let input = Tensor::<B, 2>::ones([2, 10], device);
|
||||
let _output = loaded_model.forward(input);
|
||||
}
|
||||
|
||||
/// Test SafeTensors with filtering in no-std
|
||||
pub fn test_safetensors_filtering<B: Backend>(device: &B::Device) {
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save only linear1 weights
|
||||
let mut save_store = SafetensorsStore::from_bytes(None)
|
||||
.with_full_path("linear1.weight")
|
||||
.with_full_path("linear1.bias");
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save filtered model");
|
||||
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load with partial loading allowed
|
||||
let mut load_store = SafetensorsStore::from_bytes(Some(bytes)).allow_partial(true);
|
||||
let mut partial_model = TestModel::<B>::new(device);
|
||||
let result = partial_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load partial model");
|
||||
|
||||
// Verify that only linear1 was loaded
|
||||
assert_eq!(result.applied.len(), 2, "Should have loaded 2 tensors");
|
||||
assert!(!result.missing.is_empty(), "Should have missing tensors");
|
||||
}
|
||||
|
||||
/// Test SafeTensors with metadata in no-std
|
||||
pub fn test_safetensors_metadata<B: Backend>(device: &B::Device) {
|
||||
let model = TestModel::<B>::new(device);
|
||||
|
||||
// Save with metadata
|
||||
let mut save_store = SafetensorsStore::from_bytes(None)
|
||||
.metadata("version", "1.0.0")
|
||||
.metadata("environment", "no-std");
|
||||
model
|
||||
.save_into(&mut save_store)
|
||||
.expect("Failed to save model with metadata");
|
||||
|
||||
let bytes = save_store.get_bytes().expect("Failed to get bytes");
|
||||
|
||||
// Load and verify it works
|
||||
let mut load_store = SafetensorsStore::from_bytes(Some(bytes));
|
||||
let mut loaded_model = TestModel::<B>::new(device);
|
||||
loaded_model
|
||||
.load_from(&mut load_store)
|
||||
.expect("Failed to load model with metadata");
|
||||
}
|
||||
|
||||
/// Run all SafeTensors no-std tests
|
||||
pub fn run_all_tests<B: Backend>(device: &B::Device) {
|
||||
test_safetensors_basic::<B>(device);
|
||||
test_safetensors_filtering::<B>(device);
|
||||
test_safetensors_metadata::<B>(device);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
extern crate alloc;
|
||||
|
||||
#[test]
|
||||
fn test_burnpack_no_std() {
|
||||
use burn_ndarray::NdArray;
|
||||
use burn_no_std_tests::burnpack;
|
||||
type Backend = NdArray<f32>;
|
||||
let device = Default::default();
|
||||
|
||||
// Run all Burnpack tests
|
||||
burnpack::run_all_tests::<Backend>(&device);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
extern crate alloc;
|
||||
|
||||
#[test]
|
||||
fn test_safetensors_no_std() {
|
||||
use burn_ndarray::NdArray;
|
||||
use burn_no_std_tests::safetensors;
|
||||
type Backend = NdArray<f32>;
|
||||
let device = Default::default();
|
||||
|
||||
// Run all SafeTensors tests
|
||||
safetensors::run_all_tests::<Backend>(&device);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#![no_std] // Must keep it for testing
|
||||
|
||||
use burn_no_std_tests::mlp::*;
|
||||
use burn_no_std_tests::model::*;
|
||||
|
||||
use burn::tensor::{Distribution, Tensor, backend::Backend};
|
||||
use burn_ndarray::NdArray;
|
||||
|
||||
#[test]
|
||||
fn test_mnist_model_with_random_input() {
|
||||
type Backend = NdArray<f32>;
|
||||
|
||||
// Model configurations
|
||||
let device = Default::default();
|
||||
let mlp_config = MlpConfig::new();
|
||||
let mnist_config = MnistConfig::new(mlp_config);
|
||||
let mnist_model: Model<Backend> = Model::new(&mnist_config, &device);
|
||||
|
||||
// Pass a fixed seed for random, otherwise a build generated random seed is used
|
||||
Backend::seed(&device, mnist_config.seed);
|
||||
|
||||
// Some random input
|
||||
let input_shape = [1, 28, 28];
|
||||
let input = Tensor::<Backend, 3>::random(input_shape, Distribution::Default, &device);
|
||||
|
||||
// Run through the model
|
||||
let output = mnist_model.forward(input);
|
||||
|
||||
assert_eq!(&*output.shape(), [1, 10]);
|
||||
assert!(output.to_data().iter::<f32>().all(|x| x <= 1.0));
|
||||
}
|
||||
Reference in New Issue
Block a user