Browse documentation
aner.gen · Reference

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 APIs

These 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.

MethodAner selectionTrainable stateFrozen state and scaling
Full fine tuningbase.fine_tune(...) or base.fine_tune_plan(...)All floating base weightsFP32 AdamW state for all trainable weights.
LoRAbase.lora(...)A and B for each targetBase frozen; delta = (alpha / rank) B A.
rsLoRAbase.rs_lora(...)A and B for each targetBase frozen; scaling is alpha / sqrt(rank).
LoRA FAbase.lora_fa(...)B for each targetA and its Gram preconditioner frozen; corrected B gradient before Adam moments.
TinyLoRAbase.tinylora(...)A small vector v, shared globally or per targetSVD derived bases and random projection tensors frozen.
QLoRA storage + LoRAbase.qlora(...)LoRA A and BFrozen 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.

aner
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:

aner
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:

sh
CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner training.aner
aner
let 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.

aner
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 implementationMatrix precisionMedian seconds per measured call
Host tiledFP321.424
Fused CUDAFP320.250
Fused CUDA WMMAFP160.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:

sh
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 --cuda

Build 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.

sh
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 aner

ANER_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:

sh
LD_LIBRARY_PATH=/opt/aner-deps/runtime/lib:/opt/aner-deps/libtorch/lib \
  CUBLAS_WORKSPACE_CONFIG=:4096:8 build/torch-cu126/aner training.aner

Some 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.

aner
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")
}
HandleOperations and contract
Modelfine_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.
Plansummary(), memory_summary(), checkpoints(directory,every), start(). Configuration is immutable; adding checkpoints returns another plan.
Jobstatus(), steps(), trained_tokens(), last_checkpoint(), stop(), checkpoint(directory), wait().
Resultstatus(), 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:

text
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 last
aner
let 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.

StageOriginal objectiveCurrent position and remaining work
1Artifact schema, native model type, optional backend, service only regression gateComplete 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.
2CPU llm.load/generation/responses/cleanup with real GGUF tokenization, templates and lifecycleExisting native CPU inference workflow. New training representations deploy through explicit GGUF export; they do not change the inference lifecycle contract.
3Accelerator qualification and standalone compilation on actual hardware, without an interpreter fallbackComplete 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.
4Adapter loading/conversion, exact base identity, mismatch and stale context rejectionCurrent 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.
5Training plans/jobs/checkpoints, stop/resume, immutable state and atomic publicationCurrent profile gates pass, including asynchronous jobs, coherent stopping/publication and exact continuation. Recovery remains tied to the recorded backend, hardware/runtime profile and data.
6Modern backend prototype and decision based on token IDs, logits, gradients, optimizer and resumeLibTorch C++ selected and qualified through independent component oracles, the method/storage matrix and real CPU/V100 probes. Further architectures and backends require new qualification.
7Modern supervised LoRA and deployment round trip with frozen base, held out data, resume and inference agreementComplete 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.
8Larger model memory optimization and selected full fine tuning with measured admission, throughput, recovery and precisionPartial: 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.
9QLoRA with independent quantized training validationQualified 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.

Aner documentation · Reference and guides