Skip to content

cuda: add experimental native Flash Next family and local tensor parallelism - #952

Draft
akol1 wants to merge 137 commits into
ashhart:mainfrom
akol1:codex/flashnext-cuda-native
Draft

akol1 wants to merge 137 commits into
ashhart:mainfrom
akol1:codex/flashnext-cuda-native

Conversation

@akol1

@akol1 akol1 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Native Flash Next CUDA family on the maintained engine: ModelOpt NVFP4/CSF expert weights, MXFP8 and BF16 dense projections, GDN/attention/HC operators, two-rank placement, draft verification and family-scoped tests. The shared scheduler, Recall, admission, health and HTTP contracts remain in the core.

This includes the bounded dependencies from #904, #906, #907, #908, #912, #929 and #945, preserving their commits and the original Flash Next contributors. Related discussion: #472; prior contributor work: #476 and #869.

Source: beec1aad6c967f6e32aa14a3fd21ca8358710279; current main base: b090e3b19014bfa1eb2b7f38ea3cc4d39abec9be.

Review fixes:

Final-source local validation, Zig 0.17.0, macOS 26.6.2 arm64:

  • zig build native test test-golden test-kernel-contract fuzz-cortheon -Dcpu=apple_m1 -Dversion=1.0.0 -j2: 236/236 steps succeeded; 2,740 passed and four skipped tests among executed artifacts, with unchanged build artifacts reused from cache. The standard workflow includes existing Metal unit tests; the new restore/checker/dispatch regressions are CPU-only.
  • Frozen server corpus: 309/319 equal, ten documented known differences, zero unexpected differences.
  • The four actual Cuda.open/snapRestore cases pass in Debug and Fast. Before the guard, both drafted cases reproduce the defect while both plain controls pass. Removing destination poison or the finite-error guard makes the projection controls fail.
  • CUDA-enabled Linux cross compilation: 69/69 steps succeeded with explicit placeholder image assets. These are compile-only outputs; never execute or deploy them.
  • Whole-tree formatting and whitespace pass. Lean: 517 inherited findings, zero new. Separate-agent source review checked callback wiring, preserved test registrations and explicit test reachability; configured-identity review of this head remains pending.

Contributor follow-up on this head, both commits by @balancedscorpion from akol1/TensorFold#1, without a merge commit:

  • ab7f7baa test(cuda): clear experts-rows buffers on the kernels' stream — test only. Rig.zeros cleared each buffer with fill8(0, null), which is cuMemsetD8 on the default stream, while the row kernels run on the rig's non-blocking stream; on sm_121 the clear could land after the decode kernel wrote the one-row reference output. Clearing on the rig stream removes the overlap. His receipt: thirty alternating runs of tf-cuda-test experts-rows on the same build, 8/30 failed before and 0/30 after.
  • b6fd0d92 docs: measure the ModelOpt FP4-Local checkpoint on one GB10 — docs only, the new "The ModelOpt checkpoint on one GB10" section: nvidia/Qwen3.8-Flash-Next-FP4-Local at b9ce18a6, three runs of the defaults, concurrent equal to solo and drafted equal to "draft": false, cold first tokens to 131,072, and the admission numbers for a 128 GB machine.

Neither commit changes served code, so the local validation above remains the receipt for the served source. Six other test files also pair fill8(0, null) with a non-blocking stream; they are unchanged here and none has shown the failure.

Historical device evidence, bound to 46836015d7d8fcdab4882aea8075fa69c996d05b:

  • Exact-source Linux CUDA build: 124/124 steps with real NVCC 13.3.73 and SM 80/86/89/120/121 fatbins.
  • Actual SM 120 execution on RTX PRO 6000 Blackwell:
SUMMARY 32 kernels: 32 pass gates 1-4, 0 fail, 0 known findings; 5 controls: 5 red as planted, 0 missed; 0 limit problems
  • Resource records cover all 34 added family symbols and all 98 production registrations for all five compiled architectures; both resource controls fail as planted on each. Only SM 120 ran on hardware.
  • Nine direct family device commands passed at that source: flashnext-hc, flashnext-attn, route-rows, norm-rows, experts-rows, flashnext-draft-affine, flashnext-shared, flashnext-nvfp4-tp, and flashnext-dense-hc. HC modes 2 and 4 each compared 17,408 independent FP64 columns: zero worse and 1,053 improved.
  • This review increment changes no GPU kernel source, CUDA runtime arithmetic, numerical contract adapter or launch record. It does change host lifecycle and test checking. No fresh device execution is claimed for the updated head; the strengthened direct fixtures require rerunning.

