kalfa 3.0.0.dev0__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.
Files changed (148) hide show
  1. kalfa/__init__.py +19 -0
  2. kalfa/api.py +284 -0
  3. kalfa/board/__init__.py +1 -0
  4. kalfa/board/answers.py +419 -0
  5. kalfa/board/server.py +536 -0
  6. kalfa/board/static/api.js +176 -0
  7. kalfa/board/static/app.js +122 -0
  8. kalfa/board/static/board.css +228 -0
  9. kalfa/board/static/charts.css +46 -0
  10. kalfa/board/static/components/badge.js +14 -0
  11. kalfa/board/static/components/chart.js +204 -0
  12. kalfa/board/static/components/diagram.js +857 -0
  13. kalfa/board/static/components/error.js +17 -0
  14. kalfa/board/static/components/loading.js +7 -0
  15. kalfa/board/static/components/progress.js +25 -0
  16. kalfa/board/static/components/settings.js +418 -0
  17. kalfa/board/static/components/spark.js +43 -0
  18. kalfa/board/static/components/table.js +156 -0
  19. kalfa/board/static/diagram.css +76 -0
  20. kalfa/board/static/format.js +138 -0
  21. kalfa/board/static/index.html +34 -0
  22. kalfa/board/static/plot.js +594 -0
  23. kalfa/board/static/predictions.css +20 -0
  24. kalfa/board/static/store.js +215 -0
  25. kalfa/board/static/views/compare.js +185 -0
  26. kalfa/board/static/views/home.js +90 -0
  27. kalfa/board/static/views/record/config.js +81 -0
  28. kalfa/board/static/views/record/curves.js +137 -0
  29. kalfa/board/static/views/record/data.js +378 -0
  30. kalfa/board/static/views/record/files.js +128 -0
  31. kalfa/board/static/views/record/log.js +164 -0
  32. kalfa/board/static/views/record/model.js +286 -0
  33. kalfa/board/static/views/record/overview.js +216 -0
  34. kalfa/board/static/views/record/plots.js +103 -0
  35. kalfa/board/static/views/record/predictions.js +834 -0
  36. kalfa/board/static/views/record.js +179 -0
  37. kalfa/board/static/views/runs.js +124 -0
  38. kalfa/board/static/views/sweep.js +282 -0
  39. kalfa/board/watch.py +87 -0
  40. kalfa/cli.py +371 -0
  41. kalfa/commands/__init__.py +0 -0
  42. kalfa/commands/common.py +8 -0
  43. kalfa/commands/export.py +117 -0
  44. kalfa/commands/generate.py +31 -0
  45. kalfa/commands/guide/__init__.py +101 -0
  46. kalfa/commands/guide/about.py +93 -0
  47. kalfa/commands/guide/calls.py +102 -0
  48. kalfa/commands/guide/sections.py +186 -0
  49. kalfa/commands/guide/seen.py +307 -0
  50. kalfa/commands/guide/show.py +106 -0
  51. kalfa/commands/guide/training.py +333 -0
  52. kalfa/commands/guide/words.py +97 -0
  53. kalfa/commands/legos.py +508 -0
  54. kalfa/commands/plots.py +27 -0
  55. kalfa/commands/report/__init__.py +64 -0
  56. kalfa/commands/report/page.py +112 -0
  57. kalfa/commands/report/predictions.py +216 -0
  58. kalfa/commands/report/sections.py +356 -0
  59. kalfa/commands/stop.py +60 -0
  60. kalfa/commands/sweep/__init__.py +202 -0
  61. kalfa/commands/sweep/checks.py +98 -0
  62. kalfa/commands/sweep/plan.py +183 -0
  63. kalfa/commands/sweep/points.py +123 -0
  64. kalfa/commands/sweep/runner.py +100 -0
  65. kalfa/commands/sweep/space.py +67 -0
  66. kalfa/commands/sweep/strategies.py +228 -0
  67. kalfa/core/__init__.py +0 -0
  68. kalfa/core/aliases.py +229 -0
  69. kalfa/core/build.py +727 -0
  70. kalfa/core/check.py +550 -0
  71. kalfa/core/coerce.py +36 -0
  72. kalfa/core/context.py +149 -0
  73. kalfa/core/device.py +63 -0
  74. kalfa/core/errors.py +45 -0
  75. kalfa/core/graph.py +304 -0
  76. kalfa/core/history.py +70 -0
  77. kalfa/core/loop.py +710 -0
  78. kalfa/core/make.py +121 -0
  79. kalfa/core/plan.py +156 -0
  80. kalfa/core/predict.py +290 -0
  81. kalfa/core/protocols.py +122 -0
  82. kalfa/core/record.py +347 -0
  83. kalfa/core/refs.py +88 -0
  84. kalfa/core/rng.py +100 -0
  85. kalfa/core/rules.py +513 -0
  86. kalfa/core/run.py +568 -0
  87. kalfa/core/spec.py +413 -0
  88. kalfa/core/state.py +100 -0
  89. kalfa/core/wrap.py +671 -0
  90. kalfa/records/__init__.py +24 -0
  91. kalfa/records/compare.py +86 -0
  92. kalfa/records/reader.py +561 -0
  93. kalfa/records/stats/__init__.py +36 -0
  94. kalfa/records/stats/choices.py +282 -0
  95. kalfa/records/stats/classes.py +236 -0
  96. kalfa/records/stats/measures.py +144 -0
  97. kalfa/records/stats/pairs.py +225 -0
  98. kalfa/records/stats/tables.py +153 -0
  99. kalfa/records/sweep.py +244 -0
  100. kalfa/std/__init__.py +17 -0
  101. kalfa/std/actions.py +35 -0
  102. kalfa/std/common.py +56 -0
  103. kalfa/std/data/__init__.py +1 -0
  104. kalfa/std/data/base.py +45 -0
  105. kalfa/std/data/columns.py +190 -0
  106. kalfa/std/data/flow.py +317 -0
  107. kalfa/std/data/images.py +443 -0
  108. kalfa/std/data/options.py +147 -0
  109. kalfa/std/data/pre.py +84 -0
  110. kalfa/std/data/splits.py +140 -0
  111. kalfa/std/data/stream.py +294 -0
  112. kalfa/std/data/table.py +642 -0
  113. kalfa/std/data/text.py +436 -0
  114. kalfa/std/data/transforms.py +327 -0
  115. kalfa/std/data/weights.py +113 -0
  116. kalfa/std/data/window.py +135 -0
  117. kalfa/std/figure.py +37 -0
  118. kalfa/std/init.py +72 -0
  119. kalfa/std/loggers.py +105 -0
  120. kalfa/std/losses.py +161 -0
  121. kalfa/std/metrics.py +463 -0
  122. kalfa/std/models.py +122 -0
  123. kalfa/std/notes.py +1553 -0
  124. kalfa/std/objectives.py +360 -0
  125. kalfa/std/plots/__init__.py +75 -0
  126. kalfa/std/plots/classification.py +207 -0
  127. kalfa/std/plots/common.py +44 -0
  128. kalfa/std/plots/curves.py +56 -0
  129. kalfa/std/plots/images.py +188 -0
  130. kalfa/std/plots/pairs.py +66 -0
  131. kalfa/std/plots/predictions.py +154 -0
  132. kalfa/std/samplers.py +261 -0
  133. kalfa/std/samples.py +72 -0
  134. kalfa/std/schedules.py +74 -0
  135. kalfa/std/steps.py +133 -0
  136. kalfa/std/triggers.py +135 -0
  137. kalfa/summary.py +296 -0
  138. kalfa/tui/__init__.py +4 -0
  139. kalfa/tui/app.py +107 -0
  140. kalfa/tui/config.py +61 -0
  141. kalfa/tui/guide.py +100 -0
  142. kalfa/tui/jobs.py +97 -0
  143. kalfa/tui/legos.py +62 -0
  144. kalfa/tui/records.py +439 -0
  145. kalfa-3.0.0.dev0.dist-info/METADATA +261 -0
  146. kalfa-3.0.0.dev0.dist-info/RECORD +148 -0
  147. kalfa-3.0.0.dev0.dist-info/WHEEL +4 -0
  148. kalfa-3.0.0.dev0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,164 @@
