modelexpress-client 0.7.0

Client library for Model Express gRPC server
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Host-local persistence for canonical checkpoint artifacts."""

from __future__ import annotations

import fcntl
import json
import shutil
from collections.abc import Iterable, Iterator
from contextlib import contextmanager
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from urllib.parse import quote


def _encode_cache_component(value: str) -> str:
    """Encode an opaque ID as exactly one filesystem path component."""
    # quote() never escapes dots, even with safe="". Encode dot-only IDs
    # explicitly so Path does not interpret them as the current or parent path.
    if value in {".", ".."}:
        return value.replace(".", "%2E")
    return quote(value, safe="")


class CheckpointState(str, Enum):
    """Preparation state persisted across receiver processes."""

    READY = "READY"
    UPDATING = "UPDATING"


@dataclass(frozen=True)
class CheckpointRecord:
    """Typed representation of the preparation transaction in ``state.json``."""

    status: CheckpointState
    version: str
    files: dict[str, list[int]] | None = None
    source: dict[str, str] | None = None

    @classmethod
    def from_dict(cls, value: dict | None) -> CheckpointRecord | None:
        if value is None or not isinstance(value.get("version"), str):
            return None
        try:
            status = CheckpointState(value.get("status"))
        except (TypeError, ValueError):
            return None
        files = value.get("files")
        source = value.get("source")
        if files is not None and not isinstance(files, dict):
            return None
        if source is not None and not isinstance(source, dict):
            return None
        return cls(
            status=status,
            version=value["version"],
            files=files,
            source=source,
        )

    def to_dict(self) -> dict[str, object]:
        value: dict[str, object] = {
            "status": self.status.value,
            "version": self.version,
        }
        if self.files is not None:
            value["files"] = self.files
        if self.source is not None:
            value["source"] = self.source
        return value


class CheckpointCacheCapacityError(RuntimeError):
    """The protected cache working set cannot admit an incoming write."""


def checkpoint_files_state(
    paths: Iterable[Path],
) -> dict[str, list[int]]:
    """Return the size and modification time of indexed checkpoint files."""
    return {
        path.name: [path.stat().st_size, path.stat().st_mtime_ns]
        for path in sorted(set(paths))
    }


def _artifact_files_state(path: Path) -> dict[str, list[int]]:
    return {
        str(file.relative_to(path)): [file.stat().st_size, file.stat().st_mtime_ns]
        for file in sorted(path.rglob("*"))
        if file.is_file() and file.name != ".source.json"
    }


class LocalCheckpointStore:
    """Persist one model's immutable lineage and activation state.

    Layout, locking, fingerprints, and temporary-directory promotion stay
    behind this concrete interface. Tensor reconstruction and object-storage
    access remain the receiver's responsibility.
    """

    def __init__(
        self,
        *,
        root: str | Path,
        model_name: str,
        max_size_bytes: int | None = None,
    ) -> None:
        # Per-model cache layout:
        #
        #   <cache>/
        #     full/<version>/           immutable full HF checkpoints
        #     deltas/<version>/         immutable delta index and shards
        #     chains/<version>.json     full ancestor plus ordered deltas
        #     materialized/<version>/   derived, installable full checkpoints
        #     state.json                current preparation transaction
        #     active.json               version committed after engine install
        #     .lock                     cross-process cache coordination
        #     .install.lock             blocks preparation during engine install
        #
        # Full checkpoints, deltas, and chain manifests are the canonical
        # lineage. Materialized checkpoints are rebuildable outputs for engines
        # that require an ordinary checkpoint directory. Preparation updates
        # state.json without changing active.json; installation advances
        # active.json only after the engine reload succeeds.
        self.cache = Path(root) / _encode_cache_component(model_name)
        self.full_cache = self.cache / "full"
        self.delta_cache = self.cache / "deltas"
        self.chain_cache = self.cache / "chains"
        self.materialized_cache = self.cache / "materialized"
        self.state_path = self.cache / "state.json"
        self.active_path = self.cache / "active.json"
        self.lock_path = self.cache / ".lock"
        self.install_lock_path = self.cache / ".install.lock"
        self.max_size_bytes = max_size_bytes

    def initialize(self) -> None:
        self.cache.mkdir(parents=True, exist_ok=True)
        for path in (
            self.full_cache,
            self.delta_cache,
            self.chain_cache,
            self.materialized_cache,
        ):
            path.mkdir(exist_ok=True)

    @contextmanager
    def _locked(self, path: Path, shared: bool) -> Iterator[None]:
        with path.open("a+") as handle:
            operation = fcntl.LOCK_SH if shared else fcntl.LOCK_EX
            fcntl.flock(handle, operation)
            yield

    def locked(self, *, shared: bool = False):
        return self._locked(self.lock_path, shared=shared)

    def installation_locked(self, *, shared: bool = False):
        return self._locked(self.install_lock_path, shared=shared)

    @contextmanager
    def replace_directory(
        self,
        target: Path,
        *,
        copy_from: Path | None = None,
    ) -> Iterator[Path]:
        """Populate a temporary directory and promote it to ``target``."""
        temporary = target.with_name(f"{target.name}.tmp")
        shutil.rmtree(temporary, ignore_errors=True)
        try:
            if copy_from is None:
                temporary.mkdir(parents=True)
            else:
                shutil.copytree(copy_from, temporary)
            yield temporary
            shutil.rmtree(target, ignore_errors=True)
            temporary.replace(target)
        except Exception:
            shutil.rmtree(temporary, ignore_errors=True)
            raise

    def _version_path(self, root: Path, version: str) -> Path:
        return root / _encode_cache_component(version)

    def full_path(self, version: str) -> Path:
        return self._version_path(self.full_cache, version)

    def delta_path(self, version: str) -> Path:
        return self._version_path(self.delta_cache, version)

    def materialized_path(self, version: str) -> Path:
        return self._version_path(self.materialized_cache, version)

    def chain_path(self, version: str) -> Path:
        return self.chain_cache / f"{_encode_cache_component(version)}.json"

    @staticmethod
    def path_size_bytes(path: Path) -> int:
        if path.is_file():
            return path.stat().st_size
        if not path.is_dir():
            return 0
        return sum(file.stat().st_size for file in path.rglob("*") if file.is_file())

    @staticmethod
    def _payload_size_bytes(path: Path) -> int:
        if path.is_file():
            return 0 if path.name == ".source.json" else path.stat().st_size
        if not path.is_dir():
            return 0
        return sum(
            file.stat().st_size
            for file in path.rglob("*")
            if file.is_file() and file.name != ".source.json"
        )

    def cache_size_bytes(self) -> int:
        return sum(
            self._payload_size_bytes(root)
            for root in (
                self.full_cache,
                self.delta_cache,
                self.materialized_cache,
            )
        )

    def _protected_paths(self, versions: Iterable[str]) -> set[Path]:
        protected: set[Path] = set()
        for version in versions:
            protected.update(
                {
                    self.chain_path(version),
                    self.full_path(version),
                    self.delta_path(version),
                    self.materialized_path(version),
                }
            )
            chain = self.chain(version)
            if chain is None:
                continue
            full_version = chain.get("full_version")
            if isinstance(full_version, str):
                protected.add(self.full_path(full_version))
            deltas = chain.get("deltas")
            if isinstance(deltas, list):
                protected.update(
                    self.delta_path(delta) for delta in deltas if isinstance(delta, str)
                )
        return protected

    def _eviction_candidates(self, protected: set[Path]) -> list[Path]:
        groups = (
            self.materialized_cache,
            self.delta_cache,
            self.full_cache,
        )
        candidates: list[Path] = []
        for root in groups:
            candidates.extend(
                sorted(
                    (path for path in root.iterdir() if path not in protected),
                    key=lambda path: path.stat().st_mtime_ns,
                )
            )
        return candidates

    def _remove_orphaned_chain(self, encoded_version: str) -> None:
        if any(
            (root / encoded_version).exists()
            for root in (
                self.full_cache,
                self.delta_cache,
                self.materialized_cache,
            )
        ):
            return
        (self.chain_cache / f"{encoded_version}.json").unlink(missing_ok=True)

    def ensure_capacity(
        self,
        additional_bytes: int,
        *,
        protected_versions: Iterable[str] = (),
        protected_paths: Iterable[Path] = (),
    ) -> None:
        """Evict stale entries before adding bytes, or reject the write."""
        if additional_bytes < 0:
            raise ValueError("additional_bytes must not be negative")

        size = self.cache_size_bytes()
        protected = self._protected_paths(protected_versions)
        protected.update(protected_paths)

        def has_capacity() -> bool:
            quota_available = (
                self.max_size_bytes is None
                or size + additional_bytes <= self.max_size_bytes
            )
            return (
                quota_available
                and shutil.disk_usage(self.cache).free >= additional_bytes
            )

        for candidate in self._eviction_candidates(protected):
            if has_capacity():
                break
            freed_bytes = self._payload_size_bytes(candidate)
            if candidate.is_dir():
                shutil.rmtree(candidate)
            else:
                candidate.unlink()
            self._remove_orphaned_chain(candidate.name)
            size -= freed_bytes

        if not has_capacity():
            reasons = []
            if (
                self.max_size_bytes is not None
                and size + additional_bytes > self.max_size_bytes
            ):
                reasons.append(
                    f"checkpoint cache quota of {self.max_size_bytes} bytes cannot "
                    f"accommodate {additional_bytes} additional bytes"
                )
            free_bytes = shutil.disk_usage(self.cache).free
            if free_bytes < additional_bytes:
                reasons.append(
                    f"checkpoint cache filesystem has {free_bytes} bytes free but "
                    f"requires {additional_bytes} bytes"
                )
            raise CheckpointCacheCapacityError("; ".join(reasons))

    def enforce_capacity(self, *, protected_versions: Iterable[str] = ()) -> None:
        self.ensure_capacity(0, protected_versions=protected_versions)

    @staticmethod
    def _write_json(path: Path, value: dict[str, object]) -> None:
        temporary = path.with_name(f"{path.name}.tmp")
        temporary.write_text(json.dumps(value, sort_keys=True))
        temporary.replace(path)

    @staticmethod
    def _read_json(path: Path) -> dict | None:
        if not path.is_file():
            return None
        try:
            value = json.loads(path.read_text())
        except (OSError, ValueError):
            return None
        return value if isinstance(value, dict) else None

    def state(self) -> CheckpointRecord | None:
        return CheckpointRecord.from_dict(self._read_json(self.state_path))

    def write_state(
        self,
        *,
        status: CheckpointState,
        version: str,
        checkpoint_paths: Iterable[Path],
        source: dict[str, str] | None = None,
    ) -> None:
        state = CheckpointRecord(
            status=status,
            version=version,
            files=(
                checkpoint_files_state(checkpoint_paths)
                if status is CheckpointState.READY
                else None
            ),
            source=source,
        )
        self._write_json(self.state_path, state.to_dict())

    def chain(self, version: str) -> dict | None:
        return self._read_json(self.chain_path(version))

    def write_chain(self, version: str, chain: dict[str, object]) -> None:
        self._write_json(self.chain_path(version), chain)

    def checkpoint_path(self, version: str) -> Path:
        chain = self.chain(version)
        if chain is None:
            raise RuntimeError(f"checkpoint chain for {version!r} is missing")
        if chain.get("deltas"):
            return self.materialized_path(version)
        return self.full_path(version)

    def active_version(self) -> str:
        active = self._read_json(self.active_path)
        if active is None or not isinstance(active.get("version"), str):
            raise RuntimeError("active checkpoint version is missing")
        return active["version"]

    def activate(self, version: str) -> None:
        self._write_json(self.active_path, {"version": version})

    @staticmethod
    def _source_path(artifact: Path) -> Path:
        return artifact / ".source.json"

    def record_artifact(
        self,
        artifact: Path,
        *,
        source: dict[str, str] | None = None,
    ) -> None:
        self._write_json(
            self._source_path(artifact),
            {
                "source": source,
                "files": _artifact_files_state(artifact),
            },
        )

    def _verified_artifact_metadata(self, artifact: Path) -> dict:
        metadata = self._read_json(self._source_path(artifact))
        if metadata is None or metadata.get("files") != _artifact_files_state(
            artifact
        ):
            raise ValueError("cached checkpoint artifact changed")
        return metadata

    def verify_artifact(self, artifact: Path) -> None:
        self._verified_artifact_metadata(artifact)

    def artifact_source(self, artifact: Path) -> dict[str, str] | None:
        """Return the recorded immutable source after verifying local files."""
        return self._verified_artifact_metadata(artifact).get("source")

    def verify_artifact_source(
        self,
        artifact: Path,
        expected_source: dict[str, str],
    ) -> None:
        if self.artifact_source(artifact) != expected_source:
            raise ValueError("prepared checkpoint has different source identity")


__all__ = [
    "CheckpointCacheCapacityError",
    "CheckpointRecord",
    "CheckpointState",
    "LocalCheckpointStore",
    "checkpoint_files_state",
]