mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-10 03:41:36 -10:00
103 lines
7.3 KiB
C++
103 lines
7.3 KiB
C++
// include/strata/program/split_walk.hpp - the spill walk of a layer split: what each card gives up, in which order, when it is short of VRAM.
|
|
//
|
|
// The 0.2.10 walk (draft layer dropped, a loan that keeps 16 slots, the prompt path's own small buffers, the output head in pinned host memory, a smaller KV window)
|
|
// ran for ONE card. A layer split went straight to "expert cache: no room" or "the GPU has too little free VRAM for the prompt path" (two 8 GB cards, 128K: 6 of 6
|
|
// starts failed). The arithmetic of the layer split's version is here, pure, so a host test pins it (tests/program/split_walk_test.cpp); src/program/generate.cpp
|
|
// reads the cards and prints the lines.
|
|
//
|
|
// The steps, mildest first, each only when the one before did not leave the card enough:
|
|
// 1. trim: every card loads only the dense weights of ITS layers (--trim-stage-weights). Without it each card holds the whole 3.5 GiB set (IQ3_S). Bit-exact in
|
|
// itself; the cache simply gets the room. This is the step that matters on 8 GB cards.
|
|
// 2. the KV window down (32768 -> 20480 -> 8192 -> 4096 cells a layer; the rest streams from RAM), when no placement can start the prompt path.
|
|
// 3. the drafter dropped on the card that holds it (draft layer or DFlash2's pack and buffers): decode is slower, the start works.
|
|
// 4. the loan keeps 16 slots, and the prompt path's own small buffers (256..1024-token chunks), instead of 128 and 1024..8192.
|
|
// 5. the output head in pinned host memory, on the card that holds it.
|
|
// 6. fewer cards: a card with too little free memory to hold a share is left out of the split; one card left is the single-card start (with its own walk).
|
|
// The context and the number of requests are never touched. A big card never reaches any of them: trim_needed() is false when the card has the room.
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
|
|
namespace strata::program::split_walk {
|
|
|
|
constexpr int64_t kContextMib = 1550; ///< CUDA context, streams, scratch and tables of a card that holds the whole dense set (8 GB hog, IQ3_S: 1559 MiB measured)
|
|
constexpr int64_t kWorkingCacheSlots = 263; ///< a cache the engine calls working (generate.cpp #496)
|
|
constexpr int64_t kPromptHeadroomMib = 1100; ///< a 512-token chunk's own buffers (570 MiB) + the serve check's 512 MiB headroom
|
|
constexpr int64_t kMinCardMib = 3072; ///< under this (free, after its context) a card cannot hold even a trimmed half share of the dense set (1.75 GiB) beside a session and a cache; above it the other steps (drafter, loan, head) still have to make it fit
|
|
constexpr int64_t kStageLateAllocMib = 256; ///< what a stage allocates after its cache that nothing prices (the single card's kLateAllocMib, #4474)
|
|
|
|
/// What one card needs, from the model and the start (all MiB).
|
|
struct Model {
|
|
int64_t dense_mib = 0; ///< the whole dense set a card loads without the trim (pack arena + native projections)
|
|
int64_t session_mib = 0; ///< the session of one card's layers at the asked context and KV window
|
|
int64_t head_mib = 0; ///< the output head (the last stage only)
|
|
int64_t drafter_mib = 0; ///< the draft layer or DFlash2 (the stage that carries it only)
|
|
int64_t slot_mib = 3; ///< the largest expert blob, rounded up
|
|
int64_t reserve_mib = 796; ///< what the plan keeps free on a later stage (700 + the verify windows)
|
|
};
|
|
|
|
/// MiB a card needs beyond its free memory figure when it loads the whole dense set: the weights, the context, its session, (the last stage) the head and the drafter,
|
|
/// the reserve, a working cache and the prompt path's headroom.
|
|
inline int64_t untrimmed_need_mib(const Model& m, bool carries_head_and_drafter) {
|
|
return m.dense_mib + kContextMib + m.session_mib + (carries_head_and_drafter ? m.head_mib + m.drafter_mib : 0) + m.reserve_mib +
|
|
kWorkingCacheSlots * m.slot_mib + kPromptHeadroomMib;
|
|
}
|
|
|
|
/// Is this card too short to start without the trim? (free_mib: its free memory before it loaded anything.)
|
|
inline bool trim_needed(int64_t free_mib, const Model& m, bool carries_head_and_drafter) {
|
|
return free_mib > 0 && free_mib < untrimmed_need_mib(m, carries_head_and_drafter);
|
|
}
|
|
|
|
/// May this card take part in a layer split at all?
|
|
inline bool card_usable(int64_t free_mib) { return free_mib >= kMinCardMib; }
|
|
|
|
/// The single card's rule for dropping the draft layer, unchanged (generate.cpp, "SHORT OF VRAM: THE DRAFT LAYER IS THE LAST THING TO GO"): the layer, the head, a working
|
|
/// cache beside the prompt buffers (214 slots, 543 MiB) and what is allocated after it. Used for the card that holds the drafter in a layer split too.
|
|
inline uint64_t draft_layer_need_bytes(uint64_t layer_bytes, uint64_t head_bytes) {
|
|
return layer_bytes + head_bytes + (220ull << 20) + (1550ull << 20);
|
|
}
|
|
inline bool draft_layer_short(uint64_t free_bytes, uint64_t layer_bytes, uint64_t head_bytes) {
|
|
return free_bytes > 0 && free_bytes < draft_layer_need_bytes(layer_bytes, head_bytes);
|
|
}
|
|
|
|
/// DFlash2 on a stage: its pack and its K/V ring and buffers (0.25 GiB), its draft head, and the same working cache and late allocations.
|
|
inline uint64_t dflash2_need_bytes(uint64_t pack_bytes, uint64_t head_bytes) {
|
|
return pack_bytes + (250ull << 20) + head_bytes + (220ull << 20) + (1550ull << 20);
|
|
}
|
|
|
|
/// The next KV window down from `current` (0: the whole context in VRAM) for a context of `ctx` cells, or 0 when it is already the smallest (`min_cells`).
|
|
inline int64_t next_kv_window(int64_t current, int64_t ctx, int64_t min_cells) {
|
|
constexpr int64_t steps[] = {20480, 8192, 4096};
|
|
const int64_t cur = current > 0 ? current : ctx;
|
|
for (const int64_t s : steps)
|
|
if (s < cur && s < ctx && s >= min_cells) return s;
|
|
return 0;
|
|
}
|
|
|
|
/// Can `n` cards hold the model with every card trimmed? `room_mib[i]` is a card's free memory less its fixed costs (context, reserve, late allocations; NOT the head and the
|
|
/// drafter), `per_layer_mib` what a layer costs on a card (its dense weights and its session), `relief_mib` what the later steps can still give back (the drafter and the head
|
|
/// moved off). False means even the whole walk does not fit: the split should run on fewer cards.
|
|
inline bool split_feasible(const int64_t* room_mib, int n, int64_t layers, int64_t per_layer_mib, int64_t relief_mib) {
|
|
int64_t total = relief_mib;
|
|
for (int i = 0; i < n; ++i) total += room_mib[i];
|
|
return total >= per_layer_mib * layers;
|
|
}
|
|
|
|
/// The layer share of each of `n` cards, proportional to the room each has for experts and sessions once its fixed costs are paid (`room_mib[i]` = its free memory less the
|
|
/// context, the reserve and, for the card that carries them, the head and the drafter): that gives every layer the same cache bytes on every card. At least 2 layers a card,
|
|
/// summing to `layers`. Used when the walk chooses the split before any card has loaded anything, so CUDA0's weights can be trimmed too.
|
|
inline void proportional_layers(const int64_t* room_mib, int n, int64_t layers, int64_t* out) {
|
|
int64_t total = 0;
|
|
for (int i = 0; i < n; ++i) total += std::max<int64_t>(room_mib[i], 1);
|
|
int64_t given = 0;
|
|
for (int i = 0; i + 1 < n; ++i) {
|
|
const int64_t w = std::max<int64_t>(room_mib[i], 1);
|
|
out[i] = std::max<int64_t>(2, (layers * w + total / 2) / total);
|
|
given += out[i];
|
|
}
|
|
out[n - 1] = layers - given;
|
|
}
|
|
|
|
} // namespace strata::program::split_walk
|