tk3.biz
Back to Blog

Dropping an Unused Image Feature Saved 1 GiB of Memory and Didn't Change Speed

Introduction

In the prompt cache post, I spotted this line in the server’s startup log.

srv    load_model: cache_reuse is not supported by multimodal, it will be disabled

The model I use, Qwen3.6-35B-A3B, can also handle images. When you specify it with -hf, the image mmproj (a file of about 0.9 GB) is loaded automatically.

But neither the llm command nor the dev-orchestra reviews use anything but text. So every time, I was reading a file and spending memory on a feature I never use.

What changes if I drop it? I had three candidates.

  • Memory goes down (almost certain)
  • Startup gets faster (one less file to read)
  • Loading and generation get faster (it might be the cause of the roughly 20% speed gap between llama-bench and the real server)

If the third one hit, my daily llm would get faster as it is.

Designing from the Checklist

In my previous post, I turned 19 measurement mistakes into a checklist. This time I applied it from the start.

Checklist itemWhat I did this time
Is the setting really in effect?Check that the mmproj loading line does not appear in the startup log, and stop if it differs from what I expect
How to compareAlternate with → without → with → without → with, 5 runs. Use whether the first and last “with” runs match to see drift
Same conditions as productionSame arguments as serve.ps1 (-c 65536, MTP on). The only difference is --no-mmproj
InterferenceRecord CPU and GPU used by anything other than llama every 10 seconds
Safety stopsStop if sleep prevention doesn’t work. Stop if the server doesn’t start
Raw dataRecord the beginning of the generated text, and check both conditions produce sane output

The first item comes from the lesson of the --cache-reuse episode. I was wrong for four days because I assumed “the setting I added must be working”, so now if it isn’t in effect, the measurement itself stops.

I measured startup time, memory, time to load 32K tokens, and generation speed (256 tokens × 3 runs).

Results of the First Run

#mmprojStartupMemory (after startup)Generation (avg)Loading 32K
1with9.7 s25.11 GiB18.00 t/s140.4 s
2without8.3 s24.12 GiB19.13 t/s133.6 s
3with9.4 s25.21 GiB18.42 t/s144.9 s
4without9.6 s24.12 GiB17.25 t/s134.9 s
5with10.1 s25.21 GiB19.22 t/s125.5 s

Memory Clearly Dropped by 1 GiB

“With” was 25.11–25.21 GiB, and “without” was 24.12 GiB both times. The difference is about 1.0 GiB. The values barely moved across the 5 runs, and the two ranges don’t overlap. This I can state with confidence.

On Speed, I Couldn’t Say Anything

Speed, on the other hand, shows no clear direction in the table. For generation, the first “without” run is fast and the second is slow. For loading, “without” lands somewhere in the middle.

The deciding factor was that the first and last “with” runs didn’t match.

#1 (with)#5 (with)Drift
Loading 32K140.4 s125.5 s−11%
Generation18.00 t/s19.22 t/s+7%

Same conditions, yet the numbers moved by 10% between the start and end of the measurement. The “without” value of 134 seconds sits right inside the spread among the “with” runs (loading 125–145 seconds).

The drift during measurement was larger than the difference between conditions. In this state, I can say neither “it got faster” nor “it didn’t change”. You could also say that interleaving the runs is what showed me this. If I had compared one run each, I might have put #4 and #5 side by side and written “it’s faster with mmproj”.

What the Monitoring Caught

Looking at the monitoring log, CPU use by things other than llama rose to 13–22% between 16:14 and 16:20. The top processes were python3.13 and StreamDeck. python3.13 is not the measurement script.

When I looked into it, the Python on this PC is the Microsoft Store version 3.13, and its process name shows up as python3.13. And during the same period, another Claude session was working with dev-orchestra. dev-orchestra is a tool that runs on this Python. I believe that’s what was running in the background (I didn’t check the other session’s logs, so this is a judgment from the circumstances).

That window covers the end of #3 and #4. The low generation speed in #4 (17.25 t/s) might be due to it. But #1 and #5, which don’t overlap that window, also drift by 10%, so I can’t say this was the only cause of the drift.

Stopping the Background Work and Measuring Again

I added “check that no other Claude session is running” to the “Interference” item of the checklist, and measured again.

When I checked before starting, the last activity in the dev-orchestra session was 40 seconds ago. It really was in use. I had that session stopped, confirmed there were no python processes, and then started.

I also increased the number of runs: alternating with → without 7 times (4 with, 3 without). That way I can compare each “without” run with the average of the “with” runs before and after it. Even if things drift slowly during measurement, bracketing them before and after cancels it out.

