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
trainstream/__init__.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""TrainStream: PCIe offloading engine for PyTorch training.
|
|
2
|
+
|
|
3
|
+
import trainstream
|
|
4
|
+
model, optimizer = trainstream.wrap(model, optimizer)
|
|
5
|
+
|
|
6
|
+
Optimizer state, gradients and (a profiled fraction of) activations are moved
|
|
7
|
+
to pinned host RAM over dedicated CUDA copy streams, so models that would not
|
|
8
|
+
fit in VRAM with a stock optimizer can still be trained on a single GPU.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import torch # noqa: F401 (loads libtorch / c10 before the extension)
|
|
15
|
+
except ImportError as _exc: # pragma: no cover - depends on the environment
|
|
16
|
+
raise ImportError(
|
|
17
|
+
"TrainStream needs PyTorch with CUDA. Install it first, e.g.\n"
|
|
18
|
+
" pip install torch --index-url https://download.pytorch.org/whl/cu130") from _exc
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from . import _C # prebuilt (build.bat / setup.py build_ext / --no-build-isolation install)
|
|
22
|
+
except ImportError:
|
|
23
|
+
from . import _build
|
|
24
|
+
|
|
25
|
+
_C = _build.load() # compiled on first import, then loaded from the cache
|
|
26
|
+
sys.modules[__name__ + "._C"] = _C
|
|
27
|
+
|
|
28
|
+
from .hooks import ActivationOffloader, GradientOffloader, ParamReadyHooks, find_blocks
|
|
29
|
+
from .optimizer import OffloadAdamW
|
|
30
|
+
from .profiler import HardwareProfile, profile_hardware
|
|
31
|
+
from .wrapper import TrainStreamSession, memory_report, unwrap, wrap
|
|
32
|
+
|
|
33
|
+
__version__ = "0.1.1"
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"wrap",
|
|
37
|
+
"unwrap",
|
|
38
|
+
"memory_report",
|
|
39
|
+
"OffloadAdamW",
|
|
40
|
+
"ActivationOffloader",
|
|
41
|
+
"GradientOffloader",
|
|
42
|
+
"ParamReadyHooks",
|
|
43
|
+
"TrainStreamSession",
|
|
44
|
+
"HardwareProfile",
|
|
45
|
+
"profile_hardware",
|
|
46
|
+
"find_blocks",
|
|
47
|
+
"pinned_stats",
|
|
48
|
+
"transfer_stats",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def pinned_stats():
|
|
53
|
+
"""Pin-logging allocator counters plus live bytes per allocation tag."""
|
|
54
|
+
stats = dict(_C.pinned_stats())
|
|
55
|
+
stats["by_tag"] = dict(_C.pinned_tag_bytes())
|
|
56
|
+
return stats
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def transfer_stats(device=None):
|
|
60
|
+
"""Cumulative bytes / copies issued on TrainStream's D2H and H2D streams."""
|
|
61
|
+
idx = torch.cuda.current_device() if device is None else (torch.device(device).index or 0)
|
|
62
|
+
return dict(_C.transfer_stats(idx))
|
trainstream/_build.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Compile-on-first-import for trainstream._C.
|
|
2
|
+
|
|
3
|
+
`pip install trainstream` installs a pure-Python wheel that carries the C++/CUDA
|
|
4
|
+
sources in trainstream/csrc. The first `import trainstream` compiles them
|
|
5
|
+
against the PyTorch that is installed (a few minutes) and caches the result;
|
|
6
|
+
later imports load the cached module directly, without a compiler.
|
|
7
|
+
|
|
8
|
+
The cache lives in %LOCALAPPDATA%\\trainstream (Windows) or
|
|
9
|
+
~/.cache/trainstream (Linux); TRAINSTREAM_CACHE_DIR overrides it. Builds are
|
|
10
|
+
keyed by the sources, Python, PyTorch, CUDA and GPU architectures, so upgrading
|
|
11
|
+
any of them triggers a fresh build instead of loading a stale one.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import glob
|
|
15
|
+
import hashlib
|
|
16
|
+
import importlib.machinery
|
|
17
|
+
import importlib.util
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
import torch
|
|
25
|
+
|
|
26
|
+
NAME = "trainstream_C"
|
|
27
|
+
CSRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "csrc")
|
|
28
|
+
SOURCES = ["bindings.cpp", "memory_pinner.cpp", "offload_adamw.cpp", "offload_adamw_kernel.cu"]
|
|
29
|
+
HEADERS = ["kernels.h", "offload_adamw.h", "trainstream.h"]
|
|
30
|
+
|
|
31
|
+
if sys.platform == "win32":
|
|
32
|
+
CXX_FLAGS = ["/O2", "/EHsc", "/W3", "/wd4244", "/wd4267", "/wd4251", "/wd4275", "/wd4996"]
|
|
33
|
+
NVCC_FLAGS = ["-O3", "--use_fast_math", "-lineinfo", "--expt-relaxed-constexpr", "-Xcompiler", "/wd4819"]
|
|
34
|
+
LIB_EXT = ".pyd"
|
|
35
|
+
else:
|
|
36
|
+
CXX_FLAGS = ["-O3", "-Wall", "-Wno-unused-function", "-fvisibility=hidden"]
|
|
37
|
+
NVCC_FLAGS = ["-O3", "--use_fast_math", "-lineinfo", "--expt-relaxed-constexpr"]
|
|
38
|
+
LIB_EXT = ".so"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BuildError(ImportError):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cache_root():
|
|
46
|
+
if os.environ.get("TRAINSTREAM_CACHE_DIR"):
|
|
47
|
+
return os.environ["TRAINSTREAM_CACHE_DIR"]
|
|
48
|
+
if sys.platform == "win32":
|
|
49
|
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~\\AppData\\Local")
|
|
50
|
+
return os.path.join(base, "trainstream", "build")
|
|
51
|
+
base = os.environ.get("XDG_CACHE_HOME") or os.path.expanduser("~/.cache")
|
|
52
|
+
return os.path.join(base, "trainstream")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _arch_list():
|
|
56
|
+
if os.environ.get("TORCH_CUDA_ARCH_LIST"):
|
|
57
|
+
return os.environ["TORCH_CUDA_ARCH_LIST"]
|
|
58
|
+
caps = sorted({torch.cuda.get_device_capability(i) for i in range(torch.cuda.device_count())})
|
|
59
|
+
return ";".join(f"{major}.{minor}" for major, minor in caps)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _build_key(arch):
|
|
63
|
+
h = hashlib.sha256()
|
|
64
|
+
for f in SOURCES + HEADERS:
|
|
65
|
+
with open(os.path.join(CSRC, f), "rb") as fh:
|
|
66
|
+
h.update(f.encode() + b"\0" + fh.read())
|
|
67
|
+
h.update(repr((sys.version_info[:2], torch.__version__, torch.version.cuda, arch,
|
|
68
|
+
CXX_FLAGS, NVCC_FLAGS)).encode())
|
|
69
|
+
tag = f"py{sys.version_info[0]}{sys.version_info[1]}-torch{torch.__version__}"
|
|
70
|
+
return re.sub(r"[^A-Za-z0-9_.-]", "_", tag) + "-" + h.hexdigest()[:12]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _import_library(path):
|
|
74
|
+
loader = importlib.machinery.ExtensionFileLoader(NAME, path)
|
|
75
|
+
spec = importlib.util.spec_from_file_location(NAME, path, loader=loader)
|
|
76
|
+
module = importlib.util.module_from_spec(spec)
|
|
77
|
+
loader.exec_module(module)
|
|
78
|
+
return module
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _version_key(path):
|
|
82
|
+
return tuple(int(p) for p in re.findall(r"\d+", os.path.basename(path)))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _setup_cuda_home():
|
|
86
|
+
if os.environ.get("CUDA_HOME") or os.environ.get("CUDA_PATH") or shutil.which("nvcc"):
|
|
87
|
+
return
|
|
88
|
+
if sys.platform == "win32":
|
|
89
|
+
pattern = os.path.join(os.environ.get("ProgramFiles", "C:\\Program Files"),
|
|
90
|
+
"NVIDIA GPU Computing Toolkit", "CUDA", "v*")
|
|
91
|
+
else:
|
|
92
|
+
pattern = "/usr/local/cuda*"
|
|
93
|
+
found = [d for d in glob.glob(pattern) if os.path.isfile(os.path.join(
|
|
94
|
+
d, "bin", "nvcc.exe" if sys.platform == "win32" else "nvcc"))]
|
|
95
|
+
if not found:
|
|
96
|
+
return
|
|
97
|
+
major = (torch.version.cuda or "").split(".")[0]
|
|
98
|
+
same_major = [d for d in found if _version_key(d)[:1] == (int(major),)] if major.isdigit() else []
|
|
99
|
+
os.environ["CUDA_HOME"] = max(same_major or found, key=_version_key)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _setup_msvc():
|
|
103
|
+
"""Import the x64 MSVC environment (vcvars64.bat) when cl.exe is not on PATH."""
|
|
104
|
+
if shutil.which("cl"):
|
|
105
|
+
return
|
|
106
|
+
vswhere = os.path.join(os.environ.get("ProgramFiles(x86)", "C:\\Program Files (x86)"),
|
|
107
|
+
"Microsoft Visual Studio", "Installer", "vswhere.exe")
|
|
108
|
+
if not os.path.isfile(vswhere):
|
|
109
|
+
return
|
|
110
|
+
try:
|
|
111
|
+
out = subprocess.check_output(
|
|
112
|
+
[vswhere, "-latest", "-products", "*", "-requires",
|
|
113
|
+
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath"],
|
|
114
|
+
text=True, stderr=subprocess.DEVNULL)
|
|
115
|
+
except (OSError, subprocess.CalledProcessError):
|
|
116
|
+
return
|
|
117
|
+
roots = [line.strip() for line in out.splitlines() if line.strip()]
|
|
118
|
+
if not roots:
|
|
119
|
+
return
|
|
120
|
+
vcvars = os.path.join(roots[0], "VC", "Auxiliary", "Build", "vcvars64.bat")
|
|
121
|
+
if not os.path.isfile(vcvars):
|
|
122
|
+
return
|
|
123
|
+
try:
|
|
124
|
+
env = subprocess.check_output(f'cmd /s /c ""{vcvars}" >nul && set"',
|
|
125
|
+
text=True, stderr=subprocess.DEVNULL)
|
|
126
|
+
except (OSError, subprocess.CalledProcessError):
|
|
127
|
+
return
|
|
128
|
+
for line in env.splitlines():
|
|
129
|
+
key, sep, value = line.partition("=")
|
|
130
|
+
if sep and key:
|
|
131
|
+
os.environ[key] = value
|
|
132
|
+
os.environ.setdefault("DISTUTILS_USE_SDK", "1")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _setup_ninja():
|
|
136
|
+
if shutil.which("ninja"):
|
|
137
|
+
return
|
|
138
|
+
try:
|
|
139
|
+
import ninja
|
|
140
|
+
except ImportError:
|
|
141
|
+
return
|
|
142
|
+
os.environ["PATH"] = ninja.BIN_DIR + os.pathsep + os.environ.get("PATH", "")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _hint():
|
|
146
|
+
if sys.platform == "win32":
|
|
147
|
+
return ("TrainStream needs the CUDA toolkit (same major version as PyTorch's CUDA, "
|
|
148
|
+
"https://developer.nvidia.com/cuda-downloads) and Visual Studio 2022+ Build Tools "
|
|
149
|
+
"with the 'Desktop development with C++' workload.")
|
|
150
|
+
return ("TrainStream needs the CUDA toolkit (same major version as PyTorch's CUDA) "
|
|
151
|
+
"and g++ 11+ (e.g. apt install build-essential).")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def load():
|
|
155
|
+
"""Return the compiled extension, building it first if no cached build matches."""
|
|
156
|
+
if torch.version.cuda is None:
|
|
157
|
+
raise BuildError(
|
|
158
|
+
f"TrainStream needs a CUDA build of PyTorch, but torch {torch.__version__} is CPU-only. "
|
|
159
|
+
"Install one from https://pytorch.org/get-started/locally/ , e.g.\n"
|
|
160
|
+
" pip install torch --index-url https://download.pytorch.org/whl/cu130")
|
|
161
|
+
if not torch.cuda.is_available() and not os.environ.get("TORCH_CUDA_ARCH_LIST"):
|
|
162
|
+
raise BuildError("TrainStream needs an NVIDIA GPU, but torch.cuda.is_available() is False "
|
|
163
|
+
"(set TORCH_CUDA_ARCH_LIST to compile without one).")
|
|
164
|
+
|
|
165
|
+
arch = _arch_list()
|
|
166
|
+
build_dir = os.path.join(_cache_root(), _build_key(arch))
|
|
167
|
+
library = os.path.join(build_dir, NAME + LIB_EXT)
|
|
168
|
+
if os.path.isfile(library):
|
|
169
|
+
return _import_library(library)
|
|
170
|
+
|
|
171
|
+
os.makedirs(build_dir, exist_ok=True)
|
|
172
|
+
_setup_ninja()
|
|
173
|
+
_setup_cuda_home()
|
|
174
|
+
if sys.platform == "win32":
|
|
175
|
+
_setup_msvc()
|
|
176
|
+
os.environ["TORCH_CUDA_ARCH_LIST"] = arch
|
|
177
|
+
|
|
178
|
+
from torch.utils import cpp_extension
|
|
179
|
+
if os.environ.get("CUDA_HOME"):
|
|
180
|
+
cpp_extension.CUDA_HOME = os.environ["CUDA_HOME"]
|
|
181
|
+
if cpp_extension.CUDA_HOME is None:
|
|
182
|
+
raise BuildError("TrainStream could not find the CUDA toolkit (nvcc). " + _hint()
|
|
183
|
+
+ " Set CUDA_HOME if it is installed somewhere unusual.")
|
|
184
|
+
|
|
185
|
+
print(f"TrainStream: compiling its CUDA extension for torch {torch.__version__} "
|
|
186
|
+
f"(first import only, takes a few minutes; cached in {build_dir})", file=sys.stderr, flush=True)
|
|
187
|
+
try:
|
|
188
|
+
module = cpp_extension.load(
|
|
189
|
+
name=NAME,
|
|
190
|
+
sources=[os.path.join(CSRC, f) for f in SOURCES],
|
|
191
|
+
extra_include_paths=[CSRC],
|
|
192
|
+
extra_cflags=CXX_FLAGS,
|
|
193
|
+
extra_cuda_cflags=NVCC_FLAGS,
|
|
194
|
+
build_directory=build_dir,
|
|
195
|
+
verbose=bool(os.environ.get("TRAINSTREAM_VERBOSE_BUILD")),
|
|
196
|
+
)
|
|
197
|
+
except Exception as exc:
|
|
198
|
+
raise BuildError(f"TrainStream failed to compile its CUDA extension. {_hint()}\n"
|
|
199
|
+
f"Set TRAINSTREAM_VERBOSE_BUILD=1 to see the full compiler output.\n{exc}") from exc
|
|
200
|
+
print("TrainStream: build finished.", file=sys.stderr, flush=True)
|
|
201
|
+
return module
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// TrainStream — Python bindings (module: trainstream._C).
|
|
2
|
+
//
|
|
3
|
+
// Every call that can block on the GPU or on worker threads releases the GIL,
|
|
4
|
+
// so autograd's backward thread and the training thread never deadlock on it.
|
|
5
|
+
|
|
6
|
+
#include <torch/extension.h>
|
|
7
|
+
|
|
8
|
+
#include <cstdio>
|
|
9
|
+
#include <exception>
|
|
10
|
+
|
|
11
|
+
#include "offload_adamw.h"
|
|
12
|
+
#include "trainstream.h"
|
|
13
|
+
|
|
14
|
+
namespace py = pybind11;
|
|
15
|
+
using namespace trainstream;
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
using nogil = py::call_guard<py::gil_scoped_release>;
|
|
19
|
+
|
|
20
|
+
// An exception escaping a destructor (e.g. a CUDA error surfacing inside the
|
|
21
|
+
// caching allocator's free path) calls std::terminate, and the default abort
|
|
22
|
+
// handler prints only a stack. Print the message first, then chain.
|
|
23
|
+
std::terminate_handler g_prev_terminate = nullptr;
|
|
24
|
+
void print_then_terminate() {
|
|
25
|
+
if (auto ex = std::current_exception()) {
|
|
26
|
+
try {
|
|
27
|
+
std::rethrow_exception(ex);
|
|
28
|
+
} catch (const std::exception& e) {
|
|
29
|
+
std::fprintf(stderr, "[trainstream] terminate: %s\n", e.what());
|
|
30
|
+
} catch (...) {
|
|
31
|
+
std::fprintf(stderr, "[trainstream] terminate: unknown exception\n");
|
|
32
|
+
}
|
|
33
|
+
std::fflush(stderr);
|
|
34
|
+
}
|
|
35
|
+
if (g_prev_terminate) g_prev_terminate();
|
|
36
|
+
std::abort();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
std::map<std::string, std::string> build_info() {
|
|
40
|
+
int runtime_version = 0, driver_version = 0;
|
|
41
|
+
cudaRuntimeGetVersion(&runtime_version);
|
|
42
|
+
cudaDriverGetVersion(&driver_version);
|
|
43
|
+
return {{"cuda_runtime", std::to_string(runtime_version)},
|
|
44
|
+
{"cuda_driver", std::to_string(driver_version)},
|
|
45
|
+
{"compiled_with_cuda", std::to_string(CUDART_VERSION)},
|
|
46
|
+
#ifdef _MSVC_LANG
|
|
47
|
+
{"cxx_standard", std::to_string(_MSVC_LANG)}}; // MSVC pins __cplusplus to 199711L
|
|
48
|
+
#else
|
|
49
|
+
{"cxx_standard", std::to_string(__cplusplus)}};
|
|
50
|
+
#endif
|
|
51
|
+
}
|
|
52
|
+
} // namespace
|
|
53
|
+
|
|
54
|
+
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
|
55
|
+
m.doc() = "TrainStream native runtime: pinned memory, DMA streams, offloaded AdamW";
|
|
56
|
+
g_prev_terminate = std::set_terminate(print_then_terminate);
|
|
57
|
+
|
|
58
|
+
m.def("build_info", &build_info);
|
|
59
|
+
m.def("now_ms", &now_ms);
|
|
60
|
+
m.def("init", [](int device) { runtime(device); }, py::arg("device") = -1);
|
|
61
|
+
m.def("is_dense", [](const at::Tensor& t) { return t.is_non_overlapping_and_dense(); },
|
|
62
|
+
"True if the tensor occupies exactly numel*itemsize contiguous bytes (any dimension order).");
|
|
63
|
+
|
|
64
|
+
// ---- events -------------------------------------------------------------
|
|
65
|
+
py::class_<Event, std::shared_ptr<Event>>(m, "Event")
|
|
66
|
+
.def(py::init([](int device, bool timing) { return make_event(resolve_device(device), timing); }),
|
|
67
|
+
py::arg("device") = -1, py::arg("timing") = false)
|
|
68
|
+
.def("record", [](Event& e) { e.record(compute_stream(e.device())); },
|
|
69
|
+
"Record on the current PyTorch compute stream.")
|
|
70
|
+
.def("query", &Event::query)
|
|
71
|
+
.def("synchronize", &Event::synchronize, nogil())
|
|
72
|
+
.def("wait", [](Event& e) { e.block(compute_stream(e.device())); },
|
|
73
|
+
"Make the current compute stream wait for this event (GPU-side, non-blocking for the host).")
|
|
74
|
+
.def("elapsed_ms", &Event::elapsed_ms, nogil())
|
|
75
|
+
.def_property_readonly("recorded", &Event::recorded)
|
|
76
|
+
.def_property_readonly("device", &Event::device);
|
|
77
|
+
m.def("record_event", &record_on_current, py::arg("device") = -1, py::arg("timing") = false);
|
|
78
|
+
|
|
79
|
+
// ---- pinned memory --------------------------------------------------------
|
|
80
|
+
m.def("pinned_empty", &pinned_empty, py::arg("sizes"), py::arg("dtype"), py::arg("tag") = "user",
|
|
81
|
+
py::arg("mapped") = false, py::arg("cached") = true);
|
|
82
|
+
m.def("pinned_stats", &pinned_stats);
|
|
83
|
+
m.def("pinned_tag_bytes", &pinned_tag_bytes);
|
|
84
|
+
m.def("pinned_log", &pinned_log, py::arg("limit") = 256);
|
|
85
|
+
m.def("pinned_empty_cache", &pinned_empty_cache, nogil());
|
|
86
|
+
m.def("pinned_set_cache_limit", &pinned_set_cache_limit);
|
|
87
|
+
|
|
88
|
+
// ---- transfers ------------------------------------------------------------
|
|
89
|
+
m.def("d2h_async", &d2h_async, py::arg("dst"), py::arg("src"));
|
|
90
|
+
m.def("h2d_async", &h2d_async, py::arg("dst"), py::arg("src"), py::arg("after") = EventPtr());
|
|
91
|
+
m.def("h2d_alloc_async", &h2d_alloc_async, py::arg("src"), py::arg("after") = EventPtr(),
|
|
92
|
+
py::arg("device") = -1, py::arg("sizes") = std::vector<int64_t>{},
|
|
93
|
+
py::arg("strides") = std::vector<int64_t>{});
|
|
94
|
+
m.def("transfer_stats", &transfer_stats, py::arg("device") = -1);
|
|
95
|
+
m.def("synchronize_streams", [](int device) {
|
|
96
|
+
Runtime& rt = runtime(device);
|
|
97
|
+
TS_CUDA_CHECK(cudaStreamSynchronize(rt.d2h));
|
|
98
|
+
TS_CUDA_CHECK(cudaStreamSynchronize(rt.h2d));
|
|
99
|
+
TS_CUDA_CHECK(cudaStreamSynchronize(rt.upd));
|
|
100
|
+
}, py::arg("device") = -1, nogil());
|
|
101
|
+
|
|
102
|
+
// ---- benchmarks -----------------------------------------------------------
|
|
103
|
+
m.def("benchmark_pcie", &benchmark_pcie, py::arg("device") = -1, py::arg("bytes") = 256LL << 20,
|
|
104
|
+
py::arg("iters") = 10, nogil());
|
|
105
|
+
m.def("benchmark_matmul", &benchmark_matmul, py::arg("device") = -1, py::arg("n") = 4096,
|
|
106
|
+
py::arg("dtype") = at::kBFloat16, py::arg("iters") = 20, nogil());
|
|
107
|
+
m.def("benchmark_cpu_adamw", &benchmark_cpu_adamw, py::arg("numel") = 1 << 24, py::arg("threads") = 0,
|
|
108
|
+
nogil());
|
|
109
|
+
m.def("benchmark_zero_copy_adamw", &benchmark_zero_copy_adamw, py::arg("device") = -1,
|
|
110
|
+
py::arg("numel") = 1 << 24, nogil());
|
|
111
|
+
|
|
112
|
+
// ---- optimizer engine -----------------------------------------------------
|
|
113
|
+
py::class_<OffloadAdamW>(m, "OffloadAdamWEngine")
|
|
114
|
+
.def(py::init<int, const std::string&, int, int64_t>(), py::arg("device"), py::arg("mode") = "cpu",
|
|
115
|
+
py::arg("threads") = 0, py::arg("chunk_elems") = 1 << 20)
|
|
116
|
+
.def("add_param", &OffloadAdamW::add_param, py::arg("param"), py::arg("pin_moments") = true)
|
|
117
|
+
.def("offload_grad", &OffloadAdamW::offload_grad, py::arg("id"), py::arg("grad"),
|
|
118
|
+
py::arg("apply_update") = false, py::arg("lr") = 0.0, py::arg("beta1") = 0.9, py::arg("beta2") = 0.999,
|
|
119
|
+
py::arg("eps") = 1e-8, py::arg("weight_decay") = 0.0, nogil())
|
|
120
|
+
.def("step", &OffloadAdamW::step, py::arg("ids"), py::arg("lrs"), py::arg("beta1s"), py::arg("beta2s"),
|
|
121
|
+
py::arg("epss"), py::arg("wds"), py::arg("grads"), py::arg("max_grad_norm") = 0.0,
|
|
122
|
+
py::arg("compute_norm") = false, nogil())
|
|
123
|
+
.def("wait_ready", &OffloadAdamW::wait_ready, nogil())
|
|
124
|
+
.def("synchronize", &OffloadAdamW::synchronize, nogil())
|
|
125
|
+
.def("last_grad_norm", &OffloadAdamW::last_grad_norm, nogil())
|
|
126
|
+
.def("state", &OffloadAdamW::state, nogil())
|
|
127
|
+
.def("load_state",
|
|
128
|
+
[](OffloadAdamW& e, int64_t id, const at::Tensor& m, const at::Tensor& v,
|
|
129
|
+
const std::optional<at::Tensor>& master, int64_t step) {
|
|
130
|
+
e.load_state(id, m, v, master.value_or(at::Tensor()), step);
|
|
131
|
+
},
|
|
132
|
+
py::arg("id"), py::arg("exp_avg"), py::arg("exp_avg_sq"), py::arg("master") = py::none(),
|
|
133
|
+
py::arg("step") = 0, nogil())
|
|
134
|
+
.def("get_step", &OffloadAdamW::get_step)
|
|
135
|
+
.def("stats", &OffloadAdamW::stats)
|
|
136
|
+
.def_property_readonly("mode", &OffloadAdamW::mode)
|
|
137
|
+
.def_property_readonly("num_params", &OffloadAdamW::num_params)
|
|
138
|
+
.def_property_readonly("threads", &OffloadAdamW::threads);
|
|
139
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// TrainStream — device kernel launch interface.
|
|
2
|
+
//
|
|
3
|
+
// This header is deliberately free of PyTorch includes so that nvcc only has to
|
|
4
|
+
// parse CUDA runtime headers when compiling offload_adamw_kernel.cu. The C++
|
|
5
|
+
// translation units translate at::ScalarType into kernels::DType before calling.
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
#include <cuda_runtime.h>
|
|
10
|
+
|
|
11
|
+
namespace trainstream {
|
|
12
|
+
namespace kernels {
|
|
13
|
+
|
|
14
|
+
enum class DType : int { kFloat32 = 0, kFloat16 = 1, kBFloat16 = 2 };
|
|
15
|
+
|
|
16
|
+
// Hyper-parameters for one AdamW step on one tensor. Bias corrections are folded
|
|
17
|
+
// on the host so the kernel does no pow() per element:
|
|
18
|
+
// step_size = lr / (1 - beta1^t)
|
|
19
|
+
// bias_correction2_sqrt = sqrt(1 - beta2^t)
|
|
20
|
+
struct AdamWParams {
|
|
21
|
+
float lr;
|
|
22
|
+
float beta1;
|
|
23
|
+
float beta2;
|
|
24
|
+
float eps;
|
|
25
|
+
float weight_decay;
|
|
26
|
+
float step_size;
|
|
27
|
+
float bias_correction2_sqrt;
|
|
28
|
+
float max_grad_norm; // only read when a grad_sq_norm pointer is supplied
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Fused AdamW where the optimizer state lives in *mapped pinned host memory*.
|
|
32
|
+
// `exp_avg`, `exp_avg_sq` and (optionally) `master` are device-visible aliases of
|
|
33
|
+
// cudaHostAlloc(cudaHostAllocMapped) buffers, so every state read/write is a PCIe
|
|
34
|
+
// transaction and the moments never occupy VRAM. `param` and `grad` live in VRAM.
|
|
35
|
+
// If `master` is non-null the fp32 master copy is updated and `param` receives a
|
|
36
|
+
// rounded copy (mixed-precision training with fp16/bf16 weights).
|
|
37
|
+
// If `grad_sq_norm` is non-null it points at a device float holding sum(g^2) over
|
|
38
|
+
// all gradients; the kernel derives the global-norm clip coefficient from it.
|
|
39
|
+
cudaError_t adamw_zero_copy(DType dtype, void* param, const void* grad, float* exp_avg,
|
|
40
|
+
float* exp_avg_sq, float* master, int64_t n,
|
|
41
|
+
const AdamWParams& hp, const float* grad_sq_norm, int sm_count,
|
|
42
|
+
cudaStream_t stream);
|
|
43
|
+
|
|
44
|
+
// out[0] += sum(grad[i]^2). Used for global gradient-norm clipping on the GPU path.
|
|
45
|
+
cudaError_t grad_sq_norm_accumulate(DType dtype, const void* grad, int64_t n, float* out,
|
|
46
|
+
int sm_count, cudaStream_t stream);
|
|
47
|
+
|
|
48
|
+
} // namespace kernels
|
|
49
|
+
} // namespace trainstream
|