melog 0.1.0__tar.gz

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.
Files changed (63) hide show
  1. melog-0.1.0/PKG-INFO +435 -0
  2. melog-0.1.0/README.md +421 -0
  3. melog-0.1.0/melog/__init__.py +63 -0
  4. melog-0.1.0/melog/api/__init__.py +122 -0
  5. melog-0.1.0/melog/cli/__init__.py +97 -0
  6. melog-0.1.0/melog/core.py +521 -0
  7. melog-0.1.0/melog/metrics/__init__.py +38 -0
  8. melog-0.1.0/melog/metrics/base.py +206 -0
  9. melog-0.1.0/melog/metrics/basic.py +79 -0
  10. melog-0.1.0/melog/metrics/classification.py +319 -0
  11. melog-0.1.0/melog/metrics/group.py +146 -0
  12. melog-0.1.0/melog/storage/journal.py +59 -0
  13. melog-0.1.0/melog/storage/media.py +164 -0
  14. melog-0.1.0/melog/storage/media_log.py +57 -0
  15. melog-0.1.0/melog/storage/melog_file.py +412 -0
  16. melog-0.1.0/melog/storage/mirror.py +235 -0
  17. melog-0.1.0/melog/tracking/axis.py +89 -0
  18. melog-0.1.0/melog/tracking/console.py +125 -0
  19. melog-0.1.0/melog/tracking/steps_bar.py +195 -0
  20. melog-0.1.0/melog/utils/__init__.py +1 -0
  21. melog-0.1.0/melog/utils/bar_stack.py +98 -0
  22. melog-0.1.0/melog/utils/distributed.py +99 -0
  23. melog-0.1.0/melog/utils/downsample.py +44 -0
  24. melog-0.1.0/melog/utils/epoch_end_iterable.py +52 -0
  25. melog-0.1.0/melog/utils/tqdm.py +392 -0
  26. melog-0.1.0/melog/web/__init__.py +0 -0
  27. melog-0.1.0/melog/web/app.py +161 -0
  28. melog-0.1.0/melog/web/fs.py +72 -0
  29. melog-0.1.0/melog/web/loader.py +108 -0
  30. melog-0.1.0/melog/web/media_store.py +53 -0
  31. melog-0.1.0/melog/web/media_view.py +76 -0
  32. melog-0.1.0/melog/web/server.py +185 -0
  33. melog-0.1.0/melog/web/static/css/style.css +123 -0
  34. melog-0.1.0/melog/web/static/echarts.min.js +45 -0
  35. melog-0.1.0/melog/web/static/index.html +72 -0
  36. melog-0.1.0/melog/web/static/js/charts.js +281 -0
  37. melog-0.1.0/melog/web/static/js/downsample.js +27 -0
  38. melog-0.1.0/melog/web/static/js/filebrowser.js +223 -0
  39. melog-0.1.0/melog/web/static/js/main.js +55 -0
  40. melog-0.1.0/melog/web/static/js/media.js +233 -0
  41. melog-0.1.0/melog/web/static/js/theme.js +24 -0
  42. melog-0.1.0/melog/web/static/js/ws.js +17 -0
  43. melog-0.1.0/melog/web/static/logo.svg +25 -0
  44. melog-0.1.0/melog/web/store.py +55 -0
  45. melog-0.1.0/melog/web/view.py +62 -0
  46. melog-0.1.0/melog/web/ws.py +50 -0
  47. melog-0.1.0/melog.egg-info/PKG-INFO +435 -0
  48. melog-0.1.0/melog.egg-info/SOURCES.txt +61 -0
  49. melog-0.1.0/melog.egg-info/dependency_links.txt +1 -0
  50. melog-0.1.0/melog.egg-info/entry_points.txt +2 -0
  51. melog-0.1.0/melog.egg-info/requires.txt +7 -0
  52. melog-0.1.0/melog.egg-info/top_level.txt +2 -0
  53. melog-0.1.0/pyproject.toml +28 -0
  54. melog-0.1.0/setup.cfg +4 -0
  55. melog-0.1.0/tests/test_classification.py +314 -0
  56. melog-0.1.0/tests/test_components.py +132 -0
  57. melog-0.1.0/tests/test_core.py +542 -0
  58. melog-0.1.0/tests/test_downsample.py +68 -0
  59. melog-0.1.0/tests/test_media.py +318 -0
  60. melog-0.1.0/tests/test_metrics.py +386 -0
  61. melog-0.1.0/tests/test_resume.py +194 -0
  62. melog-0.1.0/tests/test_tqdm.py +487 -0
  63. melog-0.1.0/tests/test_web.py +369 -0
