@lzhzzzzwill/cofos 1.3.4 → 1.3.8
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/README.md +19 -1
- package/bin/cofos.js +139 -9
- package/package.json +1 -1
- package/runtime/src/inference/student_adapter_inference.py +57 -17
package/README.md
CHANGED
|
@@ -71,6 +71,20 @@ Disable RAG for a session:
|
|
|
71
71
|
npx @lzhzzzzwill/cofos --no-rag
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
Show model reasoning scaffold when emitted:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx @lzhzzzzwill/cofos --show-reasoning
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Select the inference device explicitly:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @lzhzzzzwill/cofos --device cuda # CUDA GPU machine
|
|
84
|
+
npx @lzhzzzzwill/cofos --device mps # Apple Silicon Mac
|
|
85
|
+
npx @lzhzzzzwill/cofos --device cpu # CPU fallback, slow
|
|
86
|
+
```
|
|
87
|
+
|
|
74
88
|
Ask once and exit, useful for scripts and smoke tests:
|
|
75
89
|
|
|
76
90
|
```bash
|
|
@@ -89,7 +103,8 @@ On startup, the CLI does the following:
|
|
|
89
103
|
5. If `--pdf-dir` is provided, scans PDF names, sizes, and mtimes.
|
|
90
104
|
6. Re-parses PDFs only when the folder contents changed or `--rebuild-pdf-index` is passed.
|
|
91
105
|
7. Merges ROS-related PDF chunks into BM25 retrieval.
|
|
92
|
-
8.
|
|
106
|
+
8. Selects the inference backend: CUDA first, then Apple Silicon MPS, then CPU.
|
|
107
|
+
9. Loads the model once and keeps the Python backend alive for the whole chat session.
|
|
93
108
|
|
|
94
109
|
PDF files are read locally and are not uploaded anywhere. Set `COFOS_CACHE_DIR` to override the COFOS cache root, or `HF_HOME` to override only the Hugging Face model cache.
|
|
95
110
|
|
|
@@ -127,7 +142,10 @@ cofos \
|
|
|
127
142
|
| `--cache-dir` | Folder for model, KG/BM25, and parsed PDF cache; defaults to `./.cofos` |
|
|
128
143
|
| `--rebuild-pdf-index` | Force PDF parsing and BM25 rebuild |
|
|
129
144
|
| `--top-k` | Retrieval top-k |
|
|
145
|
+
| `--device` | `auto`, `cuda`, `mps`, or `cpu`; defaults to `auto` |
|
|
146
|
+
| `--dtype` | `auto`, `float32`, `float16`, or `bfloat16`; defaults to `auto` |
|
|
130
147
|
| `-p`, `--prompt` | Ask one question and exit |
|
|
148
|
+
| `--show-reasoning` | Show emitted reasoning scaffold in dim text before the final answer |
|
|
131
149
|
| `--max-new-tokens` | Override generation length |
|
|
132
150
|
| `--no-rag` | Run without KG/BM25 retrieval |
|
|
133
151
|
|
package/bin/cofos.js
CHANGED
|
@@ -216,7 +216,7 @@ function createPlainUi() {
|
|
|
216
216
|
async function main() {
|
|
217
217
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
218
218
|
console.log("\n COFOS CLI — 9B + RAG chat (Willlzh/COFOS)\n");
|
|
219
|
-
console.log(" Usage: cofos [-p QUESTION] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
|
|
219
|
+
console.log(" Usage: cofos [-p QUESTION] [--show-reasoning] [--device DEVICE] [--dtype DTYPE] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
|
|
220
220
|
console.log(" Commands in chat:");
|
|
221
221
|
console.log(" /help Show this help");
|
|
222
222
|
console.log(" /exit Quit");
|
|
@@ -229,6 +229,9 @@ async function main() {
|
|
|
229
229
|
console.log(" --pdf-dir DIR Add PDFs from DIR as extra BM25 evidence");
|
|
230
230
|
console.log(" --cache-dir DIR Store model/data cache here (default: ./.cofos)");
|
|
231
231
|
console.log(" --max-new-tokens N Override generation length");
|
|
232
|
+
console.log(" --device DEVICE auto|cuda|mps|cpu (default: auto; CUDA first, then Mac MPS)");
|
|
233
|
+
console.log(" --dtype DTYPE auto|float32|float16|bfloat16 (default: auto)");
|
|
234
|
+
console.log(" --show-reasoning Show model reasoning scaffold in dim text when emitted");
|
|
232
235
|
console.log(" -p, --prompt TEXT Ask once and exit");
|
|
233
236
|
console.log(" --rebuild-pdf-index Force reparsing PDFs and rebuilding BM25\n");
|
|
234
237
|
console.log(" Multi-line: end a line with \\ to continue on the next line.\n");
|
|
@@ -245,6 +248,9 @@ async function main() {
|
|
|
245
248
|
let maxNewTokens = null;
|
|
246
249
|
let cacheDir = process.env.COFOS_CACHE_DIR ? resolve(process.env.COFOS_CACHE_DIR) : resolve(process.cwd(), ".cofos");
|
|
247
250
|
let rebuildPdfIndex = false;
|
|
251
|
+
let showReasoning = false;
|
|
252
|
+
let devicePreference = process.env.COFOS_DEVICE || "auto";
|
|
253
|
+
let dtypePreference = process.env.COFOS_DTYPE || "auto";
|
|
248
254
|
let singlePrompt = null;
|
|
249
255
|
const backendArgs = [chatScript];
|
|
250
256
|
|
|
@@ -276,6 +282,14 @@ async function main() {
|
|
|
276
282
|
cacheDir = resolve(value(++i, arg));
|
|
277
283
|
} else if (arg === "--rebuild-pdf-index") {
|
|
278
284
|
rebuildPdfIndex = true;
|
|
285
|
+
} else if (arg === "--show-reasoning") {
|
|
286
|
+
showReasoning = true;
|
|
287
|
+
} else if (arg === "--device") {
|
|
288
|
+
devicePreference = value(++i, arg).toLowerCase();
|
|
289
|
+
if (!["auto", "cuda", "mps", "cpu", "gpu", "metal"].includes(devicePreference)) die(`--device must be one of auto, cuda, mps, cpu; got "${devicePreference}"`);
|
|
290
|
+
} else if (arg === "--dtype") {
|
|
291
|
+
dtypePreference = value(++i, arg).toLowerCase();
|
|
292
|
+
if (!["auto", "fp32", "float32", "fp16", "float16", "bf16", "bfloat16"].includes(dtypePreference)) die(`--dtype must be one of auto, float32, float16, bfloat16; got "${dtypePreference}"`);
|
|
279
293
|
} else if (arg === "-p" || arg === "--prompt") {
|
|
280
294
|
singlePrompt = value(++i, arg);
|
|
281
295
|
} else if (arg === "--no-rag") {
|
|
@@ -311,6 +325,11 @@ async function main() {
|
|
|
311
325
|
HF_HUB_ETAG_TIMEOUT: process.env.HF_HUB_ETAG_TIMEOUT || "30",
|
|
312
326
|
HF_HUB_VERBOSITY: process.env.HF_HUB_VERBOSITY || "error",
|
|
313
327
|
TRANSFORMERS_VERBOSITY: process.env.TRANSFORMERS_VERBOSITY || "error",
|
|
328
|
+
PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || "1",
|
|
329
|
+
PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8",
|
|
330
|
+
COFOS_SHOW_REASONING: showReasoning ? "1" : (process.env.COFOS_SHOW_REASONING || "0"),
|
|
331
|
+
COFOS_DEVICE: devicePreference,
|
|
332
|
+
COFOS_DTYPE: dtypePreference,
|
|
314
333
|
};
|
|
315
334
|
const proc = spawn(py, backendArgs, {
|
|
316
335
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -320,7 +339,7 @@ async function main() {
|
|
|
320
339
|
proc.stdout.setEncoding("utf-8");
|
|
321
340
|
proc.stderr.setEncoding("utf-8");
|
|
322
341
|
|
|
323
|
-
let buf = "", ready = false, generating = false, responseOpen = false, commandOpen = false, closed = false;
|
|
342
|
+
let buf = "", ready = false, generating = false, responseOpen = false, commandOpen = false, closed = false, backendExited = false;
|
|
324
343
|
let currentAnswer = "", assistantStart = -1, plainAssistantMode = "undecided", plainAssistantPending = "";
|
|
325
344
|
let plainReasonBuffer = "", plainReasonPrinted = 0;
|
|
326
345
|
const inputQ = [];
|
|
@@ -346,6 +365,37 @@ async function main() {
|
|
|
346
365
|
}
|
|
347
366
|
}
|
|
348
367
|
|
|
368
|
+
function handleLocalOnly(rawInput) {
|
|
369
|
+
const input = String(rawInput || "").trim();
|
|
370
|
+
if (!input) return true;
|
|
371
|
+
if (backendExited && !/^\/(exit|quit)$/.test(input)) {
|
|
372
|
+
ui.print("• COFOS backend is not running. Restart `cofos` after fixing the error above, or run /exit.");
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
if (input === "/") {
|
|
376
|
+
ui.print("• Type a command prefix such as /r or /to, then press Tab. Run /help for all commands.");
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
380
|
+
if (/^\/(help)$/.test(input)) {
|
|
381
|
+
ui.print(slashHint("/") + "\n Add local papers to RAG:\n cofos --pdf-dir ./new_pdfs\n cofos --pdf-dir ./new_pdfs --rebuild-pdf-index\n\n Multi-line: end a line with \\ to continue.");
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
if (/^\/(cls)$/.test(input)) {
|
|
385
|
+
ui.clear();
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
if (/^\/(info)$/.test(input)) {
|
|
389
|
+
ui.print(`\n COFOS CLI — v${pkg.version}\n Model: ${modelPath} (Hugging Face)\n RAG: Willlzh/COFOS_data/runtime (KG + BM25) + optional --pdf-dir BM25 evidence\n Cache: ${cacheDir}\n HF: ${backendEnv.HF_HOME}\n Engine: StudentAdapterInference + transformers + PyTorch\n Device: ${devicePreference}\n Dtype: ${dtypePreference}\n Status: ${ready ? "ready" : "loading"}\n Hist: ~/.cofos_history.jsonl\n`);
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
if (input.startsWith("/") && !/^\/(clear|save|topk)(?:\s|$)/.test(input) && !/^\/rag\s+(?:on|off)$/.test(input)) {
|
|
393
|
+
ui.print(`{red-fg}•{/red-fg} Unknown command: ${input}.\n${slashPrefixHint(input)}`);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
349
399
|
function handle(rawInput) {
|
|
350
400
|
let input = String(rawInput || "");
|
|
351
401
|
if (input.endsWith("\\")) {
|
|
@@ -363,8 +413,12 @@ async function main() {
|
|
|
363
413
|
input = input.trim();
|
|
364
414
|
if (!input) return false;
|
|
365
415
|
|
|
416
|
+
if (backendExited && !/^\/(exit|quit)$/.test(input)) {
|
|
417
|
+
ui.print("• COFOS backend is not running. Restart `cofos` after fixing the error above, or run /exit.");
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
366
420
|
if (input === "/") {
|
|
367
|
-
ui.print("
|
|
421
|
+
ui.print("• Type a command prefix such as /r or /to, then press Tab. Run /help for all commands.");
|
|
368
422
|
return false;
|
|
369
423
|
}
|
|
370
424
|
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
@@ -405,7 +459,7 @@ async function main() {
|
|
|
405
459
|
return true;
|
|
406
460
|
}
|
|
407
461
|
if (/^\/(info)$/.test(input)) {
|
|
408
|
-
ui.print(`\n COFOS CLI — v${pkg.version}\n Model: ${modelPath} (Hugging Face)\n RAG: Willlzh/COFOS_data/runtime (KG + BM25) + optional --pdf-dir BM25 evidence\n Cache: ${cacheDir}\n HF: ${backendEnv.HF_HOME}\n Engine: StudentAdapterInference + transformers + PyTorch\n Hist: ~/.cofos_history.jsonl\n`);
|
|
462
|
+
ui.print(`\n COFOS CLI — v${pkg.version}\n Model: ${modelPath} (Hugging Face)\n RAG: Willlzh/COFOS_data/runtime (KG + BM25) + optional --pdf-dir BM25 evidence\n Cache: ${cacheDir}\n HF: ${backendEnv.HF_HOME}\n Engine: StudentAdapterInference + transformers + PyTorch\n Device: ${devicePreference}\n Dtype: ${dtypePreference}\n Hist: ~/.cofos_history.jsonl\n`);
|
|
409
463
|
return false;
|
|
410
464
|
}
|
|
411
465
|
if (input.startsWith("/")) {
|
|
@@ -427,8 +481,19 @@ async function main() {
|
|
|
427
481
|
}
|
|
428
482
|
|
|
429
483
|
ui.setSubmitHandler((line) => {
|
|
484
|
+
if (handleLocalOnly(line)) {
|
|
485
|
+
if (!closed) ui.focus();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (backendExited) {
|
|
489
|
+
ui.print("• COFOS backend is not running. Restart `cofos` after fixing the error above, or run /exit.");
|
|
490
|
+
if (!closed) ui.focus();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
430
493
|
if (!ready || generating) {
|
|
431
494
|
inputQ.push(line);
|
|
495
|
+
ui.print(ready ? "• Queued until the current answer finishes." : "• Queued until the model is ready. Use /exit to quit while loading.");
|
|
496
|
+
if (!closed) ui.focus();
|
|
432
497
|
return;
|
|
433
498
|
}
|
|
434
499
|
const started = handle(line);
|
|
@@ -476,12 +541,68 @@ async function main() {
|
|
|
476
541
|
|
|
477
542
|
function streamPlainAssistant(text, final = false) {
|
|
478
543
|
if (ui.supportsReplace) return;
|
|
544
|
+
if (!showReasoning) {
|
|
545
|
+
if (plainAssistantMode === "undecided") {
|
|
546
|
+
if (!text && !final) return;
|
|
547
|
+
plainAssistantMode = "answer";
|
|
548
|
+
ui.append(`${GR}${B}◆${R} `);
|
|
549
|
+
}
|
|
550
|
+
if (text) ui.append(text);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
plainAssistantPending += text;
|
|
479
555
|
if (plainAssistantMode === "undecided") {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
556
|
+
const start = plainAssistantPending.trimStart();
|
|
557
|
+
if (!start && !final) return;
|
|
558
|
+
if (start.startsWith("<think>") || reasonRe.test(start)) {
|
|
559
|
+
plainAssistantMode = "reason";
|
|
560
|
+
plainReasonBuffer = start.replace(/^<think>\s*/i, "");
|
|
561
|
+
plainReasonPrinted = 0;
|
|
562
|
+
plainAssistantPending = "";
|
|
563
|
+
ui.append(`${G}-${R} ${G}`);
|
|
564
|
+
} else {
|
|
565
|
+
plainAssistantMode = "answer";
|
|
566
|
+
plainAssistantPending = start;
|
|
567
|
+
ui.append(`${GR}${B}◆${R} `);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (plainAssistantMode === "reason") {
|
|
572
|
+
plainReasonBuffer += plainAssistantPending;
|
|
573
|
+
plainAssistantPending = "";
|
|
574
|
+
const thinkEnd = plainReasonBuffer.indexOf("</think>");
|
|
575
|
+
const paraEnd = plainReasonBuffer.search(/\n\s*\n/);
|
|
576
|
+
const split = thinkEnd !== -1 ? thinkEnd : paraEnd;
|
|
577
|
+
if (split !== -1) {
|
|
578
|
+
const reason = plainReasonBuffer.slice(plainReasonPrinted, split).replace(/<\/think>/gi, "");
|
|
579
|
+
const restStart = thinkEnd !== -1 ? split + "</think>".length : split;
|
|
580
|
+
const rest = plainReasonBuffer.slice(restStart).trimStart();
|
|
581
|
+
if (reason) ui.append(reason);
|
|
582
|
+
ui.append(`${R}\n${GR}${B}◆${R} `);
|
|
583
|
+
plainAssistantMode = "answer";
|
|
584
|
+
plainAssistantPending = rest;
|
|
585
|
+
plainReasonBuffer = "";
|
|
586
|
+
plainReasonPrinted = 0;
|
|
587
|
+
} else {
|
|
588
|
+
const printableUntil = final ? plainReasonBuffer.length : Math.max(0, plainReasonBuffer.length - 2);
|
|
589
|
+
if (printableUntil > plainReasonPrinted) {
|
|
590
|
+
ui.append(plainReasonBuffer.slice(plainReasonPrinted, printableUntil));
|
|
591
|
+
plainReasonPrinted = printableUntil;
|
|
592
|
+
}
|
|
593
|
+
if (!final) return;
|
|
594
|
+
if (plainReasonPrinted < plainReasonBuffer.length) ui.append(plainReasonBuffer.slice(plainReasonPrinted));
|
|
595
|
+
plainReasonBuffer = "";
|
|
596
|
+
plainReasonPrinted = 0;
|
|
597
|
+
ui.append(R);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (plainAssistantMode === "answer" && plainAssistantPending) {
|
|
603
|
+
ui.append(plainAssistantPending);
|
|
604
|
+
plainAssistantPending = "";
|
|
483
605
|
}
|
|
484
|
-
if (text) ui.append(text);
|
|
485
606
|
}
|
|
486
607
|
|
|
487
608
|
|
|
@@ -562,8 +683,17 @@ async function main() {
|
|
|
562
683
|
proc.on("error", (e) => { ui.print("[cofos] Python error: " + e.message); shutdown(); });
|
|
563
684
|
proc.on("exit", (c) => {
|
|
564
685
|
if (closed) return;
|
|
686
|
+
backendExited = true;
|
|
687
|
+
ready = false;
|
|
688
|
+
generating = false;
|
|
689
|
+
inputQ.length = 0;
|
|
565
690
|
ui.print(c && c !== 0 ? "[cofos] Python exited with code " + c : "[cofos] Python backend exited.");
|
|
566
|
-
|
|
691
|
+
if (c && c !== 0) {
|
|
692
|
+
ui.print("[cofos] Backend failed before the model became ready. On Mac this usually means the 9B model could not fit in available memory and Transformers tried to offload the whole model to disk.");
|
|
693
|
+
ui.print("[cofos] Try a machine with more unified/GPU memory, force CPU with --device cpu for a slower attempt, or use a smaller/quantized model. Use /exit to close this session.");
|
|
694
|
+
} else {
|
|
695
|
+
ui.print("Press Ctrl+C to close.");
|
|
696
|
+
}
|
|
567
697
|
ui.focus();
|
|
568
698
|
process.exitCode = c ?? 0;
|
|
569
699
|
});
|
package/package.json
CHANGED
|
@@ -225,14 +225,32 @@ class StudentAdapterInference:
|
|
|
225
225
|
raise FileNotFoundError(f"Base model path not found: {self.model_path}")
|
|
226
226
|
|
|
227
227
|
@staticmethod
|
|
228
|
-
def
|
|
229
|
-
"""
|
|
228
|
+
def _select_device() -> str:
|
|
229
|
+
requested = os.environ.get("COFOS_DEVICE", "auto").strip().lower()
|
|
230
|
+
aliases = {"gpu": "cuda", "metal": "mps"}
|
|
231
|
+
requested = aliases.get(requested, requested)
|
|
232
|
+
if requested not in {"", "auto", "cuda", "mps", "cpu"}:
|
|
233
|
+
logging.warning("Ignoring unsupported COFOS_DEVICE=%s; using auto", requested)
|
|
234
|
+
requested = "auto"
|
|
235
|
+
if requested == "cuda":
|
|
236
|
+
if not torch.cuda.is_available():
|
|
237
|
+
raise RuntimeError("COFOS_DEVICE=cuda was requested, but CUDA is not available.")
|
|
238
|
+
return "cuda"
|
|
239
|
+
if requested == "mps":
|
|
240
|
+
if not getattr(torch.backends, "mps", None) or not torch.backends.mps.is_available():
|
|
241
|
+
raise RuntimeError("COFOS_DEVICE=mps was requested, but PyTorch MPS is not available.")
|
|
242
|
+
return "mps"
|
|
243
|
+
if requested == "cpu":
|
|
244
|
+
return "cpu"
|
|
245
|
+
if torch.cuda.is_available():
|
|
246
|
+
return "cuda"
|
|
247
|
+
if getattr(torch.backends, "mps", None) and torch.backends.mps.is_available():
|
|
248
|
+
return "mps"
|
|
249
|
+
return "cpu"
|
|
230
250
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
opts into another dtype with COFOS_DTYPE.
|
|
235
|
-
"""
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _select_dtype(device: str) -> torch.dtype:
|
|
253
|
+
"""Pick a stable dtype for the selected runtime device."""
|
|
236
254
|
requested = os.environ.get("COFOS_DTYPE", "auto").strip().lower()
|
|
237
255
|
aliases = {
|
|
238
256
|
"fp32": torch.float32,
|
|
@@ -247,10 +265,12 @@ class StudentAdapterInference:
|
|
|
247
265
|
if requested not in {"", "auto"}:
|
|
248
266
|
logging.warning("Ignoring unsupported COFOS_DTYPE=%s; using auto", requested)
|
|
249
267
|
|
|
250
|
-
if
|
|
268
|
+
if device == "cuda":
|
|
251
269
|
if torch.cuda.is_bf16_supported():
|
|
252
270
|
return torch.bfloat16
|
|
253
271
|
return torch.float16
|
|
272
|
+
if device == "mps":
|
|
273
|
+
return torch.float16
|
|
254
274
|
return torch.float32
|
|
255
275
|
|
|
256
276
|
def load_model(self) -> None:
|
|
@@ -277,15 +297,30 @@ class StudentAdapterInference:
|
|
|
277
297
|
# Decoder-only models must be left-padded for correct generation.
|
|
278
298
|
self.tokenizer.padding_side = "left"
|
|
279
299
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
model
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
300
|
+
device = self._select_device()
|
|
301
|
+
dtype = self._select_dtype(device)
|
|
302
|
+
logging.info("Loading model weights with dtype=%s on device=%s", dtype, device)
|
|
303
|
+
load_kwargs = {
|
|
304
|
+
"torch_dtype": dtype,
|
|
305
|
+
"low_cpu_mem_usage": True,
|
|
306
|
+
"trust_remote_code": True,
|
|
307
|
+
}
|
|
308
|
+
if device == "cuda":
|
|
309
|
+
load_kwargs["device_map"] = "auto"
|
|
310
|
+
try:
|
|
311
|
+
model = AutoModelForCausalLM.from_pretrained(self.model_path_str, **load_kwargs)
|
|
312
|
+
if device in {"mps", "cpu"}:
|
|
313
|
+
model = model.to(torch.device(device))
|
|
314
|
+
except ValueError as exc:
|
|
315
|
+
if "offload the whole model to the disk" in str(exc):
|
|
316
|
+
raise RuntimeError(
|
|
317
|
+
"COFOS could not fit the 9B model in available memory. "
|
|
318
|
+
"CUDA machines use automatic GPU placement; on Mac, COFOS avoids "
|
|
319
|
+
"Transformers disk offload and tries direct MPS loading instead. "
|
|
320
|
+
"Use a machine with more unified memory/GPU memory, or set "
|
|
321
|
+
"COFOS_DEVICE=cpu for a slower CPU-only attempt."
|
|
322
|
+
) from exc
|
|
323
|
+
raise
|
|
289
324
|
logging.info("Using merged model without a separate LoRA adapter")
|
|
290
325
|
|
|
291
326
|
model.generation_config.use_cache = True
|
|
@@ -489,6 +524,11 @@ class StudentAdapterInference:
|
|
|
489
524
|
@staticmethod
|
|
490
525
|
def _sanitize_stream(chunks: Any) -> Generator[str, None, None]:
|
|
491
526
|
"""Drop ``<think>`` blocks and leading labels from a text stream."""
|
|
527
|
+
if os.environ.get("COFOS_SHOW_REASONING", "").lower() in {"1", "true", "yes", "on"}:
|
|
528
|
+
for chunk in chunks:
|
|
529
|
+
yield chunk
|
|
530
|
+
return
|
|
531
|
+
|
|
492
532
|
buffer = ""
|
|
493
533
|
started = False
|
|
494
534
|
in_think = False
|