Skip to content

Commit a211332

Browse files
committed
[0.16.1] llama_bin.txt path added
1 parent d4d2ef3 commit a211332

7 files changed

Lines changed: 214 additions & 37 deletions

File tree

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,32 @@ The version in `pyproject.toml`, the git tag and the release on GitHub always sa
66
the same thing; the release workflow refuses a tag that disagrees with
77
`pyproject.toml`, or one that neither changelog has a section for.
88

9+
## 0.16.1 - 2026-08-22
10+
11+
### Added
12+
13+
- **A path in a file, for a server whose environment is not yours to set.** One
14+
line in `ComfyUI/user/minimax_h3_rewriter/llama_bin.txt` — the folder your
15+
llama.cpp was built in, or the binary itself — and the node runs that build.
16+
It is read after `MINIMAX_H3_LLAMA_BIN` and before everything else.
17+
18+
This is what `MINIMAX_H3_LLAMA_BIN` cannot do on a server. An export reaches a
19+
server started from that same shell and nothing else: a systemd unit, a
20+
container entrypoint or a launcher script hands the process an environment of
21+
its own and never reads your `~/.bashrc`, so the variable is simply absent
22+
where it matters — and so is anything you added to `PATH` the same way. The
23+
file is read by the node itself, and does not care who started ComfyUI.
24+
25+
### Changed
26+
27+
- **When no llama.cpp is found anywhere, the refusal prints what this process
28+
actually had.** The variable and whether it is set here, the file and whether
29+
it exists, the unpacked runtime folder, and `PATH` with its entry count — plus,
30+
on Linux, the one command that shows the running server's real environment.
31+
"Put it on PATH" is useless advice to someone who did exactly that in a shell
32+
the server never saw, so the message reports the search rather than repeating
33+
the instruction.
34+
935
## 0.16.0 - 2026-08-21
1036

1137
### Added

CHANGELOG_RU.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@
66
workflow релиза отклонит тег, который расходится с `pyproject.toml`, и тег, для
77
которого нет раздела ни в одном из двух changelog'ов.
88

9+
## 0.16.1 - 2026-08-22
10+
11+
### Добавлено
12+
13+
- **Путь в файле — для сервера, чьё окружение задаёте не вы.** Одна строчка в
14+
`ComfyUI/user/minimax_h3_rewriter/llama_bin.txt` — папка, в которой собран ваш
15+
llama.cpp, или сам бинарник, — и нода запускает эту сборку. Файл читается
16+
после `MINIMAX_H3_LLAMA_BIN` и раньше всего остального.
17+
18+
Именно этого `MINIMAX_H3_LLAMA_BIN` не может на сервере. `export` доходит до
19+
сервера, запущенного из этой же оболочки, и больше никуда: юнит systemd,
20+
entrypoint контейнера или скрипт-лаунчер выдают процессу собственное окружение
21+
и `~/.bashrc` не читают, так что переменной там просто нет — как нет и того,
22+
что вы тем же способом дописали в `PATH`. Файл читает сама нода, и ей всё
23+
равно, кто запустил ComfyUI.
24+
25+
### Изменено
26+
27+
- **Если llama.cpp не нашёлся нигде, отказ печатает, что было у этого процесса на
28+
самом деле.** Переменная и стоит ли она здесь, файл и есть ли он, папка
29+
распакованного рантайма и `PATH` со счётом записей — а под Linux ещё и та самая
30+
команда, которая показывает настоящее окружение работающего сервера. «Положите
31+
в PATH» — бесполезный совет тому, кто ровно это и сделал в оболочке, которой
32+
сервер не видел, так что сообщение показывает поиск, а не повторяет указание.
33+
934
## 0.16.0 - 2026-08-21
1035

1136
### Добавлено

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,21 +1122,37 @@ when the binaries are in use:
11221122
| `cpu` | 17 MB | no GPU at all |
11231123

