use std::time::Instant;
use mlua::Lua;
fn env_string(key: &str, default: &str) -> String {
std::env::var(key).unwrap_or_else(|_| default.to_string())
}
fn env_usize(key: &str, default: usize) -> usize {
std::env::var(key)
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(default)
}
fn env_f64(key: &str, default: f64) -> f64 {
std::env::var(key)
.ok()
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(default)
}
const SCRIPT: &str = r#"
local S = SMOKE
local function log(phase, desc, t0)
local dt = alc.time() - t0
print(string.format("[bridge-smoke] phase=%d %s ok in %.2fs", phase, desc, dt))
end
local function make_base()
-- dtype is pinned explicitly: on CUDA the preset default dtype is
-- bf16 (`default_dtype_for_device`), but the trainer / LoRA paths
-- train in f32 (delta VarMap + optimizer state) and fail with
-- "unexpected dtype, expected: F32, got: BF16" on a bf16 base.
-- Mirrors `nn_tinyllama_lora_gpu_smoke.rs` (`cfg.dtype = DType::F32`).
return alc.nn.preset.tinyllama(
S.variant,
{ pretrained = false, device = S.device, dtype = S.dtype }
)
end
-- Phase 1: base handle (random init, no HF download).
local t = alc.time()
local base = make_base()
assert(base:layers() > 0, "base must report a positive layer count")
assert(base:ctx() > 0, "base must report a positive ctx")
assert(base:vocab() > 0, "base must report a positive vocab")
-- Derive the effective ctx / token palette from the concrete handle:
-- clamp ctx to the model's RoPE cache size, and bound token ids
-- strictly under vocab so the same corpus is valid for the tiny
-- (vocab 32 / ctx 16) and 1.1b (vocab 32000 / ctx 2048) variants.
local eff_ctx = math.min(S.ctx, base:ctx())
local vsafe = math.min(base:vocab(), 30)
if vsafe < 1 then
vsafe = 1
end
local rows_needed = S.steps * S.batch + S.batch
log(1, string.format("preset.tinyllama variant=%s layers=%d ctx=%d vocab=%d", S.variant, base:layers(), base:ctx(), base:vocab()), t)
local function make_dataset()
local rows = {}
for r = 0, rows_needed - 1 do
local row = {}
for p = 0, eff_ctx - 1 do
row[#row + 1] = (r + p * 7) % vsafe
end
rows[#rows + 1] = row
end
return alc.nn.data.synthetic(rows, { batch_size = S.batch, ctx_len = eff_ctx })
end
local function train_opts(extra)
local o = {
lr = S.lr,
batch = S.batch,
steps = S.steps,
warmup = S.warmup,
schedule = "CosineWithWarmup",
}
if extra then
for k, v in pairs(extra) do
o[k] = v
end
end
return o
end
-- Phase 2: token dataset (single-pass; a fresh one is built per
-- trainer phase because TokenizedDataset does not cycle).
t = alc.time()
local _ds_probe = make_dataset()
assert(_ds_probe:ctx_len() == eff_ctx, "dataset ctx_len must match requested eff_ctx")
log(2, string.format("data.synthetic rows=%d batch=%d ctx=%d", rows_needed, S.batch, eff_ctx), t)
-- Phase 3: wrap_lora inspect. is_lora_wrapped() is not a Lua method,
-- so confirm the wrap took by asserting the double-wrap rejection
-- path fires on the wrapped handle (a base handle would wrap cleanly).
t = alc.time()
local wrapped = alc.nn.wrap_lora(base, { rank = S.rank, alpha = S.alpha })
assert(wrapped:arch() == "tinyllama", "wrapped handle must report tinyllama arch")
local double_ok = pcall(function()
return alc.nn.wrap_lora(wrapped, { rank = S.rank, alpha = S.alpha })
end)
assert(not double_ok, "double-wrap must be rejected (confirms handle is LoRA-wrapped)")
base = nil
wrapped = nil
collectgarbage()
log(3, string.format("wrap_lora rank=%d alpha=%.1f double_wrap_rejected=true", S.rank, S.alpha), t)
-- Phase 4: run_lora_ft on a fresh base + fresh dataset.
t = alc.time()
local lora_base = make_base()
local lora_ds = make_dataset()
local lora_id = alc.nn.trainer.run_lora_ft(
lora_base,
lora_ds,
train_opts({ rank = S.rank, alpha = S.alpha, name = "bridge-smoke-lora" })
)
assert(type(lora_id) == "string" and #lora_id > 0, "run_lora_ft must return a card id string")
lora_base = nil
lora_ds = nil
collectgarbage()
log(4, "run_lora_ft lora_card_id=" .. lora_id, t)
-- Phase 5: load_wrap round-trip (reload the LoRA Card onto a fresh base).
t = alc.time()
local reload_base = make_base()
local reloaded = alc.nn.card.load_wrap(lora_id, reload_base)
assert(reloaded:arch() == "tinyllama", "load_wrap must return a tinyllama handle")
reload_base = nil
reloaded = nil
collectgarbage()
log(5, "card.load_wrap round-trip ok for " .. lora_id, t)
-- Phase 6: run_full_ft on a fresh base + fresh dataset.
t = alc.time()
local ff_base = make_base()
local ff_ds = make_dataset()
local ff_id = alc.nn.trainer.run_full_ft(ff_base, ff_ds, train_opts({ name = "bridge-smoke-full" }))
assert(type(ff_id) == "string" and #ff_id > 0, "run_full_ft must return a card id string")
ff_base = nil
ff_ds = nil
collectgarbage()
log(6, "run_full_ft card_id=" .. ff_id, t)
-- Phase 7: run_distill on a fresh base + fresh dataset.
t = alc.time()
local rd_base = make_base()
local rd_ds = make_dataset()
local rd_id = alc.nn.trainer.run_distill(
rd_base,
rd_ds,
train_opts({ loss_kind = "ce", name = "bridge-smoke-distill" })
)
assert(type(rd_id) == "string" and #rd_id > 0, "run_distill must return a card id string")
rd_base = nil
rd_ds = nil
collectgarbage()
log(7, "run_distill card_id=" .. rd_id, t)
print(string.format(
"[bridge-smoke] summary: all 7 phases ok (variant=%s device=%s dtype=%s steps=%d batch=%d ctx=%d)",
S.variant, S.device, S.dtype, S.steps, S.batch, eff_ctx
))
"#;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let device = env_string("NN_SMOKE_DEVICE", "cuda");
let variant = env_string("NN_SMOKE_VARIANT", "1.1b");
let dtype = env_string("NN_SMOKE_DTYPE", "f32");
let steps = env_usize("NN_SMOKE_STEPS", 50);
let batch = env_usize("NN_SMOKE_BATCH", 2);
let ctx = env_usize("NN_SMOKE_CTX", 64);
let rank = env_usize("NN_SMOKE_RANK", 16);
let alpha = env_f64("NN_SMOKE_ALPHA", 32.0);
let lr = env_f64("NN_SMOKE_LR", 3e-4);
let warmup = env_usize("NN_SMOKE_WARMUP", steps.min(5));
eprintln!(
"[bridge-smoke] config: device={device} variant={variant} dtype={dtype} \
steps={steps} batch={batch} ctx={ctx} rank={rank} alpha={alpha} lr={lr} \
warmup={warmup}"
);
let boot_t0 = Instant::now();
let lua = Lua::new();
algocline_engine::bridge::install_for_pkg_test(&lua)?;
eprintln!(
"[bridge-smoke] alc.* surface installed in {:.2?}",
boot_t0.elapsed()
);
let cfg = lua.create_table()?;
cfg.set("device", device)?;
cfg.set("variant", variant)?;
cfg.set("dtype", dtype)?;
cfg.set("steps", steps)?;
cfg.set("batch", batch)?;
cfg.set("ctx", ctx)?;
cfg.set("rank", rank)?;
cfg.set("alpha", alpha)?;
cfg.set("lr", lr)?;
cfg.set("warmup", warmup)?;
lua.globals().set("SMOKE", cfg)?;
lua.load(SCRIPT).set_name("@nn_bridge_gpu_smoke").exec()?;
Ok(())
}