# glm53-compile-03-fullgraph-optout.patch — let torch.compile split the graph instead # of failing on a break. # # STATUS: WIP — third of three patches in the "compile the GLM trunk" changelist. # With 01+02+03 the model TRACES, then hits the NEXT blocker (below). The set does not # yet boot the engine; nothing in scripts/ mounts it. # # THE BREAK: with 01+02 mounted the worker dies with # # RuntimeError: Graph breaks are not supported with aot compile. # Please use torch.compile(fullgraph=True). # # because vllm/compilation/wrapper.py calls torch.compile(..., fullgraph=True, ...) and # this build has AOT compile on by default (VLLM_USE_AOT_COMPILE). The GLM trunk cannot # satisfy fullgraph: its indexer metadata path syncs to the host — # models/glm5next/amd/sparse_indexer.py:263 int(positions[...].max().item()) + 1 # models/glm5next/amd/sparse_indexer.py:419 lmax = int(decode_metadata.decode_lens.max().item()) # — and a .item() inside a captured graph is not expressible. # # WHAT WAS VERIFIED (in-image, torch 2.11.0+rocm10.0, torch.compile(backend="eager")): # # fullgraph=True suppress_errors=False -> RAISE Unsupported # fullgraph=True suppress_errors=True -> RAISE Unsupported <-- suppress_errors # does NOT cover breaks # fullgraph=False suppress_errors=False -> ok (traceable segments compiled by # inductor, the rest runs eagerly; # numerics unchanged) # # So breaks must be TOLERATED, not suppressed: the traceable mass still fuses. This # patch makes the fullgraph requirement opt-out via VLLM_COMPILE_FULLGRAPH=0 (default 1, # so every other model keeps stock behaviour), and the run also needs # VLLM_USE_AOT_COMPILE=0 because AOT compile refuses breaks outright. # # TARGET: vllm/compilation/wrapper.py (inside the pinned image) # base sha256 969961dca41c78385212e985cfc0c57ef067f4a2d7490c73c658c600a1b95cac # patched sha256 732363bb1a1367772f13c60a655c678e7e2e9e56186677b14238f5e84cac31b3 # (the file already imports os; the patch does not add a second import) # # NEXT BLOCKER — NOT FIXED HERE. With 01+02+03 and # R4D_GLM_COMPILE=1 VLLM_COMPILE_FULLGRAPH=0 VLLM_USE_AOT_COMPILE=0 the trunk traces # ("Dynamo bytecode transform time: 1.08 s") and then the worker dies during profile_run # on the decode forward: # # KeyError: 'in_proj_qkvbfg_a' # model.py:748 Glm5NextModel.forward hidden_states, residual, post, comb = layer(...) # model.py:456 Glm5NextDecoderLayer.forward # # `in_proj_qkvbfg_a` is the KDA layer's merged column-parallel projection # (models/glm5next/common/kda.py:217, called at :407) — and it is also a name in the # load_weights shard table (model.py:780-785), i.e. a real submodule attribute name, # not an arbitrary string. # # WHERE IT IS THROWN (established 2026-09-30 by reading the full traceback, since the # report line is inside dynamo-rewritten code): torch/_dynamo keeps a name -> module map # and looks it up by name — output_graph.py:1259 / :1384 register into `nn_modules`, # :3000 does `self.nn_modules[node.target]` (a bare-name KeyError when absent), and # :1225 documents stripping "_buffers[..]/_parameters[..]/_modules[..]" from those # names. So the compiler is failing to RESOLVE A SUBMODULE BY NAME while tracing the # GLM decoder layer. Whether the root cause is the fork's module tree (something # dynamo cannot register statically) or dynamo's own registration is NOT yet settled. # # STATUS OF THAT BLOCKER (updated 2026-09-30, after the diagnostics below): # * TORCHDYNAMO_VERBOSE=1 adds NO internal stack trace -> it is a plain Python # KeyError raised in a callee dynamo INLINED into Glm5NextDecoderLayer.forward # (the reported line 456 is that method's `def` line, which is how inlined frames # surface). # * It is NOT raised from dynamo's four nn_modules lookups: the probe in # /home/dank/dynamo-probe wraps three of them and stayed silent over two runs with # the probe confirmed installed in the API-server and worker processes. # * It is NOT a literal string lookup: the name appears tree-wide only as the KDA # layer's attribute (kda.py:217, used at :407) and in the load_weights shard table # (model.py:780-785). The four `proj_name = prefix.split(".")[-1]` sites in the # quantization utils do compute exactly this bare key, but each is guarded # (`if proj_name not in fused_mapping: return`), so none of them can raise it. # * It reproduces on every run of this configuration (5 of 5). # REMAINING INSTRUMENT: name the inlined caller. Either `TORCH_LOGS=+dynamo` on a boot # and grep the log for the key (dynamo prints inlined frames with source), or wrap the # inlining candidates in that forward — the mHC CustomOps (hc_pre / hc_post / # hc_fused_post_pre in layers/mhc.py) and the indexer op — with a KeyError-reporting # probe. Until that names the caller this is a dynamo-side name-resolution failure. # NOTE the launcher gotcha that wasted a boot earlier: the scratch launcher now # preserves ENVARGS (`ENVARGS="${ENVARGS:-}"`), but it previously reset it, silently # dropping outer env vars. # # SECOND BLOCKER, KNOWN AND NOT FATAL IN THIS CONFIG: the indexer's in-forward # workspace acquisition (patch 05 makes it compiler-opaque) and the fused-norm # launcher cell (patch 04 removes the call) are real breaks, but with # VLLM_COMPILE_FULLGRAPH=0 they are tolerated — they only become fatal under # fullgraph=True, which this trunk can never satisfy anyway. --- a/vllm/compilation/wrapper.py +++ b/vllm/compilation/wrapper.py @@ -19,6 +19,15 @@ logger = init_logger(__name__) + +# FORK-LOCAL (patch: fullgraph opt-out). torch.compile(fullgraph=True) turns any +# dynamo graph break into a hard failure; suppress_errors does NOT cover breaks +# (verified in-image, torch 2.11+rocm10.0). With VLLM_COMPILE_FULLGRAPH=0 the +# traceable segments are still compiled by inductor and the untraceable ones run +# eagerly, which is what a model with device-sync metadata (the GLM-5.3-Flash +# indexer) needs. Default 1 keeps stock behaviour for every other model. +_FULLGRAPH = os.environ.get("VLLM_COMPILE_FULLGRAPH", "1") == "1" + R = TypeVar("R") P = ParamSpec("P") @@ -146,7 +155,7 @@ with aot_context: self._compiled_callable = torch.compile( compiled_ptr, - fullgraph=True, + fullgraph=_FULLGRAPH, dynamic=False, backend=backend, options=options,