altar-cherimoya 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.
- altar_cherimoya-0.1.0/.gitignore +32 -0
- altar_cherimoya-0.1.0/LICENSE +21 -0
- altar_cherimoya-0.1.0/PKG-INFO +62 -0
- altar_cherimoya-0.1.0/README.md +37 -0
- altar_cherimoya-0.1.0/altar_cherimoya/__init__.py +26 -0
- altar_cherimoya-0.1.0/altar_cherimoya/plugin.py +317 -0
- altar_cherimoya-0.1.0/altar_cherimoya/py.typed +0 -0
- altar_cherimoya-0.1.0/examples/score_run.py +436 -0
- altar_cherimoya-0.1.0/pyproject.toml +51 -0
- altar_cherimoya-0.1.0/tests/test_plugin.py +470 -0
- altar_cherimoya-0.1.0/tests/test_scoring_run.py +215 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
*.pyc
|
|
2
|
+
*~
|
|
3
|
+
**/__pycache__/*
|
|
4
|
+
*.swp
|
|
5
|
+
.vscode/
|
|
6
|
+
.idea/
|
|
7
|
+
.DS_Store
|
|
8
|
+
.env*
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
coverage.xml
|
|
15
|
+
pytest.xml
|
|
16
|
+
.hypothesis/
|
|
17
|
+
.python-version
|
|
18
|
+
.venv/
|
|
19
|
+
.venv-*/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
build/
|
|
22
|
+
dist/
|
|
23
|
+
site/
|
|
24
|
+
*.sqlite3
|
|
25
|
+
*.log
|
|
26
|
+
|
|
27
|
+
# Generated variant indexes and datasets. The small canonical gene table is tracked.
|
|
28
|
+
altar/altar/variants/data/ccres.dnatree
|
|
29
|
+
altar/altar/variants/data/region_annotations.parquet
|
|
30
|
+
altar/altar/variants/data/variants.pkl.gz
|
|
31
|
+
altar/altar/variants/data/raw/
|
|
32
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Riya Sinha
|
|
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,62 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: altar-cherimoya
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cherimoya model binding for Altar
|
|
5
|
+
Project-URL: Documentation, https://kundajelab.github.io/altar/
|
|
6
|
+
Project-URL: Issues, https://github.com/kundajelab/altar/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/kundajelab/altar
|
|
8
|
+
Author: Riya Sinha
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: altar<0.2,>=0.1
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
|
|
23
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Altar Cherimoya binding
|
|
27
|
+
|
|
28
|
+
`altar-cherimoya` is the lightweight control-plane binding between Altar and the Cherimoya variant-effect
|
|
29
|
+
runtime. Installing it registers `CHERIMOYA` in the canonical `altar.model_plugins` discovery group.
|
|
30
|
+
|
|
31
|
+
The binding owns Cherimoya's artifact schema, score schema, prioritization predicate, resource requests, and
|
|
32
|
+
backend-neutral container plan. It does not import PyTorch, Cherimoya, a model registry, object-storage SDK,
|
|
33
|
+
or compute-provider SDK. The heavyweight `cherimoya-score` CLI and independently locked image live under
|
|
34
|
+
`runtimes/cherimoya` in this repository.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install altar-cherimoya
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from altar.models import get_model_plugin
|
|
42
|
+
|
|
43
|
+
plugin = get_model_plugin("CHERIMOYA")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`CherimoyaConfiguration.image` defaults to the immutable runtime digest tested with the installed binding.
|
|
47
|
+
Callers may override it with another `name@sha256:<digest>` image reference; mutable tags remain invalid. Its
|
|
48
|
+
`weights` tuple contains one generic `ResourceReference` per fold, each with a required SHA-256 digest. The
|
|
49
|
+
URI may be local, HTTPS, GCS, S3, Hugging Face, or any other location understood by the selected storage
|
|
50
|
+
adapter. The digest, not the location, defines scientific identity. Mutable images and unidentified weight
|
|
51
|
+
bytes fail validation before a plan can be built.
|
|
52
|
+
|
|
53
|
+
The binding emits one GPU scoring task per weight resource followed by one CPU summarize task. Every weight
|
|
54
|
+
is staged to a stable logical path, and the runtime receives only that mounted path and expected digest. It
|
|
55
|
+
does not download weights or know which provider stored them.
|
|
56
|
+
|
|
57
|
+
The runtime streams large variant files in fixed outer batches and validates CATv1's single-track output
|
|
58
|
+
contract. `profile_l1` is defined over softmax-normalized profile distributions.
|
|
59
|
+
|
|
60
|
+
The tests compose `altar.testing.ModelResultContract` and `ContainerScorerContract` and fake only the outer
|
|
61
|
+
Modal and Kubernetes clients, so they verify both result semantics and the plan/fan-in contract without an
|
|
62
|
+
ML framework, cloud account, cluster, GPU, or image pull.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Altar Cherimoya binding
|
|
2
|
+
|
|
3
|
+
`altar-cherimoya` is the lightweight control-plane binding between Altar and the Cherimoya variant-effect
|
|
4
|
+
runtime. Installing it registers `CHERIMOYA` in the canonical `altar.model_plugins` discovery group.
|
|
5
|
+
|
|
6
|
+
The binding owns Cherimoya's artifact schema, score schema, prioritization predicate, resource requests, and
|
|
7
|
+
backend-neutral container plan. It does not import PyTorch, Cherimoya, a model registry, object-storage SDK,
|
|
8
|
+
or compute-provider SDK. The heavyweight `cherimoya-score` CLI and independently locked image live under
|
|
9
|
+
`runtimes/cherimoya` in this repository.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install altar-cherimoya
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from altar.models import get_model_plugin
|
|
17
|
+
|
|
18
|
+
plugin = get_model_plugin("CHERIMOYA")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`CherimoyaConfiguration.image` defaults to the immutable runtime digest tested with the installed binding.
|
|
22
|
+
Callers may override it with another `name@sha256:<digest>` image reference; mutable tags remain invalid. Its
|
|
23
|
+
`weights` tuple contains one generic `ResourceReference` per fold, each with a required SHA-256 digest. The
|
|
24
|
+
URI may be local, HTTPS, GCS, S3, Hugging Face, or any other location understood by the selected storage
|
|
25
|
+
adapter. The digest, not the location, defines scientific identity. Mutable images and unidentified weight
|
|
26
|
+
bytes fail validation before a plan can be built.
|
|
27
|
+
|
|
28
|
+
The binding emits one GPU scoring task per weight resource followed by one CPU summarize task. Every weight
|
|
29
|
+
is staged to a stable logical path, and the runtime receives only that mounted path and expected digest. It
|
|
30
|
+
does not download weights or know which provider stored them.
|
|
31
|
+
|
|
32
|
+
The runtime streams large variant files in fixed outer batches and validates CATv1's single-track output
|
|
33
|
+
contract. `profile_l1` is defined over softmax-normalized profile distributions.
|
|
34
|
+
|
|
35
|
+
The tests compose `altar.testing.ModelResultContract` and `ContainerScorerContract` and fake only the outer
|
|
36
|
+
Modal and Kubernetes clients, so they verify both result semantics and the plan/fan-in contract without an
|
|
37
|
+
ML framework, cloud account, cluster, GPU, or image pull.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Cherimoya model binding for Altar."""
|
|
2
|
+
|
|
3
|
+
from altar_cherimoya.plugin import (
|
|
4
|
+
DEFAULT_IMAGE,
|
|
5
|
+
DEFAULT_IN_WINDOW,
|
|
6
|
+
KIND_SCORING_FOLDS,
|
|
7
|
+
KIND_SCORING_SUMMARIZE,
|
|
8
|
+
CherimoyaArtifact,
|
|
9
|
+
CherimoyaConfiguration,
|
|
10
|
+
CherimoyaPlugin,
|
|
11
|
+
CherimoyaRunInputs,
|
|
12
|
+
CherimoyaShardKind,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"DEFAULT_IMAGE",
|
|
18
|
+
"DEFAULT_IN_WINDOW",
|
|
19
|
+
"KIND_SCORING_FOLDS",
|
|
20
|
+
"KIND_SCORING_SUMMARIZE",
|
|
21
|
+
"CherimoyaArtifact",
|
|
22
|
+
"CherimoyaConfiguration",
|
|
23
|
+
"CherimoyaPlugin",
|
|
24
|
+
"CherimoyaRunInputs",
|
|
25
|
+
"CherimoyaShardKind",
|
|
26
|
+
]
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Backend-neutral Altar binding for Cherimoya variant scoring.
|
|
2
|
+
|
|
3
|
+
The plugin is deliberately a control-plane adapter. It validates typed model configuration and emits a
|
|
4
|
+
``ContainerScoringPlan`` containing one GPU task per fold plus a CPU reduction task. The same plan runs
|
|
5
|
+
through the local, Modal, or Kubernetes execution backend; the plugin never imports an ML framework or a
|
|
6
|
+
provider SDK.
|
|
7
|
+
|
|
8
|
+
The corresponding ``cherimoya-score`` runtime and image definition live under ``runtimes/cherimoya`` in
|
|
9
|
+
the same repository. Keeping that data-plane implementation out of both ``altar`` and this binding lets the
|
|
10
|
+
kernel and adapter remain lightweight while model images use independent Python versions and dependency
|
|
11
|
+
locks.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
import enum
|
|
16
|
+
from typing import TYPE_CHECKING, Literal, cast
|
|
17
|
+
|
|
18
|
+
from altar.execution import ContainerTaskSpec, GpuPool, ResourceRequest, Transfer
|
|
19
|
+
from altar.models import (
|
|
20
|
+
ApiVersionRange,
|
|
21
|
+
ConfigurationModel,
|
|
22
|
+
ConfigurationVersionError,
|
|
23
|
+
ContainerImageDigest,
|
|
24
|
+
ContainerScoringPlan,
|
|
25
|
+
LegacyArtifactError,
|
|
26
|
+
LegacyScoringConversion,
|
|
27
|
+
ModelConfiguration,
|
|
28
|
+
ModelPlugin,
|
|
29
|
+
OutputSelection,
|
|
30
|
+
PluginCapability,
|
|
31
|
+
PluginKind,
|
|
32
|
+
PluginManifest,
|
|
33
|
+
PrioritizationPolicy,
|
|
34
|
+
ReferenceGenomeRunInputs,
|
|
35
|
+
ResourceReference,
|
|
36
|
+
ResultField,
|
|
37
|
+
ResultSchema,
|
|
38
|
+
RuntimeIdentityPolicy,
|
|
39
|
+
RuntimeKind,
|
|
40
|
+
RuntimeRequirement,
|
|
41
|
+
ScoringRequest,
|
|
42
|
+
require_resource_digests,
|
|
43
|
+
resource_transfer,
|
|
44
|
+
)
|
|
45
|
+
from altar.predicates import Abs, Col, Ge, Lit, Predicate
|
|
46
|
+
from pydantic import Field, ValidationError, model_validator
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from collections.abc import Mapping
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
DEFAULT_IN_WINDOW = 2114
|
|
54
|
+
DEFAULT_IMAGE = "riyavsinha/altar-cherimoya@sha256:565117e3e34af57aa0f5ec851045450f7015b4b58c8b8a4a019a092c775790cc"
|
|
55
|
+
|
|
56
|
+
_LOG2FC_PRIORITIZE_THRESHOLD = 0.25
|
|
57
|
+
_VARIANT_BATCH_SIZE = 4096
|
|
58
|
+
_MODEL_BATCH_SIZE = 64
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CherimoyaShardKind(enum.StrEnum):
|
|
62
|
+
"""Stable task kinds emitted by :class:`CherimoyaPlugin`."""
|
|
63
|
+
|
|
64
|
+
SCORING_FOLDS = "cherimoya_scoring_folds"
|
|
65
|
+
SCORING_SUMMARIZE = "cherimoya_scoring_summarize"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
KIND_SCORING_FOLDS = CherimoyaShardKind.SCORING_FOLDS
|
|
69
|
+
KIND_SCORING_SUMMARIZE = CherimoyaShardKind.SCORING_SUMMARIZE
|
|
70
|
+
|
|
71
|
+
_SCORE_RESOURCES = ResourceRequest(gpu=GpuPool.NORMAL_GPU, count=1, memory_gb=32)
|
|
72
|
+
_SUMMARIZE_RESOURCES = ResourceRequest(memory_gb=8)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CherimoyaConfiguration(ModelConfiguration):
|
|
76
|
+
"""Durable model resources and scientific settings for Cherimoya.
|
|
77
|
+
|
|
78
|
+
``image`` defaults to the immutable runtime tested with this binding release. Callers may override it
|
|
79
|
+
with another digest-pinned image built from ``runtimes/cherimoya``. ``weights`` contains one
|
|
80
|
+
provider-neutral, content-addressed resource per fold; its length is the fold count. Storage adapters
|
|
81
|
+
may retrieve those bytes from any location they support.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
weights: tuple[ResourceReference, ...] = Field(min_length=1)
|
|
85
|
+
in_window: int = Field(default=DEFAULT_IN_WINDOW, gt=0)
|
|
86
|
+
image: ContainerImageDigest = DEFAULT_IMAGE
|
|
87
|
+
|
|
88
|
+
@model_validator(mode="after")
|
|
89
|
+
def require_content_addressed_weights(self) -> CherimoyaConfiguration:
|
|
90
|
+
require_resource_digests(self, owner="Cherimoya")
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CherimoyaRunInputs(ReferenceGenomeRunInputs):
|
|
95
|
+
"""Typed biological inputs restricted to CATv1's training assembly."""
|
|
96
|
+
|
|
97
|
+
@model_validator(mode="after")
|
|
98
|
+
def require_hg38(self) -> CherimoyaRunInputs:
|
|
99
|
+
if self.genome_build != "hg38":
|
|
100
|
+
msg = f"Cherimoya scoring currently requires genome_build='hg38', got {self.genome_build!r}"
|
|
101
|
+
raise ValueError(msg)
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class CherimoyaArtifact(ConfigurationModel):
|
|
106
|
+
"""Strict generic artifact mapping accepted by the legacy conversion entry point."""
|
|
107
|
+
|
|
108
|
+
mode: Literal["container"] = "container"
|
|
109
|
+
weights: tuple[ResourceReference, ...] = Field(min_length=1)
|
|
110
|
+
genome: ResourceReference
|
|
111
|
+
in_window: int = Field(default=DEFAULT_IN_WINDOW, gt=0)
|
|
112
|
+
image: ContainerImageDigest = DEFAULT_IMAGE
|
|
113
|
+
|
|
114
|
+
@model_validator(mode="after")
|
|
115
|
+
def require_content_addressed_resources(self) -> CherimoyaArtifact:
|
|
116
|
+
require_resource_digests(self, owner="Cherimoya")
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class CherimoyaPlugin(ModelPlugin):
|
|
121
|
+
"""Cherimoya results contract and backend-neutral container plan builder."""
|
|
122
|
+
|
|
123
|
+
model_type = "CHERIMOYA"
|
|
124
|
+
manifest = PluginManifest(
|
|
125
|
+
plugin_id="org.kundajelab.altar.model.cherimoya",
|
|
126
|
+
kind=PluginKind.MODEL,
|
|
127
|
+
capabilities=frozenset({PluginCapability.SCORE, PluginCapability.MATERIALIZE_RESULTS}),
|
|
128
|
+
architecture="Cherimoya",
|
|
129
|
+
plugin_version="0.0.1",
|
|
130
|
+
altar_api=ApiVersionRange("0.1.0", "0.2.0"),
|
|
131
|
+
configuration_schema_version="0.0.1",
|
|
132
|
+
result_schema=ResultSchema(
|
|
133
|
+
version="0.0.1",
|
|
134
|
+
fields=(
|
|
135
|
+
ResultField(
|
|
136
|
+
"counts_log2fc",
|
|
137
|
+
"float",
|
|
138
|
+
"Counts Log2FC",
|
|
139
|
+
"Alternate-minus-reference log2 effect on the single-track log(count + 1) head",
|
|
140
|
+
),
|
|
141
|
+
ResultField(
|
|
142
|
+
"profile_l1",
|
|
143
|
+
"float",
|
|
144
|
+
"Profile L1",
|
|
145
|
+
"L1 distance between alternate and reference softmax-normalized profile distributions",
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
prioritization=PrioritizationPolicy(name="cherimoya-counts-effect-v1", version="0.0.1"),
|
|
150
|
+
runtimes=(
|
|
151
|
+
RuntimeRequirement(
|
|
152
|
+
name="model_runtime",
|
|
153
|
+
kind=RuntimeKind.CONTAINER_IMAGE,
|
|
154
|
+
policy=RuntimeIdentityPolicy.DIGEST_REQUIRED,
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
configuration_schema_id = "org.kundajelab.altar.model.cherimoya.configuration"
|
|
159
|
+
configuration_model = CherimoyaConfiguration
|
|
160
|
+
run_input_model = CherimoyaRunInputs
|
|
161
|
+
default_output = OutputSelection(reducer="mean")
|
|
162
|
+
|
|
163
|
+
def _configuration(self, value: ModelConfiguration) -> CherimoyaConfiguration:
|
|
164
|
+
return cast("CherimoyaConfiguration", self.parse_configuration(value))
|
|
165
|
+
|
|
166
|
+
def migrate_configuration(
|
|
167
|
+
self,
|
|
168
|
+
from_version: str,
|
|
169
|
+
configuration: Mapping[str, object],
|
|
170
|
+
) -> Mapping[str, object]:
|
|
171
|
+
"""Accept an already generic resource set or reject provider-shaped configurations explicitly."""
|
|
172
|
+
if from_version not in {"1.0.0", "1.1.0"}:
|
|
173
|
+
return super().migrate_configuration(from_version, configuration)
|
|
174
|
+
try:
|
|
175
|
+
return CherimoyaConfiguration.model_validate(configuration).to_dict()
|
|
176
|
+
except ValidationError as error:
|
|
177
|
+
msg = (
|
|
178
|
+
f"CHERIMOYA configuration {from_version} cannot migrate automatically: replace the "
|
|
179
|
+
"provider-specific weights object and num_folds with a non-empty weights list containing "
|
|
180
|
+
"one ResourceReference(uri=..., digest='sha256:...') per fold, then resave with the current schema"
|
|
181
|
+
)
|
|
182
|
+
raise ConfigurationVersionError(msg) from error
|
|
183
|
+
|
|
184
|
+
def convert_legacy_artifact(
|
|
185
|
+
self,
|
|
186
|
+
artifact: Mapping[str, object],
|
|
187
|
+
*,
|
|
188
|
+
genome_build: str,
|
|
189
|
+
) -> LegacyScoringConversion:
|
|
190
|
+
"""Convert a strict provider-neutral artifact mapping, rejecting every unknown key."""
|
|
191
|
+
allowed = {"mode", "weights", "genome", "in_window", "image"}
|
|
192
|
+
unknown = set(artifact) - allowed
|
|
193
|
+
if unknown:
|
|
194
|
+
msg = f"unknown Cherimoya legacy artifact fields: {sorted(unknown)}"
|
|
195
|
+
raise LegacyArtifactError(msg)
|
|
196
|
+
try:
|
|
197
|
+
legacy = CherimoyaArtifact.model_validate(dict(artifact))
|
|
198
|
+
except ValidationError as exc:
|
|
199
|
+
msg = f"invalid Cherimoya legacy artifact: {exc}"
|
|
200
|
+
raise LegacyArtifactError(msg) from exc
|
|
201
|
+
return LegacyScoringConversion(
|
|
202
|
+
configuration=CherimoyaConfiguration(
|
|
203
|
+
weights=legacy.weights,
|
|
204
|
+
in_window=legacy.in_window,
|
|
205
|
+
image=legacy.image,
|
|
206
|
+
),
|
|
207
|
+
run_inputs=CherimoyaRunInputs(genome_build=genome_build, genome=legacy.genome),
|
|
208
|
+
output=self.default_output,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def build_scoring_plan(self, request: ScoringRequest) -> ContainerScoringPlan:
|
|
212
|
+
"""Build one score task per fold and a CPU task that averages their outputs."""
|
|
213
|
+
configuration = self._configuration(request.configuration)
|
|
214
|
+
run_inputs = cast("CherimoyaRunInputs", self.parse_run_inputs(request.run_inputs))
|
|
215
|
+
output = self.validate_output_selection(request.output)
|
|
216
|
+
identity = self.scoring_run_identity(
|
|
217
|
+
{"model_runtime": configuration.image},
|
|
218
|
+
configuration=configuration,
|
|
219
|
+
output=output,
|
|
220
|
+
run_inputs=run_inputs,
|
|
221
|
+
)
|
|
222
|
+
num_folds = len(configuration.weights)
|
|
223
|
+
|
|
224
|
+
resolver, layout = request.resolver, request.layout
|
|
225
|
+
job_id, model_id, genome = request.job_id, request.model_id, request.genome_label
|
|
226
|
+
scoring_batch = request.scoring_batch
|
|
227
|
+
|
|
228
|
+
genome_logical = layout.genome_fasta(genome)
|
|
229
|
+
variants_logical = layout.model_variants_file(job_id, model_id, scoring_batch)
|
|
230
|
+
batch_labels = {"scoring_batch": str(scoring_batch)} if scoring_batch is not None else {}
|
|
231
|
+
|
|
232
|
+
shards: list[ContainerTaskSpec] = []
|
|
233
|
+
for fold, weights in enumerate(configuration.weights):
|
|
234
|
+
weights_logical = layout.fold_model_file(model_id, fold, ".torch")
|
|
235
|
+
output_logical = layout.fold_score_file(job_id, model_id, fold, scoring_batch)
|
|
236
|
+
command = [
|
|
237
|
+
"cherimoya-score",
|
|
238
|
+
"score",
|
|
239
|
+
"--weights",
|
|
240
|
+
resolver.resolve(weights_logical),
|
|
241
|
+
"--weights-digest",
|
|
242
|
+
cast("str", weights.digest),
|
|
243
|
+
"--in-window",
|
|
244
|
+
str(configuration.in_window),
|
|
245
|
+
"--variant-batch-size",
|
|
246
|
+
str(_VARIANT_BATCH_SIZE),
|
|
247
|
+
"--model-batch-size",
|
|
248
|
+
str(_MODEL_BATCH_SIZE),
|
|
249
|
+
"-g",
|
|
250
|
+
resolver.resolve(genome_logical),
|
|
251
|
+
"--genome-digest",
|
|
252
|
+
cast("str", run_inputs.genome.digest),
|
|
253
|
+
"-v",
|
|
254
|
+
resolver.resolve(variants_logical),
|
|
255
|
+
"-o",
|
|
256
|
+
resolver.resolve(output_logical),
|
|
257
|
+
]
|
|
258
|
+
shards.append(
|
|
259
|
+
ContainerTaskSpec(
|
|
260
|
+
kind=KIND_SCORING_FOLDS,
|
|
261
|
+
image=configuration.image,
|
|
262
|
+
command=command,
|
|
263
|
+
labels={
|
|
264
|
+
"job_id": job_id,
|
|
265
|
+
"model_id": model_id,
|
|
266
|
+
"fold": str(fold),
|
|
267
|
+
"num_folds": str(num_folds),
|
|
268
|
+
"kind": KIND_SCORING_FOLDS,
|
|
269
|
+
**batch_labels,
|
|
270
|
+
},
|
|
271
|
+
resources=_SCORE_RESOURCES,
|
|
272
|
+
inputs=[
|
|
273
|
+
resource_transfer(weights, weights_logical, locality_key=model_id),
|
|
274
|
+
resource_transfer(run_inputs.genome, genome_logical),
|
|
275
|
+
Transfer(uri=variants_logical, logical_path=variants_logical, locality_key=model_id),
|
|
276
|
+
],
|
|
277
|
+
outputs=[Transfer(uri=output_logical, logical_path=output_logical, locality_key=model_id)],
|
|
278
|
+
plugin_identity=identity,
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
fold_score_logicals = [
|
|
283
|
+
layout.fold_score_file(job_id, model_id, fold, scoring_batch) for fold in range(num_folds)
|
|
284
|
+
]
|
|
285
|
+
result_logical = layout.scoring_result_file(job_id, model_id, scoring_batch)
|
|
286
|
+
summarize_command = ["cherimoya-score", "summarize"]
|
|
287
|
+
for fold, logical in enumerate(fold_score_logicals):
|
|
288
|
+
summarize_command += [f"-f{fold}", resolver.resolve(logical)]
|
|
289
|
+
summarize_command += ["-o", resolver.resolve(result_logical)]
|
|
290
|
+
|
|
291
|
+
summarize = ContainerTaskSpec(
|
|
292
|
+
kind=KIND_SCORING_SUMMARIZE,
|
|
293
|
+
image=configuration.image,
|
|
294
|
+
command=summarize_command,
|
|
295
|
+
labels={
|
|
296
|
+
"job_id": job_id,
|
|
297
|
+
"model_id": model_id,
|
|
298
|
+
"kind": KIND_SCORING_SUMMARIZE,
|
|
299
|
+
**batch_labels,
|
|
300
|
+
},
|
|
301
|
+
resources=_SUMMARIZE_RESOURCES,
|
|
302
|
+
inputs=[
|
|
303
|
+
Transfer(uri=logical, logical_path=logical, locality_key=model_id) for logical in fold_score_logicals
|
|
304
|
+
],
|
|
305
|
+
outputs=[Transfer(uri=result_logical, logical_path=result_logical, locality_key=model_id)],
|
|
306
|
+
plugin_identity=identity,
|
|
307
|
+
)
|
|
308
|
+
return ContainerScoringPlan(
|
|
309
|
+
shards=shards,
|
|
310
|
+
plugin_identity=identity,
|
|
311
|
+
summarize=summarize,
|
|
312
|
+
ready_when=num_folds,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def prioritize_predicate(self) -> Predicate:
|
|
316
|
+
# Conservative placeholder, not a calibrated biological threshold.
|
|
317
|
+
return Ge(Abs(Col("counts_log2fc")), Lit(_LOG2FC_PRIORITIZE_THRESHOLD))
|
|
File without changes
|