Rebind affinity after Modal protocol probes

This commit is contained in:
Ken Simpson
2026-08-19 22:49:16 -07:00
parent 4f5aeb1c98
commit f9d85c19f3
4 changed files with 49 additions and 3 deletions
+1 -1
View File
@@ -292,7 +292,7 @@ When the planner detects a remote → local → remote gap, those remote proxies
Independent Modal-backed components can overlap through ComfyUI's async proxy path and the local Modal call executor. Large tensor and media edges are deliberately co-located first, so this parallelism is reserved for genuinely independent branches, inexpensive scalar boundaries, mapped execution, and graph shapes that require a split for correctness. Ordinary components with several remote execution targets remain one proxy unless a local re-entry dependency would create a scheduler cycle or a mixed local-preview/remote-continuation boundary requires ordered phases. Each Modal GPU container handles one active workflow execution at a time, so parallel ready components can scale out across containers instead of multiplexing several active executions onto one worker. `COMFY_MODAL_MAX_INFLIGHT_CALLS` bounds local dispatch independently from the local CPU count and the remote autoscaler.
If a remote output also feeds both later remote work and a local branch that cannot transitively reach any later remote component, Modal-Sync splits the producer from the remote continuation. The producer publishes a small durable bridge reference; the downstream remote proxy consumes that reference immediately, while an asynchronous local materializer downloads the underlying value for the independent preview or save branch. A dispatch gate starts every nearest downstream Modal continuation before releasing the local download, so the preview still appears while later remote inference is in flight without putting transfer, rendering, or display work on its critical path. When these ordered phases place `Modal LLM` between ordinary image or video phases, the planner assigns the LLM and Comfy phases to separate reusable worker-affinity groups so a warm worker holding heavyweight Comfy models is not selected for the LLM load. Local branches that do feed remote execution remain ordinary dependencies and preserve their original ordering.
If a remote output also feeds both later remote work and a local branch that cannot transitively reach any later remote component, Modal-Sync splits the producer from the remote continuation. The producer publishes a small durable bridge reference; the downstream remote proxy consumes that reference immediately, while an asynchronous local materializer downloads the underlying value for the independent preview or save branch. A dispatch gate starts every nearest downstream Modal continuation before releasing the local download, so the preview still appears while later remote inference is in flight without putting transfer, rendering, or display work on its critical path. When these ordered phases place `Modal LLM` between ordinary image or video phases, the planner assigns the LLM and Comfy phases to separate reusable worker-affinity groups so a warm worker holding heavyweight Comfy models is not selected for the LLM load. Deployment protocol checks use a parameterless probe for backward compatibility, but actual execution always rebinds an affinity-aware worker handle, including after protocol compatibility has been cached. Local branches that do feed remote execution remain ordinary dependencies and preserve their original ordering.
When a component output is consumed by other remote components, Modal-Sync can keep the continuation remote. The producer returns a small durable bridge reference through the local ComfyUI proxy instead of returning the underlying `IMAGE`, `AUDIO`, `LATENT`, `MASK`, `SIGMAS`, `VIDEO`, or scalar value. A downstream remote component resolves that reference from the worker's warm bridge cache when possible, or restores it from shared Modal storage when Modal schedules the components on different containers. An independent local-only consumer gets the ordinary ComfyUI value through the parallel materializer described above. A local branch that later returns to remote execution still uses normal materialization and dependency ordering.
+1
View File
@@ -2,6 +2,7 @@
## 2026-08-19
- A Modal class-parameter affinity key can still be silently defeated after the first successful protocol check if the cached-current branch returns the parameterless compatibility-probe instance. Treat protocol probes as validation-only handles: both the initial-compatible and cached-compatible paths must perform a fresh parameterized lookup before execution, and regression-test both branches.
- Canvas decoration refactors must exercise an active node-progress render through `onDrawForeground`, not only verify palette source strings. Moving `shadowColor` into a palette object left one bare progress-panel reference that parsed successfully but raised only when the panel was painted; use the current palette color with a neutral fallback for the local-warning path.
- Deliberate mixed-fanout cuts can still form a cycle in the coarse component graph when an earlier value bypasses the cut and feeds a later remote node. SCC merging must preserve graph correctness, so the repair belongs after that merge: force ordered split-proxy phases at annotated local-materializer/remote-session boundaries, carry the local materializers through the phase rewrite, and isolate LLM versus ordinary Comfy phases with distinct reusable worker-affinity groups.
- ComfyUI's V3 loader assigns `RELATIVE_PYTHON_MODULE` only to nodes returned during startup. Any internal proxy or bridge class inserted into `NODE_CLASS_MAPPINGS` later must copy that loader-owned identity at the registration boundary, including cache-hit reinsertions; otherwise `/object_info` emits `python_module: null` and the frontend can abort before filtering development-only nodes.
+9 -2
View File
@@ -6742,8 +6742,15 @@ def _ensure_remote_engine_protocol_current(
settings = _settings_for_payload(payload)
runtime_cache_key = _modal_runtime_cache_key(payload)
with _MODAL_AUTO_DEPLOY_LOCK:
if runtime_cache_key in _MODAL_REMOTE_APP_VERSION_OK:
return remote_engine
runtime_is_known_current = runtime_cache_key in _MODAL_REMOTE_APP_VERSION_OK
if runtime_is_known_current:
logger.info(
"Rebinding deployed RemoteEngine after cached protocol validation "
"component=%s worker_affinity=%s.",
payload.get("component_id"),
_remote_worker_affinity_key(payload),
)
return _lookup_deployed_remote_engine(payload)
version_payload = _remote_engine_runtime_version(remote_engine)
if _is_runtime_version_payload_current(version_payload, payload):
with _MODAL_AUTO_DEPLOY_LOCK:
+38
View File
@@ -6202,6 +6202,44 @@ def test_remote_modal_rebinds_affinity_after_compatible_protocol_probe(
assert observed_payloads == [payload]
def test_remote_modal_rebinds_affinity_after_cached_protocol_validation(
remote_modal_app_module: Any,
monkeypatch: Any,
) -> None:
"""A cached protocol result must not turn its parameterless probe into execution."""
probe_engine = object()
affinity_engine = object()
observed_payloads: list[dict[str, Any]] = []
def fake_lookup(payload: dict[str, Any]) -> object:
"""Return the affinity-aware handle created for actual execution."""
observed_payloads.append(payload)
return affinity_engine
monkeypatch.setattr(
remote_modal_app_module,
"_lookup_deployed_remote_engine",
fake_lookup,
)
payload = {
"component_id": "component-llm",
"remote_worker_affinity_group": "llm",
}
runtime_cache_key = remote_modal_app_module._modal_runtime_cache_key(payload)
remote_modal_app_module._MODAL_REMOTE_APP_VERSION_OK.clear()
remote_modal_app_module._MODAL_REMOTE_APP_VERSION_OK.add(runtime_cache_key)
try:
result = remote_modal_app_module._ensure_remote_engine_protocol_current(
probe_engine,
payload,
)
finally:
remote_modal_app_module._MODAL_REMOTE_APP_VERSION_OK.clear()
assert result is affinity_engine
assert observed_payloads == [payload]
def test_workflow_gpu_changes_expected_remote_runtime_fingerprint(
remote_modal_app_module: Any,
) -> None: