2424ask for it by name.
2525
2626None 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
3234from __future__ import annotations
5759#: every build puts llama-mtmd-cli next to llama-completion.
5860BIN_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+
181197def 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
233266def _path_binary (names : tuple [str , ...]) -> str :
@@ -250,8 +283,8 @@ def _announce(binary: str, source: str) -> str:
250283
251284
252285def 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
257290def 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+
434509def 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
463552def 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