1
+ import { messageOf } from "../../components/error.js";
2
+ import { when } from "../../format.js";
3
+
4
+
5
+ const LIMIT = 200;
6
+ const TAIL = 300;
7
+ const OUTPUT = "log.txt";
8
+ const CODE = "overflow: auto; max-height: 50vh; margin: 0; padding: 10px; background: var(--surface-2); border-radius: 6px; white-space: pre";
9
+ const SKIP = ["at", "turn", "kind"];
10
+ const TONES = {
11
+ run_start: "var(--done)",
12
+ run_end: "var(--done)",
13
+ resume: "var(--running)",
14
+ checkpoint: "var(--running)",
15
+ patch: "var(--stale)",
16
+ rule: "var(--stale)",
17
+ warning: "var(--stale)",
18
+ stop: "var(--failed)",
19
+ stopping: "var(--failed)",
20
+ failure: "var(--failed)",
21
+ error: "var(--failed)",
22
+ };
23
+ const ROW = "display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; padding: 3px 0";
24
+
25
+ function shown(value) {
26
+ if (value === null || value === undefined) return "none";
27
+ if (Array.isArray(value)) return value.map(shown).join(", ");
28
+ if (typeof value === "object") {
29
+ if (typeof value.ref === "string") return "@" + value.ref;
30
+ return JSON.stringify(value);
31
+ }
32
+ return String(value);
33
+ }
34
+
35
+ export default {
36
+ name: "LogTab",
37
+ props: {
38
+ record: { type: Object, default: null },
39
+ path: { type: String, default: "" },
40
+ api: { default: null },
41
+ version: { type: Number, default: 0 },
42
+ },
43
+ data() {
44
+ return { fetched: null, error: "", loading: false, kind: "", output: "", outputError: "" };
45
+ },
46
+ computed: {
47
+ row() { return ROW; },
48
+ code() { return CODE; },
49
+ tail() { return TAIL; },
50
+ hasOutput() {
51
+ const files = this.record && Array.isArray(this.record.files) ? this.record.files : [];
52
+ return files.some(item => item && item.path === OUTPUT);
53
+ },
54
+ outputUrl() { return this.api ? this.api.fileUrl(this.path, OUTPUT, null) : ""; },
55
+ events() {
56
+ if (Array.isArray(this.fetched)) return this.fetched;
57
+ return this.record && Array.isArray(this.record.events) ? this.record.events : [];
58
+ },
59
+ kinds() {
60
+ const found = [];
61
+ for (const event of this.events) {
62
+ if (event && event.kind && !found.includes(event.kind)) found.push(event.kind);
63
+ }
64
+ return found;
65
+ },
66
+ groups() {
67
+ const groups = [];
68
+ for (const event of this.events) {
69
+ if (!event || (this.kind && event.kind !== this.kind)) continue;
70
+ const turn = event.turn === undefined || event.turn === null ? null : event.turn;
71
+ const last = groups[groups.length - 1];
72
+ if (last && last.turn === turn) last.events.push(event);
73
+ else groups.push({ key: String(groups.length), turn, events: [event] });
74
+ }
75
+ return groups.reverse().map(group => ({ ...group, events: group.events.slice().reverse() }));
76
+ },
77
+ countText() {
78
+ const asked = Array.isArray(this.fetched) ? ", up to " + LIMIT + " asked" : ", the latest the record carries";
79
+ return this.events.length + " events" + asked;
80
+ },
81
+ button() { return Array.isArray(this.fetched) ? "refresh" : "load the last " + LIMIT; },
82
+ },
83
+ watch: {
84
+ record() {
85
+ if (Array.isArray(this.fetched)) this.load();
86
+ this.loadOutput();
87
+ },
88
+ },
89
+ mounted() { this.loadOutput(); },
90
+ methods: {
91
+ async loadOutput() {
92
+ if (!this.api || !this.hasOutput) return;
93
+ const path = this.path;
94
+ try {
95
+ const text = await this.api.getText(this.api.fileUrl(path, OUTPUT, TAIL));
96
+ if (path !== this.path) return;
97
+ this.output = text;
98
+ this.outputError = "";
99
+ } catch (error) {
100
+ if (path === this.path) this.outputError = messageOf(error);
101
+ }
102
+ },
103
+ moment(value) { return when(value); },
104
+ title(group) { return group.turn === null ? "no turn" : "turn " + group.turn; },
105
+ tone(kind) { return { "--tone": TONES[kind] || "var(--muted)", minWidth: "84px", textAlign: "center" }; },
106
+ details(event) {
107
+ return Object.entries(event).filter(([key]) => !SKIP.includes(key)).map(([key, value]) => key + " " + shown(value))
108
+ .join(", ");
109
+ },
110
+ async load() {
111
+ if (!this.api) {
112
+ this.error = "the api prop is missing";
113
+ return;
114
+ }
115
+ const path = this.path;
116
+ this.loading = true;
117
+ try {
118
+ const found = await this.api.getJson(this.api.recordUrl(path, "events", { limit: LIMIT }));
119
+ if (path !== this.path) return;
120
+ this.fetched = Object.freeze(Array.isArray(found) ? found : []);
121
+ this.error = "";
122
+ } catch (error) {
123
+ if (path === this.path) this.error = messageOf(error);
124
+ } finally {
125
+ this.loading = false;
126
+ }
127
+ },
128
+ },
129
+ template: `
130
+ <div class="page pane-log">
131
+ <section class="card" v-if="hasOutput">
132
+ <div class="card-head">
133
+ <span class="card-title">output</span>
134
+ <span class="card-tools">
135
+ <span class="muted small">the last {{ tail }} lines of log.txt, followed while the record runs</span>
136
+ <a class="tool" :href="outputUrl" download="log.txt">save</a>
137
+ </span>
138
+ </div>
139
+ <div class="error-box" v-if="outputError">{{ outputError }}</div>
140
+ <pre class="mono" :style="code">{{ output }}</pre>
141
+ </section>
142
+ <div class="muted small">The events of the record from events.jsonl, newest first and grouped by turn: the start and the end
143
+ of the run, checkpoints, the patches rules applied, stops and failures.</div>
144
+ <div class="toolbar">
145
+ <button type="button" class="tool" :disabled="loading" @click="load">{{ button }}</button>
146
+ <select class="filter" v-model="kind" style="width: auto">
147
+ <option value="">every kind</option>
148
+ <option v-for="item in kinds" :key="item" :value="item">{{ item }}</option>
149
+ </select>
150
+ <span class="muted small">{{ countText }}</span>
151
+ <span class="muted small" v-if="loading">reading</span>
152
+ </div>
153
+ <div class="error-box" v-if="error">{{ error }}</div>
154
+ <div class="empty" v-if="!groups.length">no events yet</div>
155
+ <section class="card" v-for="group in groups" :key="group.key">
156
+ <div class="card-head"><span class="card-title">{{ title(group) }}</span><span class="muted small">{{ group.events.length }}</span></div>
157
+ <div v-for="(event, index) in group.events" :key="index" :style="row">
158
+ <span class="badge" :style="tone(event.kind)">{{ event.kind || "event" }}</span>
159
+ <span class="muted mono">{{ moment(event.at) }}</span>
160
+ <span class="mono" style="overflow-wrap: anywhere">{{ details(event) }}</span>
161
+ </div>
162
+ </section>
163
+ </div>`,
164
+ };
@@ -0,0 +1,286 @@
1
+ import { Diagram, shapeText } from "../../components/diagram.js";
2
+ import { integer, percent } from "../../format.js";
3
+
4
+
5
+ const COLUMNS = "minmax(120px, 1.3fr) minmax(100px, 1fr) 96px minmax(80px, 160px) 44px";
6
+ const ROW = "display: inline-grid; grid-template-columns: " + COLUMNS + "; gap: 10px; align-items: center; width: calc(100% - 20px)";
7
+ const LEAF = "display: grid; grid-template-columns: " + COLUMNS + "; gap: 10px; align-items: center; padding: 2px 0 2px 20px";
8
+
9
+ function shapeOf(value) {
10
+ if (Array.isArray(value)) return value.join(" × ");
11
+ if (value && typeof value === "object") return JSON.stringify(value);
12
+ return String(value);
13
+ }
14
+
15
+ function shown(value) {
16
+ if (value === null || value === undefined) return "";
17
+ if (typeof value === "object") return JSON.stringify(value);
18
+ return String(value);
19
+ }
20
+
21
+ const ModuleNode = {
22
+ name: "ModuleNode",
23
+ props: {
24
+ node: { type: Object, required: true },
25
+ total: { type: Number, default: 0 },
26
+ depth: { type: Number, default: 0 },
27
+ },
28
+ computed: {
29
+ row() { return ROW; },
30
+ leaf() { return LEAF; },
31
+ children() { return Array.isArray(this.node.children) ? this.node.children : []; },
32
+ type() { return this.node.class || ""; },
33
+ parameters() { return integer(Number(this.node.parameters) || 0); },
34
+ share() {
35
+ const own = Number(this.node.parameters) || 0;
36
+ return this.total > 0 ? Math.max(0, Math.min(1, own / this.total)) : 0;
37
+ },
38
+ shareText() { return percent(this.share); },
39
+ fill() { return { width: (this.share * 100).toFixed(1) + "%" }; },
40
+ opened() { return this.depth === 0; },
41
+ },
42
+ template: `
43
+ <details v-if="children.length" class="module" :open="opened">
44
+ <summary style="cursor: pointer">
45
+ <span :style="row">
46
+ <span class="mono">{{ node.name }}</span>
47
+ <span class="muted small">{{ type }}</span>
48
+ <span class="num mono">{{ parameters }}</span>
49
+ <span class="progress-track"><span class="progress-fill" :style="fill"></span></span>
50
+ <span class="num muted small">{{ shareText }}</span>
51
+ </span>
52
+ </summary>
53
+ <div class="module-children" style="padding-left: 18px">
54
+ <module-node v-for="(child, index) in children" :key="index" :node="child" :total="total" :depth="depth + 1"></module-node>
55
+ </div>
56
+ </details>
57
+ <div v-else class="module" :style="leaf">
58
+ <span class="mono">{{ node.name }}</span>
59
+ <span class="muted small">{{ type }}</span>
60
+ <span class="num mono">{{ parameters }}</span>
61
+ <span class="progress-track"><span class="progress-fill" :style="fill"></span></span>
62
+ <span class="num muted small">{{ shareText }}</span>
63
+ </div>`,
64
+ };
65
+
66
+ ModuleNode.components = { "module-node": ModuleNode };
67
+
68
+ const CROWDED = 60;
69
+
70
+ function graphOf(model) {
71
+ const graph = model.graph;
72
+ return graph && typeof graph === "object" && Array.isArray(graph.nodes) && graph.nodes.length ? graph : null;
73
+ }
74
+
75
+ function missingText(model) {
76
+ if (!Object.prototype.hasOwnProperty.call(model, "graph")) {
77
+ return "This record was written before kalfa drew model diagrams, so the module tree is shown instead.";
78
+ }
79
+ const note = model.graph_note ? " (" + model.graph_note + ")" : "";
80
+ return "kalfa drew no diagram for this model" + note + ". It traces a model on the first batch with the fields the " +
81
+ "model reads, and when a loss calls the model with inputs of its own, such as a diffusion denoiser or a generator, " +
82
+ "on the inputs of its first call in training, which exist once the first turn is over. The module tree is shown " +
83
+ "instead.";
84
+ }
85
+
86
+ function topGroups(graph) {
87
+ const groups = Array.isArray(graph.groups) ? graph.groups : [];
88
+ return groups.filter(group => group && (group.parent === null || group.parent === undefined)).map(group => String(group.id));
89
+ }
90
+
91
+ function outputRows(outputs) {
92
+ return Object.entries(outputs || {}).map(([label, shape]) => ({ label, shape: shapeOf(shape) }));
93
+ }
94
+
95
+ function shapeRows(outputs, perSample) {
96
+ const sampled = new Map(perSample.map(item => [item.label, item.shape]));
97
+ const rows = outputs.map(item => ({ key: item.label, label: item.label, batch: item.shape, sample: sampled.get(item.label) || "" }));
98
+ const named = new Set(outputs.map(item => item.label));
99
+ for (const item of perSample) {
100
+ if (!named.has(item.label)) rows.push({ key: "sample:" + item.label, label: item.label, batch: "", sample: item.shape });
101
+ }
102
+ return rows;
103
+ }
104
+
105
+ export default {
106
+ name: "ModelTab",
107
+ components: { "module-node": ModuleNode, "k-diagram": Diagram },
108
+ props: {
109
+ record: { type: Object, default: null },
110
+ path: { type: String, default: "" },
111
+ api: { default: null },
112
+ version: { type: Number, default: 0 },
113
+ },
114
+ data() {
115
+ return { views: {}, picked: {} };
116
+ },
117
+ computed: {
118
+ nodes() {
119
+ const plan = (this.record && this.record.plan) || {};
120
+ return plan.nodes && typeof plan.nodes === "object" ? plan.nodes : {};
121
+ },
122
+ models() {
123
+ const summary = (this.record && this.record.model_summary) || {};
124
+ return Object.entries(summary).map(([name, entry]) => {
125
+ const model = entry && typeof entry === "object" ? entry : {};
126
+ const node = this.nodes["models." + name] || {};
127
+ const params = node.params && typeof node.params === "object" ? node.params : {};
128
+ const parameters = Number(model.parameters) || 0;
129
+ const trainable = Number(model.trainable) || 0;
130
+ const graph = graphOf(model);
131
+ const kinds = graph ? graph.nodes.map(item => item && item.kind) : [];
132
+ const outputs = outputRows(model.outputs);
133
+ const perSample = graph ? graph.nodes.filter(item => item && item.kind === "output")
134
+ .map(item => ({ label: String(item.label), shape: shapeText(item.shape) || "?" })) : [];
135
+ return {
136
+ name,
137
+ type: model.class || node.use || "",
138
+ parameters,
139
+ total: integer(parameters),
140
+ trainable: integer(trainable),
141
+ frozen: model.trainable !== undefined && model.trainable !== null && trainable !== parameters,
142
+ tree: Array.isArray(model.tree) ? model.tree : [],
143
+ shapes: shapeRows(outputs, perSample),
144
+ params: Object.entries(params).map(([key, value]) => ({ key, value: shown(value) })),
145
+ graph,
146
+ drawnFromCall: Boolean(graph) && model.graph_from === "first training call",
147
+ missing: graph ? "" : missingText(model),
148
+ collapsed: graph && graph.nodes.length > CROWDED ? topGroups(graph) : [],
149
+ modules: kinds.filter(kind => kind === "module").length,
150
+ ops: kinds.filter(kind => kind === "op").length,
151
+ truncated: Boolean(graph && graph.truncated),
152
+ };
153
+ });
154
+ },
155
+ details() {
156
+ return Object.fromEntries(this.models.map(model => [model.name, this.detail(model)]));
157
+ },
158
+ },
159
+ methods: {
160
+ viewOf(model) {
161
+ const chosen = this.views[model.name];
162
+ if (chosen === "diagram" && !model.graph) return "tree";
163
+ return chosen || (model.graph ? "diagram" : "tree");
164
+ },
165
+ show(model, view) {
166
+ this.views = { ...this.views, [model.name]: view };
167
+ },
168
+ select(model, node) {
169
+ this.picked = { ...this.picked, [model.name]: node ? String(node.id) : null };
170
+ },
171
+ detail(model) {
172
+ const id = this.picked[model.name];
173
+ if (!model.graph || id === null || id === undefined) return null;
174
+ const byId = new Map(model.graph.nodes.filter(Boolean).map(item => [String(item.id), item]));
175
+ const node = byId.get(id);
176
+ if (!node) return null;
177
+ const groups = new Map((model.graph.groups || []).filter(Boolean).map(group => [String(group.id), group]));
178
+ const name = (other) => {
179
+ const found = byId.get(String(other));
180
+ return found ? String(found.label) + (found.path ? " " + found.path : "") : String(other);
181
+ };
182
+ const edges = Array.isArray(model.graph.edges) ? model.graph.edges.filter(Boolean) : [];
183
+ const group = node.group === null || node.group === undefined ? null : groups.get(String(node.group));
184
+ const rows = [
185
+ ["path", node.path || ""],
186
+ ["class", node.class || ""],
187
+ ["detail", node.detail && node.detail !== node.path ? node.detail : ""],
188
+ ["shape", shapeText(node.shape)],
189
+ ["parameters", typeof node.parameters === "number" ? integer(node.parameters) : ""],
190
+ ["inside", group ? [group.label, group.detail].filter(Boolean).join(", ") : ""],
191
+ ].filter(row => row[1] !== "" && row[1] !== null && row[1] !== undefined);
192
+ return {
193
+ label: String(node.label),
194
+ kind: String(node.kind || ""),
195
+ rows,
196
+ inputs: edges.filter(edge => String(edge.to) === id).map(edge => ({ name: name(edge.from), shape: shapeText(edge.shape) })),
197
+ outputs: edges.filter(edge => String(edge.from) === id).map(edge => ({ name: name(edge.to), shape: shapeText(edge.shape) })),
198
+ };
199
+ },
200
+ },
201
+ template: `
202
+ <div class="page pane-model">
203
+ <div class="muted small">Every model of the run from model_summary.json, written when the run starts and again when it
204
+ ends: its parameter counts, a diagram of one forward pass on the first batch, its modules as a tree and the shapes of
205
+ its outputs. In the diagram, shapes are per sample (the batch size left out) and the corner number is the parameter
206
+ count; in the tree, the bar is the share of the model's parameters a module holds.</div>
207
+ <div class="empty" v-if="!models.length">no model_summary.json yet; the run writes it before its first turn</div>
208
+ <section class="card" v-for="model in models" :key="model.name">
209
+ <div class="card-head">
210
+ <span class="card-title">{{ model.name }}</span>
211
+ <span class="muted mono">{{ model.type }}</span>
212
+ </div>
213
+ <div class="toolbar">
214
+ <span class="chip">{{ model.total }} parameters</span>
215
+ <span class="chip">{{ model.trainable }} trainable<span v-if="model.frozen">, the rest frozen</span></span>
216
+ <span class="chip" v-if="model.graph">{{ model.modules }} module calls, {{ model.ops }} operations</span>
217
+ <span class="chip" v-if="model.drawnFromCall"
218
+ title="the batch does not hold this model's inputs, so the diagram follows its first call in training">drawn from its first call in training</span>
219
+ </div>
220
+ <div class="muted small mono" v-if="model.params.length">
221
+ <span class="chip" v-for="item in model.params" :key="item.key">{{ item.key }} {{ item.value }}</span>
222
+ </div>
223
+ <div class="model-views">
224
+ <button type="button" class="tab" :class="{ active: viewOf(model) === 'diagram' }" :disabled="!model.graph"
225
+ :title="model.graph ? 'one forward pass on the first batch' : 'no diagram for this model'" @click="show(model, 'diagram')">diagram</button>
226
+ <button type="button" class="tab" :class="{ active: viewOf(model) === 'tree' }" @click="show(model, 'tree')">module tree</button>
227
+ <button type="button" class="tab" :class="{ active: viewOf(model) === 'outputs' }" @click="show(model, 'outputs')">output shapes</button>
228
+ </div>
229
+ <template v-if="viewOf(model) === 'diagram'">
230
+ <div class="notice-box model-note" v-if="model.truncated">The diagram stops after the first {{ model.modules + model.ops }}
231
+ module calls and operations; the module tree shows the whole model.</div>
232
+ <div class="muted small model-note" v-if="model.collapsed.length">This model has many nodes, so its top groups start
233
+ folded; click a folded group to open it, or use expand all.</div>
234
+ <div class="model-diagram">
235
+ <k-diagram :graph="model.graph" :collapsed="model.collapsed" :height="540" :title="model.name + '_diagram'"
236
+ @select="select(model, $event)"></k-diagram>
237
+ <div class="model-detail">
238
+ <template v-if="details[model.name]">
239
+ <div class="model-detail-title">{{ details[model.name].label }} <span class="chip">{{ details[model.name].kind }}</span></div>
240
+ <dl>
241
+ <template v-for="row in details[model.name].rows" :key="row[0]">
242
+ <dt>{{ row[0] }}</dt>
243
+ <dd :class="{ mono: row[0] !== 'inside' }">{{ row[1] }}</dd>
244
+ </template>
245
+ </dl>
246
+ <div v-if="details[model.name].inputs.length">
247
+ <div class="muted small">takes</div>
248
+ <ul><li v-for="(item, index) in details[model.name].inputs" :key="index">{{ item.name }} <span class="mono muted">{{ item.shape }}</span></li></ul>
249
+ </div>
250
+ <div v-if="details[model.name].outputs.length">
251
+ <div class="muted small">feeds</div>
252
+ <ul><li v-for="(item, index) in details[model.name].outputs" :key="index">{{ item.name }} <span class="mono muted">{{ item.shape }}</span></li></ul>
253
+ </div>
254
+ </template>
255
+ <div class="muted small" v-else>Click a node to see its path, class, shape, parameters and the nodes it takes from
256
+ and feeds.</div>
257
+ </div>
258
+ </div>
259
+ </template>
260
+ <template v-else-if="viewOf(model) === 'tree'">
261
+ <div class="notice-box model-note" v-if="model.missing">{{ model.missing }}</div>
262
+ <div class="module-tree" v-if="model.tree.length" style="margin-top: 8px">
263
+ <module-node v-for="(node, index) in model.tree" :key="index" :node="node" :total="model.parameters" :depth="0"></module-node>
264
+ </div>
265
+ <div class="empty" v-else>no submodules recorded</div>
266
+ </template>
267
+ <template v-else>
268
+ <div class="muted small model-note">The shapes the model's outputs had on the first batch, the batch size first; the
269
+ per sample column comes from the diagram.</div>
270
+ <div class="table-wrap" v-if="model.shapes.length">
271
+ <table class="data">
272
+ <thead><tr><th>output</th><th>shape on the first batch</th><th>per sample</th></tr></thead>
273
+ <tbody>
274
+ <tr v-for="item in model.shapes" :key="item.key">
275
+ <td class="mono">{{ item.label }}</td>
276
+ <td class="mono">{{ item.batch }}</td>
277
+ <td class="mono">{{ item.sample }}</td>
278
+ </tr>
279
+ </tbody>
280
+ </table>
281
+ </div>
282
+ <div class="empty" v-else>no output shapes recorded: the model could not run on the first batch with the fields it reads</div>
283
+ </template>
284
+ </section>
285
+ </div>`,
286
+ };
@@ -0,0 +1,216 @@
1
+ import Badge from "../../components/badge.js";
2
+ import Table from "../../components/table.js";
3
+ import { left, number, percent, seconds, when, withinText } from "../../format.js";
4
+
5
+
6
+ const LAST_COLUMNS = [
7
+ { key: "name", label: "series", className: "mono" },
8
+ { key: "value", label: "last value", numeric: true },
9
+ ];
10
+ const TRACE = "overflow: auto; max-height: 50vh; margin: 8px 0 0; white-space: pre; font-size: 12px";
11
+ const PARAM_COLUMNS = [
12
+ { key: "name", label: "param", className: "mono" },
13
+ { key: "value", label: "value", numeric: true },
14
+ ];
15
+
16
+ function present(value) {
17
+ return value !== null && value !== undefined && value !== "";
18
+ }
19
+
20
+ function shown(value) {
21
+ if (!present(value)) return "";
22
+ if (typeof value === "number") return number(value);
23
+ if (Array.isArray(value)) return "[" + value.map(shown).join(", ") + "]";
24
+ if (typeof value === "object") {
25
+ if (typeof value.ref === "string") return "@" + value.ref;
26
+ return JSON.stringify(value);
27
+ }
28
+ return String(value);
29
+ }
30
+
31
+ function described(value) {
32
+ if (value && typeof value === "object" && !Array.isArray(value) && typeof value.use === "string") {
33
+ const rest = Object.entries(value).filter(([key]) => key !== "use").map(([key, item]) => key + " " + shown(item));
34
+ return [value.use].concat(rest).join(", ");
35
+ }
36
+ return shown(value);
37
+ }
38
+
39
+ export default {
40
+ name: "OverviewTab",
41
+ components: { "k-badge": Badge, "k-table": Table },
42
+ props: {
43
+ record: { type: Object, default: null },
44
+ path: { type: String, default: "" },
45
+ api: { default: null },
46
+ version: { type: Number, default: 0 },
47
+ },
48
+ computed: {
49
+ lastColumns() { return LAST_COLUMNS; },
50
+ paramColumns() { return PARAM_COLUMNS; },
51
+ summary() { return (this.record && this.record.summary) || {}; },
52
+ progress() { return (this.record && this.record.progress) || {}; },
53
+ plan() { return (this.record && this.record.plan) || {}; },
54
+ status() { return this.summary.status || this.progress.status || "unknown"; },
55
+ best() { return this.summary.best || (this.record && this.record.best) || null; },
56
+ bestValue() { return this.best && present(this.best.value) ? number(this.best.value) : "no value yet"; },
57
+ bestMode() { return this.best && this.best.mode === "max" ? "highest" : "lowest"; },
58
+ bestTurn() { return this.best && present(this.best.turn) ? String(this.best.turn) : ""; },
59
+ turn() { return present(this.summary.turn) ? this.summary.turn : (this.progress.turn || 0); },
60
+ total() { return present(this.summary.total) ? this.summary.total : this.progress.total; },
61
+ unit() { return this.summary.unit || this.progress.unit || ""; },
62
+ elapsed() { return present(this.summary.seconds) ? seconds(this.summary.seconds) : seconds(0); },
63
+ started() { return present(this.summary.started) ? "started " + when(this.summary.started) : ""; },
64
+ fraction() {
65
+ const known = this.progress.fraction;
66
+ if (typeof known === "number" && Number.isFinite(known)) return Math.max(0, Math.min(1, known));
67
+ const total = Number(this.total);
68
+ const turn = Number(this.turn);
69
+ if (!total || !Number.isFinite(total) || !Number.isFinite(turn)) return null;
70
+ return Math.max(0, Math.min(1, turn / total));
71
+ },
72
+ within() { return withinText(this.progress.within); },
73
+ remaining() { return left(this.progress.eta); },
74
+ failure() { return (this.record && this.record.failure) || null; },
75
+ traceback() { return this.failure && this.failure.traceback ? String(this.failure.traceback) : ""; },
76
+ failureTitle() {
77
+ if (!this.failure) return "";
78
+ return [this.failure.type, this.failure.message].filter(Boolean).join(": ");
79
+ },
80
+ trace() { return TRACE; },
81
+ hasFraction() { return this.fraction !== null; },
82
+ fractionText() { return this.hasFraction ? percent(this.fraction) : ""; },
83
+ fill() { return { width: (this.hasFraction ? (this.fraction * 100).toFixed(1) : "0") + "%" }; },
84
+ turnText() { return present(this.total) ? "turn " + this.turn + " of " + this.total : "turn " + this.turn; },
85
+ lastRows() {
86
+ const watched = this.best ? this.best.series : null;
87
+ return Object.entries(this.summary.last || {}).map(([name, value]) => ({ name, value, best: name === watched }));
88
+ },
89
+ manyLast() { return this.lastRows.length > 8; },
90
+ minimizes() { return Object.entries(this.summary.minimizes || {}).map(([name, loss]) => ({ name, loss: String(loss) })); },
91
+ params() {
92
+ return Object.entries(this.summary.params || {}).map(([name, value]) => ({
93
+ name,
94
+ value: typeof value === "number" || typeof value === "string" ? value : shown(value),
95
+ }));
96
+ },
97
+ manyParams() { return this.params.length > 8; },
98
+ fired() { return Array.isArray(this.summary.rules) ? this.summary.rules : []; },
99
+ rules() {
100
+ const fired = this.fired;
101
+ const planned = Array.isArray(this.plan.rules) ? this.plan.rules : [];
102
+ return planned.map((rule, index) => ({
103
+ key: rule.id || String(index),
104
+ id: rule.id || "rule " + (index + 1),
105
+ fired: fired.includes(rule.id),
106
+ when: described(rule.when),
107
+ patch: (Array.isArray(rule.patch) ? rule.patch : []).map(item => item.path + " = " + shown(item.value)).join("; "),
108
+ stop: Boolean(rule.stop),
109
+ once: Boolean(rule.once),
110
+ }));
111
+ },
112
+ identity() {
113
+ const summary = this.summary;
114
+ const sweep = summary.sweep;
115
+ const rows = [
116
+ ["name", summary.name],
117
+ ["device", summary.device],
118
+ ["host", summary.host],
119
+ ["started", present(summary.started) ? when(summary.started) : ""],
120
+ ["updated", present(summary.updated) ? when(summary.updated) : ""],
121
+ ["finished", present(summary.finished) ? when(summary.finished) : ""],
122
+ ["elapsed", present(summary.seconds) ? seconds(summary.seconds) : ""],
123
+ ["failure", summary.failure],
124
+ ["stopped by", summary.stopped_by],
125
+ ["stop reason", summary.stop_reason],
126
+ ["resumed from", summary.resumed_from],
127
+ ["sweep point", sweep && present(sweep.id) ? String(sweep.id) : ""],
128
+ ["seed", this.plan.seed],
129
+ ["kalfa", this.plan.kalfa],
130
+ ];
131
+ return rows.filter(row => present(row[1])).map(([name, value]) => ({ name, value: shown(value) }));
132
+ },
133
+ },
134
+ methods: {
135
+ bestRow(row) { return row && row.best ? "selected" : ""; },
136
+ },
137
+ template: `
138
+ <div class="page pane-overview">
139
+ <div class="error-box" v-if="summary.failure">failed: {{ failureTitle || summary.failure }}
140
+ <details v-if="traceback">
141
+ <summary>traceback</summary>
142
+ <pre class="mono" :style="trace">{{ traceback }}</pre>
143
+ </details>
144
+ </div>
145
+ <div class="notice-box" v-if="summary.stop_reason">stopped: {{ summary.stop_reason }}</div>
146
+ <div class="cards">
147
+ <section class="card">
148
+ <div class="stat-label">status</div>
149
+ <div class="stat-value"><k-badge :status="status"></k-badge></div>
150
+ <span class="progress" v-if="hasFraction" style="width: 100%">
151
+ <span class="progress-track"><span class="progress-fill" :style="fill"></span></span>
152
+ <span class="progress-text">{{ fractionText }}</span>
153
+ </span>
154
+ <div class="muted small">{{ turnText }}<span v-if="unit"> {{ unit }}</span></div>
155
+ <div class="muted small" v-if="within">{{ within }}</div>
156
+ </section>
157
+ <section class="card">
158
+ <div class="stat-label">best</div>
159
+ <div class="stat-value mono">{{ bestValue }}</div>
160
+ <div class="muted small" v-if="best">{{ best.series }}, {{ bestMode }} is best<span v-if="bestTurn">, reached at turn {{ bestTurn }}</span></div>
161
+ <div class="muted small" v-else>the plan names no checkpoint watch series</div>
162
+ </section>
163
+ <section class="card">
164
+ <div class="stat-label">elapsed</div>
165
+ <div class="stat-value">{{ elapsed }}</div>
166
+ <div class="muted small">{{ started }}</div>
167
+ <div class="muted small" v-if="remaining">{{ remaining }}</div>
168
+ </section>
169
+ <section class="card">
170
+ <div class="stat-label">rules fired</div>
171
+ <div class="stat-value">{{ fired.length }}</div>
172
+ <div class="muted small" v-if="fired.length"><span class="chip mono" v-for="name in fired" :key="name">{{ name }}</span></div>
173
+ <div class="muted small" v-else>no rule has fired yet</div>
174
+ </section>
175
+ </div>
176
+ <div class="grid">
177
+ <section class="card">
178
+ <div class="card-head"><span class="card-title">last values</span><span class="muted small">the last line of history.jsonl</span></div>
179
+ <k-table :columns="lastColumns" :rows="lastRows" row-key="name" :filterable="manyLast" placeholder="filter series"
180
+ :row-class="bestRow" empty="no values yet"></k-table>
181
+ <div class="muted small" v-for="row in minimizes" :key="row.name">optimizer {{ row.name }} minimizes {{ row.loss }}</div>
182
+ </section>
183
+ <section class="card">
184
+ <div class="card-head"><span class="card-title">params</span></div>
185
+ <k-table :columns="paramColumns" :rows="params" row-key="name" :filterable="manyParams" placeholder="filter params"
186
+ empty="no params"></k-table>
187
+ </section>
188
+ <section class="card">
189
+ <div class="card-head"><span class="card-title">identity</span></div>
190
+ <div class="table-wrap">
191
+ <table class="data">
192
+ <tbody>
193
+ <tr v-for="row in identity" :key="row.name"><td class="muted">{{ row.name }}</td><td class="mono">{{ row.value }}</td></tr>
194
+ </tbody>
195
+ </table>
196
+ </div>
197
+ </section>
198
+ </div>
199
+ <section class="card" v-if="rules.length">
200
+ <div class="card-head"><span class="card-title">rules of the plan</span></div>
201
+ <div class="table-wrap">
202
+ <table class="data">
203
+ <thead><tr><th>rule</th><th>when</th><th>patch</th><th>fired</th></tr></thead>
204
+ <tbody>
205
+ <tr v-for="rule in rules" :key="rule.key" :class="{ selected: rule.fired }">
206
+ <td class="mono">{{ rule.id }}<span class="muted" v-if="rule.once"> once</span><span class="muted" v-if="rule.stop"> stops</span></td>
207
+ <td class="mono">{{ rule.when }}</td>
208
+ <td class="mono">{{ rule.patch }}</td>
209
+ <td>{{ rule.fired ? "yes" : "" }}</td>
210
+ </tr>
211
+ </tbody>
212
+ </table>
213
+ </div>
214
+ </section>
215
+ </div>`,
216
+ };