Remaining qualification gates:

  • Numerical framework coverage is still 22 existing kernels plus ten added instances covering seven of the 34 family symbols. The remaining 27 symbols need complete adapters, including fused GDN, GDN primitives, direct attention partials/merge, and other attention/HC/dense/router/norm/embedding operators. Resource registration and additional FP64/row tests do not substitute for those adapters.
  • Full-model teacher-forced histories, all top-token differences and margins, same-engine draft/plain equality, prompt/decode and recurrent seams, concurrent/solo equality, cancellation, partial acceptance and clean restart remain pending for this integrated source. No tie-policy waiver or cross-implementation exactness claim is made.
  • Fixed-workload performance, memory and supported-profile receipts remain pending. No full-model performance figure is claimed.
  • Positive prefix retention and speculative cache reuse remain unqualified; use --prompt-cache-gib 0. Text profiles only. SM 80/86/89/121 hardware and Metal family execution are unrun.

This remains a draft. GitHub CI on b6fd0d92 is running; the green desktop, mobile, Zig format and No GPU host/contract/golden/fuzz/lean run cited above was for beec1aad6. Compiler and kernel success do not qualify a checkpoint or the complete serving runtime.

BobClawblaw and others added 30 commits October 14, 2026 18:37
…wer: batched prompt passes (prefill_many, addStreams), a backend's refusals for a memory guard, follow() for a tensor-parallel rank 1, shared-pool admission fields, image and video inputs for the native server (vision_inputs, media_fetch, qwen_image, stb_image)
…cel; lean rule and zig fmt

setup() returned cancel()'s true for a background warm pass that yields to a newer request, so the caller
launched the job cancel() had just finished (SEGV in lane_host_warm_test). It now returns false, as for a drop.

