ngocdang83 commited on
Commit
6b561ce
·
verified ·
1 Parent(s): 4402d35

bench: add cloud sweep runner

Browse files
HachimiMT_Benchmark_Profile.ipynb CHANGED
@@ -335,59 +335,61 @@
335
  "metadata": {},
336
  "outputs": [],
337
  "source": [
338
- "# 5. Sweep nhanh batch/window/beam (tắt mặc định)\n",
339
  "# Bật RUN_SWEEP=True khi muốn tìm cấu hình tốt nhất trên runtime hiện tại.\n",
340
  "RUN_SWEEP = False\n",
341
- "SWEEP_SINGLE_GPU = True # True: fair x1 T4; False: Kaggle dùng hết GPU\n",
342
  "SWEEP_BEAMS = [1, 2]\n",
343
- "SWEEP_BATCHES = [64, 96]\n",
344
  "SWEEP_WINDOWS = [4, 8, 16]\n",
345
  "SWEEP_PROGRESS_SECONDS = 999999\n",
 
346
  "\n",
347
- "def _float_or_none(value):\n",
348
- " try:\n",
349
- " return float(value)\n",
350
- " except Exception:\n",
351
- " return None\n",
352
  "\n",
353
  "\n",
354
  "if RUN_SWEEP:\n",
355
- " sweep_rows = []\n",
356
- " for beam in SWEEP_BEAMS:\n",
357
- " for batch in SWEEP_BATCHES:\n",
358
- " for window in SWEEP_WINDOWS:\n",
359
- " label = f\"beam{beam}-batch{batch}-window{window}-{'1gpu' if SWEEP_SINGLE_GPU else 'allgpu'}\"\n",
360
- " summary = run_benchmark_once(\n",
361
- " label=label,\n",
362
- " beam=beam,\n",
363
- " batch_size=str(batch),\n",
364
- " window_multiplier=str(window),\n",
365
- " use_single_gpu=SWEEP_SINGLE_GPU,\n",
366
- " progress_seconds=SWEEP_PROGRESS_SECONDS,\n",
367
- " )\n",
368
- " done = summary[\"done\"]\n",
369
- " profile = summary[\"profile\"]\n",
370
- " sweep_rows.append({\n",
371
- " \"label\": label,\n",
372
- " \"beam\": beam,\n",
373
- " \"batch\": batch,\n",
374
- " \"window\": window,\n",
375
- " \"chunks_s\": _float_or_none(done.get(\"chunks_s\")),\n",
376
- " \"chars_s\": _float_or_none(done.get(\"chars_s\")),\n",
377
- " \"translate_s\": _float_or_none(done.get(\"translate_s\")),\n",
378
- " \"ct2_infer_s\": _float_or_none(profile.get(\"ct2_infer_s\")),\n",
379
- " \"decode_s\": _float_or_none(profile.get(\"decode_s\")),\n",
380
- " })\n",
381
  "\n",
382
- " sweep_rows = sorted(sweep_rows, key=lambda row: row[\"chars_s\"] or 0, reverse=True)\n",
383
- " print(\"\\n--- sweep summary (best first) ---\")\n",
384
- " for row in sweep_rows:\n",
385
- " print(\n",
386
- " f\"{row['label']} chars_s={row['chars_s']} chunks_s={row['chunks_s']} \"\n",
387
- " f\"translate_s={row['translate_s']} ct2_infer_s={row['ct2_infer_s']} decode_s={row['decode_s']}\"\n",
388
- " )\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  "else:\n",
390
- " print(\"RUN_SWEEP=False. Đổi thành True để chạy sweep batch/window/beam.\")"
391
  ]
392
  },
