ollada 0.3.7__py3-none-win_amd64.whl
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.
- ollada/__init__.py +3 -0
- ollada/adapters/__init__.py +33 -0
- ollada/adapters/base.py +130 -0
- ollada/adapters/diffcoder.py +103 -0
- ollada/adapters/dream.py +59 -0
- ollada/adapters/nemotron.py +70 -0
- ollada/adapters/sdar.py +1031 -0
- ollada/catalog/diffcoder-8b.yaml +37 -0
- ollada/catalog/diffcoder-8b4bit.yaml +42 -0
- ollada/catalog/dream-7b.yaml +36 -0
- ollada/catalog/nemotron-3b.yaml +25 -0
- ollada/catalog/sdar-1.7b.yaml +31 -0
- ollada/catalog/sdar-4b.yaml +48 -0
- ollada/cli.py +632 -0
- ollada/fit.py +258 -0
- ollada/measurements.py +151 -0
- ollada/recipes.py +216 -0
- ollada/runtime/__init__.py +5 -0
- ollada/runtime/manager.py +333 -0
- ollada/server.py +243 -0
- ollada/worker.py +254 -0
- ollada/workers.py +402 -0
- ollada-0.3.7.data/scripts/ollada.exe +0 -0
- ollada-0.3.7.dist-info/METADATA +99 -0
- ollada-0.3.7.dist-info/RECORD +28 -0
- ollada-0.3.7.dist-info/WHEEL +4 -0
- ollada-0.3.7.dist-info/licenses/LICENSE +21 -0
- ollada-0.3.7.dist-info/sboms/ollada-rs.cyclonedx.json +3473 -0
ollada/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Family adapter registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ollada.adapters.base import AdapterError, BaseAdapter, GenResult
|
|
6
|
+
from ollada.recipes import Recipe
|
|
7
|
+
|
|
8
|
+
__all__ = ["AdapterError", "BaseAdapter", "GenResult", "create_adapter"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_adapter(recipe: Recipe) -> BaseAdapter:
|
|
12
|
+
"""Import lazily so the CLI works on machines without torch installed."""
|
|
13
|
+
if recipe.family == "dream":
|
|
14
|
+
from ollada.adapters.dream import DreamAdapter
|
|
15
|
+
|
|
16
|
+
return DreamAdapter(recipe)
|
|
17
|
+
if recipe.family == "sdar":
|
|
18
|
+
from ollada.adapters.sdar import SdarAdapter
|
|
19
|
+
|
|
20
|
+
return SdarAdapter(recipe)
|
|
21
|
+
if recipe.family == "nemotron":
|
|
22
|
+
# Dormant: Nemotron is the first planned post-v0.1 addition. Its
|
|
23
|
+
# adapter stays in the tree; it joins the shelf (catalog) later,
|
|
24
|
+
# together with the isolated-worker build its transformers>=5
|
|
25
|
+
# requirement forces (PLAN.md section 5, decision record).
|
|
26
|
+
from ollada.adapters.nemotron import NemotronAdapter
|
|
27
|
+
|
|
28
|
+
return NemotronAdapter(recipe)
|
|
29
|
+
if recipe.family == "diffcoder":
|
|
30
|
+
from ollada.adapters.diffcoder import DiffCoderAdapter
|
|
31
|
+
|
|
32
|
+
return DiffCoderAdapter(recipe)
|
|
33
|
+
raise AdapterError(f"no adapter for family '{recipe.family}' (model {recipe.name})")
|
ollada/adapters/base.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Adapter interface — one adapter per model FAMILY.
|
|
2
|
+
|
|
3
|
+
An adapter hides everything family-specific (how the model loads, what its
|
|
4
|
+
generate() is called, which knobs it takes) behind one interface, so the
|
|
5
|
+
daemon and CLI never see a family's custom code paths directly.
|
|
6
|
+
|
|
7
|
+
IMPORTANT: an adapter existing is NOT support. Support means the recipe's
|
|
8
|
+
verification block is filled by a real-load run on real GPU hardware
|
|
9
|
+
(PLAN.md section 7). Until then each adapter is unverified.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
from ollada.recipes import Recipe
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class GenResult:
|
|
22
|
+
text: str
|
|
23
|
+
tokens: int
|
|
24
|
+
seconds: float
|
|
25
|
+
nfe: int | None = None # number of function evaluations, when the family reports it
|
|
26
|
+
# Worker-reported measurement (worker path only; in-process loads use
|
|
27
|
+
# measurements.capture_and_record instead). None means "not measured".
|
|
28
|
+
peak_gb: float | None = None
|
|
29
|
+
hardware: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AdapterError(RuntimeError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseAdapter:
|
|
37
|
+
family = "base"
|
|
38
|
+
# Watch mode: an adapter that can report per-step canvas state
|
|
39
|
+
# during generation sets this True and accepts on_step in chat().
|
|
40
|
+
# The manager forwards a step callback only to supporting
|
|
41
|
+
# adapters; everyone else is called exactly as before.
|
|
42
|
+
supports_watch = False
|
|
43
|
+
|
|
44
|
+
def __init__(self, recipe: Recipe):
|
|
45
|
+
self.recipe = recipe
|
|
46
|
+
self.model = None
|
|
47
|
+
self.tokenizer = None
|
|
48
|
+
|
|
49
|
+
# -- lifecycle ---------------------------------------------------------
|
|
50
|
+
def load(self) -> None:
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
|
|
53
|
+
def unload(self) -> None:
|
|
54
|
+
self.model = None
|
|
55
|
+
self.tokenizer = None
|
|
56
|
+
try:
|
|
57
|
+
import torch
|
|
58
|
+
|
|
59
|
+
if torch.cuda.is_available():
|
|
60
|
+
torch.cuda.empty_cache()
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def loaded(self) -> bool:
|
|
66
|
+
return self.model is not None
|
|
67
|
+
|
|
68
|
+
# -- generation --------------------------------------------------------
|
|
69
|
+
def chat(
|
|
70
|
+
self, messages: list[dict], overrides: dict | None = None, on_step=None
|
|
71
|
+
) -> GenResult:
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
# -- helpers ------------------------------------------------------------
|
|
75
|
+
def _params(self, overrides: dict | None) -> dict:
|
|
76
|
+
params = dict(self.recipe.generate)
|
|
77
|
+
if overrides:
|
|
78
|
+
params.update(overrides)
|
|
79
|
+
return params
|
|
80
|
+
|
|
81
|
+
def _prompt_ids(self, messages: list[dict]):
|
|
82
|
+
"""Tokenize a chat history, preferring the tokenizer's chat template."""
|
|
83
|
+
from collections.abc import Mapping
|
|
84
|
+
|
|
85
|
+
tok = self.tokenizer
|
|
86
|
+
if getattr(tok, "chat_template", None):
|
|
87
|
+
result = tok.apply_chat_template(
|
|
88
|
+
messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
|
|
89
|
+
)
|
|
90
|
+
# BatchEncoding is a dict subclass on transformers 4.x but a
|
|
91
|
+
# UserDict (Mapping, NOT a dict) on 5.x — an isinstance(dict)
|
|
92
|
+
# check silently passes it through whole on 5.x, and the model's
|
|
93
|
+
# generate then dies on `.shape` (DiffCoder T4 run, 2026-10-21).
|
|
94
|
+
if isinstance(result, Mapping):
|
|
95
|
+
result = result["input_ids"]
|
|
96
|
+
return result
|
|
97
|
+
text = ""
|
|
98
|
+
for msg in messages:
|
|
99
|
+
text += f"{msg['role']}: {msg['content']}\n"
|
|
100
|
+
text += "assistant:"
|
|
101
|
+
return tok(text, return_tensors="pt").input_ids
|
|
102
|
+
|
|
103
|
+
def _finish(self, out_ids, prompt_len: int, started: float, nfe: int | None = None) -> GenResult:
|
|
104
|
+
new_ids = out_ids[0][prompt_len:]
|
|
105
|
+
text = self.tokenizer.decode(new_ids, skip_special_tokens=True)
|
|
106
|
+
return GenResult(
|
|
107
|
+
text=text.strip(),
|
|
108
|
+
tokens=int(new_ids.shape[0]),
|
|
109
|
+
seconds=time.time() - started,
|
|
110
|
+
nfe=nfe,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _require_loaded(self):
|
|
114
|
+
if not self.loaded:
|
|
115
|
+
raise AdapterError(f"{self.recipe.name} is not loaded")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def quantization_config(quant: str):
|
|
119
|
+
"""Build a BitsAndBytesConfig for int8/int4, else None.
|
|
120
|
+
|
|
121
|
+
Never pass load_in_8bit as a raw from_pretrained kwarg — it leaks into
|
|
122
|
+
some families' custom __init__ and crashes (npc-engine, 2026-10-20).
|
|
123
|
+
"""
|
|
124
|
+
if quant not in ("int8", "int4"):
|
|
125
|
+
return None
|
|
126
|
+
from transformers import BitsAndBytesConfig
|
|
127
|
+
|
|
128
|
+
if quant == "int8":
|
|
129
|
+
return BitsAndBytesConfig(load_in_8bit=True)
|
|
130
|
+
return BitsAndBytesConfig(load_in_4bit=True)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Stable-DiffCoder family adapter (ByteDance-Seed).
|
|
2
|
+
|
|
3
|
+
Real signature ( modeling_stable_diffcoder.py, read 2026-10-21 ):
|
|
4
|
+
generate(input_ids=None, generation_config=None, **kwargs) -> plain tensor
|
|
5
|
+
kwargs forward to generate_block(steps=128, gen_length=128, block_length=4,
|
|
6
|
+
temperature=0.0, remasking='low_confidence', tokenizer=None,
|
|
7
|
+
mask_id=5, threshold=0.95, shift=False, eos_id=None)
|
|
8
|
+
The model card's canonical example passes tokenizer=tokenizer,
|
|
9
|
+
threshold=None and eos_id=tokenizer.eos_token_id explicitly — the
|
|
10
|
+
generate_block defaults differ (threshold 0.95, no EOS truncation), so we
|
|
11
|
+
pass all three. Assertions in generate_block: gen_length % block_length == 0
|
|
12
|
+
and steps % (gen_length // block_length) == 0 (recipe defaults satisfy both:
|
|
13
|
+
256 % 4 == 0, 128 % 64 == 0). Batch size must be 1.
|
|
14
|
+
|
|
15
|
+
The model's config was saved with transformers 5.3.0 — see the recipe pin.
|
|
16
|
+
|
|
17
|
+
Load repairs applied by this adapter:
|
|
18
|
+
1. generate_block builds its block-causal mask with dtype=torch.bfloat16
|
|
19
|
+
hardcoded, while the recipe loads fp16 on pre-Ampere GPUs (T4) —
|
|
20
|
+
transformers 5.x SDPA rejects a float mask whose dtype differs from the
|
|
21
|
+
query dtype (RuntimeError, T4 run 2026-10-21). The adapter wraps
|
|
22
|
+
_make_block_causal_mask so the mask follows the model's dtype. The mask
|
|
23
|
+
is additive (0 / -inf), so fp16 loses nothing.
|
|
24
|
+
|
|
25
|
+
UNVERIFIED in ollada until a real-load gate passes on a T4.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import time
|
|
31
|
+
|
|
32
|
+
import torch
|
|
33
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
34
|
+
|
|
35
|
+
from ollada.adapters.base import BaseAdapter, quantization_config
|
|
36
|
+
|
|
37
|
+
_DTYPES = {"float16": torch.float16, "bfloat16": torch.bfloat16}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _align_mask_dtype(model) -> bool:
|
|
41
|
+
"""Wrap _make_block_causal_mask so its output dtype follows the model.
|
|
42
|
+
|
|
43
|
+
Returns True when the wrapper was installed. See the module docstring
|
|
44
|
+
(repair 1) for why this exists.
|
|
45
|
+
"""
|
|
46
|
+
orig = getattr(model, "_make_block_causal_mask", None)
|
|
47
|
+
if orig is None:
|
|
48
|
+
return False
|
|
49
|
+
target = model.dtype
|
|
50
|
+
|
|
51
|
+
def patched(*args, **kwargs):
|
|
52
|
+
kwargs["dtype"] = target
|
|
53
|
+
return orig(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
model._make_block_causal_mask = patched
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DiffCoderAdapter(BaseAdapter):
|
|
60
|
+
family = "diffcoder"
|
|
61
|
+
|
|
62
|
+
def load(self) -> None:
|
|
63
|
+
recipe = self.recipe
|
|
64
|
+
notes: list[str] = []
|
|
65
|
+
self.tokenizer = AutoTokenizer.from_pretrained(recipe.source, trust_remote_code=True)
|
|
66
|
+
kwargs = {
|
|
67
|
+
"trust_remote_code": True,
|
|
68
|
+
"dtype": _DTYPES.get(recipe.dtype, torch.float16),
|
|
69
|
+
}
|
|
70
|
+
qc = quantization_config(recipe.quant_default)
|
|
71
|
+
if qc is not None:
|
|
72
|
+
kwargs["quantization_config"] = qc
|
|
73
|
+
kwargs["device_map"] = "auto"
|
|
74
|
+
self.model = AutoModelForCausalLM.from_pretrained(recipe.source, **kwargs)
|
|
75
|
+
if qc is None and torch.cuda.is_available():
|
|
76
|
+
self.model = self.model.cuda()
|
|
77
|
+
self.model.eval()
|
|
78
|
+
if _align_mask_dtype(self.model):
|
|
79
|
+
notes.append(f"block mask dtype aligned to model dtype ({self.model.dtype})")
|
|
80
|
+
self.load_notes = notes
|
|
81
|
+
|
|
82
|
+
@torch.no_grad()
|
|
83
|
+
def chat(self, messages, overrides=None):
|
|
84
|
+
self._require_loaded()
|
|
85
|
+
params = self._params(overrides)
|
|
86
|
+
input_ids = self._prompt_ids(messages).to(self.model.device)
|
|
87
|
+
started = time.time()
|
|
88
|
+
threshold = params.get("threshold", None) # card example: None (fixed schedule)
|
|
89
|
+
out = self.model.generate(
|
|
90
|
+
input_ids,
|
|
91
|
+
steps=int(params.get("steps", 128)),
|
|
92
|
+
gen_length=int(params.get("gen_length", 256)),
|
|
93
|
+
block_length=int(params.get("block_length", 4)),
|
|
94
|
+
temperature=float(params.get("temperature", 0.0)),
|
|
95
|
+
remasking=str(params.get("remasking", "low_confidence")),
|
|
96
|
+
tokenizer=self.tokenizer,
|
|
97
|
+
shift=False,
|
|
98
|
+
threshold=None if threshold is None else float(threshold),
|
|
99
|
+
eos_id=self.tokenizer.eos_token_id,
|
|
100
|
+
)
|
|
101
|
+
# generate() returns a plain tensor (not a GenerateOutput).
|
|
102
|
+
ids = out.sequences if hasattr(out, "sequences") else out
|
|
103
|
+
return self._finish(ids, input_ids.shape[1], started)
|
ollada/adapters/dream.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Dream family adapter (Dream-v0-Instruct-7B).
|
|
2
|
+
|
|
3
|
+
Load path mirrors the proven npc-engine harness: BitsAndBytesConfig for
|
|
4
|
+
8-bit (never a raw load_in_8bit kwarg) and the recipe's transformers 4.x
|
|
5
|
+
pin (5.x breaks Dream's RoPE config with KeyError 'default' — proven live
|
|
6
|
+
on the 5060 Ti run, 2026-10-25; verified on 4.53.3 and 4.57.6).
|
|
7
|
+
|
|
8
|
+
UNVERIFIED in ollada until the Phase 1 real-load gate passes on a T4.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
import torch
|
|
16
|
+
from transformers import AutoModel, AutoTokenizer
|
|
17
|
+
|
|
18
|
+
from ollada.adapters.base import BaseAdapter, GenResult, quantization_config
|
|
19
|
+
|
|
20
|
+
_DTYPES = {"float16": torch.float16, "bfloat16": torch.bfloat16}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DreamAdapter(BaseAdapter):
|
|
24
|
+
family = "dream"
|
|
25
|
+
|
|
26
|
+
def load(self) -> None:
|
|
27
|
+
recipe = self.recipe
|
|
28
|
+
self.tokenizer = AutoTokenizer.from_pretrained(recipe.source, trust_remote_code=True)
|
|
29
|
+
kwargs = {
|
|
30
|
+
"trust_remote_code": True,
|
|
31
|
+
"torch_dtype": _DTYPES.get(recipe.dtype, torch.float16),
|
|
32
|
+
}
|
|
33
|
+
qc = quantization_config(recipe.quant_default)
|
|
34
|
+
if qc is not None:
|
|
35
|
+
kwargs["quantization_config"] = qc
|
|
36
|
+
kwargs["device_map"] = "auto"
|
|
37
|
+
self.model = AutoModel.from_pretrained(recipe.source, **kwargs)
|
|
38
|
+
if qc is None and torch.cuda.is_available():
|
|
39
|
+
self.model = self.model.cuda()
|
|
40
|
+
self.model.eval()
|
|
41
|
+
|
|
42
|
+
@torch.no_grad()
|
|
43
|
+
def chat(self, messages, overrides=None):
|
|
44
|
+
self._require_loaded()
|
|
45
|
+
params = self._params(overrides)
|
|
46
|
+
input_ids = self._prompt_ids(messages).to(self.model.device)
|
|
47
|
+
started = time.time()
|
|
48
|
+
out = self.model.diffusion_generate(
|
|
49
|
+
input_ids,
|
|
50
|
+
max_new_tokens=int(params.get("gen_length", 256)),
|
|
51
|
+
steps=int(params.get("steps", 32)),
|
|
52
|
+
temperature=float(params.get("temperature", 0.2)),
|
|
53
|
+
top_p=float(params.get("top_p", 0.95)),
|
|
54
|
+
alg="origin",
|
|
55
|
+
alg_temp=0.0,
|
|
56
|
+
mask_id=self.model.config.mask_token_id,
|
|
57
|
+
)
|
|
58
|
+
ids = out.sequences if hasattr(out, "sequences") else out
|
|
59
|
+
return self._finish(ids, input_ids.shape[1], started)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Nemotron-Labs Diffusion family adapter (NVIDIA).
|
|
2
|
+
|
|
3
|
+
One checkpoint, three modes — the recipe picks the default:
|
|
4
|
+
diffusion -> model.generate(ids, block_length, threshold, eos_token_id)
|
|
5
|
+
ar -> model.ar_generate(ids)
|
|
6
|
+
linear_spec -> model.linear_spec_generate(ids, block_length, eos_token_id)
|
|
7
|
+
Each returns (out_ids, nfe). Requires transformers>=5.0.0 (recipe pin) and
|
|
8
|
+
therefore runs in the isolated nemotron worker env (PLAN.md section 3).
|
|
9
|
+
|
|
10
|
+
UNVERIFIED in ollada until a real-load gate passes on a T4.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
import torch
|
|
18
|
+
from transformers import AutoModel, AutoTokenizer
|
|
19
|
+
|
|
20
|
+
from ollada.adapters.base import BaseAdapter, quantization_config
|
|
21
|
+
|
|
22
|
+
_DTYPES = {"float16": torch.float16, "bfloat16": torch.bfloat16}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NemotronAdapter(BaseAdapter):
|
|
26
|
+
family = "nemotron"
|
|
27
|
+
|
|
28
|
+
def load(self) -> None:
|
|
29
|
+
recipe = self.recipe
|
|
30
|
+
self.tokenizer = AutoTokenizer.from_pretrained(recipe.source, trust_remote_code=True)
|
|
31
|
+
kwargs = {"trust_remote_code": True}
|
|
32
|
+
qc = quantization_config(recipe.quant_default)
|
|
33
|
+
if qc is not None:
|
|
34
|
+
kwargs["quantization_config"] = qc
|
|
35
|
+
kwargs["device_map"] = "auto"
|
|
36
|
+
model = AutoModel.from_pretrained(recipe.source, **kwargs)
|
|
37
|
+
if qc is None:
|
|
38
|
+
dtype = _DTYPES.get(recipe.dtype, torch.bfloat16)
|
|
39
|
+
model = model.to(dtype)
|
|
40
|
+
if torch.cuda.is_available():
|
|
41
|
+
model = model.cuda()
|
|
42
|
+
self.model = model.eval()
|
|
43
|
+
|
|
44
|
+
@torch.no_grad()
|
|
45
|
+
def chat(self, messages, overrides=None):
|
|
46
|
+
self._require_loaded()
|
|
47
|
+
params = self._params(overrides)
|
|
48
|
+
input_ids = self._prompt_ids(messages).to(self.model.device)
|
|
49
|
+
mode = str(params.get("mode", "diffusion"))
|
|
50
|
+
max_new = int(params.get("max_new_tokens", 512))
|
|
51
|
+
eos = self.tokenizer.eos_token_id
|
|
52
|
+
started = time.time()
|
|
53
|
+
if mode == "ar":
|
|
54
|
+
out_ids, nfe = self.model.ar_generate(input_ids, max_new_tokens=max_new)
|
|
55
|
+
elif mode == "linear_spec":
|
|
56
|
+
out_ids, nfe = self.model.linear_spec_generate(
|
|
57
|
+
input_ids,
|
|
58
|
+
max_new_tokens=max_new,
|
|
59
|
+
block_length=int(params.get("block_length", 32)),
|
|
60
|
+
eos_token_id=eos,
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
out_ids, nfe = self.model.generate(
|
|
64
|
+
input_ids,
|
|
65
|
+
max_new_tokens=max_new,
|
|
66
|
+
block_length=int(params.get("block_length", 32)),
|
|
67
|
+
threshold=float(params.get("threshold", 0.9)),
|
|
68
|
+
eos_token_id=eos,
|
|
69
|
+
)
|
|
70
|
+
return self._finish(out_ids, input_ids.shape[1], started, nfe=int(nfe))
|