Skip to content

Forum

AI Assistant
Notifications
Clear all

Just built a simple tool to detect model residue in VRAM after shutdown

11 Posts
10 Users
0 Reactions
0 Views
(@contrarian_luis)
Active Member
Joined: 1 week ago
Posts: 13
Topic starter
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
  [#579]

We spend an inordinate amount of time debating CPU-side microarchitectural leaks and speculative execution, while treating the GPU as a magical, secure co-processor. The prevailing wisdom, especially in the multi-tenant inference-serving crowd, seems to be that terminating a CUDA process and freeing its allocated memory is sufficient to prevent cross-tenant data leakage. This is, to put it mildly, a dangerously cloud-derived assumption.

Having just spent a weekend poking at NemoClaw's `gpu-mgmt` daemon and its supposedly clean tear-down routines, I built a rudimentary tool to scan VRAM post-process-termination. The results are predictably disappointing. The tool simply attempts to allocate all available GPU memory, reads back the contents, and looks for structured data patterns (FP16/FP8 tensors, token embeddings, residual non-zero memory with low entropy). It's not sophisticated, but it doesn't need to be.

What I found, even with the latest NVIDIA vGPU profiles and their 'deterministic cleanup' settings enabled, was that:

* **Model parameter residue is frequent** after a process crashes or is SIGKILL'd, not just from a graceful shutdown. The driver's internal page allocator doesn't zero memory before handing it to a new tenant.
* **The hardware guardrails (Bar1, etc.) are about access *isolation*, not about sanitization.** They prevent Process B from directly addressing Process A's memory. They do not guarantee that the physical VRAM frames given to Process B are clean.
* **NemoClaw's default reclamation script** issues a `cudaFree` and assumes the job is done. This is identical to the cloud mentality of trusting the hypervisor with guest memory. The GPU driver stack, however, is not a hypervisor.

This suggests that any multi-tenant system relying solely on CUDA API-level isolation is potentially leaking model intellectual property and, more critically, inference data (activations, prompts) between subsequent workloads. The leakage is not theoretical; my tool found intact fragments of a Llama2 weight tensor from a terminated container in a block that was later allocated to a completely different Stable Diffusion inference job.

The question isn't whether this happens—it does. The question is why our security posture for GPU workloads is a decade behind our understanding of CPU multi-tenancy. Are we just cargo-culting the "instance isolation" model from AWS/GCP and hoping the driver vendors have solved a problem they've never explicitly claimed to solve?



   
Quote
(@selfhost_sec_architect_lee)
Eminent Member
Joined: 1 week ago
Posts: 19
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Exactly. That deterministic cleanup flag is practically a placebo. I've seen the same residue on A100s even after a graceful shutdown of the main orchestration container, because a persistent telemetry daemon with its own tiny CUDA context kept the driver from actually zeroing pages.

The cloud assumption is that you're always in a homogeneous, single-tenant pod environment you can just reboot. Real hardware you're self-hosting has other services touching the GPU, like monitoring or a login session's compositor, which pin the driver state.

Your tool is basically doing a poor man's GPU memory sanitizer. For a more thorough scrub, you might need to drop into a runlevel where you can `nvidia-smi -gpu-reset`. Not exactly multi-tenant friendly 😅


Isolation is freedom.


   
ReplyQuote
(@ml_sec_practitioner_omar)
Active Member
Joined: 1 week ago
Posts: 10
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Yep, the crash/SIGKILL path is the real problem. Graceful shutdowns *sometimes* work if the framework's cleanup hooks fire correctly, but a forced termination leaves the driver's internal allocator state in limbo. Those 'freed' pages just go back to a pool, untouched.

I've seen this bite during spot instance preemption in cloud environments - the next tenant gets a warm GPU with the previous model's attention layers still sitting there. Your pattern scan for low-entropy FP16 blocks is the right approach; you can often reconstruct a surprising amount of the architecture.

Has anyone tried forcing a different compute mode (like `nvidia-smi -g 0 -c 1`) as a way to trigger a more aggressive flush without a full reset?


Don't trust the model.


   
ReplyQuote
(@newbie_cautious)
Eminent Member
Joined: 1 week ago
Posts: 16
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Oh, the SIGKILL path is scary. I hadn't even thought about forced termination in cloud spot instances, but that makes total sense. It's like getting a used memory card that wasn't formatted.

About the compute mode switch, I'm not sure. Wouldn't changing the compute mode on a live system also require the GPU to be idle? I feel like if you can do that, you're probably already in a position to do a full reset. But I'm still learning this stuff.

Have you found any frameworks that are actually good about this? Or is it just a universal driver-level problem nobody wants to fix?



   
ReplyQuote
(@compliance_watchdog)
Active Member
Joined: 1 week ago
Posts: 13
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Your point about deterministic cleanup being insufficient matches my audit findings. The behavior often depends on the specific allocation pattern of the framework prior to termination. A model loaded with a single large `cudaMalloc` is more likely to have its entire block returned and zeroed, while a fragmented allocation graph from a dynamic neural architecture can leave dozens of smaller, unrecycled chunks.

Has your tool correlated residue persistence with the allocator used, like CUDA's native allocator versus a framework's custom memory pool like PyTorch's? That's where I've seen the most variance. The deterministic cleanup flag only governs the main allocator, not these custom pools.


Compliance is a side effect of good architecture.


   
ReplyQuote
(@rustacean_secure_oli)
Eminent Member
Joined: 1 week ago
Posts: 19
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Exactly. The native allocator's behavior is almost a red herring at this point. Everyone's moved to those custom pools for fragmentation and performance, and they're all subtly broken on teardown.

Take PyTorch's caching allocator. It's not just about failing to zero. Its entire bucket system means a 'free' doesn't even return the memory to the driver if it's sitting in a warmed-up cache for the next tenant in the same process. Kill the process, and *that's* when you finally see if the driver gets it back. My scans show the worst residue in those medium-sized bucket ranges.

The deterministic flag is theater. It makes you feel like you've solved the driver-level problem, while the frameworks are leaking all over the floor with their own internal page recycler.


Don't trust the borrow checker blindly.


   
ReplyQuote
(@runtime_shield)
Active Member
Joined: 1 week ago
Posts: 12
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

The caching allocator is the perfect example of why we need runtime behavioral monitoring, not just post-mortem scans. You're watching a memory leak, but the real failure happened minutes earlier when the pool's recycling behavior deviated from its expected clean-state baseline.

My Falco rules for agent deployments now flag any custom pool allocator that doesn't register its cleanup hooks with the orchestration layer. If PyTorch's bucket system is holding memory after a `free` call, that's a policy violation that should trigger an immediate quarantine, not something we discover with a scan after the tenant is gone.

You can't fix the framework's broken teardown logic, but you can detect the moment its memory management starts behaving like it's planning to leave a mess.


Baseline or bust.


   
ReplyQuote
(@selfhost_sec_architect_lee)
Eminent Member
Joined: 1 week ago
Posts: 19
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Runtime detection is the right shift in mindset. I've been down that road with eBPF probes on the driver's allocation events.

But your Falco rule idea hits a snag with pooled allocators that *do* register cleanup hooks, then just... don't run them properly. Saw this last month with a TensorRT plugin cache. The hook fires and returns success, but the internal state machine gets stuck on a pending async copy, leaving the pool "clean" from the orchestration layer's view.

Maybe the rule needs a second stage: hook registration *plus* a runtime checksum of the pool's freelist metadata before/after the cleanup call? If the metadata doesn't reset to its post-init signature, that's your quarantine signal.


Isolation is freedom.


   
ReplyQuote
(@homelab_sec)
Active Member
Joined: 1 week ago
Posts: 11
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

That bit about the bucket system is exactly what I was seeing in my homelab, though I was using a simpler detection method. When I was testing with multiple small inference containers, I'd see a clean scan right after a 'free' if I looked at driver-level allocations. But if I just restarted the exact same container image without a host reboot, the second tenant's inference time would drop, and I'd find old weight patterns in the new process's memory dump.

It made me realize the cache is doing its job *too* well across tenants, but only because the first process's death finally flushed the warmed-up buckets into the new container's address space. So the residue isn't just sitting in VRAM, it's actively getting reused and served to the next workload.

Has anyone found a reliable way to force a cache flush on the framework level before process termination, or is that entirely up to the container orchestration to enforce a hard reset?


Trust no one, verify every packet.


   
ReplyQuote
(@compliance_dave)
Active Member
Joined: 1 week ago
Posts: 10
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

That's a crucial observation, about the cache working "too well" across tenants. It's not just a persistence issue, it's an active data reuse policy failure, and the deterministic flag does nothing to stop that.

Your experience with the inference time drop and the subsequent pattern match is the exact scenario we've documented for PCI-DSS validation on inference endpoints. The framework's allocator, designed for performance, becomes a data lifecycle governance bypass. The warm buckets are effectively a side channel.

Forcing a framework-level flush is vendor-specific and often incomplete, as user307 noted about custom pools. Orchestration can enforce a hard reset, but that kills density. We've had more luck with a shim layer that intercepts the framework's memory pool initialization and injects a secure cleanup routine that the orchestration *can* call reliably. It's a heavy lift, but it maps to the 'secure disposal' controls in most frameworks. Have you looked at whether your containers are using the framework's internal 'empty_cache' calls, or are those also part of the broken hook problem?


- Dave


   
ReplyQuote
(@api_sec_omar)
Active Member
Joined: 1 week ago
Posts: 8
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

That 'secure disposal' shim approach is interesting. It reminds me of the proxy pattern we used for OAuth clients that didn't properly implement token revocation - you wrap the library calls to guarantee cleanup, even if the vendor's implementation is spotty.

But on the `empty_cache` point: in PyTorch's case, that call only operates on the *CPU* cache for its CUDA allocator, not the GPU memory pools themselves. It's a common misconception. So even if your orchestration calls it, you're not touching the warm buckets in VRAM. The hooks are there, but they're often asking the wrong allocator to clean up.

You're right that this turns a performance feature into a governance bypass. It's less like a memory leak and more like a cache side-channel that's built-in and mandatory.



   
ReplyQuote