393
  {
 
335
  "metadata": {},
336
  "outputs": [],
337
  "source": [
338
+ "# 5. Sweep batch/window/beam bằng script CLI (tắt mặc định)\n",
339
  "# Bật RUN_SWEEP=True khi muốn tìm cấu hình tốt nhất trên runtime hiện tại.\n",
340
  "RUN_SWEEP = False\n",
341
+ "SWEEP_SINGLE_GPU = True # True: fair x1 T4; False: Kaggle dùng auto/all GPU\n",
342
  "SWEEP_BEAMS = [1, 2]\n",
343
+ "SWEEP_BATCHES = [96] # mini-sweep; mở rộng thành [64, 96, 128] nếu cần\n",
344
  "SWEEP_WINDOWS = [4, 8, 16]\n",
345
  "SWEEP_PROGRESS_SECONDS = 999999\n",
346
+ "SWEEP_MAX_RUNS = 0 # 0 = chạy hết combo; đặt 1/2 để smoke nhanh\n",
347
  "\n",
348
+ "def _csv(values):\n",
349
+ " return \",\".join(str(value) for value in values)\n",
 
 
 
350
  "\n",
351
  "\n",
352
  "if RUN_SWEEP:\n",
353
+ " sweep_script = Path(\"hachimimt/src/benchmark_sweep.py\")\n",
354
+ " if not sweep_script.exists():\n",
355
+ " raise FileNotFoundError(f\"Không thấy sweep script: {sweep_script}\")\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  "\n",
357
+ " cmd = [\n",
358
+ " sys.executable,\n",
359
+ " str(sweep_script),\n",
360
+ " str(input_path),\n",
361
+ " \"--model\", MODEL,\n",
362
+ " \"--backend\", \"ct2\",\n",
363
+ " \"--chunk-mode\", CHUNK_MODE,\n",
364
+ " \"--normalize\", NORMALIZE,\n",
365
+ " \"--beams\", _csv(SWEEP_BEAMS),\n",
366
+ " \"--batches\", _csv(SWEEP_BATCHES),\n",
367
+ " \"--windows\", _csv(SWEEP_WINDOWS),\n",
368
+ " \"--ct2-batch-type\", CT2_BATCH_TYPE,\n",
369
+ " \"--inter-threads\", INTER_THREADS,\n",
370
+ " \"--progress-seconds\", str(SWEEP_PROGRESS_SECONDS),\n",
371
+ " ]\n",
372
+ " if SWEEP_SINGLE_GPU:\n",
373
+ " cmd.extend([\"--single-gpu\", \"--gpu-indices\", FAIR_GPU_INDICES])\n",
374
+ " if TOKENIZE_WORKERS:\n",
375
+ " cmd.extend([\"--tokenize-workers\", TOKENIZE_WORKERS])\n",
376
+ " if TOKENIZE_JOB_SIZE:\n",
377
+ " cmd.extend([\"--tokenize-job-size\", TOKENIZE_JOB_SIZE])\n",
378
+ " if CT2_THREADS:\n",
379
+ " cmd.extend([\"--ct2-threads\", CT2_THREADS])\n",
380
+ " if SWEEP_MAX_RUNS:\n",
381
+ " cmd.extend([\"--max-runs\", str(SWEEP_MAX_RUNS)])\n",
382
+ "\n",
383
+ " print(\"RUN_SWEEP_CMD:\", \" \".join(cmd))\n",
384
+ " process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)\n",
385
+ " assert process.stdout is not None\n",
386
+ " for line in process.stdout:\n",
387
+ " print(line, end=\"\")\n",
388
+ " returncode = process.wait()\n",
389
+ " if returncode != 0:\n",
390
+ " raise subprocess.CalledProcessError(returncode, cmd)\n",
391
  "else:\n",
392
+ " print(\"RUN_SWEEP=False. Đổi thành True để chạy mini-sweep batch/window/beam.\")"
393
  ]
394
  },
395
  {
hachimimt-local.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ccf2a1d7a24a9226c257f5fe5c45b68a2bfd7a38333f6e75dcc537dbf2feb102
3
- size 105893
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf2ea8f67ada36aa0c05fe1e76367ff0c5f5198cad0fa5ed06f2baa71f537da8
3
+ size 108864
src/benchmark_sweep.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run multiple benchmark_file.py configurations and summarize throughput."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ def parse_int_list(raw: str, *, name: str) -> list[int]:
13
+ values: list[int] = []
14
+ for part in raw.split(","):
15
+ part = part.strip()
16
+ if not part:
17
+ continue
18
+ try:
19
+ value = int(part)
20
+ except ValueError as exc:
21
+ raise argparse.ArgumentTypeError(f"{name} phải là danh sách số: {raw!r}") from exc
22
+ if value <= 0:
23
+ raise argparse.ArgumentTypeError(f"{name} chỉ nhận số dương: {raw!r}")
24
+ values.append(value)
25
+ if not values:
26
+ raise argparse.ArgumentTypeError(f"{name} không được rỗng")
27
+ return values
28
+
29
+
30
+ def parse_kv_line(line: str) -> dict[str, str]:
31
+ data: dict[str, str] = {}
32
+ for part in line.split()[1:]:
33
+ if "=" not in part:
34
+ continue
35
+ key, value = part.split("=", 1)
36
+ data[key] = value
37
+ return data
38
+
39
+
40
+ def float_or_zero(value: str | None) -> float:
41
+ if value is None:
42
+ return 0.0
43
+ try:
44
+ return float(value)
45
+ except ValueError:
46
+ return 0.0
47
+
48
+
49
+ def set_or_unset(env: dict[str, str], key: str, value: str | None) -> None:
50
+ value = (value or "").strip()
51
+ if value:
52
+ env[key] = value
53
+ else:
54
+ env.pop(key, None)
55
+
56
+
57
+ def build_env(args: argparse.Namespace, *, batch: int, window: int) -> dict[str, str]:
58
+ env = os.environ.copy()
59
+ if args.single_gpu:
60
+ env["HACHIMIMT_GPU_INDICES"] = args.gpu_indices
61
+ env["HACHIMIMT_AUTO_ALL_GPUS"] = "0"
62
+ elif args.auto_all_gpus:
63
+ env.pop("HACHIMIMT_GPU_INDICES", None)
64
+ env["HACHIMIMT_AUTO_ALL_GPUS"] = "1"
65
+ else:
66
+ env.pop("HACHIMIMT_GPU_INDICES", None)
67
+ env.pop("HACHIMIMT_AUTO_ALL_GPUS", None)
68
+
69
+ env["HACHIMIMT_BATCH_SIZE"] = str(batch)
70
+ env["HACHIMIMT_CT2_WINDOW_MULTIPLIER"] = str(window)
71
+ set_or_unset(env, "HACHIMIMT_CT2_BATCH_TYPE", args.ct2_batch_type)
72
+ set_or_unset(env, "HACHIMIMT_INTER_THREADS", args.inter_threads)
73
+ set_or_unset(env, "HACHIMIMT_TOKENIZE_WORKERS", args.tokenize_workers)
74
+ set_or_unset(env, "HACHIMIMT_TOKENIZE_JOB_SIZE", args.tokenize_job_size)
75
+ set_or_unset(env, "HACHIMIMT_THREADS", args.ct2_threads)
76
+ return env
77
+
78
+
79
+ def build_command(args: argparse.Namespace, *, beam: int) -> list[str]:
80
+ script = Path(__file__).with_name("benchmark_file.py")
81
+ return [
82
+ sys.executable,
83
+ str(script),
84
+ str(args.path),
85
+ "--model",
86
+ args.model,
87
+ "--backend",
88
+ args.backend,
89
+ "--beam",
90
+ str(beam),
91
+ "--chunk-mode",
92
+ args.chunk_mode,
93
+ "--normalize",
94
+ args.normalize,
95
+ "--progress-seconds",
96
+ str(args.progress_seconds),
97
+ ]
98
+
99
+
100
+ def run_one(
101
+ args: argparse.Namespace,
102
+ *,
103
+ label: str,
104
+ beam: int,
105
+ batch: int,
106
+ window: int,
107
+ ) -> dict[str, object]:
108
+ env = build_env(args, batch=batch, window=window)
109
+ cmd = build_command(args, beam=beam)
110
+
111
+ print(f"SWEEP_RUN label={label} beam={beam} batch={batch} window={window}", flush=True)
112
+ print("SWEEP_CMD " + " ".join(cmd), flush=True)
113
+ if args.dry_run:
114
+ return {
115
+ "label": label,
116
+ "beam": beam,
117
+ "batch": batch,
118
+ "window": window,
119
+ "done": {},
120
+ "profile": {},
121
+ "runtime": {},
122
+ "packages": {},
123
+ "env": {},
124
+ "returncode": 0,
125
+ }
126
+
127
+ lines: list[str] = []
128
+ process = subprocess.Popen(
129
+ cmd,
130
+ stdout=subprocess.PIPE,
131
+ stderr=subprocess.STDOUT,
132
+ text=True,
133
+ bufsize=1,
134
+ env=env,
135
+ )
136
+ assert process.stdout is not None
137
+ for line in process.stdout:
138
+ print(line, end="")
139
+ lines.append(line.rstrip("\n"))
140
+ returncode = process.wait()
141
+ if returncode != 0:
142
+ raise subprocess.CalledProcessError(returncode, cmd)
143
+
144
+ profile_lines = [line for line in lines if line.startswith("BENCH_PROFILE")]
145
+ done_lines = [line for line in lines if line.startswith("BENCH_DONE")]
146
+ runtime_lines = [line for line in lines if line.startswith("BENCH_RUNTIME")]
147
+ package_lines = [line for line in lines if line.startswith("BENCH_PACKAGES")]
148
+ env_lines = [line for line in lines if line.startswith("BENCH_ENV")]
149
+
150
+ result = {
151
+ "label": label,
152
+ "beam": beam,
153
+ "batch": batch,
154
+ "window": window,
155
+ "done": parse_kv_line(done_lines[-1]) if done_lines else {},
156
+ "profile": parse_kv_line(profile_lines[-1]) if profile_lines else {},
157
+ "runtime": parse_kv_line(runtime_lines[-1]) if runtime_lines else {},
158
+ "packages": parse_kv_line(package_lines[-1]) if package_lines else {},
159
+ "env": parse_kv_line(env_lines[-1]) if env_lines else {},
160
+ "returncode": returncode,
161
+ }
162
+ print(format_sweep_result(result), flush=True)
163
+ return result
164
+
165
+
166
+ def format_sweep_result(result: dict[str, object], *, prefix: str = "SWEEP_RESULT") -> str:
167
+ done = result["done"]
168
+ profile = result["profile"]
169
+ assert isinstance(done, dict)
170
+ assert isinstance(profile, dict)
171
+ fields = [
172
+ f"label={result['label']}",
173
+ f"beam={result['beam']}",
174
+ f"batch={result['batch']}",
175
+ f"window={result['window']}",
176
+ f"translate_s={done.get('translate_s', '')}",
177
+ f"chars_s={done.get('chars_s', '')}",
178
+ f"chunks_s={done.get('chunks_s', '')}",
179
+ f"ct2_infer_s={profile.get('ct2_infer_s', '')}",
180
+ f"decode_s={profile.get('decode_s', '')}",
181
+ f"chunk_s={profile.get('chunk_s', '')}",
182
+ f"tokenize_wait_s={profile.get('tokenize_wait_s', '')}",
183
+ ]
184
+ return prefix + " " + " ".join(fields)
185
+
186
+
187
+ def parse_args() -> argparse.Namespace:
188
+ parser = argparse.ArgumentParser(description=__doc__)
189
+ parser.add_argument("path", type=Path, help="Input .txt file")
190
+ parser.add_argument("--model", default="HachimiMT-60")
191
+ parser.add_argument("--backend", choices=["ct2", "transformers"], default="ct2")
192
+ parser.add_argument("--chunk-mode", choices=["sentence", "paragraph"], default="sentence")
193
+ parser.add_argument("--normalize", choices=["auto", "none", "t2s"], default="auto")
194
+ parser.add_argument("--beams", default="1,2", help="Comma-separated beam sizes")
195
+ parser.add_argument("--batches", default="96", help="Comma-separated batch sizes")
196
+ parser.add_argument("--windows", default="4,8,16", help="Comma-separated CT2 window multipliers")
197
+ parser.add_argument("--single-gpu", action="store_true", help="Force HACHIMIMT_GPU_INDICES to one GPU")
198
+ parser.add_argument("--auto-all-gpus", action="store_true", help="Force HACHIMIMT_AUTO_ALL_GPUS=1")
199
+ parser.add_argument("--gpu-indices", default="0")
200
+ parser.add_argument("--ct2-batch-type", default="tokens")
201
+ parser.add_argument("--inter-threads", default="1")
202
+ parser.add_argument("--tokenize-workers", default="")
203
+ parser.add_argument("--tokenize-job-size", default="")
204
+ parser.add_argument("--ct2-threads", default="")
205
+ parser.add_argument("--progress-seconds", type=float, default=999999.0)
206
+ parser.add_argument("--max-runs", type=int, default=0, help="Optional cap for quick smoke tests")
207
+ parser.add_argument("--dry-run", action="store_true", help="Print planned runs without translating")
208
+ args = parser.parse_args()
209
+ args.beams_list = parse_int_list(args.beams, name="--beams")
210
+ args.batches_list = parse_int_list(args.batches, name="--batches")
211
+ args.windows_list = parse_int_list(args.windows, name="--windows")
212
+ if args.single_gpu and args.auto_all_gpus:
213
+ parser.error("--single-gpu và --auto-all-gpus không dùng cùng lúc")
214
+ if not args.dry_run and not args.path.exists():
215
+ parser.error(f"Input file không tồn tại: {args.path}")
216
+ return args
217
+
218
+
219
+ def main() -> None:
220
+ args = parse_args()
221
+ results: list[dict[str, object]] = []
222
+ planned = 0
223
+ for beam in args.beams_list:
224
+ for batch in args.batches_list:
225
+ for window in args.windows_list:
226
+ planned += 1
227
+ if args.max_runs and len(results) >= args.max_runs:
228
+ break
229
+ mode = "1gpu" if args.single_gpu else "allgpu" if args.auto_all_gpus else "auto"
230
+ label = f"beam{beam}-batch{batch}-window{window}-{mode}"
231
+ results.append(run_one(args, label=label, beam=beam, batch=batch, window=window))
232
+ if args.max_runs and len(results) >= args.max_runs:
233
+ break
234
+ if args.max_runs and len(results) >= args.max_runs:
235
+ break
236
+
237
+ print(f"SWEEP_DONE planned={planned} ran={len(results)}", flush=True)
238
+ if args.dry_run or not results:
239
+ return
240
+
241
+ ranked = sorted(
242
+ results,
243
+ key=lambda result: float_or_zero(result["done"].get("chars_s") if isinstance(result["done"], dict) else None),
244
+ reverse=True,
245
+ )
246
+ print("\n--- sweep summary (best first) ---", flush=True)
247
+ for result in ranked:
248
+ print(format_sweep_result(result), flush=True)
249
+ print(format_sweep_result(ranked[0], prefix="SWEEP_BEST"), flush=True)
250
+
251
+
252
+ if __name__ == "__main__":
253
+ main()