#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
use rayon::prelude::*;
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
use std::collections::VecDeque;
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
use std::path::Path;
pub const MOE_EXPERT_CACHE_SLOTS_ENV: &str = "LATTICE_MOE_EXPERT_CACHE_SLOTS";
#[derive(Debug, Clone, Copy, Default)]
pub struct MoeExpertCacheConfig {
pub num_slots: Option<usize>,
}
impl MoeExpertCacheConfig {
pub fn from_env() -> Result<Self, String> {
match std::env::var(MOE_EXPERT_CACHE_SLOTS_ENV) {
Err(std::env::VarError::NotPresent) => Ok(Self { num_slots: None }),
Err(std::env::VarError::NotUnicode(raw)) => Err(format!(
"{MOE_EXPERT_CACHE_SLOTS_ENV}={raw:?} is not valid UTF-8 — refusing to fall \
back to the device-budget default silently; unset the variable to use the \
default"
)),
Ok(raw) => {
let trimmed = raw.trim();
match trimmed.parse::<usize>() {
Ok(0) => Err(format!(
"{MOE_EXPERT_CACHE_SLOTS_ENV}=\"{raw}\" is 0 — an expert-cache needs at \
least 1 slot; unset the variable to use the device-budget default"
)),
Ok(n) => Ok(Self { num_slots: Some(n) }),
Err(e) => Err(format!(
"{MOE_EXPERT_CACHE_SLOTS_ENV}=\"{raw}\" is not a valid positive integer \
({e}) — refusing to fall back to the device-budget default silently; \
unset the variable to use the default"
)),
}
}
}
}
}
pub fn moe_expert_cache_num_slots(
cfg: &MoeExpertCacheConfig,
num_experts: usize,
top_k: usize,
per_expert_bytes: u64,
num_moe_layers: usize,
recommended_max_working_set_size: u64,
) -> Result<usize, String> {
if num_experts == 0 {
return Err("moe_expert_cache_num_slots: num_experts must be > 0".to_string());
}
if top_k == 0 {
return Err("moe_expert_cache_num_slots: top_k must be > 0".to_string());
}
if top_k > num_experts {
return Err(format!(
"moe_expert_cache_num_slots: top_k ({top_k}) exceeds num_experts ({num_experts})"
));
}
let num_moe_layers = num_moe_layers.max(1) as u64;
let threshold = (recommended_max_working_set_size as f64 * 0.85) as u64;
let per_layer_budget = threshold / num_moe_layers;
let min_required_bytes = per_expert_bytes.saturating_mul(top_k as u64);
if min_required_bytes > per_layer_budget {
return Err(format!(
"moe_expert_cache_num_slots: even the minimum top_k={top_k} concurrently-resident \
expert slots need {min_required_bytes} bytes/layer, which exceeds this device's \
per-layer MoE budget of {per_layer_budget} bytes (0.85 × \
recommendedMaxWorkingSetSize={recommended_max_working_set_size} / \
{num_moe_layers} MoE layers). This checkpoint's expert shape cannot fit even the \
lazy dequant-on-demand cache on this device."
));
}
let affordable = if per_expert_bytes == 0 {
num_experts
} else {
(per_layer_budget / per_expert_bytes) as usize
};
let budget_max_slots = affordable.clamp(top_k, num_experts);
if let Some(n) = cfg.num_slots {
return Ok(n.clamp(top_k, budget_max_slots));
}
Ok(budget_max_slots)
}
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
struct ExpertByteTable {
mmap: memmap2::Mmap,
payload_offset: u64,
per_expert_elems: usize,
per_expert_bytes: usize,
num_experts: usize,
}
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
impl ExpertByteTable {
fn open(path: &Path, expected_shape: &[usize]) -> Result<Self, String> {
use crate::weights::q4_weights::{Q4BlockCheck, open_and_mmap_q4_file};
let (header, mmap, _) = open_and_mmap_q4_file(
path,
Some(expected_shape),
Q4BlockCheck::InCallerTraversal {
traversal: "ExpertByteTable::dequant_expert_f16 per-expert loop",
},
)?;
let num_experts = expected_shape[0];
if num_experts == 0 {
return Err(format!(
"{}: expert-major shape {expected_shape:?} has zero experts",
path.display()
));
}
if !header.original_len.is_multiple_of(num_experts) {
return Err(format!(
"{}: original_len {} is not evenly divisible by num_experts {} — the \
expert-major outer-dimension slicing invariant this cache relies on does not \
hold for this file",
path.display(),
header.original_len,
num_experts
));
}
let per_expert_elems = header.original_len / num_experts;
if !per_expert_elems.is_multiple_of(crate::weights::q4_weights::Q4_BLOCK_WEIGHTS) {
return Err(format!(
"{}: per-expert element count {per_expert_elems} is not a multiple of the Q4 \
block size (32) — an expert's byte range would not be block-aligned within \
the fused per-layer tensor, which this cache's contiguous-slice addressing \
requires. This model's moe_intermediate_size/hidden_size combination is \
incompatible with the dequant-on-demand expert cache without a container \
format change (out of scope for Stage 1 — see PLAN.md §0).",
path.display()
));
}
let per_expert_blocks = per_expert_elems / crate::weights::q4_weights::Q4_BLOCK_WEIGHTS;
let per_expert_bytes = per_expert_blocks * 20;
Ok(Self {
mmap,
payload_offset: header.payload_offset,
per_expert_elems,
per_expert_bytes,
num_experts,
})
}
fn expert_bytes(&self, expert_id: usize) -> Result<&[u8], String> {
if expert_id >= self.num_experts {
return Err(format!(
"expert_id {expert_id} out of range (num_experts={})",
self.num_experts
));
}
let start = self.payload_offset as usize + expert_id * self.per_expert_bytes;
let end = start + self.per_expert_bytes;
self.mmap.get(start..end).ok_or_else(|| {
format!(
"expert {expert_id} byte range {start}..{end} beyond mapped length {}",
self.mmap.len()
)
})
}
fn dequant_expert_f16(&self, expert_id: usize) -> Result<Vec<u16>, String> {
use crate::weights::q4_weights::{
q4_f16_to_f32, q4_f32_to_f16, validate_q4_block_metadata,
};
let bytes = self.expert_bytes(expert_id)?;
let tensor_name = format!("moe expert {expert_id}");
let mut out: Vec<u16> = Vec::with_capacity(self.per_expert_elems);
for (index, chunk) in bytes.chunks_exact(20).enumerate() {
let scale_bits = u16::from_ne_bytes([chunk[0], chunk[1]]);
let bias_bits = u16::from_ne_bytes([chunk[2], chunk[3]]);
validate_q4_block_metadata(
"moe expert cache",
&tensor_name,
index,
scale_bits,
bias_bits,
)
.map_err(|e| e.to_string())?;
let scale = q4_f16_to_f32(scale_bits);
let bias = q4_f16_to_f32(bias_bits);
for b in 0..16 {
let byte_val = chunk[4 + b];
out.push(q4_f32_to_f16((byte_val & 0x0f) as f32 * scale + bias));
out.push(q4_f32_to_f16((byte_val >> 4) as f32 * scale + bias));
}
}
out.truncate(self.per_expert_elems);
Ok(out)
}
}
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
pub(crate) struct PrefetchTask {
pub(crate) slot: usize,
pub(crate) expert_id: usize,
}
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
type PrefetchDequantResult = (usize, Vec<u16>);
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
type PrefetchDequantResults = Vec<PrefetchDequantResult>;
#[cfg(all(test, target_os = "macos", feature = "metal-gpu"))]
pub(crate) struct PrefetchOrderingGate {
pub(crate) started_tx: std::sync::mpsc::Sender<()>,
pub(crate) release_rx: std::sync::mpsc::Receiver<()>,
}
#[cfg(all(test, target_os = "macos", feature = "metal-gpu"))]
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct ExpertSlotCacheSnapshot {
pub(crate) slot_owner: Vec<Option<usize>>,
pub(crate) expert_to_slot: Vec<(usize, usize)>,
pub(crate) slot_touched: Vec<bool>,
pub(crate) slot_ready: Vec<bool>,
pub(crate) lru: Vec<usize>,
pub(crate) hit_miss_eviction: (usize, usize, usize),
}
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
pub(crate) struct ExpertSlotCache {
table: ExpertByteTable,
slot_elems: usize,
slots: Vec<metal::Buffer>,
slot_owner: Vec<Option<usize>>,
slot_touched: Vec<bool>,
slot_ready: Vec<bool>,
lru: VecDeque<usize>,
expert_to_slot: std::collections::HashMap<usize, usize>,
label: String,
#[cfg(test)]
hit_count: usize,
#[cfg(test)]
miss_count: usize,
#[cfg(test)]
eviction_count: usize,
}
#[cfg(all(target_os = "macos", feature = "metal-gpu"))]
impl ExpertSlotCache {
pub(crate) fn new(
device: &metal::Device,
path: &Path,
expected_shape: &[usize],
num_slots: usize,
label: &str,
) -> Result<Self, String> {
if num_slots == 0 {
return Err(format!(
"{label}: ExpertSlotCache requires at least 1 slot (got 0)"
));
}
let table = ExpertByteTable::open(path, expected_shape)?;
let slot_elems = table.per_expert_elems;
let byte_len = (slot_elems * std::mem::size_of::<u16>()) as u64;
let slots: Vec<metal::Buffer> = (0..num_slots)
.map(|i| {
let buf = device.new_buffer(byte_len, metal::MTLResourceOptions::StorageModeShared);
buf.set_label(&format!("{label}.slot{i}"));
buf
})
.collect();
Ok(Self {
table,
slot_elems,
slots,
slot_owner: vec![None; num_slots],
slot_touched: vec![false; num_slots],
slot_ready: vec![false; num_slots],
lru: (0..num_slots).collect(),
expert_to_slot: std::collections::HashMap::with_capacity(num_slots),
label: label.to_string(),
#[cfg(test)]
hit_count: 0,
#[cfg(test)]
miss_count: 0,
#[cfg(test)]
eviction_count: 0,
})
}
#[cfg(test)]
pub(crate) fn num_slots(&self) -> usize {
self.slots.len()
}
#[cfg(test)]
pub(crate) fn debug_snapshot(&self) -> ExpertSlotCacheSnapshot {
let mut expert_to_slot: Vec<(usize, usize)> =
self.expert_to_slot.iter().map(|(&e, &s)| (e, s)).collect();
expert_to_slot.sort_unstable();
ExpertSlotCacheSnapshot {
slot_owner: self.slot_owner.clone(),
expert_to_slot,
slot_touched: self.slot_touched.clone(),
slot_ready: self.slot_ready.clone(),
lru: self.lru.iter().copied().collect(),
hit_miss_eviction: (self.hit_count, self.miss_count, self.eviction_count),
}
}
#[cfg(test)]
pub(crate) fn slot_bits(&self, slot: usize) -> Vec<u16> {
unsafe {
let ptr = self.slots[slot].contents() as *const u16;
std::slice::from_raw_parts(ptr, self.slot_elems).to_vec()
}
}
#[cfg(test)]
pub(crate) fn hit_miss_eviction_counts(&self) -> (usize, usize, usize) {
(self.hit_count, self.miss_count, self.eviction_count)
}
pub(crate) fn begin_token(&mut self) {
self.slot_touched.iter_mut().for_each(|t| *t = false);
}
#[cfg(test)]
pub(crate) fn resolve(&mut self, expert_id: usize) -> &metal::Buffer {
if let Some(&slot) = self.expert_to_slot.get(&expert_id) {
if self.slot_ready[slot] {
#[cfg(test)]
{
self.hit_count += 1;
}
self.touch(slot);
return &self.slots[slot];
}
#[cfg(test)]
{
self.miss_count += 1;
}
self.load_into(slot, expert_id);
return &self.slots[slot];
}
#[cfg(test)]
{
self.miss_count += 1;
}
let slot = self.pick_eviction_slot();
self.load_into(slot, expert_id);
&self.slots[slot]
}
pub(crate) fn get_prefetched(&self, expert_id: usize) -> &metal::Buffer {
let slot = self.expert_to_slot.get(&expert_id).unwrap_or_else(|| {
panic!(
"{}: get_prefetched({expert_id}) called without a prior prefetch_experts() (or \
plan_prefetch()) covering this expert this token — caller bug, not a data \
problem",
self.label
)
});
assert!(
self.slot_ready[*slot],
"{}: get_prefetched({expert_id}) found slot {slot} assigned but not yet populated \
— its dequant task never completed (e.g. panicked) after plan_prefetch committed \
ownership. This must never be reached in production: a caller that prefetched \
every selected expert (and correctly propagated any dequant failure instead of \
swallowing it) always applies results, or fails the whole token, before reaching \
Step 3's lookups — a data/caller bug, not routine cache behavior",
self.label
);
&self.slots[*slot]
}
pub(crate) fn plan_prefetch(&mut self, expert_ids: &[usize]) -> Vec<PrefetchTask> {
let mut tasks = Vec::new();
let mut planned_this_call: std::collections::HashMap<usize, usize> =
std::collections::HashMap::new();
for &expert_id in expert_ids {
if let Some(&slot) = planned_this_call.get(&expert_id) {
#[cfg(test)]
{
self.hit_count += 1;
}
self.touch(slot);
continue;
}
if let Some(&slot) = self.expert_to_slot.get(&expert_id) {
if self.slot_ready[slot] {
#[cfg(test)]
{
self.hit_count += 1;
}
self.touch(slot);
continue;
}
#[cfg(test)]
{
self.miss_count += 1;
}
self.slot_ready[slot] = false;
self.touch(slot);
planned_this_call.insert(expert_id, slot);
tasks.push(PrefetchTask { slot, expert_id });
continue;
}
#[cfg(test)]
{
self.miss_count += 1;
}
let slot = self.pick_eviction_slot();
if let Some(old_owner) = self.slot_owner[slot].take() {
self.expert_to_slot.remove(&old_owner);
#[cfg(test)]
{
self.eviction_count += 1;
}
}
self.slot_owner[slot] = Some(expert_id);
self.expert_to_slot.insert(expert_id, slot);
self.slot_ready[slot] = false;
self.touch(slot);
planned_this_call.insert(expert_id, slot);
tasks.push(PrefetchTask { slot, expert_id });
}
tasks
}
pub(crate) fn spawn_dequant<'scope, 'env>(
&'env self,
tasks: Vec<PrefetchTask>,
parallel: bool,
panic_on_expert: Option<usize>,
#[cfg(test)] ordering_gate: Option<PrefetchOrderingGate>,
scope: &'scope std::thread::Scope<'scope, 'env>,
) -> Option<std::thread::ScopedJoinHandle<'scope, PrefetchDequantResults>> {
if tasks.is_empty() {
return None;
}
let table = &self.table;
let label = self.label.as_str();
Some(scope.spawn(move || {
#[cfg(test)]
if let Some(gate) = ordering_gate {
let _ = gate.started_tx.send(());
let _ = gate.release_rx.recv();
}
let dequant_one = |expert_id: usize| -> Vec<u16> {
if panic_on_expert == Some(expert_id) {
panic!(
"{label}: test-injected dequant panic for expert {expert_id} — this \
message should never appear outside the readiness-recovery test that \
deliberately triggers it"
);
}
table.dequant_expert_f16(expert_id).unwrap_or_else(|e| {
panic!(
"{label}: failed to dequantize expert {expert_id} during prefetch \
(should be unreachable — expert_id is always < num_experts and the \
byte table was validated at construction): {e}"
)
})
};
if parallel && tasks.len() > 1 {
tasks
.par_iter()
.map(|t| (t.slot, dequant_one(t.expert_id)))
.collect()
} else {
tasks
.iter()
.map(|t| (t.slot, dequant_one(t.expert_id)))
.collect()
}
}))
}
pub(crate) fn apply_prefetch_results(&mut self, results: &[PrefetchDequantResult]) {
for (slot, data) in results {
debug_assert_eq!(data.len(), self.slot_elems);
unsafe {
let dst = self.slots[*slot].contents() as *mut u16;
std::ptr::copy_nonoverlapping(data.as_ptr(), dst, data.len());
}
self.slot_ready[*slot] = true;
}
}
#[cfg(test)]
pub(crate) fn prefetch_experts(&mut self, expert_ids: &[usize], parallel: bool) {
let tasks = self.plan_prefetch(expert_ids);
if tasks.is_empty() {
return;
}
let results = std::thread::scope(|scope| {
let handle = self.spawn_dequant(tasks, parallel, None, None, scope);
handle
.expect("tasks is non-empty, spawn_dequant only returns None for empty tasks")
.join()
.unwrap_or_else(|e| std::panic::resume_unwind(e))
});
self.apply_prefetch_results(&results);
}
fn pick_eviction_slot(&mut self) -> usize {
let pos = self
.lru
.iter()
.position(|&s| !self.slot_touched[s])
.unwrap_or_else(|| {
panic!(
"{}: no untouched expert-cache slot available for eviction — this means \
more than num_slots={} distinct experts were resolved within a single \
token, which `moe_expert_cache_num_slots` should have prevented by \
enforcing num_slots >= top_k at construction time (cache misconfigured, \
not a data problem)",
self.label,
self.slots.len()
)
});
self.lru.remove(pos).unwrap_or_else(|| {
panic!(
"{}: lru.remove({pos}) found nothing — `pos` was just returned by \
`lru.iter().position(..)` on this same `self.lru` with no mutation in \
between, so this is unreachable unless that invariant breaks (cache \
internally corrupted, not a data problem)",
self.label
)
})
}
#[cfg(test)]
fn load_into(&mut self, slot: usize, expert_id: usize) {
if let Some(old_owner) = self.slot_owner[slot].take() {
if old_owner != expert_id {
self.expert_to_slot.remove(&old_owner);
#[cfg(test)]
{
self.eviction_count += 1;
}
}
}
let f16_data = self
.table
.dequant_expert_f16(expert_id)
.unwrap_or_else(|e| {
panic!(
"{}: failed to dequantize expert {expert_id} (should be unreachable — \
expert_id is always < num_experts and the byte table was validated at \
construction): {e}",
self.label
)
});
debug_assert_eq!(f16_data.len(), self.slot_elems);
unsafe {
let dst = self.slots[slot].contents() as *mut u16;
std::ptr::copy_nonoverlapping(f16_data.as_ptr(), dst, f16_data.len());
}
self.slot_owner[slot] = Some(expert_id);
self.expert_to_slot.insert(expert_id, slot);
self.slot_ready[slot] = true;
self.touch(slot);
}
fn touch(&mut self, slot: usize) {
self.slot_touched[slot] = true;
if let Some(pos) = self.lru.iter().position(|&s| s == slot) {
self.lru.remove(pos);
}
self.lru.push_back(slot);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg(num_slots: Option<usize>) -> MoeExpertCacheConfig {
MoeExpertCacheConfig { num_slots }
}
#[test]
fn env_override_clamped_to_top_k_and_num_experts_when_budget_is_not_binding() {
assert_eq!(
moe_expert_cache_num_slots(&cfg(Some(1)), 256, 8, 6_291_456, 40, 400_000_000_000)
.unwrap(),
8,
"below top_k clamps up"
);
assert_eq!(
moe_expert_cache_num_slots(&cfg(Some(9999)), 256, 8, 6_291_456, 40, 400_000_000_000)
.unwrap(),
256,
"above num_experts clamps down to num_experts when the budget has headroom to spare"
);
assert_eq!(
moe_expert_cache_num_slots(&cfg(Some(64)), 256, 8, 6_291_456, 40, 400_000_000_000)
.unwrap(),
64,
"in-range passes through unchanged"
);
}
#[test]
fn env_override_capped_at_working_set_budget_even_when_below_num_experts() {
let budget_max = 135;
assert_eq!(
moe_expert_cache_num_slots(&cfg(Some(9999)), 256, 8, 6_291_456, 40, 40_000_000_000)
.unwrap(),
budget_max,
"an override far above num_experts must cap at the working-set budget, not num_experts"
);
assert_eq!(
moe_expert_cache_num_slots(&cfg(Some(200)), 256, 8, 6_291_456, 40, 40_000_000_000)
.unwrap(),
budget_max,
"an override below num_experts but above the budget must still be capped at the budget"
);
assert_eq!(
moe_expert_cache_num_slots(&cfg(Some(100)), 256, 8, 6_291_456, 40, 40_000_000_000)
.unwrap(),
100,
"an override under the budget passes through unchanged"
);
}
#[test]
fn default_zero_eviction_fast_path_when_everything_fits() {
let n = moe_expert_cache_num_slots(&cfg(None), 4, 1, 4096, 1, 8_000_000_000).unwrap();
assert_eq!(n, 4);
}
#[test]
fn auto_shrinks_under_device_budget() {
let per_expert_bytes = 2 * 512 * 2048 * 2 + 2048 * 512 * 2; let n = moe_expert_cache_num_slots(
&cfg(None),
256,
8,
per_expert_bytes as u64,
40,
28 * 1024 * 1024 * 1024,
)
.unwrap();
assert!(
n < 256,
"expected auto-shrink below num_experts=256 on a 32 GiB-class device, got {n}"
);
assert!(n >= 8, "must never shrink below top_k=8, got {n}");
}
#[test]
fn errors_when_even_top_k_does_not_fit() {
let err =
moe_expert_cache_num_slots(&cfg(None), 4, 1, 10_000_000_000, 1, 1_000_000).unwrap_err();
assert!(
err.contains("cannot fit even the lazy dequant-on-demand cache"),
"unexpected error message: {err}"
);
}
#[test]
fn rejects_zero_num_experts_or_top_k() {
assert!(moe_expert_cache_num_slots(&cfg(None), 0, 1, 100, 1, 1_000_000_000).is_err());
assert!(moe_expert_cache_num_slots(&cfg(None), 4, 0, 100, 1, 1_000_000_000).is_err());
}
#[test]
fn rejects_top_k_exceeding_num_experts() {
assert!(moe_expert_cache_num_slots(&cfg(None), 4, 5, 100, 1, 1_000_000_000).is_err());
}
static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn with_env_var<R>(value: Option<&str>, f: impl FnOnce() -> R) -> R {
let _guard = ENV_TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let prior = std::env::var(MOE_EXPERT_CACHE_SLOTS_ENV).ok();
unsafe {
match value {
Some(v) => std::env::set_var(MOE_EXPERT_CACHE_SLOTS_ENV, v),
None => std::env::remove_var(MOE_EXPERT_CACHE_SLOTS_ENV),
}
}
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
unsafe {
match &prior {
Some(v) => std::env::set_var(MOE_EXPERT_CACHE_SLOTS_ENV, v),
None => std::env::remove_var(MOE_EXPERT_CACHE_SLOTS_ENV),
}
}
match result {
Ok(r) => r,
Err(payload) => std::panic::resume_unwind(payload),
}
}
#[test]
fn from_env_unset_is_none() {
with_env_var(None, || {
assert_eq!(MoeExpertCacheConfig::from_env().unwrap().num_slots, None);
});
}
#[test]
fn from_env_valid_positive_integer_is_some() {
with_env_var(Some("42"), || {
assert_eq!(
MoeExpertCacheConfig::from_env().unwrap().num_slots,
Some(42)
);
});
with_env_var(Some(" 7 "), || {
assert_eq!(MoeExpertCacheConfig::from_env().unwrap().num_slots, Some(7));
});
}
#[test]
fn from_env_garbage_errors_loudly_instead_of_silently_falling_back() {
with_env_var(Some("not-a-number"), || {
let err = MoeExpertCacheConfig::from_env().unwrap_err();
assert!(
err.contains("not a valid positive integer"),
"unexpected error message: {err}"
);
});
with_env_var(Some("-5"), || {
assert!(MoeExpertCacheConfig::from_env().is_err());
});
with_env_var(Some(""), || {
assert!(MoeExpertCacheConfig::from_env().is_err());
});
}
#[test]
fn from_env_zero_errors_loudly() {
with_env_var(Some("0"), || {
let err = MoeExpertCacheConfig::from_env().unwrap_err();
assert!(err.contains("is 0"), "unexpected error message: {err}");
});
}
}