File size: 7,297 Bytes
28a1a01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""On-disk cache helper for compiled-execution manifests (Phase 0).

No execution-path code lives here: canonical config rendering plus
atomic manifest read/write helpers only.
"""

from __future__ import annotations

import json
import os
import re
import struct
from pathlib import Path
from typing import Union

MANIFEST_FILENAME = "manifest.json"

_SANITIZE_RE = re.compile(r"[^A-Za-z0-9_.-]")

PathLike = Union[str, os.PathLike]


def _sanitize_model_id(model_id: str) -> str:
    return _SANITIZE_RE.sub("_", model_id)


def canonical_config(config: dict) -> str:
    """Render a model config canonically for compile-key hashing.

    Legacy sorted-JSON rendering for arbitrary dicts (used by early
    Phase-0 tests). For validated model configs prefer
    :func:`canonical_model_config`, which matches the native 12-field
    pipe string in ``Model::refresh_compile_key`` exactly.
    """
    return json.dumps(config, sort_keys=True, separators=(",", ":"), default=str)


def _fp32_hex(value: object) -> str:
    """Hexfloat of the FP32-rounded value, matching ``std::hexfloat``.

    Native ``Config.eps/rope_theta`` are ``float``; Python floats are F64,
    so round-trip through FP32 first (``struct`` avoids a NumPy import).
    """
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise TypeError("rms_norm_eps and rope_theta must be real numbers")
    rounded = struct.unpack("f", struct.pack("f", float(value)))[0]
    return rounded.hex()


def canonical_model_config(config: dict) -> str:
    """Render a validated model config as the native 12-field pipe string.

    Field order mirrors ``native/runtime.cpp`` ``refresh_compile_key``:
    ``model_type|hidden|intermediate|layers|heads|kv_heads|head_dim|
    vocab|context|hexfloat(eps)|hexfloat(rope_theta)|tied``.
    Keys are the post-``validate_config`` HF names (``hidden_size``,
    ``num_hidden_layers``, ...). ``tie_word_embeddings`` renders as 1/0.
    """
    try:
        model_type = config["model_type"]
        hidden = config["hidden_size"]
        intermediate = config["intermediate_size"]
        layers = config["num_hidden_layers"]
        heads = config["num_attention_heads"]
        kv_heads = config["num_key_value_heads"]
        head_dim = config["head_dim"]
        vocab = config["vocab_size"]
        context = config["max_position_embeddings"]
        eps = config["rms_norm_eps"]
        theta = config["rope_theta"]
        tied = config["tie_word_embeddings"]
    except KeyError as exc:
        raise KeyError(f"canonical_model_config missing validated field: {exc}") from exc
    if not isinstance(model_type, str) or not model_type:
        raise TypeError("model_type must be a nonempty string")
    for name, value in (("hidden_size", hidden), ("intermediate_size", intermediate),
                        ("num_hidden_layers", layers), ("num_attention_heads", heads),
                        ("num_key_value_heads", kv_heads), ("head_dim", head_dim),
                        ("vocab_size", vocab), ("max_position_embeddings", context)):
        if type(value) is not int or value <= 0:
            raise TypeError(f"{name} must be a positive integer")
    if type(tied) is not bool:
        raise TypeError("tie_word_embeddings must be a boolean")
    return "|".join((model_type, str(hidden), str(intermediate), str(layers),
                     str(heads), str(kv_heads), str(head_dim), str(vocab),
                     str(context), _fp32_hex(eps), _fp32_hex(theta),
                     "1" if tied else "0"))


def cache_dir_for(base_dir: PathLike, model_id: str, revision: object) -> Path:
    """Return ``<base_dir>/<sanitized model_id>/<revision>`` as a Path."""
    rev = "none" if revision is None else str(revision)
    return Path(base_dir) / _sanitize_model_id(str(model_id)) / rev


def artifact_dir(*args, **kwargs) -> Path:
    """Return the artifact directory for a compile plan.

    Supported forms (positional or keyword):

    * ``artifact_dir(cache_dir, key)`` — join an existing cache dir with key.
    * ``artifact_dir(base_dir, model_id, revision, key)`` — build from
      :func:`cache_dir_for` then join with key.
    """
    # Keyword aliases.
    cache_dir = kwargs.pop("cache_dir", None)
    key = kwargs.pop("key", None)
    base_dir = kwargs.pop("base_dir", None)
    model_id = kwargs.pop("model_id", None)
    revision = kwargs.pop("revision", None)
    if kwargs:
        raise TypeError(f"unexpected keyword arguments: {sorted(kwargs)}")

    positional = list(args)

    if cache_dir is not None and key is not None and base_dir is None and model_id is None:
        if positional:
            raise TypeError("artifact_dir() called with both positional and keyword cache args")
        return Path(cache_dir) / str(key)

    if base_dir is not None or model_id is not None or revision is not None:
        # Keyword 4-arg form.
        if positional:
            raise TypeError("artifact_dir() called with both positional and keyword args")
        if base_dir is None or model_id is None or key is None:
            raise TypeError("artifact_dir() 4-arg form needs base_dir, model_id, revision, key")
        rev = revision  # may be None; cache_dir_for handles it
        return cache_dir_for(base_dir, model_id, rev) / str(key)

    # Positional-only handling.
    if len(positional) == 2:
        cache, k = positional
        return Path(cache) / str(k)
    if len(positional) == 4:
        base, mid, rev, k = positional
        return cache_dir_for(base, mid, rev) / str(k)
    raise TypeError(
        "artifact_dir() expects (cache_dir, key) or (base_dir, model_id, revision, key)"
    )


def write_artifact(directory: PathLike, manifest: str) -> Path:
    """Atomically write ``manifest`` into ``directory/manifest.json``.

    Creates parents, writes to a temp file in the same directory, then
    ``os.replace`` for atomicity. Returns the artifact file path.
    """
    target_dir = Path(directory)
    target_dir.mkdir(parents=True, exist_ok=True)
    target = target_dir / MANIFEST_FILENAME
    tmp_name = f".{MANIFEST_FILENAME}.tmp-{os.getpid()}"
    tmp_path = target_dir / tmp_name
    # Write + fsync + atomic replace; cleanup temp on failure.
    try:
        with open(tmp_path, "w", encoding="utf-8", newline="\n") as handle:
            handle.write(manifest)
            handle.flush()
            try:
                os.fsync(handle.fileno())
            except OSError:
                pass
        os.replace(tmp_path, target)
    finally:
        try:
            if tmp_path.exists():
                tmp_path.unlink()
        except OSError:
            pass
    return target


def read_artifact(directory: PathLike) -> Union[str, None]:
    """Read ``directory/manifest.json``; return str or None on any error.

    Missing files, undecodable bytes, and invalid-JSON payloads are cache
    misses (None) and never raise.
    """
    try:
        target = Path(directory) / MANIFEST_FILENAME
        raw = target.read_bytes()
        text = raw.decode("utf-8")
        # Corrupt payloads (e.g. garbage bytes that happen to decode) are
        # misses too: manifests must be valid JSON documents.
        json.loads(text)
        return text
    except Exception:
        return None