Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions infini_train/include/core/cpu_generator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <cstdint>
#include <memory>
#include <random>
#include <vector>

#include "infini_train/include/core/generator.h"
#include "infini_train/include/device.h"

namespace infini_train::core {

/**
* CPU backend Generator.
* State organization: the random sequence is driven by a std::mt19937_64
* engine. GetState() serializes a self-describing blob containing a backend
* magic, the current seed and the full engine state, allowing SetState() to
* resume the exact same sequence and to reject states coming from other
* backends.
*/
class CPUGeneratorImpl : public GeneratorImpl {
public:
static constexpr Device::DeviceType kDeviceType = Device::DeviceType::kCPU;

explicit CPUGeneratorImpl(uint64_t seed = kDefaultSeed);

void SetCurrentSeed(uint64_t seed) override;
uint64_t CurrentSeed() const override;
uint64_t Seed() override;

// CPU mt19937 engine is not counter based; only offset 0 is supported.
void SetOffset(uint64_t offset) override;
uint64_t GetOffset() const override;

std::vector<uint8_t> GetState() const override;
void SetState(const std::vector<uint8_t> &state) override;

Device GetDevice() const override;
std::shared_ptr<GeneratorImpl> Clone() const override;

// --- Internal kernel extension interface ----------------------------
// The methods below expose backend engine details for use by random
// operators (e.g. init::Uniform). They are NOT part of the user-facing
// Generator API and should not be called from outside the kernel layer.
//
// Direct access to the underlying engine for random operators. Each draw
// advances the engine, i.e. advances the generator state.
std::mt19937_64 &Engine();

private:
static constexpr uint64_t kDefaultSeed = 67280421310721ULL;

uint64_t seed_ = kDefaultSeed;
std::mt19937_64 engine_;
};

} // namespace infini_train::core
63 changes: 63 additions & 0 deletions infini_train/include/core/cuda_generator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <cstdint>
#include <memory>
#include <utility>
#include <vector>

#include "infini_train/include/core/generator.h"
#include "infini_train/include/device.h"

namespace infini_train::core {

/**
* CUDA backend Generator.
*
* Unlike the CPU backend, CUDA random kernels are counter (Philox) based. The
* host side keeps only a lightweight state: the seed and a 64-bit Philox
* offset. Random kernels read the (seed, offset) pair and advance the offset by
* the number of random values they consumed, so that subsequent kernel launches
* do not overlap the random sequence.
*
* Each CUDA device maintains its own independent generator (see
* detail::DefaultCUDAGenerator).
*/
class CUDAGeneratorImpl : public GeneratorImpl {
public:
static constexpr Device::DeviceType kDeviceType = Device::DeviceType::kCUDA;

explicit CUDAGeneratorImpl(int8_t device_index = 0, uint64_t seed = kDefaultSeed);

void SetCurrentSeed(uint64_t seed) override;
uint64_t CurrentSeed() const override;
uint64_t Seed() override;

void SetOffset(uint64_t offset) override;
uint64_t GetOffset() const override;

std::vector<uint8_t> GetState() const override;
void SetState(const std::vector<uint8_t> &state) override;

Device GetDevice() const override;
std::shared_ptr<GeneratorImpl> Clone() const override;

// --- Internal kernel extension interface ----------------------------
// The methods below expose Philox counter details for use by random
// operators (e.g. init::Uniform on CUDA tensors). They are NOT part of
// the user-facing Generator API and should not be called from outside the
// kernel layer.
//
// Reserves `increment` Philox values for the next kernel launch and returns
// the (seed, offset) pair the kernel must use. The offset is advanced so the
// next call yields a non-overlapping subsequence.
std::pair<uint64_t, uint64_t> PhiloxEngineInputs(uint64_t increment);

private:
static constexpr uint64_t kDefaultSeed = 67280421310721ULL;

int8_t device_index_ = 0;
uint64_t seed_ = kDefaultSeed;
uint64_t philox_offset_ = 0;
};

} // namespace infini_train::core
132 changes: 132 additions & 0 deletions infini_train/include/core/distribution.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#pragma once

// distribution.h — Minimal distribution template infrastructure.
//
// Mirrors PyTorch's DistributionTemplates.h / cpu_DistributionTemplates.h /
// cuda_DistributionTemplates.h three-layer design, simplified for InfiniTrain's
// current needs:
//
// 1. distribution_impl — parameter validation + backend dispatch
// 2. cpu::*_kernel — CPU backend: locks generator, fills elements
// 3. cuda::*_kernel — CUDA backend: host-side fallback for now
//
// The key improvement over the old FillBuffer is:
// - Distribution logic is decoupled from init.cc consumers
// - Generator resolution (explicit vs default) follows PyTorch's
// check_generator / GetGeneratorOrDefault pattern
// - Each backend owns its locking strategy
// - Ready for future device-kernel extension on CUDA

