From 528ea9058f1847c49bfd5cd285899c927f949db3 Mon Sep 17 00:00:00 2001 From: Pawel Date: Mon, 25 May 2026 15:27:10 -0400 Subject: [PATCH] Polish + more info --- README.md | 46 +++++++++++++++++++++++++++++-- scripts/demo_predict_grade.py | 2 +- src/climbingboardgpt/inference.py | 2 +- webapp/app.py | 2 +- webapp/static/index.html | 2 +- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index bab0745..6b297ed 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,44 @@ For grade prediction, the grade token is removed: The model then predicts the climb difficulty from the board, angle, and hold-role tokens. +## How generation and grading work + +The project uses one shared vocabulary across both boards. Every climb is converted into a short symbolic sequence: board token, angle token, optional grade token, and one token per hold/role pair. Hold tokens also carry board identity, so the model can learn TB2 and Kilter patterns together without mixing placement IDs. + +The **grade predictor** is a transformer encoder. For this task the grade token is removed and `` is replaced with ``. The model reads the board, angle, hold roles, and learned coordinate features for each hold token, then regresses a continuous difficulty value. That numeric prediction is mapped back into a grouped V-grade for demos and evaluation. + +At inference time, grade prediction is: + +1. parse a frames string into `(placement_id, role_id)` pairs, +2. canonicalize the route order using role, height, and horizontal position, +3. convert the route to model tokens such as ` ... `, +4. encode those tokens as integer IDs and pad/truncate to the model's max sequence length, +5. add three coordinate features for each token: normalized x, normalized y, and whether the token is a hold, +6. run the transformer encoder and read the final `` representation, +7. pass the route through a neural network to get a continuous difficulty prediction, +8. map that prediction into the grouped V-grade scale. + +The **route generator** is a small GPT-style causal transformer. It starts from a prompt such as: + +```text + +``` + +Then it samples the next token repeatedly until `` or a maximum length is reached. At each step: + +1. the current sequence is cropped to the model's context window, +2. the causal transformer predicts logits for the next token, +3. forbidden tokens such as ``, ``, ``, ``, and `` are masked out, +4. logits are divided by the sampling temperature, +5. optional top-k filtering keeps only the `k` most likely next tokens, +6. softmax turns the filtered logits into probabilities, +7. `torch.multinomial` samples one next token from that probability distribution, +8. the sampled token is appended to the sequence. + +Lower temperature makes the distribution sharper and more conservative. Higher temperature flattens it and makes unusual tokens more likely. Top-k prevents very low-probability tokens from being sampled at all. The sampled hold-role tokens are converted back into a frames string such as `p1084r12p1231r13...`. + +Generation is checked after sampling rather than hard-constrained during decoding. The helper code removes duplicate placements, checks that all holds belong to the requested board, requires starts and finishes, and the webapp retries a few times when `valid_only` is enabled. The trained grade predictor can also score generated climbs as a critic, which is how the evaluation measures whether generated routes are close to the requested grade. + --- @@ -222,13 +260,15 @@ This caps PyTorch CPU thread usage. ## Data expected by the full training pipeline -The full tokenization/training pipeline expects raw BoardLib databases at: +The full tokenization/training pipeline expects raw board databases at: ```text data/raw/tb2.db data/raw/kilter.db ``` +These databases can be downloaded with the [`BoardLib`](https://github.com/lemeryfertitta/BoardLib) CLI commands recorded in the board config files. After that import step, the project treats them simply as source board data. + The project configs are: ```text @@ -486,7 +526,7 @@ After training the grade predictor, or after placing a trained checkpoint at: models/joint_transformer_grade_predictor.pth ``` -you can predict a grade directly from a BoardLib-style frames string. +you can predict a grade directly from a frames string. ### Generic @@ -523,7 +563,7 @@ Predicted: V6 Difficulty: 22.400 ``` -The `Predicted` line is the grouped V-grade. The `Difficulty` line is the model's continuous prediction in the underlying BoardLib difficulty scale. +The `Predicted` line is the grouped V-grade. The `Difficulty` line is the model's continuous prediction on the source difficulty scale. ### JSON output diff --git a/scripts/demo_predict_grade.py b/scripts/demo_predict_grade.py index fcc9d8a..bca4eaa 100644 --- a/scripts/demo_predict_grade.py +++ b/scripts/demo_predict_grade.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Predict a climb grade from board, angle, and BoardLib frames string. +"""Predict a climb grade from board, angle, and frames string. Examples -------- diff --git a/src/climbingboardgpt/inference.py b/src/climbingboardgpt/inference.py index 49bdc03..774ea08 100644 --- a/src/climbingboardgpt/inference.py +++ b/src/climbingboardgpt/inference.py @@ -305,7 +305,7 @@ def predict_frames_grade( board_config: BoardConfig, df_token_meta, ) -> dict[str, object]: - """Predict grade from board, angle, and a BoardLib frames string.""" + """Predict grade from board, angle, and a frames string.""" tokens = frames_to_grade_model_tokens( frames=frames, angle=angle, diff --git a/webapp/app.py b/webapp/app.py index 646c371..60734ca 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -211,7 +211,7 @@ def _role_limit_validity(tokens: list[str], requested_board_prefix: str) -> dict """Extra webapp validity checks for start/finish counts. The lower-level validity check requires at least one start and at least one - finish, but BoardLib-style climbs should not have arbitrarily many starts or + finish, but climbs should not have arbitrarily many starts or finishes. For this demo we enforce at most two starts and at most two finishes. """ counts = { diff --git a/webapp/static/index.html b/webapp/static/index.html index 83244e2..ce57d79 100644 --- a/webapp/static/index.html +++ b/webapp/static/index.html @@ -72,7 +72,7 @@

Click a hold on the board image. The app appends the corresponding - BoardLib frame token using the selected semantic role. + frame token using the selected semantic role.