Comments one line each; lean_check skips zig/vendor/ (stb_image.h, as on ashhart#726). lane_host.zig and
lanes/engine.zig stay under 600 lines: the batched prompt pass moves to lane_host_admit.zig (many, one) and
lanes/open.zig (addStreams, adoptPrefilled), aliased where they were.
…rozen words (the errors/content-image golden)
…nsorfold-native takes memcpy from libSystem again (zig build test on macOS)
…eports; images wait for the shared decoder; no refused hook

- prompt_cache: Entry.pins, Store.pin/unpin. victimBut passes pinned entries over, so a later job's lookup in
  admit.many (or a keep during the batched pass) no longer frees a state an earlier job has yet to restore; a keep
  with nothing evictable fails instead of unwrapping null. LaneHost.setup pins, reported and requeue unpin.
- vision: zig/vendor/stb dropped and no longer compiled. qwen_image.decode refuses by name (NoDecoder) until the
  shared runtime decoder (pinned libjpeg-turbo) lands; lean_check's vendor exemption goes with it.
- lanes: Backend.VTable.refused, Engine.refused, cuda_bound's forwarding and the fake's stray refusedFn removed;
  nothing consumed them.
…roup-32 token, so the CUDA replay starts from the row the Python engine reads.
… so the linear-attention input is that mixed row.
…f the mixed row on one token, so the DeltaNet input is that projection.
…y match the host chain on one token, all 6144 gated values and all 2560 projected values, so the mixer branch is that row.
…, the write-back exact and its sums within 1 ulp, one normed value 1 step off, and the mixed row within 2 steps, so the expert input is that row.
…he shared expert exact, router logits within 29184 ulps, two weights 1 step off, the activations exact, and every down value the same bf16 with fp32 within 16384 ulps.
…the projection, the prepared heads, the one-key output, the gate and the output row all exact.
…one projected value 1 step off, and the prepared heads, the one-key output, the gate and the output row all exact.
… one projected value 1 step off, and the prepared heads, the one-key output, the gate and the output row all exact.
… the projection, the prepared heads, the one-key output, the gate and the output row all exact.
… the projection, the prepared heads, the one-key output, the gate and the output row all exact.
… the projection, the prepared heads, the one-key output, the gate and the output row all exact.
… two projected values 1 step off, one gate value 1 step off with group sums within 1024 ulps, and the prepared heads, the one-key output and the output row all exact.
… one gate value 1 step off with group sums within 8192 ulps, and the projection, the prepared heads, the one-key output and the output row all exact.
… one prepared key value 1 step off, and the projection, the other prepared heads, the one-key output, the gate and the output row all exact.
… one projected value 1 step off, and the prepared heads, the one-key output, the gate and the output row all exact.
… one projected value 1 step off, and the prepared heads, the one-key output, the gate and the output row all exact.
… two projected values 1 step off, and the prepared heads, the one-key output, the gate and the output row all exact.
…token, all 2560 mixed values exact, and three of 248320 scores 1 step off.
…m; prompts past 512 keys; LR7 capture.

The ids and position uploads were synchronous copies, which do not wait for the non-blocking compute stream, so
a later row overwrote a queued row's token id or position: wrong tokens on most prompts. Both now go on the
stream. attnStep launches one 512-key chunk per started chunk of the row's keys (as attention.attention does)
instead of always one, so prompts run to the captured NCH * 512 keys; the lanes engine defaults to that window
and refuses a larger one. The prompt check prints each run's top five scores and its timing.

zig/tests/cuda/flashnext: capture.py and capture.sh (Python engine 0.6.1, drafts off, greedy, 1536 cache slots)
and the Unbound-LR7 prompts with their references; flashnext-prompt matches the teacher paths on all three.
…ules and device weight bytes from what it declares; Flash Next registers

The registry gains flashnext.native. What was Nemotron's by name is now read from the family's declarations: its
prompt-cache copies (snap_bytes, snap_save, snap_restore, snap_drop, and snap_ready's spare buffers), the store's rules
(cache_rules; without them a state keeps at any row with the MTP head's one token on, ashhart#670), the quiet wait before a
warm pass and the lone driver's hand-over budget. Nemotron declares the copies, the wait and the budget, so its paths
are unchanged; a family without copies gets no prompt cache, and the startup line says so. A family that keeps part of
its checkpoint off the device says what it uploads (deviceWeightBytes) and admission counts that, not every shard:
Flash Next leaves its n-gram tables mapped and loads no vision tower, so the MLX 4-bit checkpoint uploads 73.3 of its
105.4 GiB, where counting every shard made the default reserve on a GB10 refuse it by 0.1 GiB. --slide is refused
before anything loads for a family that declares no learner.

The Flash Next family names what the host reads: one segment, its prompt rows, each stream's bytes, its refusals in
words, and the native host's lone-driver signature.
@ashhart

ashhart commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Thank you, @akol1, the maintained integration and affine/ModelOpt stages make #952 the Flash Next CUDA base for us to finish.

We checked beec1aa. cuda_lanes.zig:237-244 silently clears drafting on the fourth active lane; we will remove that fallback with correctly sized/priced state and a four-plus drafted-lane regression. TP draft snapshots also need implementation or a clear startup refusal for the unsupported cache combination.

We will carry @ZackSample's #869 tests/fixtures with credit, qualify affine single-rank first, then ModelOpt, then TP. The named --vision refusal stays until owned images are implemented. Please retain the teacher/reference and state-seam fixtures for fresh device gates, including actual draft activity, cache equality and NVIDIA default math.

Posted by Ash's AI Assistant Synthena

@cwschroeder cwschroeder left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction to my earlier review and its linked CPU runner: the old --mode fast command placed -Ofast after all module declarations, leaving the root in Debug. That invocation did not prove Fast coverage. The corrected runner applies the mode before every -M and asserts the root mode at compile time in its temporary export.

I reran the original pinned candidate with that correction and independently checked the adopted guard at beec1aad: all four behavior cases pass in Debug and Fast. Removing only the two guards in a temporary copy makes both drafted cases fail in each mode; both plain restore controls still pass. These exercise actual lane open and explicit restore with virtual CUDA callbacks and a nonblocking local control peer. No CUDA/NCCL library or device is opened.

Astra found no material regression in the adoption delta. The guard and test file retain the offered patch and authorship, so my bounded restore finding is closed. Whole-tree format and whitespace pass; lean is 517/517 against main 6ee828c2, zero new. All four current-head GitHub checks are green. This leaves Ash's fourth-lane drafting and unsupported-cache startup work with the existing owner, and adds no TP+MTP snapshot support, model-parity or performance qualification.

@balancedscorpion

balancedscorpion commented Oct 18, 2026 •

Copy link
Copy Markdown

Thanks for putting this together. I've been running this branch on a DGX Spark (GB10, sm_121) with NVIDIA's ModelOpt checkpoint, nvidia/Qwen3.8-Flash-Next-FP4-Local, and spotted a bug in one of the CUDA tests, so I'm leaving the details here in case they help.

experts-rows fails intermittently on sm_121 (test only). tf-cuda-test experts-rows at beec1aad6 sometimes fails with FAIL experts_rows SwiGLU rows: first difference at byte 0, 76800 of 76800 words differ, because the one-row reference reads all or partly zero. Rig.zeros clears with fill8(0, null), which is cuMemsetD8 on the default stream, but the kernels run on a non-blocking stream that doesn't wait for it, so the clear can land after the decode kernel has written. The fix is one line:

-        try b.fill8(0, null);
+        // on the kernels' stream: it is non-blocking, so a memset on the default stream may land after them
+        try b.fill8(0, r.s.handle);

Thirty runs each, alternated with the same build flags: 8/30 failed before, 0/30 after. Six other test files pair fill8(0, null) with non-blocking streams (exlr8_tests, bench, oracle_tests, exlr8_dense_tests, libs_tests, runtime_tests). I haven't seen those fail and didn't check them. The fix is also in akol1#1 against this branch, if that's easier to take.

While I had it running I checked the rest of the ModelOpt path on this machine, and everything else held up: concurrent equals solo, drafted equals "draft": false, chunked equals serial at the seams, and replies from the cache, after a cancel and after a restart all equal fresh ones. The numbers are below in case they're useful. The second commit of that PR has them as a recipe subsection, but feel free to ignore it.

Measurements: the ModelOpt checkpoint on one GB10 at beec1aad6
  • Environment.
    • TensorFold beec1aad6c967f6e32aa14a3fd21ca8358710279, built with nvcc 13.3 and zig 0.17.0 (-Dnvcc -Dsm=121 -Doptimize=fast), served with TENSORFOLD_CUDA_KERNELS=native (the build image names another kernel set).
    • NVIDIA GB10 (sm_121), driver 580.178.04, 128 GB unified memory (MemTotal 121 GiB). Ubuntu 24.04.5, kernel 7.0.0-1019-nvidia, aarch64. CUDA 13.3.1 in the serving image.
    • nvidia/Qwen3.8-Flash-Next-FP4-Local revision b9ce18a6887e18c089fdea8509a33e63429a3300: NVFP4 g16 routed experts with CSF scale planes, MXFP8 g32 shared experts and dense projections, an NVFP4 n-gram table, NVFP4 MTP experts, and bf16 embeddings and head.
    • tensorfold-native serve <checkpoint> --context 32768 --parallel auto --max-tokens 32768 --prompt-cache-gib 0 --thinking.
  • Kernel contract (tf-cuda-test kernel-contract on sm_121): SUMMARY 32 kernels: 32 pass gates 1-4, 0 fail, 0 known findings; 5 controls: 5 red as planted, 0 missed; 0 limit problems. flashnext-hc, flashnext-attn, route-rows, norm-rows, flashnext-draft-affine, flashnext-shared, flashnext-nvfp4-tp and flashnext-dense-hc pass every run. experts-rows is the race above.
  • Tests (CI's No GPU job at beec1aad6 plus the fix): zig build test 1,483/1,490 passed, 7 skipped (no libjpeg-turbo). test-kernel-contract, test-golden and fuzz-cortheon pass. test-cuda-host test-flashnext-contract-host for sm_121: 204/204. lean_check.py: 0 new. zig fmt --check: clean.
  • Exactness (greedy, --prompt-cache-gib 0 unless stated):
    • bench_concurrent.py --alone --serial --levels 1,4,8 --tokens 256 --reps 3, three runs: concurrent equals solo 78/78 and drafted equals "draft": false 6/6 in each.
    • Draft activity at 1, 2, 3, 4, 6 and 8 concurrent requests: every reply equals its "draft": false run. Streams without drafts: 0/1, 0/2, 0/3, 1/4, 3/6, 5/8, which is the fourth-lane fallback from the comment above.
  • Seams. flashnext-fidelity-single (chunked) against -serial at 7 fixed histories of 20 to 6,496 tokens: 7/7 score-row files byte-equal (sha256 prefixes: chat-turn ba034b8e9d4e3866, code-short f8a9025da48f9273, docs-3k d3f6ea2a0ec495d3, docs-7k 6d97ea7ecc2b4ee1, json-config 4f33d6db6281522e, math-steps 48af57b7d42b8087, prose-short 6cf6cc3411c2fbef).
  • Cache. With --no-drafts --prompt-cache-gib 8, a second conversation turn resumed at 24 and then 176 of its 183 prompt tokens, and both times equalled the same turn with no cache. A lone completion prompt isn't kept, since the cache keeps states at history marks. Not run with drafts.
  • Cancellation and restart. A 2,048-token greedy reply cancelled after 1 s and repeated, then repeated again after a restart, matched the fresh reply's token_sha256, with and without drafts.
  • Tokenizer. /tokenize matches the checkpoint's tokenizer.json (tokenizers 0.23.3) on all six prompt arrays.
  • Load and memory. The model takes 73.2 GiB, plus the 12.2 GiB reserve. With --parallel auto, a 32,768-token window admits 7 or 8 streams of 1.21 GiB, and 131,136 admits 2 of 4.03 GiB. A 262,144-token stream needs 7.79 GiB and fitted in one start of four (the room after load was 6.80, 9.80, 4.64 and 7.40 GiB), since the room tracks MemAvailable after load. Peak after a 131,072-token prompt: host 6.45 GB, device 83.0 GB.
  • Decode (bench_openai.py --tokens 256 --reps 3 --temperatures 0, fibonacci-raw / gpu-chat-no-think): 64.1-64.6 / 40.7-41.1 tok/s over three runs alternated with other settings in one session. bench_concurrent.py aggregate on code at 1 / 4 / 8 streams: 64.1-64.4 / 44.9-45.2 / 66.2-66.8. The same binary measured 57.0 / 36.2 in an earlier session on this machine, so only alternated runs compare.
  • Cold prompts (bench_cache.py --require-cold --tokens 64, exact token arrays cut from this repo's docs/**/*.md): first token at 2k / 8k / 32k / 64k / 128k in 1.83 / 7.11 / 29.2 / 60.1 / 131.7 s at a 131,136-token window, with 0 cached tokens on every request.
  • Not run.
    • A vLLM comparison and teacher fidelity: the model card's vLLM server (eugr/spark-vllm-b12x) refuses to start at its --gpu-memory-utilization 0.8 here, and ran the machine out of memory at 0.70 and 0.62.
    • The affine stage (no MLX checkpoint), TP (one GPU), other SMs and Metal (no hardware), and a 262k cold prompt (not admitted).

One more thing I noticed: with TF_FLASHNEXT_DRAFT_HEAD=affine4 and TF_FLASHNEXT_DRAFTS=8 the tokens were identical and it ran about 19% faster on code and 12% on chat here (76.4-77.0 / 45.9 against the defaults' 64.1-64.6 / 40.7-41.1 tok/s). Happy to write that up for the recipe if it would help, or to leave it until the drafting lanes are reworked.

Rig.zeros cleared each buffer with cuMemsetD8 on the default stream, while the kernels run on a non-blocking
stream that does not wait for it. On a GB10 (sm_121) the clear of the one-row reference's output sometimes
landed after the decode kernel wrote it, and experts-rows failed with an all-zero or partly zero reference:
8 of 30 runs at beec1aa, 0 of 30 with this change, alternated on one machine. Clear on the rows' stream.
The recipe's measurements covered only the MLX affine checkpoint. Add the ModelOpt one, measured with
beec1aa built unpatched for sm_121: decode and concurrency from bench_openai and bench_concurrent, cold
first tokens from bench_cache with exact token arrays, the served exactness checks, the seams at fixed
histories, and what the 128 GB machine admits.
@akol1

akol1 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

@balancedscorpion both commits from your branch are on this head with your authorship — ab7f7baa and b6fd0d92, rebased onto beec1aad6 without a merge commit.

The experts-rows diagnosis is right. Rig.zeros cleared each buffer with fill8(0, null), which is cuMemsetD8 on the default stream, while those row kernels are enqueued on the rig's non-blocking stream; on sm_121 the clear of the one-row reference's output could land after the decode kernel wrote it. Clearing on the rig stream closes that window, and 8/30 failing before against 0/30 after over thirty alternating runs on the same build is the receipt that failure needed. The PR body now cites both the fix and that count.

The recipe section went in as measured on your GB10, with the checkpoint revision, driver and window settings attached. Six other test files pair fill8(0, null) with a non-blocking stream (exlr8_tests, bench, oracle_tests, exlr8_dense_tests, libs_tests, runtime_tests); we left them unchanged because none has shown the failure, but the same one-line fix is welcome there if you see it.

New head b6fd0d925dbb95cef44bc25342eea469e7911080. Neither commit changes served code, so the existing receipts stay bound to the served source; CI is running on the new head.

ashhart added a commit that referenced this pull request Oct 18, 2026
…eckpoints, with MTP drafts and tensor ranks

The family registry lists Flash Next beside Qwen. The merge keeps the existing cuda_window.zig and drops #952's copy
of it, cuda_context.zig. Opened carries both the drain deadline and #952's follower hook, and the kernel list, the
experts pack instantiations and the build's module and check lists take both sides.
ashhart added a commit that referenced this pull request Oct 18, 2026
…d a drain that fails before it succeeds

#952 already carries #869's staging fix. Its regression now also fails the first copy beside the second copy and the
record, treats a failed copy as possibly queued, tells an event wait from the stream drain it requires, and adds a
failed drain then a retry. The guard stays set until the drain succeeds. The production staging code is unchanged.

Co-authored-by: ZackSample <134018356+ZackSample@users.noreply.github.com>
ashhart added a commit that referenced this pull request Oct 18, 2026
…ions.zig, so cuda.zig stays under 600 lines

#952's rank and follower wiring took native/cuda.zig to 611 lines. cuda_options.zig now sets the ranks, vision,
--precision and fp8-cache options a family takes, unchanged, and openWith calls it. cuda.zig is 593 lines. The
staging test's title now says it covers a failed first upload too.
ashhart added a commit that referenced this pull request Oct 18, 2026
… nvcc 13.2's in its own set

The release fatbins are nvcc 13.0's, and #952 recorded its launches as nvcc 13.2 compiles them. For the release
compiler 29 sm_120 and sm_121 entries of 17 launches were stale, and the live check on a GB10 failed 15 of them. Those
records now hold 13.0's usage, read from every SM's fatbins on a GB10, and the 13.2 set holds each launch's 13.2
usage, read the same way on an RTX PRO 6000. sm_80, sm_86 and sm_89 were already right.
ashhart added a commit that referenced this pull request Oct 18, 2026
@ashhart

ashhart commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Thank you, @akol1, @ZackSample, @AdrianBinDC, @Bizuayeu and @cwschroeder, for Flash Next CUDA, the staging fix and its tests.

We merged #952 for 1.1.0 in 53abe45, retaining #869's credit. Drafts stay enabled for every admitted stream. Tensor-rank drafting with a prompt cache is refused early and names --prompt-cache-gib 0. Experimental images use the shared Qwen-VL tower, with completion proven before failed cleanup releases memory.

The reported GB10 MLX 4-bit run with its MTP head passes 17 exactness cells, two/four concurrent comparisons and 12 cached chat turns. PNG checks pass; JPEG checks still need the decoder. Launch records distinguish nvcc 13.0 and 13.2.

NVIDIA GB10 affine-4 MTP passed 17/17, concurrent equality and chat 12/12; image warm-turn/JPEG gaps and the NVFP4 pair remain limited.

Posted by Ash's AI Assistant Synthena

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants