@tuned-tensor/local 0.2.9 → 0.3.0
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.
- package/CHANGELOG.md +59 -0
- package/README.md +84 -210
- package/dist/artifacts.d.ts +3 -4
- package/dist/artifacts.js +55 -33
- package/dist/artifacts.js.map +1 -1
- package/dist/compare.d.ts +1 -8
- package/dist/compare.js +1 -23
- package/dist/compare.js.map +1 -1
- package/dist/contracts.d.ts +59 -366
- package/dist/contracts.js +73 -224
- package/dist/contracts.js.map +1 -1
- package/dist/dataset.d.ts +2 -4
- package/dist/dataset.js +31 -125
- package/dist/dataset.js.map +1 -1
- package/dist/doctor.d.ts +2 -3
- package/dist/doctor.js +23 -161
- package/dist/doctor.js.map +1 -1
- package/dist/evaluation.d.ts +11 -71
- package/dist/evaluation.js +212 -572
- package/dist/evaluation.js.map +1 -1
- package/dist/huggingface-cache.d.ts +8 -7
- package/dist/huggingface-cache.js +16 -8
- package/dist/huggingface-cache.js.map +1 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.js +203 -626
- package/dist/index.js.map +1 -1
- package/dist/local-project.d.ts +1 -11
- package/dist/local-project.js +33 -61
- package/dist/local-project.js.map +1 -1
- package/dist/model-registry.d.ts +3 -16
- package/dist/model-registry.js +76 -292
- package/dist/model-registry.js.map +1 -1
- package/dist/model-server.d.ts +1 -2
- package/dist/model-server.js +9 -18
- package/dist/model-server.js.map +1 -1
- package/dist/orchestrator.d.ts +11 -25
- package/dist/orchestrator.js +246 -566
- package/dist/orchestrator.js.map +1 -1
- package/dist/prefetch.d.ts +1 -5
- package/dist/prefetch.js +14 -19
- package/dist/prefetch.js.map +1 -1
- package/dist/process-runner.d.ts +10 -29
- package/dist/process-runner.js +63 -169
- package/dist/process-runner.js.map +1 -1
- package/dist/process-training.d.ts +0 -1
- package/dist/process-training.js +10 -87
- package/dist/process-training.js.map +1 -1
- package/dist/store.d.ts +1 -3
- package/dist/store.js +92 -290
- package/dist/store.js.map +1 -1
- package/docs/architecture.md +87 -152
- package/docs/spark.md +66 -97
- package/examples/dry-runner.json +14 -0
- package/examples/local-runner.json +8 -6
- package/examples/smoke-spec.json +41 -0
- package/package.json +6 -6
- package/training/local-runner/pyproject.toml +0 -5
- package/training/local-runner/src/evaluate.py +89 -234
- package/training/local-runner/src/model_contract.py +47 -0
- package/training/local-runner/src/prefetch.py +18 -4
- package/training/local-runner/src/serve.py +54 -115
- package/training/local-runner/src/sft_data.py +97 -0
- package/training/local-runner/src/train.py +146 -346
- package/training/local-runner/uv.lock +0 -1197
- package/dist/labeling-sanitize.d.ts +0 -31
- package/dist/labeling-sanitize.js +0 -158
- package/dist/labeling-sanitize.js.map +0 -1
- package/dist/labeling.d.ts +0 -155
- package/dist/labeling.js +0 -496
- package/dist/labeling.js.map +0 -1
- package/dist/openrouter.d.ts +0 -29
- package/dist/openrouter.js +0 -66
- package/dist/openrouter.js.map +0 -1
- package/dist/server.d.ts +0 -9
- package/dist/server.js +0 -211
- package/dist/server.js.map +0 -1
- package/docs/local-workflow-remediation-2026-07-13.md +0 -130
- package/docs/local-workflow-ux-review-2026-07-13.md +0 -458
- package/examples/dpo-preferences.jsonl +0 -2
- package/examples/dpo-run-request.json +0 -34
- package/examples/smoke-run-request.json +0 -34
- package/training/local-runner/src/train_dpo.py +0 -286
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
import base64
|
|
5
3
|
import hmac
|
|
4
|
+
import json
|
|
6
5
|
import math
|
|
7
6
|
import os
|
|
8
7
|
import sys
|
|
@@ -14,14 +13,9 @@ from pathlib import Path
|
|
|
14
13
|
from tempfile import TemporaryDirectory
|
|
15
14
|
from typing import Any
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
# the model stack only after reading process configuration keeps every local
|
|
19
|
-
# workflow on the same Hugging Face cache contract.
|
|
20
|
-
from evaluate import ( # noqa: E402
|
|
16
|
+
from evaluate import (
|
|
21
17
|
configure_hugging_face_cache,
|
|
22
|
-
generate_multimodal_one,
|
|
23
18
|
import_runtime_dependencies,
|
|
24
|
-
load_multimodal_model,
|
|
25
19
|
load_text_model,
|
|
26
20
|
resolve_adapter_path,
|
|
27
21
|
sampling_kwargs,
|
|
@@ -36,21 +30,20 @@ MODEL_LOADER = os.environ.get("TT_MODEL_LOADER", "causal_lm")
|
|
|
36
30
|
SYSTEM_PROMPT = os.environ.get("TT_SYSTEM_PROMPT", "").strip()
|
|
37
31
|
HOST = os.environ.get("TT_HOST", "127.0.0.1")
|
|
38
32
|
PORT = int(os.environ.get("TT_PORT", "8000"))
|
|
39
|
-
DEVICE_REQUEST = os.environ.get("TT_DEVICE", "
|
|
40
|
-
TRUST_REMOTE_CODE = os.environ.get("TT_TRUST_REMOTE_CODE", "true").lower() in {"1", "true", "yes"}
|
|
33
|
+
DEVICE_REQUEST = os.environ.get("TT_DEVICE", "cuda")
|
|
41
34
|
DEFAULT_MAX_TOKENS = int(os.environ.get("TT_MAX_TOKENS", "512"))
|
|
42
35
|
DEFAULT_TEMPERATURE = float(os.environ.get("TT_TEMPERATURE", "0"))
|
|
43
36
|
DEFAULT_TOP_P = float(os.environ.get("TT_TOP_P", "1"))
|
|
44
|
-
CHAT_TEMPLATE_KWARGS = json.loads(os.environ.get("TT_CHAT_TEMPLATE_KWARGS", "{}"))
|
|
45
37
|
MAX_REQUEST_BYTES = 2 * 1024 * 1024
|
|
46
38
|
MAX_PROMPT_CHARS = 100_000
|
|
47
39
|
MAX_PROMPT_TOKENS = 16_384
|
|
48
40
|
MAX_MESSAGES = 128
|
|
49
|
-
MAX_IMAGE_BYTES = 5 * 1024 * 1024
|
|
50
41
|
API_KEY = os.environ.get("TT_API_KEY", "")
|
|
51
42
|
MAX_CONCURRENT_REQUESTS = int(os.environ.get("TT_MAX_CONCURRENT_REQUESTS", "1"))
|
|
52
43
|
|
|
53
44
|
|
|
45
|
+
if MODEL_LOADER != "causal_lm":
|
|
46
|
+
raise ValueError("The bundled model server is text-only and requires TT_MODEL_LOADER=causal_lm")
|
|
54
47
|
configure_hugging_face_cache(os.environ.get("HF_HOME"))
|
|
55
48
|
import_runtime_dependencies()
|
|
56
49
|
TEMP_DIR = TemporaryDirectory(prefix="tt-local-serve-")
|
|
@@ -59,94 +52,73 @@ MODEL_PAYLOAD = {
|
|
|
59
52
|
"base_model": BASE_MODEL,
|
|
60
53
|
"base_model_revision": BASE_MODEL_REVISION,
|
|
61
54
|
"device": DEVICE_REQUEST,
|
|
62
|
-
"trust_remote_code": TRUST_REMOTE_CODE,
|
|
63
55
|
"model_loader": MODEL_LOADER,
|
|
64
56
|
}
|
|
65
|
-
|
|
66
|
-
if MODEL_LOADER == "image_text_to_text":
|
|
67
|
-
MODEL, PROCESSOR, DEVICE = load_multimodal_model(MODEL_PAYLOAD, ADAPTER_PATH)
|
|
68
|
-
TOKENIZER = getattr(PROCESSOR, "tokenizer", PROCESSOR)
|
|
69
|
-
else:
|
|
70
|
-
MODEL, TOKENIZER, DEVICE = load_text_model(MODEL_PAYLOAD, ADAPTER_PATH)
|
|
71
|
-
PROCESSOR = None
|
|
72
|
-
|
|
57
|
+
MODEL, TOKENIZER, DEVICE = load_text_model(MODEL_PAYLOAD, ADAPTER_PATH)
|
|
73
58
|
GENERATION_LOCK = threading.Lock()
|
|
74
59
|
REQUEST_SLOTS = threading.BoundedSemaphore(MAX_CONCURRENT_REQUESTS)
|
|
75
60
|
|
|
76
61
|
|
|
77
|
-
def normalize_messages(raw_messages: Any) -> list[dict[str, Any]]:
|
|
78
|
-
if not isinstance(raw_messages, list) or not raw_messages:
|
|
79
|
-
raise ValueError("Request must include a non-empty messages array.")
|
|
80
|
-
if len(raw_messages) > MAX_MESSAGES:
|
|
81
|
-
raise ValueError(f"Request exceeds the {MAX_MESSAGES}-message limit.")
|
|
82
|
-
messages: list[dict[str, Any]] = []
|
|
83
|
-
for raw in raw_messages:
|
|
84
|
-
if not isinstance(raw, dict) or raw.get("role") not in {"system", "user", "assistant", "tool"}:
|
|
85
|
-
raise ValueError("Each message must have a supported role and content.")
|
|
86
|
-
messages.append({"role": raw["role"], "content": raw.get("content", "")})
|
|
87
|
-
# A spec passed by the server owner is invariant behavior. Client-supplied
|
|
88
|
-
# system messages may add context but cannot replace it.
|
|
89
|
-
if SYSTEM_PROMPT:
|
|
90
|
-
messages.insert(0, {"role": "system", "content": SYSTEM_PROMPT})
|
|
91
|
-
if sum(len(text_content(message.get("content"))) for message in messages) > MAX_PROMPT_CHARS:
|
|
92
|
-
raise ValueError(f"Prompt exceeds the {MAX_PROMPT_CHARS}-character limit.")
|
|
93
|
-
return messages
|
|
94
|
-
|
|
95
|
-
|
|
96
62
|
def text_content(content: Any) -> str:
|
|
97
63
|
if isinstance(content, str):
|
|
98
64
|
return content
|
|
65
|
+
if content is None:
|
|
66
|
+
return ""
|
|
99
67
|
if not isinstance(content, list):
|
|
100
|
-
|
|
68
|
+
raise ValueError("Message content must be text or an array of text parts.")
|
|
101
69
|
parts: list[str] = []
|
|
102
70
|
for part in content:
|
|
103
|
-
if isinstance(part, dict)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return "\n".join(
|
|
71
|
+
if not isinstance(part, dict) or part.get("type") != "text":
|
|
72
|
+
raise ValueError("The bundled Qwen model server accepts text content only.")
|
|
73
|
+
text = part.get("text")
|
|
74
|
+
if not isinstance(text, str):
|
|
75
|
+
raise ValueError("Every text content part must contain a string text field.")
|
|
76
|
+
parts.append(text)
|
|
77
|
+
return "\n".join(parts)
|
|
110
78
|
|
|
111
79
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
80
|
+
def normalize_messages(raw_messages: Any) -> list[dict[str, str]]:
|
|
81
|
+
if not isinstance(raw_messages, list) or not raw_messages:
|
|
82
|
+
raise ValueError("Request must include a non-empty messages array.")
|
|
83
|
+
if len(raw_messages) > MAX_MESSAGES:
|
|
84
|
+
raise ValueError(f"Request exceeds the {MAX_MESSAGES}-message limit.")
|
|
85
|
+
|
|
86
|
+
system_parts = [SYSTEM_PROMPT] if SYSTEM_PROMPT else []
|
|
87
|
+
conversation: list[dict[str, str]] = []
|
|
88
|
+
for raw in raw_messages:
|
|
89
|
+
if not isinstance(raw, dict) or raw.get("role") not in {"system", "user", "assistant", "tool"}:
|
|
90
|
+
raise ValueError("Each message must have a supported role and text content.")
|
|
91
|
+
role = str(raw["role"])
|
|
92
|
+
content = text_content(raw.get("content", ""))
|
|
93
|
+
if role == "system":
|
|
94
|
+
if content.strip():
|
|
95
|
+
system_parts.append(content.strip())
|
|
96
|
+
else:
|
|
97
|
+
conversation.append({"role": role, "content": content})
|
|
98
|
+
|
|
99
|
+
if not conversation:
|
|
100
|
+
raise ValueError("Request must contain at least one non-system message.")
|
|
101
|
+
messages: list[dict[str, str]] = []
|
|
102
|
+
if system_parts:
|
|
103
|
+
# Qwen permits one leading system message. Merge the invariant owner
|
|
104
|
+
# prompt and any client context instead of triggering an implicit
|
|
105
|
+
# template fallback with duplicate system turns.
|
|
106
|
+
messages.append({"role": "system", "content": "\n\n".join(system_parts)})
|
|
107
|
+
messages.extend(conversation)
|
|
108
|
+
if sum(len(message["content"]) for message in messages) > MAX_PROMPT_CHARS:
|
|
109
|
+
raise ValueError(f"Prompt exceeds the {MAX_PROMPT_CHARS}-character limit.")
|
|
110
|
+
return messages
|
|
137
111
|
|
|
138
112
|
|
|
139
|
-
def generate_text(messages: list[dict[str,
|
|
140
|
-
normalized = [{"role": message["role"], "content": text_content(message.get("content"))} for message in messages]
|
|
113
|
+
def generate_text(messages: list[dict[str, str]], generation: dict[str, Any]) -> tuple[str, int, int]:
|
|
141
114
|
try:
|
|
142
115
|
prompt = TOKENIZER.apply_chat_template(
|
|
143
|
-
|
|
116
|
+
messages,
|
|
144
117
|
tokenize=False,
|
|
145
118
|
add_generation_prompt=True,
|
|
146
|
-
**CHAT_TEMPLATE_KWARGS,
|
|
147
119
|
)
|
|
148
|
-
except Exception:
|
|
149
|
-
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
raise ValueError(f"Qwen chat-template rendering failed: {exc}") from exc
|
|
150
122
|
inputs = TOKENIZER(prompt, return_tensors="pt")
|
|
151
123
|
if int(inputs["input_ids"].shape[-1]) > MAX_PROMPT_TOKENS:
|
|
152
124
|
raise ValueError(f"Prompt exceeds the {MAX_PROMPT_TOKENS}-token limit.")
|
|
@@ -155,7 +127,7 @@ def generate_text(messages: list[dict[str, Any]], generation: dict[str, Any]) ->
|
|
|
155
127
|
with GENERATION_LOCK:
|
|
156
128
|
import torch
|
|
157
129
|
|
|
158
|
-
with torch.
|
|
130
|
+
with torch.inference_mode():
|
|
159
131
|
generated = MODEL.generate(
|
|
160
132
|
**inputs,
|
|
161
133
|
max_new_tokens=int(generation["max_new_tokens"]),
|
|
@@ -168,33 +140,6 @@ def generate_text(messages: list[dict[str, Any]], generation: dict[str, Any]) ->
|
|
|
168
140
|
return content, prompt_tokens, int(completion_tokens.shape[-1])
|
|
169
141
|
|
|
170
142
|
|
|
171
|
-
def generate_multimodal(messages: list[dict[str, Any]], generation: dict[str, Any]) -> tuple[str, int, int]:
|
|
172
|
-
system_parts = [text_content(message.get("content")) for message in messages if message["role"] == "system"]
|
|
173
|
-
conversation = "\n".join(
|
|
174
|
-
f"{message['role']}: {text_content(message.get('content'))}"
|
|
175
|
-
for message in messages
|
|
176
|
-
if message["role"] != "system"
|
|
177
|
-
)
|
|
178
|
-
example = {
|
|
179
|
-
"input": conversation,
|
|
180
|
-
"output": "",
|
|
181
|
-
"input_assets": image_assets(messages),
|
|
182
|
-
}
|
|
183
|
-
with GENERATION_LOCK:
|
|
184
|
-
result = generate_multimodal_one(
|
|
185
|
-
MODEL,
|
|
186
|
-
PROCESSOR,
|
|
187
|
-
"\n\n".join(part for part in system_parts if part),
|
|
188
|
-
example,
|
|
189
|
-
generation,
|
|
190
|
-
Path.cwd(),
|
|
191
|
-
CHAT_TEMPLATE_KWARGS,
|
|
192
|
-
)
|
|
193
|
-
# The evaluator does not currently expose token counts for multimodal
|
|
194
|
-
# requests. Returning zero is preferable to inventing usage data.
|
|
195
|
-
return str(result["actual"]), 0, 0
|
|
196
|
-
|
|
197
|
-
|
|
198
143
|
def bounded_number(value: Any, default: float, minimum: float, maximum: float) -> float:
|
|
199
144
|
number = float(default if value is None else value)
|
|
200
145
|
if not math.isfinite(number) or number < minimum or number > maximum:
|
|
@@ -220,8 +165,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
220
165
|
if not API_KEY:
|
|
221
166
|
return True
|
|
222
167
|
supplied = self.headers.get("authorization", "")
|
|
223
|
-
|
|
224
|
-
return hmac.compare_digest(supplied, expected)
|
|
168
|
+
return hmac.compare_digest(supplied, "Bearer " + API_KEY)
|
|
225
169
|
|
|
226
170
|
def send_json(self, status: int, payload: Any) -> None:
|
|
227
171
|
body = json.dumps(payload).encode("utf-8")
|
|
@@ -274,12 +218,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
274
218
|
return
|
|
275
219
|
started = time.perf_counter()
|
|
276
220
|
try:
|
|
277
|
-
|
|
278
|
-
content, prompt_tokens, completion_tokens = generate_multimodal(messages, generation)
|
|
279
|
-
else:
|
|
280
|
-
if image_assets(messages):
|
|
281
|
-
raise ValueError("This text model does not accept image content parts.")
|
|
282
|
-
content, prompt_tokens, completion_tokens = generate_text(messages, generation)
|
|
221
|
+
content, prompt_tokens, completion_tokens = generate_text(messages, generation)
|
|
283
222
|
finally:
|
|
284
223
|
REQUEST_SLOTS.release()
|
|
285
224
|
latency_ms = round((time.perf_counter() - started) * 1000)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
IGNORE_INDEX = -100
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _token_ids(value: Any, description: str) -> list[int]:
|
|
11
|
+
if isinstance(value, Mapping):
|
|
12
|
+
value = value.get("input_ids")
|
|
13
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
14
|
+
raise TypeError(f"{description} did not return a token-id sequence")
|
|
15
|
+
token_ids = list(value)
|
|
16
|
+
if not token_ids or any(not isinstance(token_id, int) for token_id in token_ids):
|
|
17
|
+
raise ValueError(f"{description} returned empty or invalid token ids")
|
|
18
|
+
return token_ids
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_assistant_only_example(
|
|
22
|
+
tokenizer: Any,
|
|
23
|
+
messages: Any,
|
|
24
|
+
*,
|
|
25
|
+
max_length: int,
|
|
26
|
+
) -> dict[str, list[int]]:
|
|
27
|
+
"""Tokenize one chat row while computing loss only on the final assistant turn.
|
|
28
|
+
|
|
29
|
+
The entire assistant completion is reserved before the prompt is truncated.
|
|
30
|
+
Prompt truncation therefore removes the oldest prompt tokens and can never
|
|
31
|
+
silently discard the expected answer.
|
|
32
|
+
"""
|
|
33
|
+
if max_length < 2:
|
|
34
|
+
raise ValueError("max_length must be at least 2")
|
|
35
|
+
if not isinstance(messages, list) or len(messages) < 2:
|
|
36
|
+
raise ValueError("messages must contain prompt context and a final assistant turn")
|
|
37
|
+
if any(not isinstance(message, dict) for message in messages):
|
|
38
|
+
raise ValueError("every message must be an object")
|
|
39
|
+
allowed_roles = {"system", "user", "assistant", "tool"}
|
|
40
|
+
for message in messages:
|
|
41
|
+
role = message.get("role")
|
|
42
|
+
if role not in allowed_roles:
|
|
43
|
+
raise ValueError(f"unsupported message role: {role!r}")
|
|
44
|
+
if not isinstance(message.get("content"), str):
|
|
45
|
+
raise ValueError("the bundled trainer accepts text-only message content")
|
|
46
|
+
|
|
47
|
+
assistant = messages[-1]
|
|
48
|
+
if assistant.get("role") != "assistant":
|
|
49
|
+
raise ValueError("the final message must have role=assistant")
|
|
50
|
+
content = assistant.get("content")
|
|
51
|
+
if not isinstance(content, str) or not content.strip():
|
|
52
|
+
raise ValueError("the final assistant message must contain non-empty text")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
prompt_ids = _token_ids(
|
|
56
|
+
tokenizer.apply_chat_template(
|
|
57
|
+
messages[:-1],
|
|
58
|
+
tokenize=True,
|
|
59
|
+
add_generation_prompt=True,
|
|
60
|
+
return_dict=False,
|
|
61
|
+
),
|
|
62
|
+
"prompt chat template",
|
|
63
|
+
)
|
|
64
|
+
full_ids = _token_ids(
|
|
65
|
+
tokenizer.apply_chat_template(
|
|
66
|
+
messages,
|
|
67
|
+
tokenize=True,
|
|
68
|
+
add_generation_prompt=False,
|
|
69
|
+
return_dict=False,
|
|
70
|
+
),
|
|
71
|
+
"full chat template",
|
|
72
|
+
)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
raise ValueError(f"Qwen chat-template rendering failed: {exc}") from exc
|
|
75
|
+
|
|
76
|
+
if full_ids[: len(prompt_ids)] != prompt_ids:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
"Qwen chat-template output is not prefix-aligned; refusing to guess the assistant loss boundary"
|
|
79
|
+
)
|
|
80
|
+
completion_ids = full_ids[len(prompt_ids) :]
|
|
81
|
+
if not completion_ids:
|
|
82
|
+
raise ValueError("Qwen chat template produced no assistant completion tokens")
|
|
83
|
+
if len(completion_ids) >= max_length:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"assistant completion requires {len(completion_ids)} tokens, but max_length={max_length}; "
|
|
86
|
+
"increase max_seq_length so the answer is not truncated"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
prompt_budget = max_length - len(completion_ids)
|
|
90
|
+
retained_prompt_ids = prompt_ids[-prompt_budget:]
|
|
91
|
+
input_ids = [*retained_prompt_ids, *completion_ids]
|
|
92
|
+
labels = [IGNORE_INDEX] * len(retained_prompt_ids) + completion_ids.copy()
|
|
93
|
+
return {
|
|
94
|
+
"input_ids": input_ids,
|
|
95
|
+
"attention_mask": [1] * len(input_ids),
|
|
96
|
+
"labels": labels,
|
|
97
|
+
}
|