Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/hipfire-arch-qwen35/map.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ _Generated by `scripts/check-crate-maps.py` from the tree — do not edit inside
| [`src/qwen35/weights.rs`](src/qwen35/weights.rs) | 1,922 | 43 | 10 |
| [`src/qwen35.rs`](src/qwen35.rs) | 63 | 7 | 0 |
| [`src/scheduler.rs`](src/scheduler.rs) | 142 | 3 | 4 |
| [`src/serve_engine.rs`](src/serve_engine.rs) | 1,273 | 8 | 2 |
| [`src/serve_engine.rs`](src/serve_engine.rs) | 1,299 | 9 | 2 |
| [`src/slot_batch.rs`](src/slot_batch.rs) | 123 | 4 | 6 |
| [`src/spec_emit.rs`](src/spec_emit.rs) | 908 | 4 | 12 |
| [`src/spec_impl.rs`](src/spec_impl.rs) | 643 | 1 | 0 |
Expand Down Expand Up @@ -78,7 +78,7 @@ _Generated by `scripts/check-crate-maps.py` from the tree — do not edit inside
- [`src/qwen35/weights.rs`](src/qwen35/weights.rs): `DeltaNetLayerWeights`, `FullAttnLayerWeights`, `ExpertWeights`, `mixed_expert_tag`, `SharedExpertWeights`, `MoeFfnWeights`, `MoeParoSidecars`, `DeltaNetMoeLayerWeights`, `FullAttnMoeLayerWeights`, `LayerWeights`, `Qwen35HfqSourceIdentity`, `capture`, +31 more
- [`src/qwen35.rs`](src/qwen35.rs): `batch`, `config`, `ep_batch`, `forward`, `load`, `prefill`, `weights`
- [`src/scheduler.rs`](src/scheduler.rs): `Scheduler`, `PendingWork`, `next_batch`
- [`src/serve_engine.rs`](src/serve_engine.rs): `EngineConfig`, `SlotEngine`, `submit`, `close`, `reset`, `stats`, `spawn`, `shutdown`
- [`src/serve_engine.rs`](src/serve_engine.rs): `EngineConfig`, `SlotEngine`, `submit`, `close`, `reset`, `stats`, `spawn`, `spawn_with_source`, `shutdown`
- [`src/slot_batch.rs`](src/slot_batch.rs): `SlotBatch`, `build`, `total_rows`, `is_empty`
- [`src/spec_emit.rs`](src/spec_emit.rs): `Qwen35Emit`, `from_ctx`, `decoded_eot`, `visible_text`
- [`src/spec_impl.rs`](src/spec_impl.rs): `Qwen35SpecScratch`
Expand All @@ -97,6 +97,6 @@ _Generated by `scripts/check-crate-maps.py` from the tree — do not edit inside

### Totals

- 29 modules · 57,391 lines · 436 public items · 189 tests · 4 examples
- 29 modules · 57,417 lines · 437 public items · 189 tests · 4 examples

<!-- crate-map:generated:end -->
42 changes: 34 additions & 8 deletions crates/hipfire-arch-qwen35/src/serve_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;

