Repository navigation
fix(cuda): allocate Qwen3.5 scratch lazily - #1879
robertosantosx2 wants to merge 15 commits into
Conversation
|
Ash, I’ve created this patch so that TensorFold continues to work on the smallest RTX cards on the market (my RTX 3050 Laptop -4GB-). This way, we make it possible for the vast majority of people to run local AI. Thanks, |
What this changesSeparates Qwen3.5 CUDA forward scratch memory from the model weights arena and allocates it lazily, according to the number of rows required by each forward pass. The INT8 prompt-projection workspace is also allocated on demand and grows geometrically. This avoids reserving the full forward scratch workspace during model loading, reducing the initial CUDA memory requirement on GPUs with limited VRAM. Related to #1827. Receipt
Checklist
The outstanding checks are explicitly marked as not yet validated. |
|
Thanks @robertosantosx2, this is the fix our numbers on #1827 pointed at. Main is frozen for the 1.2.1 release tonight, so we'll review and test it right after: a code review, and an A/B on a GB10 with Qwen3.8-27B and 35B at long contexts. That checks that the lazy allocation costs nothing on large GPUs (no stall at the first long prompt, the same speed later, the same replies drafted, resumed and concurrent). We'll post the numbers here. |
|
Thanks @robertosantosx2, this is a clean way to stop the 4,096-row reservation on small cards. Every forward grows
|
|
A second, independent read of this, @robertosantosx2, to make my three questions concrete. The lazy split itself is sound, and the Qwen3.5 host suites pass on it ( The three things to fix:
The 4B receipts are still open too: |
|
Our GB10 run on Qwen3.8-27B (main against this branch, six servers, base, new, base, new, new, base) is in. Replies are identical in all 20 rows, and steady-state speed hasn't moved:
But it does show the cost from point 3 above. Both arms' very first request is slow (decode after the first 2K prompt: 12.38 tok/s on main, 11.17 on this branch, against 24.5 later). On this branch, though, the first requests at longer prompts are slow too, where main's aren't:
That's the scratch growing on a real request and every decode graph being captured again. So allocating eagerly when the scratch fits (and lazily only when it doesn't), or growing and capturing before the first request, would remove it on large cards. With the two other fixes above, and your 4B receipts, this is ready to land. |
|
Thanks @robertosantosx2, that was quick. We read the three commits:
One more, from our lean check: the branch adds 9 problems over main (433 against 424). They're mostly new multi-line comments, plus With those two and your 4B receipts, it's ready to land. |
|
Thanks @robertosantosx2. The new head doesn't compile in our CUDA build (
|
4424405 to
b250705
Compare
|
Thanks @robertosantosx2: the compile fix is in. Two changes left:
|
|
Thanks @robertosantosx2. The 20:54 commit's geometric INT8 growth and its tests look right, and everything we cleared before still holds: the compile fix, the failed-grow rollback, eager allocation in warm-up when the scratch fits, and identical tokens. Two asks from the last review are still open:
|
Independent RTX 3050 4 GiB Validation (Ubuntu, November 3, 2026)The complete experimental report is documented in LEONES in both languages: Controlled baseline vs. PR comparisonThe experiment was conducted on the same NVIDIA RTX 3050 Laptop GPU (4,096 MiB VRAM, SM 86), using the same Separate Ubuntu worktrees and ReleaseFast binaries were used. The stable TensorFold installation and the usual llama.cpp server on port 8080 were not modified. Metric | Baseline | PR #1879 -- | -- | -- Commit | 089a18d | b880e52 Model loading | cuMemAlloc: CUDA_ERROR_OUT_OF_MEMORY | Loaded successfully in 10.4 s HTTP server | Failed to start | Started successfully Successful inference requests | 0 | 3 (HTTP 200) Decode speed | Not measurable | 54.32–56.05 tokens/s TTFT | Not measurable | 187.2–194.1 ms Maximum observed global GPU memory | Not captured during OOM | 3,227 MiB used; 542 MiB freeThe three PR inference requests generated 128, 192, and 133 tokens from prompts of 51, 56, and 59 tokens, respectively. Prefill times were 188.1, 184.1, and 183.5 ms. Baseline monitoring limitationThe baseline GPU monitoring file contains 142 lines (header + 141 samples), covering 00:54:04.339–00:56:24.418. It recorded 15 MiB used / 3,754 MiB free while idle. The monitor did not capture the failed CUDA allocation itself. Therefore, no baseline OOM-time VRAM peak or allocation profile is available. The idle measurements must not be interpreted as the memory footprint at the time of failure. Build and test statusThe following dedicated host tests and CUDA build targets completed without visible errors:
The native CUDA binary was built for Linux x86-64, SM 86, ReleaseFast. The full ConclusionThis experiment confirms a functional model-loading and memory-admission advantage on the tested RTX 3050 4 GiB configuration: the baseline fails with CUDA OOM, whereas PR #1879 successfully loads the same model and completes three real inference requests under equivalent conditions. This is not a decode-speedup comparison, since the baseline never reached inference. It is also not a direct measurement of the exact number of scratch bytes saved, because the baseline OOM-time memory peak was not captured. The LEONES reports contain the full scratch geometry and formulas, INT8 workspace calculations, memory-admission and buffer-growth analysis, commands, commits, GPU monitoring, inference metrics, evidence filenames, and experimental limitations. |
|
Thanks @robertosantosx2: both asks are answered. The pending lazy scratch is now subtracted from the room with or without a memory limit (with an unlimited-pool test), and the scratch moved to |
|
Thanks @robertosantosx2. Our GB10 A/B is in: main against this head (
So the lazy scratch costs nothing on a large card. It's in our landing queue now, with a changelog line crediting you. |
|
One thing before it can land, @robertosantosx2: main moved under this since the run. Rebasing hits conflicts in
Then an EXL3 27B on a small card still picks host-memory embeddings correctly. Could you rebase onto current main? We'll re-run the GB10 cell on the new head and land it when it's green. |
b880e52 to
8f36f46
Compare
What this changes
Separates Qwen3.5 CUDA forward scratch memory from the model weights arena and allocates it lazily, according to the number of rows required by each forward pass. The INT8 prompt-projection workspace is also allocated on demand and grows geometrically.
This avoids reserving the full forward scratch workspace during model loading, reducing the initial CUDA memory requirement on GPUs with limited VRAM.
Related to #1827.
Receipt
14f6500d1; Ubuntu 26.04.1; NVIDIA GeForce RTX 3050 Laptop GPU (4 GB VRAM, compute capability 8.6); Qwen3.5-4B MLX 4-bit checkpoint, revision32f3e8ecf65426fc3306969496342d504bfa13f3.draft: false.ttft_scomparison usingtools/bench_cache.py --require-coldhas not been performed.sm_86withReleaseFast.Checklist
draft: false.The outstanding checks are explicitly marked as not yet validated.