Summary
ct.convert(..., states=[]) and ct.convert(...) are indistinguishable inside
TorchConverter, so a caller converting an ExportedProgram has no way to say "do not
turn my mutable buffers into states". The empty list is normalised into the same value as
the default before the decision is made.
coremltools/converters/mil/frontend/torch/converter.py, in TorchConverter.__init__
(9.0, lines 579-582):
# process states
if states is None:
states = []
self.states = states
and then, on the ExportedProgram path (line 640):
if states is None or len(states) == 0:
# For torch.export, we default to create states from torch mutable buffers
self.states = []
for name, tensor in self.graph.buffers.items():
...
self.states.append(state)
By the time that runs, states is None can never be true — it was rebound to [] above —
so the condition reduces to len(states) == 0, and an explicit empty list takes the
inference branch. Every mutable buffer becomes a state whatever the caller asked for.
Why it matters
ExecuTorch's Core ML backend has a take_over_mutable_buffer=False option, and cannot
honour it. Its converter call has no way to express the intent, and the models it produces
then require an MLState the runtime was not built to supply, failing at execute rather
than at conversion (pytorch/executorch#21855). The comment in the ExecuTorch source says
the option exists precisely because the OSS runtime does not handle state.
Suggested change
Keep the caller's intent before normalising:
# process states
# An empty list is the caller saying the model has no states, which is not the
# same as saying nothing; only the latter asks for them to be inferred.
infer_states_from_buffers = states is None
if states is None:
states = []
self.states = states
and use infer_states_from_buffers in place of states is None or len(states) == 0.
Callers passing None or a non-empty list are unaffected. The one behaviour that changes
is states=[], which today means "infer them" and would come to mean what it reads as.
What I did not do
I could not reduce the ExecuTorch symptom below a full LLM export, so this report is from
reading the conversion path rather than from a minimal end-to-end repro. The two code
paths above are quoted verbatim from the installed 9.0 source, and the ExecuTorch side is
in pytorch/executorch#21855 with its own detail.
Environment: coremltools 9.0, torch 2.13.0, macOS arm64.
Summary
ct.convert(..., states=[])andct.convert(...)are indistinguishable insideTorchConverter, so a caller converting anExportedProgramhas no way to say "do notturn my mutable buffers into states". The empty list is normalised into the same value as
the default before the decision is made.
coremltools/converters/mil/frontend/torch/converter.py, inTorchConverter.__init__(9.0, lines 579-582):
and then, on the
ExportedProgrampath (line 640):By the time that runs,
states is Nonecan never be true — it was rebound to[]above —so the condition reduces to
len(states) == 0, and an explicit empty list takes theinference branch. Every mutable buffer becomes a state whatever the caller asked for.
Why it matters
ExecuTorch's Core ML backend has a
take_over_mutable_buffer=Falseoption, and cannothonour it. Its converter call has no way to express the intent, and the models it produces
then require an
MLStatethe runtime was not built to supply, failing at execute ratherthan at conversion (pytorch/executorch#21855). The comment in the ExecuTorch source says
the option exists precisely because the OSS runtime does not handle state.
Suggested change
Keep the caller's intent before normalising:
and use
infer_states_from_buffersin place ofstates is None or len(states) == 0.Callers passing
Noneor a non-empty list are unaffected. The one behaviour that changesis
states=[], which today means "infer them" and would come to mean what it reads as.What I did not do
I could not reduce the ExecuTorch symptom below a full LLM export, so this report is from
reading the conversion path rather than from a minimal end-to-end repro. The two code
paths above are quoted verbatim from the installed 9.0 source, and the ExecuTorch side is
in pytorch/executorch#21855 with its own detail.
Environment: coremltools 9.0, torch 2.13.0, macOS arm64.