Skip to content

Commit e9c8c77

Browse files
committed
feat: implement JSON-RPC caching layer and backend method handlers for model specification and orchestration
1 parent e512f7f commit e9c8c77

24 files changed

Lines changed: 4103 additions & 874 deletions

cppmega_mlx/data/nanochat_pipeline/tokenized_enriched.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
from cppmega_mlx.data.nanochat_pipeline.tokenized_enriched_schema import (
1111
PLATFORM_IDS_COLUMN,
12+
CHANGED_CHUNK_IDS_COLUMN,
13+
CHANGED_CHUNK_SPANS_COLUMN,
14+
EDIT_OP_PER_TOKEN_COLUMN,
15+
HUNK_ID_PER_TOKEN_COLUMN,
1216
TOKEN_AST_DEPTH_COLUMN,
1317
TOKEN_AST_NODE_TYPE_COLUMN,
1418
TOKEN_CALL_EDGES_COLUMN,
@@ -25,6 +29,8 @@
2529
TOKEN_SYMBOL_IDS_COLUMN,
2630
TOKEN_TYPE_EDGES_COLUMN,
2731
TOKEN_TYPE_REFS_COLUMN,
32+
TOKEN_CHANGE_MASK_POST_COLUMN,
33+
TOKEN_CHANGE_MASK_PRE_COLUMN,
2834
)
2935