use hipfire_runtime::admission::{AdmissionController, ModelFootprint};
use hipfire_runtime::loader_api::ModelSource;
use hipfire_runtime::serve::{
send_event, Continuation, DoneReason, EngineStats, Event, SubmitRequest,
};
Expand Down Expand Up @@ -121,6 +122,18 @@ impl SlotEngine {
/// Build the rig on a new thread and start serving. Returns once the model
/// is loaded, so a caller that gets `Ok` can submit immediately.
pub fn spawn(cfg: EngineConfig) -> Result<SlotEngine, String> {
Self::spawn_inner(cfg, None)
}

/// Start serving from a source opened and admitted by the daemon.
///
/// The worker consumes this source directly; it does not reopen the model
/// path after daemon teardown.
pub fn spawn_with_source(cfg: EngineConfig, source: ModelSource) -> Result<SlotEngine, String> {
Self::spawn_inner(cfg, Some(source))
}

fn spawn_inner(cfg: EngineConfig, source: Option<ModelSource>) -> Result<SlotEngine, String> {
let (tx, rx) = channel::<EngineCommand>();
let (ready_tx, ready_rx) = channel::<Result<(), String>>();
let stats = Arc::new(Mutex::new(EngineStats::default()));
Expand All @@ -129,7 +142,11 @@ impl SlotEngine {
let handle = std::thread::Builder::new()
.name("hipfire-slot-engine".to_string())
.spawn(move || -> Result<(), String> {
match Rig::build(&cfg) {
let rig = match source {
Some(source) => Rig::build(&cfg, Some(source)),
None => Rig::build(&cfg, None),
};
match rig {
Ok(rig) => {
let _ = ready_tx.send(Ok(()));
run_loop(rig, rx, stats_thread)
Expand Down Expand Up @@ -223,7 +240,7 @@ fn dn_buffers(dn: &DeltaNetState) -> Vec<&GpuTensor> {
impl Rig {
/// Build the GPU rig.
///
/// CPU arch/tensor preflight precedes this loader — `SlotBackend::cpu_preflight`
/// CPU arch/tensor preflight precedes this loader — `SlotBackend::cpu_preflight_source`
/// opens the HFQ, validates `arch_id` 5|6, rejects vision tensors/models and
/// parses config/tokenizer before this GPU path. This function assumes that
/// preflight has passed; its `get_vram_info` + `preflight_alloc` is the
Expand All @@ -234,12 +251,18 @@ impl Rig {
/// staging, PBS, scratch, logits/out are all owned after `Qwen35Weights`.
/// On any `?`/error, all completed stages are freed, weights freed,
/// caches/graph state invalidated and pool drained, so no VRAM leaks.
fn build(cfg: &EngineConfig) -> Result<Rig, String> {
fn build(cfg: &EngineConfig, source: Option<ModelSource>) -> Result<Rig, String> {
use hipfire_runtime::hfq::HfqFile;
use hipfire_runtime::tokenizer::Tokenizer;
use rdna_compute::kv_slots::preflight_alloc;

let mut hfq = HfqFile::open(&cfg.model_path).map_err(|e| format!("open model: {e}"))?;
let mut hfq = match source {
Some(ModelSource::Hfq(hfq)) => hfq,
Some(ModelSource::Dir(_)) => {
return Err("SlotEngine requires an HFQ source".to_string())
}
None => HfqFile::open(&cfg.model_path).map_err(|e| format!("open model: {e}"))?,
};
let config = qwen35::config_from_hfq(&hfq).map_err(|e| format!("config: {e}"))?;
let tokenizer = Tokenizer::from_hfq_metadata(&hfq.metadata_json)
.map_err(|e| format!("tokenizer: {e}"))?;
Expand All @@ -251,11 +274,14 @@ impl Rig {
let per_pos_bytes = config.n_kv_heads * (config.head_dim / 32) * 34;
let prefill_chunk = cfg.prefill_chunk.max(1).min(cfg.cap_tokens.max(1));
let max_batch = (prefill_chunk * cfg.n_slots).max(cfg.n_slots);

let weight_bytes = std::fs::metadata(&cfg.model_path)
.map_err(|e| format!("stat model: {e}"))?
.len();
let cap_rounded = cfg.cap_tokens.div_ceil(128) * 128;

// Size from retained file descriptor, not a later path stat (TOCTOU).
// When the source was admitted, the file was already opened and its
// identity/size captured. Using `hfq.file_len()` describes the retained
// inode, so a delete/replace of the path after admission does not change
// the planned VRAM budget or read a different file.
let weight_bytes = hfq.file_len();
let kv_bytes = (n_fa_layers as u64)
* 2
* (cfg.n_slots as u64)
Expand Down
8 changes: 4 additions & 4 deletions crates/hipfire-daemon/map.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ _Generated by `scripts/check-crate-maps.py` from the tree — do not edit inside

| File | Lines | Public items | Tests |
|---|---:|---:|---:|
| [`src/main.rs`](src/main.rs) | 4,135 | 1 | 0 |
| [`src/slots.rs`](src/slots.rs) | 1,526 | 22 | 14 |
| [`src/main.rs`](src/main.rs) | 4,547 | 1 | 3 |
| [`src/slots.rs`](src/slots.rs) | 1,559 | 23 | 14 |

### Public API surface

- [`src/main.rs`](src/main.rs): `CaskConfig`
- [`src/slots.rs`](src/slots.rs): `SlotBackend`, `load`, `arch_str`, `dim`, `layers`, `vocab`, `active_count`, `reset`, `shutdown`, `handle_generate`, `validate_arch_id`, `is_vision_hfq`, +10 more
- [`src/slots.rs`](src/slots.rs): `SlotBackend`, `load`, `load_admitted`, `arch_str`, `dim`, `layers`, `vocab`, `active_count`, `reset`, `shutdown`, `handle_generate`, `validate_arch_id`, +11 more

### Dependencies (from `Cargo.toml`)

Expand All @@ -44,6 +44,6 @@ _Generated by `scripts/check-crate-maps.py` from the tree — do not edit inside

### Totals

- 2 modules · 5,661 lines · 23 public items · 14 tests · 0 examples
- 2 modules · 6,106 lines · 24 public items · 17 tests · 0 examples

<!-- crate-map:generated:end -->
Loading
Loading