File size: 9,273 Bytes
4123334
 
 
 
 
 
 
2d7d6ad
6ae4480
25d55aa
4123334
 
 
 
2d7d6ad
4123334
 
 
2d7d6ad
 
4123334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7d6ad
 
 
 
 
 
4123334
 
 
2d7d6ad
4123334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import os
import ctypes
import sys
import torch
from torch.optim import Optimizer

"""
QPOLA v1.0.4 260731 (Moment-Free) fp8/int8 対応済  ※ CUDA特性のため4bit未対応
QPOLARIS (Quantization n Polar-Aligned Resetting Instant Zero-Master Weight SGD)
量子化に強い、履歴ゼロ、空間協調(極座標・QJL)、Zero-Master Weight による自己適応型SGD
QPOLAは従来のオプティマイザよりも大きな学習率(LR)を設定します(最大値として機能します)
低精度・量子化モデルでの学習はLRを下げてください、通常は LR:1e-3 あたりで安定的に進行します(LoRA)
事前学習やフルファインチューンニングにおいては相応しいスケールに落としてください LR:1e-4 程度等(Pre & FT)
(この仕組みは瞬時的な 勾配の分解と再構成 を行います、複次的に VRAM負荷を削減 しました)
※ fp/int4 の学習については STE 環境下等で通常動作可能/CUDA特性は生の4bit未対応なだけです 
usage / 使い方
--optimizer_type=optimizer.qpola.QPOLA
Please place qpola.py and qpola_kernel.ptx in the same folder.
update:260731
既存オプティマイザの init に合わせることで未指定項目によるエラーを防止(未使用項目はダミーになります)
"""

current_dir = os.path.dirname(os.path.abspath(__file__))
ptx_path = os.path.join(current_dir, "qpola_kernel.ptx")

if not os.path.exists(ptx_path):
    raise FileNotFoundError(f"QPOLA PTXファイルが見つかりません: {ptx_path}")

try:
    if sys.platform.startswith('win'):
        cuda_driver = ctypes.CDLL("nvcuda.dll")
    else:
        cuda_driver = ctypes.CDLL("libcuda.so")
except OSError:
    raise RuntimeError("CUDA Driver (nvcuda.dll / libcuda.so) が見つかりません")

# Driver API の関数の引数型を明示的に定義(クラッシュ防止)
cuda_driver.cuModuleLoadData.argtypes = [
    ctypes.POINTER(ctypes.c_void_p), 
    ctypes.c_void_p]
cuda_driver.cuModuleGetFunction.argtypes = [
    ctypes.POINTER(ctypes.c_void_p), 
    ctypes.c_void_p, ctypes.c_char_p,]
cuda_driver.cuLaunchKernel.argtypes = [
    ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,
    ctypes.c_uint, ctypes.c_uint, ctypes.c_uint,ctypes.c_uint, 
    ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]

with open(ptx_path, "rb") as f:
    ptx_bytes = f.read()

# デバイスごとの Loaded Module と Function のキャッシュ
CUDA_MODULES = {}
CUDA_KERNELS = {}

def get_cuda_kernel(device_index: int, kernel_name: bytes):
    global CUDA_MODULES, CUDA_KERNELS
    cache_key = (device_index, kernel_name)
    if cache_key in CUDA_KERNELS:
        return CUDA_KERNELS[cache_key]

    torch.cuda.set_device(device_index)

    ## デバイスごとに PTX モジュールを 1度だけロード
    if device_index not in CUDA_MODULES:
        module = ctypes.c_void_p()
        res = cuda_driver.cuModuleLoadData(ctypes.byref(module), ptx_bytes)
        if res != 0:
            raise RuntimeError(f"GPU:{device_index} でのモジュールロード失敗 (コード: {res})")
        CUDA_MODULES[device_index] = module
    else:
        module = CUDA_MODULES[device_index]

    kernel = ctypes.c_void_p()
    res = cuda_driver.cuModuleGetFunction(ctypes.byref(kernel), module, kernel_name)
    if res != 0:
        raise RuntimeError(f"GPU:{device_index} でのカーネル {kernel_name.decode()} 取得失敗 (コード: {res})")

    CUDA_KERNELS[cache_key] = kernel
    return kernel