Comparison of with and without mmproj in the first and second runs. Memory is about 1 GiB apart in both runs and never overlaps. For generation speed and 32K loading time, the first run varied by 10% even between identical "with" runs, while the second run stayed within about 2%, and "without" overlapped "with" inside that range

#mmprojStartupMemory (after startup)Generation (avg)Loading 32K
1with25.0 s *25.06 GiB18.91 t/s133.2 s
2without8.3 s23.97 GiB19.39 t/s136.6 s
3with8.0 s25.06 GiB18.87 t/s136.0 s
4without7.1 s23.97 GiB18.77 t/s134.7 s
5with8.1 s25.06 GiB18.91 t/s136.8 s
6without7.3 s23.97 GiB18.85 t/s136.9 s
7with7.8 s25.06 GiB18.86 t/s136.3 s

* #1 was started right after stopping the server that had been running until just before.

In the monitoring, CPU use by things other than llama had a median of 7.1%, with no sustained peak like in the first run.

The drift shrank from 10% to around 2%.

GenerationLoading 32K
First and last “with” (#1 and #7)−0.2%+2.3%
#2 (without) vs. the “with” runs around it+2.6%+1.5%
#4 (without) vs. the “with” runs around it−0.6%−1.2%
#6 (without) vs. the “with” runs around it−0.2%+0.3%

The difference between “without” and the “with” runs around it changes direction from run to run and stays within 3%. I think it’s fair to say dropping mmproj doesn’t change speed. More precisely, if there is a difference, it’s under 3%.

Memory was again the same in all 7 runs (with: 25.06 GiB, without: 23.97 GiB, a difference of 1.09 GiB).

I don’t know whether the 10% drift in the first run was entirely due to dev-orchestra, because it drifted between #1 and #5 too, which didn’t overlap that window. But it’s certain that just quieting the background cut the spread of the same script to one fifth.

The Output Is Identical, Word for Word

In all 12 runs across the two measurements, the generated text was the same from the start.

ローカルLLMをiGPU(統合グラフィックス)で動作させる最大の利点は、専用メモリ(VRAM)を必要とせず、…

mmproj is the part that reads images, so as expected it has no effect on text only. Still, checking whether things really are as expected is how I did it this time.

The Two Things I Hoped For Didn’t Pan Out

--cache-reuse still couldn’t be used after dropping it.

srv    load_model: cache_reuse is not supported by this context, it will be disabled

The reason just changes from “because it’s multimodal” to “not supported in this context”. This model is a hybrid with SSM, and it can’t coexist with a mechanism that shifts and reuses the KV. It was just as I confirmed last time.

The speed gap between llama-bench and the real server couldn’t be explained by mmproj either. If loading had become 20% faster, that would have been the answer, but the remeasured difference is under 3%. The cause remains unknown.

I Changed the Default

I made “without mmproj” the default.

WithWithout
Memoryabout 25.1 GiBabout 24.0 GiB
Startupabout 8 sabout 7–8 s
Text output—Same
Image inputAvailableNot available

The only thing I lose is image input, which I don’t use now. On a machine with 93.6 GiB, 1 GiB is a small difference. Still, there’s no reason to keep spending memory on a feature I don’t use.

I added -Vision to serve.ps1, so images are loaded only when I need them.

# 普段(llm の自動起動もこちら)
pwsh -File scripts\serve.ps1            # --no-mmproj が付く

# 画像を使うとき
llm -Stop
pwsh -File scripts\serve.ps1 -Vision    # mmproj を読み込む

After the change, I had llm start the server automatically and confirmed that it came up with --no-mmproj, returned a response, and that memory was at 24.27 GiB.

Summary

  • I only use text, yet the image mmproj was loaded every time
  • Dropping it reduces memory by about 1 GiB. All 12 runs across the two measurements showed nearly the same value, so I can state this with confidence
  • In the first run, the first and last runs of the same condition drifted by 10%, and I couldn’t say anything about speed
  • In the background, another Claude session was running dev-orchestra. After stopping it and measuring again, the drift shrank to around 2%
  • The remeasured speed difference is under 3%. Dropping mmproj makes things neither faster nor slower
  • --cache-reuse still couldn’t be used after dropping it, and mmproj wasn’t the cause of the speed gap with llama-bench either
  • I changed the default to no mmproj. When I need images, I use -Vision

I started measuring hoping for a speedup, but speed didn’t change, and what I actually gained was 1 GiB of memory.

That I could write “I don’t know” after the first run, and say “it doesn’t change” after the second, is because I measured with interleaved runs and watched the size of the drift. And what cut the drift to one fifth wasn’t the measurement method but quieting the background.

Related Posts