The Forceful Liberation of GPU Memory: A Single Command That Unblocked an Entire Evaluation Pipeline
Introduction
In the course of debugging a speculative decoding drafter model, a single bash command—issued at message index 8977—represents a pivotal moment where careful planning met the messy reality of GPU memory management. The message is deceptively simple:
ssh -o ConnectTimeout=5 root@10.1.230.172 'kill $(fuser /dev/nvidia* 2>/dev/null | tr -s " ") 2>/dev/null; sleep 5; nvidia-smi --query-gpu=index,memory.used --format=csv,noheader' 2>&1
With the triumphant output:
0, 0 MiB
1, 0 MiB
Both GPUs now show zero memory usage. But the path to this clean state was anything but straightforward. This article examines why this command was necessary, the reasoning that led to it, the assumptions that broke along the way, and the critical role it played in unblocking a complex evaluation pipeline for a DFlash drafter model.
Context: The Evaluation Infrastructure
To understand message 8977, one must first understand the broader investigation unfolding across the preceding messages. The assistant was building an evaluation harness to compare a custom DFlash drafter model (trained on the kpro6 cluster with 8 Blackwell GPUs) against the official z-lab/Qwen3.6-27B-DFlash reference model. The evaluation ran on a separate server, CT129, which hosted a production SGLang service serving the Qwen3.6-27B target model across two NVIDIA GPUs.
The central challenge was a numerical mismatch between how hidden states were computed during training versus evaluation. During training on kpro6, the target model used the fla (flash-linear-attention) library's CUDA kernels for the linear attention layers present in Qwen3.5's architecture. During evaluation on CT129, the target model initially ran on CPU (or with a CPU-only PyTorch installation), which forced PyTorch to fall back to a pure-torch implementation of linear attention. The assistant hypothesized—correctly, as it would later confirm—that these two paths produced numerically different hidden states, and that the drafter, trained on fla-generated states, would produce garbage when fed torch-fallback states.
The Plan: Stop SGLang, Extract on GPU, Restart
The assistant devised a clean three-phase plan:
- Cache completions from the running SGLang service while it was still live (msg 8974)
- Stop SGLang to free the GPUs, then extract hidden states on GPU using
flakernels - Restart SGLang and run the drafter evaluation against the cached GPU-extracted states Phase 1 succeeded: ten coding prompts (fizzbuzz, binary_search, json_parser, etc.) were sent to SGLang and their completions were saved to disk. Phase 2 began with a standard systemd service stop (msg 8975):
systemctl stop sglang-qwen && echo "SGLang stopped" && sleep 3 && nvidia-smi --query-gpu=index,memory.used --format=csv,noheader
The service reported as stopped. But nvidia-smi told a different story:
0, 47179 MiB
1, 46237 MiB
Both GPUs still showed approximately 47 GB of memory in use—nearly the full allocation for the 52 GB Qwen3.6-27B model. The service had stopped, but the GPU memory had not been released.
The Breakdown: When systemctl stop Isn't Enough
This is where the assistant's assumptions collided with reality. The expectation was that stopping the SGLang systemd service would cause the SGLang process to terminate cleanly, which would in turn release its CUDA context and free the GPU memory. This assumption is reasonable: most well-behaved GPU applications release their CUDA contexts upon process termination, and nvidia-smi should reflect the freed memory.
But several factors can prevent this from happening:
- Zombie processes: The main SGLang process might have terminated, but child processes or worker threads could still hold references to CUDA contexts.
- CUDA context persistence: The NVIDIA driver may not immediately release a CUDA context if processes are in certain states (e.g., waiting on I/O, stuck in kernel execution).
- Service management quirks:
systemctl stopsends SIGTERM, but if the service doesn't handle it properly, or if there are multiple processes in the service's cgroup, some may survive. - GPU compute mode: If processes are mid-kernel execution, the driver may hold memory until the kernel completes or is forcibly reset. The assistant waited an additional 5 seconds (msg 8976) and checked again:
0, 47179 MiB
1, 46237 MiB
No change. The memory was stubbornly held.
Message 8977: The Forceful Solution
At this point, the assistant escalated. Message 8977 executes a command that does not politely ask processes to exit—it finds them and kills them:
kill $(fuser /dev/nvidia* 2>/dev/null | tr -s " ")
Let's dissect this command:
fuser /dev/nvidia*: Thefusercommand identifies which process IDs (PIDs) have any of the NVIDIA device files (/dev/nvidia0,/dev/nvidia1,/dev/nvidiactl,/dev/nvidia-modeset, etc.) open. Any process interacting with the GPU—whether through CUDA, NVIDIA ML, or direct device access—will have at least one of these files open.2>/dev/null: Suppresses error messages if no processes are found or if device files don't exist.tr -s " ": Collapses multiple spaces into single spaces, producing a clean space-separated list of PIDs.kill $(...): Sends SIGTERM to all identified PIDs. After killing the processes, the command sleeps 5 seconds (giving the driver time to release contexts) and then queries memory usage withnvidia-smi. The output is unequivocal:
0, 0 MiB
1, 0 MiB
Both GPUs are completely free. The forceful kill worked where the polite service stop failed.
Why This Matters: The Hidden State Extraction
The liberation of GPU memory was not an end in itself—it was the prerequisite for the next critical step. Immediately after message 8977, the assistant launched the hidden state extraction script on GPU (msg 8978):
source /root/eval-venv/bin/activate && python3 /root/eval/extract_hidden_states.py
This script loaded the full Qwen3.6-27B target model onto GPU using AutoModelForCausalLM with the fla library installed, ensuring that the linear attention layers would use the exact same CUDA kernels that were used during training. The extracted hidden states—now numerically identical to what the drafter was trained on—were saved to disk.
Only then could the evaluation produce meaningful results. When the assistant later ran the drafter evaluation against these GPU-extracted states, the performance numbers jumped dramatically, revealing a 4× gap compared to the z-lab reference model—a gap that was invisible when using CPU-extracted states because the drafter was receiving numerically garbled inputs.
Assumptions and Their Failures
Message 8977 exposes several assumptions, some of which proved incorrect:
- "systemctl stop will release GPU memory" — This was the primary failed assumption. A clean service stop should, in theory, terminate all child processes and release resources. In practice, GPU memory can persist due to orphaned processes, driver caching, or incomplete cleanup. The assistant correctly diagnosed this failure and escalated.
- "Waiting longer will help" — The assistant waited 3 seconds after the service stop, then another 5 seconds. Neither wait freed the memory. This ruled out a simple timing issue and confirmed that processes were genuinely still alive.
- "Killing processes holding /dev/nvidia is safe" — This is a reasonable assumption in this context. The only GPU workload on CT129 was the SGLang service, which had already been stopped. Any remaining processes holding NVIDIA device files were either orphaned children or stuck workers. Killing them would not affect unrelated workloads. However, in a multi-tenant environment, this approach could be dangerous—
fuser /dev/nvidia*would catch any* process using the GPU, including unrelated training jobs or other inference services. - "GPU memory will immediately show as free after kill" — The 5-second sleep after the kill command suggests the assistant anticipated that the driver might need time to release contexts. This was a prudent assumption, and it proved correct.
Input Knowledge Required
To understand message 8977, one needs:
- Linux process management: Understanding of
fuser, process signals, and device files - NVIDIA GPU architecture: Knowledge that
/dev/nvidia*device files are the interface between userspace processes and the GPU driver, and that holding these files open implies GPU resource ownership - CUDA context lifecycle: Awareness that GPU memory is tied to CUDA contexts, which are created per-process and released on process exit
- SGLang deployment patterns: Familiarity with how SGLang manages GPU memory and the fact that its systemd service may not cleanly release resources
- The broader debugging context: Understanding that the hidden state numerical mismatch was the core problem, and that GPU-extracted states were the solution
Output Knowledge Created
Message 8977 produced two forms of knowledge:
Immediate output: Confirmation that both GPUs were at 0 MiB usage, validating that the forceful kill succeeded and that the GPUs were ready for the extraction script.
Downstream knowledge: The successful hidden state extraction that followed (msg 8978) would later enable the assistant to compare drafter performance against the z-lab model, leading to the discovery of three critical training bugs (noise corrupting target logits, fc shortcut including the target layer, and loss function mismatch). Without the GPU-extracted states, those bugs would have remained hidden behind the noise of the torch-fla numerical mismatch.
The Thinking Process
The assistant's reasoning leading to message 8977 is visible across the preceding messages. In msg 8962, the assistant explicitly lays out the hypothesis:
"The issue must be the fla vs torch fallback for linear attention. CT200 training uses fla; CT129 CPU can't use it. 4 of 5 target layers are linear attention."
The plan is then articulated: install fla on CT129, stop SGLang, extract on GPU, restart. The assistant methodically installs fla (msg 8964), swaps CPU torch for GPU torch (msg 8967), and verifies CUDA availability (msg 8968). The extraction script is written (msg 8969) and the eval harness is updated to support cached hidden states (msg 8970-8971).
When the service stop fails to free memory (msg 8975-8976), the assistant does not panic or restart the server. Instead, it reaches for a surgical tool: fuser to identify exactly which processes are holding GPU resources, and kill to terminate them. This is a mark of experience—knowing that systemd service management can leave orphaned processes, and knowing the right diagnostic command to find them.
The assistant also demonstrates good operational hygiene: the 2>/dev/null redirects suppress noise from fuser (which might complain if no processes match), and the 5-second sleep after the kill gives the driver time to release memory before the verification query. The command is structured as a single SSH invocation, minimizing latency and ensuring atomicity of the operation.
Conclusion
Message 8977 is a masterclass in pragmatic GPU management. It is a single command that encapsulates the gap between how systems should behave and how they actually behave. The assistant planned carefully, executed methodically, and when the plan hit a wall, escalated decisively with the right tool for the job. The 0 MiB output on both GPUs was not just a number—it was the key that unlocked the entire evaluation pipeline, enabling the discovery of fundamental training bugs that would reshape the DFlash drafter architecture.