11241124
**An llama.cpp you already have is run as it is.** Before fetching anything the
1125-
node looks in three places, in order: the path in `MINIMAX_H3_LLAMA_BIN`, then
1126-
its own `runtime/` folder, then `PATH`. A build you compiled yourself therefore
1127-
needs no setting at all — put its `build/bin` on `PATH`, or name it outright:
1125+
node looks in four places, in order: the path in `MINIMAX_H3_LLAMA_BIN`, the
1126+
path written in `ComfyUI/user/minimax_h3_rewriter/llama_bin.txt`, its own
1127+
`runtime/` folder, then `PATH`. A build you compiled yourself therefore needs no
1128+
setting at all — put its `build/bin` on `PATH`, or name it outright:
11281129

11291130
```sh
11301131
export MINIMAX_H3_LLAMA_BIN=/opt/llama.cpp/build/bin # the folder, or the binary in it
11311132
```
11321133

1134+
**On a server, prefer the file.** An export reaches a server started from that
1135+
same shell and nothing else — a systemd unit, a container entrypoint or a
1136+
launcher script hands the process an environment of its own, and never reads
1137+
your `~/.bashrc`. One line in `llama_bin.txt` is read by the node itself and
1138+
does not care who started ComfyUI:
1139+
1140+
```sh
1141+
echo /opt/llama.cpp/build/bin > ~/comfy/ComfyUI/user/minimax_h3_rewriter/llama_bin.txt
1142+
```
1143+
11331144
`llama_backend` then stops mattering: it picks which archive to download, not
11341145
what an existing binary was compiled against. This is the road to a CUDA
11351146
llama.cpp on Linux — build it once with `-DGGML_CUDA=ON`, name it here, and
11361147
`device = cuda:0` does what it says. The caption nodes look for
11371148
`llama-mtmd-cli` beside it; a build without that target sends the node back to
1138-
the archive for that one job. A variable pointing at nothing is an error rather
1139-
than a quiet download, so a typo says so instead of costing half a gigabyte.
1149+
the archive for that one job.
1150+
1151+
A path pointing at nothing is an error rather than a quiet download, so a typo
1152+
says so instead of costing half a gigabyte. And when nothing is found anywhere,
1153+
the refusal prints what the ComfyUI **process** actually had — the variable, the
1154+
file, the folder and its `PATH` — instead of repeating advice you may already
1155+
have taken in a shell it never saw.
11401156

11411157
> **Why not the `llama-cpp-python` CUDA wheels.** Both current ones fail on
11421158
> ordinary consumer hardware, in two unrelated ways:

README_RU.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,21 +1118,36 @@ llama.cpp уносит дочерний процесс, а не ComfyUI вмес
11181118
| `cpu` | 17 МБ | без GPU вовсе |
11191119

11201120
**Уже собранный llama.cpp запускается как есть.** Прежде чем что-то качать,
1121-
нода смотрит в три места по порядку: путь из `MINIMAX_H3_LLAMA_BIN`, потом
1122-
собственная папка `runtime/`, потом `PATH`. Собственной сборке поэтому не нужно
1123-
никаких настроек — положите её `build/bin` в `PATH` или назовите прямо:
1121+
нода смотрит в четыре места по порядку: путь из `MINIMAX_H3_LLAMA_BIN`, путь,
1122+
записанный в `ComfyUI/user/minimax_h3_rewriter/llama_bin.txt`, собственная папка
1123+
`runtime/`, потом `PATH`. Собственной сборке поэтому не нужно никаких настроек —
1124+
положите её `build/bin` в `PATH` или назовите прямо:
11241125

11251126
```sh
11261127
export MINIMAX_H3_LLAMA_BIN=/opt/llama.cpp/build/bin # папка или сам бинарник
11271128
```
11281129