class QPOLA(Optimizer):
    def __init__(self, params, 
                 lr=1e-3, 
                 eps=1e-8, 
                 low_vram=True, 
                 betas=(0.9, 0.995), 
                 weight_decay=0.01):
        defaults = dict(lr=lr, eps=eps)
        super(QPOLA, self).__init__(params, defaults)
        self.low_vram = low_vram
    # betas, weight_decay 等は未使用、学習側の記述書き換え等を受け流しエラー防止するダミーです

    @torch.no_grad()
    def step(self, closure=None):
        loss = None
        if closure is not None:
            with torch.enable_grad():
                loss = closure()

        for group in self.param_groups:
            lr = group['lr']
            eps = group['eps']

            for p in group['params']:
                if p.grad is None:
                    continue

                g = p.grad
                orig_device = p.device
                orig_dtype = p.dtype
                is_cpu_tensor = not p.is_cuda

                dtype_str = str(orig_dtype)
                if orig_dtype == torch.float32:
                    k_name = b"qpola_kernel_fp32"
                elif orig_dtype == torch.float16:
                    k_name = b"qpola_kernel_fp16"
                elif orig_dtype == torch.bfloat16:
                    k_name = b"qpola_kernel_bf16"
                elif orig_dtype == torch.int8:
                    k_name = b"qpola_kernel_int8"
                elif "e4m3" in dtype_str:
                    k_name = b"qpola_kernel_fp8_e4m3"
                elif "e5m2" in dtype_str:
                    k_name = b"qpola_kernel_fp8_e5m2"
                else:
                    raise NotImplementedError(f"QPOLAは現在、型 {orig_dtype} をサポートしていません")

                # デバイスの調停
                if is_cpu_tensor:
                    target_device = torch.device(f"cuda:{torch.cuda.current_device()}")
                    p_cuda = p.to(target_device)
                    g_cuda = g.to(target_device)
                else:
                    target_device = orig_device
                    p_cuda = p
                    g_cuda = g

                # メモリ連続性の保証と「非連続テンソル」への対策
                p_was_not_contiguous = not p_cuda.is_contiguous()
                if p_was_not_contiguous:
                    p_cuda = p_cuda.contiguous()
                if not g_cuda.is_contiguous():
                    g_cuda = g_cuda.contiguous()

                n = p_cuda.numel()
                device_idx = target_device.index if target_device.index is not None else 0
                kernel = get_cuda_kernel(device_idx, k_name)

                # cuLaunchKernel 用の引数配列の構築ロジック
                # 各引数のアドレスではなく、値そのものを ctypes オブジェクトとして生成
                p_ptr = ctypes.c_void_p(p_cuda.data_ptr())
                g_ptr = ctypes.c_void_p(g_cuda.data_ptr())
                c_lr = ctypes.c_float(lr)
                c_eps = ctypes.c_float(eps)
                c_n = ctypes.c_int(n)

                # args には各変数のポインタ(アドレス)を直接格納
                args = [
                    ctypes.byref(p_ptr),
                    ctypes.byref(g_ptr),
                    ctypes.byref(c_lr),
                    ctypes.byref(c_eps),
                    ctypes.byref(c_n)
                ]
                # void* args[] に相当するポインタ配列を作成
                arg_arr = (ctypes.c_void_p * len(args))(*[ctypes.cast(a, ctypes.c_void_p) for a in args])

                stream = torch.cuda.current_stream(target_device).cuda_stream

                res = cuda_driver.cuLaunchKernel(
                    kernel,
                    (n + 255) // 256, 1, 1,  # gridDim
                    256, 1, 1,               # blockDim
                    0,                       # sharedMem
                    ctypes.c_void_p(stream), # stream
                    arg_arr,                 # kernelParams
                    None                     # extra
                )

                if res != 0:
                    raise RuntimeError(f"GPU:{device_idx} 内で {k_name.decode()} の実行に失敗 (コード: {res})")

                # GPUかつ非連続だった場合のインプレース書き戻し
                if p_was_not_contiguous and not is_cpu_tensor:
                    p.copy_(p_cuda)

                # CPU配置の場合の同期と書き戻し
                if is_cpu_tensor:
                    torch.cuda.current_stream(target_device).synchronize()
                    p.copy_(p_cuda)

                # 不要になった一時テンソルを明示的に削除してVRAMを解放
                if p_was_not_contiguous or is_cpu_tensor:
                    del p_cuda
                if not g.is_contiguous():
                    del g_cuda

        # 【選択式】 プールされた未使用VRAMキャッシュの完全解放(クリーンアップ) / 中級者以上向け
        # 毎ステップの解放は速度低下しますが 通常:True です(多くの方に学習可能状態を届けるため)
        # VRAMに余裕がある方は初期化時に low_vram=False を設定してください (Falseで高速化)
        if self.low_vram and torch.cuda.is_available():
            torch.cuda.empty_cache()

        return loss

"""
 https://github.com/muooon/qpola
 True Gradient will guide you through it all; believing in it and continuing to move forward is what fosters growth.
 Don’t let the past control you—the noise within the past is the very source of your worries and suffering.
"""