Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6694d2e
bench_metal_kq_race: the per-format Metal KQ race rig + the test-back…
borisbat Sep 1, 2026
3107e4c
kq race lab: the ptrstore mul_mm arm - llama.cpp's 'massively slower'…
borisbat Sep 1, 2026
78ab668
Metal-4 tensor mul_mm twins for all nine iquant formats - seven now b…
borisbat Sep 1, 2026
4946945
wire the iquant tensor twins: crowns, PSO ladders, site picks, race r…
borisbat Sep 1, 2026
d342a0c
kq race lab: the D1 lane-map decode spellings for the iq2 family - al…
borisbat Sep 1, 2026
11d15cf
promote the D1 lane-map decode GEMVs for the iq2 family + the iq2xxs …
borisbat Sep 1, 2026
a40edcc
kq race lab: per-cell GPU burn (--burn-ms, default 150) - the idle-cl…
borisbat Sep 1, 2026
7e7c24d
kq race lab: the k2 d1 arm - llama.cpp's q2_K shape (u16 shift-free m…
borisbat Sep 1, 2026
39121e3
promote the k2 d1 GEMV - llama.cpp's q2_K shape on our planes, both b…
borisbat Sep 1, 2026
0eb718e
kq race lab: iq3s/iq3xxs d1 arms - M5 verdict: the shipped f4 forms K…
borisbat Sep 1, 2026
84c7044
iq3s/iq3xxs: the d1 constant form becomes the base, the f4-slab forms…
borisbat Sep 1, 2026
60c15b2
densify the gemv crown-race encoders - 32 back-to-back dispatches per…
borisbat Sep 1, 2026
312a92c
gemv crown racer: mint on the dominant decode shape (2048x8192, the f…
borisbat Sep 1, 2026
d7fba05
gemv crown racer: burn before the alternation - race_pair's base-firs…
borisbat Sep 1, 2026
6fd6d5a
gemv crown racer: overlap the y buffers - the served graph's regime, …
borisbat Sep 1, 2026
e94c921
kq_gemv_iq2xxs_f4 leaves the automatic crown race - e2e is its only v…
borisbat Sep 1, 2026
296c533
k5 tensor twins: byte-parallel QH compose - 1.4x, the last lagging mm…
borisbat Sep 1, 2026
bd042d0
iq2/iq3 gemv compose: float-expand + sign-select replaces the integer…
borisbat Sep 1, 2026
d641eec
mvb batch-decode twins get the cvt compose; the race lab grows an mvb…
borisbat Sep 1, 2026
e0b18ae
iq3s/iq3xxs tensor twins: the cvt compose in stage16 - the last two s…
borisbat Sep 1, 2026
455758a
race lab: the ptrstore arm retires - flat 1.000x on both boxes, the c…
borisbat Sep 1, 2026
ab130cf
followup_metal: the smmla leg closes - first race on M5, NEON keeps t…
borisbat Sep 1, 2026
72739eb
the tune sidecar stops being able to lie: cache-keyed, fallback-safe,…
borisbat Sep 1, 2026
3479b4b
followup_metal: the elementwise/activation lane, sized honestly
borisbat Sep 1, 2026
3cbfc21
PR comment harvest: three mechanisms become architecture, two rules b…
borisbat Sep 1, 2026
f37bdb0
the audit round lands: four test-gate repairs, ten checklist repairs,…
borisbat Sep 1, 2026
246c52c
the dragon round: seven checklists take their verdicts, the grooming …
borisbat Sep 1, 2026
e3f210c
the race checklist takes its auditor's verdicts, and the twin racer s…
borisbat Sep 1, 2026
0380213
add_module_cache_dependency gets its stdlib page - the ast module is …
borisbat Sep 1, 2026
ee08591
woodpecker: drive-relative Windows paths reach add_module_cache_depen…
borisbat Sep 1, 2026
f7e876e
woodpecker round 2: both burn loops get a round cap
borisbat Sep 1, 2026
a773f15
the race lab compiles on the metal-less lint lane - its typed helpers…
borisbat Sep 1, 2026
b1616e7
review round 1: the confirm rail's scratch moves to unique temp files
borisbat Sep 1, 2026
3eefced
review round 2: the confirm spawn gets a timeout, the fail path clean…
borisbat Sep 1, 2026
065629c
CI: ENVIRONMENT.md regenerated for the confirm_iq2xxs knob
borisbat Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 23 additions & 27 deletions daslib/REVIEW.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# daslib Code Review Checklist

**Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture doc:
`daslib/ARCHITECTURE.md` and its companions `ARCHITECTURE_LINT.md`, `ARCHITECTURE_EMIT.md`,
`ARCHITECTURE_LINQ.md`. Planned work: `plans/daslib_comment_sweep.md` (repo root).
**Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture
docs: `ARCHITECTURE.md`, `ARCHITECTURE_LINT.md`, `ARCHITECTURE_EMIT.md`, `ARCHITECTURE_LINQ.md`.
Planned work: `plans/daslib_comment_sweep.md` (repo root).
A diff touching the linq family - `linq*.das`, `sql_*.das` - applies `REVIEW_LINQ.md` too.

**A diff that adds or changes a lint report path (`perf_warning` / `style_warning` /
Expand All @@ -20,8 +20,9 @@ the actionable line is an intermediate call site.
inside a lint visitor method is a defect.** The guard's position decides which rules apply
inside a lambda; the per-rule policy is in `ARCHITECTURE_LINT.md` sec. 1.

**Never move `add_ptr_ref`'s depth tracking behind the `in_closure` gate.** The rules block
is itself a closure, so a gated tracker never sees the splice.
**Never move PERF023's `add_ptr_ref` splice-depth tracking (`perf_lint.das`) behind the
`in_closure` gate.** The rules block is itself a closure, so a gated tracker never sees the
splice.

**A lint warning that anchors anywhere but the code its fix rewrites is a defect.** A remedy
that deletes a statement reports the statement; a remedy that edits one variable's
Expand All @@ -34,23 +35,21 @@ construct's visit.
**Never keep per-loop visitor state in a bare scalar - keep it in a stack.** A scalar
survives into the sibling loop's exit path and unbalances its counter.

**A diff that makes a daslib predicate or emitted identifier depend on a C++-side
definition records the pair in the architecture doc, in its module's section, naming both
sides.** Nothing catches it when one side later moves alone.
**A diff that adds or changes a daslib fact - code or a `//!` contract - whose truth is
decided by a C++-side definition records the pair in the architecture doc, in its module's
section, naming both sides.** Nothing catches it when one side later moves alone.

**When a diff changes one side of a recorded daslib/C++ pair so the two no longer match,
it changes the other side and updates the pair's architecture-doc entry in the same
diff.**
**When a diff changes one side of a recorded daslib/C++ pair so the two no longer match, it
changes the other side and updates the pair's architecture-doc entry in the same diff.**

**A diff that adds an architecture-doc entry adds it only where no name, shape, or test can
carry the fact.**

**Weakening `tests/lint/test_nolint_suppression.das` is a defect** - it pins that a string
**A diff that changes daslib's nolint scanning - suppression parsing, staleness detection, or
the header window - keeps `tests/lint/test_nolint_suppression.das` and
`tests/lint/test_stale_nolint.das` green without weakening them.** They pin that a string
literal, a URL, and a mid-comment `nolint:` do not suppress while a first-token directive
after `//` or `//!` does.

**Weakening `tests/lint/test_stale_nolint.das` is a defect** - it pins that a
`lint-skip-file` past the header window is prose.
after `//` or `//!` does, and that a `lint-skip-file` past the header window is prose.

**A diff that changes `build_lint_macro_disabled` keeps its four sources layered in this
order: defaults, repo `off`, repo `on`, environment.** Env last lets a one-run
Expand All @@ -60,12 +59,10 @@ order: defaults, repo `off`, repo `on`, environment.** Env last lets a one-run
seeding and the repo `off` directives.** Repo policy must not silence the rule a fixture
exists to exercise.

**Weakening `tests/lint/test_lint_config.das` is a defect** - it pins that each `[format]`
key resolves nearest-wins independently up to the `.git` root, so a nearer `.lint_config`
declaring one key does not reset the other.

**Weakening the kept-comment cases in `utils/mcp/test_tools.das` is a defect** - they pin
the formatter's kept set.
**A diff that changes daslib's `.lint_config` resolution keeps `tests/lint/test_lint_config.das`
green without weakening it** - it pins that each `[format]` key resolves nearest-wins
independently up to the `.git` root, so a nearer `.lint_config` declaring one key does not
reset the other.

**A daslib module that emits a lint rule id joins `RULE_MODULES` in `utils/lint/REVIEW.das`
in the same change.** An unlisted module's ids are never scanned, so its fixture-and-rst
Expand Down Expand Up @@ -115,11 +112,10 @@ set without the cap is a silently missed finding; raising a cap without the over
suggestion that does not compile.

**A diff that adds or changes an emit entry point - a function that runs the emit visitor
(`CppAot` or any subclass of it - the visitors that write C++) and then
returns or writes the generated C++ - keeps the error check ahead of that return
or write.** The error check is the program's `macroException`/`failToCompile` state, read
directly or through `log_aot_emit_errors`; a codegen exception mid-visit leaves partial
C++.
(`CppAot` or any subclass of it) and then returns or writes the generated C++ - keeps the
error check ahead of that return or write.** The error check is the program's
`macroException`/`failToCompile` state, read directly or through `log_aot_emit_errors`; a
codegen exception mid-visit leaves partial C++.

**Never gate a visitor override in `CppAot` or any subclass of it on
`macroException`/`failToCompile` - the function that runs the visitor owns that check.** An
Expand Down
2 changes: 2 additions & 0 deletions daslib/builtin.das
Original file line number Diff line number Diff line change
Expand Up @@ -2123,6 +2123,8 @@ def add_ptr_ref(src : smart_ptr<auto(TT)>) : smart_ptr<TT> {
}
}

//! The process argv as an array of strings. The result is a LOCKED VIEW over the host's own
//! argv storage - keep it in a `let` and never `delete` it or move elements out of it.
[generic]
def get_command_line_arguments : array<string> {
var args : array<string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Registers the file at ``path`` as a compile-time dependency of the module being compiled, so the module cache re-parses the module when that file's size or content hash changes. A macro that reads a file at compile time (a tune sidecar, a config, a generated table) calls this before acting on the contents; outside compilation the call is a no-op.
8 changes: 8 additions & 0 deletions include/daScript/ast/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -1691,8 +1691,16 @@ namespace das
CodeOfPolicies policies;
vector<tuple<Module *,string,string,bool,LineInfo>> allRequireDecl;
das_hash_map<uint64_t,TypeDecl *> astTypeInfo;
// compile-time file inputs a macro pinned via add_module_cache_dependency: (path, byte
// size, content hash). The module cache stores and re-validates them, so a changed input
// invalidates the cached record instead of serving stamps minted against the old file.
vector<tuple<string,int64_t,uint64_t>> moduleCacheDependencies;
};

// the shared capture/validate stat for Program::moduleCacheDependencies: size -1 + hash 0
// when the file is absent or unreadable, else (byte size, hash_block64 of the content)
DAS_API void statAndHashFileDependency ( const string & path, int64_t & size, uint64_t & hash );

// access function from class adapter
DAS_API int adapt_field_offset ( const char * fName, const StructInfo * info );
DAS_API int adapt_field_offset_ex ( const char * fName, const StructInfo * info, uint32_t & i );
Expand Down
2 changes: 1 addition & 1 deletion include/daScript/ast/ast_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ namespace das {
AstSerializer & serializeModule ( Module & module, bool already_exists );

static constexpr uint32_t getVersion () {
return 117; // 117: Function::fromGeneric round-trips (generic-instance origin)
return 118; // 118: per-record macro file dependencies (module cache re-validates them)
}

void serializeProgram ( ProgramPtr program, ModuleGroup & libGroup ) noexcept;
Expand Down
11 changes: 6 additions & 5 deletions modules/dasLLAMA/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ re-transcoding `$LCPP/src/unicode-data.cpp`).

- `ARCHITECTURE_IMAGE.md` - sec.2.1-2.1i: the prepared-image rail, the baked dev-W f16 plane,
and the baked tower twin-W plane.
- `ARCHITECTURE_GPU.md` - sec.2.2b, 2.2w-2.2y: the tensor-GEMM and fused-attention shapes that
measured out, the tower attention routes, and the tower driver's encode chains.
- `ARCHITECTURE_GPU_PREFILL.md` - sec.2.2c-2.2i, 2.2u-2.2v: the Metal prefill driver's GEMM form
ladder, dev-W knee map, attention slab, MoE bucket rail, chunked submission, the f16 twin
dual-store, and the last-layer FFN tail.
- `ARCHITECTURE_GPU.md` - sec.2.2b, 2.2w-2.2z: the tensor-GEMM and fused-attention shapes that
measured out, the tower attention routes, the tower driver's encode chains, and the iquant
GEMV grid read with its f4-slab twin.
- `ARCHITECTURE_GPU_PREFILL.md` - sec.2.2c-2.2i, 2.2u-2.2v, 2.2aa: the Metal prefill driver's
GEMM form ladder, dev-W knee map, attention slab, MoE bucket rail, chunked submission, the
f16 twin dual-store, the last-layer FFN tail, and the dense-KQ tensor mul_mm scaffold.
- `ARCHITECTURE_GPU_VULKAN.md` - sec.2.2j-2.2q: the Vulkan resident driver - the prefill window
chain, the cm2 decode lanes and tile pick, the class-pipeline build seat, the residency plan,
the GPU-slot marks swap, the Q8 requant byte store, and the MoE expert chain on the cm2 tiles.
Expand Down
21 changes: 20 additions & 1 deletion modules/dasLLAMA/ARCHITECTURE_GPU.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ that a question answered for one backend has an obvious address in the other. Th
| role | holds | must not hold |
|---|---|---|
| the kernel home<br>`dasllama_metal_kernels`, `dasllama_vulkan_classes` | kernel source, the derived-access/PSO census | device state, engine types |
| `dasllama_<gpu>_common`<br>`dasllama_metal_common`, `dasllama_vulkan_common` | device state, buffer/command plumbing, hazard + capture rail, profiler | driver policy |
| `dasllama_<gpu>_common`<br>`dasllama_metal_common`, `dasllama_vulkan_common` | device state, buffer/command plumbing, hazard + capture rail, profiler, shared quant-decode helpers (`iq4_lut`) | driver policy |
| `dasllama_<gpu>_decode`<br>`dasllama_metal_decode`, `dasllama_vulkan_decode` | the resident token-step driver + decode-time arms | kernel bodies |
| `dasllama_<gpu>_prefill`<br>`dasllama_metal_prefill`, `dasllama_vulkan_prefill` | the batched prefill driver + batch arms | kernel bodies |
| `dasllama_<gpu>_shapes`<br>`dasllama_metal_shapes` | PORTABLE servability gates - no GPU C++ require, so any box can bake | device calls |
Expand Down Expand Up @@ -272,3 +272,22 @@ a region's bind offset must be a multiple of 512 elements - the `(off/256)*2` d-
the exception: it reuses q40's 16-byte plane of eight f16 d per superblock, binds once, and
ignores `doff`. The Vulkan tier does not use this form - it binds the decoded 20-byte row as five
uints per superblock.

### 2.2z The iquant GEMV grid read and its f4-slab twin {#metal-iquant-gemv-grid}

Every iquant Metal GEMV - iq3s, iq3xxs, iq2s, iq2xs, iq2xxs - reads its codebook grid DIRECT
off the module's hoisted constant tables and float-expands each word in place. That is the base
form and the one that ships everywhere.

Three of them carry a second form, the f4-slab twin (`MetalKqGemvIq3sF4`,
`MetalKqGemvIq3xxsF4`, `MetalKqGemvIq2xxsF4`): the whole grid staged into threadgroup memory
once per threadgroup, pre-expanded to `float4` magnitudes, signs applied by select, so the
inner loop does no byte extraction. The twin is a PER-BOX CROWN, never a heuristic and never a
default - it wins on M5-class GPUs and REGRESSES on M1/M4-class, which have less threadgroup
bandwidth to trade for the byte work. Its PSO compiles only when the box's tune manifest
carries the crown (`metal_tensor_crowned("kq_gemv_<fmt>_f4")`), and `enc_kq_gemv` picks the
twin exactly when that PSO is non-null, so a box with no crown never compiles it.

The `kq_gemv_iq3s_f4` and `kq_gemv_iq3xxs_f4` crowns are raced (`race_gemv_f4_twin`).
`kq_gemv_iq2xxs_f4` cannot be settled by an isolated race at all and is minted from a serving
A/B instead - `ARCHITECTURE_MEASUREMENT.md` sec.2.21.
29 changes: 29 additions & 0 deletions modules/dasLLAMA/ARCHITECTURE_GPU_PREFILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,32 @@ takes it. `DASLLAMA_METAL_LASTROW=0` pins the full-panel tail.
A caller that consumes the whole `x_b` plane afterwards - embedding pooling, a plane-compare
probe - sets `Session.keep_hidden` and the prefill keeps every row; the flag is zero-init, so
narrowing is the default.

### 2.2aa The dense-KQ tensor mul_mm scaffold {#prefill-kq-tensor-scaffold}

Nine iquant and split-scale formats - iq4xs, iq4nl, k3, iq3s, iq3xxs, k2, iq2s, iq2xs, iq2xxs -
share ONE tensor mul_mm body, `MetalKqMulMmSplitTensorBase`. The base holds the k6 tensor
shell: the `tmm2d_tg_*` accumulate loop, the 6144-half `twb` W chunk, the store. It exposes
exactly one overridable stage, `stage16` - 16 elements per work item, decoded into `twb`. A
format derives, binds its own weight views, and overrides `stage16` alone. 16 is not an
arbitrary granularity: it is the base GEMV arm's own granularity, so each format's decode ports
into its `stage16` verbatim, the arm's `va[]` store becoming a `twb` store. This is the shape
sec.2.2g's MoE tensor twins ride, applied to the dense sites; as there, the q8 twin stays its
own template because its body is a different staging mechanism.

Each format's `stage16` takes one of three forms, inherited from that format's base GEMV arm:

- **byte-parallel compose** (k3, k2, and the q5 arm of k45) - masked uint-wide shifts produce
one `qv` per four elements, with the byte-position shift folded into exact power-of-two
pre-scales. It replaced a per-element bit compose and measures 1.4x it
(`benchmarks/matmul/bench_metal_kq_race.das --tiers mm`, m5).
- **staged-grid slab** (iq3s, iq3xxs) - the 2 KB / 1 KB grid staged into threadgroup memory
once per threadgroup.
- **direct constant-table gather** (iq2s, iq2xs, iq2xxs) - the u64 grid pair read straight off
the hoisted tables, no slab.

Each format stamps two instances, `T` (`XT = float`) and `TH` (`XT = float16`); this family has
no tall or double-buffered twins. Every stamp compiles only behind its own crown
(`metal_tensor_crowned("kq_mulmm_<fmt>")`) in `pf_compile_kq_iquant_tensor_twins`, and
`pf_enc_kq_site_mm` dispatches a twin only when both the crown flag and the PSO are live, so a
box with no tensor toolchain never leaves the base kernels.
39 changes: 39 additions & 0 deletions modules/dasLLAMA/ARCHITECTURE_MEASUREMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,42 @@ commit (the remedy `performance/REVIEW.md` allows when the measured `modules/das
byte-identical) therefore re-hashes and renames the file, and every `records/<box>.json` row
whose `tune_sha` named the old file is repointed in the same change - a row left on the old
name points at a file that no longer exists.

### 2.21 An isolated kernel race is only as good as the graph it imitates {#kernel-race-fidelity}

A kernel A/B race times two spellings of one compute on a synthetic fixture. Three conditions
decide whether its winner is the winner the served graph would pick, and a race missing any of
them crowns confidently and wrongly:

- **Overlap.** The served graph's consecutive GEMVs write DIFFERENT output buffers, so the
scheduler overlaps them. A race chaining every dispatch through one output buffer serializes
on the write-after-read hazard; on the iq2xxs f4-slab twin that one difference read +9% for
an arm the served graph rejects at -8.6%.
- **A warmed clock.** An Apple GPU's clock governor ramps under load, and `race_pair_ms` runs
base-then-twin per round, so the first side pays the ramp the second rides. About 150 ms of
GPU work before the first timed round removes the bias; back-to-back dispatches inside each
timed encoder hold the clock there. A sparse invocation without the burn reads idle-clock
times and can flip a verdict outright.
- **A real site shape.** `REVIEW_GPU.md` binds this one.

Even a race meeting all three can be structurally blind. `kq_gemv_iq2xxs_f4` is the standing
case: every isolated regime crowns the twin and the served decode graph loses 8.6% with it,
because the effect is mixed-stream occupancy - it exists only when the GEMV runs beside the
rest of the token step. That crown is therefore NOT auto-raced. It is minted on the real shapes
by `harness/tune_kernels.das`'s serving confirm: two temporary manifests differing only in
`runtime.metal_tensor` membership, each served to
`benchmarks/lcpp_bench.das --for-debug-purposes --ngl 99 -p 32 -n 128 -r 3` under
`DAS_TUNE_MANIFEST`, the tg128 line the verdict. Both arms carry an EMPTY `kernels` section, so
they stamp identical fallbacks and the tg delta isolates the crown under test. The margin is
`CONFIRM_TG_MARGIN` = 1.005, best-of-3: the crown's serving win where it wins is +0.6% (m5) and
its serving loss where it loses is -8.6% (m4), so the margin only has to clear run noise, and a
spuriously minted crown costs a re-mint rather than a board row. No IQ2_XXS vehicle on the box
means no crown - the base kernel is the safe side - and the run says so with a provisioning
hint.

The per-format isolated rig is `benchmarks/matmul/bench_metal_kq_race.das`: synthetic planes,
no model, no tuner, every arm gated against a CPU plane-dequant oracle before it is timed, and
`--burn-ms` (default 150) spent on GPU work before each cell's first timed round. Its cells
chain every dispatch through ONE shared output buffer on purpose - the serialized regime is
the instrument's probe shape, imitating the reference tool it is compared against - and its
numbers reach the engine only through a human porting decision, never a minted crown.
1 change: 1 addition & 0 deletions modules/dasLLAMA/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Apple Accelerate / AMX float lane. `DASLLAMA_ACCEL` arms the whole group.
|---|---|---|---|
| `DASLLAMA_MODELS_DIR` | path | unset | Directory holding the .gguf models the probes, benches and tests load. dasllama-server's model catalog downloads here too when set. |
| `DASLLAMA_CONFIRM_MODEL` | path | auto-resolved from the models dir | Model used by the tuner's confirm gate (FULL path, not a bare filename). Unset: the gate auto-resolves from the models dir - the preferred confirm carrier, else the largest present q8 gguf; the fallback pins only when the box has no q8 model at all. |
| `DASLLAMA_CONFIRM_IQ2XXS` | path | auto-resolved from the models dir | IQ2_XXS vehicle for the tuner's serving-crown confirm (FULL path). Unset: the confirm auto-resolves the first *IQ2_XXS*.gguf in the models dir; none found = no crown (the base kernel serves). |
| `DASLLAMA_BATCH_CHUNKS` | text | unset | Override the batched-dispatch chunk count in the 1-core GEMM probe. |
| `DASLLAMA_PROBE_MODEL` | text | Qwen3VL-8B-Instruct-Q8_0.gguf | Model filename (inside the models dir) the image-turn attribution probe loads. |
| `DASLLAMA_BATCH_GRID_2D` | number | unset | Use the 2D batch grid in the parity probe. |
Expand Down
Loading
Loading