Jump latent upscale fallback to spatial split

This commit is contained in:
2026-09-04 08:00:06 +00:00
parent 179fa0778e
commit 30ae81b86b
5 changed files with 80 additions and 47 deletions
+10 -1
View File
@@ -150,7 +150,16 @@ class H3ModelInspector:
@classmethod
def INPUT_TYPES(cls):
return {"required": {"model": ("MODEL",)}}
return {
"required": {
"model": (
"MODEL",
{
"tooltip": "MiniMax / H3 model to inspect for quantization and tensor format."
},
)
}
}
def inspect(self, model):
label, _counts, report = _detect(model)
+12 -10
View File
@@ -521,21 +521,23 @@ def _shrink_model_tile_param(param):
tile_w = int(next_param.get("tile_width", 0) or 0)
tile_h = int(next_param.get("tile_height", 0) or 0)
new_w = _halve_32(tile_w)
new_h = _halve_32(tile_h)
if new_w < 32 or new_h < 32:
if tile_w <= 0 or tile_h <= 0:
return None
if new_w >= tile_w and new_h >= tile_h:
return None
next_param["tile_width"] = new_w
next_param["tile_height"] = new_h
next_param["overlap"] = _halve_32(next_param.get("overlap", 0))
# Jump straight from a coarse specific-size tile to a conservative 2x2
# equal-grid split. That usually cuts peak VRAM much harder than halving
# the tile size through several retries.
next_param["tile_size_mode"] = "rows_cols"
next_param["grid_rows"] = 2
next_param["grid_cols"] = 2
next_param["spatial_w_overlap"] = _halve_32(next_param.get("overlap", 0))
next_param["spatial_h_overlap"] = _halve_32(next_param.get("overlap", 0))
next_param["fade_width"] = _halve_32(next_param.get("fade_width", 0))
next_param["fade_height"] = _halve_32(next_param.get("fade_height", 0))
next_param["min_tile_size"] = min(
_halve_32(next_param.get("min_tile_size", 0)),
new_w,
new_h,
max(32, _halve_32(tile_w)),
max(32, _halve_32(tile_h)),
)
return next_param
+4 -3
View File
@@ -39,10 +39,11 @@ class H3ShotLength:
def INPUT_TYPES(cls):
return {
"required": {
"shot_seconds": ("FLOAT", {"default": 5.0, "min": 0.2, "max": 15.1, "step": 0.5,
"shot_seconds": ("FLOAT", {"default": 3.0, "min": 0.2, "max": 15.1, "step": 0.5,
"tooltip": "Length of each shot. Feeds the sampler's shot_seconds AND (as frames) "
"the preview override. Max ~15s (362 frames)."}),
"fps": ("INT", {"default": 24, "min": 1, "max": 60}),
"the preview override. Default 3s matches the common one-beat H3 test shot. Max ~15s (362 frames)."}),
"fps": ("INT", {"default": 24, "min": 1, "max": 60,
"tooltip": "Frame rate used for the seconds->frames conversion. H3 itself renders at 24fps, so 24 is the realistic default."}),
},
"optional": {
"cap_to_h3_max": ("BOOLEAN", {"default": True,
+50 -29
View File
@@ -277,7 +277,14 @@ class DumasJSONStringToObjectNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_string": ("STRING", {"multiline": True}),
"json_string": (
"STRING",
{
"multiline": True,
"default": '{\n "shots": [\n {\n "prompt": "Francine stands by the window."\n }\n ]\n}',
"tooltip": "Raw JSON text to parse into a structured JSON object."
},
),
}
}
@@ -299,7 +306,14 @@ class DumasStripIterationSuffixNode:
def INPUT_TYPES(cls):
return {
"required": {
"filename": ("STRING", {"default": "", "multiline": False}),
"filename": (
"STRING",
{
"default": "francine_pose_final.png",
"multiline": False,
"tooltip": "Filename to normalize by removing everything after the first underscore in the stem."
},
),
}
}
@@ -316,7 +330,14 @@ class DumasSlugifyStringNode:
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": "", "multiline": False}),
"text": (
"STRING",
{
"default": "Francine Coffee Shop",
"multiline": False,
"tooltip": "Text to slugify into lowercase ASCII words joined with hyphens."
},
),
}
}
@@ -334,8 +355,8 @@ class DumasJSONObjectToStringNode:
return {
"required": {
"json_object": ("JSON",),
"pretty": ("BOOLEAN", {"default": True}),
"sort_keys": ("BOOLEAN", {"default": False}),
"pretty": ("BOOLEAN", {"default": True, "tooltip": "Pretty-print the JSON with indentation."}),
"sort_keys": ("BOOLEAN", {"default": False, "tooltip": "Sort object keys alphabetically before serializing."}),
}
}
@@ -355,7 +376,7 @@ class DumasJSONGetValueNode:
return {
"required": {
"json_object": ("JSON",),
"path": ("STRING", {"default": "", "multiline": False}),
"path": ("STRING", {"default": "shots.0.prompt", "multiline": False, "tooltip": "Dot-path to read, such as 'shots.0.prompt'."}),
}
}
@@ -373,8 +394,8 @@ class DumasJSONSetValueNode:
return {
"required": {
"json_object": ("JSON",),
"path": ("STRING", {"default": "", "multiline": False}),
"value_json": ("STRING", {"multiline": True, "default": "null"}),
"path": ("STRING", {"default": "shots.0.prompt", "multiline": False, "tooltip": "Dot-path to write, such as 'shots.0.prompt' or 'shots.1.duration'."}),
"value_json": ("STRING", {"multiline": True, "default": '"Francine stands by the window."', "tooltip": "JSON value to store at the path. Must be valid JSON, so strings need quotes."}),
}
}
@@ -398,7 +419,7 @@ class DumasJSONHasKeyNode:
return {
"required": {
"json_object": ("JSON",),
"path": ("STRING", {"default": "", "multiline": False}),
"path": ("STRING", {"default": "shots.0.prompt", "multiline": False, "tooltip": "Dot-path to test for existence."}),
}
}
@@ -416,7 +437,7 @@ class DumasJSONRemoveKeyNode:
return {
"required": {
"json_object": ("JSON",),
"path": ("STRING", {"default": "", "multiline": False}),
"path": ("STRING", {"default": "shots.0.prompt", "multiline": False, "tooltip": "Dot-path to remove from the object."}),
}
}
@@ -434,7 +455,7 @@ class DumasJSONPickFieldsNode:
return {
"required": {
"json_object": ("JSON",),
"paths": ("STRING", {"multiline": True, "default": ""}),
"paths": ("STRING", {"multiline": True, "default": "shots.0.prompt\nshots.0.duration", "tooltip": "One dot-path per line. Only those fields are copied into the output object."}),
}
}
@@ -463,8 +484,8 @@ class DumasJSONMergeObjectsNode:
def INPUT_TYPES(cls):
return {
"required": {
"base_object": ("JSON",),
"overlay_object": ("JSON",),
"base_object": ("JSON", {"tooltip": "Base JSON object to start from."}),
"overlay_object": ("JSON", {"tooltip": "Overlay JSON object whose keys replace or merge into the base object."}),
}
}
@@ -482,7 +503,7 @@ class DumasJSONKeysNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_object": ("JSON",),
"json_object": ("JSON", {"tooltip": "JSON object whose top-level keys should be listed."}),
}
}
@@ -502,7 +523,7 @@ class DumasJSONArrayLengthNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_array": ("JSON",),
"json_array": ("JSON", {"tooltip": "JSON array whose length should be measured."}),
}
}
@@ -521,8 +542,8 @@ class DumasJSONArrayAppendNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_array": ("JSON",),
"value_json": ("STRING", {"multiline": True, "default": "null"}),
"json_array": ("JSON", {"tooltip": "JSON array to append to."}),
"value_json": ("STRING", {"multiline": True, "default": '{"prompt":"Francine looks toward the door."}', "tooltip": "JSON value to append. Must be valid JSON."}),
}
}
@@ -548,10 +569,10 @@ class DumasJSONArraySliceNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_array": ("JSON",),
"start": ("INT", {"default": 0, "step": 1}),
"end": ("INT", {"default": 0, "step": 1}),
"step": ("INT", {"default": 1, "step": 1, "min": 1}),
"json_array": ("JSON", {"tooltip": "JSON array to slice."}),
"start": ("INT", {"default": 0, "step": 1, "tooltip": "Zero-based start index."}),
"end": ("INT", {"default": 0, "step": 1, "tooltip": "Zero-based end index. Use 0 to mean 'to the end'."}),
"step": ("INT", {"default": 1, "step": 1, "min": 1, "tooltip": "Slice step size."}),
}
}
@@ -572,9 +593,9 @@ class DumasJSONArrayIteratorNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_input": ("JSON",),
"index": ("INT", {"default": 0, "min": 0, "step": 1}),
"mode": (["fixed", "incr", "decr"], {"default": "fixed"}),
"json_input": ("JSON", {"tooltip": "JSON array to iterate over."}),
"index": ("INT", {"default": 0, "min": 0, "step": 1, "tooltip": "Current zero-based index."}),
"mode": (["fixed", "incr", "decr"], {"default": "fixed", "tooltip": "Keep the index fixed, increment it, or decrement it before reading."}),
}
}
@@ -600,9 +621,9 @@ class DumasJSONObjectIteratorNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_input": ("JSON",),
"index": ("INT", {"default": 0, "min": 0, "step": 1}),
"mode": (["fixed", "incr", "decr"], {"default": "fixed"}),
"json_input": ("JSON", {"tooltip": "JSON object whose key/value pairs should be iterated in insertion order."}),
"index": ("INT", {"default": 0, "min": 0, "step": 1, "tooltip": "Current zero-based index into the object's items."}),
"mode": (["fixed", "incr", "decr"], {"default": "fixed", "tooltip": "Keep the index fixed, increment it, or decrement it before reading."}),
}
}
@@ -628,7 +649,7 @@ class DumasJSONFlattenNode:
def INPUT_TYPES(cls):
return {
"required": {
"json_input": ("JSON",),
"json_input": ("JSON", {"tooltip": "Nested JSON value to flatten into dot-path keys."}),
}
}
@@ -645,7 +666,7 @@ class DumasJSONUnflattenNode:
def INPUT_TYPES(cls):
return {
"required": {
"flat_json_object": ("JSON",),
"flat_json_object": ("JSON", {"tooltip": "Flat JSON object whose keys are dot-paths to rebuild into nested JSON."}),
}
}
+4 -4
View File
@@ -1172,12 +1172,12 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
"fade_height": 32,
})
self.assertIsNotNone(smaller)
self.assertLess(smaller["tile_width"], 512)
self.assertLess(smaller["tile_height"], 512)
self.assertEqual(smaller["tile_size_mode"], "rows_cols")
self.assertEqual(smaller["grid_rows"], 2)
self.assertEqual(smaller["grid_cols"], 2)
self.assertEqual(smaller["fade_width"] % 32, 0)
self.assertEqual(smaller["fade_height"] % 32, 0)
self.assertLessEqual(smaller["min_tile_size"], smaller["tile_width"])
self.assertLessEqual(smaller["min_tile_size"], smaller["tile_height"])
self.assertGreaterEqual(smaller["min_tile_size"], 0)
def test_temporal_segments_split_long_sequences(self):
latent = importlib.import_module("dumas_h3_latent_upscale")