|
| 1 | +# SPDX-License-Identifier: Apache-2.0 |
| 2 | +# DeepSpeed Team |
| 3 | +""" |
| 4 | +Activation / checkpoint hidden-state CPU offload: pin vs pageable with async on. |
| 5 | +
|
| 6 | +Holds use_streams=True. Compares use_pin_memory True vs False. Do not treat |
| 7 | +this as DeepSpeed #8282's async-vs-blocking table. |
| 8 | +""" |
| 9 | + |
| 10 | +from __future__ import annotations |
| 11 | + |
| 12 | +import argparse |
| 13 | +import os |
| 14 | +import sys |
| 15 | +import time |
| 16 | + |
| 17 | +_PIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 18 | +if _PIN_ROOT not in sys.path: |
| 19 | + sys.path.insert(0, _PIN_ROOT) |
| 20 | + |
| 21 | +from common import dist_env, patch_cpp_extension_drop_cxx17, print_arm_result, print_driver_result, run_arm_subprocess |
| 22 | + |
| 23 | + |
| 24 | +def parse_args(): |
| 25 | + parser = argparse.ArgumentParser(description=__doc__) |
| 26 | + parser.add_argument("--hidden", type=int, default=1024) |
| 27 | + parser.add_argument("--layers", type=int, default=8) |
| 28 | + parser.add_argument("--batch", type=int, default=1) |
| 29 | + parser.add_argument("--seq", type=int, default=2048) |
| 30 | + parser.add_argument("--steps", type=int, default=4) |
| 31 | + parser.add_argument("--warmup", type=int, default=2) |
| 32 | + parser.add_argument("--pin", type=int, default=None, help="internal: use_pin_memory 0/1") |
| 33 | + return parser.parse_args() |
| 34 | + |
| 35 | + |
| 36 | +def run_arm(args): |
| 37 | + dist_env() |
| 38 | + patch_cpp_extension_drop_cxx17() |
| 39 | + |
| 40 | + import torch |
| 41 | + from torch.utils.checkpoint import checkpoint |
| 42 | + |
| 43 | + from deepspeed.accelerator import get_accelerator |
| 44 | + from deepspeed.runtime.activation_checkpointing.offload_activations import CheckpointHiddenStatesOffload |
| 45 | + |
| 46 | + accelerator = get_accelerator() |
| 47 | + if not accelerator.is_available(): |
| 48 | + raise RuntimeError(f"No {accelerator.device_name()} device is available") |
| 49 | + accelerator.set_device(0) |
| 50 | + device = accelerator.current_device_name() |
| 51 | + |
| 52 | + class Block(torch.nn.Module): |
| 53 | + |
| 54 | + def __init__(self, hidden): |
| 55 | + super().__init__() |
| 56 | + self.fc1 = torch.nn.Linear(hidden, 4 * hidden) |
| 57 | + self.fc2 = torch.nn.Linear(4 * hidden, hidden) |
| 58 | + |
| 59 | + def forward(self, hidden_states): |
| 60 | + return self.fc2(torch.nn.functional.gelu(self.fc1(hidden_states))) |
| 61 | + |
| 62 | + class Net(torch.nn.Module): |
| 63 | + |
| 64 | + def __init__(self, hidden, layers): |
| 65 | + super().__init__() |
| 66 | + self.blocks = torch.nn.ModuleList([Block(hidden) for _ in range(layers)]) |
| 67 | + |
| 68 | + def forward(self, hidden_states, offload): |
| 69 | + x = hidden_states |
| 70 | + for block in self.blocks: |
| 71 | + offload.mark(x) |
| 72 | + x = x + checkpoint(block, x, use_reentrant=False) |
| 73 | + return x.sum() |
| 74 | + |
| 75 | + model = Net(args.hidden, args.layers).to(device) |
| 76 | + opt = torch.optim.AdamW(model.parameters(), lr=1e-4) |
| 77 | + x = torch.randn(args.batch, args.seq, args.hidden, device=device, requires_grad=True) |
| 78 | + |
| 79 | + # Async side stream stays on; pin vs pageable is the only axis. |
| 80 | + offload = CheckpointHiddenStatesOffload(use_pin_memory=bool(args.pin), |
| 81 | + use_streams=True, |
| 82 | + min_offload_bytes=0, |
| 83 | + keep_last_count=1) |
| 84 | + |
| 85 | + step_times = [] |
| 86 | + for step in range(args.warmup + args.steps): |
| 87 | + accelerator.synchronize() |
| 88 | + t0 = time.perf_counter() |
| 89 | + opt.zero_grad(set_to_none=True) |
| 90 | + with offload: |
| 91 | + loss = model(x, offload) |
| 92 | + loss.backward() |
| 93 | + opt.step() |
| 94 | + accelerator.synchronize() |
| 95 | + if step >= args.warmup: |
| 96 | + step_times.append(time.perf_counter() - t0) |
| 97 | + offload.reset() |
| 98 | + |
| 99 | + def _peak(): |
| 100 | + try: |
| 101 | + return round(torch.get_device_module(device).max_memory_allocated() / 1e9, 2) |
| 102 | + except Exception: |
| 103 | + return None |
| 104 | + |
| 105 | + print_arm_result({ |
| 106 | + "experiment": "activation_offload", |
| 107 | + "use_pin_memory": bool(args.pin), |
| 108 | + "use_streams": True, |
| 109 | + "device": accelerator.device_name(), |
| 110 | + "hidden": args.hidden, |
| 111 | + "layers": args.layers, |
| 112 | + "batch": args.batch, |
| 113 | + "seq": args.seq, |
| 114 | + "steps": len(step_times), |
| 115 | + "step_avg_s": sum(step_times) / len(step_times), |
| 116 | + "step_min_s": min(step_times), |
| 117 | + "gpu_peak_gb": _peak(), |
| 118 | + }) |
| 119 | + |
| 120 | + |
| 121 | +def run_driver(args): |
| 122 | + script = os.path.abspath(__file__) |
| 123 | + base = [ |
| 124 | + "--hidden", |
| 125 | + str(args.hidden), |
| 126 | + "--layers", |
| 127 | + str(args.layers), |
| 128 | + "--batch", |
| 129 | + str(args.batch), |
| 130 | + "--seq", |
| 131 | + str(args.seq), |
| 132 | + "--steps", |
| 133 | + str(args.steps), |
| 134 | + "--warmup", |
| 135 | + str(args.warmup), |
| 136 | + ] |
| 137 | + results = {} |
| 138 | + for name, pin in (("pageable", 0), ("pinned", 1)): |
| 139 | + results[name] = run_arm_subprocess(script, base + ["--pin", str(pin)]) |
| 140 | + |
| 141 | + pageable = results["pageable"] |
| 142 | + pinned = results["pinned"] |
| 143 | + print("\n================ Activation offload (async on) ================") |
| 144 | + print(f"device: {pinned['device']} hidden: {pinned['hidden']} layers: {pinned['layers']} " |
| 145 | + f"batch: {pinned['batch']} seq: {pinned['seq']}") |
| 146 | + print(f"{'arm':<22}{'avg step (s)':>14}{'min step (s)':>14}{'GPU peak (GB)':>16}") |
| 147 | + for name in ("pageable", "pinned"): |
| 148 | + row = results[name] |
| 149 | + print(f"{name:<22}{row['step_avg_s']:>14.3f}{row['step_min_s']:>14.3f}" |
| 150 | + f"{(row['gpu_peak_gb'] or 0):>16.2f}") |
| 151 | + speedup = pageable["step_avg_s"] / pinned["step_avg_s"] |
| 152 | + print() |
| 153 | + print(f"pin vs pageable step-time ratio: {speedup:.2f}x (async held on)") |
| 154 | + print_driver_result({"pageable": pageable, "pinned": pinned, "speedup": speedup}) |
| 155 | + |
| 156 | + |
| 157 | +if __name__ == "__main__": |
| 158 | + parsed = parse_args() |
| 159 | + if parsed.pin is None: |
| 160 | + run_driver(parsed) |
| 161 | + else: |
| 162 | + run_arm(parsed) |
0 commit comments