Skip to content

fix(mtmd): clean up successful MTMD results after sibling decode failure - #177

Merged
JamePeng merged 2 commits into
JamePeng:mainfrom
craftingmod:fix-leftover
Sep 5, 2026
Merged

JamePeng merged 2 commits into
JamePeng:mainfrom
craftingmod:fix-leftover

Conversation

@craftingmod

Copy link
Copy Markdown

Summary

When a media is failed to decode in ThreadPoolExecutor, other succeed media is leaked from cleanup list.
This PR fixes leaking succeed media from cleanup list.

AI Disclosure

Codex(GPT 5.6 Luna) is used to analyze and sugessting fix, but I manually reviewed and confirmed entire changes.

@craftingmod craftingmod changed the title Fix(mtmd): ensure inserting image/video into cleanup array at Concurrent Media Decoding. Fix(mtmd): leaking successful image/video in cleanup array at Concurrent Media Decoding. Sep 2, 2026
@craftingmod craftingmod changed the title Fix(mtmd): leaking successful image/video in cleanup array at Concurrent Media Decoding. Fix(mtmd): leaking successful image/video in cleanup array on decoding error. Sep 2, 2026
@JamePeng

JamePeng commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Could you provide details on how to reproduce the issue you mentioned?

@craftingmod

craftingmod commented Sep 3, 2026

Copy link
Copy Markdown
Author

Reproduce issue

"""
Reproduce issue of memory leak
when MTMD has failed with one invalid/multiple valid images
"""

from llama_cpp import Llama
import base64
from PIL import UnidentifiedImageError
import psutil
import os

def image_data_uri(raw: bytes, mime: str = "image/png") -> str:
  encoded = base64.b64encode(raw).decode("ascii")
  return f"data:{mime};base64,{encoded}"

def get_memory_mb() -> float:
  pid = os.getpid()
  process = psutil.Process(pid)
  
  memory_bytes = process.memory_info().rss
  memory_mb = memory_bytes / (1024 * 1024)
  
  return memory_mb

# Model and multimodal projection paths
MODEL_PATH = r"./gemma-4-E2B-it-Q4_K_M.gguf"
MMPROJ_PATH = r"./mmproj-BF16.gguf"

invalid_image = b"\x89PNG\r\n\x1a\n"
valid_image_path = "images/correct.png"

llm = Llama(
  model_path=MODEL_PATH,
  mmproj_path=MMPROJ_PATH,
  n_gpu_layers=-1,
  n_ctx=10240,
  verbose=True,
  verbosity=2,
  chat_handler_kwargs={
    "verbose": False,
  },
)

llm_messages = [
  {
    "role": "user",
    "content": [
      {
        "type": "image_url",
        "image_url": {
          "url": image_data_uri(invalid_image),
        },
      },
      *[
        {
          "type": "image_url",
          "image_url": {
            "url": valid_image_path,
          },
        }
        for _ in range(5)
      ],
      {
        "type": "text",
        "text": "Describe each images in one sentence.",
      },
    ],
  }
]

attemps = 100
previous_mb = get_memory_mb()
try:
  for i in range(attemps):
    prefix = f"[{i+1}/{attemps}]"
    print(f"{prefix} create_chat_completion", flush=True)
    
    try:
      response = llm.create_chat_completion(
        messages=llm_messages,
      )
    except UnidentifiedImageError as exc:
      print(f"{prefix} expected error: {exc}", flush=True)
    except Exception:
      raise
    else:
      raise AssertionError("Invalid image unexpectedly succeeded")
    finally:
      current_mb = get_memory_mb()
      print(
        f"{prefix} rss={current_mb:.2f} MB "
        f"delta={(current_mb - previous_mb):.2f} MB",
        flush=True
      )
      previous_mb = current_mb
finally:
  print(f"before close: {get_memory_mb():.2f} MB", flush=True)
  llm.close()
  print(f"after close: {get_memory_mb():.2f} MB", flush=True)

From repo: https://github.com/craftingmod/llama_cpp_python_resetissue/blob/main/reproduce2.py

Summary

Request MTMD messages with 1 invalid & 5 valid images 100 times and
calculate usage memory of python process.

  • Expected behavior: rss is simirar each tries
  • Actual behavior: rss is increasing each tires

Result

Expected memory usage

(After fix)

...
[95/100] create_chat_completion
[95/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000228BF5828E0>
[95/100] rss=1897.29 MB delta=1.73 MB
[96/100] create_chat_completion
[96/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000228BF591B20>
[96/100] rss=1897.29 MB delta=0.00 MB
[97/100] create_chat_completion
[97/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000228BF590DB0>
[97/100] rss=1897.29 MB delta=0.00 MB
[98/100] create_chat_completion
[98/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000228BF55F600>
[98/100] rss=1897.29 MB delta=0.00 MB
[99/100] create_chat_completion
[99/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000228BF55E390>
[99/100] rss=1897.29 MB delta=0.00 MB
[100/100] create_chat_completion
[100/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000228BF55FD30>
[100/100] rss=1895.78 MB delta=-1.51 MB
before close: 1895.78 MB
after close: 323.73 MB

Actual memory usage

(Before fix)

...
[95/100] create_chat_completion
[95/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000120E820E5C0>
[95/100] rss=2865.31 MB delta=15.32 MB
[96/100] create_chat_completion
[96/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000120E81DC5E0>
[96/100] rss=2875.93 MB delta=10.62 MB
[97/100] create_chat_completion
[97/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000120E81F81D0>
[97/100] rss=2886.22 MB delta=10.29 MB
[98/100] create_chat_completion
[98/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000120E81F9120>
[98/100] rss=2895.18 MB delta=8.96 MB
[99/100] create_chat_completion
[99/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000120E81F9DF0>
[99/100] rss=2905.15 MB delta=9.98 MB
[100/100] create_chat_completion
[100/100] expected error: cannot identify image file <_io.BytesIO object at 0x00000120E822AA20>
[100/100] rss=2909.12 MB delta=3.97 MB
before close: 2909.12 MB
after close: 1343.98 MB

@craftingmod craftingmod changed the title Fix(mtmd): leaking successful image/video in cleanup array on decoding error. fix(mtmd): clean up successful MTMD results after sibling decode failure Sep 3, 2026
@JamePeng

JamePeng commented Sep 3, 2026

Copy link
Copy Markdown
Owner

I'll probably check it later.

@JamePeng

JamePeng commented Sep 5, 2026

Copy link
Copy Markdown
Owner

LGTM

@JamePeng
JamePeng merged commit 2976f80 into JamePeng:main Sep 5, 2026
6 checks passed
@JamePeng

JamePeng commented Sep 5, 2026

Copy link
Copy Markdown
Owner

I’ve re-optimized this part of the logic; you might want to see how your example performs with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants