Support reviewed native Krea source profile
Bind the exact older sampler and model-base pair after reviewing its no-op writable-model scope difference for Krea. Reject mixed revisions, writable instance overrides and additional multigpu models. Admit the installed Krea2T revision with unchanged math and extra registrations. Verify observed loaded call-path code and enhancer constants against admitted source instead of relying on file hashes alone. Keep native node ports, task recipes, inference identity/revision limits and existing defaults unchanged. Validation: 170 host-free tests, required compile, saved-source native control, enhancer invocation and loaded-code counterexamples. Real runtime/model and application end-to-end acceptance remain separate.
This commit is contained in:
@@ -148,7 +148,12 @@ The supported source reference is the surviving
|
||||
The original repository URL currently returns404. The integration verifies the
|
||||
installed `ComfyUI-Krea2T-Enhancer` class source Git blob
|
||||
`c46e1c3ed244caaef231c580bb601ea935b3d1c7` and the pinned native text-fusion source
|
||||
before attachment. It delegates the original helper's two text-fusion paths and
|
||||
before attachment. The separately reviewed source blob
|
||||
`31e24388a8d495a524f46371659d3087e2302664` is also admitted: its basic enhancer
|
||||
functions, class and numeric constants are identical, with only additional
|
||||
imports/registrations for independent utilities. Loaded helper code and constants
|
||||
must match the admitted source; a matching file alone is insufficient.
|
||||
It delegates the original helper's two text-fusion paths and
|
||||
bounded token delta; no upstream numeric implementation is bundled here. The
|
||||
separate package retains its own MIT notice. Model and task-weight terms remain
|
||||
separate from plugin code; this source compatibility check makes no commercial
|
||||
@@ -229,5 +234,27 @@ guider, evaluation, batch and grounded-wrapper control flow with loading/device
|
||||
boundaries and neural/VAE work explicitly substituted. Failure/short-schedule
|
||||
cleanup evidence is source evidence, not installed-runtime or model acceptance.
|
||||
|
||||
Two exact native source pairs are admitted. The original pair binds sampler blob
|
||||
`a7cc3419e325e7d55aac8c7d5ce6d65f620fc511` to model-base SHA256
|
||||
`e7d08a4450287c76994b109437fbd03d721cf373fc8214bb9c30c478db060ffe`.
|
||||
The reviewed earlier API pair binds sampler blob
|
||||
`17f7bb8babdeec605586cc08df2ab1c4f1c92fd4` to model-base SHA256
|
||||
`5722b46418c6b7475041733d659bbd71f11f1dacb4d0aaff2b21aeb2c82d5542`.
|
||||
Mixed pairs reject. The earlier sampler omits the writable-model scope; Krea's
|
||||
inherited writable-model request in the newer source returns None. The consumer
|
||||
rejects instance writable-model overrides and additional multigpu models, keeping
|
||||
this equivalence limited to the exact Krea path. Changes to other model families
|
||||
do not establish their compatibility. Helper and numerical-sampling pins remain
|
||||
unchanged.
|
||||
|
||||
At active binding, the consumer compiles the admitted source without executing
|
||||
its module body and compares the observed, unwrapped loaded call-path code with
|
||||
it, including nested code and exception regions. It also checks loaded enhancer
|
||||
constants and native fusion methods. This detects stale or replaced function
|
||||
bodies despite matching files; it is not a whole-runtime or model-state proof.
|
||||
Device behavior, actual tokenizer/tensors, effect and task quality still require
|
||||
installed-host validation. Node labels, caller revisions and inference-handle
|
||||
limitations are unchanged.
|
||||
|
||||
To roll back this source addition, retain the previous checkout/package; no old
|
||||
saved graph is rewritten and no model/recipe is migrated by these nodes.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Compare loaded Python function bodies with admitted source without executing it.
|
||||
|
||||
This observes the imported callable's code, rather than assuming a file hash
|
||||
proves which code was loaded. Dependency/device/model state needs separate host
|
||||
verification. No upstream implementation is copied or executed by compilation.
|
||||
"""
|
||||
import inspect
|
||||
import types
|
||||
|
||||
|
||||
def _constant(value):
|
||||
if isinstance(value, types.CodeType):
|
||||
return ('code', _code_key(value))
|
||||
if isinstance(value, tuple):
|
||||
return tuple(_constant(item) for item in value)
|
||||
if isinstance(value, frozenset):
|
||||
return frozenset(_constant(item) for item in value)
|
||||
return value
|
||||
|
||||
|
||||
def _code_key(code):
|
||||
# Filename/line tables are debugging coordinates. Preserve executable byte
|
||||
# code, constants, closure layout and exception regions, including nesting.
|
||||
return (code.co_argcount, code.co_posonlyargcount, code.co_kwonlyargcount,
|
||||
code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code,
|
||||
tuple(_constant(item) for item in code.co_consts), code.co_names,
|
||||
code.co_varnames, code.co_freevars, code.co_cellvars,
|
||||
getattr(code, 'co_exceptiontable', b''))
|
||||
|
||||
|
||||
def verify_loaded_functions(module, source, names):
|
||||
compiled = compile(source, '<v12-admitted-source>', 'exec', dont_inherit=True)
|
||||
codes = {}
|
||||
def collect(code, prefix=()):
|
||||
for value in code.co_consts:
|
||||
if isinstance(value, types.CodeType):
|
||||
path = (*prefix, value.co_name)
|
||||
codes['.'.join(path)] = value
|
||||
collect(value, path)
|
||||
collect(compiled)
|
||||
for name in names:
|
||||
value = module
|
||||
for part in name.split('.'):
|
||||
value = getattr(value, part, None)
|
||||
if isinstance(value, (classmethod, staticmethod)):
|
||||
value = value.__func__
|
||||
value = inspect.unwrap(value)
|
||||
actual = getattr(value, '__code__', None)
|
||||
expected = codes.get(name)
|
||||
if (not isinstance(actual, types.CodeType) or expected is None
|
||||
or _code_key(actual) != _code_key(expected)):
|
||||
raise RuntimeError('V12 loaded function differs from admitted source: ' + name)
|
||||
+46
-3
@@ -14,11 +14,20 @@ import uuid
|
||||
|
||||
from .native import GROUNDING_BLOB, _signature
|
||||
from .studies import NativeStudies
|
||||
from .compatibility import verify_loaded_functions
|
||||
|
||||
SAMPLERS_BLOB = 'a7cc3419e325e7d55aac8c7d5ce6d65f620fc511'
|
||||
EULER_SHA256 = 'cfc4221ed0bc1f84a6f5689d3ec440cad3008c2541ab8f5e2599d1a73fac74ce'
|
||||
HELPERS_SHA256 = '53c82235bfea0c4edf3cd5219f080c44c45ac1c9a6c3171f2aae151e18b56b91'
|
||||
MODEL_BASE_SHA256 = 'e7d08a4450287c76994b109437fbd03d721cf373fc8214bb9c30c478db060ffe'
|
||||
# Exact paired API profiles. The second omits writable_models around sampling;
|
||||
# Krea's inherited writable-model request in the first is a no-op. Other model
|
||||
# families and arbitrary cross-pairs are outside this native Krea boundary.
|
||||
CONSUMER_SOURCE_PROFILES = {
|
||||
(SAMPLERS_BLOB, MODEL_BASE_SHA256): 'native-krea-writable-scope-v1',
|
||||
('17f7bb8babdeec605586cc08df2ab1c4f1c92fd4',
|
||||
'5722b46418c6b7475041733d659bbd71f11f1dacb4d0aaff2b21aeb2c82d5542'): 'native-krea-pre-writable-scope-v1',
|
||||
}
|
||||
_lock = threading.Lock()
|
||||
_busy = set()
|
||||
_MISSING = object()
|
||||
@@ -32,6 +41,13 @@ def _source_bytes(obj):
|
||||
return Path(inspect.getfile(obj)).read_bytes().replace(b'\r\n', b'\n')
|
||||
|
||||
|
||||
def _consumer_source_profile(samplers_blob, model_base_sha256):
|
||||
profile = CONSUMER_SOURCE_PROFILES.get((samplers_blob, model_base_sha256))
|
||||
if profile is None:
|
||||
raise RuntimeError('V12 native guider/model source pair is unverified')
|
||||
return profile
|
||||
|
||||
|
||||
def load_consumer_host():
|
||||
import comfy.samplers as samplers
|
||||
import comfy.sampler_helpers as helpers
|
||||
@@ -39,14 +55,40 @@ def load_consumer_host():
|
||||
import comfy.k_diffusion.sampling as euler
|
||||
data = _source_bytes(samplers)
|
||||
blob = hashlib.sha1(b'blob ' + str(len(data)).encode() + b'\0' + data).hexdigest()
|
||||
if blob != SAMPLERS_BLOB:
|
||||
raise RuntimeError('V12 native guider/sampler source revision is unverified')
|
||||
for module, expected in ((euler, EULER_SHA256), (helpers, HELPERS_SHA256), (model_base, MODEL_BASE_SHA256)):
|
||||
model_data = _source_bytes(model_base)
|
||||
profile = _consumer_source_profile(blob, hashlib.sha256(model_data).hexdigest())
|
||||
for module, expected in ((euler, EULER_SHA256), (helpers, HELPERS_SHA256)):
|
||||
if hashlib.sha256(_source_bytes(module)).hexdigest() != expected:
|
||||
raise RuntimeError('V12 native evaluation dependency source revision is unverified')
|
||||
verify_loaded_functions(samplers, data, (
|
||||
'CFGGuider.sample', 'CFGGuider.outer_sample', 'CFGGuider.inner_sample',
|
||||
'CFGGuider.predict_noise', 'CFGGuider.inner_set_conds', 'CFGGuider.set_conds',
|
||||
'KSAMPLER.sample', 'KSamplerX0Inpaint.__call__', 'process_conds',
|
||||
'calc_cond_batch', '_calc_cond_batch', 'sampling_function', 'cfg_function'))
|
||||
verify_loaded_functions(model_base, model_data, (
|
||||
'Krea2.__init__', 'Krea2.extra_conds', 'BaseModel.apply_model',
|
||||
'BaseModel._apply_model', 'BaseModel.extra_conds',
|
||||
'BaseModel.process_latent_in', 'BaseModel.process_latent_out'))
|
||||
if profile == 'native-krea-writable-scope-v1':
|
||||
verify_loaded_functions(model_base, model_data, ('BaseModel.get_writable_model',))
|
||||
elif hasattr(model_base.BaseModel, 'get_writable_model'):
|
||||
raise RuntimeError('V12 loaded model has an unverified writable-model hook')
|
||||
verify_loaded_functions(helpers, _source_bytes(helpers), ('convert_cond',))
|
||||
verify_loaded_functions(euler, _source_bytes(euler), (
|
||||
'sample_euler', 'sample_euler_ancestral', 'sample_euler_ancestral_RF',
|
||||
'sample_er_sde', 'offset_first_sigma_for_snr', 'sigma_to_half_log_snr'))
|
||||
return samplers, euler, model_base
|
||||
|
||||
|
||||
def _validate_writable_scope(patcher):
|
||||
# Both admitted source pairs have the same Krea path only when the primary
|
||||
# class has no instance writable-model override and no additional GPU model
|
||||
# can enter the newer writable-model scope. Do not infer other model support.
|
||||
if ('get_writable_model' in vars(patcher.model)
|
||||
or patcher.get_additional_models_with_key('multigpu')):
|
||||
raise RuntimeError('V12 native source profiles do not support extra writable/multigpu models')
|
||||
|
||||
|
||||
def _scalar(value):
|
||||
if not isinstance(value, Real):
|
||||
if not callable(getattr(value, 'numel', None)) or value.numel() != 1:
|
||||
@@ -167,6 +209,7 @@ class NativePhaseGuider:
|
||||
samplers, euler, model_base = self.host
|
||||
if type(guider) is not samplers.CFGGuider or type(guider.model_patcher.model) is not model_base.Krea2:
|
||||
raise RuntimeError('V12 consumer requires the pinned native Krea2 CFGGuider')
|
||||
_validate_writable_scope(guider.model_patcher)
|
||||
allowed_samplers = (euler.sample_euler, euler.sample_euler_ancestral,
|
||||
getattr(euler, 'sample_er_sde', None))
|
||||
if (type(sampler) is not samplers.KSAMPLER
|
||||
|
||||
+21
-4
@@ -14,8 +14,13 @@ import threading
|
||||
|
||||
from .ledger import TAP_WIDTH, TAPS
|
||||
from .native import _signature
|
||||
from .compatibility import verify_loaded_functions
|
||||
|
||||
ENHANCER_BLOB = 'c46e1c3ed244caaef231c580bb601ea935b3d1c7'
|
||||
ENHANCER_SOURCE_PROFILES = {
|
||||
ENHANCER_BLOB: 'krea2t-basic-math-v1',
|
||||
'31e24388a8d495a524f46371659d3087e2302664': 'krea2t-basic-math-extra-registrations-v1',
|
||||
}
|
||||
CORE_FUSION_BLOB = 'a904e05e0053d38bda0fdbd70625cd8c5e9b70a2'
|
||||
WRAPPER_KEY = 'kg_native_enhancer_v12'
|
||||
_OTHER_KEYS = ('krea2t_prompt_adherence_enhancer', 'krea2t_prompt_adherence_enhancer_advanced')
|
||||
@@ -31,8 +36,9 @@ def load_enhancer_host():
|
||||
raise RuntimeError('V12 enhancer requires the pinned original Krea2T enhancer package')
|
||||
data = Path(inspect.getfile(cls)).read_bytes().replace(b'\r\n', b'\n')
|
||||
blob = hashlib.sha1(b'blob ' + str(len(data)).encode() + b'\0' + data).hexdigest()
|
||||
if blob != ENHANCER_BLOB:
|
||||
if blob not in ENHANCER_SOURCE_PROFILES:
|
||||
raise RuntimeError('V12 enhancer source revision is unverified')
|
||||
enhancer_blob = blob
|
||||
module = sys.modules[cls.__module__]
|
||||
helper = getattr(module, '_enhanced_txtfusion_forward', None)
|
||||
compatible = getattr(module, '_is_krea2_dm', None)
|
||||
@@ -40,12 +46,23 @@ def load_enhancer_host():
|
||||
or inspect.getfile(helper) != inspect.getfile(cls)
|
||||
or inspect.getfile(compatible) != inspect.getfile(cls)):
|
||||
raise RuntimeError('V12 enhancer helper does not belong to the pinned source')
|
||||
verify_loaded_functions(module, data, (
|
||||
'_is_krea2_dm', '_chunk_gains', '_run_refiners',
|
||||
'_run_txtfusion_parts', '_enhanced_txtfusion_forward'))
|
||||
profile = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.5, 5.0, 1.1, 4.0, 1.0)
|
||||
expected_constants = {'KREA2_TAP_LAYERS': TAPS, 'KREA2_TAP_DIM': TAP_WIDTH,
|
||||
'KREA2_CHUNK_COUNT': 24, 'KREA2_CHUNK_DIM': 1280,
|
||||
'ENHANCER_PROFILE_12': profile, 'ENHANCER_CHUNK_PROFILE': profile + profile,
|
||||
'ENHANCER_GLOBAL_MULTIPLIER': 15.0, 'TXTFUSION_TOKEN_REL_CAP': 0.75}
|
||||
if any(getattr(module, name, None) != value for name, value in expected_constants.items()):
|
||||
raise RuntimeError('V12 loaded enhancer constants differ from admitted source')
|
||||
import comfy.ldm.krea2.model as core
|
||||
data = Path(inspect.getfile(core.TextFusionTransformer)).read_bytes().replace(b'\r\n', b'\n')
|
||||
blob = hashlib.sha1(b'blob ' + str(len(data)).encode() + b'\0' + data).hexdigest()
|
||||
if blob != CORE_FUSION_BLOB:
|
||||
raise RuntimeError('V12 enhancer native text-fusion source revision is unverified')
|
||||
return helper, compatible
|
||||
verify_loaded_functions(core, data, ('TextFusionTransformer.forward', 'TextFusionBlock.forward'))
|
||||
return helper, compatible, enhancer_blob
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -81,7 +98,7 @@ def apply_enhancer(model, enabled=False, strength=1.0):
|
||||
raise ValueError('V12 enhancer strength must be finite within [0, 2]')
|
||||
if not enabled or strength == 0:
|
||||
return model, 'Krea native enhancer V12: OFF; original MODEL retained.'
|
||||
helper, compatible = load_enhancer_host()
|
||||
helper, compatible, enhancer_blob = load_enhancer_host()
|
||||
dm = model.model.diffusion_model
|
||||
_check_architecture(dm, compatible)
|
||||
original_options_signature = _signature(model.model_options)
|
||||
@@ -90,7 +107,7 @@ def apply_enhancer(model, enabled=False, strength=1.0):
|
||||
raise RuntimeError('V12 enhancer refuses an already enhanced MODEL')
|
||||
if 'krea2_edit' in original_wrappers:
|
||||
raise RuntimeError('V12 enhancer must precede the native edit patch, which bypasses inner wrappers')
|
||||
config = {'enabled': True, 'strength': float(strength), 'source_blob': ENHANCER_BLOB}
|
||||
config = {'enabled': True, 'strength': float(strength), 'source_blob': enhancer_blob}
|
||||
|
||||
def wrapper(executor, *args, **kwargs):
|
||||
options = kwargs.get('transformer_options')
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Source-admission and observed loaded-code regressions; no host or model import."""
|
||||
import functools
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from types import ModuleType, SimpleNamespace as NS
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SPEC = importlib.util.spec_from_file_location('compatibility_v12_tests', ROOT / 'kg_krea_v12/__init__.py',
|
||||
submodule_search_locations=[str(ROOT / 'kg_krea_v12')])
|
||||
PACKAGE = importlib.util.module_from_spec(SPEC)
|
||||
sys.modules[SPEC.name] = PACKAGE
|
||||
SPEC.loader.exec_module(PACKAGE)
|
||||
from compatibility_v12_tests import compatibility, consumer, enhancer
|
||||
|
||||
|
||||
class CompatibilityTests(unittest.TestCase):
|
||||
def test_exact_core_pairs_admit_but_cross_pairs_and_unknown_reject(self):
|
||||
pairs = list(consumer.CONSUMER_SOURCE_PROFILES)
|
||||
self.assertEqual(len(pairs), 2)
|
||||
for pair in pairs:
|
||||
self.assertTrue(consumer._consumer_source_profile(*pair))
|
||||
for pair in ((pairs[0][0], pairs[1][1]), (pairs[1][0], pairs[0][1]), ('unknown', pairs[0][1])):
|
||||
with self.assertRaisesRegex(RuntimeError, 'source pair is unverified'):
|
||||
consumer._consumer_source_profile(*pair)
|
||||
|
||||
def test_loaded_body_changed_after_disk_read_rejects(self):
|
||||
source = 'def predict(x):\n return x + 1\n'
|
||||
module = ModuleType('observed_source_fixture')
|
||||
exec(compile(source, '<original-loaded-file>', 'exec', dont_inherit=True), module.__dict__)
|
||||
compatibility.verify_loaded_functions(module, source.encode(), ('predict',))
|
||||
changed = ModuleType('changed')
|
||||
exec('def predict(x):\n return x - 1\n', changed.__dict__)
|
||||
module.predict = changed.predict
|
||||
with self.assertRaisesRegex(RuntimeError, 'loaded function differs'):
|
||||
compatibility.verify_loaded_functions(module, source.encode(), ('predict',))
|
||||
|
||||
def test_loaded_nested_code_and_exception_cleanup_must_match(self):
|
||||
source = ('class Guider:\n'
|
||||
' def sample(self, x):\n'
|
||||
' def evaluate():\n'
|
||||
' return x + 1\n'
|
||||
' try:\n'
|
||||
' return evaluate()\n'
|
||||
' finally:\n'
|
||||
' self.clean = True\n')
|
||||
module = ModuleType('nested_source_fixture')
|
||||
exec(compile(source, '<original>', 'exec', dont_inherit=True), module.__dict__)
|
||||
compatibility.verify_loaded_functions(module, source.encode(), ('Guider.sample',))
|
||||
other = ModuleType('changed_nested')
|
||||
exec(compile(source.replace('x + 1', 'x + 2'), '<other>', 'exec', dont_inherit=True), other.__dict__)
|
||||
module.Guider.sample = other.Guider.sample
|
||||
with self.assertRaisesRegex(RuntimeError, 'loaded function differs'):
|
||||
compatibility.verify_loaded_functions(module, source.encode(), ('Guider.sample',))
|
||||
|
||||
def test_standard_decorator_unwrap_matches_original_loaded_body(self):
|
||||
source = 'def step(x):\n return x * 2\n'
|
||||
module = ModuleType('decorated_source_fixture')
|
||||
exec(compile(source, '<loaded>', 'exec', dont_inherit=True), module.__dict__)
|
||||
original = module.step
|
||||
@functools.wraps(original)
|
||||
def wrapper(*args, **kwargs):
|
||||
return original(*args, **kwargs)
|
||||
module.step = wrapper
|
||||
compatibility.verify_loaded_functions(module, source.encode(), ('step',))
|
||||
|
||||
def test_source_compilation_never_executes_module_body(self):
|
||||
source = 'must_never_execute()\ndef step(x):\n return x\n'
|
||||
module = ModuleType('compile_only_fixture')
|
||||
exec(compile('def step(x):\n return x\n', '<loaded>', 'exec', dont_inherit=True), module.__dict__)
|
||||
compatibility.verify_loaded_functions(module, source.encode(), ('step',))
|
||||
|
||||
def test_extra_writable_or_multigpu_models_reject(self):
|
||||
patcher = NS(model=NS(), get_additional_models_with_key=lambda name: [])
|
||||
consumer._validate_writable_scope(patcher)
|
||||
patcher.model.get_writable_model = lambda *args: object()
|
||||
with self.assertRaisesRegex(RuntimeError, 'extra writable/multigpu'):
|
||||
consumer._validate_writable_scope(patcher)
|
||||
del patcher.model.get_writable_model
|
||||
patcher.get_additional_models_with_key = lambda name: [object()]
|
||||
with self.assertRaisesRegex(RuntimeError, 'extra writable/multigpu'):
|
||||
consumer._validate_writable_scope(patcher)
|
||||
|
||||
def test_enhancer_profiles_bind_only_two_reviewed_whole_sources(self):
|
||||
self.assertEqual(set(enhancer.ENHANCER_SOURCE_PROFILES),
|
||||
{'c46e1c3ed244caaef231c580bb601ea935b3d1c7',
|
||||
'31e24388a8d495a524f46371659d3087e2302664'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -60,7 +60,7 @@ class EnhancerV12Tests(unittest.TestCase):
|
||||
self.calls.append((fusion, x, kwargs))
|
||||
return NS(shape=(1, 2, 2560), value='delegated-helper'), None
|
||||
|
||||
host_patch = patch.object(enhancer, 'load_enhancer_host', return_value=(helper, lambda dm: True))
|
||||
host_patch = patch.object(enhancer, 'load_enhancer_host', return_value=(helper, lambda dm: True, enhancer.ENHANCER_BLOB))
|
||||
host_patch.start()
|
||||
self.addCleanup(host_patch.stop)
|
||||
self.addCleanup(self.assertFalse, enhancer._busy)
|
||||
@@ -175,7 +175,7 @@ class EnhancerV12Tests(unittest.TestCase):
|
||||
self.assertEqual(self.calls, [])
|
||||
def helper(*args, **kwargs):
|
||||
return NS(shape=(1, 3, 2560)), None
|
||||
with patch.object(enhancer, 'load_enhancer_host', return_value=(helper, lambda dm: True)):
|
||||
with patch.object(enhancer, 'load_enhancer_host', return_value=(helper, lambda dm: True, enhancer.ENHANCER_BLOB)):
|
||||
_, malformed_options, malformed_wrapper = self.attached()
|
||||
with self.assertRaisesRegex(RuntimeError, 'helper returned'):
|
||||
malformed_wrapper(Executor(dm), transformer_options=malformed_options)
|
||||
|
||||
Reference in New Issue
Block a user