diff --git a/Cargo.lock b/Cargo.lock
index 3fd1ac1d2..1a2f81b08 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -83,6 +83,7 @@ dependencies = [
"bloomery-gguf",
"bloomery-gpu",
"bloomery-gpu-deepseek41",
+ "bloomery-gpu-flint",
"bloomery-gpu-glm5next",
"bloomery-gpu-mimo2",
"bloomery-gpu-minimaxm3",
@@ -206,6 +207,20 @@ dependencies = [
"cuda-host",
]
+[[package]]
+name = "bloomery-gpu-flint"
+version = "0.1.0"
+dependencies = [
+ "bloomery-gguf",
+ "bloomery-gpu",
+ "bloomery-gpu-deepseek41",
+ "bloomery-levers",
+ "bloomery-model",
+ "bloomery-models",
+ "bloomery-runtime",
+ "cuda-core",
+]
+
[[package]]
name = "bloomery-gpu-gates"
version = "0.3.9"
@@ -217,6 +232,7 @@ dependencies = [
"bloomery-gguf",
"bloomery-gpu",
"bloomery-gpu-deepseek41",
+ "bloomery-gpu-flint",
"bloomery-gpu-gemma-embedding2",
"bloomery-gpu-gemma4a",
"bloomery-gpu-glm5next",
diff --git a/Cargo.toml b/Cargo.toml
index f04237316..39373b849 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
-members = ["crates/qdot", "crates/gguf", "crates/models", "crates/model", "crates/model-hf", "crates/placement", "crates/threads", "crates/levers", "crates/verbs", "crates/gpu", "crates/gpu-deepseek41", "crates/gpu-glm5next", "crates/gpu-mimo2", "crates/gpu-solar2", "crates/gpu-minimaxm3", "crates/gpu-gemma-embedding2", "crates/gpu-gates", "crates/engram", "crates/engram-lab", "crates/sampler", "crates/tokenizer", "crates/serve", "crates/jinja", "crates/vision", "crates/gpu-vision", "crates/gpu-gemma4a", "crates/refset", "crates/runtime", "crates/app", "crates/decision", "crates/embed", "crates/hf", "crates/safetensors", "crates/os", "crates/device", "crates/metal", "crates/record", "crates/chain"]
+members = ["crates/qdot", "crates/gguf", "crates/models", "crates/model", "crates/model-hf", "crates/placement", "crates/threads", "crates/levers", "crates/verbs", "crates/gpu", "crates/gpu-deepseek41", "crates/gpu-glm5next", "crates/gpu-mimo2", "crates/gpu-solar2", "crates/gpu-minimaxm3", "crates/gpu-flint", "crates/gpu-gemma-embedding2", "crates/gpu-gates", "crates/engram", "crates/engram-lab", "crates/sampler", "crates/tokenizer", "crates/serve", "crates/jinja", "crates/vision", "crates/gpu-vision", "crates/gpu-gemma4a", "crates/refset", "crates/runtime", "crates/app", "crates/decision", "crates/embed", "crates/hf", "crates/safetensors", "crates/os", "crates/device", "crates/metal", "crates/record", "crates/chain"]
# 의도적으로 컴파일되지 않는 재현기다(MUL-7). 워크스페이스에 넣으면 --workspace가 영구히 빨갛다.
exclude = ["crates/oxide-ice-unroll", "crates/oxide-ice-unroll/variants/unroll_smoke", "fuzz", "labs/hip-native"]
diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml
index b828c35a2..38f3d0dd0 100644
--- a/crates/app/Cargo.toml
+++ b/crates/app/Cargo.toml
@@ -29,6 +29,7 @@ bloomery-gpu-mimo2 = { path = "../gpu-mimo2", optional = true }
bloomery-gpu-minimaxm3 = { path = "../gpu-minimaxm3", optional = true }
# The Solar-Open2 session (`arch::solar2`).
bloomery-gpu-solar2 = { path = "../gpu-solar2", optional = true }
+bloomery-gpu-flint = { path = "../gpu-flint", optional = true }
# The levers the sessions' own configurations carry (the plan's card budget, the host set's load
# settings). The crate has no dependency of its own, so every session reads it.
bloomery-levers = { path = "../levers" }
@@ -41,3 +42,4 @@ glm5next = ["dep:bloomery-gpu-glm5next"]
mimo2 = ["dep:bloomery-gpu-mimo2"]
minimaxm3 = ["dep:bloomery-gpu-minimaxm3"]
solar2 = ["dep:bloomery-gpu-solar2"]
+flint = ["dep:bloomery-gpu-flint"]
diff --git a/crates/app/src/arch/flint.rs b/crates/app/src/arch/flint.rs
new file mode 100644
index 000000000..5587fe585
--- /dev/null
+++ b/crates/app/src/arch/flint.rs
@@ -0,0 +1,76 @@
+//! Flint's session over its actual attention-ring body and common hybrid planner.
+
+use std::num::NonZeroUsize;
+
+use bloomery_gpu::head::Head;
+use bloomery_gpu::host::swap::Residency;
+use bloomery_gpu::weights::Weights;
+use bloomery_gpu::{Gpu, GpuError, GpuModel};
+use bloomery_gpu_flint::Body;
+use bloomery_levers::HostCfg;
+use gguf::Split;
+use model::arch::flint::place::PlanInputs;
+use model::placement::Plan;
+
+use super::{Batch, ResidentSequences};
+
+const WHAT: &str = "flint session";
+
+/// A Flint session, with resident slots backed by the body's own stores.
+pub type FlintSession = crate::Session
;
+
+impl Batch for Body {
+ type Inputs = PlanInputs;
+ type Sequences = ResidentSequences;
+
+ const WHAT: &'static str = self::WHAT;
+
+ fn read(file: &Split) -> Result {
+ PlanInputs::read(file).map_err(|e| GpuError::plan(WHAT, e))
+ }
+
+ fn configure(inputs: &mut PlanInputs, cfg: &super::HybridCfg) -> Result<(), GpuError> {
+ model::arch::flint::place::configure_group(inputs, cfg.group)
+ .map_err(|e| GpuError::plan(WHAT, e))
+ }
+
+ fn open_placed(
+ file: Split,
+ plan: &Plan<'_>,
+ inputs: &PlanInputs,
+ host: HostCfg,
+ residency: Residency,
+ slots: NonZeroUsize,
+ ) -> Result, GpuError> {
+ Body::open_placed_slots(file, plan, inputs, 0, host, residency, slots)
+ }
+
+ fn batch(
+ gpu: &Gpu,
+ w: &Weights,
+ body: &mut Body,
+ head: &mut Head,
+ ids: &[u32],
+ at: u32,
+ ) -> Result {
+ bloomery_gpu_flint::prefill::prefill(gpu, w, body, head, ids, at)
+ }
+
+ fn group(m: &mut GpuModel, g: usize) -> Result {
+ bloomery_gpu_flint::set_prefill_group(m, g)
+ }
+
+ fn keepable(m: &GpuModel, n: u32) -> u32 {
+ m.body(WHAT)
+ .map_or(0, |b| b.keepable_at(m.pos(), n.min(m.pos())))
+ }
+
+ fn cut(m: &mut GpuModel, n: u32) -> Result<(), GpuError> {
+ if n == 0 {
+ return m.reset();
+ }
+ let pos = m.pos();
+ m.body_parts(WHAT)?.2.stand_for_cut(pos);
+ m.rollback(n)
+ }
+}
diff --git a/crates/app/src/arch/minimaxm3/mod.rs b/crates/app/src/arch/minimaxm3/mod.rs
index ba4cadc72..765a0ce3d 100644
--- a/crates/app/src/arch/minimaxm3/mod.rs
+++ b/crates/app/src/arch/minimaxm3/mod.rs
@@ -9,8 +9,8 @@
//! every layer's key and value rows at those positions, every sparse layer's
//! index keys, and the last position's logits. Every store is a plane of
//! positions a later position never reads before it writes, so a cut keeps
-//! every held position. This session holds one sequence; the serve seat parks
-//! more ([`bloomery_gpu_minimaxm3`]'s `Slots`).
+//! every held position. The plan counts the configured resident sequences;
+//! [`crate::Session::add_slots`] parks them through the body's actual `Slots`.
//!
//! The session's shape is the common owner's ([`super::Batch`]): what the
//! placement plans, how the prompt runs and how a cut is kept are shared with
@@ -37,6 +37,7 @@ const WHAT: &str = "minimaxm3 session";
impl Batch for Body {
type Inputs = PlanInputs;
+ type Sequences = super::ResidentSequences;
const WHAT: &'static str = self::WHAT;
@@ -52,8 +53,9 @@ impl Batch for Body {
inputs: &PlanInputs,
host: HostCfg,
residency: Residency,
+ slots: NonZeroUsize,
) -> Result, GpuError> {
- Body::open_placed_slots(file, plan, inputs, 0, host, residency, NonZeroUsize::MIN)
+ Body::open_placed_slots(file, plan, inputs, 0, host, residency, slots)
}
fn batch(
diff --git a/crates/app/src/arch/mod.rs b/crates/app/src/arch/mod.rs
index 04b2b3bd2..bff1125c4 100644
--- a/crates/app/src/arch/mod.rs
+++ b/crates/app/src/arch/mod.rs
@@ -8,10 +8,12 @@
//! and the cut rule — and [`Batch`] holds the rest: the [`Open`] the placement
//! plans by, the [`Prompt`] the batch runs and the [`Keep`] the cut follows.
+use std::num::NonZeroUsize;
+
use bloomery_gpu::head::Head;
use bloomery_gpu::host::served::prompt_pass;
use bloomery_gpu::host::swap::Residency;
-use bloomery_gpu::model::{ChainBody, Rollback};
+use bloomery_gpu::model::{ChainBody, Rollback, Slots};
use bloomery_gpu::weights::Weights;
use bloomery_gpu::{Gpu, GpuError, GpuModel};
use bloomery_levers::HostCfg;
@@ -24,6 +26,8 @@ use crate::{Keep, Open, Prompt};
#[cfg(feature = "deepseek41")]
pub mod deepseek41;
+#[cfg(feature = "flint")]
+pub mod flint;
#[cfg(feature = "glm5next")]
pub mod glm5next;
#[cfg(feature = "mimo2")]
@@ -56,6 +60,8 @@ pub struct HybridCfg {
pub residency: Residency,
/// The batches a prompt group runs layer by layer.
pub group: usize,
+ /// The resident sequences charged by the plan before any allocation.
+ pub slots: NonZeroUsize,
}
/// The plan inputs of a hybrid body: the family's own hyperparameters under
@@ -71,6 +77,7 @@ pub trait HybridInputs {
machine: &'a Machine,
ctx: u64,
levers: &PlanLevers,
+ slots: NonZeroUsize,
what: &'static str,
) -> Result, GpuError>;
}
@@ -85,9 +92,11 @@ impl HybridInputs for PlanInputs {
machine: &'a Machine,
ctx: u64,
levers: &PlanLevers,
+ slots: NonZeroUsize,
what: &'static str,
) -> Result, GpuError> {
- PlanInputs::plan(self, machine, ctx, levers).map_err(|e| GpuError::plan(what, e))
+ self.plan_with_slots(machine, ctx, levers, slots)
+ .map_err(|e| GpuError::plan(what, e))
}
}
@@ -98,6 +107,8 @@ impl HybridInputs for PlanInputs {
pub trait Batch: ChainBody + Rollback + Sized {
/// The plan inputs the body is loaded by.
type Inputs: HybridInputs;
+ /// The sequence capability the body actually implements.
+ type Sequences: SequenceSlots;
/// The session's name in its refusals.
const WHAT: &'static str;
@@ -105,6 +116,11 @@ pub trait Batch: ChainBody + Rollback + Sized {
/// The plan inputs of `file`.
fn read(file: &Split) -> Result;
+ /// Price configuration-dependent buffers before placement; unchanged for a fixed schedule.
+ fn configure(_inputs: &mut Self::Inputs, _cfg: &HybridCfg) -> Result<(), GpuError> {
+ Ok(())
+ }
+
/// The body resident by `plan`, which `inputs` made: the layers the plan
/// puts on the card, the host tier over the routed ones.
fn open_placed(
@@ -113,6 +129,7 @@ pub trait Batch: ChainBody + Rollback + Sized {
inputs: &Self::Inputs,
host: HostCfg,
residency: Residency,
+ slots: NonZeroUsize,
) -> Result, GpuError>;
/// The prompt batch of `ids` from `at`: the head's row after the last.
@@ -154,6 +171,10 @@ impl Open for B {
B::read(file)
}
+ fn configure_inputs(inputs: &mut B::Inputs, cfg: &HybridCfg) -> Result<(), GpuError> {
+ B::configure(inputs, cfg)
+ }
+
fn layer_count(inputs: &B::Inputs) -> usize {
inputs.layers()
}
@@ -169,8 +190,9 @@ impl Open for B {
ctx: usize,
cfg: &HybridCfg,
) -> Result, GpuError> {
+ >::check(cfg.slots, B::WHAT)?;
let ctx = one_card_ctx(B::WHAT, machine, ctx)?;
- inputs.plan_at(machine, ctx, &cfg.place, B::WHAT)
+ inputs.plan_at(machine, ctx, &cfg.place, cfg.slots, B::WHAT)
}
fn open(
@@ -179,7 +201,8 @@ impl Open for B {
plan: &Plan<'_>,
cfg: &HybridCfg,
) -> Result, GpuError> {
- B::open_placed(file, plan, inputs, cfg.host, cfg.residency)
+ >::check(cfg.slots, B::WHAT)?;
+ B::open_placed(file, plan, inputs, cfg.host, cfg.residency, cfg.slots)
}
/// The configuration's group; the units it needs are made here.
@@ -222,6 +245,81 @@ impl Keep for B {
}
}
+/// The sequence operations a common hybrid seat may offer.
+pub trait SequenceSlots {
+ /// Whether several resident sequences are backed by body-owned stores.
+ const RESIDENT: bool;
+ /// Refuse an unsupported count before planning or allocation.
+ fn check(slots: NonZeroUsize, what: &'static str) -> Result<(), GpuError>;
+ /// Park the planned sequences through the session's actual slot operations.
+ fn add(s: &mut crate::Session, slots: NonZeroUsize) -> Result<(), crate::SessionError>;
+ /// The number of resident sequences the session currently holds.
+ fn count(s: &crate::Session) -> usize;
+ /// Select the actual sequence every following call acts on.
+ fn select(s: &mut crate::Session, slot: usize) -> Result<(), crate::SessionError>;
+}
+
+/// A body that holds one sequence; other slots are refused, not simulated.
+pub struct SingleSequence;
+
+impl SequenceSlots for SingleSequence {
+ const RESIDENT: bool = false;
+
+ fn check(slots: NonZeroUsize, what: &'static str) -> Result<(), GpuError> {
+ if slots.get() != 1 {
+ return Err(GpuError::Shape {
+ what,
+ detail: format!(
+ "the body holds one sequence, not {} resident slots",
+ slots.get()
+ ),
+ });
+ }
+ Ok(())
+ }
+
+ fn add(_s: &mut crate::Session, slots: NonZeroUsize) -> Result<(), crate::SessionError> {
+ >::check(slots, "single-sequence session").map_err(Into::into)
+ }
+
+ fn count(_s: &crate::Session) -> usize {
+ 1
+ }
+
+ fn select(_s: &mut crate::Session, slot: usize) -> Result<(), crate::SessionError> {
+ if slot == 0 {
+ Ok(())
+ } else {
+ Err(crate::SessionError::Refused(format!(
+ "select_slot {slot}: the body holds one sequence"
+ )))
+ }
+ }
+}
+
+/// Resident sequences backed by the body's actual parked stores.
+pub struct ResidentSequences;
+
+impl> SequenceSlots for ResidentSequences {
+ const RESIDENT: bool = true;
+
+ fn check(_slots: NonZeroUsize, _what: &'static str) -> Result<(), GpuError> {
+ Ok(())
+ }
+
+ fn add(s: &mut crate::Session, slots: NonZeroUsize) -> Result<(), crate::SessionError> {
+ s.add_slots(slots.get())
+ }
+
+ fn count(s: &crate::Session) -> usize {
+ s.slots()
+ }
+
+ fn select(s: &mut crate::Session, slot: usize) -> Result<(), crate::SessionError> {
+ s.select_slot(slot)
+ }
+}
+
/// `ctx` positions as a plan takes them, for a body whose chain runs on one
/// card: refused by name (`what` the body's session) when `machine` puts the
/// layers on any other number of cards or `ctx` passes u64.
diff --git a/crates/app/src/arch/musespark/mod.rs b/crates/app/src/arch/musespark/mod.rs
index 91194dd31..a66a0ca01 100644
--- a/crates/app/src/arch/musespark/mod.rs
+++ b/crates/app/src/arch/musespark/mod.rs
@@ -47,8 +47,9 @@ impl Open for Body {
ctx: usize,
cfg: &HybridCfg,
) -> Result, GpuError> {
+ >::check(cfg.slots, WHAT)?;
let ctx = one_card_ctx(WHAT, machine, ctx)?;
- inputs.plan_at(machine, ctx, &cfg.place, WHAT)
+ inputs.plan_at(machine, ctx, &cfg.place, cfg.slots, WHAT)
}
/// The model resident by `plan`, on the plan's one card, under the
@@ -60,6 +61,7 @@ impl Open for Body {
plan: &Plan<'_>,
cfg: &HybridCfg,
) -> Result, GpuError> {
+ >::check(cfg.slots, WHAT)?;
GpuModel::::open_placed_with(file, plan, 0, cfg.host, cfg.residency)
}
diff --git a/crates/app/src/arch/solar2/mod.rs b/crates/app/src/arch/solar2/mod.rs
index 482533bfe..56eac5094 100644
--- a/crates/app/src/arch/solar2/mod.rs
+++ b/crates/app/src/arch/solar2/mod.rs
@@ -40,6 +40,7 @@ const WHAT: &str = "solar2 session";
impl Batch for Body {
type Inputs = PlanInputs;
+ type Sequences = super::SingleSequence;
const WHAT: &'static str = self::WHAT;
@@ -53,8 +54,9 @@ impl Batch for Body {
inputs: &PlanInputs,
host: HostCfg,
residency: Residency,
+ slots: NonZeroUsize,
) -> Result, GpuError> {
- Body::open_placed_slots(file, plan, inputs, 0, host, residency, NonZeroUsize::MIN)
+ Body::open_placed_slots(file, plan, inputs, 0, host, residency, slots)
}
fn batch(
diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs
index 5eee1b184..df3396b79 100644
--- a/crates/app/src/lib.rs
+++ b/crates/app/src/lib.rs
@@ -161,6 +161,12 @@ pub trait Open: ChainBody + Sized {
/// Read `file`'s headers.
fn inputs(file: &Split) -> Result;
+ /// Apply load settings that change planned buffers, before the plan borrows its inputs.
+ /// Bodies with no configuration-dependent buffers keep the header-derived inputs.
+ fn configure_inputs(_inputs: &mut Self::Inputs, _cfg: &Self::Cfg) -> Result<(), GpuError> {
+ Ok(())
+ }
+
/// The layer count a placement's machine is laid out for.
fn layer_count(inputs: &Self::Inputs) -> usize;
@@ -303,7 +309,8 @@ impl Loaded {
log: &mut impl OpenLog,
edit: impl FnOnce(&B::Inputs, &mut Plan<'_>) -> Result<(), SessionError>,
) -> Result