olympiad-lab 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.
- olympiad_lab-0.1.0/CHANGELOG.md +93 -0
- olympiad_lab-0.1.0/LICENSE +21 -0
- olympiad_lab-0.1.0/MANIFEST.in +2 -0
- olympiad_lab-0.1.0/PKG-INFO +285 -0
- olympiad_lab-0.1.0/README.md +254 -0
- olympiad_lab-0.1.0/pyproject.toml +48 -0
- olympiad_lab-0.1.0/setup.cfg +4 -0
- olympiad_lab-0.1.0/src/olympiad/__init__.py +21 -0
- olympiad_lab-0.1.0/src/olympiad/_util.py +312 -0
- olympiad_lab-0.1.0/src/olympiad/_version.py +3 -0
- olympiad_lab-0.1.0/src/olympiad/api.py +296 -0
- olympiad_lab-0.1.0/src/olympiad/audit.py +81 -0
- olympiad_lab-0.1.0/src/olympiad/checkpoints.py +158 -0
- olympiad_lab-0.1.0/src/olympiad/config.py +112 -0
- olympiad_lab-0.1.0/src/olympiad/display.py +166 -0
- olympiad_lab-0.1.0/src/olympiad/drills.py +134 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/__init__.py +52 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/_common.py +290 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/batches.py +138 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/describe.py +372 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/display.py +406 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/forward.py +350 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/frameworks.py +153 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/ids.py +280 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/media.py +56 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/models.py +250 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/numbers.py +460 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/numpy_.py +171 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/pandas_.py +187 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/parts.py +175 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/submission.py +185 -0
- olympiad_lab-0.1.0/src/olympiad/evidence/torch_.py +201 -0
- olympiad_lab-0.1.0/src/olympiad/finish.py +306 -0
- olympiad_lab-0.1.0/src/olympiad/hooks.py +298 -0
- olympiad_lab-0.1.0/src/olympiad/messages.py +110 -0
- olympiad_lab-0.1.0/src/olympiad/reading.py +435 -0
- olympiad_lab-0.1.0/src/olympiad/redact.py +304 -0
- olympiad_lab-0.1.0/src/olympiad/sender.py +536 -0
- olympiad_lab-0.1.0/src/olympiad/session.py +645 -0
- olympiad_lab-0.1.0/src/olympiad/spool.py +591 -0
- olympiad_lab-0.1.0/src/olympiad/submit.py +133 -0
- olympiad_lab-0.1.0/src/olympiad/transport.py +306 -0
- olympiad_lab-0.1.0/src/olympiad_lab.egg-info/PKG-INFO +285 -0
- olympiad_lab-0.1.0/src/olympiad_lab.egg-info/SOURCES.txt +116 -0
- olympiad_lab-0.1.0/src/olympiad_lab.egg-info/dependency_links.txt +1 -0
- olympiad_lab-0.1.0/src/olympiad_lab.egg-info/requires.txt +1 -0
- olympiad_lab-0.1.0/src/olympiad_lab.egg-info/top_level.txt +1 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_drill_keyword.py +70 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_exit_budget.py +148 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_finish_memory.py +92 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_kernel_surrogates.py +100 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_reading_answers_dropped.py +130 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_spawn_workers.py +79 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_spool_gone.py +295 -0
- olympiad_lab-0.1.0/tests/adversarial/breaks/test_adv_breaks_stale_reading.py +122 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_colab_identity.py +131 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_describe_copies.py +84 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_redaction_rewrites_code.py +122 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_secrets_slip_through.py +149 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_split_body_caps.py +97 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_split_dataset_rows.py +116 -0
- olympiad_lab-0.1.0/tests/adversarial/privacy/test_adv_privacy_windows_home.py +65 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_batch_dict.py +96 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_load_containers.py +131 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_load_keyword_names.py +99 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_loop_downsampling.py +104 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_loop_history.py +156 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_loop_pairs.py +120 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_model_forward.py +205 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_model_sklearn_scores.py +111 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_reading_metric_fn.py +223 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_split_frames.py +150 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_split_no_ids.py +166 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_split_path_ids.py +107 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_split_tables_without_id.py +117 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_stage_argument.py +69 -0
- olympiad_lab-0.1.0/tests/adversarial/variations/test_adv_var_submit_frames.py +181 -0
- olympiad_lab-0.1.0/tests/conftest.py +286 -0
- olympiad_lab-0.1.0/tests/fake_server.py +621 -0
- olympiad_lab-0.1.0/tests/test_audit.py +268 -0
- olympiad_lab-0.1.0/tests/test_budgets.py +119 -0
- olympiad_lab-0.1.0/tests/test_chaos.py +205 -0
- olympiad_lab-0.1.0/tests/test_checkpoints.py +481 -0
- olympiad_lab-0.1.0/tests/test_contract.py +468 -0
- olympiad_lab-0.1.0/tests/test_display.py +146 -0
- olympiad_lab-0.1.0/tests/test_drills.py +191 -0
- olympiad_lab-0.1.0/tests/test_evidence_arrays.py +463 -0
- olympiad_lab-0.1.0/tests/test_evidence_display.py +494 -0
- olympiad_lab-0.1.0/tests/test_evidence_models.py +212 -0
- olympiad_lab-0.1.0/tests/test_evidence_parts.py +668 -0
- olympiad_lab-0.1.0/tests/test_evidence_parts_torch.py +159 -0
- olympiad_lab-0.1.0/tests/test_evidence_plain.py +478 -0
- olympiad_lab-0.1.0/tests/test_evidence_real_libraries.py +241 -0
- olympiad_lab-0.1.0/tests/test_evidence_safety.py +498 -0
- olympiad_lab-0.1.0/tests/test_evidence_sklearn.py +188 -0
- olympiad_lab-0.1.0/tests/test_evidence_support.py +620 -0
- olympiad_lab-0.1.0/tests/test_evidence_torch.py +665 -0
- olympiad_lab-0.1.0/tests/test_finish.py +306 -0
- olympiad_lab-0.1.0/tests/test_fork.py +145 -0
- olympiad_lab-0.1.0/tests/test_fuzz.py +310 -0
- olympiad_lab-0.1.0/tests/test_hardening.py +748 -0
- olympiad_lab-0.1.0/tests/test_hooks.py +355 -0
- olympiad_lab-0.1.0/tests/test_import.py +150 -0
- olympiad_lab-0.1.0/tests/test_isolation.py +59 -0
- olympiad_lab-0.1.0/tests/test_never_raises.py +436 -0
- olympiad_lab-0.1.0/tests/test_notebook_e2e.py +381 -0
- olympiad_lab-0.1.0/tests/test_packaging.py +195 -0
- olympiad_lab-0.1.0/tests/test_reading.py +426 -0
- olympiad_lab-0.1.0/tests/test_real_evidence.py +209 -0
- olympiad_lab-0.1.0/tests/test_real_server.py +225 -0
- olympiad_lab-0.1.0/tests/test_redact.py +329 -0
- olympiad_lab-0.1.0/tests/test_sender.py +657 -0
- olympiad_lab-0.1.0/tests/test_spool.py +387 -0
- olympiad_lab-0.1.0/tests/test_start.py +457 -0
- olympiad_lab-0.1.0/tests/test_status_export.py +259 -0
- olympiad_lab-0.1.0/tests/test_submit.py +274 -0
- olympiad_lab-0.1.0/tests/test_transport.py +364 -0
- olympiad_lab-0.1.0/tests/test_util.py +216 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are listed here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/). Within the `0.x` releases the HTTP API stays at
|
|
6
|
+
version 1 and stays backward compatible.
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-11-13
|
|
9
|
+
|
|
10
|
+
First release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- The `ck` object with the steps `start`, `reading`, `load`, `look`, `split`, `batch`, `model`,
|
|
15
|
+
`loop`, `eval`, `submit`, `drill_start`, `drill_check`, `status`, `finish` and `export`, and
|
|
16
|
+
`ck.last` with the last answer.
|
|
17
|
+
- Evidence descriptions for plain Python objects, NumPy arrays, pandas tables, torch tensors and
|
|
18
|
+
models, scikit-learn models, Keras models and Hugging Face outputs. The libraries are only used
|
|
19
|
+
when the notebook has already imported them.
|
|
20
|
+
- IPython cell hooks that record one event per executed cell (source, duration, error). A cell
|
|
21
|
+
that was interrupted is recorded with the error type `KeyboardInterrupt`, also when it was
|
|
22
|
+
waiting in an `await`; an error inside a cell magic such as `%%capture` belongs to that cell.
|
|
23
|
+
- Redaction on the student's machine, before anything is stored or sent, in cell source, error
|
|
24
|
+
messages, tracebacks, the source of the metric function and the notebook: the student id
|
|
25
|
+
becomes `<student_id>`; tokens of well-known shapes (Hugging Face, OpenAI, GitHub, Google,
|
|
26
|
+
Slack, AWS, Kaggle, a PEM private key), the password in an address and the token of an
|
|
27
|
+
`Authorization` header become `<redacted>`. A value given to a name that ends in key, token,
|
|
28
|
+
secret, auth or pass (in Python code, in a shell or environment line, in the query of an
|
|
29
|
+
address) is removed when it has the shape of a key: twelve or more letters and digits, mixed
|
|
30
|
+
through one another, or a UUID. A value given to a name that ends in password or passwd is
|
|
31
|
+
removed whatever it holds. Everything else stays as written, in the recorded source and in
|
|
32
|
+
the notebook alike: `label_key = "category_name"` is not a key. The home folder is written
|
|
33
|
+
as `~`.
|
|
34
|
+
- A background sender for cell events: batches of at most 100 events, sent every 20 seconds, when
|
|
35
|
+
50 events wait, ahead of every step, at `finish` and at interpreter exit. A batch that failed is
|
|
36
|
+
sent again unchanged, so the server never stores it twice.
|
|
37
|
+
- A write-ahead local spool (`olympiad_<lab>_<student>.jsonl`) with a fall-back to the temp
|
|
38
|
+
folder and to memory, and `ck.export()` to hand everything over in one file. The running
|
|
39
|
+
kernel finds its lines also after the file was renamed, moved, emptied or deleted; a line that
|
|
40
|
+
was cut off in the middle never swallows the record after it; a record the files hold twice is
|
|
41
|
+
exported once; and an export never overwrites the local file it is made from.
|
|
42
|
+
- Bounded memory: at most 5,000 cell events wait in memory; in a longer outage the newer ones
|
|
43
|
+
wait in the spool file only and are sent from there, in order. A spool file above 200 MB takes
|
|
44
|
+
new cell records without their source and says so once. Waiting calls are bounded by count and
|
|
45
|
+
by size.
|
|
46
|
+
- Offline mode: when the server cannot be reached the session keeps recording, tries to connect
|
|
47
|
+
again in the background and from later steps, and delivers what waited.
|
|
48
|
+
- Time budgets for every step, one automatic retry where repeating a request is safe, and a short
|
|
49
|
+
pause after a failed connection so that an outage does not slow every cell.
|
|
50
|
+
- Every check and every drill check carries a `call_id` that stays the same on every retry of it
|
|
51
|
+
(the one right after a broken connection, the one from the background queue, an import of the
|
|
52
|
+
exported file), so the server judges and counts each check once.
|
|
53
|
+
- Only an answer that has the fields of the API counts as delivered. Anything else that answers at
|
|
54
|
+
the server address (a placeholder, a network sign-in page) is treated as no answer, so its
|
|
55
|
+
records stay in the spool and in the export.
|
|
56
|
+
- Notebook capture at `finish` in Colab, Jupyter and VS Code, without image outputs when the
|
|
57
|
+
notebook is very large, and without the account name Colab writes into its metadata (the name
|
|
58
|
+
of the kernel stays, so the file is still a valid notebook). In Colab the wait for the notebook
|
|
59
|
+
page is said first (`Reading the notebook; this can take a few seconds.`) and ends after 20
|
|
60
|
+
seconds. Where the notebook program does not hand the notebook over, the cells the kernel ran
|
|
61
|
+
are sent in its place (`notebook_source: "history"`).
|
|
62
|
+
- Audit mode (`OLYMPIAD_AUDIT_DIR`): no network, every call written to a local log. A `lab.json`
|
|
63
|
+
left in that folder (the `lab` object of a start answer) stands in for the answer that never
|
|
64
|
+
arrives: a table is then written in the lab's file format and stages are spelled as the lab
|
|
65
|
+
spells them.
|
|
66
|
+
- The guarantee that no step raises into the notebook, in any state and from any thread, and that
|
|
67
|
+
a worker process stays silent, forked or spawned, even where it runs `ck.start` again.
|
|
68
|
+
- One line for every situation a student can get into: a step before `ck.start`
|
|
69
|
+
(`! olympiad: run the first cell (ck.start) before this one.`), a second `ck.start` with another
|
|
70
|
+
lab or id (`! START: switched from lab ... to lab ...`; what the old session still had to send
|
|
71
|
+
goes out in the background), an answer of `ck.reading` that cannot be read, a metric function
|
|
72
|
+
that raises or returns no number.
|
|
73
|
+
- No step keeps a reference to what it was given: data the student deletes is really gone.
|
|
74
|
+
- Evidence for the forms a notebook naturally holds: a loader batch given as one argument or as
|
|
75
|
+
a dict (`{"image": ..., "label": ...}`), an `(X, y)` pair or a dict of splits in `ck.load`, a
|
|
76
|
+
model that returns `(logits, hidden)`. A loss history may be a list, records, a log with a
|
|
77
|
+
counter, a table or a history object; from records of two curves (`(train_loss, val_loss)` as
|
|
78
|
+
tuples, lists or rows of a table) the training loss is taken and the curves are never mixed.
|
|
79
|
+
- The metric function of `ck.reading` is called with NumPy arrays first when NumPy is loaded and
|
|
80
|
+
with plain lists only when that raises; without NumPy it gets lists. A function written for
|
|
81
|
+
lists that joins its two arguments with `+` (two arrays would be added) is recognised and gets
|
|
82
|
+
lists; torch tensors and pandas Series are tried for a function that needs them.
|
|
83
|
+
- The model description of `ck.model` carries `n_classes`: the number of classes a fitted
|
|
84
|
+
scikit-learn classifier knows, also through a pipeline, a search and a calibrated or voting
|
|
85
|
+
wrapper. The server needs it for models that return one value per row.
|
|
86
|
+
- `ck.submit` in a lab that takes JSON lines (`submission_format: "jsonl"` in the lab info of the
|
|
87
|
+
start answer) writes a DataFrame or a list of records as one JSON object per row. A file or
|
|
88
|
+
text is sent as it is; without the field a table is written as CSV.
|
|
89
|
+
- Output that every channel can carry: each block has an HTML and a plain-text form, colours with
|
|
90
|
+
a contrast of at least 4.5 on its own background, and no half characters that a kernel could
|
|
91
|
+
not send. Text from the server is shown as text, never as HTML.
|
|
92
|
+
- Leaving the interpreter never takes longer than the exit budget of 3 seconds, whatever the
|
|
93
|
+
network does: the last round runs in the sender threads, all sessions at the same time.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The olympiad-lab authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: olympiad-lab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Checkpoints for AI Olympiad training labs: sends evidence from a notebook to a scorer server and shows the verdict.
|
|
5
|
+
Author: olympiad-lab maintainers
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SattamAltwaim/olympiad-lab
|
|
8
|
+
Project-URL: Repository, https://github.com/SattamAltwaim/olympiad-lab
|
|
9
|
+
Project-URL: Issues, https://github.com/SattamAltwaim/olympiad-lab/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/SattamAltwaim/olympiad-lab/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: education,notebook,jupyter,colab,kaggle,machine-learning,olympiad
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Education
|
|
14
|
+
Classifier: Framework :: IPython
|
|
15
|
+
Classifier: Framework :: Jupyter
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Education
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: requests>=2.25
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# olympiad-lab
|
|
33
|
+
|
|
34
|
+
Checkpoints for AI Olympiad training labs.
|
|
35
|
+
|
|
36
|
+
A lab notebook calls a few short steps (`ck.load`, `ck.look`, `ck.split`, ...) between the
|
|
37
|
+
student's own code. Each step describes what was just built and sends that description to the
|
|
38
|
+
scorer server of the course. The server decides whether the step passes and answers with one
|
|
39
|
+
line. The library holds no expected value, no rule and no label: the notebook sends evidence, the
|
|
40
|
+
server decides.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
pip install olympiad-lab
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The only dependency is `requests`. NumPy, pandas, torch and the other libraries of a lab are
|
|
49
|
+
used when the notebook has already imported them, and never imported by this package.
|
|
50
|
+
|
|
51
|
+
## What this library records
|
|
52
|
+
|
|
53
|
+
Read this before you use it. From the moment `ck.start()` runs, and only in that notebook:
|
|
54
|
+
|
|
55
|
+
- **Every cell you run**: the first 4000 characters of its source, how long it took, whether it
|
|
56
|
+
raised, and for an error its type, its message and the last 15 lines of the traceback.
|
|
57
|
+
- **What each step describes**: types, shapes, row counts, column names, data types, small number
|
|
58
|
+
summaries (minimum, maximum, mean, count of missing values) and the first 200 characters of how
|
|
59
|
+
the object prints. For `ck.split` the row ids (or their hashes for very large sets). For
|
|
60
|
+
`ck.loop` up to 200 loss values. For `ck.eval` the score.
|
|
61
|
+
- **What you hand in**: the submission file, the answers of `ck.reading` (including the source of
|
|
62
|
+
the metric function you pass), and at `ck.finish()` the notebook itself. When the notebook
|
|
63
|
+
program does not hand it over, the cells this kernel ran are sent in its place.
|
|
64
|
+
- **Where it runs**: Colab, Kaggle, Jupyter, VS Code or script; the Python and IPython versions;
|
|
65
|
+
the operating system name; the GPU model.
|
|
66
|
+
|
|
67
|
+
It does not record other notebooks, other files, the values of environment variables, or your
|
|
68
|
+
account. Cells that ran before `ck.start()` are not recorded.
|
|
69
|
+
|
|
70
|
+
Three things are taken out on your computer, before anything is stored or sent, from cell source,
|
|
71
|
+
error messages, tracebacks and the notebook:
|
|
72
|
+
|
|
73
|
+
- **Your student id** becomes `<student_id>`.
|
|
74
|
+
- **Anything that looks like a secret** becomes `<redacted>`: access tokens of well-known shapes
|
|
75
|
+
(Hugging Face, OpenAI, GitHub, Google, Slack, AWS, Kaggle, a private key); a value given to a
|
|
76
|
+
name such as `api_key`, `HF_TOKEN` or `os.environ["KAGGLE_KEY"]`, also in a line such as
|
|
77
|
+
`%env KAGGLE_KEY=...`, when it has the shape of a key (twelve or more letters and digits, mixed
|
|
78
|
+
through one another); any value given to a name that ends in `password`; the password inside
|
|
79
|
+
an address and the token of an `Authorization` header.
|
|
80
|
+
- **Names**: your home folder is written as `~`, and the account name that Colab writes into a
|
|
81
|
+
notebook is removed.
|
|
82
|
+
|
|
83
|
+
Code that holds no secret stays exactly as you wrote it: `label_key = "category_name"` and
|
|
84
|
+
`stop_token = "<|end_of_text|>"` are not keys, although their names end like one.
|
|
85
|
+
|
|
86
|
+
This is a safety net, not a guarantee. A secret that looks like any other word cannot be told
|
|
87
|
+
apart from one, so do not paste keys into a notebook: use `getpass.getpass()` or the secrets
|
|
88
|
+
panel of Colab or Kaggle.
|
|
89
|
+
|
|
90
|
+
The records go to the scorer server over HTTPS and are used by the instructors of the course for
|
|
91
|
+
assessment. A copy is kept next to the notebook in `olympiad_<lab>_<student>.jsonl`.
|
|
92
|
+
`ck.start()` says so every time:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
This lab records cell runs, cell source and errors for assessment.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Use
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
!pip -q install olympiad-lab
|
|
102
|
+
from olympiad import ck
|
|
103
|
+
|
|
104
|
+
ck.start("nlp-01", "S07-K4QX") # the lab id and your student id
|
|
105
|
+
ck.load(train=train_df, test=test_df) # LOAD: the data is in memory
|
|
106
|
+
ck.look(train_df.head(), size=len(train_df)) # LOOK: shows a sample, prints the size
|
|
107
|
+
ck.split(train_ids=tr.id, val_ids=va.id) # SPLIT: no row in both sets
|
|
108
|
+
ck.batch(X_tr[:32], y_tr[:32]) # BATCH: one batch or feature table
|
|
109
|
+
ck.model(model, X_va[:32]) # MODEL: runs the model on one batch
|
|
110
|
+
ck.loop(losses) # LOOP: the training loss fell
|
|
111
|
+
ck.eval(val_f1, metric="macro_f1") # EVAL: prints the validation score
|
|
112
|
+
ck.submit("submission.csv") # SUBMIT: a file, a DataFrame or the text of a file
|
|
113
|
+
ck.finish() # sends what waits, prints a summary
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Every step prints a tick or a cross, one sentence, and a strip of the lab's parts:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
✓ BATCH: Batch shape and data type are right.
|
|
120
|
+
Parts 4 of 7: LOAD ✓ | LOOK ✓ | SPLIT ✓ | BATCH ✓ | MODEL - | EVAL - | SUBMIT -
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
In a notebook the same block is shown as a small box that reads the same in a light and in a
|
|
124
|
+
dark theme. A part that the lab does not use answers `! LOOP is not part of this lab.` A step
|
|
125
|
+
that runs before `ck.start()` does nothing and says:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
! olympiad: run the first cell (ck.start) before this one.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Other steps:
|
|
132
|
+
|
|
133
|
+
| Step | What it does |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `ck.reading(metric_fn=..., trivial={...}, rewards="b", format={...}, facts="...")` | Sends the task reading. It is scored after the lab; no score is shown. |
|
|
136
|
+
| `ck.model(...)`, `ck.loop(...)`, `ck.eval(...)`, `ck.submit(...)` with `stage="frozen"` | Names the stage in labs that compare a frozen and a fine-tuned model. |
|
|
137
|
+
| `ck.drill_start("id")`, `ck.drill_check("id", result)` | A timed drill: the server stamps the start and answers the check. |
|
|
138
|
+
| `ck.status()` | The minute, the parts passed, the submissions used and left. |
|
|
139
|
+
| `ck.export()` | One file with everything recorded locally. Returns its path. |
|
|
140
|
+
| `ck.last` | The last answer as a dictionary. |
|
|
141
|
+
|
|
142
|
+
Running a cell again is always safe. `ck.start()` never resets a session or its clock, a check
|
|
143
|
+
may be repeated as often as you like (the first pass counts), and the same submission file sent
|
|
144
|
+
twice uses one attempt. `ck.start()` with another lab id or another student id in the same
|
|
145
|
+
notebook says so in one line (`! START: switched from lab nlp-01 to lab cv-02.`) and goes on
|
|
146
|
+
with the new session; what the old one still had to send is sent in the background.
|
|
147
|
+
|
|
148
|
+
The steps take what a notebook naturally holds:
|
|
149
|
+
|
|
150
|
+
- A batch may be `(inputs, targets)` as a loader yields it, or a dict such as
|
|
151
|
+
`{"image": ..., "label": ...}`.
|
|
152
|
+
- `ck.load` takes an `(X, y)` pair, a data loader, or a dict of splits such as
|
|
153
|
+
`{"train": ..., "test": ...}`.
|
|
154
|
+
- A loss history may be a list, an array, one record per epoch (a dict, or a pair such as
|
|
155
|
+
`(train_loss, val_loss)`: the training loss is read, the two are never mixed), a table, or the
|
|
156
|
+
history object of Keras or of a Hugging Face trainer.
|
|
157
|
+
- A model may return `(logits, hidden)`; a fitted scikit-learn classifier also reports how many
|
|
158
|
+
classes it knows.
|
|
159
|
+
- The metric function of `ck.reading` is called as `metric_fn(y_true, y_pred)` with NumPy arrays
|
|
160
|
+
when the notebook has loaded NumPy, and with plain lists when that raises or when NumPy is not
|
|
161
|
+
loaded. A function written for lists that joins the two with `+` is recognised and gets lists.
|
|
162
|
+
- `ck.submit` sends a file or the text of a file as it is. A DataFrame or a list of records is
|
|
163
|
+
written as CSV, or as JSON lines (one object per row) in a lab that asks for them.
|
|
164
|
+
|
|
165
|
+
An answer of `ck.reading` that cannot be read is named in one line while the reading is still open.
|
|
166
|
+
|
|
167
|
+
## What it never does
|
|
168
|
+
|
|
169
|
+
- **It never raises into your code.** Whatever you pass and whatever happens to the network, a
|
|
170
|
+
step prints at most one line such as
|
|
171
|
+
`! olympiad: the LOAD step could not finish (TypeError); your code is not affected.`
|
|
172
|
+
Only `KeyboardInterrupt` and `SystemExit` pass through.
|
|
173
|
+
- **It never blocks for long.** Each step has a time budget: 15 seconds for `start`, 6 for a
|
|
174
|
+
check, a reading, a drill or the status, 40 for `submit` and `finish`. Cell records travel in a
|
|
175
|
+
background thread. In Colab, `ck.finish()` first asks the notebook page for the notebook and
|
|
176
|
+
says so (`Reading the notebook; this can take a few seconds.`); without an answer it goes on
|
|
177
|
+
after 20 seconds with the cells the kernel ran.
|
|
178
|
+
- **It never changes your objects.** The model is run on the batch you give, without training it.
|
|
179
|
+
- **It never imports a heavy library.** NumPy, pandas, torch and the others are only looked at
|
|
180
|
+
when your notebook has already imported them.
|
|
181
|
+
- **It stays quiet in worker processes.** A worker of `multiprocessing` or of a `DataLoader`,
|
|
182
|
+
forked or spawned, that reaches a step (even `ck.start` at the top of a script) sends nothing
|
|
183
|
+
and prints nothing.
|
|
184
|
+
|
|
185
|
+
## When the server cannot be reached
|
|
186
|
+
|
|
187
|
+
The library writes everything to the local file first and sends it second. So when the server
|
|
188
|
+
does not answer:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
! START: no answer from the server. Working offline: everything is saved locally and sent when the connection is back.
|
|
192
|
+
! LOAD: no answer from the server yet. Saved locally; it will be sent again.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Keep working. Checks, readings and drills that got no answer are sent again in the background
|
|
196
|
+
(a check that arrives this way is marked as late, and the server counts every check once however
|
|
197
|
+
often it is sent). A submission and `ck.finish()` are not sent again by themselves: run the cell
|
|
198
|
+
again when the connection is back. After a connection that failed, the steps of the next ten
|
|
199
|
+
seconds do not wait for the server again; they are saved and queued at once.
|
|
200
|
+
|
|
201
|
+
`ck.export()` is for the case that the connection never comes back. Run it at the end and send
|
|
202
|
+
the file to your instructor:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
ck.export()
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
It writes `olympiad_export_<lab>_<student>_<utc>.json.gz` into the working folder (in Colab it
|
|
209
|
+
also offers the download) and returns the path. The file holds every record of the session,
|
|
210
|
+
including the submission files, so the lab can be scored later. The times in it are your
|
|
211
|
+
computer's, not the server's.
|
|
212
|
+
|
|
213
|
+
## Supported environments
|
|
214
|
+
|
|
215
|
+
- Python 3.9, 3.10, 3.11, 3.12 and 3.13.
|
|
216
|
+
- Google Colab, Kaggle notebooks (internet on), Jupyter (Notebook and Lab), VS Code notebooks and
|
|
217
|
+
plain Python scripts. In a script there are no cells, so only the steps are recorded.
|
|
218
|
+
- IPython 7.34 and newer with ipykernel 6.17 and newer (the versions of Colab), up to the current
|
|
219
|
+
releases.
|
|
220
|
+
- Evidence is built for plain Python objects, NumPy, pandas, torch, scikit-learn, Keras and
|
|
221
|
+
Hugging Face objects, whichever of them the notebook uses.
|
|
222
|
+
|
|
223
|
+
## Settings
|
|
224
|
+
|
|
225
|
+
| Setting | Meaning |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `ck.start(lab, student_id, server="https://...")` | Use another scorer server. |
|
|
228
|
+
| `OLYMPIAD_SERVER_URL` | The same, as an environment variable. The argument wins. |
|
|
229
|
+
| `OLYMPIAD_STUDENT_ID` | The student id for `ck.start(lab)` without one (automated runs). |
|
|
230
|
+
| `OLYMPIAD_AUDIT_DIR` | Audit mode, see below. |
|
|
231
|
+
|
|
232
|
+
Without a setting the library talks to the server it was written for,
|
|
233
|
+
`https://us-central1-ka-ai-olympiad.cloudfunctions.net/api`.
|
|
234
|
+
|
|
235
|
+
## Audit mode
|
|
236
|
+
|
|
237
|
+
With `OLYMPIAD_AUDIT_DIR` set to a folder, nothing touches the network. Every call is appended to
|
|
238
|
+
`audit.jsonl` in that folder as `{"route", "body", "ts"}`, the body exactly as it would have
|
|
239
|
+
been sent; submitted files are copied there as `submission_<n>_<stage>__<filename>` and the
|
|
240
|
+
submit body names the copy under `file` instead of carrying the content. Every step prints
|
|
241
|
+
`! LOAD recorded (audit mode)`. Instructors use it to run a handed-in notebook again and compare.
|
|
242
|
+
|
|
243
|
+
No start answer arrives in this mode. A tool that knows the lab can leave its public info in the
|
|
244
|
+
folder as `lab.json` (the `lab` object of a start answer); a table handed to `ck.submit` is then
|
|
245
|
+
written in the lab's file format and stages are spelled as the lab spells them. Without the file
|
|
246
|
+
a table is written as CSV.
|
|
247
|
+
|
|
248
|
+
## The local file, for tool authors
|
|
249
|
+
|
|
250
|
+
`olympiad_<lab>_<student>.jsonl` is append-only, one JSON object per line:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
{"k": "event", "ts": "...Z", "sent": false, "session_id": "...", "boot": "a3f9c2d1", "event": {"seq": 12, "type": "cell_run", ...}}
|
|
254
|
+
{"k": "call", "ts": "...Z", "sent": false, "session_id": "...", "id": "a3f9c2d1-c4", "route": "/v1/check", "body": {...}, "response": null}
|
|
255
|
+
{"k": "ack", "ts": "...Z", "boot": "a3f9c2d1", "seq": [10, 12]}
|
|
256
|
+
{"k": "ack", "ts": "...Z", "id": "a3f9c2d1-c4", "sent": true, "response": {...}}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The body of a check carries its `call_id`, the same on every retry. An `ack` line records what
|
|
260
|
+
happened later to earlier lines: a range of events the server accepted, or how a call ended.
|
|
261
|
+
`ck.export()` folds them in: the exported file is gzip-compressed JSON lines with `event` and
|
|
262
|
+
`call` lines only, where `sent` is true for what the server accepted and `response` holds its
|
|
263
|
+
answer (or its error). A kernel that was restarted appends to the same file under a new `boot`
|
|
264
|
+
id. A `finish` call is kept without the notebook while it is under way; only one that got no
|
|
265
|
+
answer is kept in full, as a second line.
|
|
266
|
+
|
|
267
|
+
The file may be renamed, moved or deleted while the notebook runs: the running kernel still
|
|
268
|
+
finds every line for `ck.export()`. A line that was cut off (a kernel that was killed while it
|
|
269
|
+
wrote) is skipped by every reader and never swallows the next one. Above 200 MB new cell records
|
|
270
|
+
are kept without their source (the notebook says so once), and a submission is kept in full only
|
|
271
|
+
while it has no answer.
|
|
272
|
+
|
|
273
|
+
## Development
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
python -m pip install -e . pytest numpy pandas ipython ipykernel nbclient
|
|
277
|
+
python -m pytest
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The tests run against a small fake scorer on localhost (`tests/fake_server.py`) and cannot reach
|
|
281
|
+
the real one: the built-in address is replaced and sockets are confined to the local machine.
|
|
282
|
+
|
|
283
|
+
## Licence
|
|
284
|
+
|
|
285
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# olympiad-lab
|
|
2
|
+
|
|
3
|
+
Checkpoints for AI Olympiad training labs.
|
|
4
|
+
|
|
5
|
+
A lab notebook calls a few short steps (`ck.load`, `ck.look`, `ck.split`, ...) between the
|
|
6
|
+
student's own code. Each step describes what was just built and sends that description to the
|
|
7
|
+
scorer server of the course. The server decides whether the step passes and answers with one
|
|
8
|
+
line. The library holds no expected value, no rule and no label: the notebook sends evidence, the
|
|
9
|
+
server decides.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install olympiad-lab
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The only dependency is `requests`. NumPy, pandas, torch and the other libraries of a lab are
|
|
18
|
+
used when the notebook has already imported them, and never imported by this package.
|
|
19
|
+
|
|
20
|
+
## What this library records
|
|
21
|
+
|
|
22
|
+
Read this before you use it. From the moment `ck.start()` runs, and only in that notebook:
|
|
23
|
+
|
|
24
|
+
- **Every cell you run**: the first 4000 characters of its source, how long it took, whether it
|
|
25
|
+
raised, and for an error its type, its message and the last 15 lines of the traceback.
|
|
26
|
+
- **What each step describes**: types, shapes, row counts, column names, data types, small number
|
|
27
|
+
summaries (minimum, maximum, mean, count of missing values) and the first 200 characters of how
|
|
28
|
+
the object prints. For `ck.split` the row ids (or their hashes for very large sets). For
|
|
29
|
+
`ck.loop` up to 200 loss values. For `ck.eval` the score.
|
|
30
|
+
- **What you hand in**: the submission file, the answers of `ck.reading` (including the source of
|
|
31
|
+
the metric function you pass), and at `ck.finish()` the notebook itself. When the notebook
|
|
32
|
+
program does not hand it over, the cells this kernel ran are sent in its place.
|
|
33
|
+
- **Where it runs**: Colab, Kaggle, Jupyter, VS Code or script; the Python and IPython versions;
|
|
34
|
+
the operating system name; the GPU model.
|
|
35
|
+
|
|
36
|
+
It does not record other notebooks, other files, the values of environment variables, or your
|
|
37
|
+
account. Cells that ran before `ck.start()` are not recorded.
|
|
38
|
+
|
|
39
|
+
Three things are taken out on your computer, before anything is stored or sent, from cell source,
|
|
40
|
+
error messages, tracebacks and the notebook:
|
|
41
|
+
|
|
42
|
+
- **Your student id** becomes `<student_id>`.
|
|
43
|
+
- **Anything that looks like a secret** becomes `<redacted>`: access tokens of well-known shapes
|
|
44
|
+
(Hugging Face, OpenAI, GitHub, Google, Slack, AWS, Kaggle, a private key); a value given to a
|
|
45
|
+
name such as `api_key`, `HF_TOKEN` or `os.environ["KAGGLE_KEY"]`, also in a line such as
|
|
46
|
+
`%env KAGGLE_KEY=...`, when it has the shape of a key (twelve or more letters and digits, mixed
|
|
47
|
+
through one another); any value given to a name that ends in `password`; the password inside
|
|
48
|
+
an address and the token of an `Authorization` header.
|
|
49
|
+
- **Names**: your home folder is written as `~`, and the account name that Colab writes into a
|
|
50
|
+
notebook is removed.
|
|
51
|
+
|
|
52
|
+
Code that holds no secret stays exactly as you wrote it: `label_key = "category_name"` and
|
|
53
|
+
`stop_token = "<|end_of_text|>"` are not keys, although their names end like one.
|
|
54
|
+
|
|
55
|
+
This is a safety net, not a guarantee. A secret that looks like any other word cannot be told
|
|
56
|
+
apart from one, so do not paste keys into a notebook: use `getpass.getpass()` or the secrets
|
|
57
|
+
panel of Colab or Kaggle.
|
|
58
|
+
|
|
59
|
+
The records go to the scorer server over HTTPS and are used by the instructors of the course for
|
|
60
|
+
assessment. A copy is kept next to the notebook in `olympiad_<lab>_<student>.jsonl`.
|
|
61
|
+
`ck.start()` says so every time:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
This lab records cell runs, cell source and errors for assessment.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Use
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
!pip -q install olympiad-lab
|
|
71
|
+
from olympiad import ck
|
|
72
|
+
|
|
73
|
+
ck.start("nlp-01", "S07-K4QX") # the lab id and your student id
|
|
74
|
+
ck.load(train=train_df, test=test_df) # LOAD: the data is in memory
|
|
75
|
+
ck.look(train_df.head(), size=len(train_df)) # LOOK: shows a sample, prints the size
|
|
76
|
+
ck.split(train_ids=tr.id, val_ids=va.id) # SPLIT: no row in both sets
|
|
77
|
+
ck.batch(X_tr[:32], y_tr[:32]) # BATCH: one batch or feature table
|
|
78
|
+
ck.model(model, X_va[:32]) # MODEL: runs the model on one batch
|
|
79
|
+
ck.loop(losses) # LOOP: the training loss fell
|
|
80
|
+
ck.eval(val_f1, metric="macro_f1") # EVAL: prints the validation score
|
|
81
|
+
ck.submit("submission.csv") # SUBMIT: a file, a DataFrame or the text of a file
|
|
82
|
+
ck.finish() # sends what waits, prints a summary
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Every step prints a tick or a cross, one sentence, and a strip of the lab's parts:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
✓ BATCH: Batch shape and data type are right.
|
|
89
|
+
Parts 4 of 7: LOAD ✓ | LOOK ✓ | SPLIT ✓ | BATCH ✓ | MODEL - | EVAL - | SUBMIT -
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
In a notebook the same block is shown as a small box that reads the same in a light and in a
|
|
93
|
+
dark theme. A part that the lab does not use answers `! LOOP is not part of this lab.` A step
|
|
94
|
+
that runs before `ck.start()` does nothing and says:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
! olympiad: run the first cell (ck.start) before this one.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Other steps:
|
|
101
|
+
|
|
102
|
+
| Step | What it does |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `ck.reading(metric_fn=..., trivial={...}, rewards="b", format={...}, facts="...")` | Sends the task reading. It is scored after the lab; no score is shown. |
|
|
105
|
+
| `ck.model(...)`, `ck.loop(...)`, `ck.eval(...)`, `ck.submit(...)` with `stage="frozen"` | Names the stage in labs that compare a frozen and a fine-tuned model. |
|
|
106
|
+
| `ck.drill_start("id")`, `ck.drill_check("id", result)` | A timed drill: the server stamps the start and answers the check. |
|
|
107
|
+
| `ck.status()` | The minute, the parts passed, the submissions used and left. |
|
|
108
|
+
| `ck.export()` | One file with everything recorded locally. Returns its path. |
|
|
109
|
+
| `ck.last` | The last answer as a dictionary. |
|
|
110
|
+
|
|
111
|
+
Running a cell again is always safe. `ck.start()` never resets a session or its clock, a check
|
|
112
|
+
may be repeated as often as you like (the first pass counts), and the same submission file sent
|
|
113
|
+
twice uses one attempt. `ck.start()` with another lab id or another student id in the same
|
|
114
|
+
notebook says so in one line (`! START: switched from lab nlp-01 to lab cv-02.`) and goes on
|
|
115
|
+
with the new session; what the old one still had to send is sent in the background.
|
|
116
|
+
|
|
117
|
+
The steps take what a notebook naturally holds:
|
|
118
|
+
|
|
119
|
+
- A batch may be `(inputs, targets)` as a loader yields it, or a dict such as
|
|
120
|
+
`{"image": ..., "label": ...}`.
|
|
121
|
+
- `ck.load` takes an `(X, y)` pair, a data loader, or a dict of splits such as
|
|
122
|
+
`{"train": ..., "test": ...}`.
|
|
123
|
+
- A loss history may be a list, an array, one record per epoch (a dict, or a pair such as
|
|
124
|
+
`(train_loss, val_loss)`: the training loss is read, the two are never mixed), a table, or the
|
|
125
|
+
history object of Keras or of a Hugging Face trainer.
|
|
126
|
+
- A model may return `(logits, hidden)`; a fitted scikit-learn classifier also reports how many
|
|
127
|
+
classes it knows.
|
|
128
|
+
- The metric function of `ck.reading` is called as `metric_fn(y_true, y_pred)` with NumPy arrays
|
|
129
|
+
when the notebook has loaded NumPy, and with plain lists when that raises or when NumPy is not
|
|
130
|
+
loaded. A function written for lists that joins the two with `+` is recognised and gets lists.
|
|
131
|
+
- `ck.submit` sends a file or the text of a file as it is. A DataFrame or a list of records is
|
|
132
|
+
written as CSV, or as JSON lines (one object per row) in a lab that asks for them.
|
|
133
|
+
|
|
134
|
+
An answer of `ck.reading` that cannot be read is named in one line while the reading is still open.
|
|
135
|
+
|
|
136
|
+
## What it never does
|
|
137
|
+
|
|
138
|
+
- **It never raises into your code.** Whatever you pass and whatever happens to the network, a
|
|
139
|
+
step prints at most one line such as
|
|
140
|
+
`! olympiad: the LOAD step could not finish (TypeError); your code is not affected.`
|
|
141
|
+
Only `KeyboardInterrupt` and `SystemExit` pass through.
|
|
142
|
+
- **It never blocks for long.** Each step has a time budget: 15 seconds for `start`, 6 for a
|
|
143
|
+
check, a reading, a drill or the status, 40 for `submit` and `finish`. Cell records travel in a
|
|
144
|
+
background thread. In Colab, `ck.finish()` first asks the notebook page for the notebook and
|
|
145
|
+
says so (`Reading the notebook; this can take a few seconds.`); without an answer it goes on
|
|
146
|
+
after 20 seconds with the cells the kernel ran.
|
|
147
|
+
- **It never changes your objects.** The model is run on the batch you give, without training it.
|
|
148
|
+
- **It never imports a heavy library.** NumPy, pandas, torch and the others are only looked at
|
|
149
|
+
when your notebook has already imported them.
|
|
150
|
+
- **It stays quiet in worker processes.** A worker of `multiprocessing` or of a `DataLoader`,
|
|
151
|
+
forked or spawned, that reaches a step (even `ck.start` at the top of a script) sends nothing
|
|
152
|
+
and prints nothing.
|
|
153
|
+
|
|
154
|
+
## When the server cannot be reached
|
|
155
|
+
|
|
156
|
+
The library writes everything to the local file first and sends it second. So when the server
|
|
157
|
+
does not answer:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
! START: no answer from the server. Working offline: everything is saved locally and sent when the connection is back.
|
|
161
|
+
! LOAD: no answer from the server yet. Saved locally; it will be sent again.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Keep working. Checks, readings and drills that got no answer are sent again in the background
|
|
165
|
+
(a check that arrives this way is marked as late, and the server counts every check once however
|
|
166
|
+
often it is sent). A submission and `ck.finish()` are not sent again by themselves: run the cell
|
|
167
|
+
again when the connection is back. After a connection that failed, the steps of the next ten
|
|
168
|
+
seconds do not wait for the server again; they are saved and queued at once.
|
|
169
|
+
|
|
170
|
+
`ck.export()` is for the case that the connection never comes back. Run it at the end and send
|
|
171
|
+
the file to your instructor:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
ck.export()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
It writes `olympiad_export_<lab>_<student>_<utc>.json.gz` into the working folder (in Colab it
|
|
178
|
+
also offers the download) and returns the path. The file holds every record of the session,
|
|
179
|
+
including the submission files, so the lab can be scored later. The times in it are your
|
|
180
|
+
computer's, not the server's.
|
|
181
|
+
|
|
182
|
+
## Supported environments
|
|
183
|
+
|
|
184
|
+
- Python 3.9, 3.10, 3.11, 3.12 and 3.13.
|
|
185
|
+
- Google Colab, Kaggle notebooks (internet on), Jupyter (Notebook and Lab), VS Code notebooks and
|
|
186
|
+
plain Python scripts. In a script there are no cells, so only the steps are recorded.
|
|
187
|
+
- IPython 7.34 and newer with ipykernel 6.17 and newer (the versions of Colab), up to the current
|
|
188
|
+
releases.
|
|
189
|
+
- Evidence is built for plain Python objects, NumPy, pandas, torch, scikit-learn, Keras and
|
|
190
|
+
Hugging Face objects, whichever of them the notebook uses.
|
|
191
|
+
|
|
192
|
+
## Settings
|
|
193
|
+
|
|
194
|
+
| Setting | Meaning |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `ck.start(lab, student_id, server="https://...")` | Use another scorer server. |
|
|
197
|
+
| `OLYMPIAD_SERVER_URL` | The same, as an environment variable. The argument wins. |
|
|
198
|
+
| `OLYMPIAD_STUDENT_ID` | The student id for `ck.start(lab)` without one (automated runs). |
|
|
199
|
+
| `OLYMPIAD_AUDIT_DIR` | Audit mode, see below. |
|
|
200
|
+
|
|
201
|
+
Without a setting the library talks to the server it was written for,
|
|
202
|
+
`https://us-central1-ka-ai-olympiad.cloudfunctions.net/api`.
|
|
203
|
+
|
|
204
|
+
## Audit mode
|
|
205
|
+
|
|
206
|
+
With `OLYMPIAD_AUDIT_DIR` set to a folder, nothing touches the network. Every call is appended to
|
|
207
|
+
`audit.jsonl` in that folder as `{"route", "body", "ts"}`, the body exactly as it would have
|
|
208
|
+
been sent; submitted files are copied there as `submission_<n>_<stage>__<filename>` and the
|
|
209
|
+
submit body names the copy under `file` instead of carrying the content. Every step prints
|
|
210
|
+
`! LOAD recorded (audit mode)`. Instructors use it to run a handed-in notebook again and compare.
|
|
211
|
+
|
|
212
|
+
No start answer arrives in this mode. A tool that knows the lab can leave its public info in the
|
|
213
|
+
folder as `lab.json` (the `lab` object of a start answer); a table handed to `ck.submit` is then
|
|
214
|
+
written in the lab's file format and stages are spelled as the lab spells them. Without the file
|
|
215
|
+
a table is written as CSV.
|
|
216
|
+
|
|
217
|
+
## The local file, for tool authors
|
|
218
|
+
|
|
219
|
+
`olympiad_<lab>_<student>.jsonl` is append-only, one JSON object per line:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
{"k": "event", "ts": "...Z", "sent": false, "session_id": "...", "boot": "a3f9c2d1", "event": {"seq": 12, "type": "cell_run", ...}}
|
|
223
|
+
{"k": "call", "ts": "...Z", "sent": false, "session_id": "...", "id": "a3f9c2d1-c4", "route": "/v1/check", "body": {...}, "response": null}
|
|
224
|
+
{"k": "ack", "ts": "...Z", "boot": "a3f9c2d1", "seq": [10, 12]}
|
|
225
|
+
{"k": "ack", "ts": "...Z", "id": "a3f9c2d1-c4", "sent": true, "response": {...}}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The body of a check carries its `call_id`, the same on every retry. An `ack` line records what
|
|
229
|
+
happened later to earlier lines: a range of events the server accepted, or how a call ended.
|
|
230
|
+
`ck.export()` folds them in: the exported file is gzip-compressed JSON lines with `event` and
|
|
231
|
+
`call` lines only, where `sent` is true for what the server accepted and `response` holds its
|
|
232
|
+
answer (or its error). A kernel that was restarted appends to the same file under a new `boot`
|
|
233
|
+
id. A `finish` call is kept without the notebook while it is under way; only one that got no
|
|
234
|
+
answer is kept in full, as a second line.
|
|
235
|
+
|
|
236
|
+
The file may be renamed, moved or deleted while the notebook runs: the running kernel still
|
|
237
|
+
finds every line for `ck.export()`. A line that was cut off (a kernel that was killed while it
|
|
238
|
+
wrote) is skipped by every reader and never swallows the next one. Above 200 MB new cell records
|
|
239
|
+
are kept without their source (the notebook says so once), and a submission is kept in full only
|
|
240
|
+
while it has no answer.
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
python -m pip install -e . pytest numpy pandas ipython ipykernel nbclient
|
|
246
|
+
python -m pytest
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The tests run against a small fake scorer on localhost (`tests/fake_server.py`) and cannot reach
|
|
250
|
+
the real one: the built-in address is replaced and sockets are confined to the local machine.
|
|
251
|
+
|
|
252
|
+
## Licence
|
|
253
|
+
|
|
254
|
+
MIT. See `LICENSE`.
|