#include <cstdint>
#include <mutex>
#include <optional>
#include <random>
#include <vector>

#include "infini_train/include/core/cpu_generator.h"
#include "infini_train/include/core/generator.h"
#ifdef USE_CUDA
#include "infini_train/include/core/cuda_generator.h"
#endif
#include "infini_train/include/device.h"

namespace infini_train::core::distribution {

// ============================== CPU backend ==============================

namespace cpu {

// Fills `buffer` with values drawn from `dist`, using the CPU generator's
// engine directly. The generator mutex is held for the entire draw loop
// because Engine() returns a reference to the generator's internal state.
//
// Corresponds to PyTorch's cpu::uniform_kernel / cpu::normal_kernel pattern,
// simplified to operate on a flat float buffer instead of a TensorIterator.
template <typename Dist> void FillKernel(float *data, int64_t numel, CPUGeneratorImpl *impl, Dist dist) {
std::lock_guard<std::mutex> lock(impl->mutex_);
auto &engine = impl->Engine();
for (int64_t i = 0; i < numel; ++i) { data[i] = dist(engine); }
}

} // namespace cpu

// ============================== CUDA backend =============================

#ifdef USE_CUDA
namespace cuda {

// Host-side fallback: generates random values on the host using a temporary
// mt19937_64 engine derived from the CUDA generator's (seed, offset) pair,
// then the caller copies the result to the device.
//
// A future device-kernel path will generate values directly on the GPU via
// Philox (mirroring PyTorch's distribution_nullary_kernel).
//
// Locking strategy matches PyTorch: only the offset reservation (PhiloxEngine-
// Inputs) touches impl state; the subsequent host-side generation uses purely
// local variables and runs outside the impl lock to reduce contention.
template <typename Dist> void FillKernel(float *data, int64_t numel, CUDAGeneratorImpl *impl, Dist dist) {
uint64_t seed, offset;
{
std::lock_guard<std::mutex> lock(impl->mutex_);
std::tie(seed, offset) = impl->PhiloxEngineInputs(static_cast<uint64_t>(numel));
}
// Mix a backend-specific constant so that the derived engine produces a
// sequence distinct from the CPU generator even when seed and offset
// values coincide (e.g. both backends seeded identically via ManualSeed).
constexpr uint64_t kCUDABackendTag = 0xD2511F53D9E08B47ULL;
std::mt19937_64 engine(seed ^ ((offset + kCUDABackendTag) * 0x9E3779B97F4A7C15ULL));
for (int64_t i = 0; i < numel; ++i) { data[i] = dist(engine); }
}

} // namespace cuda
#endif // USE_CUDA

// ======================== Distribution dispatch ==========================
//
// These mirror PyTorch's DistributionTemplates.h top-level functions
// (normal_impl_, uniform_impl_, etc.) which validate parameters, resolve the
// generator, and dispatch to the correct backend kernel.

// Fills `buffer` by drawing from the given distribution, using the resolved
// generator for the target device. Replaces the old FillBuffer in init.cc.
//
// Flow:
// 1. Resolve generator (explicit or device default)
// 2. Dispatch to cpu::FillKernel or cuda::FillKernel
// 3. Backend kernel handles locking and element-wise generation
template <typename Dist>
void FillBuffer(std::vector<float> &buffer, const Device &device, const std::optional<Generator> &generator,
Dist dist) {
auto *data = buffer.data();
const auto numel = static_cast<int64_t>(buffer.size());

if (device.IsCPU()) {
auto *impl = GetGeneratorOrDefault<CPUGeneratorImpl>(generator, device);
cpu::FillKernel(data, numel, impl, dist);
return;
}

#ifdef USE_CUDA
auto *impl = GetGeneratorOrDefault<CUDAGeneratorImpl>(generator, device);
cuda::FillKernel(data, numel, impl, dist);
#else
LOG(FATAL) << "Random operator on non-CPU device requires CUDA support";
#endif
}

// Convenience wrappers matching PyTorch's named distribution templates.

// uniform_impl_: fills buffer with values from [from, to).
inline void UniformFill(std::vector<float> &buffer, const Device &device, const std::optional<Generator> &generator,
float from, float to) {
FillBuffer(buffer, device, generator, std::uniform_real_distribution<float>(from, to));
}

// normal_impl_: fills buffer with values from N(mean, std).
inline void NormalFill(std::vector<float> &buffer, const Device &device, const std::optional<Generator> &generator,
float mean, float std) {
FillBuffer(buffer, device, generator, std::normal_distribution<float>(mean, std));
}

} // namespace infini_train::core::distribution
Loading
Loading