1130+
**На сервере надёжнее файл.** `export` доходит до сервера, запущенного из этой же
1131+
оболочки, и больше никуда: юнит systemd, entrypoint контейнера или скрипт-лаунчер
1132+
выдают процессу собственное окружение и ваш `~/.bashrc` не читают вовсе. Строчку в
1133+
`llama_bin.txt` читает сама нода, и ей всё равно, кто запустил ComfyUI:
1134+
1135+
```sh
1136+
echo /opt/llama.cpp/build/bin > ~/comfy/ComfyUI/user/minimax_h3_rewriter/llama_bin.txt
1137+
```
1138+
11291139
После этого `llama_backend` перестаёт что-либо значить: он выбирает, какой архив
11301140
скачать, а не то, с чем скомпилирован уже имеющийся бинарник. Это и есть дорога
11311141
к CUDA-сборке llama.cpp под Linux — собрать один раз с `-DGGML_CUDA=ON`,
11321142
назвать здесь, и `device = cuda:0` начнёт делать то, что написано. Ноды-описатели
11331143
ищут рядом `llama-mtmd-cli`; сборка без этой цели отправит ноду за архивом ради
1134-
одной этой задачи. Переменная, указывающая в пустоту, — ошибка, а не тихая
1135-
загрузка: опечатка скажет о себе, а не обойдётся в полгигабайта.
1144+
одной этой задачи.
1145+
1146+
Путь, указывающий в пустоту, — ошибка, а не тихая загрузка: опечатка скажет о
1147+
себе, а не обойдётся в полгигабайта. А если не нашлось нигде, отказ печатает, что
1148+
было у **процесса** ComfyUI на самом деле — переменная, файл, папка и его `PATH`, —
1149+
вместо того чтобы повторять совет, которому вы могли уже последовать в оболочке,
1150+
которой сервер не видел.
11361151

11371152
> **Почему не CUDA-колёса `llama-cpp-python`.** Оба нынешних не работают на
11381153
> обычном потребительском железе, причём по двум независимым причинам:

minimax_h3_rewriter/llamacpp.py

Lines changed: 116 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
ask for it by name.
2525
2626
None of which applies when the machine already has an llama.cpp:
27-
``MINIMAX_H3_LLAMA_BIN`` names one outright, PATH is read when it does not,
28-
and whatever is found is run as it is. That is the only road to a CUDA
29-
llama.cpp on Linux, since no CUDA archive is published for it.
27+
``MINIMAX_H3_LLAMA_BIN`` names one outright, ``llama_bin.txt`` in the user
28+
directory says the same to a server whose environment you cannot set, PATH is
29+
read when neither is there, and whatever is found is run as it is. That is the
30+
only road to a CUDA llama.cpp on Linux, since no CUDA archive is published for
31+
it.
3032
"""
3133

3234
from __future__ import annotations
@@ -57,6 +59,15 @@
5759
#: every build puts llama-mtmd-cli next to llama-completion.
5860
BIN_ENV = "MINIMAX_H3_LLAMA_BIN"
5961

62+
#: The same thing written down, for a server whose environment is not yours.
63+
#:
64+
#: An export in a shell reaches a server started from that shell and nothing
65+
#: else: a systemd unit, a container entrypoint or a launcher script hands the
66+
#: process an environment of its own, and the variable above is simply absent
67+
#: there. This file is read from ComfyUI's own user directory instead, beside
68+
#: the model list -- one path in it, blank lines and '#' comments ignored.
69+
BIN_FILE = "llama_bin.txt"
70+
6071
#: (sys.platform, backend) -> archive names within the release.
6172
#:
6273
#: The CUDA entry is two archives: the runtime libraries live in a separate
@@ -178,6 +189,11 @@ def install_dir(backend: str) -> str:
178189
return os.path.join(root(), f"{RELEASE}-{backend}")
179190

180191

192+
def bin_file() -> str:
193+
"""``<ComfyUI user>/minimax_h3_rewriter/llama_bin.txt``, read if it is there."""
194+
return os.path.join(os.path.dirname(root()), BIN_FILE)
195+
196+
181197
def find_binary(directory: str, names: tuple[str, ...] = BINARIES) -> str:
182198
"""Locate the completion executable inside an unpacked release, at any depth."""
183199
for name in names:
@@ -197,14 +213,14 @@ def installed(backend: str) -> str:
197213
return find_binary(directory) if os.path.isdir(directory) else ""
198214

199215

200-
def _named_binary(names: tuple[str, ...]) -> str:
201-
"""The binary ``BIN_ENV`` points at, or "" when the variable is unset.
216+
def _binary_at(value: str, names: tuple[str, ...], source: str) -> str:
217+
"""Resolve a path someone gave us to one of ``names``, or say why not.
202218
203-
A variable that is set but points nowhere useful raises rather than falls
219+
A path that is given but points nowhere useful raises rather than falls
204220
through: naming a build is an instruction, and quietly downloading a
205221
different one instead would hide a typo behind half a gigabyte.
206222
"""
207-
value = (os.environ.get(BIN_ENV) or "").strip().strip('"')
223+
value = (value or "").strip().strip('"')
208224
if not value:
209225
return ""
210226
if EXE and not os.path.exists(value) and os.path.isfile(value + EXE):
@@ -217,17 +233,34 @@ def _named_binary(names: tuple[str, ...]) -> str:
217233
if beside:
218234
return beside
219235
raise RuntimeError(
220-
f"{BIN_ENV} is set to '{value}', and none of {', '.join(names)} is that "
221-
f"file or sits beside it."
236+
f"{source} is '{value}', and none of {', '.join(names)} is that file "
237+
f"or sits beside it."
222238
)
223239
if os.path.isdir(value):
224240
found = find_binary(value, names)
225241
if found:
226242
return found
227243
raise RuntimeError(
228-
f"{BIN_ENV} is set to '{value}', which holds none of {', '.join(names)}."
244+
f"{source} is '{value}', which holds none of {', '.join(names)}."
229245
)
230-
raise RuntimeError(f"{BIN_ENV} is set to '{value}', which does not exist.")
246+
raise RuntimeError(f"{source} is '{value}', which does not exist.")
247+
248+
249+
def _named_binary(names: tuple[str, ...]) -> str:
250+
"""The binary ``BIN_ENV`` points at, or "" when the variable is unset."""
251+
return _binary_at(os.environ.get(BIN_ENV) or "", names, BIN_ENV)
252+
253+
254+
def _file_binary(names: tuple[str, ...]) -> str:
255+
"""The binary ``BIN_FILE`` names, or "" when there is no such file."""
256+
path = bin_file()
257+
try:
258+
with open(path, "r", encoding="utf-8") as handle:
259+
lines = [line.strip() for line in handle]
260+
except OSError:
261+
return ""
262+
wanted = next((line for line in lines if line and not line.startswith("#")), "")
263+
return _binary_at(wanted, names, path)
231264

