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
kalfa/std/loggers.py ADDED
@@ -0,0 +1,105 @@
1
+ import sys
2
+
3
+ from kalfa.core.aliases import register
4
+ from kalfa.core.errors import BuildError
5
+ from kalfa.core.protocols import Logger
6
+ from kalfa.std.common import numeric, positive_int
7
+
8
+
9
+ MISSING = "tensorboard logger needs the tensorboard package (uv sync installs the dependencies of kalfa)"
10
+ COUNTERS = ("turn", "update", "seconds")
11
+ PER_UPDATE = ("loss/", "lr/", "grad_norm/")
12
+
13
+
14
+ def summary_writer():
15
+ try:
16
+ from torch.utils.tensorboard import SummaryWriter
17
+ except ImportError as exc:
18
+ raise BuildError(MISSING) from exc
19
+ return SummaryWriter
20
+
21
+
22
+ def per_update(key):
23
+ return key.startswith(PER_UPDATE) and key.count("/") == 1
24
+
25
+
26
+ def series_of(line):
27
+ return [key for key, value in line.items() if key not in COUNTERS and numeric(value)]
28
+
29
+
30
+ class TensorBoard(Logger):
31
+ def __init__(self, dir=None, updates=True, flush_every=1):
32
+ self.dir = dir
33
+ self.updates = bool(updates)
34
+ self.flush_every = positive_int(flush_every, "flush_every")
35
+ self.writer = None
36
+ self.writer_class = None
37
+ self.turns = 0
38
+
39
+ def bind(self, ctx):
40
+ self.writer_class = summary_writer()
41
+
42
+ def directory(self, ctx):
43
+ if self.dir is not None:
44
+ return str(self.dir)
45
+ record = getattr(ctx, "record", None)
46
+ if record is None:
47
+ where = f"{self.path}: " if self.path else ""
48
+ raise BuildError(f"{where}the tensorboard logger needs a dir when the run writes no record")
49
+ return str(record.path("tensorboard"))
50
+
51
+ def open(self, ctx):
52
+ if self.writer is None:
53
+ if self.writer_class is None:
54
+ self.writer_class = summary_writer()
55
+ self.writer = self.writer_class(log_dir=self.directory(ctx))
56
+ return self.writer
57
+
58
+ def turn(self, ctx, line):
59
+ writer = self.open(ctx)
60
+ step = int(line["turn"])
61
+ for key in series_of(line):
62
+ if self.updates and per_update(key):
63
+ continue
64
+ writer.add_scalar(key, float(line[key]), step)
65
+ self.turns += 1
66
+ if self.turns % self.flush_every == 0:
67
+ writer.flush()
68
+
69
+ def update(self, ctx, line):
70
+ if not self.updates:
71
+ return
72
+ writer = self.open(ctx)
73
+ step = int(line["update"])
74
+ for key in series_of(line):
75
+ if per_update(key):
76
+ writer.add_scalar(key, float(line[key]), step)
77
+
78
+ def close(self, ctx):
79
+ if self.writer is None:
80
+ return
81
+ writer, self.writer = self.writer, None
82
+ writer.flush()
83
+ writer.close()
84
+
85
+
86
+ class Echo(Logger):
87
+ def __init__(self, series=None, stream=None):
88
+ self.series = None if series is None else [series] if isinstance(series, str) else list(series)
89
+ self.stream = stream
90
+
91
+ def turn(self, ctx, line):
92
+ keys = series_of(line) if self.series is None else self.series
93
+ pairs = [f"{key} {line[key]:.4g}" for key in keys if numeric(line.get(key))]
94
+ stream = sys.stderr if self.stream is None else self.stream
95
+ stream.write(" ".join([f"turn {line['turn']}", *pairs]) + "\n")
96
+ flush = getattr(stream, "flush", None)
97
+ if callable(flush):
98
+ flush()
99
+
100
+
101
+ register("tensorboard", "kalfa.std.loggers.TensorBoard", slot="logger",
102
+ doc="writes every history series per turn and the update losses to <record>/tensorboard for TensorBoard; "
103
+ "needs the tensorboard package (uv sync installs the dependencies of kalfa)")
104
+ register("echo", "kalfa.std.loggers.Echo", slot="logger",
105
+ doc="prints the named series (or all) per turn to stderr; a logger to copy when writing your own")
kalfa/std/losses.py ADDED
@@ -0,0 +1,161 @@
1
+ import torch
2
+ from torch import nn
3
+
4
+ from kalfa.core.aliases import register
5
+ from kalfa.core.errors import RunError
6
+ from kalfa.core.protocols import Objective
7
+ from kalfa.core.wrap import loss_scalar
8
+ from kalfa.std.common import classes_last_of
9
+
10
+
11
+ def name_of(of):
12
+ return getattr(of, "name", "") or getattr(of, "__name__", "") or type(of).__name__
13
+
14
+
15
+ def label_of(of, term):
16
+ name = name_of(of)
17
+ return name if term is None else f"{name}.{term}"
18
+
19
+
20
+ def term_of(value, term, name):
21
+ if term is None:
22
+ return loss_scalar(value, name)
23
+ if isinstance(value, dict) and term in value:
24
+ return value[term]
25
+ have = f"its terms are {sorted(value)}" if isinstance(value, dict) else "it returns one value"
26
+ raise RunError(f"loss {name!r} has no term {term!r}; {have}")
27
+
28
+
29
+ def items_of(items, kind, owner):
30
+ if isinstance(items, (dict, kind)):
31
+ items = [items]
32
+ out = []
33
+ for item in items or ():
34
+ if isinstance(item, kind):
35
+ out.append(item)
36
+ elif isinstance(item, dict):
37
+ out.append(kind(**item))
38
+ else:
39
+ raise TypeError(f"{owner} takes a list of {{of: ..., ...}} mappings, got {type(item).__name__}")
40
+ return out
41
+
42
+
43
+ class Term:
44
+ def __init__(self, of, weight=1.0, term=None):
45
+ self.of = of
46
+ self.weight = float(weight)
47
+ self.term = term
48
+
49
+ def label(self):
50
+ return label_of(self.of, self.term)
51
+
52
+
53
+ class WeightedSum(Objective):
54
+ def __init__(self, terms):
55
+ super().__init__()
56
+ self.terms = items_of(terms, Term, "weighted_sum")
57
+ if not self.terms:
58
+ raise ValueError('weighted_sum needs terms: a list of {of: "@losses.<name>", weight: <value>}')
59
+
60
+ def forward(self, outputs, batch, ctx):
61
+ out = {"loss": None}
62
+ total = None
63
+ for term in self.terms:
64
+ value = term_of(term.of(outputs, batch, ctx), term.term, name_of(term.of))
65
+ out[term.label()] = value
66
+ total = term.weight * value if total is None else total + term.weight * value
67
+ out["loss"] = total
68
+ return out
69
+
70
+
71
+ class Constraint:
72
+ def __init__(self, of, epsilon, term=None, scale=1.0, damping=1.0, lmbda_init=0.0):
73
+ self.of = of
74
+ self.epsilon = float(epsilon)
75
+ self.term = term
76
+ self.scale = float(scale)
77
+ self.damping = float(damping)
78
+ self.lmbda_init = float(lmbda_init)
79
+
80
+ def label(self):
81
+ return label_of(self.of, self.term)
82
+
83
+
84
+ class Mdmm(Objective):
85
+ def __init__(self, objective, constraints, term=None):
86
+ super().__init__()
87
+ self.objective = objective
88
+ self.constraints = items_of(constraints, Constraint, "mdmm")
89
+ self.term = term
90
+ if not self.constraints:
91
+ raise ValueError('mdmm needs constraints: a list of {of: "@losses.<name>", epsilon: <value>}')
92
+ self.lmbda = nn.Parameter(torch.tensor([entry.lmbda_init for entry in self.constraints],
93
+ dtype=torch.float32))
94
+
95
+ def forward(self, outputs, batch, ctx):
96
+ value = term_of(self.objective(outputs, batch, ctx), self.term, name_of(self.objective))
97
+ out = {"loss": None, "objective": value}
98
+ for position, constraint in enumerate(self.constraints):
99
+ held = term_of(constraint.of(outputs, batch, ctx), constraint.term, name_of(constraint.of))
100
+ infeasibility = constraint.epsilon - held
101
+ value = value + constraint.scale * (self.lmbda[position] * infeasibility
102
+ + constraint.damping * infeasibility ** 2 / 2)
103
+ label = constraint.label()
104
+ out[f"lambda/{label}"] = self.lmbda[position]
105
+ out[f"inf/{label}"] = infeasibility
106
+ out["loss"] = value
107
+ return out
108
+
109
+
110
+ class CrossEntropy(nn.CrossEntropyLoss):
111
+ def __init__(self, weight=None, label_smoothing=0.0, classes_last=None):
112
+ super().__init__(weight=weight, label_smoothing=label_smoothing)
113
+ self.classes_last = classes_last
114
+
115
+ def forward(self, pred, target):
116
+ if classes_last_of(pred, target, self.classes_last, self, "cross_entropy") and pred.ndim >= 3:
117
+ pred = pred.movedim(-1, 1)
118
+ return super().forward(pred, target)
119
+
120
+
121
+ def cross_entropy(weight=None, label_smoothing=0.0, classes_last=None):
122
+ if weight is not None:
123
+ weight = torch.as_tensor(weight, dtype=torch.float32)
124
+ return CrossEntropy(weight=weight, label_smoothing=float(label_smoothing), classes_last=classes_last)
125
+
126
+
127
+ LOSSES = (
128
+ ("mse", "torch.nn.MSELoss", "mean squared error"),
129
+ ("l1", "torch.nn.L1Loss", "mean absolute error (the short name mae is the metric; write torch.nn.L1Loss or l1 "
130
+ "for the loss)"),
131
+ ("huber", "torch.nn.HuberLoss", "huber loss, quadratic below delta and linear above"),
132
+ ("bce_logits", "torch.nn.BCEWithLogitsLoss", "binary cross entropy over logits"),
133
+ ("poisson_nll", "torch.nn.PoissonNLLLoss", "poisson negative log likelihood"),
134
+ ("cross_entropy", "kalfa.std.losses.cross_entropy", "cross entropy over logits (classes in dim 1 or last, as a "
135
+ "language model gives them) with optional class weights "
136
+ "(for example @data.class_weights) and label smoothing; "
137
+ "classes_last: true or false states the layout when the "
138
+ "tokens and the vocabulary have the same size"),
139
+ ("weighted_sum", "kalfa.std.losses.WeightedSum", "a weighted sum of other losses: terms: [{of: @losses.a, "
140
+ "weight: 1.0, term: <key of a dict result>}]"),
141
+ ("mdmm", "kalfa.std.losses.Mdmm", "modified differential method of multipliers: an objective plus constraints "
142
+ "[{of: @losses.b, epsilon, scale, damping, lmbda_init, term}] with a "
143
+ "lambda parameter per constraint; give lmbda to an optimizer group with a "
144
+ "negative lr"),
145
+ )
146
+
147
+ OPTIMIZERS = (
148
+ ("adam", "torch.optim.Adam", "adam"),
149
+ ("adamw", "torch.optim.AdamW", "adam with decoupled weight decay"),
150
+ ("sgd", "torch.optim.SGD", "stochastic gradient descent with optional momentum"),
151
+ )
152
+
153
+
154
+ def register_names():
155
+ for name, target, doc in LOSSES:
156
+ register(name, target, slot="loss", doc=doc)
157
+ for name, target, doc in OPTIMIZERS:
158
+ register(name, target, slot="optimizer", doc=doc)
159
+
160
+
161
+ register_names()