trainstream 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trainstream/__init__.py +62 -0
- trainstream/_build.py +201 -0
- trainstream/csrc/bindings.cpp +139 -0
- trainstream/csrc/kernels.h +49 -0
- trainstream/csrc/memory_pinner.cpp +680 -0
- trainstream/csrc/offload_adamw.cpp +712 -0
- trainstream/csrc/offload_adamw.h +154 -0
- trainstream/csrc/offload_adamw_kernel.cu +183 -0
- trainstream/csrc/trainstream.h +132 -0
- trainstream/hooks.py +547 -0
- trainstream/integrations/__init__.py +7 -0
- trainstream/integrations/huggingface.py +211 -0
- trainstream/optimizer.py +333 -0
- trainstream/profiler.py +166 -0
- trainstream/wrapper.py +191 -0
- trainstream-0.1.1.dist-info/METADATA +127 -0
- trainstream-0.1.1.dist-info/RECORD +21 -0
- trainstream-0.1.1.dist-info/WHEEL +5 -0
- trainstream-0.1.1.dist-info/licenses/LICENSE +202 -0
- trainstream-0.1.1.dist-info/licenses/NOTICE +4 -0
- trainstream-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// TrainStream — offloaded AdamW engine.
|
|
2
|
+
//
|
|
3
|
+
// Two execution modes share one bookkeeping model ("slots", one per parameter):
|
|
4
|
+
//
|
|
5
|
+
// * "cpu": gradients are streamed D2H as soon as autograd produces them; host
|
|
6
|
+
// worker threads accumulate them into fp32 buffers, run AdamW on fp32 master
|
|
7
|
+
// weights held in pinned RAM, and stream the updated weights back H2D on a
|
|
8
|
+
// dedicated copy stream. step() returns immediately; the next forward pass
|
|
9
|
+
// waits per-module (wait_ready) so layer 0 can start computing while the
|
|
10
|
+
// CPU is still updating layer N.
|
|
11
|
+
//
|
|
12
|
+
// * "gpu": gradients stay in VRAM; a CUDA kernel updates the parameter in
|
|
13
|
+
// place while reading/writing the moments through mapped pinned memory
|
|
14
|
+
// (zero-copy over PCIe). Moments never occupy VRAM.
|
|
15
|
+
#pragma once
|
|
16
|
+
|
|
17
|
+
#include "trainstream.h"
|
|
18
|
+
|
|
19
|
+
#include <condition_variable>
|
|
20
|
+
#include <functional>
|
|
21
|
+
#include <mutex>
|
|
22
|
+
#include <thread>
|
|
23
|
+
#include <deque>
|
|
24
|
+
|
|
25
|
+
namespace trainstream {
|
|
26
|
+
|
|
27
|
+
class WorkerPool {
|
|
28
|
+
public:
|
|
29
|
+
WorkerPool(int threads, int device);
|
|
30
|
+
~WorkerPool();
|
|
31
|
+
int size() const { return static_cast<int>(workers_.size()); }
|
|
32
|
+
// Jobs submitted to the same worker run in FIFO order.
|
|
33
|
+
void submit(size_t worker, std::function<void()> fn);
|
|
34
|
+
|
|
35
|
+
private:
|
|
36
|
+
struct Worker {
|
|
37
|
+
std::thread thread;
|
|
38
|
+
std::mutex mu;
|
|
39
|
+
std::condition_variable cv;
|
|
40
|
+
std::deque<std::function<void()>> queue;
|
|
41
|
+
bool stop = false;
|
|
42
|
+
};
|
|
43
|
+
std::vector<std::unique_ptr<Worker>> workers_;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
class OffloadAdamW {
|
|
47
|
+
public:
|
|
48
|
+
OffloadAdamW(int device, const std::string& mode, int threads, int64_t chunk_elems);
|
|
49
|
+
~OffloadAdamW();
|
|
50
|
+
|
|
51
|
+
// pin_moments=false keeps exp_avg/exp_avg_sq in ordinary RAM (cpu mode only).
|
|
52
|
+
int64_t add_param(const at::Tensor& param, bool pin_moments = true);
|
|
53
|
+
|
|
54
|
+
// cpu mode: stream one gradient to host and queue its accumulation. With
|
|
55
|
+
// apply_update the AdamW update for this tensor is queued right behind it
|
|
56
|
+
// ("eager" / optimizer-in-backward), overlapping host math with the rest of
|
|
57
|
+
// backward; the H2D upload is ordered after every kernel queued so far.
|
|
58
|
+
void offload_grad(int64_t id, const at::Tensor& grad, bool apply_update = false, double lr = 0.0,
|
|
59
|
+
double beta1 = 0.9, double beta2 = 0.999, double eps = 1e-8, double weight_decay = 0.0);
|
|
60
|
+
|
|
61
|
+
// Queue one optimizer step for `ids` (in the order the next forward needs
|
|
62
|
+
// them). `grads` is used by gpu mode only. Returns the global grad norm when
|
|
63
|
+
// it was computed on the host (cpu mode with clipping / compute_norm), else -1.
|
|
64
|
+
double step(const std::vector<int64_t>& ids, const std::vector<double>& lrs,
|
|
65
|
+
const std::vector<double>& beta1s, const std::vector<double>& beta2s,
|
|
66
|
+
const std::vector<double>& epss, const std::vector<double>& wds,
|
|
67
|
+
const std::vector<at::Tensor>& grads, double max_grad_norm, bool compute_norm);
|
|
68
|
+
|
|
69
|
+
// Host-waits until each slot's update has been *issued*, then makes the
|
|
70
|
+
// current compute stream wait (on the GPU) for the H2D / kernel to land.
|
|
71
|
+
void wait_ready(const std::vector<int64_t>& ids);
|
|
72
|
+
void synchronize();
|
|
73
|
+
|
|
74
|
+
double last_grad_norm();
|
|
75
|
+
std::tuple<at::Tensor, at::Tensor, at::Tensor> state(int64_t id); // master|undef, m, v
|
|
76
|
+
void load_state(int64_t id, const at::Tensor& exp_avg, const at::Tensor& exp_avg_sq,
|
|
77
|
+
const at::Tensor& master, int64_t step);
|
|
78
|
+
int64_t get_step(int64_t id);
|
|
79
|
+
std::map<std::string, double> stats();
|
|
80
|
+
std::string mode() const { return gpu_mode_ ? "gpu" : "cpu"; }
|
|
81
|
+
int64_t num_params() const { return static_cast<int64_t>(slots_.size()); }
|
|
82
|
+
int threads() const { return pool_ ? pool_->size() : 0; }
|
|
83
|
+
|
|
84
|
+
private:
|
|
85
|
+
struct Slot {
|
|
86
|
+
int64_t id = 0;
|
|
87
|
+
at::Tensor param; // flat alias of the user's parameter (shares storage)
|
|
88
|
+
int64_t numel = 0;
|
|
89
|
+
at::ScalarType pdtype = at::kFloat;
|
|
90
|
+
at::Tensor master, exp_avg, exp_avg_sq; // fp32, pinned host
|
|
91
|
+
float* d_master = nullptr; // device aliases (gpu mode)
|
|
92
|
+
float* d_m = nullptr;
|
|
93
|
+
float* d_v = nullptr;
|
|
94
|
+
at::Tensor grad_stage, grad_acc, param_stage; // cpu mode staging
|
|
95
|
+
at::Tensor grad_keep; // VRAM gradient kept alive until its DMA / kernel is done
|
|
96
|
+
int64_t step = 0;
|
|
97
|
+
bool has_grad = false;
|
|
98
|
+
bool acc_fresh = true;
|
|
99
|
+
EventPtr d2h_done;
|
|
100
|
+
EventPtr ready;
|
|
101
|
+
std::mutex mu;
|
|
102
|
+
std::condition_variable cv;
|
|
103
|
+
int inflight = 0;
|
|
104
|
+
int64_t nchunks = 1;
|
|
105
|
+
};
|
|
106
|
+
using ChunkFn = std::function<void(int64_t chunk, int64_t begin, int64_t end)>;
|
|
107
|
+
|
|
108
|
+
Slot& slot(int64_t id);
|
|
109
|
+
size_t worker_for(int64_t id, int64_t chunk) const;
|
|
110
|
+
void run_batch(Slot& s, std::shared_ptr<ChunkFn> body, std::shared_ptr<std::function<void()>> finalize);
|
|
111
|
+
void wait_idle(Slot& s);
|
|
112
|
+
// Queue AdamW + H2D for one slot. Reads the fp32 accumulator, or the raw
|
|
113
|
+
// staging buffer once `from_stage` (its D2H event) has completed.
|
|
114
|
+
void enqueue_update(Slot& s, double lr, double beta1, double beta2, double eps, double wd, double clip,
|
|
115
|
+
EventPtr after, EventPtr from_stage);
|
|
116
|
+
void record_error(const std::string& msg);
|
|
117
|
+
void check_error();
|
|
118
|
+
double step_cpu(const std::vector<int64_t>& ids, const std::vector<double>& lrs,
|
|
119
|
+
const std::vector<double>& beta1s, const std::vector<double>& beta2s,
|
|
120
|
+
const std::vector<double>& epss, const std::vector<double>& wds,
|
|
121
|
+
double max_grad_norm, bool compute_norm);
|
|
122
|
+
void step_gpu(const std::vector<int64_t>& ids, const std::vector<double>& lrs,
|
|
123
|
+
const std::vector<double>& beta1s, const std::vector<double>& beta2s,
|
|
124
|
+
const std::vector<double>& epss, const std::vector<double>& wds,
|
|
125
|
+
const std::vector<at::Tensor>& grads, double max_grad_norm, bool compute_norm);
|
|
126
|
+
|
|
127
|
+
int device_;
|
|
128
|
+
bool gpu_mode_;
|
|
129
|
+
int64_t chunk_;
|
|
130
|
+
std::unique_ptr<WorkerPool> pool_;
|
|
131
|
+
std::vector<std::unique_ptr<Slot>> slots_;
|
|
132
|
+
|
|
133
|
+
std::mutex err_mu_;
|
|
134
|
+
std::string error_;
|
|
135
|
+
|
|
136
|
+
// gpu-mode global-norm scratch
|
|
137
|
+
float* norm_dev_ = nullptr;
|
|
138
|
+
at::Tensor norm_host_;
|
|
139
|
+
EventPtr norm_ev_;
|
|
140
|
+
double last_norm_ = -1.0;
|
|
141
|
+
|
|
142
|
+
// telemetry (microseconds / bytes)
|
|
143
|
+
std::atomic<int64_t> update_us_{0}, accumulate_us_{0}, ready_stall_us_{0}, grad_stall_us_{0};
|
|
144
|
+
std::atomic<int64_t> grad_bytes_{0}, param_bytes_{0}, steps_{0}, steps_eager_{0};
|
|
145
|
+
std::atomic<int64_t> host_state_bytes_{0}, pinned_state_bytes_{0}, pageable_state_bytes_{0};
|
|
146
|
+
std::atomic<double> step_start_ms_{0.0}, last_finalize_ms_{0.0};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Throughput of the host AdamW loop (elements / second) across `threads`.
|
|
150
|
+
std::map<std::string, double> benchmark_cpu_adamw(int64_t numel, int threads);
|
|
151
|
+
// Throughput of the zero-copy kernel with moments in mapped pinned memory.
|
|
152
|
+
std::map<std::string, double> benchmark_zero_copy_adamw(int device, int64_t numel);
|
|
153
|
+
|
|
154
|
+
} // namespace trainstream
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// TrainStream — CUDA kernels for the zero-copy offloaded AdamW path.
|
|
2
|
+
//
|
|
3
|
+
// The optimizer moments are never resident in VRAM: the kernel dereferences
|
|
4
|
+
// mapped pinned host pointers directly, so each warp-wide load becomes a PCIe
|
|
5
|
+
// read request and each store a posted PCIe write. The kernel is therefore
|
|
6
|
+
// PCIe-bound by design (~16-24 bytes of host traffic per parameter), and the
|
|
7
|
+
// main tuning lever is keeping enough independent requests in flight to cover
|
|
8
|
+
// the ~1-2 us PCIe round-trip latency. We do that with a 4-way unrolled
|
|
9
|
+
// "load everything first, then compute" body and a grid sized to fill every SM.
|
|
10
|
+
|
|
11
|
+
#include "kernels.h"
|
|
12
|
+
|
|
13
|
+
#include <cuda_bf16.h>
|
|
14
|
+
#include <cuda_fp16.h>
|
|
15
|
+
|
|
16
|
+
namespace trainstream {
|
|
17
|
+
namespace kernels {
|
|
18
|
+
namespace {
|
|
19
|
+
|
|
20
|
+
constexpr int kThreads = 256;
|
|
21
|
+
constexpr int kUnroll = 4;
|
|
22
|
+
constexpr int kBlocksPerSm = 16;
|
|
23
|
+
|
|
24
|
+
template <typename T>
|
|
25
|
+
struct Cvt;
|
|
26
|
+
|
|
27
|
+
template <>
|
|
28
|
+
struct Cvt<float> {
|
|
29
|
+
__device__ __forceinline__ static float load(float x) { return x; }
|
|
30
|
+
__device__ __forceinline__ static float store(float x) { return x; }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
template <>
|
|
34
|
+
struct Cvt<__half> {
|
|
35
|
+
__device__ __forceinline__ static float load(__half x) { return __half2float(x); }
|
|
36
|
+
__device__ __forceinline__ static __half store(float x) { return __float2half_rn(x); }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
template <>
|
|
40
|
+
struct Cvt<__nv_bfloat16> {
|
|
41
|
+
__device__ __forceinline__ static float load(__nv_bfloat16 x) { return __bfloat162float(x); }
|
|
42
|
+
__device__ __forceinline__ static __nv_bfloat16 store(float x) { return __float2bfloat16_rn(x); }
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
inline int grid_for(int64_t n, int per_block, int sm_count) {
|
|
46
|
+
int64_t blocks = (n + per_block - 1) / per_block;
|
|
47
|
+
const int64_t cap = static_cast<int64_t>(sm_count > 0 ? sm_count : 80) * kBlocksPerSm;
|
|
48
|
+
if (blocks > cap) blocks = cap;
|
|
49
|
+
if (blocks < 1) blocks = 1;
|
|
50
|
+
return static_cast<int>(blocks);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
template <typename T>
|
|
54
|
+
__global__ void __launch_bounds__(kThreads)
|
|
55
|
+
adamw_zero_copy_kernel(T* __restrict__ param, const T* __restrict__ grad,
|
|
56
|
+
float* __restrict__ exp_avg, float* __restrict__ exp_avg_sq,
|
|
57
|
+
float* __restrict__ master, int64_t n, AdamWParams hp,
|
|
58
|
+
const float* __restrict__ grad_sq_norm) {
|
|
59
|
+
float clip = 1.0f;
|
|
60
|
+
if (grad_sq_norm != nullptr) {
|
|
61
|
+
const float norm = sqrtf(*grad_sq_norm);
|
|
62
|
+
clip = fminf(1.0f, hp.max_grad_norm / (norm + 1e-6f));
|
|
63
|
+
}
|
|
64
|
+
const float decay = 1.0f - hp.lr * hp.weight_decay;
|
|
65
|
+
const float one_minus_b1 = 1.0f - hp.beta1;
|
|
66
|
+
const float one_minus_b2 = 1.0f - hp.beta2;
|
|
67
|
+
|
|
68
|
+
const int64_t tile = static_cast<int64_t>(kThreads) * kUnroll;
|
|
69
|
+
const int64_t stride = static_cast<int64_t>(gridDim.x) * tile;
|
|
70
|
+
|
|
71
|
+
for (int64_t base = static_cast<int64_t>(blockIdx.x) * tile + threadIdx.x; base < n;
|
|
72
|
+
base += stride) {
|
|
73
|
+
float g[kUnroll], m[kUnroll], v[kUnroll], p[kUnroll];
|
|
74
|
+
|
|
75
|
+
// Phase 1: issue every load for the unrolled lanes before consuming any of
|
|
76
|
+
// them, so up to 3*kUnroll host reads per thread are in flight together.
|
|
77
|
+
#pragma unroll
|
|
78
|
+
for (int u = 0; u < kUnroll; ++u) {
|
|
79
|
+
const int64_t i = base + static_cast<int64_t>(u) * kThreads;
|
|
80
|
+
if (i < n) {
|
|
81
|
+
g[u] = Cvt<T>::load(grad[i]) * clip;
|
|
82
|
+
m[u] = exp_avg[i];
|
|
83
|
+
v[u] = exp_avg_sq[i];
|
|
84
|
+
p[u] = master != nullptr ? master[i] : Cvt<T>::load(param[i]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Phase 2: AdamW math (matches torch.optim.AdamW, foreach=False semantics).
|
|
89
|
+
#pragma unroll
|
|
90
|
+
for (int u = 0; u < kUnroll; ++u) {
|
|
91
|
+
const int64_t i = base + static_cast<int64_t>(u) * kThreads;
|
|
92
|
+
if (i < n) {
|
|
93
|
+
float pu = p[u] * decay;
|
|
94
|
+
const float mu = m[u] + one_minus_b1 * (g[u] - m[u]); // lerp
|
|
95
|
+
const float vu = hp.beta2 * v[u] + one_minus_b2 * g[u] * g[u];
|
|
96
|
+
const float denom = sqrtf(vu) / hp.bias_correction2_sqrt + hp.eps;
|
|
97
|
+
pu -= hp.step_size * (mu / denom);
|
|
98
|
+
exp_avg[i] = mu;
|
|
99
|
+
exp_avg_sq[i] = vu;
|
|
100
|
+
if (master != nullptr) master[i] = pu;
|
|
101
|
+
param[i] = Cvt<T>::store(pu);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
template <typename T>
|
|
108
|
+
__global__ void __launch_bounds__(kThreads)
|
|
109
|
+
sq_norm_kernel(const T* __restrict__ grad, int64_t n, float* __restrict__ out) {
|
|
110
|
+
__shared__ float warp_sums[kThreads / 32];
|
|
111
|
+
float acc = 0.0f;
|
|
112
|
+
const int64_t stride = static_cast<int64_t>(gridDim.x) * kThreads;
|
|
113
|
+
for (int64_t i = static_cast<int64_t>(blockIdx.x) * kThreads + threadIdx.x; i < n; i += stride) {
|
|
114
|
+
const float x = Cvt<T>::load(grad[i]);
|
|
115
|
+
acc += x * x;
|
|
116
|
+
}
|
|
117
|
+
#pragma unroll
|
|
118
|
+
for (int off = 16; off > 0; off >>= 1) acc += __shfl_down_sync(0xffffffffu, acc, off);
|
|
119
|
+
if ((threadIdx.x & 31) == 0) warp_sums[threadIdx.x >> 5] = acc;
|
|
120
|
+
__syncthreads();
|
|
121
|
+
if (threadIdx.x < 32) {
|
|
122
|
+
acc = threadIdx.x < (kThreads / 32) ? warp_sums[threadIdx.x] : 0.0f;
|
|
123
|
+
#pragma unroll
|
|
124
|
+
for (int off = 16; off > 0; off >>= 1) acc += __shfl_down_sync(0xffffffffu, acc, off);
|
|
125
|
+
if (threadIdx.x == 0) atomicAdd(out, acc);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
template <typename T>
|
|
130
|
+
cudaError_t launch_adamw(void* param, const void* grad, float* m, float* v, float* master,
|
|
131
|
+
int64_t n, const AdamWParams& hp, const float* norm, int sm_count,
|
|
132
|
+
cudaStream_t stream) {
|
|
133
|
+
if (n == 0) return cudaSuccess;
|
|
134
|
+
const int grid = grid_for(n, kThreads * kUnroll, sm_count);
|
|
135
|
+
adamw_zero_copy_kernel<T><<<grid, kThreads, 0, stream>>>(
|
|
136
|
+
static_cast<T*>(param), static_cast<const T*>(grad), m, v, master, n, hp, norm);
|
|
137
|
+
return cudaGetLastError();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
template <typename T>
|
|
141
|
+
cudaError_t launch_sq_norm(const void* grad, int64_t n, float* out, int sm_count,
|
|
142
|
+
cudaStream_t stream) {
|
|
143
|
+
if (n == 0) return cudaSuccess;
|
|
144
|
+
const int grid = grid_for(n, kThreads, sm_count);
|
|
145
|
+
sq_norm_kernel<T><<<grid, kThreads, 0, stream>>>(static_cast<const T*>(grad), n, out);
|
|
146
|
+
return cudaGetLastError();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
} // namespace
|
|
150
|
+
|
|
151
|
+
cudaError_t adamw_zero_copy(DType dtype, void* param, const void* grad, float* exp_avg,
|
|
152
|
+
float* exp_avg_sq, float* master, int64_t n,
|
|
153
|
+
const AdamWParams& hp, const float* grad_sq_norm, int sm_count,
|
|
154
|
+
cudaStream_t stream) {
|
|
155
|
+
switch (dtype) {
|
|
156
|
+
case DType::kFloat32:
|
|
157
|
+
return launch_adamw<float>(param, grad, exp_avg, exp_avg_sq, master, n, hp, grad_sq_norm,
|
|
158
|
+
sm_count, stream);
|
|
159
|
+
case DType::kFloat16:
|
|
160
|
+
return launch_adamw<__half>(param, grad, exp_avg, exp_avg_sq, master, n, hp, grad_sq_norm,
|
|
161
|
+
sm_count, stream);
|
|
162
|
+
case DType::kBFloat16:
|
|
163
|
+
return launch_adamw<__nv_bfloat16>(param, grad, exp_avg, exp_avg_sq, master, n, hp,
|
|
164
|
+
grad_sq_norm, sm_count, stream);
|
|
165
|
+
}
|
|
166
|
+
return cudaErrorInvalidValue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
cudaError_t grad_sq_norm_accumulate(DType dtype, const void* grad, int64_t n, float* out,
|
|
170
|
+
int sm_count, cudaStream_t stream) {
|
|
171
|
+
switch (dtype) {
|
|
172
|
+
case DType::kFloat32:
|
|
173
|
+
return launch_sq_norm<float>(grad, n, out, sm_count, stream);
|
|
174
|
+
case DType::kFloat16:
|
|
175
|
+
return launch_sq_norm<__half>(grad, n, out, sm_count, stream);
|
|
176
|
+
case DType::kBFloat16:
|
|
177
|
+
return launch_sq_norm<__nv_bfloat16>(grad, n, out, sm_count, stream);
|
|
178
|
+
}
|
|
179
|
+
return cudaErrorInvalidValue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
} // namespace kernels
|
|
183
|
+
} // namespace trainstream
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// TrainStream — shared runtime declarations (events, streams, pinned memory,
|
|
2
|
+
// asynchronous transfers). Implemented in memory_pinner.cpp.
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <ATen/ATen.h>
|
|
6
|
+
#include <ATen/cuda/CUDAContext.h>
|
|
7
|
+
#include <c10/cuda/CUDAGuard.h>
|
|
8
|
+
#include <cuda_runtime.h>
|
|
9
|
+
|
|
10
|
+
#include <atomic>
|
|
11
|
+
#include <cstdint>
|
|
12
|
+
#include <map>
|
|
13
|
+
#include <memory>
|
|
14
|
+
#include <string>
|
|
15
|
+
#include <tuple>
|
|
16
|
+
#include <vector>
|
|
17
|
+
|
|
18
|
+
#define TS_CUDA_CHECK(expr) \
|
|
19
|
+
do { \
|
|
20
|
+
const cudaError_t _ts_err = (expr); \
|
|
21
|
+
TORCH_CHECK(_ts_err == cudaSuccess, "TrainStream CUDA error: ", cudaGetErrorString(_ts_err), \
|
|
22
|
+
" (", #expr, ") at ", __FILE__, ":", __LINE__); \
|
|
23
|
+
} while (0)
|
|
24
|
+
|
|
25
|
+
namespace trainstream {
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Events
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// RAII wrapper around cudaEvent_t. Non-timing events are recycled through a
|
|
31
|
+
// per-device pool because transfer-heavy workloads create thousands per step.
|
|
32
|
+
// Re-recording a recycled event is safe: cudaStreamWaitEvent snapshots the
|
|
33
|
+
// event's state at call time, so earlier waiters are unaffected.
|
|
34
|
+
class Event {
|
|
35
|
+
public:
|
|
36
|
+
Event(int device, bool timing);
|
|
37
|
+
~Event();
|
|
38
|
+
Event(const Event&) = delete;
|
|
39
|
+
Event& operator=(const Event&) = delete;
|
|
40
|
+
|
|
41
|
+
void record(cudaStream_t stream);
|
|
42
|
+
bool query() const; // true if completed (or never recorded)
|
|
43
|
+
void synchronize() const; // host blocks until completion
|
|
44
|
+
void block(cudaStream_t stream) const; // GPU-side: stream waits for this event
|
|
45
|
+
float elapsed_ms(const Event& end) const;
|
|
46
|
+
bool recorded() const { return recorded_; }
|
|
47
|
+
int device() const { return device_; }
|
|
48
|
+
|
|
49
|
+
private:
|
|
50
|
+
cudaEvent_t ev_ = nullptr;
|
|
51
|
+
int device_;
|
|
52
|
+
bool timing_;
|
|
53
|
+
bool recorded_ = false;
|
|
54
|
+
};
|
|
55
|
+
using EventPtr = std::shared_ptr<Event>;
|
|
56
|
+
|
|
57
|
+
EventPtr make_event(int device, bool timing = false);
|
|
58
|
+
EventPtr record_on_current(int device, bool timing = false);
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Runtime: dedicated streams + traffic counters, one instance per device.
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
struct Runtime {
|
|
64
|
+
explicit Runtime(int device);
|
|
65
|
+
|
|
66
|
+
int device;
|
|
67
|
+
int sm_count = 0;
|
|
68
|
+
cudaStream_t d2h = nullptr; // device -> host (gradients, activations out)
|
|
69
|
+
cudaStream_t h2d = nullptr; // host -> device (parameters, activation prefetch)
|
|
70
|
+
cudaStream_t upd = nullptr; // zero-copy optimizer kernels
|
|
71
|
+
|
|
72
|
+
std::atomic<uint64_t> bytes_d2h{0};
|
|
73
|
+
std::atomic<uint64_t> bytes_h2d{0};
|
|
74
|
+
std::atomic<uint64_t> copies_d2h{0};
|
|
75
|
+
std::atomic<uint64_t> copies_h2d{0};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
Runtime& runtime(int device);
|
|
79
|
+
int resolve_device(int device); // -1 -> current device
|
|
80
|
+
cudaStream_t compute_stream(int device);
|
|
81
|
+
std::vector<int> initialized_devices();
|
|
82
|
+
|
|
83
|
+
// Tell the CUDA caching allocator that `t`'s memory is in use on `stream`, so
|
|
84
|
+
// the block is not handed out again until that stream's queued work finishes.
|
|
85
|
+
void record_stream(const at::Tensor& t, cudaStream_t stream, int device);
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Pinned host memory (pin-logging caching allocator)
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// cached=true -> returned to a size-bucketed free list on release (cheap reuse;
|
|
91
|
+
// used for activation stashes that churn every step).
|
|
92
|
+
// cached=false -> cudaFreeHost on release (persistent optimizer state).
|
|
93
|
+
// mapped=true -> cudaHostAllocMapped: device-addressable for zero-copy kernels.
|
|
94
|
+
at::Tensor pinned_empty(const std::vector<int64_t>& sizes, at::ScalarType dtype,
|
|
95
|
+
const std::string& tag, bool mapped, bool cached);
|
|
96
|
+
std::map<std::string, int64_t> pinned_stats();
|
|
97
|
+
std::map<std::string, int64_t> pinned_tag_bytes();
|
|
98
|
+
// (timestamp_ms, op, bytes, tag) — op is one of "alloc", "free", "hit", "release".
|
|
99
|
+
std::vector<std::tuple<double, std::string, int64_t, std::string>> pinned_log(int64_t limit);
|
|
100
|
+
void pinned_empty_cache();
|
|
101
|
+
void pinned_set_cache_limit(int64_t bytes);
|
|
102
|
+
void* pinned_device_pointer(const at::Tensor& t); // for mapped allocations
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Asynchronous transfers on the dedicated streams.
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// D2H: the copy stream first waits for the *current* compute stream so it
|
|
108
|
+
// reads fully produced data. The caller must keep `src` alive until the
|
|
109
|
+
// returned event completes (no recordStream; see memory_pinner.cpp).
|
|
110
|
+
EventPtr d2h_async(const at::Tensor& dst, const at::Tensor& src);
|
|
111
|
+
// H2D into an existing device tensor; waits for `after` (if any) first.
|
|
112
|
+
EventPtr h2d_async(const at::Tensor& dst, const at::Tensor& src, const EventPtr& after);
|
|
113
|
+
// H2D into a freshly allocated device tensor (from the compute stream's pool;
|
|
114
|
+
// the copy is ordered after work already queued there). The consumer must
|
|
115
|
+
// wait on the returned event before using or releasing the tensor.
|
|
116
|
+
// With `sizes`/`strides` the device tensor is created with that (dense) layout,
|
|
117
|
+
// restoring the exact strides a tensor had before it was paged out.
|
|
118
|
+
std::tuple<at::Tensor, EventPtr> h2d_alloc_async(const at::Tensor& src, const EventPtr& after,
|
|
119
|
+
int device, const std::vector<int64_t>& sizes = {},
|
|
120
|
+
const std::vector<int64_t>& strides = {});
|
|
121
|
+
std::map<std::string, int64_t> transfer_stats(int device);
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Micro-benchmarks (CUDA-event timed)
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
std::map<std::string, double> benchmark_pcie(int device, int64_t bytes, int iters);
|
|
127
|
+
std::map<std::string, double> benchmark_matmul(int device, int64_t n, at::ScalarType dtype,
|
|
128
|
+
int iters);
|
|
129
|
+
|
|
130
|
+
double now_ms();
|
|
131
|
+
|
|
132
|
+
} // namespace trainstream
|