232265

233266
def _path_binary(names: tuple[str, ...]) -> str:
@@ -250,8 +283,8 @@ def _announce(binary: str, source: str) -> str:
250283

251284

252285
def external(names: tuple[str, ...] = BINARIES) -> str:
253-
"""An llama.cpp that is already here: named in the environment, or on PATH."""
254-
return _named_binary(names) or _path_binary(names)
286+
"""An llama.cpp that is already here: given by name, or found on PATH."""
287+
return _named_binary(names) or _file_binary(names) or _path_binary(names)
255288

256289

257290
def available() -> bool:
@@ -350,6 +383,7 @@ def _packaged(backend: str, auto_download: bool, progress=None) -> str:
350383
f"The llama.cpp {backend} runtime is not in '{directory}' and auto_download is off. "
351384
f"Enable it, or unpack {', '.join(names)} from "
352385
f"https://github.com/{REPO}/releases/tag/{RELEASE} into that folder."
386+
f"\n\n{where_looked(backend)}"
353387
)
354388

355389
from . import download
@@ -431,23 +465,71 @@ def report(position: int, label: str, _name=name) -> None:
431465
return binary
432466

433467

468+
def where_looked(backend: str) -> str:
469+
"""The places that were searched and what each held, as a block of text.
470+
471+
"Put it on PATH" is useless advice to someone who did exactly that in a
472+
shell the server never saw, so a refusal reports the environment this
473+
process actually has rather than repeating the instruction.
474+
"""
475+
named = os.environ.get(BIN_ENV)
476+
entries = [entry for entry in os.environ.get("PATH", "").split(os.pathsep) if entry]
477+
shown = os.pathsep.join(entries)
478+
if len(shown) > 400:
479+
shown = shown[:400] + " ..."
480+
directory = install_dir(resolve_backend(backend))
481+
written = bin_file()
482+
483+
lines = [
484+
f"Where this ComfyUI process (pid {os.getpid()}) looked:",
485+
f" {BIN_ENV}: " + (f"'{named}'" if named else "not set in this process"),
486+
f" {written}: " + ("read" if os.path.isfile(written) else "no such file"),
487+
f" unpacked runtime: {directory}"
488+
+ ("" if os.path.isdir(directory) else " (not there)"),
489+
f" PATH: {len(entries)} entries, none holding {' or '.join(BINARIES)}",
490+
f" {shown}",
491+
]
492+
if not named:
493+
lines.append(
494+
"An export in your shell reaches a server started from that shell and "
495+
"nothing else -- a service is handed an environment of its own."
496+
)
497+
if sys.platform == "linux":
498+
lines.append(
499+
f"See what this process really has: tr '\\0' '\\n' "
500+
f"< /proc/{os.getpid()}/environ | grep -E '^(PATH|{BIN_ENV})='"
501+
)
502+
lines.append(
503+
f"The way that needs no environment at all: write the path to your build "
504+
f"into {written}, or put the build itself in {directory}."
505+
)
506+
return "\n".join(lines)
507+
508+
434509
def ensure(backend: str, auto_download: bool, progress=None) -> str:
435510
"""Return the path to the completion binary, fetching the release if needed.
436511
437-
Four places, in this order: what ``BIN_ENV`` names, because naming a build
438-
is an instruction; then what this pack has already unpacked, which is the
439-
pinned, known-good one; then PATH, so an llama.cpp compiled on this machine
440-
is used rather than a second copy downloaded beside it; then the download.
441-
442-
That third step is the only road to a CUDA llama.cpp on Linux, where
443-
upstream publishes no CUDA archive at all. It is also why ``llama_backend``
444-
stops mattering once a build is found: it picks which archive to fetch, not
445-
what an existing binary was compiled against.
512+
Five places, in this order: what ``BIN_ENV`` names and what ``BIN_FILE``
513+
names, because giving a path is an instruction; then what this pack has
514+
already unpacked, which is the pinned, known-good one; then PATH, so an
515+
llama.cpp compiled on this machine is used rather than a second copy
516+
downloaded beside it; then the download.
517+
518+
Those given paths are the only road to a CUDA llama.cpp on Linux, where
519+
upstream publishes no CUDA archive at all. They are also why
520+
``llama_backend`` stops mattering once a build is found: it picks which
521+
archive to fetch, not what an existing binary was compiled against. When
522+
there is no archive to fetch either, the refusal carries the search rather
523+
than repeating advice the reader may already have taken.
446524
"""
447525
named = _named_binary(BINARIES)
448526
if named:
449527
return _announce(named, f"named in {BIN_ENV}")
450528

