Native model adaptation and NF4
Choose full fine tuning, LoRA, QLoRA storage, LoRA FA, TinyLoRA or rsLoRA; inspect lifecycle, qualified CPU/V100 evidence and remaining scale limits.
Development APIsThese APIs require the matching development native runtime and optional capabilities. Published Aner 0.0.5 runtime packages and the published editor do not supply these additions. This documentation publishes no runtime, editor package or model weights. See release status and Downloads for separately verified artifacts.
This development increment extends the native pretrained workflow with LoRA FA,
rank stabilized LoRA, TinyLoRA, packed NF4 bases and typed asynchronous training
plans. It keeps llm.ollama, cloud providers and native llm.load inference as
separate supported choices. Training runs inside the optional LibTorch C++
backend; it does not launch a Python training process.
The qualified training profiles are supported dense Llama GGUF, CPU FP32 and
the recorded Tesla V100 CUDA FP32/FP16 profiles.
Floating F32/F16/BF16 GGUF files are admitted and decoded using the existing
architecture/tokenizer checks. gen.import_nf4 packs those floating source
matrices into a frozen training representation. Explicit CUDA computation
supports floating full fine tuning and adapter training. The V100 fixture and
real model adapter qualification described below passed. Arbitrary quantized GGUF files,
arbitrary architectures remain outside these profiles. CUDA FP16, paged AdamW and
fused NF4 are explicit additional profiles described below. A
model name such as Qwen does not imply admission to this Llama training backend;
native inference has its own architecture support.
See pretrained training and deployment for supported Llama
configuration, data preparation, source build requirements and baseline APIs.
These additions require the matching development runtime with
ANER_USE_LLM_NATIVE=ON, ANER_USE_LLM_TRAINING=ON and the pinned LibTorch 2.9.0
installation. They are not a claim that published runtime/editor packages contain
the feature.
One model contract, explicit numerical choices
PretrainedModel and TrainingData are immutable handles. Selecting storage or an
adapter returns a new model; training owns private trainable state and publishes
coherent optimizer updates. The source handle remains usable. Full fine tuning
starts directly from a floating model. An NF4 base is frozen and requires an
adapter before training.
| Method | Aner selection | Trainable state | Frozen state and scaling |
|---|---|---|---|
| Full fine tuning | base.fine_tune(...) or base.fine_tune_plan(...) | All floating base weights | FP32 AdamW state for all trainable weights. |
| LoRA | base.lora(...) | A and B for each target | Base frozen; delta = (alpha / rank) B A. |
| rsLoRA | base.rs_lora(...) | A and B for each target | Base frozen; scaling is alpha / sqrt(rank). |
| LoRA FA | base.lora_fa(...) | B for each target | A and its Gram preconditioner frozen; corrected B gradient before Adam moments. |
| TinyLoRA | base.tinylora(...) | A small vector v, shared globally or per target | SVD derived bases and random projection tensors frozen. |
| QLoRA storage + LoRA | base.qlora(...) | LoRA A and B | Frozen packed NF4 base, optional nested scale quantization; explicit FP32 or qualified CUDA FP16 compute. |
The adapter parameterization and base storage are independent: a model returned
by gen.import_nf4(...) also supports lora_fa, rs_lora and tinylora. The
qlora convenience method converts an unchanged floating base to NF4 with double
quantization before selecting ordinary LoRA; it keeps an already packed base's
selected scale format.
Adapter API
The default targets are the query and value attention projections in every
supported layer. Each adapter method accepts no arguments, a target list, or its
complete explicit configuration. A target can be an exact registered weight name
or a supported projection alias. Inspect model.parameter_names() before
selecting exact names; model.trainable_parameters() reports actual optimizer
leaves, including a shared TinyLoRA vector only once.
let targets = List("attention.q_proj", "attention.v_proj")
let low_rank = base.lora(targets, rank: 8, alpha: 16.0, seed: 71)
let stabilized = base.rs_lora(targets, rank: 8, alpha: 16.0, seed: 71)
let fixed_a = base.lora_fa(targets, rank: 8, alpha: 16.0, seed: 71)
let tiny = base.tinylora(targets, rank: 8, parameters: 13, seed: 71,
sharing: "global")
let quantized = base.qlora(targets, rank: 8, alpha: 16.0, seed: 71)These are alternative configurations for comparison. Holding every alternative and its training result retains their owned buffers. A model's memory summary does not total memory owned by other live handles.
Supported aliases are attention.q_proj, attention.k_proj, attention.v_proj,
attention.o_proj, mlp.gate_proj, mlp.up_proj and mlp.down_proj. Configuration
is admitted on an unchanged imported base. Existing adapters and changed full
weights cannot be silently reconfigured. Rank must fit each target and is bounded
to 1 to 256; the public target request is bounded to 256 entries. Defaults are rank
8, alpha 16, seed 0 and, for TinyLoRA, 13 globally shared parameters.
LoRA FA includes gradient preconditioning
Freezing A removes its gradients and optimizer moments. Aner also applies the
explicit preconditioner s^-2 g_B pinv(A A^T + 1e-8 I), where s = alpha / rank,
before updating Adam moments. The fixed preconditioner is persisted with the
adapter. This follows the gradient correction approach in
LoRA FA and the
PEFT LoRA FA optimizer.
There are no promised task quality or end to end memory ratios: attention,
remaining activations and the frozen model still consume memory.
TinyLoRA uses an explicit, persisted basis
For each selected matrix, Aner's paper_usigma_v1 parameterization is
delta W = U_r Sigma_r R V_r^T, with R = sum_i v_i P_i. The truncated SVD
factors and P are frozen. v starts at zero. Aner specifies independent Gaussian P
entries with standard deviation 1/sqrt(rank) and saves the sampled tensors;
the paper leaves that distribution unspecified. sharing: "global" trains one
vector of parameters elements across all targets. "per_target" trains a
separate vector per target. TinyLoRA uses scale 1 and has no alpha argument.
Initialization needs an exact truncated SVD and a matrix workspace. Tiny trainable state does not make the base, SVD or fixed projection buffers tiny. The TinyLoRA paper's small vector reasoning results used reinforcement learning; this supervised Aner backend does not reproduce those experiments or promise their quality.
Packed NF4 and the portable QLoRA profile
Prefer direct packed import when a dense copy of every base matrix would be unnecessary:
let packed = gen.import_nf4("models/base-f16.gguf", double_quant: true,
max_memory_bytes: 4294967296)
let adapted = packed.qlora()
print(adapted.storage())
print(adapted.method())
print(adapted.memory_summary())gen.import_nf4(path[,double_quant[,max_memory_bytes[,device]]]) streams the supported
floating source one matrix at a time. base.nf4() and
base.nf4(double_quant: false) convert an unchanged floating handle instead;
the caller's original floating handle still owns its weights. The default budget
is 4 GiB, with the same explicit admission range of 1 MiB: 64 GiB.
Every matrix uses the exact 16 value NF4 codebook and 64 element absolute maximum blocks. Two indices occupy each uint8 byte. With double quantization, the block maxima are centered by their global FP32 mean and quantized with the signed dynamic8 codebook in 256 element blocks. This is actual nested quantization, not uniform int8 scales. Norm vectors stay FP32. The representation has no persistent dense floating matrix cache. These choices follow QLoRA and the official bitsandbytes codebooks.
Linear operations decode bounded weight tiles and compute FP32 input gradients through the frozen base; no base weight gradients are produced. Embeddings decode only selected rows. The default decoded weight tile limit is 4 MiB. This bounds decoded weight storage, excluding mandatory outputs/gradients, input contiguity copies and BLAS scratch. It is not a total process RAM limit. Import, export and TinyLoRA initialization can require one full floating matrix workspace.
storage() distinguishes float32, nf4 and nf4_double_quant. method()
reports full, lora, rslora, lora_fa or tinylora. Inspect both: QLoRA is
the combination of NF4 storage and an adapter, rather than a new optimizer leaf
type. The CPU and explicit CUDA FP32 paths implement frozen NF4 adapter training.
The default preserves the host tiled FP32 implementation. The explicit accelerated
profile below adds fused GPU decoding, FP16 matrix compute and managed memory
paged AdamW. BF16, activation checkpointing and the original QLoRA paper's
throughput/memory results are not established by these additions. Deployment q4_0 GGUF conversion remains
a different operation and format.
Explicit CUDA computation and qualification
The optional CUDA runtime accepts device: "cuda:0"; CPU remains the default.
Set the deterministic cuBLAS workspace policy before starting the process:
CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner training.anerlet packed_gpu = gen.import_nf4("models/base-f16.gguf", double_quant: true,
max_memory_bytes: 4294967296, device: "cuda:0")
let adapted_gpu = packed_gpu.qlora()
print(adapted_gpu.device())
// Transfer an unchanged imported model explicitly; the source remains usable.
let moved = base.to("cuda:0")Device changes require an untrained model without saved optimizer state. A trained checkpoint resumes on its recorded device and execution profile. The CUDA path requires an available CUDA enabled LibTorch installation and visible hardware, enables deterministic algorithms, and disables TF32. An unsupported device or missing deterministic workspace policy raises an error.
Floating base weights and adapter computations use the selected CUDA device.
For NF4, packed base buffers stay on the CPU. Each linear operation decodes a
host tile and copies it to the input's CUDA device for FP32 multiplication,
including its input gradient calculation. At the default tile limit this means
up to 4 MiB of decoded host weights and 4 MiB of decoded GPU weights at a time,
plus required outputs, gradients, activations, transfer/allocator and BLAS
workspace. There is no persistent full floating GPU base cache. This initial
path has CPU decoding and PCIe transfer costs. Selecting nf4_kernel: "fused"
uses the separate device resident packed representation described below.
The recorded V100 profile passed native numerical, update, checkpoint/resume,
job and deployment checks. Its real SmolLM2 135M run qualified the LoRA,
LoRA FA and TinyLoRA adapter paths using the frozen NF4 base. Real full weight fine tuning also passed on the V100 with all 134,515,008
parameters trainable, an unchanged source and exact checkpoint/resume. The
optional aner_modern_full_finetune_probe target repeated these checks. Its
whole probe Torch CUDA allocated peak was 7,959,815,680 bytes, including retained
source, trained, restored and comparison models; this is not the VRAM requirement
for a single training operation. These results qualify the selected FP32
implementation and hardware/runtime profile. They do not establish other
accelerators, isolated run peak VRAM requirements or cross device
bitwise equality.
Explicit accelerated training profiles
Select the profile after choosing device, storage and adapter. The original CPU
and CUDA FP32 defaults remain precision: "float32", optimizer: "adamw",
nf4_kernel: "host_tiled". There is no automatic precision change or CPU fallback.
let packed = gen.import_nf4("models/base-f16.gguf", double_quant: true,
max_memory_bytes: 4294967296, device: "cuda:0")
let selected = packed.qlora()
let accelerated = selected.with_training_profile(precision: "float16",
optimizer: "paged_adamw", nf4_kernel: "fused", page_bytes: 2097152)
print(accelerated.training_profile())
print(accelerated.memory_summary())
let tuned = accelerated.fine_tune(training, validation, steps: 20,
micro_batch: 1, accumulation: 4, rate: 0.0002, seed: 71)
tuned.checkpoint("checkpoint-fp16-20")The same selection applies to floating full fine tuning, LoRA, rsLoRA, LoRA FA
and TinyLoRA, including their NF4 variants. nf4_kernel has no weight decoding
work to do for a floating base. Choose float32 to isolate paging or fused kernel
behavior without changing matrix precision. Profile changes require an untrained
handle. Exact resume retains the recorded precision, optimizer, kernel and scaler.
Unsupported values and unsupported device/build combinations fail explicitly.
Mixed precision keeps stable state in FP32
FP16 is an operation policy, rather than a conversion of trainable master weights. Dense projections and attention matrix multiplications use FP16 operands. Fused NF4 decodes weights directly to FP16 inside the GPU tile; WMMA Tensor Cores accumulate in FP32 and return FP16 results. Adapter contractions, master parameters, master parameter gradients, Adam moments, RMS normalization, RoPE calculations, softmax and the loss remain FP32. The FP32 path disables TF32. The V100 supports this FP16 path; BF16 is rejected by this implementation.
FP16 training scales the summed masked loss, then unscales accumulated gradients
by the scale and the actual scored token count before clipping. A nonfinite
scaled gradient clears the window's gradients, halves the scale and replays the
same examples. It commits no optimizer update or token count. The default scale
is 65536, doubles after 2000 successful updates and is bounded above by 2^32.
At most 32 consecutive retries are allowed, with a minimum scale of 2^-24.
Nonfinite forward loss and persistent overflow fail explicitly. Checkpoints save
the scale, successful update counter and overflow count. FP16 is a distinct
numerical profile and does not promise FP32 bitwise results or equal task quality.
See the gradient scaling guidance.
Paged AdamW uses real CUDA managed memory
optimizer: "paged_adamw" allocates both FP32 moment tensors with
cudaMallocManaged, initializes them on the CPU and sets CPU location preference.
Each optimizer chunk is prefetched to the active Torch CUDA stream, updated with
the same AdamW equations, then prefetched back to the CPU. Parameter masters and
gradients remain ordinary CUDA tensors. This is moment paging, not whole model
offloading or an 8 bit optimizer. It requires concurrent CUDA managed memory
support and fails if that capability is absent.
page_bytes is a per moment update/prefetch chunk, default 2 MiB, aligned to 4096
bytes and bounded to 4 KiB: 64 MiB. CUDA may round and migrate pages differently;
this setting is not a hard VRAM residency cap. Paging can increase PCIe traffic
and slow training when the moments already fit on the GPU. It does not guarantee
that a model larger than available memory will fit. The memory summary reports
managed optimizer allocation bytes separately; Torch allocator peaks exclude
these external allocations. Resume clones private managed moments and restores
the same policy. See CUDA unified memory APIs.
Fused GPU NF4 decodes inside the matrix kernel
nf4_kernel: "fused" keeps an immutable packed copy of codes and scale metadata
on the GPU. CPU packed buffers remain authoritative for checkpoints and identity;
memory_summary() counts both copies. Forward, input gradient and selected row
embedding kernels decode NF4 and optional nested dynamic8 scales on the device.
They allocate no global decoded floating weight tile and keep no full floating
base cache. Required outputs, activations, contiguous input copies, CUDA modules
and allocator overhead still consume memory.
The FP32 implementation uses deterministic shared memory tiles. The FP16
implementation uses Volta compatible WMMA with FP32 accumulation; reconstructed
weights are rounded to FP16 before multiplication. Both handle partial matrix
and quantization blocks without atomic reductions. Norm vectors stay FP32.
dequant_tile_bytes becomes zero for the fused profile; it is not total scratch
or total training memory.
The runtime compiles only fixed repository kernel source through the pinned bundled NVRTC library, targeting the actual GPU capability. Models cannot supply kernel code. Compilation is cached in a bounded process local module; no Python, Triton or NVCC process is launched. The checkpoint binds source/compiler policy, actual NVRTC version, compiler/builtins/header content digest and device target. Supply an immutable, dereferenced CUDA header tree without directory symlinks; dependencies must remain immutable for the process. The runtime hashes inputs once and checks ordinary file/inventory changes around new kernel compilation. The existing host tiled path remains an explicit baseline. See CUDA WMMA.
V100 accelerated qualification, 31 October 2026
Recorded accelerated qualification binds the current numerical source hashes, backend, JIT toolchain and test binaries. It passed 36,565 fused kernel checks, 235 managed optimizer checks, and 15,861 profile/overflow/recovery checks across 52 storage/method/precision/optimizer combinations and a background job. These are mechanism checks, not independent training tasks or quality scores.
The real SmolLM2 to 135M Instruct floating GGUF qualification passed FP16/fused/paged LoRA, LoRA FA and globally shared 13 scalar TinyLoRA on all 60 query/value targets. Floating full fine tuning updated all 134,515,008 parameters in 272 tensors. Each method preserved its source and matched uninterrupted two update training exactly after one update, checkpoint, reload and one resumed update, including parameters, optimizer moments, loss scaler state and scored token counters. The real probe completed 590,039 checks on short explicit training/validation texts. It establishes execution and recovery, not language quality or large model feasibility. Full fine tuning owned 1,076,120,064 managed moment bytes per model; that allocation count does not state physical GPU residency. The probe released all managed allocations with no reported CUDA free failure.
A same host short QLoRA comparison held AdamW, all 60 targets, rank 2, micro batch 1 and accumulation 1 constant. These are median wall times of three synchronized one update resume calls after one warmup; private optimizer cloning and held out evaluation are included:
| NF4 implementation | Matrix precision | Median seconds per measured call |
|---|---|---|
| Host tiled | FP32 | 1.424 |
| Fused CUDA | FP32 | 0.250 |
| Fused CUDA WMMA | FP16 | 0.281 |
Fused FP32 was about 5.7 times faster than host tiling in this measured workload. FP16 did not improve the whole workload time, although its warmed projection kernels were faster. Choose precision and paging for the actual model and memory pressure; neither is an automatic speed or fit guarantee. The measurements do not establish isolated run peak VRAM/RSS, oversubscription behavior, sustained large training throughput or task quality. Complete component timings and numerical comparison bounds are in the recorded evidence.
The complete accelerated source shows profile selection, data, training, exact resume and FP32 deployment. The optional test targets expose explicit GPU qualification; a missing device/capability is a failure:
CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner_modern_nf4_cuda_tests --require-cuda --benchmark
CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner_modern_paged_tests --require-cuda
CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner_modern_accelerated_tests build/torch-cu126/modern-fixtures validation-output --cudaBuild an existing Linux cu126 LibTorch package
The normal CPU build continues to use the existing pinned LibTorch 2.9.0
find_package(Torch) path. A separate opt in C++ build profile consumes the
official 64 bit Linux LibTorch 2.9.0+cu126 package and existing CUDA runtime
headers. It links vendor compiled CUDA libraries without compiling CUDA source
or invoking NVCC. This does not change the separate toolchain gates for Aner's
native CUDA kernels or llama.cpp CUDA builds.
Provide these dependencies locally before configuring. Aner's build does not download LibTorch, CUDA headers, model weights or datasets.
cmake -S . -B build/torch-cu126 \
-DANER_USE_LLM_NATIVE=ON \
-DANER_LLAMA_SOURCE_DIR=/opt/aner-deps/llama.cpp \
-DANER_USE_LLM_TRAINING=ON \
-DANER_TORCH_PREBUILT_CUDA=ON \
-DANER_LLM_FUSED_NF4=ON \
-DANER_TORCH_PREFIX=/opt/aner-deps/libtorch \
-DANER_TORCH_CUDA_INCLUDE_DIR=/opt/aner-deps/cuda-runtime/include \
-DCMAKE_BUILD_RPATH="/opt/aner-deps/runtime/lib;/opt/aner-deps/libtorch/lib"
cmake --build build/torch-cu126 --target anerANER_TORCH_CUDA_INCLUDE_DIR must contain cuda_runtime_api.h and the associated
vendor headers. Fused NF4 additionally requires cuda.h, cuda_fp16.h, mma.h
and their associated CUDA 12.6/CCCL headers, plus the bundled NVRTC and builtins
libraries. ANER_LLM_FUSED_NF4 defaults on in the pinned prebuilt CUDA profile;
other builds can leave it off and retain existing inference/training paths.
The prebuilt profile verifies the package version, required
shared libraries and C++11 ABI compatibility. The paths above are examples and
must identify the installed dependencies on that machine.
CMAKE_BUILD_RPATH uses a semicolon separated CMake list. At execution, the
corresponding LD_LIBRARY_PATH form uses colons:
LD_LIBRARY_PATH=/opt/aner-deps/runtime/lib:/opt/aner-deps/libtorch/lib \
CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner training.anerSome vendor bundles name their CUDA runtime library with an additional hash.
If the dynamic loader needs a CUDA runtime SONAME alias, provide it in the
isolated runtime/lib directory pointing to the actual library in that same
vendor bundle. Preserve the vendor binary; do not substitute an unrelated
system CUDA runtime. Installed package runtime loading remains a separate
publication and qualification gate.
Recorded CPU and V100 qualification on SmolLM2 135M
The recorded qualification covers
native C++ execution on the Mac CPU and a Tesla V100 PCIE 32GB. Both real model
runs used SmolLM2 135M: 134,515,008 parameters, 30 layers and width 576. Source SHA-256 was
c953b337d61834a459884f51250e8242c7a4d2d7f74e465e84eb30e0e51d6d59.
The final CPU probe passed 933,929 checks. The real extended V100 probe passed
933,930 checks in 82.19 seconds. The V100 profile used native LibTorch
2.9.0+cu126, compute capability 7.0, CUDA runtime API version 12060, driver API
version 13000 and the deterministic cuBLAS policy :4096:8. No Python training
process was involved.
On the V100 build, the NF4 component suite passed 536 CPU/CUDA checks. The GPU training suite passed 518,517 checks across three base storage choices and all four adapter methods, plus floating full fine tuning, asynchronous jobs, checkpoint/resume and deployment fixtures. CPU/native and service only regression checks also passed for the recorded build: 17 native tests and six service only tests, including the provider loopback rerun. These are checks of the implemented method matrix and selected profiles; architecture breadth and broader workloads remain separate work.
Owned NF4 base tensor storage was 69,515,644 bytes (66.30 MiB), including floating norm vectors, compared with 538,060,032 bytes (513.13 MiB) for the same logical weights in FP32. This is an 87.08% reduction in base tensor storage. It excludes process overhead, other live handles and temporary workspaces; it is not a peak RSS or training memory measurement.
The context 32 runs used nine scored training tokens and ten held out tokens. Rank 2 LoRA on the first layer's query/value projections updated 3,840 parameters in 0.491 seconds on the Mac CPU and 1.469 seconds on the V100. First layer query LoRA FA updated 1,152 parameters; TinyLoRA updated 13 FP32 scalars, with 9,424 additional bytes of frozen basis/projection buffers and 104 bytes of optimizer state. The GPU profile was slower in this tiny workload observation. The runs used different hosts and do not form a controlled hardware benchmark. CPU decoding, small GPU operations and PCIe transfers have overhead; this initial tiled path provides working computation and bounded base weight workspaces without a speedup promise.
On each device, checkpoint logits restored identically; continuing the one update checkpoint to two total updates matched the uninterrupted run's fingerprint exactly. Same GPU continuation retained bit identical logits. Merged deployment logits agreed with an independent pinned llama.cpp CPU FP32 oracle to maximum absolute error 0.000034 for the CPU run and 0.000025 for the V100 run across 49,152 final token logits. That oracle used FP32 KV storage and disabled flash attention. CPU held out loss increased from 5.605949 to 5.606572 after the LoRA update; the GPU run changed from 5.605982 to 5.605275. Neither tiny sample establishes improved task quality or comparative training performance.
Training plans and observable jobs
The native types PretrainedPlan, PretrainedJob and PretrainedResult preserve
the distinction between preparing work, running it and observing its outcome.
The existing bounded scientific model TrainingPlan and TrainingJob types
retain their own contracts.
let plan = adapted.fine_tune_plan(training, validation, steps: 20,
micro_batch: 1, accumulation: 4, rate: 0.0002, seed: 71)
print(plan.summary())
print(plan.memory_summary())
// The checkpoints directory must already exist. Each run gets a fresh child.
let scheduled = plan.checkpoints(directory: "checkpoints", every: 5)
let job = scheduled.start()
print(job.status())
let result = job.wait()
print(result.summary())
if result.status() == "failed" {
print(result.error())
} else {
let tuned = result.model()
tuned.checkpoint("checkpoint-final")
}| Handle | Operations and contract |
|---|---|
| Model | fine_tune_plan(training,validation,steps,micro_batch,accumulation,rate,seed[,clip_norm,weight_decay]) prepares a new optimizer run. resume_plan(training,validation,steps) retains a compatible saved optimizer and data progression. |
| Plan | summary(), memory_summary(), checkpoints(directory,every), start(). Configuration is immutable; adding checkpoints returns another plan. |
| Job | status(), steps(), trained_tokens(), last_checkpoint(), stop(), checkpoint(directory), wait(). |
| Result | status(), error(), completed(), steps(), trained_tokens(), summary(), model(), validation_loss(). |
The local executor admits one running job and at most 64 waiting jobs. It does
not create one thread per job. Status is queued, running, completed,
stopped or failed. Releasing the last job handle requests a cooperative stop;
retain the handle while work should continue. Stopping an active job takes effect
at a committed optimizer update boundary, so it does not interrupt an individual
matrix operation. Cancelling a queued run retains the unchanged source model.
wait() returns a typed result even for a failed run; read error() before
requesting its model. A failed result has no model accessor value. Counters count
committed updates and scored training tokens. validation_loss() requires a
completed evaluation, which a run cancelled while queued does not have.
Manual job.checkpoint(directory) is synchronous: it publishes at the next
coherent update or from a terminal model. It can wait while a job is queued or
performing an update. The request queue is bounded. A manual publication error
affects that request; an automatic checkpoint failure fails its job. Automatic
publications use fresh run/step paths and never overwrite existing artifacts.
Plans validate their configuration when created. Full data compatibility and
training memory admission occur before the worker updates model state, so a plan
can still produce a failed result at execution. plan.memory_summary() describes
the source model's accounting; it is not a separately measured peak for that
plan's batch/context configuration.
Checkpoints, exact continuation and deployment
The version 3 training package records the numerical backend identity, base and
tokenizer identities, adapter method/rank/scaling/sharing, fixed tensors, actual
packed NF4 buffers, optimizer moments, data fingerprints, sample cursor, counters
and known training lineage. It also records precision, optimizer kind, prefetch
window, NF4 kernel identity/runtime and dynamic loss scaler policy/state. The 45 field checkpoint manifest also records an
execution profile: CPU kernel capability and Torch configuration SHA-256, plus
the CUDA device name, compute capability, multiprocessor count, global memory,
runtime and driver API versions, and deterministic cuBLAS policy when applicable.
Publication stages its files and publishes the
manifest last. Unsupported representation versions, mismatched shapes, malformed
buffers, changed data or incompatible backend identity are refused.
fine_tune/fine_tune_plan starts a new optimizer run. resume/resume_plan
continues the saved compatible progression. Exact resume is a claim about the
recorded numerical profile and inputs. Hardware/runtime profile mismatches are
refused. Qualification on one host/runtime does not establish bitwise recovery
on other hardware, devices or versions. Preserve the recorded binary/build for
all earlier version 1 and version 2 checkpoint layouts. This increment does not automatically migrate their optimizer or
lineage into a new numerical implementation. Exporting weights and importing
them for a new run is not exact continuation or complete lineage migration.
save_model(path) publishes merged F32 GGUF deployment weights and provenance,
including a decoded NF4 base when applicable. Export works one matrix at a time
and must pass its own workspace admission. The resulting file can be loaded by
llm.load; a deployment quantization step is separately explicit.
LoRA, rsLoRA, LoRA FA and TinyLoRA export standard GGUF factors with
save_adapter(directory). The conversion preserves their effective scaling and
binds the manifest to the exact deployment base. For a floating base, that is the
unchanged source GGUF. For an NF4 base, the frozen training weights differ from
the original floating GGUF, so export creates a standalone package:
adapted-package/
base.gguf # exact dequantized frozen training base
adapter/adapter.gguf
adapter/adapter.json # returned manifest; exact base identity
adapter/training.json
deployment.json # package publication marker, written lastlet manifest = tuned.save_adapter("adapted-package")
let deployment_base = llm.load("adapted-package/base.gguf", context: 128,
device: "cpu")
let specialized = deployment_base.with_adapter(manifest)
print(specialized.generate("Explain this observation.", max_tokens: 32).text())
specialized.close()
deployment_base.close()The original floating GGUF still fails the exact base identity check for that NF4 package. Explicitly publishing the effective base preserves training and deployment agreement without hiding a change in the model underneath an adapter.
The original nine stages: scope and remaining gates
The original delivery commitment covered stages 1 to 5, with stage 6 as a backend decision gate. It did not establish that all nine advanced stages were complete. This table preserves those stage definitions and records the completed gates for the current profiles alongside the remaining scope.
| Stage | Original objective | Current position and remaining work |
|---|---|---|
| 1 | Artifact schema, native model type, optional backend, service only regression gate | Complete for this increment: native/service separation, optional builds and the recorded native/service-only regression gates pass, including the provider loopback rerun. New profiles repeat those gates. |
| 2 | CPU llm.load/generation/responses/cleanup with real GGUF tokenization, templates and lifecycle | Existing native CPU inference workflow. New training representations deploy through explicit GGUF export; they do not change the inference lifecycle contract. |
| 3 | Accelerator qualification and standalone compilation on actual hardware, without an interpreter fallback | Complete for the recorded CPU and V100 FP32 native C++ build/compute profiles. Native inference also has prior Metal qualification. Other hardware, optimized kernels and packaged standalone deployment retain their own gates. |
| 4 | Adapter loading/conversion, exact base identity, mismatch and stale context rejection | Current method gates pass. All four adapters export standard factors; NF4 packages include their effective base, reject the original floating source, and pass recorded deployment agreement checks. |
| 5 | Training plans/jobs/checkpoints, stop/resume, immutable state and atomic publication | Current profile gates pass, including asynchronous jobs, coherent stopping/publication and exact continuation. Recovery remains tied to the recorded backend, hardware/runtime profile and data. |
| 6 | Modern backend prototype and decision based on token IDs, logits, gradients, optimizer and resume | LibTorch C++ selected and qualified through independent component oracles, the method/storage matrix and real CPU/V100 probes. Further architectures and backends require new qualification. |
| 7 | Modern supervised LoRA and deployment round trip with frozen base, held out data, resume and inference agreement | Complete for the current supported methods/profiles: frozen base updates, held out scoring, checkpoint/resume and exported inference agreement pass. The small real model runs establish workflow correctness, not task quality. |
| 8 | Larger model memory optimization and selected full fine tuning with measured admission, throughput, recovery and precision | Partial: full fine tuning, packed storage, streamed matrix workspaces, explicit admission and real 135M adapter/full-weight recovery and a complete probe CUDA allocator peak are measured. Multi billion model qualification, isolated run peak RSS/VRAM accounting, broader throughput, activation checkpointing and larger model admission remain open. Explicit FP16, managed memory AdamW and fused device NF4 are now implemented with separate qualification evidence. |
| 9 | QLoRA with independent quantized training validation | Qualified for frozen NF4/nested-scale adapter training with CPU FP32 or the recorded V100 FP32 host tile path. Independent component checks and real model resume/deployment pass. Explicit fused device NF4, paged AdamW and FP16 extend this profile. Large model task quality, oversubscription behavior and broader hardware remain separate qualification work. |
The NF4 component checks include exact codebook values, odd and block tails,
nested scales, malformed serialization, and forward/input-backward agreement
with a dense FP32 oracle. Such component checks do not establish training quality,
large model practicality or GPU performance. A passed V100 profile applies to
its recorded computation path and runtime. Qualification reports should name
the model/tokenizer checksum, build, device, data identities, method, precision,
peak memory measurement, validation metric and deployment agreement.
Aner's foundation for later collaboration
The useful native language addition is a clear contract around model identity,
explicit data, immutable plans, bounded admission and inspectable results. Frozen
state is distinguishable from trainable state; stopping, checkpointing and
publishing have visible meanings. memory_summary() exposes owned tensor
buffers, frozen adapter state, optimizer buffers and the accounting scope; it is
not process RSS or a machine wide quota.
These contracts can support future user contributed computing. They currently run locally and do not enroll a machine, schedule remote work or implement a distributed training protocol. Volunteer scheduling, trust, aggregation, bandwidth policy and distributed recovery are separate future work. Millions of potential participants do not by themselves make dense synchronized training practical. The native foundation should first establish reproducible updates and recoverable artifacts on each explicitly supported numerical profile.
The method definitions come from the primary LoRA, rsLoRA, LoRA FA, TinyLoRA and QLoRA papers. The comparison illustration is useful intuition, but its parameter counts are not a training memory estimate.
See the Native generative research module reference for the shared API.