File size: 6,372 Bytes
944d481
 
 
 
 
 
 
 
2e4bff8
 
 
 
 
fd041a3
2e4bff8
 
 
 
 
 
8cbcdb6
 
 
fd041a3
 
 
 
 
 
 
 
944d481
fd041a3
 
 
 
 
944d481
fd041a3
 
 
944d481
fd041a3
944d481
 
 
 
 
 
 
fd041a3
 
944d481
 
fd041a3
944d481
fd041a3
 
 
 
 
 
944d481
 
 
 
 
 
 
 
fd041a3
 
 
944d481
fd041a3
 
 
 
944d481
fd041a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
944d481
 
 
 
 
 
 
2e4bff8
 
 
 
 
fd041a3
2e4bff8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd041a3
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
"""Hugging Face Inference Endpoints custom handler for Banyan Sapling.

Loads the model exactly like the reference `inference.py` shipped with the
weights package (verified against the H200 reference runtime), with two
serving-only deviations:
  - skips the strict package-version assertion loop (requirements.txt pins them)
  - skips the full 46 GB sha256 pre-hash (the Hub delivers checksummed storage)
"""
from __future__ import annotations

import json
from pathlib import Path
import sys
import time
from typing import Any, Dict

CURRENT_DIR = Path(__file__).resolve().parent
if str(CURRENT_DIR) not in sys.path:
    sys.path.insert(0, str(CURRENT_DIR))

import os
os.environ.setdefault('CUBLAS_WORKSPACE_CONFIG', ':4096:8')


def load_model_for_endpoint(root: Path, device: str = "cuda"):
    root = Path(root).resolve()
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    import torch
    from safetensors.torch import load_file
    # The text-only runtime does not require torchvision or torchaudio.
    sys.modules.setdefault('torchvision', None)
    sys.modules.setdefault('torchaudio', None)
    from transformers import AutoConfig, AutoModel, AutoTokenizer
    from transformers.models.qwen3_moe.modeling_qwen3_moe import Qwen3MoeRotaryEmbedding
    from runtime.backbone import attach_lora, set_shared
    from runtime.model import FullModel, identity, seed_all, tensor_hash
    from runtime.conformal_calibrator import ConformalCalibrator

    seed_all(1707)
    config_data = json.loads((root / 'config.json').read_text())
    architecture = json.loads((root / 'architecture.json').read_text())
    expert_counts = [config_data[k] for k in ('num_local_experts', 'num_experts') if k in config_data]
    if (not expert_counts or any(n != 96 for n in expert_counts)
            or config_data.get('model_type') != 'qwen3_moe'
            or identity(config_data) != architecture['hf_config_sha256']
            or architecture.get('modify_rope') is not False
            or architecture.get('factorized_lora') is not True):
        raise ValueError('Unsupported model architecture')
    config = AutoConfig.from_pretrained(root, local_files_only=True)
    with torch.device('meta'):
        backbone = AutoModel.from_config(config, dtype=torch.bfloat16,
                                        attn_implementation='sdpa', experts_implementation='grouped_mm')
        attach_lora(backbone, architecture['config'])
        if architecture['shared_ids'] is not None:
            set_shared(backbone, architecture['shared_ids'])
        model = FullModel(backbone)
    state = load_file(str(root / 'model.safetensors'), device=device)
    model.load_state_dict(state, strict=True, assign=True)
    del state
    model.backbone.rotary_emb = Qwen3MoeRotaryEmbedding(config, device=device)
    rope = model.backbone.rotary_emb
    scaling = rope.attention_scaling
    fingerprint = {'inv_freq': tensor_hash({'inv_freq': rope.inv_freq}),
                   'attention_scaling': scaling.item() if hasattr(scaling, 'item') else scaling}
    if fingerprint != architecture['rotary_fingerprint']:
        raise ValueError('Native positional encoding does not match the checkpoint')
    if any(t.is_meta for t in list(model.parameters()) + list(model.buffers())):
        raise ValueError('Incomplete model materialization')
    for name, parameter in model.named_parameters():
        expected = torch.float32 if name.startswith('head.') or name.endswith(('.A', '.B')) or '.mlp.gate.' in name else torch.bfloat16
        if parameter.dtype != expected:
            raise ValueError(f'Unexpected precision: {name}')
    model.requires_grad_(False)
    model.eval()
    tokenizer = AutoTokenizer.from_pretrained(root / 'tokenizer', local_files_only=True)
    calibrator = ConformalCalibrator.from_dict(json.loads((root / 'calibration.json').read_text()))

    manifest_path = root / 'distribution_manifest.json'
    if manifest_path.exists():
        manifest = json.loads(manifest_path.read_text())
    else:
        manifest = {'source_release_sha256': '', 'files': {'model.safetensors': ''}}
    return model, tokenizer, calibrator, manifest


def predict_for_endpoint(model, tokenizer, calibrator, manifest, request):
    import torch
    from runtime.backbone import encode
    from runtime.model import infer

    row = encode(tokenizer, request, 16384)
    pad = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else tokenizer.eos_token_id
    start = time.perf_counter()
    logits = infer(model, [row], pad)[0]
    detail = calibrator.predict_detailed(logits.reshape(1, 4), empty_set_policy='rounded_argmax')[0]
    probabilities = torch.softmax(logits.double() / calibrator.temperature, -1).tolist()
    return {'model': 'banyan-sapling', 'release_stage': 'research-preview',
            'source_release_sha256': manifest.get('source_release_sha256', ''),
            'weights_sha256': manifest.get('files', {}).get('model.safetensors', ''),
            'logits': logits.tolist(), **detail,
            'probabilities': dict(zip(calibrator.LABELS, probabilities)),
            'usage': {'input_tokens': len(row['input_ids']), 'generated_tokens': 0},
            'timing_ms': {'model': (time.perf_counter() - start) * 1000}}


class EndpointHandler:
    def __init__(self, path: str = "") -> None:
        model_dir = Path(path) if path else CURRENT_DIR
        self.model, self.tokenizer, self.calibrator, self.manifest = load_model_for_endpoint(
            model_dir, device="cuda"
        )

    def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
        payload = data.get("inputs", data)
        if isinstance(payload, str):
            try:
                payload = json.loads(payload)
            except Exception:
                return {"error": "Invalid payload: expected JSON object with policy, evidence, and claim strings."}

        if not isinstance(payload, dict):
            return {"error": "Payload must be a dictionary with policy, evidence, and claim fields."}

        for field in ("policy", "evidence", "claim"):
            val = payload.get(field)
            if not val or not str(val).strip():
                return {"error": f"Missing required non-empty string field: '{field}'"}

        return predict_for_endpoint(self.model, self.tokenizer, self.calibrator, self.manifest, payload)