529+
written = _file_binary(BINARIES)
530+
if written:
531+
return _announce(written, f"named in {bin_file()}")
532+
451533
backend = resolve_backend(backend)
452534
existing = installed(backend)
453535
if existing:
@@ -457,13 +539,20 @@ def ensure(backend: str, auto_download: bool, progress=None) -> str:
457539
if on_path:
458540
return _announce(on_path, "found on PATH")
459541

542+
try:
543+
assets(backend)
544+
except RuntimeError as error:
545+
raise RuntimeError(
546+
f"{error}\n\n{where_looked(backend)}"
547+
) from error
548+
460549
return _packaged(backend, auto_download, progress)
461550

462551

463552
def ensure_mtmd(backend: str, auto_download: bool, progress=None) -> str:
464553
"""Return the path to ``llama-mtmd-cli``, fetching the release if needed.
465554
466-
The same four places in the same order, since the captioner is built beside
555+
The same five places in the same order, since the captioner is built beside
467556
the completion binary and ships in the same archive: a captioner run on a
468557
machine that has already rewritten a prompt downloads nothing.
469558
@@ -475,6 +564,10 @@ def ensure_mtmd(backend: str, auto_download: bool, progress=None) -> str:
475564
if named:
476565
return _announce(named, f"named in {BIN_ENV}")
477566

567+
written = _file_binary(MTMD_BINARIES)
568+
if written:
569+
return _announce(written, f"named in {bin_file()}")
570+
478571
directory = install_dir(resolve_backend(backend))
479572
unpacked = find_binary(directory, MTMD_BINARIES) if os.path.isdir(directory) else ""
480573
if unpacked:

0 commit comments

Comments
 (0)