For Miners
The V1 submission contract, output format, hard limits, and the forkable reference miner.
Your job is simple to state and hard to do: train the best ASL to English model you can, freeze it, and publish it so a validator can run it without you.
You do no work during scoring. Once your manifest is committed, your machine can be switched off.
What you submit
| Component | Requirement |
|---|---|
| Model weights | One public, ungated Hugging Face repository pinned to an exact commit SHA. Branch names such as main are rejected (R02). A SHA-256 hash is required for every declared file. |
| Loader class | A signet_model.py in the same repository exposing class Model(model_dir, seed, device) with load() and translate(video_path). The module is declared and hashed exactly like the weights, and the manifest's entry names it as module:Class (default signet_model:Model). |
| Runtime profile | One name from the small set of execution environments the network publishes. The manifest names the profile and never a digest; when the network releases a runtime it publishes that profile as an image pinned by digest. The current and default profile is torch2.4-transformers4.44. You do not build, publish, or pin a container image. |
| Manifest | The single committed object, schema_version 3.0: your hotkey; the model reference (provider huggingface, repo, revision, files_sha256, entry, runtime); the declaration (total and trainable parameters, architecture family, base-model provenance, precision); and declared_seed. |
| Dependencies | Everything load() opens must be inside the repository and declared: weights, tokenizer, config, and any pose estimator of your own. The runtime's shared MediaPipe Holistic bundle is the one exception. Evaluation runs with no network interface, so anything fetched at load time is an R07 rejection. |
Weights and loader are public, ungated, and immutable by reference. This is a condition of participation, not a preference.
Execution interface
You publish a model, not a program. The validator imports one class from your repository:
class Model:
def __init__(self, model_dir, seed, device): ... # cheap; no loading here
def load(self): ... # everything, once, offline, from model_dir
def translate(self, video_path): ... # str, or {"translation": str, "pose": [frames, 543, 4]}Input: your repository mounted read-only at /model, and one raw .mp4 path per translate() call.
Output: a string, or a dict carrying translation and optionally pose.
The validator's own runner, which you never see and cannot change, is what actually executes: it imports the class named by your manifest's entry, calls load() with no network, re-seeds before every clip, hands translate() one clip at a time, and writes the JSONL row for each clip itself:
{"clip_id": "...", "translation": "...", "pose": [...]}A clip whose translate() raises is recorded with an empty translation and the reason, so it costs that clip rather than the round. A load() that raises is an R03 rejection of the submission.
The runner does not check the shape of what translate() returns. A dict is read through its translation key and coerced with str(), an absent key giving an empty translation; anything that is not a dict is coerced with str() and written as the translation. A pose value is written through as returned: one that is not a list (or an array with tolist()) fails R10 for the run, and one that JSON cannot serialise ends the run. A wrong translation type therefore costs only the clips it garbles; a wrong pose type costs the round. R09 and R10 are parsing gates on the JSONL the runner wrote: a line that is not a JSON object (R09), or one without a string clip_id and translation, or with a pose that is not a list (R10).
Pose contract
The pose sequence follows the fixed 543-landmark MediaPipe Holistic layout:
| Segment | Landmarks |
|---|---|
| Body | 33 |
| Face | 468 |
| Left hand | 21 |
| Right hand | 21 |
| Total | 543 |
Each point is [x, y, z, c]: normalized image coordinates plus confidence, with semantics fixed by the protocol.
Pose is optional. A submission that returns only a translation is not rejected for omitting it; it simply earns nothing on the pose component.
Hard limits
A V1 submission is rejected for any of the following.
| Limit | Rule |
|---|---|
| Network | Any attempted egress, including a download at load() time (R07). The sandbox has no network interface, and the attempt is rejected even though it could not succeed |
| Determinism | Non-deterministic output |
| Model size | Above 2 billion parameters total, including frozen backbones and pose estimators |
| Compute | Above 8 seconds per second of input video on reference validator hardware |
| Integrity | Hash mismatch |
| Format | Runner output that does not parse: a line that is not a JSON object (R09), or a line without a string clip_id and translation, or with a pose that is not a list (R10). The runner writes every line itself and coerces the translation, so R09 and a missing translation cannot come from your return type; a pose that is not a list can. A pose that does not match the 543-landmark layout or the clip's frame count (R11); an output that does not have exactly one parsed line per clip (R12) |
| Grounding | Canary grounding ratio below the calibrated hard floor |
A rejection returns an explicit code, not a silently low score. See Risks & FAQ.
The reference miner
The reference miner is a forkable loader, not a container: docker/reference_model/signet_model.py in the subnet repository is the file you replace, and a submission is that file plus your weights, published to Hugging Face. It follows the pose-first branch: whole-body keypoints into a seq2seq decoder.
video → MediaPipe Holistic keypoints (runtime's shared bundle) → body-frame normalisation
→ projection into a seq2seq decoder → English + canonical 543-landmark pose outputThe reference signet_model.py handles keypoint extraction through the runtime's shared MediaPipe Holistic bundle, body-frame normalisation (mid-shoulder origin, shoulder-width scale), the pose-to-embedding projection, fixed-beam deterministic decoding, and pose output in the 543-landmark layout at the clip's native frame count. There is no image to build and no entrypoint to get right.
What it does not include yet is trained weights. The loader reads a pose_projection.pt beside the seq2seq weights when the repository ships one; without it the baseline still runs deterministically from the declared seed, but decodes from an untrained projection and prints a notice saying so. Publishing the reference model repository is Phase 0 work on the Roadmap. Until then, forking the loader gives you the pipeline; the translation quality is yours to train.
Research directions that are open
- pose-first graph or transformer systems
- SHuBERT-style sign-specific self-supervised encoders
- learned video encoders
- RGB + pose fusion
- efficient multi-stream systems such as SignMusketeers-style approaches
- LLM-assisted decoders and LoRA-based adaptation
- broad multi-corpus training
The fixed 2B cap is deliberate. It rewards data quality, representation, and training efficiency instead of raw model scale.
Public training substrate
Train and self-evaluate on public data: YouTube-ASL, OpenASL, How2Sign, PHOENIX-2014T, and anything else you can legally use.
These corpora never set Signet weights. Training-corpus breadth is one of the strongest measured determinants of generalization, so the economic pressure points toward better data mixture, pretraining, and representation, not toward overfitting one academic benchmark.
Once the private corpus exists and footage begins to retire from active scoring, retired Signet footage would be released as a public self-evaluation archive. See The Private Corpus.
Pre-submission checklist
- One public, ungated Hugging Face repository, pinned to an exact commit SHA, not a branch
- SHA-256 recorded for every declared file,
signet_model.pyincluded entrynames the loader asmodule:Class, and that module is among the declared filesruntimeis a profile the network publishes (defaulttorch2.4-transformers4.44)load()opens everything from/modeloffline; nothing is downloaded at load or translate time- Same input produces byte-identical output twice at the declared seed
- Total loaded parameters under 2B, counted the way the validator counts: frozen backbones and any pose estimator you ship included
- Declared parameter count within 2% of that traversal
- Within 8 s of compute per second of video on reference hardware
translate()returns a string, or a dict withtranslationand a pose in the 543-landmark layoutneurons/miner.py --dry-runpasses the admission checks