melog-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,435 @@
1
+ Metadata-Version: 2.4
2
+ Name: melog
3
+ Version: 0.1.0
4
+ Summary: 轻量级训练监控库:多 GPU 指标合并、控制台实时进度条、Web 可视化
5
+ License: MIT
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: fastapi>=0.100
9
+ Requires-Dist: uvicorn>=0.23
10
+ Requires-Dist: websockets>=11.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: httpx>=0.24; extra == "dev"
14
+
15
+ <div align="center">
16
+ <img src="assets/logo.svg" alt="Melog" width="340">
17
+ </div>
18
+
19
+ 轻量级训练监控库:**多 GPU 指标合并 + 控制台实时进度条 + Web 可视化**。
20
+
21
+ ```text
22
+ epoch 3 loss=0.2153 acc=0.8974 lr=8.2e-04 ━━━━━━━━━━━────────── 45.0% [90/200] [0:03<0:04 30.0it/s]
23
+ ```
24
+
25
+ ## 特性
26
+
27
+ - **控制台实时进度条**:自研 tqdm(用法与 tqdm.tqdm 一致),`[n/total]` 领先、指标紧随其后实时刷新;进度条与 print 同步镜像到每次会话独立的 `console-<时间戳>.log`(run 目录已有会话产物时新文件自动加序号前缀 `2.`、`3.`……,metrics 日志同理;进度条行就地实时刷新、带时间戳前缀,与终端内容一致,编辑器打开可看到动的进度条)
28
+ - **多 GPU 指标合并**:基于 `torch.distributed` all_reduce 跨进程聚合(默认取均值),仅 rank0 记录与展示;未装 torch 自动退化单进程
29
+ - **Web 可视化**:FastAPI + WebSocket + ECharts,后台线程运行,实时推送曲线,断线自动重连
30
+ - **持久化**:指标写入自研二进制容器(符号表 + varint 增量编码,体积约为 JSONL 的 1/4),每次启动一个带时间戳的会话文件,互不覆盖
31
+ - **断点续训**:重跑同一 `log_dir` 自动接续历史曲线;从某个 epoch 重新训练时自动清除上次中断留下的重叠数据,折线不会在 x 轴上回退
32
+
33
+ ## 安装
34
+
35
+ ```bash
36
+ pip install -e . # 基础安装
37
+ ```
38
+
39
+ 多 GPU 合并基于 `torch.distributed`,假定环境中已装好 PyTorch;未装 torch 时自动退化单进程。
40
+
41
+ ## 快速开始
42
+
43
+ ```python
44
+ import melog
45
+ from melog import StepsBar
46
+
47
+ melog.init("runs/my-exp") # 日志保存路径;端口缺省自动选空闲端口
48
+ # Web 地址启动时自动打印,也可读 melog.current().web_url
49
+
50
+ for step in StepsBar(range(1000)): # tqdm 风格:自动推进,无需手动 update
51
+ loss = train_one_step()
52
+ melog.scalar({"loss": loss, "lr": 1e-3}) # 记录 + 刷新进度条指标 + 推送 Web
53
+ ```
54
+
55
+ 训练期间浏览器打开启动时打印的 Web 地址(即 `melog.current().web_url`)查看实时曲线。
56
+
57
+ ## 全局共享
58
+
59
+ `melog.init` 是唯一入口,创建的实例自动成为全局活动实例。入口处 `init` 一次,
60
+ 项目任何地方直接用模块级接口,无需层层传递实例:
61
+
62
+ ```python
63
+ import melog
64
+
65
+ melog.init(log_dir="runs/my-exp") # 日志保存路径;端口缺省自动选空闲端口
66
+
67
+ # 任意其他模块中:
68
+ import melog
69
+ melog.scalar({"loss": 0.5})
70
+ melog.image("sample", img)
71
+ # 收尾:进程退出时自动完成,无需调用
72
+ ```
73
+
74
+ - `log_dir` 末级目录名即项目名(`runs/my-exp` → 项目 `my-exp`),本次运行落在
75
+ `runs/my-exp/<时间戳>/` 下;`project=` 可覆盖项目名
76
+ - 最近一次创建的实例即全局活动实例(`melog.current()` 取回),收尾后清空
77
+ - 进程退出时经 atexit 自动收尾:落盘剩余指标、定稿进度条、停 Web、还原 print,
78
+ 无需任何手动调用
79
+ - 模块级 `scalar / image / audio / log / success / error / warn / set_colors / current_bar` 与实例方法等价
80
+ - 实例内部有锁,多线程 / 多模块共享安全;多 GPU 约定不变
81
+
82
+ ## 曲线上体现 epoch
83
+
84
+ 本库**按 epoch 组织训练记录**:每个 epoch 的循环必须用 `StepsBar` 包裹并传入
85
+ `epoch`,坐标(epoch / step)由它统一管理——`scalar()` / `image()` /
86
+ `audio()` 都**没有坐标参数**,记录自动依附当前 epoch 与下一个空槽:
87
+
88
+ ```python
89
+ from melog import StepsBar
90
+
91
+ for epoch in range(epochs):
92
+ for _ in StepsBar(loader, epoch=epoch): # 行首自动标注 "epoch N"
93
+ loss = train_one_step()
94
+ melog.scalar({"loss": loss, "lr": lr}) # 坐标自动依附当前 epoch
95
+ ```
96
+
97
+ - `StepsBar(epoch=...)` 进入进度条即绑定 epoch:epoch 内步数清零、全局 x 从上一位置
98
+ 接续;bar 结束后沿用绑定值,直至下一个 epoch
99
+ - `step` 为**当前 epoch 内**的记录序号,内部自增(每个 epoch 从 0 重新计步);
100
+ 完全没用 `StepsBar(epoch=...)` 时退化为全局自增 x、不标注 epoch 分界
101
+ - 要控制记录粒度(每步 / 每 N 步窗口),调整调用 `scalar()` 的频率即可,无需手动指定坐标
102
+ - Web 曲线在每个 epoch 起点画分界虚线(标注 `e0` / `e1` / …),悬浮提示显示 `epoch N · step X`
103
+ - `MetricGroup` 末尾收尾交给 `StepsBar` 的自动记录(见下文),epoch 沿用绑定值
104
+
105
+ ## 控制台消息
106
+
107
+ print 风格的控制台输出接口:多参数自动转 `str()`、以 `sep` 拼接,签名对齐 `print`
108
+ (支持 `sep` / `end` / `flush`):
109
+
110
+ ```python
111
+ melog.log("普通消息", {"k": 1}) # 终端默认色(黑字),无前缀
112
+ melog.success("保存完成") # 绿色 ✔
113
+ melog.error("加载失败") # 红色 ✘
114
+ melog.warn("学习率过大") # 黄色 ⚠
115
+ ```
116
+
117
+ 实例存活期间(仅 rank0),官方 `print(...)` 会被拦截内部改走 `log()`——普通打印
118
+ 自动带上图标/配色并同步进 console.log,进程退出收尾后还原原生 print。颜色仅在真实
119
+ 终端(TTY)启用,重定向 / console.log 始终纯文本。
120
+
121
+ ## 记录图像与音频
122
+
123
+ 除指标曲线外,Web 端 header 可在 **曲线 / 图像 / 音频** 三个页签间切换。图像与音频用
124
+ `image` / `audio` 记录,Web 端按名字建卡片、滑杆按 step 回放(图像点击看原图,
125
+ 音频在线播放);文件自动落盘到 `run_dir/media/`,元数据随日志持久化,历史日志加载时
126
+ 媒体一并恢复:
127
+
128
+ ```python
129
+ melog.scalar({"loss": loss}) # 坐标自动依附当前 epoch(StepsBar 绑定)
130
+ melog.image("train/sample", img) # 路径 / PIL / numpy / torch
131
+ melog.image("val/sample", img) # 自动附着最近一次 scalar() 的位置
132
+ melog.audio("val/audio", wav, sr=16000) # 路径(wav/mp3/…) / numpy / torch 波形
133
+ ```
134
+
135
+ - 图像 / 音频自动附着到**最近一次 `scalar()` 的位置**,不推进计数;
136
+ 坐标(epoch / step)由 `StepsBar` 统一管理,接口无坐标参数
137
+ - `caption="..."` 可为每条图像 / 音频配一段文字(如样本说明、转写文本),
138
+ 显示在卡片上、随滑杆切换;换行会被保留
139
+ - 图像:`(H,W)` 灰度或 `(H,W,C)`(C=1/3/4),浮点自动映射 0-255,统一存为 PNG
140
+ - 音频:`(N,)` 单声道或 `(N, 声道数)`,浮点按 [-1,1] 裁剪存为 16bit WAV;
141
+ 传文件路径则按原格式复制
142
+ - 数组编码需要 `pillow`(仅图像):`pip install pillow`
143
+
144
+ ## 指标计算(多 GPU 自动同步)
145
+
146
+ 内置 `Mean` / `Sum` / `Last` / `Count`,按 epoch 组织在 `MetricGroup` 中使用:
147
+
148
+ ```python
149
+ import melog
150
+ from melog import Last, Mean, MetricGroup, StepsBar, Sum
151
+
152
+ melog.init("runs/my-exp")
153
+ metrics = MetricGroup({
154
+ "loss": Mean(), # 各 batch 等权平均
155
+ "acc": Mean(),
156
+ "seen": Sum(), # 求和
157
+ "lr": Last(), # 最近一次喂入值
158
+ })
159
+
160
+ for epoch in range(epochs):
161
+ # metrics=... 传入后:每次 feed 自动把本卡本地值写入日志/面板
162
+ # (实时曲线,零通信),epoch 末自动跨 GPU 合并出全局值再记录
163
+ # 一次并 reset 清零
164
+ for _ in StepsBar(range(steps), epoch=epoch, metrics=metrics):
165
+ metrics.feed(loss=loss, acc=acc, seen=batch_size, lr=lr)
166
+ # feed(..., write=False) 只累积内存(如验证集不想逐 batch 写曲线);
167
+ # epoch 末仍自动跨 GPU 合并记录 + reset,无需手动 scalar
168
+ ```
169
+ - `Mean` 默认按 StepsBar 自动识别的**批次样本数**精确平均(feed 无需传
170
+ batch_size;各 batch 等样本数时即等权);多 GPU 下合并为全局样本平均,
171
+ 而非"各卡平均值的平均"
172
+ - 批次样本数自动识别(tensor / numpy 的 shape[0]、字典、列表 / 元组递归);
173
+ 识别失败(如迭代 range)回退等权平均并警告一次。需手动指定时传**元组**
174
+ `(值, 观测数)`:`metrics.feed(loss=(loss, token_num))`,显式值优先
175
+ - `melog.scalar(metrics)` 随时可落盘当前累计值;**必须算完一个 epoch 才有意义的指标**,
176
+ 在 epoch 末统一记录一次即可(交给 StepsBar 自动执行,或手动调用)
177
+ - 跨 GPU 合并是集合操作:**所有 rank 必须以相同顺序执行**,返回值各 rank 一致;单进程自动直通
178
+ - 实时 + 精确一步到位:`StepsBar(loader, epoch=e, metrics=metrics)`——
179
+ 每次 feed 自动把本卡本地值写入日志/面板(零通信,仅 rank0 落盘),bar 同步实时显示;
180
+ 迭代自然结束时自动 gather 所有 rank 合并出**全局值**再记录一次(提前 break / 抛异常不触发,
181
+ 以免各 rank 在 all_gather 处互相等待;所有 rank 都会执行,落盘仅 rank0)。
182
+ `feed(..., write=False)` 关闭逐 batch 实时写入(如验证集场景),epoch 末
183
+ 仍自动合并记录,无需手动 scalar
184
+
185
+ ### feed 如何分发观测
186
+
187
+ `metrics.feed(args=..., **scalars)` 把一个 batch 的观测一次喂入,两类指标
188
+ **分开传、各取所需**。以
189
+
190
+ ```python
191
+ metrics = MetricGroup({"loss": Mean(), "macc": MaskedAcc()})
192
+ metrics.feed(args={"logits": logits, "labels": labels, "mask": mask},
193
+ loss=(loss, batch_size))
194
+ ```
195
+
196
+ 为例,一次 feed 内部的流转:
197
+
198
+ - **`args=`:观测型指标**(如 `"macc"` 与所有内置分类指标)的观测,
199
+ 单独成组——**字典**按键名对应各指标 `compute` / `prepare` 的形参
200
+ (推荐,形参多时更可读),自动分发给形参名匹配的指标,多余的键忽略;
201
+ **元组**按位置喂给未被注册名喂入的指标。
202
+ 缺少必需形参才抛 `KeyError`。
203
+ - **`**scalars`:按注册名喂入的指标**(`Mean` / `Sum` / `Last` / `Count`,如 `"loss"`):
204
+ 按**注册名**找同名键——取出 `loss=(loss, batch_size)`;是元组就展开为
205
+ `feed(loss, batch_size)` 加权累积,普通数值则等权。本 batch 没有同名键就跳过
206
+ (不累积也不报错)。
207
+
208
+ 一句话:**观测型指标的观测放 `args`,标量指标按注册名"点名取值"**。两类规则
209
+ 互不干扰,所以同一个 feed 调用可以同时喂两类指标;无主的多余观测两边都不收。
210
+
211
+ 单独使用某个指标时规则一致:标量指标位置喂入 `Mean().feed(value, count)`;
212
+ 观测型指标具名或位置均可 `MaskedAcc().feed(logits=..., labels=..., mask=...)`,
213
+ 框架同样按 `compute` / `prepare` 形参名组装。
214
+
215
+ ### 分类指标
216
+
217
+ 内置 `Accuracy` / `Precision` / `Recall` / `F1` / `ConfusionMatrix`,接口与基础指标一致,
218
+ `feed(logits, labels)` 直接接收模型输出与标签:
219
+
220
+ ```python
221
+ from melog import Accuracy, F1, MetricGroup, Mean, Precision
222
+
223
+ metrics = MetricGroup({
224
+ "loss": Mean(),
225
+ "acc": Accuracy(), # 二分类:一维得分按阈值 0.5 判定
226
+ "acc5": Accuracy(topk=5), # top-5 准确率(多分类)
227
+ "f1": F1(num_classes=10), # 多分类:二维 (N, K) logits 按行 argmax
228
+ })
229
+
230
+ # 验证集:write=False 不逐 batch 写曲线,epoch 末自动跨 GPU 合并记录
231
+ for logits, labels in StepsBar(val_loader, epoch=epoch, metrics=val_metrics):
232
+ # feed:观测型指标的观测放 args(元组按位置 / 字典按键名),
233
+ # 标量指标按注册名喂入(loss 自动按批次样本数平均)
234
+ val_metrics.feed(args=(logits, labels), loss=loss, write=False)
235
+ # 无需手动 scalar:StepsBar 结束时自动合并记录并 reset
236
+ ```
237
+
238
+ - `Accuracy(topk=k)`:真实类别在前 k 个预测中即算正确
239
+ - `Precision / Recall / F1` 的 `average`:`None`(二分类=正类,多分类=macro)/ `"macro"` / `"micro"` / `"weighted"`
240
+ - `ConfusionMatrix` 的 `compute()` 返回矩阵(行=真实、列=预测),适合直接读取而非画曲线
241
+ - 预测规则由 `preds_from_logits` 实现,可传 `predictor=` 替换(如多标签、分割等自定义转换)
242
+
243
+ ### 自定义指标
244
+
245
+ 统一继承 `Metric`,按指标何时出值选择实现方式:
246
+
247
+ **实时指标——只实现 `compute()`**:每次喂入立即用本批观测算出指标值,
248
+ 形参名和个数完全由你定义,框架按形参名自动从 `feed()` 的观测中取值回调;
249
+ 各 batch 结果按各自实际的样本数加权平均、跨 GPU 合并,全部由框架完成:
250
+
251
+ ```python
252
+ from melog import Metric
253
+
254
+ class MaskedAcc(Metric):
255
+ """需要几个参数就声明几个,logits/labels 仅为示例。"""
256
+ def compute(self, logits, labels, mask):
257
+ hits = ((logits.argmax(-1) == labels) & mask).sum()
258
+ n = mask.sum()
259
+ return (hits / n, n) # 返回 (值, 观测数):按样本数平均出全局结果
260
+
261
+ # 训练循环里:位置或具名喂入均可,多余观测自动忽略
262
+ metric.feed(logits, labels, mask)
263
+ metric.feed(logits=logits, labels=labels, mask=mask)
264
+ ```
265
+
266
+ - `compute` 返回 `(值, 观测数)` 元组:各 batch 按观测数(如样本数)平均(样本数不同时务必带上);
267
+ 只返回 float 时各 batch 等权平均
268
+ - 组合使用时交给 `MetricGroup.feed(...)` 统一分发:
269
+
270
+ ```python
271
+ metrics = MetricGroup({"loss": Mean(), "macc": MaskedAcc()})
272
+
273
+ # 每个 batch:feed 把观测累积进各指标的内存状态并自动记录本卡实时值
274
+ # (观测型指标观测放 args,标量指标按注册名,Mean 自动按批次样本数平均)
275
+ for logits, labels, mask in StepsBar(loader, epoch=epoch, metrics=metrics):
276
+ metrics.feed(args={"logits": logits, "labels": labels, "mask": mask},
277
+ loss=loss)
278
+
279
+ # epoch 末无需任何手动调用:StepsBar 结束时自动跨 GPU 合并记录 + reset
280
+ ```
281
+
282
+ 不用 StepsBar 包裹时(如独立验证脚本)才需要手动落盘:
283
+ `melog.scalar(metrics)` 跨 GPU 合并记录,`metrics.reset()` 清零开启下一轮。
284
+
285
+ **epoch 级指标——加实现 `prepare()`**:全局结果无法由各 batch 值按样本数加权平均还原时
286
+ (如 macro F1、AUC),每次喂入先用 `prepare()`"备料"——接收同样的观测,
287
+ 返回本批次贡献的增量(数值 / 字典 / 列表),框架自动累积并跨 GPU 合并
288
+ (数值求和、字典按键合并、列表拼接);epoch 末把合并后的总量交给
289
+ `compute()` 算出全局结果:
290
+
291
+ ```python
292
+ from melog import Metric
293
+
294
+ class F1(Metric):
295
+ """epoch 末才能计算的指标:累积混淆计数,末尾统一算。"""
296
+ def prepare(self, tp, fp, fn): # 每个 batch:本批次贡献的计数
297
+ return {"tp": tp, "fp": fp, "fn": fn}
298
+
299
+ def compute(self, tp, fp, fn): # epoch 末:由总量算出全局值
300
+ return 2 * tp / (2 * tp + fp + fn) if tp + fp + fn else float("nan")
301
+
302
+ # 使用(通常放进 MetricGroup / StepsBar 自动记录):
303
+ f1 = F1()
304
+ f1.feed(tp=2, fp=1, fn=0) # 位置或具名喂入均可
305
+ f1.result() # 跨 GPU 合并并计算(单进程直通)
306
+ ```
307
+
308
+ ## 多 GPU
309
+
310
+ 代码无需修改:按你的分布式训练方式(`torch.distributed` 初始化完成),
311
+ 每个 rank 进程各自跑同一份 melog 代码,`melog.init` 后库自动感知
312
+ 分布式环境(未装 torch 或单进程运行则一切退化为本地直通,行为完全一致)。
313
+
314
+ 自定义 `Metric` 时**多卡对你透明,不需要写任何分布式代码**。`compute()`
315
+ 拿到的永远是框架合并好的**全量**,合并规则按指标类型:
316
+
317
+ **实时指标(只实现 `compute`)**——各卡的本批值按各自实际的样本数加权平均:
318
+
319
+ ```text
320
+ 全局值 = Σ(各卡每批的 值 × 该批样本数) / Σ(各卡每批的 样本数)
321
+ ```
322
+
323
+ 即框架把每卡的 `(值, 观测数)` 对累加后相除——等价于把所有卡的样本
324
+ 放到一起算,而非"各卡平均值的平均"。
325
+
326
+ **epoch 级指标(`prepare` + `compute`)**——各卡 `prepare()` 返回的
327
+ 增量先在本地逐 batch 累积,epoch 末把各卡的本地增量跨卡合并,规则只有
328
+ 三条(可递归组合):
329
+
330
+ | 增量形态 | 合并规则 | 例 |
331
+ |---|---|---|
332
+ | 数值 | 求和 | `{"tp": 3}` + `{"tp": 2}` → `tp=5` |
333
+ | 字典 | 按键递归合并(值按同规则) | 两卡各自 `{"(1,1)": 2, "(1,0)": 1}` 与 `{"(1,1)": 1}` → `{"(1,1)": 3, "(1,0)": 1}` |
334
+ | 列表 / 元组 | 拼接 | 两卡各 64 个 `(得分, 标签)` 对 → 128 个 |
335
+
336
+ 三条规则都与合并次序无关(求和与拼接可结合),所以"逐 batch 累积"和
337
+ "跨卡合并"用的是同一套规则;epoch 末合并后的总量按 `compute()` 的
338
+ 形参名传入(字符串键字典展开为关键字参数,其余作为单个位置参数)。
339
+
340
+ 由此带来的性质:
341
+
342
+ - 不要求各卡数据同构:各卡观测到的类别、样本数可以不同,计数按
343
+ 键自动对齐;逐样本对自动拼接,结果与单卡全量计算一致。
344
+ - 唯一的纪律:**所有 rank 以相同顺序喂入**(正常训练代码天然满足)。
345
+ 记录交给 `StepsBar` / `MetricGroup` 自动完成即自动满足;手动
346
+ `melog.scalar(...)` 时所有 rank 执行同一位置即可。
347
+ - 验证正确性最简单的办法:单进程跑一遍的 `result()` 应等于多卡
348
+ 全局值(合并规则保证)。
349
+
350
+ ## API
351
+
352
+ ### `melog.init(...)`
353
+
354
+ | 参数 | 默认 | 说明 |
355
+ |---|---|---|
356
+ | `log_dir` | `./melog_runs` | 日志保存路径(即 run 目录,日志直接落在其中);重跑同一目录即断点续训 |
357
+ | `web_port` | 随机空闲端口 | Web 监听端口(`web_host` 默认 `127.0.0.1`,地址启动时自动打印,也可读 `melog.current().web_url`) |
358
+ | `enable_web` | `True` | 启动 Web 服务(仅 rank0) |
359
+ | `enable_progress` | `True` | 启用控制台进度条 |
360
+ | `reduce_op` | `"mean"` | 多 GPU 合并方式 |
361
+ | `flush_every` | `1` | 每 N 次 scalar 落盘一次 |
362
+ | `project` | `log_dir` 末级目录名 | 覆盖项目名 |
363
+
364
+ ### 主要方法
365
+
366
+ - `scalar(metrics, advance=0)` — 记录一批指标(dict 或 MetricGroup,后者跨 GPU 合并由内部完成);坐标由 `StepsBar` 自动管理(epoch 绑定 + 内部计步),调用频率即记录粒度(见上文)
367
+ - `image(name, data, caption=None)` / `audio(name, data, sr=22050, ...)` — 记录图像 / 音频,自动附着最近一次记录位置,Web 端页签展示(见上文)
368
+ - `StepsBar(iterable, epoch=None, metrics=None)` — tqdm 风格训练进度条(`from melog import StepsBar`,模块级 `melog.stepsbar(...)` 等价),**epoch 循环必须用它包裹**:包裹可迭代对象即自动推进,`scalar()` 指标实时显示在条上;`epoch=...` 绑定当前 epoch 并统一管理坐标;`metrics=...` 传入 MetricGroup 时 bar 实时显示本卡本地值(feed 零通信刷新),迭代自然结束自动 gather 全局值合并记录并重置组内指标(提前 break / 异常不触发)(见上文)
369
+ - 允许嵌套(如训练 bar 内嵌验证 bar):内部以栈管理,`current_bar()` 返回栈顶即当前环境;`scalar()` 的 postfix 与 `advance` 自动作用于栈顶,下层 bar 暂停渲染(数据照常累计),栈顶关闭后自动恢复下层渲染;提前 break / 抛异常时 bar 自动出栈(如需立即定稿可 `close()` 或用 with),否则等引用释放时兜底
370
+ - `current_bar()` — 当前栈顶进度条(无打开的 bar 时 `None`);深层函数需要手动推进 / 读数 / 写 postfix 时取它,免层层传参
371
+ - `log / success / error / warn` — print 风格控制台消息(`[MM-DD HH:MM:SS][文件名]` 前缀 + 图标 + 彩色文字),`print` 被拦截改走 `log()`(见上文)
372
+ - 收尾无需手动调用:进程退出时经 atexit 自动落盘剩余指标、定稿进度条、停 Web、还原 print
373
+ - 全局共享:`melog.init(...)` 创建活动实例后,模块级 `melog.scalar(...)` 等可在任意位置直接调用(见上文)
374
+
375
+ 进度条显示的指标可通过环境变量 `MELOG_DISABLE_PROGRESS=1` 全局关闭。
376
+
377
+ ## CLI 快速查看
378
+
379
+ 安装后可直接用命令行查看历史日志,自动打开浏览器:
380
+
381
+ ```bash
382
+ melog F:/runs/exp1/metrics-20260903_101010.melog # 指定会话日志(自动合并同目录全部会话)
383
+ melog F:/runs/exp1 # 指定 run 目录(合并其全部会话文件)
384
+ melog # 缺省在 ./melog_runs 中查找
385
+ melog F:/runs/exp1 --port 9000 --no-browser # 自定义端口 / 不开浏览器
386
+ ```
387
+
388
+ ## 项目结构
389
+
390
+ ```text
391
+ melog/
392
+ ├── __init__.py # 包入口:公开 API 导出
393
+ ├── core.py # Melog 主类:组合组件、调度记录、生命周期
394
+ ├── api/ # 全局入口 melog.init() + 模块级便捷接口(melog.scalar 等)
395
+ ├── cli/ # 命令行入口:melog <path>
396
+ ├── tracking/ # 训练记录上下文
397
+ │ ├── axis.py # Axis:全局 x / epoch 坐标的唯一裁决者
398
+ │ ├── steps_bar.py # StepsBar:tqdm 风格训练进度条(epoch 绑定 + 自动记录)
399
+ │ └── console.py # Console:控制台消息(log/success/error/warn)+ print 拦截
400
+ ├── storage/ # 持久化与产物
401
+ │ ├── journal.py # Journal:二进制日志落盘(批量写 + 即时追加 + 截断)
402
+ │ ├── melog_file.py # MelogFile:melog 二进制容器(符号表 + varint 编码 / 读取 / 重叠截断)
403
+ │ ├── media.py # 图像/音频落盘编码(路径复制或数组编码)
404
+ │ ├── media_log.py # MediaLog:媒体记录流程(定位->落盘->索引->日志->推送)
405
+ │ └── mirror.py # Mirror:控制台日志镜像(进度条就地刷新 + stdio 接管)
406
+ ├── metrics/ # 指标计算与跨 GPU 同步
407
+ │ ├── base.py # Metric 基类:compute 实时出值 / prepare+compute epoch 出值
408
+ │ ├── basic.py # Mean / Sum / Last / Count
409
+ │ ├── classification.py # Accuracy / Precision / Recall / F1 / ConfusionMatrix
410
+ │ └── group.py # MetricGroup:具名指标集合
411
+ ├── web/ # Web 可视化面板
412
+ │ ├── server.py # WebServer:uvicorn 线程生命周期
413
+ │ ├── app.py # ApiRoutes:路由注册(指标/媒体/文件浏览/加载/WS)
414
+ │ ├── store.py # MetricStore:内存指标历史
415
+ │ ├── view.py # MetricView:实时/历史视图切换
416
+ │ ├── media_store.py # MediaStore:实时媒体索引
417
+ │ ├── media_view.py # MediaView:媒体视图切换 + 文件白名单解析
418
+ │ ├── fs.py # FileBrowser:文件浏览
419
+ │ ├── loader.py # LogLoader / MediaLoader:二进制日志解析与会话合并
420
+ │ ├── ws.py # WsHub:WebSocket 广播
421
+ │ └── static/ # 前端(js 按类分模块)
422
+ └── utils/ # 通用工具类
423
+ ├── tqdm.py # 自研进度条(tqdm 兼容,样式重设计)
424
+ ├── downsample.py # 曲线降采样
425
+ ├── distributed.py # 多 GPU all_reduce / all_gather 原语
426
+ ├── bar_stack.py # BarStack / BarFrame:进度条栈帧管理(嵌套、恢复渲染)
427
+ └── epoch_end_iterable.py # EpochEndIterable:自然耗尽触发回调的迭代包装
428
+ ```
429
+
430
+ ## 开发
431
+
432
+ ```bash
433
+ pip install -e ".[dev]"
434
+ pytest tests -q
435
+ ```