diff --git a/python/bindings.cpp b/python/bindings.cpp index 7d77d8a..a89a4db 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -91,6 +91,15 @@ void bind_tensor_creation_functions(pybind11::module &m) { bind_tensor_creation_overloads(m, "zeros", ZerosFunctor{}); bind_tensor_creation_overloads(m, "ones", OnesFunctor{}); bind_tensor_creation_overloads(m, "empty", EmptyFunctor{}); + + m.def("full_like", &smollnet::full_like, "tensor"_a, "value"_a, + "requires_grad"_a = false); + m.def("zeros_like", &smollnet::zeros_like, "tensor"_a, + "requires_grad"_a = false); + m.def("ones_like", &smollnet::ones_like, "tensor"_a, + "requires_grad"_a = false); + m.def("rand_like", &smollnet::rand_like, "tensor"_a, + "requires_grad"_a = false); } // Single module definition diff --git a/src/tensor.cpp b/src/tensor.cpp index 2c22175..17405be 100644 --- a/src/tensor.cpp +++ b/src/tensor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -14,6 +15,16 @@ namespace smollnet { +namespace { + +std::mt19937 &cpu_random_generator() { + // NOLINTNEXTLINE + static std::mt19937 generator(1234U); + return generator; +} + +} // namespace + template void SetupAutograd(const Tensor &l, const Tensor &r, const Tensor &n) { if (n.requires_grad()) { @@ -34,8 +45,9 @@ void SetupAutograd(const Tensor &n, const Tensor &other) { } } -Tensor full_like(const Tensor &t, float value) { - Tensor out = empty(t.dims().data(), t.ndims(), t.dtype(), t.device()); +Tensor full_like(const Tensor &t, float value, bool requires_grad) { + Tensor out = empty(t.dims().data(), t.ndims(), t.dtype(), t.device(), + requires_grad); if (t.device() == Device::CUDA) { launch_fill(static_cast(out.data()), out.numel(), value); } else { @@ -862,12 +874,17 @@ Tensor ones(const int64_t *dims, size_t rank, DataType t, Device d, bool requires_grad) { auto tensor = empty(dims, rank, t, d, requires_grad); - launch_fill(static_cast(tensor.data()), tensor.numel(), 1.0f); + if (d == Device::CUDA) { + launch_fill(static_cast(tensor.data()), tensor.numel(), 1.0f); + } else { + std::fill_n(static_cast(tensor.data()), tensor.numel(), 1.0f); + } return Tensor{tensor}; } void manual_seed(unsigned long long seed) { + cpu_random_generator().seed(static_cast(seed)); launch_random_init(seed); } @@ -875,9 +892,31 @@ Tensor rand(const int64_t *dims, size_t rank, DataType t, Device d, bool requires_grad) { auto tensor = empty(dims, rank, t, d, requires_grad); - launch_random_fill(tensor.data(), tensor.numel()); + if (d == Device::CUDA) { + launch_random_fill(tensor.data(), tensor.numel()); + } else { + auto *data = static_cast(tensor.data()); + std::uniform_real_distribution dist(0.0f, 1.0f); + auto &generator = cpu_random_generator(); + for (size_t i = 0; i < tensor.numel(); ++i) { + data[i] = dist(generator); + } + } return Tensor{tensor}; } +Tensor zeros_like(const Tensor &t, bool requires_grad) { + return zeros(t.dims().data(), t.ndims(), t.dtype(), t.device(), + requires_grad); +} + +Tensor ones_like(const Tensor &t, bool requires_grad) { + return full_like(t, 1.0f, requires_grad); +} + +Tensor rand_like(const Tensor &t, bool requires_grad) { + return rand(t.dims().data(), t.ndims(), t.dtype(), t.device(), requires_grad); +} + } // namespace smollnet diff --git a/src/tensor.hpp b/src/tensor.hpp index db70f2c..f95df1b 100644 --- a/src/tensor.hpp +++ b/src/tensor.hpp @@ -156,6 +156,10 @@ Tensor ones(const int64_t *dims, size_t rank, DataType t, Device d, bool requires_grad = false); Tensor rand(const int64_t *dims, size_t rank, DataType t, Device d, bool requires_grad = false); +Tensor full_like(const Tensor &t, float value, bool requires_grad = false); +Tensor zeros_like(const Tensor &t, bool requires_grad = false); +Tensor ones_like(const Tensor &t, bool requires_grad = false); +Tensor rand_like(const Tensor &t, bool requires_grad = false); void manual_seed(unsigned long long seed); template diff --git a/utils/tests.py b/utils/tests.py index 8faa238..2a32c79 100644 --- a/utils/tests.py +++ b/utils/tests.py @@ -32,6 +32,27 @@ def test_tensor_creation(): print(f"2D ones shape: {t2d_ones.dims()}") print(f"2D rand shape: {t2d_rand.dims()}") + print("\nCreating like tensors...") + t2d_full_like = smollnet.full_like(t2d_rand, 3.0) + t2d_zeros_like = smollnet.zeros_like(t2d_rand) + t2d_ones_like = smollnet.ones_like(t2d_rand, requires_grad=True) + t2d_rand_like = smollnet.rand_like(t2d_rand) + + assert t2d_full_like.dims() == t2d_rand.dims() + assert t2d_zeros_like.dims() == t2d_rand.dims() + assert t2d_ones_like.dims() == t2d_rand.dims() + assert t2d_rand_like.dims() == t2d_rand.dims() + assert t2d_full_like.dtype() == t2d_rand.dtype() + assert t2d_zeros_like.dtype() == t2d_rand.dtype() + assert t2d_ones_like.device() == t2d_rand.device() + assert not t2d_zeros_like.requires_grad() + assert t2d_ones_like.requires_grad() + + print(f"2D full_like shape: {t2d_full_like.dims()}") + print(f"2D zeros_like shape: {t2d_zeros_like.dims()}") + print(f"2D ones_like shape: {t2d_ones_like.dims()}") + print(f"2D rand_like shape: {t2d_rand_like.dims()}") + # Test 3D tensors print("\nCreating 3D tensors...") t3d_zeros = smollnet.zeros(2, 3, 4) @@ -52,6 +73,30 @@ def test_tensor_creation(): print("✓ Tensor creation tests passed!\n") +def test_manual_seed(): + """Test that manual_seed changes and resets CPU random state.""" + print("=== Testing Manual Seed ===") + + reference = smollnet.zeros(2, 3, device=smollnet.Device.CPU) + + smollnet.manual_seed(2025) + first = smollnet.rand_like(reference) + second = smollnet.rand_like(reference) + + smollnet.manual_seed(2025) + first_replay = smollnet.rand_like(reference) + second_replay = smollnet.rand_like(reference) + + assert repr(first) == repr(first_replay) + assert repr(second) == repr(second_replay) + + smollnet.manual_seed(2026) + different_seed = smollnet.rand_like(reference) + + assert repr(first) != repr(different_seed) + + print("✓ Manual seed tests passed!\n") + def test_tensor_operations(): """Test basic tensor operations.""" print("=== Testing Tensor Operations ===") @@ -312,6 +357,7 @@ def main(): try: test_tensor_creation() + test_manual_seed() test_tensor_operations() test_activation_functions() test_neural_network_layers()