3036
_KIND_STR_TO_INT = {
@@ -277,6 +283,44 @@ def _build_token_chunk_layout(
277283
}
278284

279285

286+
def _tokenize_optional_char_field(
287+
doc: dict[str, Any],
288+
keys: tuple[str, ...],
289+
token_spans: list[tuple[int, int]],
290+
) -> list[int]:
291+
for key in keys:
292+
values = doc.get(key, [])
293+
if values:
294+
return _chars_to_tokens_structure_ids(values, "", token_spans)
295+
return []
296+
297+
298+
def _changed_chunk_metadata(
299+
token_chunk_starts: list[int],
300+
token_chunk_ends: list[int],
301+
change_masks: tuple[list[int], ...],
302+
) -> tuple[list[int], list[dict[str, int]]]:
303+
if not token_chunk_starts or not token_chunk_ends:
304+
return [], []
305+
changed_ids: list[int] = []
306+
changed_spans: list[dict[str, int]] = []
307+
for chunk_idx, (start, end) in enumerate(zip(token_chunk_starts, token_chunk_ends)):
308+
start_i = max(int(start), 0)
309+
end_i = max(int(end), start_i)
310+
changed = False
311+
for mask in change_masks:
312+
if not mask:
313+
continue
314+
bounded_end = min(end_i, len(mask))
315+
if start_i < bounded_end and any(int(value) != 0 for value in mask[start_i:bounded_end]):
316+
changed = True
317+
break
318+
if changed:
319+
changed_ids.append(chunk_idx)
320+
changed_spans.append({"start": start_i, "end": end_i})
321+
return changed_ids, changed_spans
322+
323+
280324
def _platform_ids_from_doc(doc: dict[str, Any]) -> list[int]:
281325
platform_info = doc.get("platform_info")
282326
if not platform_info:
@@ -344,7 +388,28 @@ def materialize_tokenized_enriched_batch(
344388
len(token_ids),
345389
)
346390

347-
row = {
391+
token_change_mask_pre = _tokenize_optional_char_field(
392+
doc,
393+
("change_mask_pre", TOKEN_CHANGE_MASK_PRE_COLUMN),
394+
token_spans,
395+
)
396+
token_change_mask_post = _tokenize_optional_char_field(
397+
doc,
398+
("change_mask_post", "token_change_mask", TOKEN_CHANGE_MASK_POST_COLUMN),
399+
token_spans,
400+
)
401+
hunk_id_per_token = _tokenize_optional_char_field(
402+
doc,
403+
("hunk_id_per_char", HUNK_ID_PER_TOKEN_COLUMN),
404+
token_spans,
405+
)
406+
edit_op_per_token = _tokenize_optional_char_field(
407+
doc,
408+
("edit_op_per_char", "token_edit_op", EDIT_OP_PER_TOKEN_COLUMN),
409+
token_spans,
410+
)
411+
412+
row: dict[str, Any] = {
348413
TOKEN_IDS_COLUMN: token_ids,
349414
PLATFORM_IDS_COLUMN: _platform_ids_from_doc(doc),
350415
TOKEN_STRUCTURE_IDS_COLUMN: token_structure_ids,
@@ -384,8 +449,19 @@ def materialize_tokenized_enriched_batch(
384449
"",
385450
token_spans,
386451
),
452+
TOKEN_CHANGE_MASK_PRE_COLUMN: token_change_mask_pre,
453+
TOKEN_CHANGE_MASK_POST_COLUMN: token_change_mask_post,
454+
HUNK_ID_PER_TOKEN_COLUMN: hunk_id_per_token,
455+
EDIT_OP_PER_TOKEN_COLUMN: edit_op_per_token,
387456
}
388457
row.update(cast(dict[str, list[int]], _build_token_chunk_layout(doc, tok_chunks, len(token_ids))))
458+
changed_chunk_ids, changed_chunk_spans = _changed_chunk_metadata(
459+
cast(list[int], row[TOKEN_CHUNK_STARTS_COLUMN]),
460+
cast(list[int], row[TOKEN_CHUNK_ENDS_COLUMN]),
461+
(token_change_mask_pre, token_change_mask_post),
462+
)
463+
row[CHANGED_CHUNK_IDS_COLUMN] = changed_chunk_ids
464+
row[CHANGED_CHUNK_SPANS_COLUMN] = changed_chunk_spans
389465
out.append(row)
390466

391467
return out

cppmega_mlx/runtime/path_c_fusion.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2380,6 +2380,8 @@ def _tilelang_optimizer_for(
23802380
capture = io.StringIO()
23812381
with redirect_stdout(capture), redirect_stderr(capture):
23822382
from tilelang.engine.fusion import (
2383+
FusionBlockDescriptor,
2384+
FusionBlockRegistry,
23832385
FusionOptimizer,
23842386
FusionScheduleRegistry,
23852387
)
@@ -2408,14 +2410,25 @@ def _tilelang_optimizer_for(
24082410
require_single_kernel=True,
24092411
enable_z3_sync_async=region.z3_sync.enabled,
24102412
)
2411-
for node in region.nodes:
2412-
optimizer.add_node(
2413-
node.name,
2414-
op=node.op_name,
2415-
inputs=node.inputs,
2416-
outputs=node.outputs,
2417-
attrs=_tilelang_node_attrs(node),
2413+
block_registry = FusionBlockRegistry(
2414+
tuple(
2415+
FusionBlockDescriptor(
2416+
op=node.op_name,
2417+
inputs=node.inputs,
2418+
outputs=node.outputs,
2419+
attrs=_tilelang_node_attrs(node),
2420+
aliases=(f"{node.name}:{node.op_name}",),
2421+
)
2422+
for node in region.nodes
24182423
)
2424+
)
2425+
optimizer.add_blocks(
2426+
tuple(
2427+
{"name": node.name, "kind": f"{node.name}:{node.op_name}"}
2428+
for node in region.nodes
2429+
),
2430+
block_registry=block_registry,
2431+
)
24192432
workspace_edge_buffers = _declared_workspace_edge_buffers(schedule_template)
24202433
node_by_name = {node.name: node for node in region.nodes}
24212434
for edge in region.edges:

cppmega_v4/jsonrpc/cache.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""LRU cache keyed on canonical sha256 of the spec.
2+
3+
Cache key: SHA-256 of ``canonicalize(spec)`` — sorted keys, no
4+
whitespace, NaN/Inf rejected. Node positions are stripped via
5+
:func:`strip_layout` before canonicalisation so that dragging nodes
6+
around in the canvas doesn't bust the cache.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import hashlib
12+
import json
13+
from collections import OrderedDict
14+
from threading import Lock
15+
from typing import Any, Mapping
16+
17+
18+
DEFAULT_CAPACITY: int = 50
19+
20+
21+
_LAYOUT_KEYS: frozenset[str] = frozenset({"x", "y", "position", "layout"})
22+
23+
24+
def strip_layout(payload: Any) -> Any:
25+
"""Recursively drop layout-only fields before hashing.
26+
27+
Visual canvas positions never influence backend semantics; stripping
28+
them lets the cache survive drag operations without rebuilding.
29+
"""
30+
if isinstance(payload, Mapping):
31+
return {
32+
k: strip_layout(v)
33+
for k, v in payload.items()
34+
if k not in _LAYOUT_KEYS
35+
}
36+
if isinstance(payload, list):
37+
return [strip_layout(v) for v in payload]
38+
if isinstance(payload, tuple):
39+
return [strip_layout(v) for v in payload]
40+
return payload
41+
42+
43+
def canonical_json(payload: Any) -> str:
44+
"""Produce a deterministic JSON string for cache keying."""
45+
return json.dumps(
46+
strip_layout(payload),
47+
sort_keys=True,
48+
separators=(",", ":"),
49+
ensure_ascii=False,
50+
allow_nan=False,
51+
)
52+
53+
54+
def canonical_sha256(payload: Any) -> str:
55+
"""Stable cache key — SHA-256 over :func:`canonical_json`."""
56+
return hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest()
57+
58+
59+
class LRUCache:
60+
"""Bounded LRU cache for backend RPC results.
61+
62+
Thread-safe via a single Lock — RPC backends often share one cache
63+
across event-loop tasks. Capacity defaults to 50 per VBPlan §5.1.
64+
"""
65+
66+
def __init__(self, capacity: int = DEFAULT_CAPACITY) -> None:
67+
if capacity < 1:
68+
raise ValueError(f"capacity must be ≥ 1, got {capacity}")
69+
self._capacity = capacity
70+
self._data: OrderedDict[str, Any] = OrderedDict()
71+
self._lock = Lock()
72+
self._hits = 0
73+
self._misses = 0
74+
75+
@property
76+
def capacity(self) -> int:
77+
return self._capacity
78+
79+
def __len__(self) -> int:
80+
with self._lock:
81+
return len(self._data)
82+
83+
def get(self, key: str) -> Any | None:
84+
with self._lock:
85+
if key not in self._data:
86+
self._misses += 1
87+
return None
88+
self._data.move_to_end(key)
89+
self._hits += 1
90+
return self._data[key]
91+
92+
def set(self, key: str, value: Any) -> None:
93+
with self._lock:
94+
if key in self._data:
95+
self._data.move_to_end(key)
96+
self._data[key] = value
97+
return
98+
self._data[key] = value
99+
if len(self._data) > self._capacity:
100+
self._data.popitem(last=False)
101+
102+
def clear(self) -> None:
103+
with self._lock:
104+
self._data.clear()
105+
self._hits = 0
106+
self._misses = 0
107+
108+
def stats(self) -> dict[str, int]:
109+
with self._lock:
110+
return {
111+
"size": len(self._data),
112+
"capacity": self._capacity,
113+
"hits": self._hits,
114+
"misses": self._misses,
115+
}

0 commit comments

Comments
 (0)