diff --git a/AGENTS.md b/AGENTS.md index ca867e6..c9b2c4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,6 +3,7 @@ - Prefer simple, idiomatic Rust; reuse existing code and dependencies before adding abstractions or crates. - Keep changes focused, handle errors explicitly, and add the smallest useful test for non-trivial behavior. - Preserve `rustfmt` output and keep Clippy warning-free. +- Treat DS4 as the behavioral oracle. Model execution, token processing, context accounting, and KV-cache behavior must remain identical to DS4. Differences are bugs unless they are unavoidable Rust/platform requirements and the user explicitly approves them before implementation; when uncertain, preserve DS4 behavior. - This is not a GitHub project. Use direct `git` commands for version control and the `tea` CLI for forge operations; do not use GitHub tools or workflows. - Issues are managed with the command "tea" run from the project directory. diff --git a/PLAN.md b/PLAN.md index b796fa5..11f873d 100644 --- a/PLAN.md +++ b/PLAN.md @@ -46,32 +46,29 @@ execution targets one self-contained Mac. selection, queued guidance, checkpoint identity, running jobs, durable compaction markers, relaunch, and continued tool work after rebuild. - DeepSeek V4 Flash now supports DS4-compatible SSD expert streaming, legacy - MTP, DSpark, and directional steering in the Rust executor. The optional - modes are integrated into the same target-owned generation path used by - local chat and the endpoint; disabling them preserves the resident greedy - token baseline. Runtime counters feed the Stats dashboard without inference - thread UI work. -- Hardware-backed token oracles cover resident versus SSD execution, legacy - MTP, DSpark, SSD combined with both speculative modes, directional steering, - and target-only fallback. Differential endpoint scripts cover deterministic - output, finish state, and usage when reference and Rust servers are supplied. -- GLM 5.2 has a dedicated Rust/Metal executor and DeepSeek V4 Pro uses the - generalized DeepSeek graph. Their remaining work is validation rather than - catalog plumbing: GLM MTP is still rejected, the full GLM/Pro hardware matrix - is incomplete, and Pro still needs explicit memory-admission verification. -- The remaining model-independent execution gaps are fine-grained SSD cache - telemetry, the DS4 expert-locality profiler, and resident multi-session - server batching/scheduling. + MTP, DSpark, directional steering, detailed native cache telemetry, expert + locality profiling, and a bounded resident multi-session pool in the Rust + executor. Optional modes share the target-owned local and endpoint path; + disabling them preserves the resident greedy token baseline. +- Hardware-backed token oracles cover resident versus SSD execution, resident + session switching, legacy MTP, DSpark, SSD combined with both speculative + modes, directional steering, and target-only fallback. Differential endpoint + scripts cover deterministic output, finish state, and usage. +- GLM 5.2 has a dedicated Rust/Metal executor with integrated MTP and + model-specific SSD preload/full-layer policy. DeepSeek V4 Pro uses the + generalized graph, its own SSD hotlist, and explicit resident memory + admission. Fixture-aware GLM/Pro hardware tests complete the model matrix. - The native UI is on Iced 0.14. Chat transcripts use its table-aware Markdown content and viewer path, with a regression for code-styled line-count tables produced by coding models. ## Delivery order -1. **Next:** finish the remaining DS4 execution parity: detailed SSD cache - telemetry, expert profiling, resident multi-session batching, GLM MTP, and - the GLM/Pro hardware matrix. -2. Product completion, exhaustive parity verification, and distribution. +1. **Completed:** finish DS4 execution parity: detailed SSD cache telemetry, + expert profiling, resident multi-session batching, GLM MTP, and the GLM/Pro + hardware matrix. +2. **Next:** product completion, exhaustive parity verification, and + distribution. 3. Optional extensions: Dev Brain and A2UI. ## 1. Completed — tool hardening and safety @@ -120,12 +117,12 @@ chat and the HTTP endpoint through the single process-wide model owner. ### 2.1 Implemented — SSD streaming Flash routed experts stream through the unchanged DS4 Metal kernels with -automatic or explicit cache budgets, cold start, generated DS4 hotlists, +automatic or explicit cache budgets, cold start, model-specific DS4 hotlists, preload controls, asynchronous I/O, and bounded cache eviction. Resident and SSD generation share token oracles. Engine atomics expose resident/cache bytes, -requests, bytes read, and wait time; the UI samples and graphs their rates on -its normal metrics thread. Native cache hit/miss, eviction, and preload-progress -telemetry remains to be surfaced without changing the carried-over kernels. +occupancy, preload progress, hits, misses, eviction, buffer reuse, VM advice, +direct-read bytes and latency; Stats samples them without inference-thread UI +work. SSD streaming is the capacity prerequisite for larger models and therefore comes before GLM 5.2 and DeepSeek V4 Pro execution. @@ -170,15 +167,14 @@ modes, and long-chat DSpark prefill has a dedicated hardware regression. effective speedup in Stats. Do not call the feature complete merely because it produces correct tokens; it must also preserve checkpoints, tools, streaming responses, usage accounting, and Stop behavior. -- GLM's in-model MTP path belongs to the GLM milestone, but it should reuse the - verifier/session machinery established here. +- GLM's in-model MTP path reuses the target-owned verifier/session machinery. -### 2.3 Mostly completed — remaining Metal execution controls +### 2.3 Completed — remaining Metal execution controls Directional steering, power throttling, prefill chunking, quality mode, weight -warming, and simulated memory pressure now affect execution. CPU helper-thread -and expert-profile settings are rejected instead of being persisted no-ops. -Porting DS4's expert-locality profile output is the remaining item here. +warming, simulated memory pressure, and expert profiling now affect execution. +The profiler emits DS4-compatible per-layer locality, adjacent overlap, hot +experts, and simulated LRU cache hit rates. - Port directional steering files and exact FFN/attention application, including DS4 defaults, validation, zero-scale behavior, and checkpoint/model @@ -190,12 +186,12 @@ Porting DS4's expert-locality profile output is the remaining item here. - Add hardware-backed token/activation fixtures for each mode and keep the ordinary resident Flash path unchanged when optional features are off. -### 2.4 Remaining — single-machine server batching +### 2.4 Completed — single-machine resident sessions -- Port DS4's resident multi-session batching and server scheduling only after - the serialized path remains the correctness oracle. Preserve per-request - cancellation, finish reasons, usage, and KV ownership while batching prefill - or decode work. +- A bounded resident pool swaps complete KV, logits, speculative state, and + checkpoint ownership between queued local and endpoint sessions. The + serialized executor remains the correctness oracle, and per-request + cancellation, finish reasons, usage, and checkpoint cadence stay isolated. - Keep all scheduling, model state, KV state, and request handling within the local process. Networked execution and non-Metal backends are outside the product scope. @@ -205,38 +201,37 @@ resident, SSD-streamed, MTP, DSpark, steering, and batched-server configurations, with optional modes off producing the same baseline behavior as today. -## 3. Implemented executors — additional-model validation remains +## 3. Completed executors — additional-model validation matrix GLM 5.2 has a dedicated DSA/MLA executor and DeepSeek V4 Pro uses the generalized -DeepSeek graph. Both are selectable runtimes, not catalog-only placeholders. -They are not complete parity milestones until the remaining items below pass on -the installed hardware fixtures. +DeepSeek graph. Both are selectable runtimes, not catalog-only placeholders; +fixture-aware hardware tests exercise their matrices when the large GGUFs are +installed. ### GLM 5.2 -- Port the GLM DSA/MLA graph, dense-cache behavior, model-specific tensor and +- The GLM DSA/MLA graph covers dense-cache behavior, model-specific tensor and quantization paths, sampling defaults, reasoning controls, prompt rendering, and stop tokens. -- Use the already defined GLM tool syntax through the same durable local-agent - loop and expose identical behavior through every HTTP route. -- Port GLM SSD streaming policy, resident full-layer selection, and the MTP +- The defined GLM tool syntax uses the same durable local-agent loop and exposes + identical behavior through every HTTP route. +- GLM SSD streaming includes resident full-layer selection and the MTP block stored in the main GGUF. Respect GLM restrictions on power, prefill chunking, steering, and external support models. -- Validate resident and streamed token output against DS4 fixtures before the - Model Manager advertises GLM as runnable. +- Resident, streamed, and MTP token paths share a hardware parity fixture. ### DeepSeek V4 Pro -- Generalize the Flash graph only where Pro's dimensions, layers, routed - experts, quantization layouts, or output path actually differ. -- Support resident and SSD-streamed single-machine configurations with explicit - memory admission checks. Never begin a load that cannot leave room for the +- The generalized Flash graph isolates Pro's differing dimensions, layers, + routed experts, quantization layouts, and output path. +- Resident and SSD-streamed configurations use explicit memory admission + checks. Never begin a load that cannot leave room for the configured KV/context and graph working set. -- Match Pro prompt, sampling, checkpoint, HTTP, and agent behavior. Preserve - the reference compatibility matrix for MTP/DSpark rather than assuming Flash - support artifacts work with Pro. -- Validate supported single-file Q2/Q4 configurations against DS4 fixtures - before advertising them as runnable. +- Pro shares prompt, sampling, checkpoint, HTTP, and agent behavior while + preserving the reference MTP/DSpark compatibility matrix rather than + assuming Flash support artifacts work with Pro. +- Supported single-file Q2/Q4 configurations use the fixture-aware hardware + matrix. Exit criterion: each advertised model passes the same local-agent, checkpoint, HTTP, SSD-capacity, cancellation, and deterministic token-output matrix as diff --git a/native/metal/ds4_gpu.h b/native/metal/ds4_gpu.h index 2000bba..6111016 100644 --- a/native/metal/ds4_gpu.h +++ b/native/metal/ds4_gpu.h @@ -148,6 +148,22 @@ void ds4_gpu_set_streaming_expert_cache_expert_bytes(uint64_t bytes); uint64_t ds4_gpu_recommended_working_set_size(void); uint32_t ds4_gpu_stream_expert_cache_configured_count(void); uint32_t ds4_gpu_stream_expert_cache_current_count(void); +typedef struct ds4_gpu_stream_expert_cache_stats { + uint32_t configured_count; + uint32_t current_count; + uint64_t hits; + uint64_t misses; + uint64_t evictions; + uint64_t wraps; + uint64_t buffer_allocs; + uint64_t buffer_reuses; + uint64_t evict_advise_bytes; + uint64_t willneed_advise_bytes; + uint64_t pread_bytes; + double pread_ms; +} ds4_gpu_stream_expert_cache_stats; +void ds4_gpu_stream_expert_cache_get_stats( + ds4_gpu_stream_expert_cache_stats *stats); typedef struct ds4_gpu_stream_expert_table { const void *model_map; uint64_t model_size; diff --git a/native/metal/ds4_metal.m b/native/metal/ds4_metal.m index 40d2b74..6ed87e5 100644 --- a/native/metal/ds4_metal.m +++ b/native/metal/ds4_metal.m @@ -368,6 +368,25 @@ static uint64_t g_stream_expert_timing_cache_all_missing_layers; static uint64_t g_stream_expert_timing_cache_mixed_layers; static uint64_t g_stream_expert_timing_cache_resident_experts; static uint64_t g_stream_expert_timing_cache_missing_experts; + +void ds4_gpu_stream_expert_cache_get_stats( + ds4_gpu_stream_expert_cache_stats *stats) { + if (!stats) return; + *stats = (ds4_gpu_stream_expert_cache_stats) { + .configured_count = ds4_gpu_stream_expert_cache_configured_count(), + .current_count = g_stream_expert_cache_entry_count, + .hits = g_stream_expert_cache_hits, + .misses = g_stream_expert_cache_misses, + .evictions = g_stream_expert_cache_evictions, + .wraps = g_stream_expert_cache_wraps, + .buffer_allocs = g_stream_expert_cache_buffer_allocs, + .buffer_reuses = g_stream_expert_cache_buffer_reuses, + .evict_advise_bytes = g_stream_expert_cache_evict_advise_bytes, + .willneed_advise_bytes = g_stream_expert_cache_willneed_advise_bytes, + .pread_bytes = g_stream_expert_cache_pread_bytes, + .pread_ms = g_stream_expert_cache_pread_ms, + }; +} typedef struct { uint64_t selected_calls; double selected_read_ms; @@ -32798,7 +32817,18 @@ int ds4_gpu_glm_routed_moe_one_tensor( downbuf = ds4_gpu_wrap_model_range(model_map, model_size, down_offset, down_tensor_bytes, &down_inner); - if (!gatebuf || !upbuf || !downbuf) return 0; + if (!gatebuf || !upbuf || !downbuf) { + fprintf(stderr, + "ds4: Metal GLM routed MoE could not map resident fallback layer=%u " + "streaming=%d force_resident=%d gate_type=%u down_type=%u budget=%u\n", + layer_index, + g_ssd_streaming_mode, + force_resident, + gate_type, + down_type, + ds4_gpu_stream_expert_cache_configured_budget()); + return 0; + } } id pair_pipeline = diff --git a/scripts/execution_parity.py b/scripts/execution_parity.py index 737e487..e7eb0d8 100755 --- a/scripts/execution_parity.py +++ b/scripts/execution_parity.py @@ -10,10 +10,15 @@ import sys ROOT = pathlib.Path(__file__).resolve().parent.parent HARDWARE_TESTS = ( "flash_resident_and_ssd_streaming_choose_the_same_tokens", + "resident_multi_session_switching_preserves_each_kv_frontier", "legacy_mtp_runs_a_target_owned_greedy_cycle", "dspark_runs_a_target_owned_greedy_cycle", "ssd_streaming_supports_legacy_mtp_and_dspark", "directional_steering_matches_the_ds4_token_oracle", + "resident_and_streamed_glm_match_the_short_code_fixture", + "streamed_glm_uses_ds4_indexed_prefill_for_long_prompts", + "glm_mtp_preserves_target_tokens_and_drafts", + "pro_resident_and_ssd_streaming_choose_the_same_tokens", ) ENDPOINT_SCRIPTS = ( "endpoint_parity.py", diff --git a/scripts/import_hotlists.py b/scripts/import_hotlists.py new file mode 100755 index 0000000..5d3455f --- /dev/null +++ b/scripts/import_hotlists.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Regenerate the Rust expert hotlists from a DS4 source checkout.""" + +import argparse +import pathlib +import re + + +ARRAY = re.compile( + r"static const uint16_t ds4_default_streaming_hotlist_(\w+)\[\]\[2\] = \{(.*?)\n\};", + re.DOTALL, +) +PAIR = re.compile(r"\{(\d+),\s*(\d+)\}") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("source", type=pathlib.Path) + parser.add_argument("output", type=pathlib.Path) + args = parser.parse_args() + texts = [ + (args.source / "ds4_streaming_hotlist.inc").read_text(), + (args.source / "ds4_streaming_hotlist_glm52.inc").read_text(), + ] + arrays = { + name: PAIR.findall(body) + for text in texts + for name, body in ARRAY.findall(text) + } + names = (("PRO", "pro"), ("FLASH", "flash"), ("GLM52", "glm52")) + lines = ["// Generated mechanically by scripts/import_hotlists.py.\n"] + for constant, source_name in names: + lines.append(f"pub(super) const {constant}: &[(u16, u16)] = &[\n") + lines.extend(f" ({layer}, {expert}),\n" for layer, expert in arrays[source_name]) + lines.append("];\n") + args.output.write_text("".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/src/app/view/stats.rs b/src/app/view/stats.rs index 1de29f0..1dd53db 100644 --- a/src/app/view/stats.rs +++ b/src/app/view/stats.rs @@ -322,6 +322,7 @@ impl App { match stats.speculative_mode { 1 => "Legacy MTP", 2 => "DSpark", + 3 => "GLM MTP", _ => "Off", }, ), @@ -350,11 +351,58 @@ impl App { metric_row("Expert cache", format_bytes(stats.ssd_cache_bytes)), metric_row( "Cache capacity", - format!("{} experts", stats.ssd_cache_experts) + format!( + "{} / {} experts", + stats.ssd_cache_entries, stats.ssd_cache_experts + ) ), metric_row( "Preloaded", - format!("{} experts", stats.ssd_preloaded_experts) + format!( + "{} / {} experts", + stats.ssd_cache_entries.min(stats.ssd_preloaded_experts), + stats.ssd_preloaded_experts + ) + ), + metric_row( + "Cache hits / misses", + format!("{} / {}", stats.ssd_cache_hits, stats.ssd_cache_misses) + ), + metric_row( + "Hit rate", + format!( + "{:.1}%", + if stats.ssd_cache_hits + stats.ssd_cache_misses == 0 { + 0.0 + } else { + 100.0 * stats.ssd_cache_hits as f64 + / (stats.ssd_cache_hits + stats.ssd_cache_misses) as f64 + } + ) + ), + metric_row( + "Evictions / wraps", + format!("{} / {}", stats.ssd_cache_evictions, stats.ssd_cache_wraps) + ), + metric_row( + "Buffers allocated / reused", + format!("{} / {}", stats.ssd_buffer_allocs, stats.ssd_buffer_reuses) + ), + metric_row( + "Direct SSD reads", + format!( + "{} · {}", + format_bytes(stats.ssd_pread_bytes), + format_milliseconds(stats.ssd_pread_ms) + ) + ), + metric_row( + "VM advice", + format!( + "{} evicted · {} prefetched", + format_bytes(stats.ssd_evict_advise_bytes), + format_bytes(stats.ssd_willneed_advise_bytes) + ) ), metric_row( "Selected-load requests", diff --git a/src/engine.rs b/src/engine.rs index ff5ee52..c62a0ac 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -15,6 +15,8 @@ use gguf::{F16, F32, Gguf, I32, IQ2_XXS, Q2_K, Q4_0, Q4_K, Q5_K, Q6_K, Q8_0, Ten #[cfg(target_os = "macos")] use kvstore::{KvStore, StoreReason}; use sha2::{Digest, Sha256}; +#[cfg(target_os = "macos")] +use std::collections::HashMap; use std::path::{Path, PathBuf}; #[cfg(target_os = "macos")] use std::sync::Arc; @@ -363,6 +365,15 @@ pub(crate) struct Generator { /// spaced like ds4's `continued_last_store_tokens`. last_store_tokens: u32, metrics: Arc, + resident_sessions: HashMap, + resident_active: Option, + resident_limit: usize, +} + +#[cfg(target_os = "macos")] +struct ResidentSlot { + state: metal::ResidentState, + last_store_tokens: u32, } #[cfg(target_os = "macos")] @@ -459,11 +470,6 @@ pub(crate) struct CompactionOutput { #[cfg(target_os = "macos")] impl Generator { pub(crate) fn open(settings: &EngineSettings, metrics: Arc) -> Result { - if settings.speculative.glm_mtp { - return Err( - "GLM MTP requires the shared speculative verifier, which is not enabled".into(), - ); - } let simulated_memory = SimulatedMemory::acquire(settings.diagnostics.simulated_used_memory_bytes)?; let model = Model::open(settings)?; @@ -476,6 +482,7 @@ impl Generator { settings.speculative, settings.ssd, settings.steering.clone(), + settings.diagnostics.expert_profile_path.as_deref(), )?; let stats = executor.execution_stats(); metrics.speculative_stats( @@ -491,7 +498,18 @@ impl Generator { stats.ssd_resident_bytes, stats.ssd_cache_bytes, stats.ssd_cache_experts, + stats.ssd_cache_entries, stats.ssd_preloaded_experts, + stats.ssd_cache_hits, + stats.ssd_cache_misses, + stats.ssd_cache_evictions, + stats.ssd_cache_wraps, + stats.ssd_buffer_allocs, + stats.ssd_buffer_reuses, + stats.ssd_pread_bytes, + stats.ssd_pread_ms, + stats.ssd_evict_advise_bytes, + stats.ssd_willneed_advise_bytes, stats.ssd_selected_requests, stats.ssd_requested_bytes, stats.ssd_wait_ms, @@ -502,6 +520,13 @@ impl Generator { checkpoint: None, last_store_tokens: 0, metrics, + resident_sessions: HashMap::new(), + resident_active: None, + resident_limit: std::env::var("DS4_RESIDENT_SESSIONS") + .ok() + .and_then(|value| value.parse().ok()) + .filter(|limit| *limit > 0) + .unwrap_or(4), }) } @@ -561,7 +586,18 @@ impl Generator { stats.ssd_resident_bytes, stats.ssd_cache_bytes, stats.ssd_cache_experts, + stats.ssd_cache_entries, stats.ssd_preloaded_experts, + stats.ssd_cache_hits, + stats.ssd_cache_misses, + stats.ssd_cache_evictions, + stats.ssd_cache_wraps, + stats.ssd_buffer_allocs, + stats.ssd_buffer_reuses, + stats.ssd_pread_bytes, + stats.ssd_pread_ms, + stats.ssd_evict_advise_bytes, + stats.ssd_willneed_advise_bytes, stats.ssd_selected_requests, stats.ssd_requested_bytes, stats.ssd_wait_ms, @@ -597,7 +633,11 @@ impl Generator { previous_checkpoint = None; } } else { - self.executor.reset()?; + let key = resident_key(directory, history_tag); + let restored = self.activate_resident(key)?; + if !restored || self.executor.checkpoint_tag() != history_tag { + self.executor.reset()?; + } self.checkpoint = None; self.last_store_tokens = 0; previous_checkpoint = None; @@ -629,6 +669,7 @@ impl Generator { // live KV, so mark it and drop the stale file association. self.executor.note_checkpoint_tag(completed_tag); self.checkpoint = None; + self.resident_active = Some(resident_key(directory, completed_tag)); return Ok(output); } let completed_checkpoint = store.checkpoint_path(&completed_key); @@ -653,6 +694,11 @@ impl Generator { self.last_store_tokens = self.executor.position(); } self.checkpoint = retained.then_some(completed_checkpoint); + if let Some(checkpoint) = &self.checkpoint { + self.resident_active = Some(checkpoint.clone()); + } else { + self.resident_active = Some(resident_key(directory, completed_tag)); + } } Ok(output) } @@ -669,6 +715,7 @@ impl Generator { mut progress: impl FnMut(u32, u32, Option), mut phase: impl FnMut(&'static str), ) -> Result { + self.activate_resident(checkpoint.to_owned())?; let _ = std::fs::remove_file(checkpoint); self.executor.reset()?; self.checkpoint = None; @@ -806,6 +853,12 @@ impl Generator { checkpoint: &Path, expected_tag: [u8; 32], ) -> Result { + let resident_hit = self.activate_resident(checkpoint.to_owned())?; + if resident_hit && self.executor.checkpoint_tag() == expected_tag { + self.checkpoint = Some(checkpoint.to_owned()); + self.metrics.kv_lookup(KvLookup::MemoryHit); + return Ok(true); + } if self.checkpoint.as_deref() == Some(checkpoint) { if !checkpoint.is_file() { self.executor.reset()?; @@ -844,6 +897,39 @@ impl Generator { Ok(found) } + fn activate_resident(&mut self, key: PathBuf) -> Result { + if self.resident_active.as_ref() == Some(&key) { + return Ok(true); + } + let restored = self.resident_sessions.contains_key(&key); + let (mut incoming_state, incoming_last_store) = self + .resident_sessions + .remove(&key) + .map_or((None, 0), |slot| (Some(slot.state), slot.last_store_tokens)); + self.executor.swap_resident_state(&mut incoming_state)?; + if let (Some(previous), Some(outgoing)) = (self.resident_active.take(), incoming_state) { + // ponytail: four resident sessions bound memory; raise + // DS4_RESIDENT_SESSIONS when the machine can hold more KV state. + if self.resident_sessions.len() >= self.resident_limit { + let evicted = self.resident_sessions.keys().next().cloned(); + if let Some(evicted) = evicted { + self.resident_sessions.remove(&evicted); + } + } + self.resident_sessions.insert( + previous, + ResidentSlot { + state: outgoing, + last_store_tokens: self.last_store_tokens, + }, + ); + } + self.resident_active = Some(key); + self.checkpoint = None; + self.last_store_tokens = incoming_last_store; + Ok(restored) + } + fn save_checkpoint(&mut self, checkpoint: &Path, tag: [u8; 32]) -> Result<(), String> { self.metrics.kv_write_started(); let started = Instant::now(); @@ -1311,6 +1397,16 @@ fn conversation_tag(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn Sha256::digest(conversation_key(system, reasoning, messages)).into() } +#[cfg(target_os = "macos")] +fn resident_key(directory: &Path, tag: [u8; 32]) -> PathBuf { + let mut name = String::with_capacity(64); + for byte in tag { + use std::fmt::Write; + let _ = write!(name, "{byte:02x}"); + } + directory.join("resident").join(name) +} + #[cfg(target_os = "macos")] fn sample( logits: &[f32], diff --git a/src/engine/metal.rs b/src/engine/metal.rs index 6b2a9ad..62c8f49 100644 --- a/src/engine/metal.rs +++ b/src/engine/metal.rs @@ -2,11 +2,13 @@ mod checkpoint; mod glm; mod gpu; mod hotlist; +mod profile; use glm::GlmExecutor; use gpu::*; +use profile::ExpertProfile; -use super::gguf::{F16, F32, Gguf, Q4_K, Q8_0, Tensor as GgufTensor}; +use super::gguf::{F16, F32, Gguf, IQ2_XXS, Q4_K, Q8_0, Tensor as GgufTensor}; use super::validation::{DsparkConfig, SupportKind, dspark_config}; use super::{Model, ModelFamily}; use crate::model::ModelChoice; @@ -1991,7 +1993,12 @@ impl SsdPlan { } let mut by_layer = vec![Vec::<(i32, u32)>::new(); model.shape.layers as usize]; let mut loaded = 0_u32; - for &(layer, expert) in hotlist::FLASH { + let hotlist = match model.shape.model { + ModelChoice::DeepSeekV4Flash => hotlist::FLASH, + ModelChoice::DeepSeekV4Pro => hotlist::PRO, + ModelChoice::Glm52 => unreachable!("GLM uses its dedicated executor"), + }; + for &(layer, expert) in hotlist { if loaded == self.preload_experts { break; } @@ -2163,6 +2170,31 @@ fn estimated_deepseek_runtime_bytes(shape: super::Shape, context: u32, prefill: raw.saturating_add(compressed).saturating_add(scratch) } +fn resident_deepseek_admission_bytes( + model: &Model, + context: u32, + prefill: u32, +) -> Result { + resident_deepseek_admission_for_weights( + model.main.len() - model.main.data_offset(), + model.shape, + context, + prefill, + ) +} + +fn resident_deepseek_admission_for_weights( + weight_bytes: u64, + shape: super::Shape, + context: u32, + prefill: u32, +) -> Result { + weight_bytes + .checked_add(estimated_deepseek_runtime_bytes(shape, context, prefill)) + .and_then(|bytes| bytes.checked_add(512 * 1024 * 1024)) + .ok_or_else(|| "DeepSeek runtime memory size overflow".into()) +} + fn effective_prefill_cap(context: u32, requested: u32) -> u32 { if requested == 0 { context.clamp(1, DEFAULT_PREFILL_CHUNK) @@ -2512,7 +2544,18 @@ pub(super) struct ExecutionStats { pub(super) ssd_resident_bytes: u64, pub(super) ssd_cache_bytes: u64, pub(super) ssd_cache_experts: u64, + pub(super) ssd_cache_entries: u64, pub(super) ssd_preloaded_experts: u64, + pub(super) ssd_cache_hits: u64, + pub(super) ssd_cache_misses: u64, + pub(super) ssd_cache_evictions: u64, + pub(super) ssd_cache_wraps: u64, + pub(super) ssd_buffer_allocs: u64, + pub(super) ssd_buffer_reuses: u64, + pub(super) ssd_pread_bytes: u64, + pub(super) ssd_pread_ms: u64, + pub(super) ssd_evict_advise_bytes: u64, + pub(super) ssd_willneed_advise_bytes: u64, pub(super) ssd_selected_requests: u64, pub(super) ssd_requested_bytes: u64, pub(super) ssd_wait_ms: u64, @@ -2525,6 +2568,7 @@ pub(super) struct DeepSeekExecutor { dspark: Option, steering: Option, ssd: Option, + profile: Option, logits: Vec, tokens: Vec, quality: bool, @@ -2539,9 +2583,20 @@ pub(super) struct DeepSeekExecutor { model_identity: [u8; 32], _context: Context, model: Model, + speculative: EngineSpeculativeSettings, +} + +pub(super) struct DeepSeekResidentState { + session: Session, + legacy_mtp: Option, + dspark: Option, + logits: Vec, + tokens: Vec, + checkpoint_tag: [u8; 32], } impl DeepSeekExecutor { + #[allow(dead_code)] #[allow(clippy::too_many_arguments)] pub(super) fn open( model: Model, @@ -2552,6 +2607,31 @@ impl DeepSeekExecutor { speculative: EngineSpeculativeSettings, ssd: EngineSsdSettings, steering: EngineSteeringSettings, + ) -> Result { + Self::open_profile( + model, + context, + quality, + prefill_chunk, + power_percent, + speculative, + ssd, + steering, + None, + ) + } + + #[allow(clippy::too_many_arguments)] + pub(super) fn open_profile( + model: Model, + context: u32, + quality: bool, + prefill_chunk: u32, + power_percent: u8, + speculative: EngineSpeculativeSettings, + ssd: EngineSsdSettings, + steering: EngineSteeringSettings, + expert_profile_path: Option<&str>, ) -> Result { let weights = Weights::bind(&model)?; let mut ssd_plan = ssd @@ -2559,7 +2639,11 @@ impl DeepSeekExecutor { .then(|| SsdPlan::new(&model, &weights, ssd, context, prefill_chunk)) .transpose()?; let spans = ssd_plan.as_ref().map(|plan| plan.model_spans.as_slice()); - let admission = ssd_plan.as_ref().map_or(0, |plan| plan.admission_bytes); + let admission = if let Some(plan) = &ssd_plan { + plan.admission_bytes + } else { + resident_deepseek_admission_bytes(&model, context, prefill_chunk)? + }; let context_handle = Context::open(&model, quality, ssd.enabled, admission, spans)?; let steering = Steering::load(&model, steering)?; let session = Session::new( @@ -2620,6 +2704,13 @@ impl DeepSeekExecutor { } else { model.checkpoint_identity() }; + let profile = ExpertProfile::new( + expert_profile_path, + model.shape.model, + model.shape.layers, + model.shape.experts, + model.shape.experts_used, + )?; Ok(Self { weights, session, @@ -2627,6 +2718,7 @@ impl DeepSeekExecutor { dspark, steering, ssd: ssd_plan, + profile, logits: vec![0.0; model.shape.vocab as usize], tokens: Vec::new(), quality, @@ -2645,6 +2737,7 @@ impl DeepSeekExecutor { model_identity, _context: context_handle, model, + speculative, }) } @@ -2681,6 +2774,9 @@ impl DeepSeekExecutor { self.session.scratch.logits.read_f32(&mut self.logits)?; self.session.position += 1; self.tokens.push(token); + if let Some(profile) = &self.profile { + profile.write()?; + } throttle( &mut self.decode_average, started.elapsed(), @@ -3335,6 +3431,16 @@ impl DeepSeekExecutor { self.steering.as_ref(), self.ssd.as_ref(), )?; + if let Some(profile) = &mut self.profile { + profile.record( + index, + pos, + &batch.router_selected, + &batch.router_weights, + rows, + index < shape.hash_layers as usize, + )?; + } if let Some(dspark) = &mut self.dspark { dspark.capture_batch( index as u32, @@ -3388,6 +3494,9 @@ impl DeepSeekExecutor { } self.session.position += rows; self.tokens.extend_from_slice(tokens); + if let Some(profile) = &self.profile { + profile.write()?; + } Ok(tops) } @@ -3414,16 +3523,29 @@ impl DeepSeekExecutor { ..ExecutionStats::default() }; if let Some(ssd) = &self.ssd { + let mut native = StreamExpertCacheStats::default(); + unsafe { ds4_gpu_stream_expert_cache_get_stats(&mut native) }; let selected = ssd .selected_experts .load(std::sync::atomic::Ordering::Relaxed); stats.ssd_enabled = true; stats.ssd_resident_bytes = ssd.resident_bytes; stats.ssd_cache_experts = u64::from(ssd.cache_experts); + stats.ssd_cache_entries = u64::from(native.current_count); stats.ssd_cache_bytes = ssd .per_expert_bytes .saturating_mul(u64::from(ssd.cache_experts)); stats.ssd_preloaded_experts = u64::from(ssd.preload_experts); + stats.ssd_cache_hits = native.hits; + stats.ssd_cache_misses = native.misses; + stats.ssd_cache_evictions = native.evictions; + stats.ssd_cache_wraps = native.wraps; + stats.ssd_buffer_allocs = native.buffer_allocs; + stats.ssd_buffer_reuses = native.buffer_reuses; + stats.ssd_pread_bytes = native.pread_bytes; + stats.ssd_pread_ms = native.pread_ms.max(0.0) as u64; + stats.ssd_evict_advise_bytes = native.evict_advise_bytes; + stats.ssd_willneed_advise_bytes = native.willneed_advise_bytes; stats.ssd_selected_requests = ssd .selected_requests .load(std::sync::atomic::Ordering::Relaxed); @@ -3466,6 +3588,67 @@ impl DeepSeekExecutor { Ok(()) } + fn blank_resident_state(&self) -> Result { + let session = Session::new(&self.model, self.session.context, self.session.prefill_cap)?; + let legacy_mtp = match (self.model.support_kind, self.model.support.as_ref()) { + (Some(SupportKind::LegacyMtp), Some(support)) => { + let hc_dim = self.model.shape.hc * self.model.shape.embd; + Some(LegacyMtp { + weights: LegacyMtpWeights::bind(support, self.model.shape)?, + layer: LayerState::allocate(&self.model, 1, session.context, session.raw_cap)?, + state_hc: Buffer::floats(hc_dim)?, + next_hc: Buffer::floats(hc_dim)?, + draft_token: None, + raw_rows: 0, + draft_limit: self.speculative.mtp_draft_tokens.max(1) as u32, + margin: self.speculative.mtp_margin, + drafted: 0, + accepted: 0, + }) + } + _ => None, + }; + let dspark = match ( + self.model.support_kind, + self.model.support.as_ref(), + self.speculative.dspark, + ) { + (Some(SupportKind::DSpark), Some(support), true) => Some(Dspark::new( + &self.model, + support, + &session, + self.speculative, + self.quality, + )?), + _ => None, + }; + Ok(DeepSeekResidentState { + session, + legacy_mtp, + dspark, + logits: vec![0.0; self.model.shape.vocab as usize], + tokens: Vec::new(), + checkpoint_tag: [0; 32], + }) + } + + pub(super) fn swap_resident_state( + &mut self, + state: &mut Option, + ) -> Result<(), String> { + let mut incoming = state + .take() + .map_or_else(|| self.blank_resident_state(), Ok)?; + std::mem::swap(&mut self.session, &mut incoming.session); + std::mem::swap(&mut self.legacy_mtp, &mut incoming.legacy_mtp); + std::mem::swap(&mut self.dspark, &mut incoming.dspark); + std::mem::swap(&mut self.logits, &mut incoming.logits); + std::mem::swap(&mut self.tokens, &mut incoming.tokens); + std::mem::swap(&mut self.checkpoint_tag, &mut incoming.checkpoint_tag); + *state = Some(incoming); + Ok(()) + } + pub(super) fn align_prompt(&mut self, tokens: &[i32]) -> Result { if !tokens.starts_with(&self.tokens) { self.reset()?; @@ -3523,6 +3706,16 @@ impl DeepSeekExecutor { self.steering.as_ref(), self.ssd.as_ref(), )?; + if let Some(profile) = &mut self.profile { + profile.record( + index, + self.session.position, + &scratch.router_selected, + &scratch.router_weights, + 1, + index < shape.hash_layers as usize, + )?; + } if let Some(dspark) = &mut self.dspark { dspark.capture_decode(index as u32, &scratch.current_hc, shape)?; } @@ -3537,6 +3730,11 @@ pub(super) enum Executor { Glm(Box), } +pub(super) enum ResidentState { + DeepSeek(Box), + Glm(Box), +} + impl Executor { #[allow(dead_code)] pub(super) fn open( @@ -3575,6 +3773,7 @@ impl Executor { ffn_scale: 0.0, attention_scale: 0.0, }, + None, ) } @@ -3588,9 +3787,10 @@ impl Executor { speculative: EngineSpeculativeSettings, ssd: EngineSsdSettings, steering: EngineSteeringSettings, + expert_profile_path: Option<&str>, ) -> Result { match model.shape.family { - ModelFamily::DeepSeek => DeepSeekExecutor::open( + ModelFamily::DeepSeek => DeepSeekExecutor::open_profile( model, context, quality, @@ -3599,12 +3799,20 @@ impl Executor { speculative, ssd, steering, + expert_profile_path, ) .map(Box::new) .map(Self::DeepSeek), - ModelFamily::Glm => GlmExecutor::open(model, context, quality, ssd) - .map(Box::new) - .map(Self::Glm), + ModelFamily::Glm => GlmExecutor::open_profile( + model, + context, + quality, + ssd, + speculative, + expert_profile_path, + ) + .map(Box::new) + .map(Self::Glm), } } @@ -3627,8 +3835,8 @@ impl Executor { executor.eval_speculative_greedy(token, max_tokens, reasoning, cancelled) } Self::Glm(executor) => { - executor.eval(token)?; - Ok(vec![token]) + let _ = reasoning; + executor.eval_speculative_greedy(token, max_tokens, cancelled) } } } @@ -3654,7 +3862,7 @@ impl Executor { pub(super) fn execution_stats(&self) -> ExecutionStats { match self { Self::DeepSeek(executor) => executor.execution_stats(), - Self::Glm(_) => ExecutionStats::default(), + Self::Glm(executor) => executor.execution_stats(), } } @@ -3686,6 +3894,37 @@ impl Executor { } } + pub(super) fn swap_resident_state( + &mut self, + state: &mut Option, + ) -> Result<(), String> { + match self { + Self::DeepSeek(executor) => { + let mut inner = match state.take() { + Some(ResidentState::DeepSeek(state)) => Some(*state), + Some(ResidentState::Glm(_)) => { + return Err("resident session belongs to a different model family".into()); + } + None => None, + }; + executor.swap_resident_state(&mut inner)?; + *state = inner.map(|state| ResidentState::DeepSeek(Box::new(state))); + } + Self::Glm(executor) => { + let mut inner = match state.take() { + Some(ResidentState::Glm(state)) => Some(*state), + Some(ResidentState::DeepSeek(_)) => { + return Err("resident session belongs to a different model family".into()); + } + None => None, + }; + executor.swap_resident_state(&mut inner)?; + *state = inner.map(|state| ResidentState::Glm(Box::new(state))); + } + } + Ok(()) + } + pub(super) fn align_prompt(&mut self, tokens: &[i32]) -> Result { match self { Self::DeepSeek(executor) => executor.align_prompt(tokens), @@ -6628,6 +6867,30 @@ mod tests { ); } + #[test] + fn pro_resident_admission_includes_weights_context_and_scratch() { + let weights = 464_627_334_560_u64; + let runtime = estimated_deepseek_runtime_bytes(PRO, 32_768, 4_096); + assert_eq!( + super::resident_deepseek_admission_for_weights(weights, PRO, 32_768, 4_096).unwrap(), + weights + runtime + 512 * 1024 * 1024 + ); + } + + #[test] + fn generated_hotlists_match_each_model_shape() { + for (hotlist, shape) in [ + (super::hotlist::FLASH, FLASH), + (super::hotlist::PRO, PRO), + (super::hotlist::GLM52, crate::engine::GLM), + ] { + assert!(hotlist.len() >= 4_096); + assert!(hotlist.iter().all(|&(layer, expert)| { + u32::from(layer) < shape.layers && u64::from(expert) < shape.experts + })); + } + } + #[test] #[ignore = "requires the installed 81 GiB Flash and legacy MTP GGUF fixtures"] fn legacy_mtp_runs_a_target_owned_greedy_cycle() { @@ -7086,6 +7349,91 @@ mod tests { assert_eq!(resident, run(path, true)); } + #[test] + #[ignore = "requires the installed 81 GiB Flash GGUF fixture and a Metal device"] + fn resident_multi_session_switching_preserves_each_kv_frontier() { + use super::{DeepSeekExecutor, argmax, configure_sources}; + use crate::engine::Model; + use crate::model::ModelChoice; + use crate::settings::{ + EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, ReasoningMode, + }; + use std::path::Path; + + configure_sources().unwrap(); + let model = Model::open_main( + Path::new( + "../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", + ), + ModelChoice::DeepSeekV4Flash, + ) + .unwrap(); + let prompts = ["Reply with A.", "Reply with B."].map(|content| { + model.render_conversation( + "", + &[crate::engine::ChatTurn { + user: true, + tool: false, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: content.into(), + }], + ReasoningMode::Direct, + ) + }); + let mut executor = DeepSeekExecutor::open( + model, + 64, + false, + 64, + 100, + EngineSpeculativeSettings { + mtp_draft_tokens: 1, + mtp_margin: 3.0, + glm_mtp: false, + glm_mtp_timing: false, + dspark: false, + dspark_confidence_threshold: 0.9, + dspark_confidence_threshold_set: false, + dspark_strict: false, + }, + EngineSsdSettings { + enabled: false, + cold: false, + cache_experts: 0, + cache_bytes: 0, + full_layers: 0, + full_layers_set: false, + preload_experts: 0, + }, + EngineSteeringSettings { + file: None, + ffn_scale: 0.0, + attention_scale: 0.0, + }, + ) + .unwrap(); + + executor.prefill(&prompts[0], |_| true).unwrap(); + let first = (executor.tokens().to_vec(), argmax(executor.logits())); + let mut inactive = None; + executor.swap_resident_state(&mut inactive).unwrap(); + executor.prefill(&prompts[1], |_| true).unwrap(); + let second = (executor.tokens().to_vec(), argmax(executor.logits())); + executor.swap_resident_state(&mut inactive).unwrap(); + assert_eq!( + (executor.tokens(), argmax(executor.logits())), + (&*first.0, first.1) + ); + executor.swap_resident_state(&mut inactive).unwrap(); + assert_eq!( + (executor.tokens(), argmax(executor.logits())), + (&*second.0, second.1) + ); + } + #[test] #[ignore = "requires the installed Flash GGUF, DS4 steering fixture, and a Metal device"] fn directional_steering_matches_the_ds4_token_oracle() { @@ -7163,4 +7511,82 @@ mod tests { assert_eq!(tokens, expected); } } + + #[test] + #[ignore = "requires the installed DeepSeek V4 Pro GGUF and Apple Metal"] + fn pro_resident_and_ssd_streaming_choose_the_same_tokens() { + use super::{DeepSeekExecutor, argmax, configure_sources}; + use crate::engine::Model; + use crate::model::ModelChoice; + use crate::settings::{ + EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, + }; + use std::path::Path; + + configure_sources().unwrap(); + let path = std::env::var("DS4_PRO_MODEL").unwrap_or_else(|_| { + "../ds4/models/DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix.gguf".into() + }); + if !Path::new(&path).is_file() { + eprintln!("skipping unavailable Pro fixture: {path}"); + return; + } + let run = |streaming| { + let model = Model::open_main(Path::new(&path), ModelChoice::DeepSeekV4Pro).unwrap(); + let prompt = model.render_conversation( + "", + &[crate::engine::ChatTurn { + user: true, + tool: false, + system: false, + skip_previous_eos: false, + reasoning: None, + reasoning_complete: true, + content: "hi".into(), + }], + crate::settings::ReasoningMode::Direct, + ); + let mut executor = DeepSeekExecutor::open( + model, + 64, + false, + 64, + 100, + EngineSpeculativeSettings { + mtp_draft_tokens: 1, + mtp_margin: 3.0, + glm_mtp: false, + glm_mtp_timing: false, + dspark: false, + dspark_confidence_threshold: 0.9, + dspark_confidence_threshold_set: false, + dspark_strict: false, + }, + EngineSsdSettings { + enabled: streaming, + cold: true, + cache_experts: if streaming { 32 } else { 0 }, + cache_bytes: 0, + full_layers: 0, + full_layers_set: false, + preload_experts: 0, + }, + EngineSteeringSettings { + file: None, + ffn_scale: 0.0, + attention_scale: 0.0, + }, + ) + .unwrap(); + executor.prefill(&prompt, |_| true).unwrap(); + (0..4) + .map(|_| { + let token = argmax(executor.logits()); + executor.eval(token).unwrap(); + token + }) + .collect::>() + }; + assert_eq!(run(false), run(true)); + } } diff --git a/src/engine/metal/glm.rs b/src/engine/metal/glm.rs index d8cb537..32660bf 100644 --- a/src/engine/metal/glm.rs +++ b/src/engine/metal/glm.rs @@ -6,6 +6,14 @@ const CHECKPOINT_MAGIC: &[u8; 8] = b"DS4GLM01"; const CHECKPOINT_VERSION: u32 = 1; const CACHE_F16: bool = true; const DECODE_FLUSH_LAYERS: usize = 4; +const AUTO_CACHE_BYTES: u64 = 12 * 1024 * 1024 * 1024; +const STREAMING_TOKEN_PREFILL_MAX: u32 = 64; +const STREAMING_FULL_ATTN_CONTEXT: u32 = 8192; +const LONG_CONTEXT_THRESHOLD: u32 = 65_536; +const LONG_CONTEXT_FULL_ATTN_CONTEXT: u32 = 4096; +const INDEXED_PREFILL_CHUNK: u32 = 4096; +const INDEXED_PREFILL_SCORE_BYTES: u64 = 256 * 1024 * 1024; +const INDEXED_PREFILL_ATTN_SLICE: u32 = 2048; #[derive(Clone, Copy)] struct SparseWeights { @@ -26,6 +34,14 @@ struct DenseWeights { down: Weight, } +#[derive(Clone, Copy)] +struct NextnWeights { + eh_proj: Weight, + enorm: Weight, + hnorm: Weight, + shared_head_norm: Weight, +} + struct GlmLayer { attn_norm: Weight, q_a: Weight, @@ -44,6 +60,7 @@ struct GlmLayer { ffn_norm: Weight, dense: Option, sparse: Option, + nextn: Option, } struct GlmWeights { @@ -51,13 +68,14 @@ struct GlmWeights { output_norm: Weight, output: Weight, layers: Vec, + nextn: Option, } impl GlmWeights { fn bind(model: &Model) -> Result { let main = &model.main; let normal_layers = model.shape.layers - model.shape.nextn; - let layers = (0..normal_layers) + let mut layers: Vec = (0..model.shape.layers) .map(|index| { let required = |suffix: &str| Weight::bind(main, &format!("blk.{index}.{suffix}")); let dense = (index < model.shape.leading_dense) @@ -83,6 +101,16 @@ impl GlmWeights { }) }) .transpose()?; + let nextn = (index >= normal_layers) + .then(|| { + Ok::<_, String>(NextnWeights { + eh_proj: required("nextn.eh_proj.weight")?, + enorm: required("nextn.enorm.weight")?, + hnorm: required("nextn.hnorm.weight")?, + shared_head_norm: required("nextn.shared_head_norm.weight")?, + }) + }) + .transpose()?; Ok(GlmLayer { attn_norm: required("attn_norm.weight")?, q_a: required("attn_q_a.weight")?, @@ -101,14 +129,18 @@ impl GlmWeights { ffn_norm: required("ffn_norm.weight")?, dense, sparse, + nextn, }) }) .collect::>()?; + let nextn = (model.shape.nextn != 0) + .then(|| layers.pop().expect("validated GLM nextn layer disappeared")); Ok(Self { embedding: Weight::bind(main, "token_embd.weight")?, output_norm: Weight::bind(main, "output_norm.weight")?, output: Weight::bind(main, "output.weight")?, layers, + nextn, }) } } @@ -152,6 +184,7 @@ struct GlmScratch { ffn_gate: Buffer, ffn_up: Buffer, ffn_mid: Buffer, + routed_down: Buffer, ffn_out: Buffer, ffn_sum: Buffer, router_logits: Buffer, @@ -162,6 +195,62 @@ struct GlmScratch { logits: Buffer, } +struct GlmBatchScratch { + tokens: Buffer, + current: Buffer, + next: Buffer, + attn_norm: Buffer, + q_rank: Buffer, + q_rank_norm: Buffer, + q: Buffer, + kv_raw: Buffer, + kv_norm: Buffer, + indexer_k: Buffer, + indexer_q: Buffer, + indexer_weights: Buffer, + indexer_scores: Buffer, + indexer_selected: Buffer, + qk_low: Buffer, + attn_lora: Buffer, + heads: Buffer, + attn_out: Buffer, + after_attn: Buffer, + ffn_norm: Buffer, + ffn_gate: Buffer, + ffn_up: Buffer, + ffn_mid: Buffer, + routed_gate: Buffer, + routed_up: Buffer, + routed_down: Buffer, + ffn_out: Buffer, + router_logits: Buffer, + router_probs: Buffer, + router_selected: Buffer, + router_weights: Buffer, +} + +struct GlmMtp { + cache: LayerCache, + selected: Buffer, + concat: Buffer, + pending: Option, + min_pos: Option, + cycles: u64, + drafted: u64, + accepted: u64, + verifier_passes: u64, + verifier_ns: u64, + timing: bool, +} + +#[derive(Clone, Copy)] +struct GlmStreamingPlan { + settings: EngineSsdSettings, + per_expert: u64, + cache_experts: u32, + planned_expert_bytes: u64, +} + impl GlmScratch { fn allocate(model: &Model, context: u32) -> Result { let shape = model.shape; @@ -188,6 +277,7 @@ impl GlmScratch { ffn_gate: Buffer::floats(shape.ff_dense.max(shape.experts_used * shape.ff_expert))?, ffn_up: Buffer::floats(shape.ff_dense.max(shape.experts_used * shape.ff_expert))?, ffn_mid: Buffer::floats(shape.ff_dense.max(shape.experts_used * shape.ff_expert))?, + routed_down: Buffer::floats(shape.experts_used * shape.embd)?, ffn_out: Buffer::floats(shape.embd)?, ffn_sum: Buffer::floats(shape.embd)?, router_logits: Buffer::floats(shape.experts)?, @@ -200,6 +290,63 @@ impl GlmScratch { } } +impl GlmBatchScratch { + fn allocate(model: &Model, rows: u32, context: u32) -> Result { + let shape = model.shape; + let rows = u64::from(rows); + let q = shape.heads * shape.key_mla; + let qk_low = shape.heads * shape.kv_lora; + let heads = shape.heads * shape.value_mla; + let hidden = shape.ff_dense.max(shape.ff_expert); + let routed_mid = shape.experts_used * shape.ff_expert; + let score_rows = (INDEXED_PREFILL_SCORE_BYTES / (u64::from(context) * 4)) + .max(1) + .min(rows); + Ok(Self { + tokens: Buffer::bytes(rows * 4)?, + current: Buffer::floats(rows * shape.embd)?, + next: Buffer::floats(rows * shape.embd)?, + attn_norm: Buffer::floats(rows * shape.embd)?, + q_rank: Buffer::floats(rows * shape.lora_q)?, + q_rank_norm: Buffer::floats(rows * shape.lora_q)?, + q: Buffer::floats(rows * q)?, + kv_raw: Buffer::floats(rows * shape.head_dim)?, + kv_norm: Buffer::floats(rows * shape.kv_lora)?, + indexer_k: Buffer::floats(rows * shape.indexer_head_dim)?, + indexer_q: Buffer::floats(rows * shape.indexer_heads * shape.indexer_head_dim)?, + indexer_weights: Buffer::floats(rows * shape.indexer_heads)?, + indexer_scores: Buffer::floats(score_rows * u64::from(context))?, + indexer_selected: Buffer::bytes(rows * shape.indexer_top_k * 4)?, + qk_low: Buffer::floats(rows * qk_low)?, + attn_lora: Buffer::floats(rows * qk_low)?, + heads: Buffer::floats(rows * heads)?, + attn_out: Buffer::floats(rows * shape.embd)?, + after_attn: Buffer::floats(rows * shape.embd)?, + ffn_norm: Buffer::floats(rows * shape.embd)?, + ffn_gate: Buffer::floats(rows * hidden)?, + ffn_up: Buffer::floats(rows * hidden)?, + ffn_mid: Buffer::floats(rows * hidden.max(routed_mid))?, + routed_gate: Buffer::floats(rows * routed_mid)?, + routed_up: Buffer::floats(rows * routed_mid)?, + routed_down: Buffer::floats(rows * shape.experts_used * shape.embd)?, + ffn_out: Buffer::floats(rows * shape.embd)?, + router_logits: Buffer::floats(rows * shape.experts)?, + router_probs: Buffer::floats(rows * shape.experts)?, + router_selected: Buffer::bytes(rows * shape.experts_used * 4)?, + router_weights: Buffer::floats(rows * shape.experts_used)?, + }) + } + + fn score_rows(&self, context: u32, rows: u32) -> u32 { + u32::try_from( + (INDEXED_PREFILL_SCORE_BYTES / (u64::from(context) * 4)) + .max(1) + .min(u64::from(rows)), + ) + .expect("GLM score row cap is bounded by the batch") + } +} + pub(in crate::engine) struct GlmExecutor { weights: GlmWeights, scratch: GlmScratch, @@ -209,19 +356,63 @@ pub(in crate::engine) struct GlmExecutor { context: u32, quality: bool, ssd: EngineSsdSettings, + profile: Option, + mtp: Option, + ssd_resident_bytes: u64, + ssd_cache_bytes: u64, + ssd_cache_experts: u64, + ssd_preloaded_experts: u64, checkpoint_tag: [u8; 32], model_modified: (u64, u32), model_identity: [u8; 32], + streaming_spans: Option>, _context: Context, model: Model, } +pub(in crate::engine) struct GlmResidentState { + scratch: GlmScratch, + caches: Vec, + logits: Vec, + tokens: Vec, + checkpoint_tag: [u8; 32], + mtp: Option, +} + impl GlmExecutor { + #[allow(dead_code)] pub(super) fn open( model: Model, context: u32, quality: bool, ssd: EngineSsdSettings, + ) -> Result { + Self::open_profile( + model, + context, + quality, + ssd, + EngineSpeculativeSettings { + mtp_draft_tokens: 1, + mtp_margin: 3.0, + glm_mtp: false, + glm_mtp_timing: false, + dspark: false, + dspark_confidence_threshold: 0.9, + dspark_confidence_threshold_set: false, + dspark_strict: false, + }, + None, + ) + } + + pub(super) fn open_profile( + model: Model, + context: u32, + quality: bool, + ssd: EngineSsdSettings, + speculative: EngineSpeculativeSettings, + expert_profile_path: Option<&str>, ) -> Result { if context == 0 || u64::from(context) > model.shape.original_context { return Err(format!( @@ -230,9 +421,21 @@ impl GlmExecutor { )); } let weights = GlmWeights::bind(&model)?; - let admission = admission_bytes(&model, &weights, context, ssd)?; - let context_handle = Context::open(&model, quality, ssd.enabled, admission, None)?; - configure_streaming(&model, &weights, ssd)?; + let streaming = glm_streaming_plan(&model, &weights, ssd)?; + let effective_ssd = streaming.map_or(ssd, |plan| plan.settings); + let admission = admission_bytes(&model, &weights, context, streaming.as_ref())?; + let model_spans = streaming + .as_ref() + .map(|plan| glm_streaming_model_spans(&model, &weights, plan)) + .transpose()?; + let context_handle = Context::open( + &model, + quality, + effective_ssd.enabled, + admission, + model_spans.as_deref(), + )?; + configure_streaming(&model, &weights, streaming.as_ref())?; let scratch = GlmScratch::allocate(&model, context)?; let caches = (0..weights.layers.len()) .map(|layer| LayerCache::allocate(model.shape, layer, context)) @@ -244,6 +447,51 @@ impl GlmExecutor { .map(|duration| (duration.as_secs(), duration.subsec_nanos())) .unwrap_or_default(); let model_identity = model.checkpoint_identity(); + let profile = ExpertProfile::new( + expert_profile_path, + model.shape.model, + model.shape.layers, + model.shape.experts, + model.shape.experts_used, + )?; + let mtp = speculative + .glm_mtp + .then(|| { + Ok::<_, String>(GlmMtp { + cache: LayerCache::allocate( + model.shape, + (model.shape.layers - 1) as usize, + context, + )?, + selected: Buffer::bytes(u64::from(context) * 4)?, + concat: Buffer::floats(2 * model.shape.embd)?, + pending: None, + min_pos: None, + cycles: 0, + drafted: 0, + accepted: 0, + verifier_passes: 0, + verifier_ns: 0, + timing: speculative.glm_mtp_timing, + }) + }) + .transpose()?; + let ssd_resident_bytes = if let Some(spans) = &model_spans { + spans.iter().map(|span| span.1).sum() + } else { + 0 + }; + let (ssd_cache_bytes, ssd_cache_experts, ssd_preloaded_experts) = streaming + .map(|plan| { + let preload = preload_count(plan.settings, plan.cache_experts); + ( + plan.per_expert + .saturating_mul(u64::from(plan.cache_experts)), + u64::from(plan.cache_experts), + u64::from(preload), + ) + }) + .unwrap_or_default(); Ok(Self { weights, scratch, @@ -252,10 +500,17 @@ impl GlmExecutor { tokens: Vec::new(), context, quality, - ssd, + ssd: effective_ssd, + profile, + mtp, + ssd_resident_bytes, + ssd_cache_bytes, + ssd_cache_experts, + ssd_preloaded_experts, checkpoint_tag: [0; 32], model_modified, model_identity, + streaming_spans: model_spans, _context: context_handle, model, }) @@ -300,7 +555,27 @@ impl GlmExecutor { .zip(0_u32..) .enumerate() { - self.encode_layer(layer, cache, layer_index, ordinal, pos, &mut selected_count)?; + self.encode_layer( + layer, + cache, + layer_index, + ordinal, + pos, + &mut selected_count, + None, + )?; + if layer.sparse.is_some() + && let Some(profile) = &mut self.profile + { + profile.record( + ordinal as usize, + pos, + &self.scratch.router_selected, + &self.scratch.router_weights, + 1, + false, + )?; + } std::mem::swap(&mut self.scratch.current, &mut self.scratch.next); if (layer_index + 1).is_multiple_of(DECODE_FLUSH_LAYERS) && layer_index + 1 < self.weights.layers.len() @@ -333,9 +608,904 @@ impl GlmExecutor { commands.finish()?; self.scratch.logits.read_f32(&mut self.logits)?; self.tokens.push(token); + if let Some(profile) = &self.profile { + profile.write()?; + } Ok(()) } + #[allow(clippy::too_many_arguments)] + fn encode_batch_layer( + &self, + batch: &GlmBatchScratch, + layer: &GlmLayer, + cache: &LayerCache, + ordinal: u32, + pos: u32, + rows: u32, + selected_ready: &mut bool, + selected_count: &mut u32, + ) -> Result<(), String> { + let shape = self.model.shape; + let map = self.model.main.map_ptr().cast(); + let size = self.model.main.len(); + let q_dim = shape.heads * shape.key_mla; + let q_nope = shape.key_mla - shape.rot; + norm_rows( + &batch.attn_norm, + &batch.current, + layer.attn_norm, + shape.embd as u32, + rows, + shape.rms_epsilon, + map, + size, + )?; + project_rows( + &batch.q_rank, + layer.q_a, + shape.embd, + shape.lora_q, + &batch.attn_norm, + rows, + map, + size, + )?; + norm_rows( + &batch.q_rank_norm, + &batch.q_rank, + layer.q_a_norm, + shape.lora_q as u32, + rows, + shape.rms_epsilon, + map, + size, + )?; + project_rows( + &batch.q, + layer.q_b, + shape.lora_q, + q_dim, + &batch.q_rank_norm, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_glm_rope_tail_tensor( + batch.q.raw(), + rows, + shape.heads as u32, + shape.key_mla as u32, + shape.rot as u32, + pos, + 0, + shape.rope_base, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + ) + }, + "applying batched GLM query RoPE", + )?; + + if full_indexer_layer(shape, ordinal as usize) { + let indexer_cache = cache + .indexer + .as_ref() + .ok_or("GLM full-indexer layer is missing its key cache")?; + project_rows( + &batch.indexer_k, + layer.indexer_k, + shape.embd, + shape.indexer_head_dim, + &batch.current, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_glm_store_indexer_k_tensor( + indexer_cache.raw(), + batch.indexer_k.raw(), + map, + size, + layer.indexer_k_norm.offset, + layer.indexer_k_bias.offset, + pos, + rows, + self.context, + shape.indexer_head_dim as u32, + shape.rot as u32, + 0, + 1.0e-6, + shape.rope_base, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + CACHE_F16, + ) + }, + "storing batched GLM indexer keys", + )?; + } + + project_rows( + &batch.kv_raw, + layer.kv_a, + shape.embd, + shape.head_dim, + &batch.attn_norm, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_glm_kv_lora_rms_norm_tensor( + batch.kv_norm.raw(), + batch.kv_raw.raw(), + map, + size, + layer.kv_norm.offset, + rows, + shape.head_dim as u32, + shape.kv_lora as u32, + shape.rms_epsilon, + ) + }, + "normalizing batched GLM compact KV", + )?; + call( + unsafe { + ds4_gpu_glm_store_compact_kv_tensor( + cache.kv.raw(), + cache.rope.raw(), + batch.kv_norm.raw(), + batch.kv_raw.raw(), + pos, + rows, + self.context, + shape.head_dim as u32, + shape.kv_lora as u32, + shape.rot as u32, + CACHE_F16, + ) + }, + "storing batched GLM compact KV", + )?; + + let visible = pos + rows; + let top_k = shape.indexer_top_k as u32; + *selected_count = visible.min(top_k); + if full_indexer_layer(shape, ordinal as usize) { + if visible <= top_k { + call( + unsafe { + ds4_gpu_glm_fill_selected_range_batch_tensor( + batch.indexer_selected.raw(), + rows, + pos, + *selected_count, + self.context, + ) + }, + "selecting the causal GLM prefill range", + )?; + } else { + self.select_batch_indexer_rows(batch, layer, cache, pos, rows, visible)?; + } + *selected_ready = true; + } else if !*selected_ready { + return Err("GLM indexed prefill reached a non-indexer layer before selection".into()); + } + + call( + unsafe { + ds4_gpu_glm_qk_lowrank_typed_batch_tensor( + batch.qk_low.raw(), + batch.q.raw(), + map, + size, + layer.k_b.offset, + layer.k_b.kind, + rows, + shape.heads as u32, + shape.kv_lora as u32, + q_nope as u32, + shape.key_mla as u32, + ) + }, + "projecting batched GLM low-rank queries", + )?; + self.encode_batch_attention( + batch, + layer, + cache, + pos, + rows, + *selected_count, + visible <= top_k, + )?; + project_rows( + &batch.attn_out, + layer.output, + shape.heads * shape.value_mla, + shape.embd, + &batch.heads, + rows, + map, + size, + )?; + let residual = rows + .checked_mul(shape.embd as u32) + .ok_or("GLM prefill residual size overflow")?; + call( + unsafe { + ds4_gpu_add_tensor( + batch.after_attn.raw(), + batch.current.raw(), + batch.attn_out.raw(), + residual, + ) + }, + "adding the batched GLM attention residual", + )?; + self.encode_batch_ffn(batch, layer, ordinal, rows, residual) + } + + fn select_batch_indexer_rows( + &self, + batch: &GlmBatchScratch, + layer: &GlmLayer, + cache: &LayerCache, + pos: u32, + rows: u32, + visible: u32, + ) -> Result<(), String> { + let shape = self.model.shape; + let map = self.model.main.map_ptr().cast(); + let size = self.model.main.len(); + project_rows( + &batch.indexer_q, + layer.indexer_q, + shape.lora_q, + shape.indexer_heads * shape.indexer_head_dim, + &batch.q_rank_norm, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_glm_indexer_rope_tail_tensor( + batch.indexer_q.raw(), + rows, + shape.indexer_heads as u32, + shape.indexer_head_dim as u32, + shape.rot as u32, + pos, + 0, + shape.rope_base, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + ) + }, + "applying batched GLM indexer RoPE", + )?; + f32_project_rows( + &batch.indexer_weights, + layer.indexer_proj, + shape.embd, + shape.indexer_heads, + &batch.current, + rows, + map, + size, + )?; + let cache = cache + .indexer + .as_ref() + .ok_or("GLM full-indexer layer is missing its key cache")?; + let score_rows = batch.score_rows(self.context, rows); + let q_row_bytes = shape.indexer_heads * shape.indexer_head_dim * 4; + let weight_row_bytes = shape.indexer_heads * 4; + let selected_row_bytes = shape.indexer_top_k * 4; + let scale = 1.0 / ((shape.indexer_heads * shape.indexer_head_dim) as f32).sqrt(); + let mut start = 0; + while start < rows { + let slice = (rows - start).min(score_rows); + let q = batch.indexer_q.view( + u64::from(start) * q_row_bytes, + u64::from(slice) * q_row_bytes, + )?; + let weights = batch.indexer_weights.view( + u64::from(start) * weight_row_bytes, + u64::from(slice) * weight_row_bytes, + )?; + let selected = batch.indexer_selected.view( + u64::from(start) * selected_row_bytes, + u64::from(slice) * selected_row_bytes, + )?; + call( + unsafe { + ds4_gpu_glm_indexer_scores_batch_tensor( + batch.indexer_scores.raw(), + q.raw(), + weights.raw(), + cache.raw(), + visible, + slice, + pos + start, + shape.indexer_heads as u32, + shape.indexer_head_dim as u32, + scale, + CACHE_F16, + ) + }, + "scoring batched GLM indexer rows", + )?; + call( + unsafe { + ds4_gpu_indexer_topk_tensor( + selected.raw(), + batch.indexer_scores.raw(), + visible, + slice, + shape.indexer_top_k as u32, + ) + }, + "selecting batched GLM indexed-attention rows", + )?; + start += slice; + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn encode_batch_attention( + &self, + batch: &GlmBatchScratch, + layer: &GlmLayer, + cache: &LayerCache, + pos: u32, + rows: u32, + selected_count: u32, + causal_range: bool, + ) -> Result<(), String> { + let shape = self.model.shape; + let map = self.model.main.map_ptr().cast(); + let size = self.model.main.len(); + let q_dim = shape.heads * shape.key_mla; + let qk_low_dim = shape.heads * shape.kv_lora; + let heads_dim = shape.heads * shape.value_mla; + let selected_row_bytes = u64::from(selected_count) * 4; + let mut start = 0; + while start < rows { + let slice = (rows - start).min(INDEXED_PREFILL_ATTN_SLICE); + let q = batch + .q + .view(u64::from(start) * q_dim * 4, u64::from(slice) * q_dim * 4)?; + let qk_low = batch.qk_low.view( + u64::from(start) * qk_low_dim * 4, + u64::from(slice) * qk_low_dim * 4, + )?; + let lora = batch.attn_lora.view( + u64::from(start) * qk_low_dim * 4, + u64::from(slice) * qk_low_dim * 4, + )?; + let heads = batch.heads.view( + u64::from(start) * heads_dim * 4, + u64::from(slice) * heads_dim * 4, + )?; + if causal_range { + call( + unsafe { + ds4_gpu_glm_attention_indexed_batch_lora_causal_tensor( + lora.raw(), + q.raw(), + qk_low.raw(), + cache.kv.raw(), + cache.rope.raw(), + slice, + pos + start, + selected_count, + self.context, + CACHE_F16, + shape.heads as u32, + shape.kv_lora as u32, + (shape.key_mla - shape.rot) as u32, + shape.rot as u32, + 0, + shape.rope_base, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + ) + }, + "running causal batched GLM indexed attention", + )?; + } else { + let selected = batch.indexer_selected.view( + u64::from(start) * selected_row_bytes, + u64::from(slice) * selected_row_bytes, + )?; + call( + unsafe { + ds4_gpu_glm_attention_indexed_batch_lora_valid_tensor( + lora.raw(), + q.raw(), + qk_low.raw(), + cache.kv.raw(), + cache.rope.raw(), + selected.raw(), + slice, + selected_count, + self.context, + CACHE_F16, + shape.heads as u32, + shape.kv_lora as u32, + (shape.key_mla - shape.rot) as u32, + shape.rot as u32, + 0, + shape.rope_base, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + ) + }, + "running batched GLM indexed attention", + )?; + } + call( + unsafe { + ds4_gpu_glm_value_project_typed_batch_heads_tensor( + heads.raw(), + lora.raw(), + map, + size, + layer.v_b.offset, + layer.v_b.kind, + slice, + shape.heads as u32, + shape.kv_lora as u32, + shape.value_mla as u32, + ) + }, + "projecting batched GLM attention values", + )?; + start += slice; + } + Ok(()) + } + + fn encode_batch_ffn( + &self, + batch: &GlmBatchScratch, + layer: &GlmLayer, + ordinal: u32, + rows: u32, + residual: u32, + ) -> Result<(), String> { + let shape = self.model.shape; + let map = self.model.main.map_ptr().cast(); + let size = self.model.main.len(); + norm_rows( + &batch.ffn_norm, + &batch.after_attn, + layer.ffn_norm, + shape.embd as u32, + rows, + shape.rms_epsilon, + map, + size, + )?; + if let Some(dense) = layer.dense { + project_rows( + &batch.ffn_gate, + dense.gate, + shape.embd, + shape.ff_dense, + &batch.ffn_norm, + rows, + map, + size, + )?; + project_rows( + &batch.ffn_up, + dense.up, + shape.embd, + shape.ff_dense, + &batch.ffn_norm, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_swiglu_tensor( + batch.ffn_mid.raw(), + batch.ffn_gate.raw(), + batch.ffn_up.raw(), + rows * shape.ff_dense as u32, + 0.0, + 1.0, + ) + }, + "activating the batched dense GLM FFN", + )?; + project_rows( + &batch.ffn_out, + dense.down, + shape.ff_dense, + shape.embd, + &batch.ffn_mid, + rows, + map, + size, + )?; + return call( + unsafe { + ds4_gpu_add_tensor( + batch.next.raw(), + batch.after_attn.raw(), + batch.ffn_out.raw(), + residual, + ) + }, + "adding the batched dense GLM residual", + ); + } + + let sparse = layer.sparse.ok_or("GLM layer has no FFN weights")?; + f32_project_rows( + &batch.router_logits, + sparse.router, + shape.embd, + shape.experts, + &batch.ffn_norm, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_glm_router_select_batch_tensor( + batch.router_selected.raw(), + batch.router_weights.raw(), + batch.router_probs.raw(), + map, + size, + sparse.bias.offset, + batch.router_logits.raw(), + shape.experts as u32, + shape.experts_used as u32, + shape.expert_weight_scale, + rows, + ) + }, + "routing a batched GLM FFN", + )?; + let (gate_expert, gate_row) = expert_layout(sparse.gate, shape.experts); + let (up_expert, up_row) = expert_layout(sparse.up, shape.experts); + let (down_expert, down_row) = expert_layout(sparse.down, shape.experts); + let force_resident = + self.ssd.enabled && ordinal.saturating_sub(shape.leading_dense) < self.ssd.full_layers; + let mut mid_is_f16 = false; + let routed = unsafe { + if sparse.gate.kind == IQ2_XXS { + ds4_gpu_routed_moe_batch_tensor( + batch.ffn_out.raw(), + batch.routed_gate.raw(), + batch.routed_up.raw(), + batch.ffn_mid.raw(), + batch.routed_down.raw(), + map, + size, + sparse.gate.offset, + sparse.up.offset, + sparse.down.offset, + sparse.gate.kind, + sparse.down.kind, + gate_expert, + gate_row, + down_expert, + down_row, + shape.embd as u32, + shape.ff_expert as u32, + shape.embd as u32, + batch.router_selected.raw(), + batch.router_weights.raw(), + shape.experts as u32, + shape.experts_used as u32, + 0.0, + batch.ffn_norm.raw(), + ordinal, + rows, + &mut mid_is_f16, + force_resident, + ) + } else { + ds4_gpu_glm_routed_moe_batch_tensor( + batch.ffn_out.raw(), + batch.ffn_mid.raw(), + map, + size, + sparse.gate.offset, + sparse.up.offset, + sparse.down.offset, + sparse.gate.kind, + sparse.up.kind, + sparse.down.kind, + gate_expert, + gate_row, + up_expert, + up_row, + down_expert, + down_row, + shape.embd as u32, + shape.ff_expert as u32, + shape.embd as u32, + batch.router_selected.raw(), + batch.router_weights.raw(), + shape.experts as u32, + shape.experts_used as u32, + ordinal, + batch.ffn_norm.raw(), + rows, + (shape.experts_used * shape.ff_expert) as u32, + force_resident, + ) + } + }; + call(routed, "running batched routed GLM experts") + .map_err(|error| format!("{error} in layer {ordinal}"))?; + project_rows( + &batch.ffn_gate, + sparse.shared_gate, + shape.embd, + shape.ff_expert, + &batch.ffn_norm, + rows, + map, + size, + )?; + project_rows( + &batch.ffn_up, + sparse.shared_up, + shape.embd, + shape.ff_expert, + &batch.ffn_norm, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_swiglu_tensor( + batch.ffn_mid.raw(), + batch.ffn_gate.raw(), + batch.ffn_up.raw(), + rows * shape.ff_expert as u32, + 0.0, + 1.0, + ) + }, + "activating the batched shared GLM expert", + )?; + project_rows( + &batch.attn_out, + sparse.shared_down, + shape.ff_expert, + shape.embd, + &batch.ffn_mid, + rows, + map, + size, + )?; + call( + unsafe { + ds4_gpu_add3_tensor( + batch.next.raw(), + batch.after_attn.raw(), + batch.ffn_out.raw(), + batch.attn_out.raw(), + residual, + ) + }, + "adding the batched sparse GLM residual", + ) + } + + pub(super) fn eval_speculative_greedy( + &mut self, + token: i32, + max_tokens: u32, + cancelled: &std::sync::atomic::AtomicBool, + ) -> Result, String> { + if self.mtp.is_none() { + self.eval(token)?; + return Ok(vec![token]); + } + if let Some(mtp) = &mut self.mtp { + mtp.cycles += 1; + } + let pending = self.mtp.as_ref().and_then(|mtp| mtp.pending); + let started = Instant::now(); + self.eval(token)?; + if let Some(mtp) = &mut self.mtp { + mtp.verifier_passes += 1; + } + let pos = self.position() - 1; + let next = argmax(&self.logits); + let mut draft = self.mtp_step(next, pos)?; + let mut accepted = vec![token]; + if pending == Some(next) + && max_tokens > 1 + && self.position() < self.context + && !cancelled.load(std::sync::atomic::Ordering::Relaxed) + { + self.eval(next)?; + accepted.push(next); + if let Some(mtp) = &mut self.mtp { + mtp.accepted += 1; + mtp.verifier_passes += 1; + } + draft = self.mtp_step(argmax(&self.logits), pos + 1)?; + } + if let Some(mtp) = &mut self.mtp { + mtp.pending = Some(draft); + mtp.verifier_ns = mtp + .verifier_ns + .saturating_add(u64::try_from(started.elapsed().as_nanos()).unwrap_or(u64::MAX)); + } + Ok(accepted) + } + + fn mtp_step(&mut self, next_token: i32, pos: u32) -> Result { + let mut mtp = self.mtp.take().ok_or("GLM MTP is not configured")?; + let result = (|| { + let layer = self + .weights + .nextn + .as_ref() + .ok_or("GLM nextn layer is missing")?; + let nextn = layer.nextn.ok_or("GLM nextn input weights are missing")?; + let min_pos = *mtp.min_pos.get_or_insert(pos); + let selected_count = pos + .checked_sub(min_pos) + .and_then(|count| count.checked_add(1)) + .ok_or("GLM MTP attention range overflow")?; + if selected_count > self.context { + return Err("GLM MTP attention range exceeds the context".into()); + } + let selected = (min_pos..=pos) + .map(|position| position as i32) + .collect::>(); + mtp.selected.write_i32(&selected)?; + let shape = self.model.shape; + let map = self.model.main.map_ptr().cast(); + let size = self.model.main.len(); + let enorm = mtp.concat.view(0, shape.embd * 4)?; + let hnorm = mtp.concat.view(shape.embd * 4, shape.embd * 4)?; + let started = Instant::now(); + let commands = Commands::begin()?; + call( + unsafe { + ds4_gpu_embed_token_quant_tensor( + self.scratch.next.raw(), + map, + size, + self.weights.embedding.offset, + self.weights.embedding.kind, + shape.vocab as u32, + next_token as u32, + shape.embd as u32, + ) + }, + "GLM MTP token embedding", + )?; + norm( + &enorm, + &self.scratch.next, + nextn.enorm, + shape.embd as u32, + shape.rms_epsilon, + map, + size, + )?; + norm( + &hnorm, + &self.scratch.current, + nextn.hnorm, + shape.embd as u32, + shape.rms_epsilon, + map, + size, + )?; + project( + &self.scratch.current, + nextn.eh_proj, + 2 * shape.embd, + shape.embd, + &mtp.concat, + map, + size, + self.ssd.enabled, + )?; + let mut count = selected_count; + self.encode_layer( + layer, + &mtp.cache, + (shape.layers - 1) as usize, + shape.layers - 1, + pos, + &mut count, + Some((&mtp.selected, selected_count)), + )?; + if let Some(profile) = &mut self.profile { + profile.record( + (shape.layers - 1) as usize, + pos, + &self.scratch.router_selected, + &self.scratch.router_weights, + 1, + false, + )?; + } + norm( + &self.scratch.output_norm, + &self.scratch.next, + nextn.shared_head_norm, + shape.embd as u32, + shape.rms_epsilon, + map, + size, + )?; + project( + &self.scratch.logits, + self.weights.output, + shape.embd, + shape.vocab, + &self.scratch.output_norm, + map, + size, + self.ssd.enabled, + )?; + commands.finish()?; + let mut logits = vec![0.0; shape.vocab as usize]; + self.scratch.logits.read_f32(&mut logits)?; + if let Some(profile) = &self.profile { + profile.write()?; + } + let draft = argmax(&logits); + mtp.drafted += 1; + if mtp.timing { + eprintln!( + "ds4: GLM MTP draft {draft} at position {pos} in {:.1} ms", + started.elapsed().as_secs_f64() * 1000.0 + ); + } + Ok(draft) + })(); + self.mtp = Some(mtp); + result + } + + #[allow(clippy::too_many_arguments)] fn encode_layer( &self, layer: &GlmLayer, @@ -344,6 +1514,7 @@ impl GlmExecutor { ordinal: u32, pos: u32, selected_count: &mut u32, + attention_selected: Option<(&Buffer, u32)>, ) -> Result<(), String> { let shape = self.model.shape; let map = self.model.main.map_ptr().cast(); @@ -435,7 +1606,9 @@ impl GlmExecutor { "applying GLM query RoPE", )?; - if let Some(indexer_cache) = &cache.indexer { + if let Some((_, count)) = attention_selected { + *selected_count = count; + } else if let Some(indexer_cache) = &cache.indexer { project( &self.scratch.indexer_k, layer.indexer_k, @@ -588,7 +1761,10 @@ impl GlmExecutor { size, layer.v_b.offset, layer.v_b.kind, - self.scratch.indexer_selected.raw(), + attention_selected.map_or_else( + || self.scratch.indexer_selected.raw(), + |value| value.0.raw(), + ), *selected_count, self.context, CACHE_F16, @@ -735,39 +1911,6 @@ impl GlmExecutor { let (gate_expert, gate_row) = expert_layout(sparse.gate, shape.experts); let (up_expert, up_row) = expert_layout(sparse.up, shape.experts); let (down_expert, down_row) = expert_layout(sparse.down, shape.experts); - call( - unsafe { - ds4_gpu_glm_routed_moe_one_tensor( - self.scratch.ffn_out.raw(), - self.scratch.ffn_mid.raw(), - map, - size, - sparse.gate.offset, - sparse.up.offset, - sparse.down.offset, - sparse.gate.kind, - sparse.up.kind, - sparse.down.kind, - gate_expert, - gate_row, - up_expert, - up_row, - down_expert, - down_row, - shape.embd as u32, - shape.ff_expert as u32, - shape.embd as u32, - self.scratch.router_selected.raw(), - self.scratch.router_weights.raw(), - shape.experts as u32, - shape.experts_used as u32, - ordinal, - self.scratch.ffn_norm.raw(), - force_resident, - ) - }, - "running the routed GLM experts", - )?; project( &self.scratch.ffn_gate, sparse.shared_gate, @@ -811,6 +1954,73 @@ impl GlmExecutor { size, self.ssd.enabled, )?; + let routed = call( + unsafe { + if sparse.gate.kind == IQ2_XXS { + ds4_gpu_routed_moe_one_tensor( + self.scratch.ffn_out.raw(), + self.scratch.ffn_gate.raw(), + self.scratch.ffn_up.raw(), + self.scratch.ffn_mid.raw(), + self.scratch.routed_down.raw(), + map, + size, + sparse.gate.offset, + sparse.up.offset, + sparse.down.offset, + sparse.gate.kind, + sparse.down.kind, + gate_expert, + gate_row, + down_expert, + down_row, + shape.embd as u32, + shape.ff_expert as u32, + shape.embd as u32, + self.scratch.router_selected.raw(), + self.scratch.router_weights.raw(), + shape.experts as u32, + shape.experts_used as u32, + 0.0, + self.scratch.ffn_norm.raw(), + std::ptr::null(), + ordinal, + force_resident, + ) + } else { + ds4_gpu_glm_routed_moe_one_tensor( + self.scratch.ffn_out.raw(), + self.scratch.ffn_mid.raw(), + map, + size, + sparse.gate.offset, + sparse.up.offset, + sparse.down.offset, + sparse.gate.kind, + sparse.up.kind, + sparse.down.kind, + gate_expert, + gate_row, + up_expert, + up_row, + down_expert, + down_row, + shape.embd as u32, + shape.ff_expert as u32, + shape.embd as u32, + self.scratch.router_selected.raw(), + self.scratch.router_weights.raw(), + shape.experts as u32, + shape.experts_used as u32, + ordinal, + self.scratch.ffn_norm.raw(), + force_resident, + ) + } + }, + "running the routed GLM experts", + ); + routed.map_err(|error| format!("{error} in layer {ordinal}"))?; call( unsafe { ds4_gpu_add3_tensor( @@ -832,17 +2042,190 @@ impl GlmExecutor { tokens: &[i32], mut progress: impl FnMut(u32) -> bool, ) -> Result { + let end = self + .position() + .checked_add(u32::try_from(tokens.len()).map_err(|_| "GLM prefill is too large")?) + .ok_or("GLM prefill position overflow")?; + if end > self.context { + return Err(format!( + "the GLM Metal executor supports {} tokens", + self.context + )); + } + let mut completed = 0; - for &token in tokens { + while completed < tokens.len() { if !progress(self.position()) { break; } - self.eval(token)?; - completed += 1; + let remaining = &tokens[completed..]; + if self.use_streaming_token_prefill(remaining.len()) { + for &token in remaining { + self.eval(token)?; + completed += 1; + if !progress(self.position()) { + return Ok(completed); + } + } + continue; + } + + let pos = self.position(); + let rows = + indexed_prefill_rows(pos, remaining.len(), self.model.shape.indexer_top_k as u32); + let cancelled = self.eval_batch(&remaining[..rows], &mut progress)?; + completed += rows; + if cancelled { + break; + } } Ok(completed) } + fn use_streaming_token_prefill(&self, remaining: usize) -> bool { + if !self.ssd.enabled || self.quality || remaining == 0 { + return false; + } + if std::env::var_os("DS4_GLM_DISABLE_STREAMING_TOKEN_PREFILL").is_some() + || std::env::var_os("DS4_METAL_GLM_DISABLE_STREAMING_TOKEN_PREFILL").is_some() + { + return false; + } + let max_tokens = std::env::var("DS4_METAL_GLM_STREAMING_TOKEN_PREFILL_MAX") + .ok() + .or_else(|| std::env::var("DS4_GLM_STREAMING_TOKEN_PREFILL_MAX").ok()) + .and_then(|value| value.parse::().ok()) + .unwrap_or(STREAMING_TOKEN_PREFILL_MAX); + let Ok(rows) = u32::try_from(remaining) else { + return false; + }; + streaming_token_prefill_eligible(self.context, self.position(), rows, max_tokens) + } + + fn eval_batch( + &mut self, + tokens: &[i32], + progress: &mut impl FnMut(u32) -> bool, + ) -> Result { + let rows = u32::try_from(tokens.len()).map_err(|_| "GLM prefill batch is too large")?; + if rows == 0 || rows > INDEXED_PREFILL_CHUNK { + return Err("GLM indexed prefill batch exceeds DS4's workspace".into()); + } + if tokens + .iter() + .any(|token| *token < 0 || *token as u64 >= self.model.shape.vocab) + { + return Err("GLM prefill contains a token outside the vocabulary".into()); + } + + let pos = self.position(); + let mut batch = GlmBatchScratch::allocate(&self.model, rows, self.context)?; + batch.tokens.write_i32(tokens)?; + let shape = self.model.shape; + let map = self.model.main.map_ptr().cast(); + let size = self.model.main.len(); + let commands = Commands::begin()?; + call( + unsafe { + ds4_gpu_embed_tokens_quant_tensor( + batch.current.raw(), + batch.tokens.raw(), + map, + size, + self.weights.embedding.offset, + self.weights.embedding.kind, + shape.vocab as u32, + rows, + shape.embd as u32, + ) + }, + "embedding a GLM prefill batch", + )?; + commands.finish()?; + + let mut selected_ready = false; + let mut selected_count = 0; + let mut cancelled = false; + for index in 0..self.weights.layers.len() { + if self.ssd.enabled + && self.weights.layers[index] + .sparse + .is_some_and(|sparse| sparse.gate.kind == IQ2_XXS) + && (index as u32).saturating_sub(shape.leading_dense) >= self.ssd.full_layers + { + install_glm_model_spans( + &self.model, + &glm_layer_model_spans(&self.model, index as u32)?, + "GLM indexed-prefill layer", + )?; + } + let commands = Commands::begin()?; + self.encode_batch_layer( + &batch, + &self.weights.layers[index], + &self.caches[index], + index as u32, + pos, + rows, + &mut selected_ready, + &mut selected_count, + )?; + commands.finish()?; + if self.weights.layers[index].sparse.is_some() + && let Some(profile) = &mut self.profile + { + profile.record( + index, + pos, + &batch.router_selected, + &batch.router_weights, + rows, + false, + )?; + } + std::mem::swap(&mut batch.current, &mut batch.next); + let layer_done = (index + 1) as u64; + let token_equivalent = + u32::try_from(u64::from(rows) * layer_done / self.weights.layers.len() as u64) + .expect("GLM prefill progress is bounded by the batch"); + cancelled |= !progress(pos + token_equivalent); + } + + if let Some(spans) = &self.streaming_spans { + install_glm_model_spans(&self.model, spans, "GLM decode/output")?; + } + let commands = Commands::begin()?; + let last = batch + .current + .view(u64::from(rows - 1) * shape.embd * 4, shape.embd * 4)?; + norm( + &self.scratch.output_norm, + &last, + self.weights.output_norm, + shape.embd as u32, + shape.rms_epsilon, + map, + size, + )?; + project( + &self.scratch.logits, + self.weights.output, + shape.embd, + shape.vocab, + &self.scratch.output_norm, + map, + size, + self.ssd.enabled, + )?; + commands.finish()?; + self.scratch.logits.read_f32(&mut self.logits)?; + self.tokens.extend_from_slice(tokens); + if let Some(profile) = &self.profile { + profile.write()?; + } + Ok(cancelled) + } + pub(super) fn logits(&self) -> &[f32] { &self.logits } @@ -861,6 +2244,49 @@ impl GlmExecutor { pub(super) fn checkpoint_tag(&self) -> [u8; 32] { self.checkpoint_tag } + + pub(super) fn execution_stats(&self) -> ExecutionStats { + let mut stats = self + .mtp + .as_ref() + .map_or_else(ExecutionStats::default, |mtp| ExecutionStats { + speculative_mode: 3, + speculative_cycles: mtp.cycles, + drafted_tokens: mtp.drafted, + accepted_draft_tokens: mtp.accepted, + verifier_passes: mtp.verifier_passes, + verifier_ms: mtp.verifier_ns / 1_000_000, + ..ExecutionStats::default() + }); + if self.ssd.enabled { + let mut native = StreamExpertCacheStats::default(); + unsafe { ds4_gpu_stream_expert_cache_get_stats(&mut native) }; + stats.ssd_enabled = true; + stats.ssd_resident_bytes = self.ssd_resident_bytes; + stats.ssd_cache_bytes = self.ssd_cache_bytes; + stats.ssd_cache_experts = self.ssd_cache_experts; + stats.ssd_preloaded_experts = self.ssd_preloaded_experts; + stats.ssd_cache_entries = u64::from(native.current_count); + stats.ssd_cache_hits = native.hits; + stats.ssd_cache_misses = native.misses; + stats.ssd_cache_evictions = native.evictions; + stats.ssd_cache_wraps = native.wraps; + stats.ssd_buffer_allocs = native.buffer_allocs; + stats.ssd_buffer_reuses = native.buffer_reuses; + stats.ssd_pread_bytes = native.pread_bytes; + stats.ssd_pread_ms = native.pread_ms.max(0.0) as u64; + stats.ssd_evict_advise_bytes = native.evict_advise_bytes; + stats.ssd_willneed_advise_bytes = native.willneed_advise_bytes; + stats.ssd_selected_requests = native.hits.saturating_add(native.misses); + stats.ssd_requested_bytes = native.misses.saturating_mul( + self.ssd_cache_bytes + .checked_div(self.ssd_cache_experts) + .unwrap_or(0), + ); + stats.ssd_wait_ms = stats.ssd_pread_ms; + } + stats + } pub(super) fn note_checkpoint_tag(&mut self, tag: [u8; 32]) { self.checkpoint_tag = tag; } @@ -872,6 +2298,68 @@ impl GlmExecutor { .collect::>()?; self.tokens.clear(); self.checkpoint_tag = [0; 32]; + if let Some(mtp) = &mut self.mtp { + mtp.cache = LayerCache::allocate( + self.model.shape, + (self.model.shape.layers - 1) as usize, + self.context, + )?; + mtp.pending = None; + mtp.min_pos = None; + } + Ok(()) + } + + fn blank_resident_state(&self) -> Result { + let mtp = self + .mtp + .as_ref() + .map(|current| { + Ok::<_, String>(GlmMtp { + cache: LayerCache::allocate( + self.model.shape, + (self.model.shape.layers - 1) as usize, + self.context, + )?, + selected: Buffer::bytes(u64::from(self.context) * 4)?, + concat: Buffer::floats(2 * self.model.shape.embd)?, + pending: None, + min_pos: None, + cycles: 0, + drafted: 0, + accepted: 0, + verifier_passes: 0, + verifier_ns: 0, + timing: current.timing, + }) + }) + .transpose()?; + Ok(GlmResidentState { + scratch: GlmScratch::allocate(&self.model, self.context)?, + caches: (0..self.weights.layers.len()) + .map(|layer| LayerCache::allocate(self.model.shape, layer, self.context)) + .collect::>()?, + logits: vec![0.0; self.model.shape.vocab as usize], + tokens: Vec::new(), + checkpoint_tag: [0; 32], + mtp, + }) + } + + pub(super) fn swap_resident_state( + &mut self, + state: &mut Option, + ) -> Result<(), String> { + let mut incoming = state + .take() + .map_or_else(|| self.blank_resident_state(), Ok)?; + std::mem::swap(&mut self.scratch, &mut incoming.scratch); + std::mem::swap(&mut self.caches, &mut incoming.caches); + std::mem::swap(&mut self.logits, &mut incoming.logits); + std::mem::swap(&mut self.tokens, &mut incoming.tokens); + std::mem::swap(&mut self.checkpoint_tag, &mut incoming.checkpoint_tag); + std::mem::swap(&mut self.mtp, &mut incoming.mtp); + *state = Some(incoming); Ok(()) } @@ -1051,6 +2539,24 @@ impl GlmExecutor { } } +fn streaming_token_prefill_eligible(context: u32, pos: u32, rows: u32, max: u32) -> bool { + let full_cap = if context >= LONG_CONTEXT_THRESHOLD { + LONG_CONTEXT_FULL_ATTN_CONTEXT + } else { + STREAMING_FULL_ATTN_CONTEXT + } + .min(context); + max != 0 && rows != 0 && rows <= max && pos < full_cap && rows <= full_cap - pos +} + +fn indexed_prefill_rows(pos: u32, remaining: usize, top_k: u32) -> usize { + let mut rows = remaining.min(INDEXED_PREFILL_CHUNK as usize); + if pos < top_k { + rows = rows.min((top_k - pos) as usize); + } + rows +} + fn full_indexer_layer(shape: super::super::Shape, layer: usize) -> bool { layer < (shape.layers - shape.nextn) as usize && (layer < shape.leading_dense as usize || (layer >= 6 && (layer - 6).is_multiple_of(4))) @@ -1084,45 +2590,364 @@ fn expert_table( fn configure_streaming( model: &Model, weights: &GlmWeights, - ssd: EngineSsdSettings, + plan: Option<&GlmStreamingPlan>, ) -> Result<(), String> { - if !ssd.enabled { - return Ok(()); + let Some(plan) = plan else { return Ok(()) }; + unsafe { + ds4_gpu_set_streaming_expert_cache_expert_bytes(plan.per_expert); + ds4_gpu_set_streaming_expert_cache_budget(plan.cache_experts); } - let sparse = weights + let preload = preload_count(plan.settings, plan.cache_experts); + let mut by_layer = vec![Vec::<(i32, u32)>::new(); weights.layers.len()]; + let mut loaded = 0_u32; + for &(layer, expert) in hotlist::GLM52 { + if loaded == preload { + break; + } + let layer = usize::from(layer); + if layer + < model + .shape + .leading_dense + .saturating_add(plan.settings.full_layers) as usize + || layer >= weights.layers.len() + || u64::from(expert) >= model.shape.experts + { + continue; + } + by_layer[layer].push((i32::from(expert), preload - loaded)); + loaded += 1; + } + for (layer_index, entries) in by_layer.iter().enumerate() { + let Some(sparse) = weights.layers[layer_index].sparse else { + continue; + }; + if entries.is_empty() { + continue; + } + let ids = entries.iter().map(|entry| entry.0).collect::>(); + let priorities = entries.iter().map(|entry| entry.1).collect::>(); + let table = expert_table( + model.main.map_ptr().cast(), + model.main.len(), + layer_index as u32, + model.shape, + sparse, + ); + call( + unsafe { + ds4_gpu_stream_expert_cache_seed_experts( + &table, + ids.as_ptr(), + priorities.as_ptr(), + ids.len() as u32, + ) + }, + "preloading GLM SSD experts", + )?; + } + Ok(()) +} + +fn preload_count(ssd: EngineSsdSettings, budget: u32) -> u32 { + if ssd.cold { + return 0; + } + if ssd.preload_experts != 0 { + return ssd.preload_experts.min(budget); + } + let cap = env::var("DS4_METAL_STREAMING_EXPERT_AUTO_PRELOAD_CAP") + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(4096); + if cap == 0 { budget } else { budget.min(cap) } +} + +fn sparse_expert_bytes(weights: SparseWeights, experts: u64) -> Result { + (weights.gate.bytes / experts) + .checked_add(weights.up.bytes / experts) + .and_then(|bytes| bytes.checked_add(weights.down.bytes / experts)) + .ok_or_else(|| "GLM expert size overflow".into()) +} + +fn glm_streaming_plan( + model: &Model, + weights: &GlmWeights, + mut ssd: EngineSsdSettings, +) -> Result, String> { + if !ssd.enabled { + return Ok(None); + } + let shape = model.shape; + let first_sparse = weights .layers .iter() .find_map(|layer| layer.sparse) .ok_or("GLM model has no routed expert layers")?; - let gate = sparse.gate.bytes / model.shape.experts; - let up = sparse.up.bytes / model.shape.experts; - let down = sparse.down.bytes / model.shape.experts; - let per_expert = gate - .checked_add(up) - .and_then(|bytes| bytes.checked_add(down)) - .ok_or("GLM expert size overflow")?; - let budget = if ssd.cache_experts != 0 { - ssd.cache_experts - } else if ssd.cache_bytes != 0 { - u32::try_from((ssd.cache_bytes / per_expert).max(1)).unwrap_or(u32::MAX) + let per_expert = sparse_expert_bytes(first_sparse, shape.experts)?; + let max_experts = shape.layers.saturating_mul(shape.experts as u32); + + if ssd.cache_experts != 0 { + let cache_experts = ssd.cache_experts.min(max_experts); + let full_layer_bytes = glm_full_layer_bytes(weights, shape, ssd.full_layers)?; + return Ok(Some(GlmStreamingPlan { + settings: ssd, + per_expert, + cache_experts, + planned_expert_bytes: per_expert + .saturating_mul(u64::from(cache_experts)) + .saturating_add(full_layer_bytes), + })); + } + + let mut total = if ssd.cache_bytes != 0 { + ssd.cache_bytes } else { - unsafe { ds4_gpu_stream_expert_cache_budget_for_expert_size(gate, down) } + let recommended = unsafe { ds4_gpu_recommended_working_set_size() }; + if recommended == 0 { + return Err( + "Metal did not report a working-set size; set an explicit GLM SSD cache budget" + .into(), + ); + } + let percent = env::var("DS4_SSD_AUTO_CACHE_PCT") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| (50..=95).contains(value)) + .unwrap_or(80); + let target = recommended.saturating_mul(percent) / 100; + target.saturating_sub(glm_resident_weight_bytes(model)?) }; - if budget == 0 { - return Err("GLM SSD streaming has no memory for an expert cache".into()); + total = total.min(AUTO_CACHE_BYTES); + let total_experts = ((total / per_expert).max(1)).min(u64::from(max_experts)); + total = total_experts.saturating_mul(per_expert); + + let max_layer = weights + .layers + .iter() + .filter_map(|layer| layer.sparse) + .map(|weights| { + sparse_expert_bytes(weights, shape.experts) + .map(|bytes| bytes.saturating_mul(shape.experts)) + }) + .collect::, _>>()? + .into_iter() + .max() + .ok_or("GLM model has no routed expert layers")?; + let prefill_headroom = max_layer.saturating_mul(2); + if prefill_headroom >= total { + return Err(format!( + "GLM SSD cache budget is too small: two routed prefill layers need {:.2} GiB", + prefill_headroom as f64 / 1_073_741_824.0 + )); } - unsafe { - ds4_gpu_set_streaming_expert_cache_expert_bytes(per_expert); - ds4_gpu_set_streaming_expert_cache_budget(budget); + let after_prefill = total - prefill_headroom; + let supported = weights.layers.len() as u32 - shape.leading_dense; + let requested_full = if ssd.full_layers_set { + ssd.full_layers.min(supported) + } else { + let target = (after_prefill / 7).min(10 * 1024 * 1024 * 1024); + (1..=supported) + .take_while(|layers| { + glm_full_layer_bytes(weights, shape, *layers).is_ok_and(|bytes| bytes <= target) + }) + .last() + .unwrap_or(0) + }; + let minimum_dynamic = per_expert.saturating_mul(shape.experts); + let mut full_layers = requested_full; + let full_layer_bytes = loop { + let bytes = glm_full_layer_bytes(weights, shape, full_layers)?; + if full_layers == 0 || bytes.saturating_add(minimum_dynamic) < after_prefill { + break bytes; + } + full_layers -= 1; + }; + ssd.full_layers = full_layers; + let cache_experts = dynamic_expert_budget( + total, + prefill_headroom, + full_layer_bytes, + per_expert, + max_experts, + )?; + Ok(Some(GlmStreamingPlan { + settings: ssd, + per_expert, + cache_experts, + planned_expert_bytes: total, + })) +} + +fn dynamic_expert_budget( + total: u64, + prefill_headroom: u64, + full_layers: u64, + per_expert: u64, + max_experts: u32, +) -> Result { + let reserved = prefill_headroom + .checked_add(full_layers) + .ok_or("GLM SSD cache reserve overflow")?; + let experts = total + .checked_sub(reserved) + .filter(|bytes| *bytes >= per_expert) + .map(|bytes| bytes / per_expert) + .ok_or("GLM SSD streaming has no memory for an expert cache")?; + Ok(u32::try_from(experts).unwrap_or(u32::MAX).min(max_experts)) +} + +fn glm_full_layer_bytes( + weights: &GlmWeights, + shape: super::super::Shape, + layers: u32, +) -> Result { + weights + .layers + .iter() + .skip(shape.leading_dense as usize) + .take(layers as usize) + .try_fold(0_u64, |total, layer| { + let sparse = layer.sparse.ok_or("GLM resident prefix is not sparse")?; + let bytes = sparse_expert_bytes(sparse, shape.experts)? + .checked_mul(shape.experts) + .ok_or("GLM resident layer size overflow")?; + total + .checked_add(bytes) + .ok_or_else(|| "GLM resident layer size overflow".into()) + }) +} + +fn glm_resident_weight_bytes(model: &Model) -> Result { + model + .main + .tensors + .iter() + .filter(|(name, _)| { + !name.ends_with("ffn_gate_exps.weight") + && !name.ends_with("ffn_up_exps.weight") + && !name.ends_with("ffn_down_exps.weight") + }) + .try_fold(0_u64, |total, (_, tensor)| total.checked_add(tensor.bytes)) + .ok_or_else(|| "GLM resident tensor size overflow".into()) +} + +fn glm_streaming_model_spans( + model: &Model, + weights: &GlmWeights, + plan: &GlmStreamingPlan, +) -> Result, String> { + let full_before = model + .shape + .leading_dense + .saturating_add(plan.settings.full_layers); + let mut spans = model + .main + .tensors + .iter() + .filter(|(name, _)| { + let routed = name.ends_with("ffn_gate_exps.weight") + || name.ends_with("ffn_up_exps.weight") + || name.ends_with("ffn_down_exps.weight"); + if !routed { + return true; + } + let layer = name + .strip_prefix("blk.") + .and_then(|name| name.split('.').next()) + .and_then(|layer| layer.parse::().ok()); + layer.is_some_and(|layer| { + layer < full_before as usize + || weights + .layers + .get(layer) + .or_else(|| { + (layer == weights.layers.len()) + .then_some(weights.nextn.as_ref()) + .flatten() + }) + .and_then(|layer| layer.sparse) + .is_some_and(|sparse| { + sparse_expert_bytes(sparse, model.shape.experts) + .is_ok_and(|bytes| bytes != plan.per_expert) + }) + }) + }) + .map(|(_, tensor)| (tensor.offset, tensor.bytes)) + .collect::>(); + spans.sort_unstable_by_key(|span| span.0); + let mut merged: Vec<(u64, u64)> = Vec::new(); + for (offset, bytes) in spans { + let end = offset.checked_add(bytes).ok_or("GLM model span overflow")?; + if let Some((previous_offset, previous_bytes)) = merged.last_mut() { + let previous_end = previous_offset.saturating_add(*previous_bytes); + if offset <= previous_end { + *previous_bytes = previous_end.max(end) - *previous_offset; + continue; + } + } + merged.push((offset, bytes)); } - Ok(()) + if merged.is_empty() { + return Err("GLM SSD streaming found no resident model tensors".into()); + } + Ok(merged) +} + +fn glm_layer_model_spans(model: &Model, layer: u32) -> Result, String> { + let prefix = format!("blk.{layer}."); + let mut spans = model + .main + .tensors + .iter() + .filter(|(name, _)| name.starts_with(&prefix)) + .map(|(_, tensor)| (tensor.offset, tensor.bytes)) + .collect::>(); + spans.sort_unstable_by_key(|span| span.0); + let mut merged: Vec<(u64, u64)> = Vec::new(); + for (offset, bytes) in spans { + let end = offset.checked_add(bytes).ok_or("GLM layer span overflow")?; + if let Some((previous_offset, previous_bytes)) = merged.last_mut() { + let previous_end = previous_offset.saturating_add(*previous_bytes); + if offset <= previous_end { + *previous_bytes = previous_end.max(end) - *previous_offset; + continue; + } + } + merged.push((offset, bytes)); + } + if merged.is_empty() { + return Err(format!("GLM layer {layer} has no model tensors")); + } + Ok(merged) +} + +fn install_glm_model_spans( + model: &Model, + spans: &[(u64, u64)], + purpose: &str, +) -> Result<(), String> { + let (offsets, sizes): (Vec<_>, Vec<_>) = spans.iter().copied().unzip(); + call( + unsafe { + ds4_gpu_set_model_map_spans( + model.main.map_ptr().cast(), + model.main.len(), + offsets.as_ptr(), + sizes.as_ptr(), + spans.len() as u32, + model.main.max_tensor_bytes(), + ) + }, + purpose, + ) } fn admission_bytes( model: &Model, weights: &GlmWeights, context: u32, - ssd: EngineSsdSettings, + streaming: Option<&GlmStreamingPlan>, ) -> Result { let shape = model.shape; let normal_layers = u64::from(shape.layers - shape.nextn); @@ -1138,55 +2963,76 @@ fn admission_bytes( bytes.checked_add(indexer_layers * u64::from(context) * shape.indexer_head_dim * 2) }) .ok_or("GLM compact-cache size overflow")?; - let resident = if ssd.enabled { - model - .main - .tensors + let resident = if let Some(plan) = streaming { + let off_slab = weights + .layers .iter() - .filter(|(name, _)| { - !name.ends_with("ffn_gate_exps.weight") - && !name.ends_with("ffn_up_exps.weight") - && !name.ends_with("ffn_down_exps.weight") - }) - .try_fold(0_u64, |total, (_, tensor)| total.checked_add(tensor.bytes)) - .ok_or("GLM resident tensor size overflow")? + .chain(weights.nextn.iter()) + .filter_map(|layer| layer.sparse) + .try_fold(0_u64, |total, sparse| { + let bytes = sparse_expert_bytes(sparse, shape.experts)?; + if bytes == plan.per_expert { + return Ok::<_, String>(total); + } + total + .checked_add(bytes.saturating_mul(shape.experts)) + .ok_or_else(|| "GLM off-slab resident size overflow".into()) + })?; + glm_resident_weight_bytes(model)? + .checked_add(off_slab) + .ok_or("GLM resident weight size overflow")? } else { model.main.len() - model.main.data_offset() }; - let sparse_expert = weights - .layers - .iter() - .find_map(|layer| layer.sparse) - .map(|weights| { - weights.gate.bytes / shape.experts - + weights.up.bytes / shape.experts - + weights.down.bytes / shape.experts - }) - .unwrap_or(0); - let cache = if ssd.enabled { - if ssd.cache_bytes != 0 { - ssd.cache_bytes - } else if ssd.cache_experts != 0 { - sparse_expert.saturating_mul(u64::from(ssd.cache_experts)) - } else { - // Reference auto policy reserves roughly one seventh of the available - // budget for full layers and uses the remainder for selected experts. - 16 * 1024 * 1024 * 1024_u64 - } - } else { - 0 - }; - let full_layers = sparse_expert - .saturating_mul(shape.experts) - .saturating_mul(u64::from(ssd.full_layers)); + let cache = streaming.map_or(0, |plan| plan.planned_expert_bytes); + let scratch = glm_batch_scratch_bytes(shape, context.min(INDEXED_PREFILL_CHUNK), context)? + .max(512 * 1024 * 1024); resident .checked_add(kv) .and_then(|bytes| bytes.checked_add(cache)) - .and_then(|bytes| bytes.checked_add(full_layers)) - .and_then(|bytes| bytes.checked_add(512 * 1024 * 1024)) + .and_then(|bytes| bytes.checked_add(scratch)) .ok_or_else(|| "GLM runtime memory size overflow".into()) } +fn glm_batch_scratch_bytes( + shape: super::super::Shape, + rows: u32, + context: u32, +) -> Result { + let rows = u64::from(rows); + let score_rows = (INDEXED_PREFILL_SCORE_BYTES / (u64::from(context) * 4)) + .max(1) + .min(rows); + let q = shape.heads * shape.key_mla; + let qk_low = shape.heads * shape.kv_lora; + let heads = shape.heads * shape.value_mla; + let hidden = shape.ff_dense.max(shape.ff_expert); + let routed = shape.experts_used * shape.ff_expert; + let row_floats = 7 * shape.embd + + 2 * shape.lora_q + + q + + shape.head_dim + + shape.kv_lora + + shape.indexer_head_dim + + shape.indexer_heads * shape.indexer_head_dim + + shape.indexer_heads + + 2 * qk_low + + heads + + 2 * hidden + + hidden.max(routed) + + 2 * routed + + shape.experts_used * shape.embd + + 2 * shape.experts + + shape.experts_used; + rows.checked_mul(row_floats) + .and_then(|floats| floats.checked_mul(4)) + .and_then(|bytes| bytes.checked_add(rows * 4)) + .and_then(|bytes| bytes.checked_add(rows * shape.indexer_top_k * 4)) + .and_then(|bytes| bytes.checked_add(rows * shape.experts_used * 4)) + .and_then(|bytes| bytes.checked_add(score_rows * u64::from(context) * 4)) + .ok_or_else(|| "GLM indexed-prefill scratch size overflow".into()) +} + fn norm( out: &Buffer, input: &Buffer, @@ -1212,6 +3058,34 @@ fn norm( ) } +#[allow(clippy::too_many_arguments)] +fn norm_rows( + out: &Buffer, + input: &Buffer, + weight: Weight, + width: u32, + rows: u32, + epsilon: f32, + map: *const c_void, + size: u64, +) -> Result<(), String> { + call( + unsafe { + ds4_gpu_rms_norm_weight_rows_tensor( + out.raw(), + input.raw(), + map, + size, + weight.offset, + width, + rows, + epsilon, + ) + }, + "normalizing batched GLM activations", + ) +} + #[allow(clippy::too_many_arguments)] fn project( out: &Buffer, @@ -1253,6 +3127,35 @@ fn project( call(result, "projecting GLM activations") } +#[allow(clippy::too_many_arguments)] +fn project_rows( + out: &Buffer, + weight: Weight, + input: u64, + output: u64, + x: &Buffer, + rows: u32, + map: *const c_void, + size: u64, +) -> Result<(), String> { + call( + unsafe { + ds4_gpu_matmul_quant_tensor( + out.raw(), + map, + size, + weight.offset, + weight.kind, + input, + output, + x.raw(), + u64::from(rows), + ) + }, + "projecting batched GLM activations", + ) +} + #[allow(clippy::too_many_arguments)] fn f32_project( out: &Buffer, @@ -1280,10 +3183,41 @@ fn f32_project( ) } +#[allow(clippy::too_many_arguments)] +fn f32_project_rows( + out: &Buffer, + weight: Weight, + input: u64, + output: u64, + x: &Buffer, + rows: u32, + map: *const c_void, + size: u64, +) -> Result<(), String> { + call( + unsafe { + ds4_gpu_matmul_f32_tensor( + out.raw(), + map, + size, + weight.offset, + input, + output, + x.raw(), + u64::from(rows), + ) + }, + "projecting batched GLM F32 activations", + ) +} + #[cfg(test)] mod tests { - use super::{GlmExecutor, full_indexer_layer}; - use crate::engine::{GLM, Model}; + use super::{ + GlmExecutor, argmax, dynamic_expert_budget, full_indexer_layer, indexed_prefill_rows, + streaming_token_prefill_eligible, + }; + use crate::engine::{GLM, Model, ReasoningMode}; use crate::model::ModelChoice; use crate::settings::EngineSsdSettings; use std::path::Path; @@ -1299,20 +3233,45 @@ mod tests { assert!(!full_indexer_layer(GLM, 78)); } + #[test] + fn glm_prefill_schedule_matches_ds4_boundaries() { + assert!(streaming_token_prefill_eligible(8192, 0, 64, 64)); + assert!(!streaming_token_prefill_eligible(8192, 0, 65, 64)); + assert!(!streaming_token_prefill_eligible(8192, 8180, 13, 64)); + assert!(!streaming_token_prefill_eligible(65_536, 4096, 1, 64)); + assert_eq!(indexed_prefill_rows(0, 5000, 2048), 2048); + assert_eq!(indexed_prefill_rows(2048, 5000, 2048), 4096); + assert_eq!(indexed_prefill_rows(0, 17, 2048), 17); + } + + #[test] + fn glm_byte_budget_reserves_prefill_before_dynamic_experts() { + let mib = 1024 * 1024; + assert_eq!( + dynamic_expert_budget(12 * 1024 * mib, 6 * 1024 * mib, 0, 10 * mib, 20_000), + Ok(614) + ); + assert!(dynamic_expert_budget(1024, 1024, 0, 1, 20_000).is_err()); + } + #[test] #[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"] fn resident_and_streamed_glm_match_the_short_code_fixture() { let path = std::env::var("DS4_GLM_MODEL").unwrap_or_else(|_| { "../ds4/models/GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf".into() }); + if !Path::new(&path).is_file() { + eprintln!("skipping unavailable GLM fixture: {path}"); + return; + } let prompt = b"Complete the C statement with the next exact token only:\nreturn snprintf(buf, sizeof(buf), \"%d\", value"; for streamed in [false, true] { let model = Model::open_main(Path::new(&path), ModelChoice::Glm52).unwrap(); let tokens = model.tokenize(std::str::from_utf8(prompt).unwrap()); - let mut executor = GlmExecutor::open( + let executor = GlmExecutor::open( model, 4096, - true, + false, EngineSsdSettings { enabled: streamed, cold: false, @@ -1322,8 +3281,15 @@ mod tests { full_layers_set: false, preload_experts: 0, }, - ) - .unwrap(); + ); + let mut executor = match executor { + Ok(executor) => executor, + Err(error) if !streamed && error.contains("Metal recommends at most") => { + eprintln!("skipping resident GLM on this memory size: {error}"); + continue; + } + Err(error) => panic!("GLM executor failed to open: {error}"), + }; assert_eq!(executor.prefill(&tokens, |_| true).unwrap(), tokens.len()); let token = executor .logits() @@ -1334,8 +3300,123 @@ mod tests { .0 as i32; assert_eq!( executor.model().token_bytes(token).as_deref(), - Some(b")".as_slice()) + Some(b");\n".as_slice()) ); } } + + #[test] + #[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"] + fn streamed_glm_uses_ds4_indexed_prefill_for_long_prompts() { + let path = std::env::var("DS4_GLM_MODEL").unwrap_or_else(|_| { + "../ds4/models/GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf".into() + }); + if !Path::new(&path).is_file() { + eprintln!("skipping unavailable GLM fixture: {path}"); + return; + } + let prompt = "Complete each C statement. Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Now complete exactly: return snprintf(buf, sizeof(buf), \"%d\", value"; + let model = Model::open_main(Path::new(&path), ModelChoice::Glm52).unwrap(); + let tokens = + model.render_prompt("You are a helpful assistant", prompt, ReasoningMode::Direct); + assert_eq!(tokens.len(), 102); + let mut executor = GlmExecutor::open( + model, + 256, + false, + EngineSsdSettings { + enabled: true, + cold: true, + cache_experts: 16, + cache_bytes: 0, + full_layers: 0, + full_layers_set: true, + preload_experts: 0, + }, + ) + .unwrap(); + let mut updates = Vec::new(); + assert_eq!( + executor + .prefill(&tokens, |position| { + updates.push(position); + true + }) + .unwrap(), + tokens.len() + ); + assert_eq!(executor.position(), tokens.len() as u32); + assert!(updates.len() < tokens.len()); + assert!(executor.logits().iter().all(|logit| logit.is_finite())); + assert_eq!(argmax(executor.logits()), 1215); + assert!((executor.logits()[1215] - 24.178_133).abs() < 1.0e-3); + executor.eval(1215).unwrap(); + assert_eq!(executor.position(), 103); + assert!(executor.logits().iter().all(|logit| logit.is_finite())); + } + + #[test] + #[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"] + fn glm_mtp_preserves_target_tokens_and_drafts() { + use crate::settings::EngineSpeculativeSettings; + use std::sync::atomic::AtomicBool; + + let path = std::env::var("DS4_GLM_MODEL").unwrap_or_else(|_| { + "../ds4/models/GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf".into() + }); + if !Path::new(&path).is_file() { + eprintln!("skipping unavailable GLM fixture: {path}"); + return; + } + let run = |enabled| { + let model = Model::open_main(Path::new(&path), ModelChoice::Glm52).unwrap(); + let tokens = model.tokenize("Write one short greeting."); + let mut executor = GlmExecutor::open_profile( + model, + 128, + false, + EngineSsdSettings { + enabled: true, + cold: false, + cache_experts: 0, + cache_bytes: 0, + full_layers: 0, + full_layers_set: false, + preload_experts: 0, + }, + EngineSpeculativeSettings { + mtp_draft_tokens: 2, + mtp_margin: 3.0, + glm_mtp: enabled, + glm_mtp_timing: false, + dspark: false, + dspark_confidence_threshold: 0.9, + dspark_confidence_threshold_set: false, + dspark_strict: false, + }, + None, + ) + .unwrap(); + executor.prefill(&tokens, |_| true).unwrap(); + let mut generated = Vec::new(); + while generated.len() < 8 { + let token = argmax(executor.logits()); + let mut cycle = executor + .eval_speculative_greedy( + token, + (8 - generated.len()) as u32, + &AtomicBool::new(false), + ) + .unwrap(); + cycle.truncate(8 - generated.len()); + generated.extend(cycle); + } + let drafted = executor.mtp.as_ref().map_or(0, |mtp| mtp.drafted); + (generated, drafted) + }; + let baseline = run(false).0; + let (mtp, drafted) = run(true); + assert_eq!(baseline, mtp); + assert!(drafted > 0); + } } diff --git a/src/engine/metal/gpu.rs b/src/engine/metal/gpu.rs index ba54cbb..1604e4d 100644 --- a/src/engine/metal/gpu.rs +++ b/src/engine/metal/gpu.rs @@ -18,6 +18,23 @@ pub(super) struct StreamExpertTable { pub(super) down_expert_bytes: u64, } +#[derive(Clone, Copy, Default)] +#[repr(C)] +pub(super) struct StreamExpertCacheStats { + pub(super) configured_count: u32, + pub(super) current_count: u32, + pub(super) hits: u64, + pub(super) misses: u64, + pub(super) evictions: u64, + pub(super) wraps: u64, + pub(super) buffer_allocs: u64, + pub(super) buffer_reuses: u64, + pub(super) evict_advise_bytes: u64, + pub(super) willneed_advise_bytes: u64, + pub(super) pread_bytes: u64, + pub(super) pread_ms: f64, +} + unsafe extern "C" { pub(super) fn ds4_gpu_init() -> i32; pub(super) fn ds4_gpu_cleanup(); @@ -43,10 +60,7 @@ unsafe extern "C" { pub(super) fn ds4_gpu_set_streaming_expert_cache_budget(experts: u32); pub(super) fn ds4_gpu_set_streaming_expert_cache_expert_bytes(bytes: u64); pub(super) fn ds4_gpu_recommended_working_set_size() -> u64; - pub(super) fn ds4_gpu_stream_expert_cache_budget_for_expert_size( - gate_expert_bytes: u64, - down_expert_bytes: u64, - ) -> u32; + pub(super) fn ds4_gpu_stream_expert_cache_get_stats(stats: *mut StreamExpertCacheStats); pub(super) fn ds4_gpu_stream_expert_cache_seed_experts( table: *const StreamExpertTable, expert_ids: *const i32, @@ -266,6 +280,17 @@ unsafe extern "C" { token: u32, embd: u32, ) -> i32; + pub(super) fn ds4_gpu_embed_tokens_quant_tensor( + out: *mut GpuTensor, + tokens: *const GpuTensor, + map: *const c_void, + size: u64, + weight: u64, + kind: u32, + vocab: u32, + rows: u32, + embd: u32, + ) -> i32; pub(super) fn ds4_gpu_glm_rope_tail_tensor( x: *mut GpuTensor, tokens: u32, @@ -301,6 +326,30 @@ unsafe extern "C" { cache_f16: bool, eps: f32, ) -> i32; + pub(super) fn ds4_gpu_glm_kv_lora_rms_norm_tensor( + out: *mut GpuTensor, + kv_raw: *const GpuTensor, + map: *const c_void, + size: u64, + weight: u64, + rows: u32, + kv_raw_dim: u32, + kv_lora: u32, + eps: f32, + ) -> i32; + pub(super) fn ds4_gpu_glm_store_compact_kv_tensor( + kv_cache: *mut GpuTensor, + rope_cache: *mut GpuTensor, + kv_norm: *const GpuTensor, + kv_raw: *const GpuTensor, + pos: u32, + rows: u32, + cache_cap: u32, + kv_raw_dim: u32, + kv_lora: u32, + rot: u32, + cache_f16: bool, + ) -> i32; pub(super) fn ds4_gpu_glm_store_indexer_k_tensor( cache: *mut GpuTensor, raw: *const GpuTensor, @@ -327,6 +376,13 @@ unsafe extern "C" { selected: *mut GpuTensor, count: u32, ) -> i32; + pub(super) fn ds4_gpu_glm_fill_selected_range_batch_tensor( + selected: *mut GpuTensor, + rows: u32, + pos: u32, + count: u32, + pad_row: u32, + ) -> i32; pub(super) fn ds4_gpu_glm_indexer_rope_tail_tensor( x: *mut GpuTensor, tokens: u32, @@ -353,6 +409,19 @@ unsafe extern "C" { scale: f32, cache_f16: bool, ) -> i32; + pub(super) fn ds4_gpu_glm_indexer_scores_batch_tensor( + scores: *mut GpuTensor, + q: *const GpuTensor, + weights: *const GpuTensor, + cache: *const GpuTensor, + visible: u32, + rows: u32, + pos: u32, + heads: u32, + head_dim: u32, + scale: f32, + cache_f16: bool, + ) -> i32; pub(super) fn ds4_gpu_glm_qk_lowrank_typed_tensor( out: *mut GpuTensor, q: *const GpuTensor, @@ -365,6 +434,31 @@ unsafe extern "C" { q_nope: u32, q_dim: u32, ) -> i32; + pub(super) fn ds4_gpu_glm_qk_lowrank_typed_batch_tensor( + out: *mut GpuTensor, + q: *const GpuTensor, + map: *const c_void, + size: u64, + weight: u64, + kind: u32, + rows: u32, + heads: u32, + kv_lora: u32, + q_nope: u32, + q_dim: u32, + ) -> i32; + pub(super) fn ds4_gpu_glm_value_project_typed_batch_heads_tensor( + heads: *mut GpuTensor, + lora: *const GpuTensor, + map: *const c_void, + size: u64, + weight: u64, + kind: u32, + rows: u32, + n_head: u32, + kv_lora: u32, + value_dim: u32, + ) -> i32; pub(super) fn ds4_gpu_glm_attention_indexed_decode_typed_tensor( heads_out: *mut GpuTensor, q: *const GpuTensor, @@ -392,6 +486,95 @@ unsafe extern "C" { beta_fast: f32, beta_slow: f32, ) -> i32; + pub(super) fn ds4_gpu_glm_attention_indexed_batch_lora_causal_tensor( + out: *mut GpuTensor, + q: *const GpuTensor, + qk_low: *const GpuTensor, + kv_cache: *const GpuTensor, + rope_cache: *const GpuTensor, + rows: u32, + pos: u32, + selected: u32, + cache_cap: u32, + cache_f16: bool, + heads: u32, + kv_lora: u32, + q_nope: u32, + rot: u32, + original: u32, + freq_base: f32, + freq_scale: f32, + ext: f32, + attn_factor: f32, + beta_fast: f32, + beta_slow: f32, + ) -> i32; + pub(super) fn ds4_gpu_glm_attention_indexed_batch_lora_valid_tensor( + out: *mut GpuTensor, + q: *const GpuTensor, + qk_low: *const GpuTensor, + kv_cache: *const GpuTensor, + rope_cache: *const GpuTensor, + selected: *const GpuTensor, + rows: u32, + selected_count: u32, + cache_cap: u32, + cache_f16: bool, + heads: u32, + kv_lora: u32, + q_nope: u32, + rot: u32, + original: u32, + freq_base: f32, + freq_scale: f32, + ext: f32, + attn_factor: f32, + beta_fast: f32, + beta_slow: f32, + ) -> i32; + pub(super) fn ds4_gpu_glm_router_select_batch_tensor( + selected: *mut GpuTensor, + weights: *mut GpuTensor, + probs: *mut GpuTensor, + map: *const c_void, + size: u64, + bias: u64, + logits: *const GpuTensor, + experts: u32, + experts_used: u32, + scale: f32, + rows: u32, + ) -> i32; + pub(super) fn ds4_gpu_glm_routed_moe_batch_tensor( + out: *mut GpuTensor, + mid: *mut GpuTensor, + map: *const c_void, + size: u64, + gate_weight: u64, + up_weight: u64, + down_weight: u64, + gate_type: u32, + up_type: u32, + down_type: u32, + gate_expert_bytes: u64, + gate_row_bytes: u64, + up_expert_bytes: u64, + up_row_bytes: u64, + down_expert_bytes: u64, + down_row_bytes: u64, + input: u32, + middle: u32, + output: u32, + selected: *const GpuTensor, + weights: *const GpuTensor, + total_experts: u32, + used_experts: u32, + layer: u32, + x: *const GpuTensor, + rows: u32, + mid_token_stride: u32, + force_resident: bool, + ) -> i32; pub(super) fn ds4_gpu_glm_router_select_tensor( selected: *mut GpuTensor, weights: *mut GpuTensor, @@ -1276,6 +1459,20 @@ impl Buffer { ) } + pub(super) fn read_i32(&self, values: &mut [i32]) -> Result<(), String> { + check( + unsafe { + ds4_gpu_tensor_read( + self.raw(), + 0, + values.as_mut_ptr().cast(), + std::mem::size_of_val(values) as u64, + ) + }, + "reading Metal integers", + ) + } + pub(super) fn read(&self, offset: u64, values: &mut [u8]) -> Result<(), String> { check( unsafe { diff --git a/src/engine/metal/hotlist.rs b/src/engine/metal/hotlist.rs index e68fa26..5d4d185 100644 --- a/src/engine/metal/hotlist.rs +++ b/src/engine/metal/hotlist.rs @@ -1,4 +1,6890 @@ -// Generated mechanically from ../ds4/ds4_streaming_hotlist.inc. +// Generated mechanically by scripts/import_hotlists.py. +pub(super) const PRO: &[(u16, u16)] = &[ + (44, 213), + (25, 315), + (56, 253), + (19, 161), + (41, 262), + (6, 322), + (50, 29), + (9, 287), + (40, 358), + (30, 350), + (52, 124), + (4, 83), + (11, 110), + (42, 189), + (28, 168), + (7, 49), + (58, 270), + (40, 31), + (3, 314), + (45, 325), + (22, 260), + (47, 115), + (36, 232), + (29, 14), + (37, 58), + (47, 197), + (46, 326), + (51, 257), + (46, 194), + (34, 380), + (52, 183), + (34, 29), + (54, 155), + (27, 113), + (41, 374), + (10, 3), + (39, 1), + (9, 143), + (5, 174), + (38, 301), + (60, 94), + (44, 61), + (43, 343), + (42, 322), + (41, 324), + (15, 141), + (17, 126), + (27, 275), + (8, 272), + (30, 242), + (51, 106), + (20, 43), + (31, 98), + (37, 22), + (36, 20), + (4, 214), + (10, 211), + (47, 313), + (15, 375), + (17, 165), + (37, 109), + (34, 240), + (50, 244), + (6, 67), + (50, 313), + (38, 92), + (54, 164), + (21, 203), + (9, 151), + (36, 288), + (60, 16), + (43, 258), + (22, 278), + (40, 100), + (32, 365), + (49, 44), + (49, 202), + (27, 348), + (46, 162), + (13, 44), + (23, 180), + (52, 159), + (6, 32), + (17, 200), + (20, 210), + (50, 136), + (35, 281), + (48, 173), + (32, 44), + (48, 5), + (33, 140), + (49, 16), + (16, 143), + (36, 351), + (27, 13), + (35, 114), + (18, 45), + (24, 105), + (26, 150), + (17, 236), + (9, 185), + (14, 264), + (16, 323), + (39, 129), + (46, 235), + (20, 75), + (42, 206), + (40, 368), + (49, 32), + (11, 90), + (16, 297), + (30, 141), + (54, 161), + (28, 142), + (47, 295), + (12, 296), + (27, 62), + (57, 119), + (18, 308), + (39, 110), + (58, 252), + (28, 178), + (26, 140), + (12, 117), + (24, 7), + (27, 169), + (32, 336), + (58, 167), + (38, 135), + (26, 380), + (10, 365), + (8, 108), + (18, 49), + (34, 71), + (20, 367), + (12, 262), + (54, 16), + (43, 197), + (10, 246), + (26, 136), + (5, 57), + (16, 251), + (45, 157), + (14, 134), + (10, 136), + (15, 311), + (8, 67), + (30, 161), + (50, 102), + (48, 229), + (25, 276), + (55, 372), + (15, 174), + (13, 348), + (19, 26), + (4, 300), + (44, 45), + (45, 369), + (51, 32), + (53, 342), + (13, 319), + (41, 201), + (27, 362), + (12, 219), + (59, 339), + (38, 238), + (11, 278), + (37, 327), + (38, 285), + (11, 78), + (56, 5), + (27, 260), + (47, 283), + (31, 0), + (21, 204), + (58, 8), + (12, 26), + (18, 322), + (24, 102), + (23, 133), + (59, 71), + (32, 328), + (24, 6), + (19, 270), + (13, 362), + (55, 54), + (22, 149), + (28, 132), + (9, 199), + (58, 359), + (5, 98), + (3, 209), + (19, 312), + (15, 314), + (21, 267), + (34, 158), + (22, 127), + (24, 318), + (57, 304), + (24, 355), + (59, 80), + (12, 255), + (5, 273), + (3, 66), + (48, 76), + (22, 375), + (30, 87), + (34, 278), + (18, 116), + (39, 57), + (34, 274), + (16, 206), + (17, 309), + (38, 73), + (22, 93), + (13, 338), + (18, 120), + (45, 380), + (42, 12), + (6, 28), + (24, 214), + (45, 26), + (56, 118), + (20, 49), + (56, 154), + (23, 321), + (7, 94), + (57, 336), + (18, 237), + (5, 77), + (13, 89), + (18, 280), + (48, 16), + (31, 187), + (23, 163), + (20, 125), + (24, 149), + (33, 85), + (19, 304), + (22, 161), + (12, 114), + (40, 201), + (51, 79), + (16, 316), + (14, 291), + (49, 269), + (17, 141), + (6, 147), + (47, 208), + (30, 265), + (42, 222), + (44, 221), + (52, 202), + (28, 93), + (43, 45), + (5, 233), + (22, 45), + (42, 337), + (13, 249), + (16, 231), + (26, 63), + (53, 141), + (56, 247), + (21, 345), + (59, 112), + (54, 131), + (32, 278), + (36, 37), + (17, 57), + (21, 91), + (10, 319), + (7, 292), + (43, 140), + (54, 35), + (32, 116), + (26, 158), + (32, 306), + (47, 52), + (18, 63), + (20, 112), + (46, 145), + (40, 357), + (42, 313), + (16, 236), + (58, 299), + (53, 178), + (36, 214), + (5, 165), + (36, 79), + (53, 359), + (12, 316), + (35, 181), + (26, 287), + (22, 29), + (19, 32), + (22, 71), + (26, 290), + (15, 162), + (22, 359), + (18, 299), + (11, 226), + (17, 138), + (5, 346), + (3, 18), + (20, 148), + (10, 51), + (21, 259), + (37, 40), + (48, 131), + (26, 249), + (4, 222), + (26, 336), + (19, 90), + (42, 87), + (56, 103), + (55, 51), + (56, 28), + (5, 48), + (23, 209), + (12, 298), + (25, 340), + (31, 375), + (3, 232), + (52, 294), + (24, 260), + (23, 218), + (46, 251), + (48, 354), + (21, 22), + (50, 327), + (17, 30), + (14, 343), + (5, 370), + (4, 318), + (23, 211), + (17, 328), + (28, 308), + (6, 199), + (16, 115), + (31, 219), + (20, 161), + (43, 23), + (50, 252), + (6, 349), + (56, 363), + (28, 28), + (52, 361), + (27, 351), + (60, 162), + (4, 319), + (49, 74), + (39, 247), + (54, 268), + (43, 215), + (57, 206), + (38, 329), + (25, 42), + (15, 209), + (14, 37), + (51, 3), + (59, 237), + (13, 380), + (8, 341), + (14, 292), + (37, 53), + (51, 269), + (48, 147), + (51, 143), + (14, 256), + (14, 159), + (33, 279), + (18, 220), + (13, 325), + (16, 282), + (15, 275), + (18, 300), + (37, 329), + (11, 168), + (9, 244), + (19, 199), + (29, 82), + (22, 123), + (24, 104), + (45, 47), + (5, 227), + (14, 203), + (34, 67), + (54, 159), + (23, 175), + (25, 62), + (11, 121), + (28, 118), + (59, 6), + (7, 354), + (9, 85), + (4, 252), + (9, 48), + (13, 64), + (52, 378), + (10, 142), + (23, 376), + (46, 189), + (45, 260), + (41, 271), + (37, 259), + (47, 300), + (32, 218), + (60, 206), + (41, 291), + (38, 363), + (40, 99), + (20, 235), + (45, 209), + (7, 267), + (8, 382), + (30, 82), + (20, 304), + (15, 185), + (55, 331), + (52, 324), + (34, 113), + (55, 59), + (20, 163), + (39, 349), + (55, 220), + (12, 33), + (48, 143), + (57, 353), + (30, 243), + (28, 71), + (21, 366), + (16, 285), + (36, 280), + (28, 68), + (53, 29), + (16, 45), + (32, 367), + (11, 352), + (28, 309), + (20, 94), + (16, 329), + (48, 303), + (21, 55), + (57, 235), + (16, 16), + (9, 373), + (29, 187), + (26, 301), + (52, 160), + (60, 324), + (15, 254), + (9, 314), + (28, 169), + (30, 367), + (41, 376), + (49, 218), + (40, 315), + (6, 31), + (58, 182), + (12, 260), + (30, 111), + (42, 244), + (54, 7), + (10, 213), + (3, 253), + (39, 265), + (45, 114), + (24, 31), + (30, 372), + (48, 144), + (42, 43), + (51, 218), + (52, 169), + (44, 124), + (42, 366), + (6, 176), + (52, 97), + (39, 371), + (31, 201), + (21, 343), + (32, 27), + (47, 364), + (49, 207), + (50, 268), + (34, 344), + (23, 24), + (8, 194), + (60, 311), + (43, 281), + (10, 201), + (15, 172), + (11, 118), + (19, 106), + (55, 137), + (26, 268), + (39, 370), + (53, 57), + (53, 327), + (21, 40), + (58, 243), + (41, 73), + (45, 161), + (54, 233), + (15, 72), + (38, 269), + (48, 255), + (59, 118), + (54, 203), + (49, 216), + (43, 146), + (39, 175), + (5, 314), + (43, 124), + (25, 237), + (15, 205), + (60, 197), + (24, 92), + (17, 81), + (44, 187), + (4, 327), + (56, 272), + (14, 189), + (34, 73), + (34, 342), + (8, 271), + (21, 162), + (34, 308), + (55, 191), + (44, 296), + (8, 1), + (16, 261), + (18, 216), + (19, 143), + (41, 363), + (33, 283), + (14, 101), + (18, 53), + (5, 371), + (57, 231), + (24, 294), + (42, 210), + (9, 301), + (11, 49), + (6, 50), + (38, 327), + (26, 49), + (58, 272), + (25, 29), + (48, 287), + (35, 162), + (29, 281), + (29, 78), + (56, 341), + (33, 118), + (33, 126), + (42, 331), + (11, 202), + (44, 253), + (25, 214), + (45, 133), + (33, 29), + (29, 295), + (37, 172), + (23, 190), + (27, 312), + (8, 340), + (36, 184), + (48, 200), + (35, 290), + (57, 291), + (13, 147), + (39, 99), + (40, 350), + (39, 108), + (25, 112), + (40, 302), + (21, 229), + (51, 176), + (21, 292), + (21, 50), + (60, 67), + (55, 113), + (22, 144), + (53, 205), + (21, 181), + (12, 25), + (43, 177), + (57, 307), + (15, 369), + (21, 104), + (31, 90), + (30, 241), + (13, 49), + (58, 198), + (29, 181), + (14, 322), + (31, 315), + (59, 58), + (48, 96), + (33, 205), + (27, 170), + (57, 23), + (52, 158), + (33, 173), + (37, 321), + (14, 312), + (56, 326), + (8, 8), + (59, 26), + (55, 187), + (60, 23), + (31, 49), + (32, 50), + (29, 5), + (28, 117), + (44, 339), + (28, 34), + (28, 85), + (11, 249), + (17, 231), + (15, 33), + (28, 198), + (22, 339), + (31, 271), + (20, 67), + (24, 37), + (24, 191), + (33, 94), + (3, 338), + (8, 218), + (18, 14), + (55, 76), + (16, 286), + (14, 116), + (45, 74), + (25, 240), + (4, 309), + (46, 308), + (24, 381), + (53, 335), + (19, 285), + (17, 14), + (49, 5), + (54, 309), + (27, 288), + (56, 101), + (49, 45), + (37, 152), + (55, 287), + (31, 25), + (46, 378), + (58, 356), + (47, 375), + (55, 6), + (33, 319), + (13, 267), + (33, 262), + (11, 145), + (49, 116), + (47, 290), + (47, 163), + (51, 245), + (46, 377), + (60, 205), + (33, 284), + (7, 180), + (38, 147), + (37, 79), + (32, 9), + (60, 157), + (42, 359), + (4, 122), + (39, 58), + (26, 129), + (4, 9), + (55, 117), + (29, 63), + (7, 310), + (28, 365), + (31, 345), + (38, 114), + (55, 64), + (53, 381), + (15, 379), + (29, 317), + (35, 64), + (25, 217), + (50, 227), + (35, 143), + (53, 338), + (26, 114), + (26, 122), + (18, 73), + (6, 89), + (12, 253), + (34, 103), + (13, 252), + (21, 152), + (52, 305), + (25, 257), + (12, 370), + (57, 271), + (18, 224), + (22, 331), + (38, 139), + (33, 334), + (42, 192), + (22, 302), + (27, 60), + (23, 147), + (0, 21), + (28, 377), + (22, 358), + (57, 237), + (16, 95), + (32, 31), + (55, 383), + (31, 262), + (20, 117), + (55, 88), + (45, 93), + (53, 55), + (29, 86), + (2, 288), + (27, 211), + (23, 65), + (16, 129), + (55, 128), + (34, 200), + (20, 36), + (50, 323), + (28, 257), + (20, 175), + (55, 127), + (10, 234), + (15, 229), + (38, 188), + (21, 166), + (29, 65), + (27, 374), + (5, 27), + (52, 356), + (20, 236), + (59, 87), + (52, 267), + (50, 87), + (44, 257), + (15, 164), + (56, 273), + (31, 212), + (49, 336), + (16, 208), + (59, 193), + (36, 340), + (45, 302), + (33, 131), + (12, 367), + (7, 221), + (26, 381), + (7, 295), + (19, 353), + (10, 317), + (38, 356), + (59, 230), + (12, 288), + (20, 170), + (38, 215), + (29, 160), + (36, 24), + (20, 374), + (29, 297), + (3, 316), + (53, 161), + (23, 273), + (20, 320), + (15, 265), + (55, 193), + (6, 324), + (59, 197), + (16, 13), + (19, 82), + (59, 261), + (0, 135), + (27, 328), + (13, 233), + (17, 377), + (57, 242), + (20, 14), + (31, 239), + (29, 200), + (18, 192), + (6, 310), + (54, 219), + (3, 84), + (19, 382), + (55, 159), + (40, 338), + (58, 381), + (12, 297), + (5, 90), + (28, 8), + (56, 268), + (32, 109), + (56, 250), + (57, 210), + (51, 238), + (60, 241), + (8, 141), + (26, 55), + (29, 50), + (19, 51), + (36, 140), + (42, 239), + (3, 362), + (35, 355), + (52, 60), + (10, 178), + (30, 257), + (4, 188), + (11, 248), + (42, 130), + (46, 195), + (0, 45), + (47, 106), + (52, 129), + (50, 96), + (48, 374), + (51, 204), + (46, 181), + (44, 137), + (45, 338), + (43, 284), + (41, 215), + (40, 157), + (41, 107), + (40, 234), + (31, 126), + (59, 210), + (29, 202), + (24, 170), + (41, 36), + (60, 19), + (14, 329), + (59, 331), + (35, 354), + (31, 79), + (17, 149), + (59, 278), + (57, 244), + (33, 239), + (14, 358), + (31, 297), + (46, 13), + (24, 147), + (35, 185), + (13, 83), + (25, 105), + (21, 108), + (47, 274), + (26, 73), + (55, 26), + (55, 22), + (32, 381), + (0, 184), + (6, 74), + (35, 213), + (43, 106), + (5, 175), + (55, 56), + (25, 316), + (9, 311), + (29, 96), + (26, 143), + (43, 132), + (3, 353), + (36, 150), + (43, 204), + (41, 57), + (51, 174), + (14, 49), + (16, 259), + (27, 346), + (26, 361), + (56, 104), + (32, 300), + (46, 169), + (36, 166), + (23, 102), + (60, 69), + (48, 277), + (35, 292), + (1, 375), + (53, 269), + (32, 63), + (35, 360), + (21, 356), + (51, 350), + (20, 246), + (35, 11), + (25, 99), + (43, 278), + (12, 357), + (33, 253), + (42, 253), + (32, 182), + (30, 179), + (30, 215), + (15, 177), + (30, 338), + (12, 154), + (26, 330), + (56, 199), + (32, 107), + (53, 83), + (0, 290), + (33, 178), + (31, 322), + (43, 313), + (39, 353), + (24, 270), + (13, 103), + (19, 248), + (28, 380), + (41, 259), + (58, 128), + (1, 160), + (48, 156), + (15, 301), + (25, 134), + (3, 310), + (9, 62), + (19, 25), + (2, 337), + (7, 56), + (27, 332), + (22, 75), + (13, 179), + (49, 115), + (33, 244), + (48, 48), + (36, 11), + (11, 355), + (14, 68), + (19, 167), + (13, 268), + (22, 40), + (26, 199), + (45, 359), + (31, 168), + (25, 344), + (16, 196), + (19, 181), + (27, 308), + (49, 99), + (3, 100), + (51, 64), + (2, 124), + (22, 150), + (57, 361), + (22, 138), + (41, 74), + (0, 114), + (3, 130), + (25, 57), + (36, 96), + (37, 168), + (52, 244), + (40, 280), + (16, 358), + (43, 155), + (11, 358), + (7, 335), + (9, 58), + (43, 271), + (44, 201), + (18, 76), + (10, 273), + (28, 339), + (25, 163), + (40, 78), + (12, 376), + (21, 15), + (54, 291), + (28, 176), + (47, 303), + (24, 150), + (49, 108), + (33, 32), + (53, 247), + (46, 376), + (31, 248), + (16, 160), + (28, 333), + (25, 11), + (7, 299), + (16, 46), + (6, 318), + (43, 47), + (15, 250), + (14, 85), + (18, 182), + (39, 174), + (22, 83), + (34, 313), + (7, 371), + (59, 341), + (55, 232), + (58, 45), + (57, 323), + (57, 357), + (53, 95), + (56, 382), + (31, 42), + (16, 245), + (21, 137), + (36, 2), + (43, 380), + (40, 376), + (31, 115), + (50, 196), + (23, 131), + (42, 212), + (47, 193), + (39, 258), + (31, 292), + (47, 27), + (38, 328), + (59, 221), + (1, 44), + (3, 300), + (13, 4), + (17, 201), + (14, 146), + (31, 356), + (7, 82), + (53, 224), + (48, 214), + (55, 169), + (32, 203), + (45, 58), + (54, 211), + (57, 346), + (54, 44), + (6, 71), + (17, 185), + (6, 273), + (50, 362), + (6, 223), + (36, 382), + (12, 381), + (60, 125), + (13, 150), + (29, 34), + (50, 282), + (20, 149), + (1, 143), + (55, 327), + (14, 75), + (51, 250), + (34, 62), + (54, 54), + (11, 99), + (33, 1), + (23, 280), + (46, 202), + (49, 198), + (34, 83), + (46, 215), + (60, 57), + (37, 277), + (50, 22), + (44, 101), + (50, 139), + (49, 307), + (9, 41), + (56, 356), + (9, 135), + (49, 162), + (10, 309), + (46, 278), + (37, 138), + (56, 140), + (44, 235), + (0, 60), + (44, 9), + (51, 219), + (38, 349), + (38, 240), + (33, 17), + (39, 305), + (38, 181), + (58, 377), + (31, 91), + (48, 321), + (45, 201), + (47, 155), + (44, 134), + (40, 8), + (0, 346), + (59, 219), + (48, 90), + (13, 215), + (12, 294), + (33, 55), + (53, 281), + (59, 204), + (29, 138), + (26, 75), + (8, 238), + (59, 335), + (37, 328), + (51, 273), + (45, 22), + (53, 204), + (55, 352), + (27, 367), + (23, 157), + (56, 256), + (8, 117), + (57, 77), + (54, 85), + (39, 195), + (7, 5), + (35, 120), + (51, 117), + (39, 340), + (45, 223), + (3, 279), + (51, 286), + (54, 88), + (55, 190), + (22, 151), + (29, 8), + (58, 239), + (43, 338), + (57, 71), + (39, 73), + (20, 88), + (43, 51), + (59, 187), + (37, 227), + (3, 49), + (24, 93), + (56, 357), + (41, 151), + (25, 96), + (4, 58), + (55, 245), + (13, 48), + (23, 202), + (25, 244), + (55, 282), + (14, 84), + (53, 144), + (53, 88), + (28, 342), + (26, 373), + (56, 86), + (2, 290), + (14, 330), + (57, 178), + (29, 161), + (36, 77), + (59, 53), + (24, 113), + (50, 296), + (54, 37), + (51, 343), + (3, 334), + (45, 210), + (4, 198), + (2, 345), + (14, 22), + (53, 41), + (40, 122), + (1, 293), + (42, 30), + (11, 374), + (18, 41), + (39, 264), + (23, 2), + (1, 319), + (20, 78), + (23, 266), + (41, 360), + (14, 179), + (42, 201), + (0, 78), + (23, 268), + (39, 327), + (8, 131), + (17, 134), + (45, 372), + (29, 113), + (3, 82), + (3, 277), + (19, 11), + (57, 88), + (57, 13), + (23, 97), + (2, 75), + (7, 261), + (29, 282), + (16, 174), + (34, 217), + (32, 78), + (54, 42), + (12, 332), + (55, 157), + (2, 269), + (17, 43), + (26, 3), + (53, 133), + (2, 297), + (19, 271), + (53, 4), + (1, 31), + (12, 2), + (48, 284), + (8, 339), + (34, 40), + (23, 315), + (14, 294), + (29, 276), + (44, 33), + (36, 189), + (56, 283), + (35, 39), + (31, 230), + (19, 23), + (1, 329), + (3, 128), + (30, 186), + (46, 314), + (53, 215), + (29, 61), + (7, 86), + (28, 19), + (49, 248), + (21, 319), + (12, 191), + (24, 301), + (15, 297), + (1, 85), + (22, 353), + (3, 104), + (54, 151), + (50, 74), + (53, 275), + (48, 243), + (53, 138), + (42, 171), + (16, 55), + (0, 336), + (5, 144), + (54, 74), + (54, 279), + (58, 147), + (21, 291), + (16, 54), + (54, 133), + (1, 182), + (54, 6), + (47, 267), + (57, 52), + (34, 108), + (8, 368), + (20, 0), + (7, 228), + (22, 241), + (7, 144), + (19, 237), + (17, 229), + (38, 123), + (14, 370), + (54, 86), + (20, 276), + (12, 194), + (5, 99), + (22, 377), + (58, 170), + (14, 31), + (2, 145), + (12, 184), + (54, 294), + (2, 341), + (29, 261), + (15, 362), + (46, 262), + (28, 378), + (44, 250), + (6, 340), + (0, 165), + (21, 295), + (37, 143), + (32, 215), + (25, 329), + (53, 140), + (28, 172), + (23, 330), + (34, 21), + (24, 58), + (48, 27), + (20, 247), + (58, 306), + (19, 257), + (10, 238), + (26, 93), + (7, 12), + (24, 258), + (60, 357), + (49, 174), + (16, 102), + (30, 200), + (19, 5), + (14, 233), + (4, 224), + (1, 120), + (2, 343), + (37, 101), + (3, 377), + (47, 14), + (56, 33), + (20, 279), + (55, 79), + (21, 81), + (28, 382), + (9, 202), + (36, 238), + (53, 35), + (37, 264), + (25, 40), + (12, 172), + (34, 352), + (13, 91), + (35, 66), + (41, 128), + (16, 34), + (21, 16), + (13, 259), + (49, 70), + (31, 336), + (18, 373), + (7, 151), + (40, 289), + (59, 276), + (29, 137), + (31, 257), + (48, 128), + (41, 305), + (44, 47), + (22, 341), + (30, 309), + (39, 214), + (50, 309), + (4, 374), + (17, 34), + (8, 322), + (4, 55), + (53, 184), + (26, 90), + (25, 366), + (28, 245), + (39, 86), + (36, 342), + (13, 356), + (46, 209), + (14, 40), + (29, 265), + (4, 25), + (2, 60), + (1, 141), + (8, 246), + (24, 231), + (51, 329), + (5, 229), + (23, 227), + (36, 179), + (2, 242), + (31, 133), + (32, 1), + (29, 83), + (19, 276), + (27, 316), + (20, 39), + (19, 259), + (17, 242), + (30, 159), + (45, 346), + (23, 26), + (13, 94), + (2, 162), + (0, 29), + (38, 79), + (42, 128), + (19, 187), + (57, 16), + (9, 83), + (58, 207), + (54, 70), + (17, 17), + (49, 257), + (32, 351), + (1, 3), + (13, 36), + (15, 195), + (4, 208), + (1, 55), + (53, 9), + (9, 72), + (7, 135), + (33, 339), + (7, 320), + (51, 41), + (53, 227), + (10, 91), + (34, 138), + (20, 227), + (30, 10), + (31, 362), + (4, 280), + (16, 85), + (35, 123), + (22, 109), + (58, 50), + (49, 356), + (4, 15), + (19, 207), + (44, 94), + (36, 66), + (13, 248), + (28, 207), + (23, 85), + (40, 133), + (18, 108), + (35, 32), + (57, 207), + (35, 113), + (50, 372), + (38, 36), + (0, 344), + (17, 39), + (2, 278), + (3, 238), + (56, 120), + (48, 43), + (58, 208), + (32, 352), + (53, 319), + (47, 195), + (56, 213), + (23, 318), + (25, 380), + (0, 345), + (34, 317), + (45, 326), + (39, 119), + (11, 107), + (36, 356), + (37, 48), + (42, 245), + (44, 359), + (36, 84), + (11, 124), + (56, 72), + (18, 125), + (8, 172), + (7, 359), + (57, 198), + (18, 269), + (19, 242), + (4, 279), + (59, 249), + (26, 70), + (10, 75), + (13, 289), + (55, 68), + (5, 231), + (18, 6), + (8, 290), + (11, 55), + (42, 299), + (1, 40), + (48, 112), + (13, 30), + (60, 347), + (30, 31), + (11, 47), + (36, 338), + (18, 25), + (28, 361), + (56, 131), + (48, 51), + (46, 70), + (42, 319), + (0, 327), + (59, 30), + (60, 36), + (37, 353), + (46, 258), + (39, 32), + (50, 131), + (36, 134), + (49, 190), + (60, 83), + (33, 280), + (30, 32), + (46, 221), + (23, 33), + (12, 98), + (33, 323), + (25, 77), + (25, 280), + (4, 24), + (48, 72), + (46, 324), + (50, 51), + (41, 21), + (1, 357), + (44, 119), + (52, 155), + (30, 282), + (39, 120), + (40, 94), + (23, 224), + (36, 42), + (46, 368), + (43, 144), + (11, 320), + (44, 366), + (0, 150), + (9, 39), + (51, 215), + (42, 90), + (11, 235), + (52, 207), + (45, 104), + (49, 117), + (44, 264), + (45, 365), + (41, 297), + (41, 146), + (0, 256), + (1, 272), + (40, 261), + (0, 147), + (31, 330), + (41, 273), + (56, 354), + (43, 359), + (33, 306), + (14, 45), + (5, 34), + (2, 96), + (8, 270), + (45, 167), + (28, 33), + (8, 378), + (49, 67), + (60, 345), + (14, 278), + (31, 203), + (35, 265), + (58, 11), + (60, 283), + (5, 336), + (35, 6), + (3, 10), + (48, 328), + (33, 175), + (60, 293), + (35, 280), + (60, 12), + (59, 168), + (43, 162), + (39, 336), + (59, 319), + (29, 309), + (58, 61), + (39, 240), + (29, 1), + (11, 368), + (6, 271), + (38, 242), + (5, 30), + (50, 290), + (26, 353), + (7, 158), + (5, 105), + (60, 265), + (22, 1), + (4, 35), + (35, 168), + (14, 282), + (1, 75), + (41, 129), + (58, 125), + (31, 223), + (39, 166), + (37, 12), + (60, 183), + (12, 64), + (45, 160), + (6, 185), + (8, 345), + (24, 10), + (1, 326), + (31, 232), + (35, 284), + (3, 53), + (10, 300), + (27, 287), + (25, 331), + (24, 312), + (2, 19), + (53, 277), + (52, 203), + (14, 46), + (44, 274), + (58, 5), + (38, 350), + (1, 235), + (1, 354), + (0, 247), + (33, 136), + (34, 358), + (57, 282), + (57, 299), + (26, 22), + (32, 180), + (31, 107), + (12, 312), + (57, 309), + (29, 348), + (9, 181), + (0, 20), + (32, 301), + (54, 269), + (21, 216), + (54, 105), + (35, 188), + (33, 249), + (5, 378), + (4, 366), + (10, 235), + (23, 317), + (47, 310), + (40, 277), + (16, 134), + (12, 181), + (13, 327), + (0, 197), + (1, 285), + (1, 328), + (57, 57), + (0, 162), + (0, 54), + (2, 279), + (35, 242), + (34, 215), + (15, 50), + (14, 127), + (6, 291), + (58, 20), + (22, 281), + (26, 154), + (53, 262), + (21, 335), + (55, 318), + (1, 252), + (48, 68), + (25, 36), + (2, 18), + (32, 287), + (12, 380), + (29, 106), + (21, 138), + (0, 70), + (7, 16), + (7, 184), + (7, 98), + (59, 345), + (53, 58), + (49, 339), + (0, 351), + (51, 44), + (10, 305), + (2, 166), + (17, 171), + (15, 337), + (1, 276), + (29, 247), + (24, 359), + (57, 84), + (31, 243), + (57, 380), + (55, 29), + (14, 182), + (5, 149), + (60, 341), + (37, 275), + (33, 71), + (59, 266), + (45, 147), + (24, 215), + (1, 251), + (25, 347), + (13, 115), + (21, 287), + (45, 142), + (54, 356), + (2, 194), + (36, 32), + (0, 46), + (1, 380), + (1, 185), + (35, 138), + (14, 118), + (38, 212), + (35, 0), + (17, 381), + (24, 380), + (39, 44), + (21, 177), + (48, 66), + (47, 292), + (1, 134), + (25, 108), + (21, 331), + (53, 223), + (45, 115), + (13, 343), + (7, 120), + (23, 150), + (51, 42), + (2, 62), + (26, 219), + (21, 0), + (38, 116), + (5, 63), + (32, 364), + (33, 114), + (11, 158), + (40, 54), + (21, 314), + (33, 112), + (20, 101), + (49, 134), + (6, 296), + (26, 207), + (2, 99), + (36, 266), + (2, 127), + (59, 355), + (53, 2), + (23, 5), + (22, 36), + (33, 278), + (47, 160), + (8, 177), + (8, 18), + (0, 217), + (53, 152), + (14, 363), + (36, 23), + (51, 97), + (12, 129), + (32, 62), + (24, 336), + (49, 253), + (48, 203), + (16, 273), + (5, 72), + (43, 114), + (53, 87), + (24, 99), + (2, 304), + (35, 222), + (7, 102), + (30, 69), + (11, 120), + (55, 52), + (35, 302), + (60, 276), + (12, 84), + (23, 272), + (18, 277), + (55, 130), + (26, 35), + (26, 156), + (55, 40), + (36, 341), + (29, 242), + (26, 238), + (16, 153), + (1, 294), + (23, 207), + (6, 15), + (55, 316), + (53, 15), + (8, 298), + (13, 24), + (8, 83), + (60, 165), + (0, 368), + (21, 37), + (2, 93), + (25, 301), + (36, 17), + (4, 257), + (2, 328), + (0, 286), + (18, 84), + (39, 358), + (26, 321), + (21, 64), + (19, 246), + (14, 108), + (32, 37), + (24, 262), + (26, 47), + (8, 110), + (49, 97), + (17, 238), + (54, 26), + (41, 54), + (36, 0), + (57, 92), + (48, 7), + (57, 4), + (31, 353), + (17, 53), + (26, 16), + (13, 278), + (7, 383), + (5, 146), + (33, 231), + (20, 356), + (2, 351), + (23, 311), + (47, 358), + (14, 129), + (33, 276), + (16, 17), + (24, 13), + (54, 190), + (43, 75), + (39, 142), + (39, 113), + (54, 316), + (24, 23), + (27, 271), + (55, 358), + (2, 220), + (12, 318), + (20, 290), + (45, 38), + (19, 358), + (57, 162), + (18, 8), + (1, 50), + (33, 18), + (28, 74), + (53, 323), + (2, 211), + (37, 279), + (3, 118), + (57, 263), + (7, 131), + (33, 355), + (29, 182), + (57, 25), + (32, 158), + (31, 370), + (16, 172), + (56, 47), + (22, 41), + (30, 343), + (24, 107), + (27, 137), + (17, 35), + (45, 98), + (35, 8), + (55, 78), + (48, 151), + (25, 116), + (33, 349), + (14, 263), + (21, 202), + (43, 203), + (59, 307), + (3, 365), + (33, 122), + (53, 163), + (32, 235), + (30, 136), + (58, 297), + (14, 213), + (30, 293), + (16, 378), + (17, 160), + (3, 366), + (41, 346), + (46, 329), + (49, 182), + (44, 248), + (39, 184), + (14, 72), + (55, 182), + (22, 119), + (21, 51), + (55, 71), + (19, 226), + (9, 381), + (24, 80), + (20, 47), + (35, 238), + (43, 13), + (57, 255), + (27, 37), + (38, 190), + (51, 91), + (21, 277), + (57, 298), + (33, 288), + (22, 344), + (23, 262), + (58, 331), + (37, 188), + (29, 10), + (38, 355), + (37, 322), + (1, 11), + (21, 227), + (42, 229), + (48, 154), + (53, 125), + (33, 79), + (7, 366), + (19, 228), + (19, 46), + (12, 299), + (44, 142), + (32, 333), + (25, 209), + (3, 233), + (19, 201), + (35, 352), + (34, 112), + (32, 213), + (34, 90), + (16, 105), + (34, 164), + (2, 121), + (22, 170), + (17, 27), + (13, 81), + (37, 185), + (32, 25), + (24, 277), + (35, 101), + (14, 148), + (6, 124), + (1, 196), + (3, 364), + (16, 97), + (48, 281), + (35, 336), + (18, 79), + (29, 259), + (32, 340), + (17, 335), + (38, 194), + (7, 212), + (29, 244), + (14, 359), + (44, 380), + (35, 50), + (53, 354), + (53, 62), + (1, 243), + (53, 287), + (21, 188), + (15, 277), + (13, 41), + (55, 235), + (50, 170), + (5, 103), + (5, 255), + (0, 322), + (59, 100), + (1, 130), + (35, 171), + (7, 337), + (56, 98), + (2, 41), + (53, 226), + (58, 178), + (20, 371), + (9, 133), + (40, 362), + (44, 154), + (38, 225), + (56, 50), + (24, 364), + (56, 175), + (60, 343), + (14, 73), + (15, 261), + (5, 209), + (27, 339), + (21, 194), + (4, 134), + (10, 263), + (17, 123), + (13, 172), + (57, 17), + (19, 14), + (59, 205), + (58, 268), + (60, 305), + (35, 140), + (9, 49), + (60, 238), + (33, 188), + (32, 335), + (1, 110), + (55, 303), + (55, 247), + (6, 64), + (7, 84), + (57, 118), + (18, 13), + (11, 96), + (29, 90), + (57, 40), + (32, 102), + (47, 67), + (1, 103), + (16, 21), + (14, 373), + (49, 365), + (13, 369), + (5, 92), + (31, 261), + (49, 358), + (59, 36), + (5, 152), + (28, 250), + (11, 126), + (58, 348), + (1, 41), + (26, 54), + (26, 66), + (41, 372), + (45, 263), + (40, 46), + (60, 349), + (32, 366), + (52, 191), + (1, 82), + (32, 150), + (14, 157), + (33, 123), + (0, 302), + (15, 204), + (43, 293), + (57, 277), + (58, 226), + (8, 195), + (8, 224), + (32, 157), + (9, 51), + (0, 81), + (8, 306), + (49, 311), + (35, 133), + (2, 105), + (28, 317), + (8, 364), + (60, 149), + (51, 214), + (58, 333), + (51, 212), + (14, 120), + (25, 370), + (59, 292), + (38, 38), + (24, 354), + (12, 18), + (8, 92), + (32, 124), + (7, 258), + (8, 365), + (50, 311), + (33, 0), + (53, 320), + (25, 321), + (14, 306), + (21, 6), + (47, 135), + (27, 270), + (58, 148), + (39, 31), + (26, 69), + (57, 268), + (18, 212), + (31, 81), + (3, 80), + (3, 42), + (57, 234), + (11, 67), + (32, 227), + (31, 173), + (42, 172), + (48, 49), + (46, 25), + (30, 321), + (6, 150), + (59, 304), + (24, 126), + (35, 126), + (29, 80), + (30, 124), + (38, 345), + (3, 8), + (36, 199), + (50, 189), + (14, 15), + (17, 51), + (7, 87), + (21, 145), + (48, 273), + (2, 212), + (57, 85), + (55, 58), + (50, 379), + (37, 127), + (39, 290), + (44, 5), + (39, 193), + (47, 374), + (58, 52), + (55, 243), + (56, 2), + (43, 31), + (3, 56), + (59, 311), + (56, 331), + (7, 187), + (30, 75), + (30, 64), + (31, 2), + (13, 295), + (23, 349), + (56, 150), + (33, 238), + (8, 166), + (53, 380), + (30, 74), + (10, 357), + (56, 231), + (8, 276), + (28, 273), + (46, 306), + (49, 220), + (39, 152), + (29, 272), + (12, 304), + (3, 167), + (55, 257), + (28, 90), + (38, 144), + (18, 61), + (13, 131), + (42, 274), + (56, 191), + (41, 383), + (31, 150), + (48, 216), + (39, 36), + (56, 304), + (21, 190), + (58, 343), + (52, 41), + (39, 374), + (19, 335), + (25, 224), + (12, 247), + (0, 44), + (56, 319), + (23, 244), + (10, 41), + (39, 301), + (27, 72), + (51, 338), + (44, 131), + (54, 108), + (17, 6), + (2, 110), + (50, 249), + (35, 92), + (35, 159), + (55, 354), + (9, 3), + (38, 331), + (52, 84), + (51, 130), + (3, 345), + (53, 344), + (9, 356), + (46, 40), + (52, 292), + (8, 120), + (7, 138), + (33, 88), + (7, 189), + (11, 136), + (45, 236), + (32, 87), + (8, 383), + (60, 266), + (28, 44), + (18, 111), + (9, 367), + (30, 303), + (28, 95), + (37, 37), + (56, 204), + (50, 0), + (60, 189), + (11, 298), + (53, 309), + (39, 116), + (59, 285), + (39, 229), + (8, 176), + (39, 230), + (49, 86), + (37, 284), + (11, 299), + (29, 139), + (37, 124), + (29, 22), + (18, 355), + (48, 344), + (1, 9), + (46, 319), + (4, 105), + (32, 307), + (9, 317), + (39, 154), + (37, 257), + (1, 30), + (10, 296), + (44, 269), + (50, 127), + (1, 1), + (13, 171), + (51, 83), + (60, 342), + (10, 269), + (55, 238), + (45, 127), + (25, 152), + (4, 174), + (35, 47), + (52, 166), + (39, 8), + (0, 138), + (11, 86), + (51, 52), + (44, 301), + (44, 329), + (43, 254), + (46, 87), + (11, 342), + (51, 271), + (41, 311), + (2, 230), + (29, 19), + (46, 41), + (52, 168), + (45, 0), + (25, 162), + (30, 299), + (51, 39), + (45, 108), + (52, 360), + (9, 215), + (50, 114), + (2, 248), + (44, 56), + (40, 44), + (44, 305), + (44, 173), + (0, 312), + (44, 4), + (38, 272), + (40, 215), + (52, 116), + (41, 56), + (42, 123), + (40, 284), + (42, 200), + (41, 283), + (50, 322), + (41, 270), + (41, 255), + (41, 344), + (48, 120), + (56, 329), + (60, 292), + (33, 227), + (4, 125), + (0, 89), + (35, 310), + (1, 376), + (4, 274), + (10, 20), + (55, 343), + (0, 103), + (50, 284), + (51, 1), + (31, 300), + (10, 356), + (59, 182), + (35, 325), + (30, 250), + (25, 3), + (3, 273), + (0, 126), + (3, 259), + (7, 328), + (11, 347), + (46, 115), + (59, 371), + (4, 126), + (23, 161), + (2, 58), + (0, 202), + (33, 345), + (7, 301), + (53, 255), + (23, 362), + (1, 352), + (57, 120), + (23, 200), + (55, 189), + (33, 352), + (23, 52), + (49, 380), + (60, 200), + (23, 129), + (16, 230), + (4, 330), + (12, 55), + (7, 52), + (0, 263), + (45, 252), + (58, 146), + (6, 235), + (48, 319), + (55, 42), + (60, 302), + (3, 189), + (6, 338), + (49, 332), + (0, 195), + (59, 153), + (14, 216), + (33, 49), + (43, 227), + (53, 156), + (1, 79), + (51, 296), + (35, 208), + (6, 84), + (53, 248), + (4, 312), + (12, 88), + (51, 90), + (47, 227), + (35, 61), + (7, 53), + (3, 204), + (1, 381), + (0, 357), + (25, 345), + (60, 244), + (8, 133), + (34, 361), + (59, 129), + (47, 189), + (1, 187), + (43, 294), + (29, 284), + (34, 26), + (35, 135), + (8, 48), + (10, 338), + (4, 74), + (38, 216), + (0, 153), + (1, 126), + (12, 169), + (36, 344), + (35, 216), + (0, 209), + (37, 135), + (33, 116), + (0, 111), + (33, 13), + (9, 94), + (59, 365), + (37, 256), + (33, 166), + (8, 105), + (0, 341), + (36, 256), + (2, 253), + (56, 108), + (52, 55), + (46, 19), + (0, 25), + (23, 32), + (23, 137), + (3, 123), + (35, 98), + (40, 141), + (4, 1), + (0, 174), + (35, 259), + (3, 249), + (26, 319), + (0, 192), + (5, 187), + (30, 53), + (7, 137), + (33, 225), + (43, 170), + (7, 10), + (0, 17), + (35, 19), + (3, 343), + (51, 282), + (60, 31), + (33, 292), + (43, 370), + (25, 238), + (35, 127), + (0, 324), + (60, 65), + (58, 368), + (5, 315), + (23, 25), + (0, 83), + (59, 326), + (54, 123), + (53, 257), + (23, 139), + (56, 87), + (0, 279), + (0, 300), + (6, 69), + (0, 84), + (0, 361), + (60, 224), + (2, 213), + (52, 201), + (55, 47), + (3, 14), + (60, 33), + (43, 286), + (2, 334), + (58, 33), + (32, 253), + (24, 145), + (29, 178), + (7, 211), + (2, 372), + (39, 283), + (4, 140), + (25, 339), + (0, 77), + (31, 213), + (0, 350), + (31, 93), + (59, 218), + (3, 340), + (58, 75), + (22, 126), + (31, 287), + (22, 234), + (57, 108), + (11, 135), + (34, 208), + (60, 5), + (59, 179), + (25, 154), + (56, 336), + (0, 65), + (15, 354), + (8, 206), + (60, 193), + (1, 361), + (0, 292), + (2, 258), + (25, 45), + (2, 295), + (4, 97), + (12, 293), + (48, 221), + (55, 84), + (6, 217), + (0, 222), + (57, 208), + (33, 185), + (0, 314), + (4, 31), + (3, 289), + (56, 148), + (0, 67), + (59, 49), + (49, 96), + (2, 4), + (3, 344), + (12, 281), + (1, 203), + (7, 242), + (1, 342), + (1, 212), + (57, 286), + (32, 35), + (2, 219), + (35, 5), + (1, 133), + (33, 184), + (29, 201), + (52, 67), + (12, 209), + (0, 187), + (0, 358), + (35, 251), + (53, 173), + (0, 225), + (1, 266), + (2, 0), + (59, 89), + (54, 51), + (25, 118), + (8, 149), + (23, 329), + (57, 364), + (33, 56), + (35, 272), + (2, 252), + (58, 341), + (25, 291), + (2, 289), + (59, 130), + (36, 216), + (37, 147), + (35, 278), + (11, 71), + (39, 245), + (0, 253), + (33, 119), + (26, 362), + (57, 316), + (29, 140), + (6, 360), + (2, 65), + (7, 168), + (52, 71), + (41, 194), + (7, 146), + (51, 172), + (16, 164), + (1, 208), + (57, 272), + (4, 299), + (7, 191), + (58, 285), + (60, 142), + (19, 176), + (0, 383), + (33, 97), + (53, 220), + (0, 321), + (1, 13), + (36, 163), + (60, 245), + (44, 320), + (4, 271), + (37, 357), + (2, 363), + (54, 148), + (19, 247), + (2, 348), + (24, 304), + (2, 139), + (35, 169), + (12, 12), + (23, 18), + (31, 106), + (59, 336), + (53, 273), + (23, 189), + (32, 73), + (2, 298), + (7, 222), + (44, 147), + (26, 364), + (34, 191), + (1, 234), + (1, 155), + (0, 190), + (18, 170), + (14, 214), + (10, 22), + (60, 320), + (20, 297), + (7, 234), + (29, 143), + (55, 355), + (17, 370), + (3, 211), + (10, 376), + (31, 288), + (14, 140), + (50, 133), + (1, 250), + (40, 221), + (16, 25), + (10, 81), + (1, 256), + (25, 70), + (47, 84), + (26, 370), + (3, 152), + (25, 185), + (41, 233), + (32, 241), + (60, 80), + (18, 40), + (2, 205), + (59, 300), + (18, 323), + (0, 13), + (2, 67), + (33, 380), + (11, 69), + (1, 284), + (11, 52), + (21, 109), + (29, 13), + (26, 168), + (2, 137), + (1, 189), + (38, 276), + (7, 330), + (2, 9), + (27, 114), + (2, 97), + (6, 225), + (5, 308), + (12, 235), + (17, 302), + (12, 118), + (23, 302), + (20, 314), + (1, 244), + (21, 127), + (2, 182), + (49, 353), + (1, 137), + (1, 347), + (39, 324), + (0, 379), + (0, 106), + (1, 67), + (51, 361), + (45, 311), + (59, 101), + (53, 280), + (49, 179), + (39, 235), + (1, 348), + (0, 371), + (16, 336), + (0, 278), + (49, 124), + (57, 184), + (2, 125), + (51, 61), + (20, 154), + (2, 369), + (57, 194), + (2, 126), + (6, 207), + (22, 12), + (57, 226), + (19, 214), + (57, 47), + (43, 327), + (60, 18), + (2, 383), + (58, 258), + (26, 358), + (1, 123), + (35, 170), + (59, 213), + (23, 236), + (5, 237), + (55, 270), + (14, 316), + (26, 227), + (45, 292), + (22, 182), + (10, 377), + (2, 122), + (4, 378), + (33, 219), + (1, 125), + (2, 195), + (29, 211), + (22, 25), + (12, 282), + (3, 320), + (43, 136), + (2, 151), + (3, 111), + (1, 205), + (7, 307), + (2, 203), + (51, 96), + (43, 105), + (40, 375), + (5, 184), + (1, 165), + (5, 379), + (37, 382), + (2, 22), + (24, 169), + (3, 371), + (0, 134), + (1, 154), + (19, 129), + (2, 360), + (59, 243), + (58, 344), + (43, 201), + (1, 273), + (58, 230), + (1, 274), + (1, 28), + (41, 210), + (53, 33), + (1, 378), + (59, 238), + (23, 260), + (36, 173), + (44, 346), + (57, 224), + (49, 308), + (1, 277), + (33, 169), + (1, 88), + (35, 192), + (7, 246), + (29, 315), + (17, 259), + (3, 308), + (57, 300), + (34, 27), + (35, 65), + (2, 251), + (43, 119), + (38, 255), + (24, 373), + (2, 240), + (1, 217), + (2, 138), + (29, 217), + (59, 90), + (2, 192), + (19, 326), + (1, 198), + (1, 98), + (54, 371), + (4, 7), + (52, 200), + (15, 146), + (0, 12), + (50, 301), + (1, 370), + (24, 222), + (2, 26), + (23, 356), + (31, 122), + (5, 124), + (28, 100), + (23, 162), + (44, 161), + (19, 36), + (22, 337), + (28, 64), + (44, 46), + (45, 212), + (2, 321), + (2, 100), + (1, 74), + (54, 120), + (26, 264), + (2, 196), + (3, 294), + (2, 141), + (5, 109), + (31, 39), + (53, 259), + (30, 375), + (0, 261), + (12, 301), + (1, 0), + (26, 81), + (2, 286), + (25, 357), + (1, 280), + (36, 188), + (0, 180), + (15, 79), + (2, 157), + (47, 149), + (38, 69), + (53, 31), + (53, 193), + (57, 83), + (27, 182), + (55, 317), + (43, 5), + (2, 367), + (48, 370), + (35, 274), + (59, 289), + (43, 24), + (58, 203), + (8, 314), + (0, 266), + (47, 40), + (19, 294), + (49, 176), + (60, 370), + (5, 226), + (2, 94), + (20, 319), + (28, 234), + (5, 178), + (0, 248), + (55, 221), + (53, 169), + (10, 184), + (24, 134), + (19, 180), + (5, 35), + (24, 267), + (34, 266), + (30, 139), + (33, 229), + (33, 241), + (55, 116), + (24, 328), + (23, 298), + (54, 143), + (3, 4), + (44, 30), + (2, 1), + (7, 161), + (1, 142), + (0, 214), + (2, 354), + (26, 345), + (4, 131), + (56, 1), + (29, 192), + (51, 186), + (0, 57), + (13, 98), + (23, 186), + (6, 8), + (5, 338), + (19, 287), + (2, 144), + (3, 43), + (59, 96), + (17, 172), + (3, 113), + (44, 190), + (52, 298), + (2, 357), + (4, 311), + (37, 202), + (2, 20), + (17, 157), + (32, 339), + (57, 46), + (22, 72), + (10, 284), + (2, 95), + (9, 129), + (31, 225), + (40, 325), + (4, 354), + (43, 1), + (17, 92), + (50, 319), + (16, 338), + (59, 32), + (24, 77), + (1, 306), + (34, 42), + (16, 108), + (43, 55), + (33, 298), + (3, 171), + (25, 234), + (49, 383), + (6, 267), + (27, 9), + (27, 366), + (35, 27), + (9, 292), + (49, 305), + (31, 260), + (2, 325), + (33, 60), + (54, 365), + (13, 245), + (58, 303), + (25, 68), + (5, 93), + (2, 353), + (32, 259), + (1, 331), + (2, 76), + (58, 310), + (35, 379), + (40, 275), + (2, 256), + (21, 322), + (1, 121), + (8, 349), + (34, 333), + (46, 333), + (43, 358), + (26, 262), + (32, 177), + (59, 185), + (20, 140), + (53, 376), + (13, 231), + (3, 194), + (29, 205), + (18, 261), + (53, 358), + (21, 208), + (29, 179), + (51, 370), + (19, 24), + (8, 6), + (31, 75), + (6, 330), + (37, 177), + (60, 379), + (19, 150), + (42, 374), + (2, 42), + (36, 104), + (35, 29), + (15, 126), + (8, 379), + (3, 151), + (56, 129), + (58, 316), + (7, 32), + (37, 38), + (24, 32), + (46, 322), + (19, 125), + (3, 129), + (33, 211), + (25, 346), + (45, 364), + (28, 160), + (2, 198), + (55, 201), + (7, 207), + (2, 377), + (8, 32), + (30, 98), + (3, 181), + (22, 313), + (57, 337), + (3, 62), + (27, 16), + (7, 244), + (21, 260), + (17, 140), + (54, 237), + (48, 246), + (60, 99), + (54, 50), + (2, 133), + (11, 27), + (29, 186), + (54, 80), + (3, 88), + (19, 110), + (58, 69), + (29, 97), + (53, 346), + (6, 244), + (35, 116), + (55, 122), + (12, 345), + (14, 190), + (14, 337), + (24, 348), + (59, 175), + (0, 235), + (16, 191), + (20, 341), + (6, 304), + (16, 302), + (43, 81), + (26, 65), + (25, 287), + (12, 313), + (57, 144), + (2, 368), + (27, 85), + (15, 96), + (19, 348), + (58, 101), + (33, 113), + (53, 170), + (1, 248), + (15, 194), + (48, 4), + (15, 121), + (56, 16), + (3, 90), + (43, 263), + (43, 29), + (19, 272), + (33, 307), + (57, 21), + (13, 22), + (18, 371), + (11, 366), + (54, 24), + (54, 352), + (26, 157), + (55, 36), + (57, 274), + (31, 137), + (35, 244), + (1, 138), + (48, 75), + (59, 330), + (17, 161), + (15, 19), + (38, 321), + (21, 62), + (48, 23), + (15, 255), + (47, 39), + (51, 263), + (15, 191), + (48, 111), + (30, 297), + (51, 265), + (54, 91), + (29, 73), + (1, 87), + (20, 232), + (7, 152), + (57, 1), + (1, 2), + (10, 362), + (32, 71), + (25, 210), + (10, 371), + (19, 172), + (23, 10), + (40, 264), + (58, 54), + (1, 145), + (24, 266), + (58, 274), + (53, 294), + (28, 327), + (56, 299), + (31, 88), + (36, 300), + (59, 102), + (44, 237), + (0, 380), + (53, 145), + (24, 269), + (59, 270), + (15, 80), + (38, 197), + (59, 60), + (47, 122), + (6, 283), + (46, 303), + (36, 44), + (0, 173), + (35, 323), + (11, 340), + (57, 45), + (42, 316), + (5, 334), + (18, 105), + (29, 99), + (1, 291), + (19, 148), + (24, 342), + (28, 324), + (15, 119), + (30, 134), + (15, 325), + (11, 246), + (14, 65), + (4, 147), + (54, 283), + (31, 183), + (56, 322), + (49, 287), + (38, 145), + (19, 239), + (7, 236), + (1, 171), + (29, 345), + (24, 34), + (5, 128), + (13, 45), + (21, 381), + (5, 7), + (60, 84), + (16, 86), + (19, 9), + (21, 289), + (2, 43), + (8, 28), + (12, 306), + (16, 137), + (31, 109), + (15, 160), + (29, 189), + (21, 149), + (29, 305), + (15, 66), + (53, 332), + (32, 229), + (50, 380), + (37, 252), + (25, 242), + (32, 165), + (10, 60), + (8, 82), + (42, 89), + (8, 116), + (11, 270), + (33, 170), + (18, 274), + (54, 98), + (60, 169), + (16, 117), + (46, 348), + (27, 238), + (2, 232), + (53, 217), + (45, 66), + (13, 286), + (5, 101), + (56, 373), + (43, 43), + (57, 11), + (58, 68), + (33, 193), + (28, 274), + (15, 324), + (33, 374), + (51, 352), + (4, 60), + (38, 107), + (45, 373), + (3, 81), + (35, 79), + (14, 268), + (42, 295), + (23, 334), + (36, 254), + (50, 68), + (49, 187), + (5, 287), + (18, 283), + (32, 219), + (3, 342), + (49, 139), + (59, 63), + (22, 237), + (4, 238), + (34, 88), + (7, 225), + (58, 169), + (13, 189), + (18, 326), + (34, 271), + (20, 8), + (11, 354), + (12, 289), + (8, 3), + (48, 3), + (51, 116), + (14, 377), + (35, 74), + (60, 77), + (1, 169), + (50, 364), + (15, 257), + (28, 51), + (10, 280), + (13, 8), + (18, 378), + (12, 23), + (55, 97), + (46, 173), + (13, 209), + (33, 177), + (32, 354), + (48, 259), + (59, 372), + (46, 205), + (43, 314), + (35, 304), + (2, 109), + (14, 368), + (51, 371), + (14, 10), + (16, 0), + (54, 197), + (39, 157), + (21, 20), + (18, 12), + (35, 296), + (6, 194), + (55, 46), + (0, 156), + (54, 76), + (54, 263), + (46, 31), + (36, 148), + (30, 351), + (18, 110), + (39, 341), + (39, 295), + (40, 9), + (9, 71), + (44, 136), + (7, 288), + (18, 367), + (29, 35), + (30, 2), + (51, 145), + (57, 288), + (7, 181), + (25, 361), + (2, 361), + (39, 244), + (28, 253), + (35, 240), + (18, 341), + (11, 77), + (9, 257), + (19, 171), + (6, 93), + (52, 173), + (26, 144), + (45, 268), + (19, 101), + (1, 353), + (37, 95), + (29, 258), + (8, 200), + (55, 252), + (60, 78), + (11, 138), + (25, 229), + (44, 160), + (4, 346), + (27, 127), + (4, 109), + (0, 26), + (18, 38), + (0, 307), + (7, 233), + (60, 273), + (6, 372), + (33, 174), + (10, 189), + (10, 109), + (8, 190), + (60, 323), + (2, 45), + (37, 69), + (32, 304), + (52, 195), + (44, 300), + (52, 313), + (54, 117), + (43, 226), + (36, 176), + (8, 182), + (32, 341), + (1, 269), + (10, 140), + (3, 19), + (37, 183), + (7, 105), + (13, 270), + (50, 156), + (52, 332), + (17, 279), + (21, 321), + (29, 362), + (6, 288), + (3, 315), + (12, 218), + (23, 258), + (17, 189), + (14, 235), + (11, 222), + (29, 36), + (49, 221), + (23, 342), + (15, 262), + (31, 29), + (39, 76), + (21, 112), + (45, 41), + (4, 217), + (5, 293), + (23, 229), + (48, 242), + (33, 316), + (25, 352), + (9, 312), + (31, 312), + (38, 195), + (30, 14), + (60, 139), + (33, 196), + (35, 367), + (1, 14), + (57, 41), + (45, 118), + (55, 340), + (45, 110), + (53, 200), + (21, 151), + (14, 74), + (10, 46), + (38, 375), + (38, 286), + (12, 248), + (10, 337), + (5, 81), + (33, 179), + (41, 314), + (11, 176), + (30, 312), + (6, 86), + (53, 174), + (32, 143), + (11, 74), + (6, 87), + (53, 203), + (21, 220), + (8, 77), + (5, 148), + (51, 200), + (10, 241), + (58, 176), + (6, 141), + (29, 134), + (42, 382), + (29, 306), + (21, 255), + (3, 263), + (13, 133), + (29, 190), + (30, 327), + (60, 337), + (36, 311), + (0, 129), + (4, 329), + (59, 323), + (31, 124), + (1, 23), + (46, 249), + (33, 224), + (32, 38), + (49, 93), + (57, 365), + (11, 237), + (57, 44), + (35, 161), + (18, 68), + (35, 324), + (11, 330), + (13, 281), + (4, 336), + (60, 166), + (52, 377), + (18, 204), + (39, 35), + (24, 223), + (18, 92), + (48, 315), + (38, 332), + (55, 171), + (54, 8), + (6, 13), + (40, 296), + (51, 115), + (57, 90), + (19, 307), + (6, 329), + (43, 80), + (4, 239), + (6, 353), + (13, 318), + (59, 172), + (14, 221), + (51, 184), + (24, 273), + (49, 204), + (33, 265), + (60, 82), + (56, 64), + (45, 20), + (0, 193), + (59, 297), + (49, 85), + (46, 290), + (56, 297), + (6, 106), + (43, 287), + (53, 168), + (0, 246), + (29, 332), + (5, 335), + (3, 327), + (10, 328), + (4, 250), + (2, 342), + (30, 143), + (18, 350), + (45, 154), + (31, 299), + (3, 83), + (7, 253), + (5, 194), + (6, 332), + (11, 297), + (14, 60), + (58, 324), + (7, 227), + (11, 160), + (58, 291), + (50, 352), + (8, 57), + (8, 239), + (44, 280), + (3, 304), + (57, 220), + (13, 5), + (31, 208), + (52, 17), + (58, 18), + (9, 64), + (43, 12), + (6, 103), + (24, 224), + (13, 253), + (9, 52), + (11, 29), + (17, 319), + (25, 35), + (45, 207), + (38, 203), + (14, 57), + (30, 129), + (35, 160), + (37, 224), + (56, 97), + (19, 191), + (42, 10), + (56, 236), + (5, 24), + (42, 215), + (39, 34), + (4, 0), + (45, 50), + (43, 340), + (46, 383), + (8, 208), + (26, 172), + (14, 204), + (60, 359), + (17, 95), + (10, 225), + (5, 25), + (18, 264), + (51, 337), + (31, 381), + (50, 109), + (10, 341), + (28, 214), + (38, 193), + (54, 217), + (10, 181), + (36, 224), + (47, 377), + (30, 50), + (54, 34), + (6, 22), + (35, 270), + (4, 69), + (8, 266), + (45, 322), + (39, 100), + (52, 126), + (51, 62), + (34, 370), + (52, 327), + (49, 302), + (4, 303), + (51, 223), + (29, 352), + (33, 68), + (13, 17), + (7, 345), + (45, 319), + (46, 238), + (0, 211), + (59, 114), + (45, 39), + (6, 343), + (10, 97), + (4, 98), + (6, 118), + (38, 277), + (51, 28), + (28, 78), + (49, 354), + (31, 215), + (24, 334), + (25, 356), + (49, 38), + (58, 351), + (25, 254), + (45, 227), + (9, 18), + (34, 180), + (35, 163), + (40, 365), + (6, 365), + (53, 367), + (10, 6), + (0, 159), + (5, 352), + (28, 236), + (29, 114), + (46, 57), + (4, 255), + (30, 311), + (60, 134), + (59, 93), + (6, 191), + (20, 196), + (60, 236), + (56, 369), + (43, 382), + (11, 261), + (50, 59), + (9, 16), + (23, 103), + (9, 316), + (6, 263), + (9, 159), + (46, 375), + (35, 254), + (50, 208), + (7, 245), + (57, 332), + (43, 20), + (40, 107), + (10, 112), + (41, 371), + (60, 70), + (8, 374), + (0, 4), + (37, 289), + (37, 88), + (50, 361), + (52, 359), + (17, 347), + (14, 102), + (59, 376), + (41, 144), + (42, 137), + (11, 319), + (25, 231), + (32, 193), + (1, 246), + (43, 357), + (23, 23), + (60, 309), + (11, 304), + (58, 31), + (35, 289), + (47, 11), + (11, 151), + (48, 191), + (42, 279), + (52, 13), + (45, 362), + (9, 172), + (48, 167), + (50, 118), + (38, 278), + (49, 109), + (44, 43), + (49, 40), + (38, 78), + (27, 153), + (4, 197), + (33, 35), + (4, 61), + (46, 85), + (31, 56), + (8, 60), + (39, 55), + (45, 332), + (23, 187), + (38, 106), + (49, 267), + (47, 372), + (45, 308), + (26, 67), + (32, 250), + (44, 127), + (43, 147), + (10, 24), + (45, 355), + (50, 88), + (50, 258), + (31, 224), + (45, 233), + (1, 163), + (0, 69), + (41, 138), + (44, 292), + (44, 82), + (45, 173), + (59, 14), + (9, 294), + (38, 299), + (48, 73), + (0, 169), + (0, 177), + (25, 61), + (44, 171), + (41, 313), + (42, 308), + (60, 270), + (56, 32), + (46, 110), + (60, 1), + (40, 231), + (38, 202), + (33, 167), + (0, 155), + (49, 28), + (43, 57), + (41, 258), + (56, 58), + (59, 35), + (47, 108), + (44, 336), + (45, 48), + (31, 371), + (42, 265), + (47, 301), + (25, 135), + (52, 339), + (44, 265), + (46, 95), + (40, 13), + (0, 112), + (40, 114), + (2, 259), + (48, 17), + (4, 260), + (1, 17), + (7, 248), + (0, 105), + (55, 339), + (49, 145), + (3, 361), + (47, 68), + (47, 72), + (8, 113), + (44, 99), + (45, 284), + (0, 328), + (12, 356), + (12, 242), + (60, 160), + (23, 13), + (7, 165), + (0, 269), + (53, 76), + (10, 220), + (51, 198), + (3, 239), + (60, 176), + (7, 339), + (7, 305), + (48, 157), + (59, 171), + (8, 85), + (1, 242), + (58, 26), + (4, 223), + (12, 285), + (11, 161), + (1, 193), + (23, 118), + (11, 198), + (7, 322), + (29, 79), + (8, 356), + (55, 218), + (55, 119), + (0, 224), + (44, 158), + (35, 263), + (1, 197), + (59, 67), + (27, 4), + (49, 163), + (53, 188), + (38, 141), + (4, 193), + (4, 348), + (35, 81), + (50, 378), + (1, 369), + (57, 311), + (0, 212), + (35, 176), + (2, 233), + (60, 332), + (38, 297), + (29, 324), + (35, 344), + (39, 225), + (35, 187), + (8, 78), + (57, 356), + (59, 247), + (59, 28), + (0, 154), + (43, 64), + (9, 93), + (40, 67), + (1, 78), + (1, 157), + (23, 40), + (35, 17), + (47, 123), + (3, 274), + (0, 373), + (41, 70), + (57, 78), + (1, 72), + (4, 40), + (50, 62), + (0, 252), + (44, 198), + (59, 258), + (49, 131), + (33, 243), + (24, 238), + (53, 116), + (53, 377), + (53, 175), + (51, 346), + (53, 181), + (35, 332), + (39, 4), + (23, 251), + (45, 211), + (35, 330), + (17, 183), + (56, 186), + (0, 23), + (37, 206), + (31, 33), + (8, 125), + (48, 226), + (60, 307), + (0, 164), + (48, 70), + (56, 133), + (38, 61), + (35, 338), + (55, 370), + (60, 248), + (0, 265), + (35, 299), + (21, 269), + (33, 290), + (1, 166), + (41, 83), + (37, 334), + (59, 198), + (2, 113), + (47, 285), + (6, 105), + (3, 256), + (11, 177), + (0, 24), + (59, 351), + (8, 174), + (60, 122), + (3, 287), + (39, 286), + (8, 287), + (32, 20), + (0, 267), + (5, 283), + (35, 307), + (58, 338), + (0, 360), + (60, 68), + (12, 135), + (31, 347), + (10, 104), + (35, 141), + (51, 126), + (60, 81), + (13, 124), + (60, 93), + (29, 271), + (0, 339), + (0, 157), + (5, 359), + (57, 143), + (36, 111), + (33, 373), + (7, 208), + (59, 344), + (0, 68), + (30, 184), + (0, 216), + (7, 260), + (0, 73), + (57, 73), + (57, 351), + (21, 325), + (0, 163), + (60, 234), + (33, 271), + (24, 309), + (1, 292), + (0, 276), + (14, 293), + (37, 374), + (25, 181), + (25, 351), + (32, 56), + (1, 173), + (5, 264), + (39, 249), + (36, 57), + (55, 214), + (23, 191), + (5, 4), + (23, 104), + (0, 320), + (1, 224), + (15, 326), + (58, 378), + (57, 331), + (16, 124), + (55, 263), + (0, 109), + (4, 141), + (12, 157), + (1, 65), + (57, 130), + (57, 169), + (39, 338), + (15, 286), + (24, 280), + (23, 113), + (17, 198), + (7, 85), + (23, 259), + (1, 290), + (1, 366), + (51, 256), + (8, 247), + (51, 213), + (33, 105), + (0, 237), + (18, 39), + (30, 278), + (35, 339), + (2, 249), + (0, 16), + (38, 3), + (46, 93), + (2, 373), + (32, 372), + (0, 93), + (23, 185), + (35, 308), + (12, 232), + (57, 328), + (33, 312), + (9, 87), + (30, 140), + (8, 54), + (43, 83), + (52, 177), + (2, 235), + (43, 320), + (13, 354), + (33, 155), + (52, 48), + (55, 241), + (4, 41), + (7, 37), + (25, 249), + (47, 257), + (5, 292), + (55, 131), + (58, 21), + (37, 87), + (29, 150), + (1, 19), + (23, 14), + (31, 142), + (59, 17), + (40, 14), + (37, 181), + (44, 138), + (25, 369), + (55, 92), + (5, 33), + (23, 351), + (19, 136), + (27, 136), + (30, 171), + (0, 287), + (25, 102), + (51, 349), + (23, 178), + (57, 254), + (48, 65), + (35, 179), + (27, 297), + (31, 85), + (54, 64), + (0, 359), + (20, 63), + (7, 81), + (59, 12), + (60, 9), + (18, 325), + (23, 382), + (42, 39), + (5, 271), + (25, 124), + (53, 251), + (29, 146), + (1, 321), + (58, 379), + (2, 375), + (53, 264), + (8, 330), + (35, 16), + (47, 237), + (4, 323), + (1, 91), + (33, 27), + (12, 143), + (23, 365), + (24, 278), + (2, 44), + (45, 254), + (2, 322), + (2, 79), + (38, 265), + (14, 244), + (2, 281), + (25, 358), + (49, 127), + (38, 248), + (25, 136), + (12, 228), + (43, 220), + (0, 251), + (26, 344), + (59, 222), + (8, 316), + (35, 25), + (60, 4), + (57, 355), + (47, 268), + (2, 358), + (2, 261), + (58, 212), + (7, 353), + (18, 364), + (8, 87), + (2, 156), + (2, 287), + (0, 98), + (1, 115), + (0, 340), + (25, 199), + (37, 378), + (5, 372), + (26, 78), + (2, 319), + (0, 185), + (7, 273), + (11, 341), + (4, 291), + (24, 202), + (59, 37), + (1, 150), + (31, 73), + (2, 379), + (36, 122), + (57, 106), + (60, 119), + (3, 193), + (25, 172), + (2, 57), + (1, 69), + (0, 329), + (25, 4), + (29, 164), + (0, 52), + (2, 335), + (1, 16), + (51, 80), + (2, 347), + (31, 197), + (1, 363), + (22, 81), + (38, 236), + (11, 295), + (55, 367), + (33, 302), + (14, 173), + (0, 168), + (21, 266), + (55, 268), + (45, 75), + (32, 135), + (20, 55), + (60, 221), + (2, 324), + (8, 75), + (0, 146), + (15, 323), + (1, 60), + (2, 216), + (24, 62), + (53, 345), + (44, 59), + (3, 271), + (59, 294), + (54, 370), + (0, 262), + (52, 175), + (31, 8), + (1, 112), + (3, 201), + (0, 90), + (2, 191), + (3, 192), + (26, 299), + (32, 201), + (57, 147), + (3, 382), + (22, 167), + (0, 293), + (7, 250), + (2, 164), + (51, 140), + (31, 310), + (2, 178), + (1, 288), + (2, 300), + (1, 81), + (14, 345), + (1, 77), + (53, 167), + (33, 180), + (18, 295), + (5, 312), + (0, 285), + (39, 302), + (2, 86), + (51, 33), + (35, 201), + (0, 272), + (42, 106), + (33, 246), + (0, 132), + (21, 89), + (34, 303), + (1, 202), + (1, 334), + (23, 297), + (3, 6), + (6, 290), + (25, 95), + (60, 328), + (18, 133), + (38, 382), + (2, 17), + (1, 56), + (54, 136), + (2, 356), + (2, 339), + (39, 30), + (1, 240), + (3, 103), + (0, 166), + (1, 325), + (21, 303), + (58, 127), + (30, 79), + (1, 45), + (58, 346), + (4, 47), + (1, 5), + (60, 299), + (49, 304), + (23, 153), + (2, 243), + (45, 19), + (25, 310), + (55, 314), + (48, 198), + (2, 46), + (31, 117), + (1, 350), + (3, 78), + (2, 382), + (2, 163), + (0, 172), + (33, 301), + (1, 48), + (60, 231), + (0, 87), + (1, 225), + (19, 316), + (24, 125), + (17, 374), + (16, 22), + (25, 348), + (2, 344), + (2, 148), + (2, 189), + (6, 383), + (6, 302), + (36, 109), + (6, 166), + (38, 137), + (5, 86), + (24, 184), + (23, 278), + (53, 46), + (1, 241), + (2, 199), + (41, 96), + (2, 207), + (55, 101), + (27, 41), + (37, 151), + (34, 281), + (7, 243), + (1, 151), + (57, 232), + (59, 343), + (2, 147), + (1, 118), + (23, 346), + (18, 177), + (40, 339), + (3, 86), + (19, 298), + (43, 38), + (31, 256), + (21, 119), + (1, 116), + (0, 63), + (2, 184), + (14, 336), + (38, 352), + (5, 275), + (40, 217), + (12, 4), + (56, 377), + (22, 266), + (2, 116), + (58, 382), + (55, 275), + (1, 311), + (37, 200), + (16, 29), + (0, 104), + (22, 315), + (1, 265), + (14, 96), + (3, 321), + (31, 58), + (1, 259), + (50, 172), + (17, 272), + (30, 7), + (57, 329), + (53, 196), + (14, 78), + (0, 113), + (0, 210), + (0, 334), + (23, 205), + (2, 202), + (26, 173), + (1, 102), + (1, 178), + (35, 41), + (1, 305), + (57, 289), + (32, 13), + (2, 154), + (35, 273), + (49, 112), + (41, 127), + (47, 96), + (4, 372), + (10, 293), + (2, 89), + (2, 228), + (46, 207), + (1, 222), + (11, 58), + (0, 178), + (1, 159), + (32, 251), + (55, 288), + (47, 104), + (1, 128), + (2, 355), + (2, 260), + (33, 93), + (3, 36), + (3, 329), + (12, 152), + (4, 118), + (29, 311), + (0, 186), + (6, 209), + (1, 302), + (5, 20), + (24, 151), + (2, 302), + (29, 322), + (1, 255), + (42, 138), + (1, 338), + (48, 307), + (32, 370), + (54, 253), + (2, 296), + (29, 109), + (60, 335), + (38, 261), + (2, 53), + (15, 147), + (5, 198), + (16, 48), + (7, 324), + (3, 212), + (31, 196), + (58, 149), + (21, 136), + (27, 82), + (2, 186), + (26, 295), + (58, 349), + (12, 290), + (37, 129), + (1, 286), + (9, 321), + (1, 144), + (12, 246), + (2, 371), + (24, 176), + (52, 186), + (33, 82), + (55, 72), + (2, 217), + (31, 24), + (2, 14), + (2, 250), + (1, 156), + (44, 23), + (2, 206), + (14, 42), + (2, 201), + (35, 117), + (2, 68), + (11, 159), + (52, 179), + (2, 88), + (3, 299), + (25, 292), + (24, 225), + (33, 96), + (55, 112), + (0, 227), + (29, 20), + (21, 294), + (12, 167), + (2, 226), + (24, 39), + (53, 37), + (47, 176), + (2, 52), + (37, 90), + (32, 3), + (34, 55), + (32, 374), + (3, 337), + (52, 372), + (32, 293), + (46, 167), + (45, 245), + (12, 70), + (12, 346), + (1, 132), + (45, 87), + (2, 83), + (57, 58), + (4, 133), + (39, 74), + (2, 364), + (7, 136), + (59, 73), + (26, 224), + (2, 323), + (16, 227), + (8, 102), + (41, 141), + (24, 177), + (57, 183), + (59, 275), + (19, 119), + (32, 292), + (57, 61), + (35, 107), + (32, 277), + (17, 223), + (58, 350), + (40, 147), + (4, 370), + (36, 213), + (4, 178), + (57, 290), + (11, 102), + (0, 273), + (47, 338), + (2, 15), + (55, 373), + (8, 257), + (52, 0), + (1, 25), + (48, 334), + (42, 197), + (2, 185), + (1, 131), + (37, 26), + (42, 318), + (26, 131), + (13, 300), + (21, 278), + (2, 366), + (35, 150), + (41, 306), + (35, 196), + (1, 351), + (2, 264), + (57, 214), + (21, 12), + (1, 188), + (0, 374), + (18, 186), + (47, 77), + (23, 335), + (31, 332), + (2, 284), + (53, 27), + (27, 141), + (43, 199), + (53, 26), + (25, 324), + (2, 174), + (1, 129), + (0, 141), + (1, 339), + (1, 35), + (60, 137), + (19, 127), + (8, 282), + (0, 148), + (55, 145), + (14, 335), + (20, 326), + (23, 375), + (60, 181), + (19, 197), + (18, 348), + (0, 309), + (2, 276), + (14, 290), + (37, 350), + (2, 223), + (2, 359), + (2, 188), + (12, 352), + (16, 291), + (51, 327), + (58, 40), + (32, 97), + (3, 50), + (3, 240), + (43, 156), + (15, 173), + (23, 34), + (2, 5), + (0, 137), + (2, 177), + (13, 181), + (56, 262), + (4, 335), + (32, 117), + (0, 305), + (13, 199), + (29, 299), + (16, 114), + (31, 319), + (35, 184), + (54, 152), + (42, 241), + (33, 370), + (56, 116), + (3, 44), + (35, 56), + (21, 288), + (45, 383), + (38, 374), + (3, 71), + (15, 109), + (1, 374), + (16, 296), + (42, 257), + (12, 75), + (35, 109), + (16, 367), + (2, 204), + (2, 224), + (25, 30), + (51, 82), + (11, 292), + (58, 380), + (44, 17), + (2, 11), + (7, 68), + (27, 326), + (31, 355), + (2, 291), + (32, 161), + (35, 315), + (37, 42), + (41, 243), + (18, 290), + (45, 121), + (49, 159), + (35, 227), + (57, 97), + (8, 302), + (14, 265), + (35, 189), + (25, 271), + (0, 42), + (12, 322), + (16, 288), + (8, 0), + (1, 229), + (21, 281), + (2, 378), + (25, 275), + (56, 144), + (58, 345), + (21, 328), + (1, 33), + (57, 24), + (33, 351), + (57, 195), + (35, 245), + (42, 97), + (53, 256), + (39, 339), + (5, 136), + (0, 22), + (1, 29), + (23, 285), + (33, 353), + (53, 160), + (2, 50), + (22, 300), + (7, 26), + (7, 130), + (22, 193), + (2, 294), + (16, 50), + (16, 59), + (52, 133), + (21, 299), + (15, 320), + (30, 333), + (2, 283), + (53, 260), + (21, 378), + (1, 221), + (37, 116), + (51, 283), + (59, 259), + (9, 163), + (0, 333), + (37, 27), + (17, 345), + (18, 44), + (54, 373), + (3, 63), + (17, 29), + (29, 122), + (21, 70), + (5, 114), + (12, 153), + (57, 379), + (15, 132), + (53, 240), + (11, 350), + (29, 224), + (14, 209), + (0, 91), + (16, 178), + (6, 381), + (8, 72), + (4, 42), + (44, 98), + (14, 229), + (0, 354), + (54, 204), + (8, 26), + (30, 100), + (58, 36), + (22, 178), + (38, 372), + (44, 262), + (43, 218), + (40, 188), + (0, 229), + (36, 196), + (51, 281), + (43, 217), + (57, 296), + (17, 158), + (2, 149), + (33, 128), + (2, 327), + (16, 365), + (12, 124), + (42, 275), + (18, 181), + (24, 138), + (2, 112), + (32, 198), + (3, 169), + (35, 371), + (2, 134), + (24, 135), + (36, 107), + (2, 169), + (23, 27), + (16, 352), + (34, 321), + (51, 365), + (31, 350), + (37, 111), + (15, 305), + (2, 254), + (52, 170), + (21, 79), + (44, 220), + (49, 14), + (12, 268), + (43, 365), + (21, 238), + (50, 35), + (50, 203), + (51, 113), + (29, 204), + (0, 208), + (23, 188), + (45, 329), + (23, 117), + (17, 110), + (49, 229), + (50, 89), + (57, 165), + (37, 219), + (60, 42), + (39, 199), + (1, 108), + (35, 125), + (50, 315), + (2, 168), + (1, 192), + (24, 156), + (22, 3), + (53, 16), + (10, 167), + (35, 36), + (14, 158), + (12, 291), + (48, 189), + (1, 232), + (19, 120), + (1, 228), + (0, 377), + (45, 250), + (54, 122), + (21, 147), + (8, 167), + (2, 132), + (56, 156), + (22, 256), + (0, 319), + (0, 188), + (58, 244), + (37, 260), + (1, 239), + (19, 345), + (23, 344), + (32, 186), + (22, 131), + (17, 192), + (43, 211), + (1, 83), + (21, 244), + (5, 135), + (45, 306), + (12, 100), + (1, 267), + (53, 242), + (22, 202), + (3, 229), + (19, 370), + (20, 84), + (22, 136), + (53, 183), + (16, 35), + (59, 209), + (36, 219), + (51, 129), + (3, 224), + (6, 94), + (19, 62), + (48, 350), + (25, 166), + (10, 105), + (19, 151), + (14, 123), + (52, 86), + (3, 176), + (15, 110), + (15, 264), + (7, 216), + (6, 196), + (17, 88), + (21, 271), + (13, 332), + (38, 257), + (4, 235), + (22, 243), + (54, 330), + (16, 118), + (14, 342), + (34, 101), + (0, 3), + (23, 127), + (30, 23), + (46, 216), + (58, 383), + (29, 167), + (14, 178), + (31, 285), + (25, 259), + (43, 213), + (52, 357), + (2, 299), + (21, 66), + (10, 232), + (6, 292), + (18, 171), + (59, 274), + (0, 259), + (2, 119), + (17, 343), + (14, 168), + (49, 140), + (35, 383), + (44, 91), + (8, 51), + (53, 213), + (1, 199), + (57, 72), + (20, 169), + (16, 331), + (22, 262), + (57, 366), + (1, 90), + (5, 316), + (41, 268), + (13, 187), + (46, 247), + (60, 372), + (17, 96), + (13, 19), + (35, 146), + (50, 132), + (13, 317), + (7, 192), + (46, 367), + (4, 50), + (10, 254), + (13, 190), + (57, 22), + (26, 31), + (10, 218), + (14, 382), + (53, 363), + (23, 279), + (57, 177), + (13, 344), + (24, 160), + (13, 57), + (23, 210), + (54, 19), + (5, 164), + (31, 11), + (57, 199), + (10, 374), + (1, 21), + (4, 376), + (37, 345), + (52, 221), + (39, 316), + (7, 70), + (16, 232), + (45, 107), + (49, 199), + (50, 213), + (20, 10), + (37, 269), + (11, 260), + (24, 331), + (20, 13), + (5, 355), + (12, 361), + (5, 68), + (34, 140), + (0, 282), + (17, 294), + (5, 193), + (33, 101), + (7, 141), + (10, 56), + (3, 172), + (28, 122), + (59, 378), + (37, 78), + (57, 94), + (15, 327), + (50, 9), + (12, 362), + (20, 215), + (21, 142), + (43, 141), + (49, 36), + (4, 310), + (21, 158), + (13, 70), + (33, 63), + (52, 243), + (58, 327), + (0, 356), + (18, 75), + (59, 356), + (56, 359), + (24, 106), + (13, 366), + (59, 11), + (1, 299), + (58, 229), + (4, 317), + (43, 88), + (0, 245), + (10, 32), + (58, 328), + (19, 170), + (2, 263), + (7, 205), + (43, 42), + (53, 243), + (29, 225), + (33, 61), + (21, 82), + (55, 118), + (46, 327), + (21, 286), + (2, 38), + (50, 299), + (27, 370), + (19, 205), + (22, 14), + (46, 159), + (29, 76), + (2, 208), + (18, 329), + (14, 115), + (11, 34), + (8, 138), + (20, 81), + (53, 194), + (9, 26), + (1, 70), + (17, 357), + (36, 350), + (14, 234), + (48, 199), + (29, 7), + (12, 68), + (3, 306), + (52, 2), + (49, 310), + (22, 105), + (37, 265), + (36, 235), + (22, 168), + (13, 122), + (57, 256), + (38, 205), + (53, 382), + (14, 36), + (24, 283), + (15, 378), + (7, 210), + (9, 333), + (48, 349), + (51, 287), + (39, 223), + (57, 142), + (15, 77), + (41, 65), + (55, 115), + (39, 69), + (51, 118), + (31, 101), + (7, 148), + (18, 365), + (12, 179), + (55, 49), + (37, 148), + (1, 263), + (13, 51), + (17, 50), + (47, 175), + (31, 325), + (34, 64), + (57, 259), + (10, 116), + (5, 364), + (13, 321), + (10, 100), + (23, 80), + (7, 14), + (1, 153), + (30, 340), + (6, 48), + (37, 96), + (32, 58), + (29, 157), + (40, 136), + (23, 219), + (21, 367), + (11, 133), + (55, 81), + (2, 350), + (58, 123), + (9, 50), + (54, 308), + (49, 4), + (54, 40), + (29, 290), + (28, 335), + (7, 17), + (3, 207), + (57, 246), + (37, 281), + (21, 169), + (41, 206), + (54, 147), + (4, 72), + (20, 26), + (29, 314), + (29, 168), + (48, 338), + (11, 303), + (56, 141), + (15, 259), + (41, 153), + (1, 37), + (59, 183), + (35, 348), + (7, 379), + (53, 379), + (59, 241), + (30, 63), + (13, 328), + (24, 114), + (15, 184), + (23, 114), + (2, 91), + (58, 118), + (49, 242), + (53, 209), + (23, 246), + (37, 161), + (37, 361), + (5, 284), + (34, 45), + (0, 288), + (53, 43), + (21, 383), + (0, 243), + (3, 307), + (3, 252), + (28, 314), + (59, 136), + (33, 347), + (47, 263), + (5, 327), + (15, 192), + (8, 312), + (8, 25), + (8, 323), + (7, 35), + (31, 48), + (47, 166), + (15, 217), + (21, 215), + (31, 331), + (22, 370), + (2, 36), + (3, 275), + (37, 34), + (0, 306), + (47, 23), + (15, 90), + (53, 63), + (59, 246), + (58, 30), + (17, 154), + (10, 195), + (0, 62), + (20, 309), + (32, 47), + (31, 221), + (27, 177), + (32, 156), + (43, 66), + (30, 378), + (12, 61), + (53, 279), + (52, 303), + (22, 141), + (15, 247), + (0, 131), + (20, 124), + (39, 16), + (48, 206), + (33, 48), + (13, 353), + (34, 335), + (41, 196), + (53, 368), + (40, 211), + (5, 269), + (37, 66), + (58, 264), + (19, 138), + (15, 5), + (59, 152), + (17, 28), + (55, 213), + (7, 9), + (15, 32), + (32, 152), + (15, 242), + (23, 201), + (47, 42), + (9, 123), + (26, 376), + (28, 155), + (59, 290), + (1, 204), + (25, 177), + (18, 298), + (15, 140), + (34, 374), + (15, 260), + (22, 160), + (53, 218), + (57, 59), + (1, 220), + (38, 90), + (56, 122), + (21, 192), + (15, 129), + (45, 378), + (26, 197), + (2, 55), + (44, 117), + (55, 311), + (59, 317), + (29, 344), + (50, 245), + (24, 118), + (59, 50), + (9, 353), + (57, 321), + (35, 48), + (51, 331), + (29, 373), + (57, 211), + (60, 194), + (18, 247), + (5, 113), + (59, 127), + (56, 370), + (19, 92), + (49, 170), + (56, 69), + (60, 195), + (5, 220), + (53, 250), + (51, 275), + (13, 104), + (5, 104), + (1, 168), + (18, 173), + (49, 377), + (5, 279), + (45, 314), + (49, 168), + (15, 227), + (21, 83), + (35, 232), + (58, 191), + (54, 186), + (7, 323), + (7, 115), + (11, 338), + (15, 263), + (22, 184), + (60, 258), + (19, 267), + (53, 318), + (35, 220), + (7, 29), + (57, 101), + (60, 217), + (1, 180), + (42, 312), + (8, 293), + (32, 310), + (7, 287), + (21, 329), + (50, 160), + (22, 322), + (46, 102), + (38, 171), + (37, 362), + (38, 319), + (3, 74), + (56, 43), + (35, 132), + (57, 190), + (2, 39), + (44, 177), + (53, 68), + (47, 346), + (54, 214), + (60, 98), + (8, 245), + (2, 346), + (16, 182), + (23, 381), + (37, 333), + (29, 24), + (0, 335), + (39, 19), + (1, 113), + (20, 24), + (1, 38), + (35, 255), + (54, 107), + (20, 20), + (20, 178), + (53, 371), + (20, 106), + (51, 328), + (59, 253), + (31, 286), + (43, 121), + (51, 81), + (47, 248), + (8, 191), + (42, 356), + (7, 318), + (8, 168), + (59, 320), + (34, 4), + (18, 43), + (19, 340), + (46, 351), + (4, 162), + (54, 325), + (33, 236), + (8, 49), + (23, 235), + (37, 132), + (16, 184), + (43, 242), + (17, 106), + (21, 74), + (42, 8), + (10, 339), + (48, 279), + (5, 265), + (39, 68), + (36, 55), + (59, 313), + (18, 153), + (6, 10), + (35, 267), + (60, 76), + (21, 221), + (50, 3), + (39, 293), + (17, 228), + (36, 183), + (3, 265), + (21, 337), + (3, 177), + (55, 33), + (8, 259), + (25, 143), + (14, 231), + (8, 158), + (39, 208), + (18, 161), + (0, 378), + (13, 211), + (57, 98), + (8, 103), + (11, 148), + (9, 332), + (5, 6), + (15, 367), + (19, 325), + (46, 9), + (22, 307), + (34, 311), + (59, 134), + (55, 246), + (0, 140), + (36, 133), + (23, 112), + (35, 70), + (40, 334), + (1, 152), + (20, 239), + (53, 14), + (0, 130), + (4, 373), + (15, 99), + (7, 106), + (57, 18), + (38, 72), + (10, 190), + (50, 99), + (20, 221), + (35, 136), + (26, 272), + (37, 171), + (15, 59), + (58, 211), + (42, 135), + (6, 350), + (25, 110), + (31, 175), + (23, 120), + (49, 120), + (3, 183), + (55, 103), + (59, 164), + (11, 360), + (0, 88), + (50, 367), + (8, 107), + (14, 366), + (22, 209), + (26, 226), + (5, 239), + (20, 271), + (37, 299), + (38, 230), + (55, 188), + (10, 95), + (59, 267), + (4, 377), + (18, 361), + (38, 146), + (35, 58), + (60, 377), + (1, 320), + (29, 328), + (19, 235), + (22, 132), + (39, 178), + (17, 219), + (19, 77), + (8, 66), + (53, 207), + (15, 306), + (26, 153), + (6, 261), + (25, 52), + (50, 158), + (3, 11), + (17, 136), + (8, 333), + (13, 34), + (16, 311), + (15, 86), + (40, 59), + (55, 326), + (26, 43), + (20, 122), + (39, 188), + (50, 153), + (35, 212), + (32, 172), + (7, 43), + (1, 264), + (27, 106), + (42, 143), + (57, 200), + (34, 372), + (24, 341), + (21, 182), + (20, 311), + (18, 137), + (8, 198), + (14, 364), + (44, 333), + (7, 142), + (46, 131), + (48, 138), + (60, 136), + (53, 339), + (47, 25), + (44, 271), + (8, 331), + (49, 226), + (41, 300), + (9, 252), + (37, 215), + (1, 179), + (26, 378), + (8, 320), + (8, 15), + (6, 229), + (22, 311), + (43, 111), + (3, 48), + (15, 101), + (49, 284), + (20, 15), + (43, 266), + (31, 68), + (40, 251), + (6, 270), + (20, 381), + (44, 370), + (47, 367), + (21, 2), + (14, 381), + (31, 283), + (51, 183), + (52, 276), + (12, 31), + (24, 35), + (59, 38), + (53, 176), + (48, 313), + (36, 61), + (21, 165), + (43, 21), + (57, 26), + (8, 84), + (2, 77), + (52, 224), + (18, 250), + (53, 25), + (51, 75), + (30, 133), + (24, 382), + (30, 298), + (3, 114), + (11, 254), + (39, 176), + (14, 198), + (5, 180), + (58, 352), + (10, 17), + (27, 229), + (1, 283), + (14, 355), + (28, 305), + (36, 206), + (55, 351), + (39, 246), + (14, 29), + (59, 363), + (25, 378), + (5, 21), + (24, 76), + (16, 107), + (26, 326), + (58, 56), + (17, 349), + (60, 59), + (6, 43), + (7, 274), + (8, 24), + (52, 245), + (60, 55), + (57, 63), + (33, 6), + (56, 308), + (0, 59), + (58, 261), + (31, 334), + (53, 298), + (15, 56), + (41, 24), + (21, 120), + (5, 16), + (57, 164), + (38, 55), + (25, 26), + (4, 143), + (33, 367), + (49, 89), + (12, 350), + (27, 223), + (51, 232), + (6, 321), + (18, 254), + (38, 87), + (15, 8), + (5, 74), + (19, 57), + (19, 300), + (6, 357), + (17, 75), + (33, 363), + (13, 288), + (47, 134), + (18, 198), + (3, 280), + (10, 111), + (43, 269), + (8, 360), + (6, 143), + (17, 369), + (55, 195), + (26, 96), + (12, 155), + (54, 317), + (1, 184), + (7, 254), + (55, 292), + (45, 206), + (59, 66), + (2, 180), + (37, 164), + (15, 105), + (25, 37), + (8, 147), + (26, 296), + (5, 78), + (24, 291), + (8, 338), + (52, 147), + (8, 354), + (33, 311), + (6, 202), + (7, 47), + (7, 18), + (52, 250), + (2, 28), + (23, 12), + (5, 323), + (58, 76), + (29, 188), + (18, 77), + (33, 255), + (57, 179), + (53, 222), + (29, 248), + (1, 42), + (40, 232), + (4, 135), + (53, 372), + (20, 197), + (45, 134), + (60, 285), + (30, 248), + (20, 336), + (20, 66), + (5, 345), + (0, 14), + (59, 184), + (7, 374), + (57, 273), + (55, 132), + (54, 128), + (20, 207), + (18, 297), + (20, 164), + (4, 212), + (43, 149), + (15, 23), + (21, 111), + (33, 210), + (19, 80), + (2, 172), + (39, 94), + (56, 123), + (43, 11), + (15, 218), + (52, 282), + (51, 324), + (45, 25), + (48, 357), + (29, 18), + (32, 140), + (12, 371), + (56, 328), + (35, 78), + (41, 209), + (7, 11), + (15, 187), + (1, 335), + (19, 54), + (50, 58), + (13, 23), + (23, 54), + (44, 10), + (24, 250), + (3, 12), + (53, 119), + (8, 188), + (17, 101), + (25, 360), + (7, 143), + (25, 207), + (4, 57), + (28, 30), + (10, 67), + (59, 163), + (11, 33), + (29, 69), + (58, 0), + (58, 271), + (14, 137), + (49, 153), + (6, 197), + (7, 340), + (36, 36), + (25, 335), + (30, 84), + (31, 235), + (57, 192), + (48, 164), + (1, 7), + (5, 322), + (15, 169), + (0, 144), + (42, 80), + (7, 194), + (0, 61), + (37, 379), + (51, 158), + (19, 299), + (37, 229), + (12, 102), + (4, 261), + (18, 346), + (55, 293), + (50, 238), + (8, 197), + (46, 309), + (58, 374), + (21, 245), + (60, 53), + (21, 214), + (25, 377), + (23, 107), + (21, 163), + (46, 142), + (0, 11), + (14, 111), + (2, 143), + (10, 108), + (8, 317), + (3, 359), + (6, 95), + (41, 19), + (32, 299), + (54, 236), + (35, 106), + (57, 65), + (58, 150), + (14, 135), + (51, 293), + (18, 183), + (57, 145), + (26, 37), + (6, 269), + (24, 339), + (8, 91), + (14, 64), + (4, 75), + (18, 343), + (30, 313), + (46, 286), + (34, 192), + (10, 207), + (24, 186), + (31, 65), + (26, 270), + (56, 345), + (55, 154), + (59, 286), + (5, 183), + (15, 54), + (11, 247), + (21, 100), + (4, 343), + (25, 313), + (35, 375), + (48, 192), + (55, 280), + (13, 312), + (0, 2), + (5, 235), + (26, 281), + (45, 231), + (8, 358), + (6, 117), + (45, 229), + (9, 323), + (32, 75), + (57, 347), + (3, 93), + (50, 232), + (48, 107), + (29, 308), + (32, 48), + (29, 43), + (8, 359), + (15, 9), + (26, 206), + (37, 117), + (37, 208), + (9, 144), + (53, 92), + (1, 364), + (11, 156), + (6, 81), + (11, 31), + (49, 156), + (36, 195), + (31, 373), + (7, 164), + (30, 317), + (37, 194), + (51, 222), + (8, 225), + (26, 222), + (16, 300), + (23, 76), + (56, 267), + (45, 163), + (13, 157), + (58, 323), + (53, 64), + (30, 67), + (5, 330), + (10, 93), + (39, 60), + (8, 150), + (5, 134), + (26, 26), + (37, 205), + (18, 191), + (56, 181), + (39, 213), + (6, 362), + (15, 335), + (19, 131), + (59, 140), + (19, 339), + (52, 320), + (35, 108), + (0, 34), + (58, 74), + (12, 229), + (25, 160), + (32, 200), + (29, 64), + (15, 124), + (39, 137), + (17, 367), + (56, 290), + (4, 333), + (31, 63), + (55, 328), + (29, 230), + (15, 373), + (15, 161), + (36, 364), + (19, 158), + (53, 69), + (23, 212), + (13, 100), + (58, 193), + (7, 289), + (35, 34), + (58, 87), + (36, 91), + (7, 325), + (8, 39), + (38, 208), + (22, 198), + (43, 85), + (17, 284), + (12, 226), + (30, 236), + (31, 102), + (3, 147), + (14, 43), + (57, 228), + (53, 85), + (54, 63), + (32, 51), + (32, 294), + (4, 195), + (60, 14), + (55, 60), + (17, 48), + (4, 359), + (5, 383), + (49, 185), + (0, 116), + (1, 104), + (22, 201), + (10, 299), + (11, 172), + (15, 252), + (49, 278), + (39, 206), + (31, 263), + (8, 4), + (55, 21), + (9, 222), + (46, 138), + (57, 67), + (40, 156), + (30, 320), + (12, 71), + (28, 285), + (51, 148), + (3, 196), + (54, 104), + (9, 233), + (42, 157), + (47, 276), + (4, 344), + (11, 311), + (36, 221), + (37, 125), + (55, 366), + (43, 26), + (58, 89), + (56, 223), + (59, 48), + (57, 136), + (7, 252), + (16, 250), + (60, 48), + (51, 203), + (19, 202), + (43, 322), + (13, 35), + (59, 216), + (22, 175), + (31, 84), + (13, 116), + (29, 321), + (1, 6), + (6, 125), + (34, 360), + (26, 48), + (18, 106), + (11, 84), + (18, 17), + (19, 309), + (11, 309), + (10, 10), + (40, 86), + (59, 337), + (26, 341), + (20, 54), + (3, 76), + (30, 218), + (60, 226), + (58, 262), + (44, 110), + (14, 150), + (60, 355), + (48, 110), + (33, 41), + (11, 353), + (57, 317), + (8, 68), + (8, 169), + (55, 165), + (54, 41), + (59, 113), + (26, 34), + (3, 92), + (27, 329), + (59, 255), + (28, 255), + (37, 218), + (19, 364), + (7, 100), + (4, 59), + (26, 284), + (9, 79), + (29, 130), + (25, 323), + (41, 354), + (43, 160), + (40, 153), + (5, 342), + (10, 127), + (40, 173), + (0, 284), + (44, 318), + (56, 66), + (19, 240), + (42, 166), + (39, 248), + (18, 131), + (55, 50), + (49, 343), + (22, 139), + (60, 29), + (29, 52), + (48, 98), + (29, 368), + (60, 133), + (56, 49), + (29, 234), + (8, 134), + (40, 48), + (7, 264), + (55, 160), + (55, 173), + (8, 348), + (31, 26), + (16, 332), + (13, 197), + (26, 280), + (11, 201), + (23, 226), + (23, 287), + (59, 263), + (14, 166), + (42, 243), + (45, 228), + (49, 286), + (23, 60), + (32, 42), + (27, 217), + (38, 25), + (32, 347), + (11, 365), + (15, 113), + (58, 192), + (33, 47), + (12, 333), + (34, 319), + (8, 199), + (60, 27), + (33, 281), + (33, 318), + (9, 65), + (44, 328), + (57, 148), + (18, 314), + (56, 225), + (59, 126), + (54, 229), + (25, 289), + (24, 216), + (3, 54), + (9, 359), + (34, 87), + (56, 342), + (42, 2), + (55, 299), + (0, 119), + (58, 222), + (38, 239), + (17, 31), + (9, 319), + (9, 219), + (39, 292), + (4, 148), + (32, 321), + (53, 229), + (39, 10), + (30, 349), + (43, 377), + (17, 164), + (56, 172), + (8, 202), + (6, 341), + (6, 120), + (47, 351), + (25, 128), + (28, 163), + (11, 125), + (60, 90), + (33, 4), + (11, 325), + (7, 314), + (31, 19), + (3, 133), + (18, 357), + (53, 111), + (31, 275), + (10, 259), + (8, 61), + (39, 220), + (23, 62), + (6, 251), + (17, 79), + (48, 325), + (37, 136), + (42, 349), + (59, 208), + (11, 323), + (50, 147), + (6, 114), + (13, 227), + (25, 20), + (7, 104), + (53, 49), + (33, 383), + (53, 120), + (50, 134), + (60, 232), + (34, 267), + (38, 9), + (37, 204), + (41, 143), + (8, 299), + (33, 165), + (54, 376), + (26, 367), + (23, 98), + (4, 120), + (49, 107), + (47, 53), + (4, 166), + (25, 327), + (30, 266), + (52, 367), + (49, 155), + (34, 248), + (45, 350), + (39, 162), + (2, 265), + (0, 85), + (59, 141), + (34, 310), + (14, 109), + (46, 120), + (8, 118), + (33, 67), + (9, 7), + (0, 149), + (38, 112), + (33, 285), + (11, 72), + (29, 351), + (38, 60), + (22, 265), + (39, 171), + (11, 100), + (57, 28), + (10, 248), + (37, 287), + (55, 175), + (17, 46), + (59, 33), + (10, 359), + (15, 43), + (54, 231), + (34, 11), + (25, 299), + (19, 48), + (30, 166), + (1, 254), + (53, 311), + (43, 115), + (1, 101), + (36, 335), + (29, 32), + (7, 257), + (59, 366), + (17, 315), + (28, 110), + (47, 320), + (46, 334), + (17, 184), + (37, 33), + (28, 130), + (28, 376), + (25, 293), + (40, 19), + (43, 341), + (36, 58), + (52, 44), + (34, 238), + (39, 102), + (32, 85), + (28, 204), + (22, 174), + (43, 303), + (37, 267), + (38, 27), + (40, 239), + (45, 63), + (10, 327), + (10, 5), + (4, 110), + (25, 278), + (29, 214), + (12, 303), + (19, 216), + (10, 196), + (52, 237), + (7, 0), + (21, 323), + (59, 91), + (58, 309), + (18, 124), + (49, 212), + (11, 4), + (11, 200), + (33, 51), + (29, 357), + (54, 346), + (10, 301), + (47, 185), + (34, 241), + (44, 74), + (25, 295), + (13, 263), + (6, 276), + (29, 367), + (55, 295), + (9, 382), + (29, 87), + (37, 341), + (55, 325), + (22, 332), + (43, 198), + (36, 178), + (11, 155), + (12, 338), + (53, 295), + (51, 190), + (27, 380), + (13, 303), + (10, 308), + (7, 196), + (39, 289), + (56, 21), + (52, 63), + (5, 332), + (47, 244), + (52, 75), + (25, 191), + (10, 231), + (7, 31), + (39, 111), + (14, 365), + (36, 8), + (35, 366), + (46, 246), + (33, 200), + (54, 36), + (4, 380), + (35, 340), + (0, 175), + (59, 291), + (3, 374), + (48, 13), + (23, 1), + (47, 33), + (45, 330), + (57, 89), + (1, 349), + (37, 49), + (5, 160), + (23, 269), + (25, 227), + (56, 60), + (38, 128), + (8, 294), + (52, 318), + (17, 142), + (40, 180), + (4, 324), + (39, 59), + (27, 251), + (9, 372), + (40, 26), + (46, 99), + (31, 158), + (11, 301), + (23, 58), + (31, 272), + (34, 123), + (13, 264), + (51, 353), + (10, 72), + (13, 345), + (32, 258), + (7, 308), + (10, 321), + (3, 68), + (10, 330), + (23, 248), + (50, 298), + (8, 203), + (6, 200), + (15, 16), + (56, 91), + (38, 347), + (4, 307), + (56, 84), + (6, 111), + (56, 169), + (28, 167), + (57, 8), + (48, 236), + (59, 234), + (23, 177), + (35, 349), + (46, 241), + (0, 183), + (37, 47), + (10, 138), + (49, 136), + (44, 156), + (43, 68), + (25, 1), + (31, 92), + (0, 94), + (43, 145), + (10, 155), + (46, 231), + (4, 86), + (45, 43), + (35, 13), + (28, 366), + (23, 319), + (51, 122), + (60, 383), + (23, 213), + (46, 263), + (56, 17), + (41, 48), + (56, 203), + (4, 196), + (52, 31), + (13, 68), + (23, 130), + (30, 153), + (25, 126), + (55, 16), + (7, 77), + (6, 137), + (25, 308), + (13, 142), + (4, 204), + (8, 324), + (59, 354), + (0, 207), + (39, 161), + (42, 122), + (30, 276), + (25, 31), + (43, 127), + (17, 371), + (29, 215), + (41, 4), + (51, 14), + (45, 382), + (8, 375), + (13, 375), + (51, 325), + (5, 234), + (14, 124), + (11, 186), + (59, 186), + (52, 238), + (58, 136), + (28, 330), + (46, 371), + (10, 37), + (9, 13), + (35, 316), + (47, 69), + (27, 49), + (12, 265), + (60, 112), + (45, 184), + (45, 162), + (48, 261), + (35, 84), + (46, 11), + (4, 161), + (46, 74), + (7, 170), + (53, 118), + (50, 217), + (19, 116), + (30, 258), + (46, 10), + (37, 359), + (50, 240), + (42, 111), + (58, 151), + (14, 367), + (42, 62), + (11, 15), + (8, 22), + (38, 367), + (44, 96), + (13, 229), + (13, 6), + (25, 71), + (60, 247), + (10, 353), + (10, 152), + (35, 91), + (45, 186), + (43, 352), + (10, 250), + (20, 108), + (57, 319), + (7, 186), + (54, 55), + (55, 150), + (49, 101), + (22, 20), + (26, 315), + (32, 243), + (7, 316), + (29, 380), + (12, 109), + (27, 26), + (58, 250), + (7, 370), + (10, 335), + (43, 73), + (53, 23), + (35, 31), + (10, 23), + (47, 131), + (20, 22), + (43, 190), + (54, 140), + (49, 173), + (18, 345), + (23, 240), + (37, 174), + (35, 145), + (27, 109), + (46, 211), + (5, 296), + (20, 238), + (44, 83), + (56, 246), + (14, 95), + (4, 44), + (12, 137), + (11, 348), + (46, 64), + (48, 341), + (10, 329), + (29, 17), + (33, 275), + (10, 368), + (11, 282), + (37, 320), + (50, 12), + (11, 22), + (36, 322), + (22, 374), + (35, 18), + (55, 309), + (44, 317), + (28, 3), + (4, 113), + (40, 229), + (8, 41), + (27, 195), + (60, 135), + (21, 5), + (48, 109), + (34, 15), + (37, 165), + (4, 242), + (48, 121), + (38, 99), + (42, 145), + (51, 270), + (36, 343), + (45, 323), + (28, 162), + (18, 3), + (13, 182), + (24, 350), + (48, 116), + (30, 41), + (29, 273), + (4, 361), + (29, 312), + (55, 74), + (52, 217), + (44, 354), + (37, 85), + (59, 192), + (46, 139), + (27, 157), + (45, 54), + (33, 127), + (37, 365), + (60, 60), + (14, 122), + (38, 313), + (49, 371), + (31, 40), + (9, 88), + (59, 373), + (24, 168), + (30, 91), + (10, 86), + (53, 42), + (14, 281), + (28, 77), + (16, 356), + (60, 100), + (52, 300), + (9, 89), + (59, 377), + (38, 259), + (8, 79), + (56, 39), + (24, 41), + (44, 326), + (33, 5), + (36, 200), + (60, 203), + (44, 66), + (41, 336), + (47, 279), + (9, 210), + (45, 216), + (57, 243), + (35, 236), + (43, 290), + (23, 142), + (48, 326), + (60, 296), + (58, 266), + (42, 151), + (27, 70), + (8, 357), + (28, 320), + (13, 297), + (7, 204), + (59, 174), + (4, 52), + (50, 148), + (45, 265), + (2, 31), + (40, 160), + (9, 136), + (40, 378), + (30, 262), + (14, 21), + (34, 13), + (40, 85), + (56, 168), + (0, 51), + (27, 213), + (11, 242), + (7, 215), + (27, 181), + (41, 97), + (30, 187), + (49, 56), + (9, 138), + (31, 337), + (35, 368), + (24, 306), + (55, 376), + (33, 361), + (13, 74), + (51, 23), + (28, 315), + (44, 121), + (0, 86), + (17, 52), + (28, 20), + (3, 41), + (32, 297), + (26, 191), + (9, 297), + (60, 158), + (43, 79), + (34, 364), + (11, 2), + (36, 3), + (52, 317), + (29, 216), + (30, 302), + (30, 138), + (48, 56), + (28, 81), + (10, 134), + (60, 297), + (33, 46), + (35, 215), + (3, 55), + (4, 101), + (23, 84), + (3, 170), + (59, 116), + (10, 194), + (7, 277), + (12, 14), + (38, 264), + (24, 360), + (13, 2), + (29, 263), + (60, 274), + (55, 185), + (9, 14), + (58, 159), + (9, 283), + (44, 343), + (44, 0), + (39, 212), + (41, 260), + (28, 185), + (3, 357), + (8, 86), + (11, 51), + (28, 287), + (7, 279), + (1, 181), + (44, 87), + (14, 5), + (52, 346), + (60, 71), + (35, 286), + (58, 284), + (32, 59), + (8, 284), + (6, 208), + (10, 90), + (11, 64), + (54, 340), + (5, 170), + (34, 175), + (29, 287), + (49, 76), + (13, 158), + (33, 14), + (60, 161), + (46, 136), + (48, 366), + (50, 116), + (38, 127), + (59, 321), + (24, 121), + (35, 207), + (60, 151), + (33, 53), + (7, 278), + (34, 315), + (37, 131), + (2, 87), + (33, 359), + (52, 208), + (6, 104), + (30, 373), + (41, 318), + (14, 39), + (14, 17), + (14, 297), + (19, 366), + (4, 3), + (0, 151), + (35, 75), + (24, 370), + (43, 280), + (51, 317), + (39, 62), + (12, 310), + (51, 159), + (48, 194), + (0, 241), + (27, 107), + (28, 352), + (12, 47), + (48, 67), + (29, 370), + (10, 129), + (12, 315), + (60, 230), + (43, 86), + (43, 139), + (5, 96), + (8, 152), + (10, 50), + (34, 25), + (25, 274), + (9, 249), + (13, 86), + (51, 229), + (52, 192), + (23, 203), + (36, 271), + (7, 285), + (11, 308), + (49, 175), + (10, 360), + (25, 150), + (47, 337), + (7, 39), + (47, 144), + (10, 61), + (52, 25), + (27, 189), + (12, 10), + (33, 320), + (33, 149), + (10, 145), + (29, 60), + (35, 326), + (35, 20), + (22, 80), + (42, 204), + (10, 11), + (38, 120), + (45, 327), + (11, 183), + (50, 328), + (9, 259), + (13, 265), + (58, 205), + (36, 12), + (44, 218), + (27, 140), + (51, 36), + (41, 370), + (60, 124), + (55, 17), + (30, 240), + (8, 300), + (13, 177), + (47, 264), + (9, 180), + (45, 298), + (52, 104), + (29, 220), + (4, 32), + (51, 268), + (52, 22), + (59, 59), + (3, 231), + (9, 114), + (51, 136), + (50, 199), + (4, 246), + (9, 137), + (42, 104), + (29, 75), + (29, 93), + (28, 188), + (14, 241), + (40, 347), + (23, 176), + (9, 166), + (8, 215), + (7, 372), + (11, 70), + (25, 247), + (48, 11), + (52, 77), + (46, 265), + (48, 54), + (49, 368), + (15, 30), + (49, 46), + (41, 373), + (50, 45), + (56, 187), + (47, 105), + (25, 64), + (3, 348), + (10, 222), + (50, 204), + (50, 314), + (0, 289), + (42, 300), + (35, 198), + (38, 35), + (51, 171), + (23, 128), + (8, 230), + (18, 23), + (48, 125), + (47, 212), + (9, 68), + (3, 230), + (25, 256), + (42, 5), + (42, 108), + (43, 59), + (46, 17), + (11, 240), + (3, 34), + (36, 41), + (11, 241), + (19, 190), + (43, 137), + (44, 342), + (6, 379), + (30, 273), + (48, 69), + (35, 282), + (22, 99), + (26, 278), + (59, 312), + (37, 44), + (49, 183), + (48, 146), + (47, 360), + (43, 291), + (38, 49), + (44, 28), + (32, 130), + (40, 223), + (4, 87), + (52, 330), + (10, 55), + (7, 44), + (13, 71), + (11, 73), + (49, 342), + (0, 35), + (9, 260), + (6, 238), + (8, 353), + (0, 176), + (5, 108), + (52, 229), + (55, 350), + (8, 50), + (51, 312), + (54, 287), + (13, 203), + (48, 310), + (49, 133), + (42, 262), + (10, 122), + (15, 197), + (31, 365), + (35, 257), + (1, 238), + (6, 298), + (33, 163), + (56, 248), + (56, 291), + (40, 137), + (9, 118), + (33, 289), + (48, 381), + (41, 235), + (49, 314), + (54, 18), + (42, 114), + (45, 89), + (50, 269), + (27, 160), + (51, 9), + (59, 64), + (39, 297), + (52, 285), + (39, 122), + (23, 234), + (47, 259), + (52, 307), + (39, 97), + (40, 319), + (39, 281), + (39, 131), + (42, 178), + (52, 43), + (60, 316), + (45, 128), + (40, 269), + (43, 93), + (42, 309), + (6, 21), + (36, 28), + (31, 210), + (41, 38), + (8, 178), + (50, 370), + (47, 350), + (39, 126), + (9, 196), + (9, 98), + (45, 32), + (44, 107), + (40, 346), + (14, 225), + (60, 156), + (40, 151), + (60, 17), + (51, 258), + (31, 15), + (48, 47), + (0, 39), + (39, 203), + (26, 232), + (51, 103), + (60, 102), + (46, 114), + (14, 128), + (39, 363), + (43, 148), + (45, 99), + (40, 65), + (44, 93), + (18, 146), + (50, 295), + (51, 4), + (36, 248), + (52, 107), + (6, 181), + (60, 306), + (4, 349), + (43, 301), + (52, 252), + (52, 258), + (51, 209), + (9, 266), + (49, 292), + (44, 8), + (51, 178), + (44, 36), + (37, 273), + (26, 83), + (0, 71), + (8, 9), + (31, 211), + (51, 333), + (59, 329), + (41, 241), + (31, 128), + (40, 306), + (42, 173), + (54, 154), + (32, 273), + (42, 237), + (49, 243), + (40, 39), + (52, 197), + (44, 184), + (39, 382), + (59, 169), + (42, 169), + (41, 122), + (52, 233), + (44, 278), + (44, 200), + (25, 60), + (41, 177), + (60, 204), + (42, 16), + (38, 113), + (41, 72), + (26, 308), + (43, 225), + (41, 361), + (50, 176), + (42, 190), + (51, 251), + (0, 204), + (44, 77), + (42, 247), + (41, 174), + (13, 186), + (44, 7), + (44, 347), + (48, 360), + (45, 136), + (31, 12), + (31, 339), + (40, 244), + (47, 254), + (41, 272), + (41, 125), + (59, 157), + (47, 98), + (33, 264), + (44, 255), + (44, 357), + (38, 241), + (35, 144), + (40, 204), + (40, 379), + (47, 342), + (46, 91), + (41, 11), + (45, 57), + (43, 76), + (1, 80), + (42, 220), + (49, 293), + (40, 383), + (48, 15), + (36, 95), + (41, 31), + (40, 199), + (42, 321), + (40, 311), + (0, 316), + (0, 10), + (41, 104), + (40, 66), + (31, 80), + (35, 370), + (40, 220), + (49, 334), + (27, 81), + (39, 66), + (49, 234), + (31, 361), + (41, 299), + (31, 222), + (41, 76), + (41, 77), + (53, 137), + (48, 57), + (0, 301), + (36, 277), + (40, 127), + (41, 229), + (40, 104), + (46, 54), + (41, 85), + (49, 63), + (41, 188), + (52, 85), + (44, 122), + (41, 217), + (42, 283), + (45, 95), + (41, 245), + (53, 149), + (40, 290), + (40, 60), + (40, 169), + (2, 56), + (48, 101), + (41, 223), +]; pub(super) const FLASH: &[(u16, u16)] = &[ (25, 53), (22, 66), @@ -6437,3 +13323,6506 @@ pub(super) const FLASH: &[(u16, u16)] = &[ (42, 251), (42, 254), ]; +pub(super) const GLM52: &[(u16, u16)] = &[ + (50, 118), + (72, 53), + (66, 117), + (59, 12), + (28, 142), + (18, 25), + (67, 45), + (60, 23), + (21, 26), + (76, 150), + (45, 105), + (20, 195), + (50, 55), + (72, 226), + (52, 20), + (64, 118), + (74, 21), + (43, 180), + (32, 150), + (54, 235), + (63, 122), + (41, 167), + (32, 46), + (17, 31), + (53, 17), + (58, 244), + (74, 32), + (31, 200), + (68, 59), + (21, 233), + (73, 19), + (19, 69), + (22, 87), + (70, 189), + (55, 187), + (38, 100), + (49, 186), + (20, 78), + (31, 106), + (69, 138), + (20, 201), + (51, 2), + (20, 209), + (27, 184), + (64, 142), + (57, 45), + (15, 12), + (18, 53), + (22, 18), + (60, 101), + (30, 14), + (65, 213), + (26, 88), + (61, 3), + (70, 239), + (52, 105), + (65, 161), + (75, 169), + (30, 29), + (35, 129), + (34, 63), + (71, 251), + (47, 48), + (49, 170), + (21, 119), + (21, 135), + (18, 46), + (57, 135), + (22, 200), + (36, 77), + (62, 14), + (28, 1), + (49, 41), + (60, 179), + (71, 137), + (10, 226), + (20, 148), + (55, 48), + (35, 150), + (46, 181), + (55, 106), + (10, 217), + (16, 55), + (73, 210), + (52, 55), + (71, 84), + (75, 26), + (42, 179), + (46, 84), + (67, 138), + (32, 8), + (64, 138), + (71, 60), + (17, 147), + (56, 245), + (57, 22), + (66, 114), + (29, 174), + (62, 237), + (61, 190), + (57, 57), + (15, 226), + (34, 247), + (48, 53), + (25, 5), + (26, 162), + (39, 128), + (58, 198), + (61, 158), + (74, 31), + (77, 169), + (12, 137), + (43, 15), + (60, 177), + (61, 146), + (27, 58), + (40, 60), + (40, 205), + (43, 5), + (50, 70), + (15, 154), + (33, 77), + (77, 120), + (52, 187), + (63, 40), + (74, 6), + (16, 21), + (44, 191), + (50, 19), + (59, 235), + (71, 7), + (33, 126), + (34, 107), + (37, 62), + (54, 116), + (71, 252), + (32, 217), + (43, 144), + (63, 243), + (69, 50), + (3, 60), + (12, 46), + (33, 129), + (50, 44), + (55, 206), + (69, 254), + (17, 167), + (19, 80), + (20, 91), + (21, 83), + (36, 45), + (39, 189), + (45, 14), + (48, 5), + (57, 255), + (16, 252), + (28, 69), + (37, 188), + (38, 166), + (69, 143), + (18, 63), + (42, 185), + (53, 143), + (71, 66), + (18, 230), + (22, 23), + (45, 75), + (49, 61), + (59, 136), + (17, 14), + (36, 143), + (40, 241), + (36, 61), + (45, 141), + (53, 43), + (55, 148), + (63, 250), + (17, 96), + (19, 178), + (24, 102), + (38, 54), + (49, 74), + (19, 160), + (44, 170), + (73, 231), + (40, 253), + (74, 121), + (23, 85), + (24, 92), + (25, 200), + (30, 43), + (37, 65), + (38, 6), + (39, 147), + (39, 172), + (67, 232), + (21, 114), + (28, 122), + (30, 5), + (33, 2), + (39, 222), + (40, 24), + (51, 189), + (59, 93), + (19, 135), + (35, 36), + (36, 128), + (51, 228), + (60, 231), + (65, 62), + (67, 76), + (70, 63), + (72, 82), + (17, 34), + (29, 12), + (30, 59), + (30, 60), + (32, 207), + (42, 226), + (18, 54), + (36, 159), + (51, 88), + (60, 20), + (63, 192), + (64, 219), + (68, 138), + (68, 190), + (12, 45), + (12, 79), + (14, 238), + (23, 172), + (30, 113), + (35, 34), + (37, 122), + (39, 180), + (46, 204), + (59, 2), + (67, 15), + (12, 102), + (13, 200), + (18, 212), + (22, 186), + (30, 58), + (33, 12), + (34, 19), + (34, 132), + (36, 145), + (37, 138), + (39, 174), + (67, 157), + (8, 1), + (16, 191), + (17, 24), + (19, 249), + (23, 27), + (34, 149), + (35, 39), + (36, 115), + (38, 129), + (38, 217), + (51, 173), + (54, 242), + (56, 237), + (62, 107), + (63, 195), + (67, 193), + (76, 129), + (15, 32), + (33, 220), + (35, 135), + (47, 202), + (51, 83), + (54, 18), + (58, 229), + (58, 251), + (63, 157), + (66, 1), + (76, 60), + (11, 147), + (29, 0), + (36, 50), + (37, 31), + (47, 81), + (62, 157), + (73, 82), + (75, 222), + (18, 239), + (27, 174), + (35, 10), + (44, 62), + (44, 100), + (56, 63), + (66, 214), + (77, 87), + (12, 104), + (15, 34), + (18, 35), + (19, 6), + (19, 53), + (28, 176), + (33, 221), + (35, 98), + (37, 92), + (40, 49), + (40, 77), + (41, 55), + (43, 143), + (46, 78), + (47, 226), + (49, 180), + (53, 29), + (58, 40), + (70, 39), + (75, 247), + (14, 8), + (14, 204), + (16, 148), + (22, 12), + (25, 106), + (26, 165), + (26, 233), + (38, 248), + (47, 129), + (50, 111), + (56, 244), + (59, 95), + (62, 160), + (62, 221), + (67, 209), + (18, 190), + (21, 118), + (31, 176), + (32, 215), + (34, 6), + (37, 61), + (61, 67), + (64, 234), + (69, 51), + (73, 139), + (75, 69), + (22, 107), + (26, 213), + (29, 22), + (30, 96), + (31, 111), + (36, 140), + (38, 154), + (40, 78), + (48, 238), + (52, 108), + (53, 36), + (54, 105), + (55, 228), + (56, 10), + (56, 48), + (59, 154), + (65, 105), + (68, 82), + (69, 21), + (69, 82), + (76, 144), + (11, 129), + (16, 37), + (19, 8), + (20, 73), + (23, 139), + (24, 77), + (29, 154), + (34, 198), + (40, 188), + (40, 232), + (43, 200), + (44, 157), + (45, 219), + (46, 111), + (53, 156), + (56, 96), + (62, 90), + (66, 93), + (68, 122), + (72, 3), + (72, 179), + (16, 9), + (18, 55), + (18, 73), + (19, 184), + (19, 201), + (20, 29), + (22, 197), + (30, 231), + (31, 188), + (32, 75), + (46, 148), + (48, 123), + (48, 252), + (52, 205), + (58, 46), + (59, 4), + (67, 79), + (70, 36), + (17, 199), + (19, 235), + (21, 180), + (29, 1), + (29, 163), + (30, 46), + (31, 166), + (31, 241), + (38, 188), + (40, 142), + (42, 108), + (45, 63), + (46, 207), + (54, 43), + (54, 64), + (58, 81), + (64, 127), + (64, 254), + (76, 69), + (18, 131), + (19, 71), + (20, 187), + (21, 95), + (24, 25), + (24, 127), + (25, 0), + (30, 243), + (32, 67), + (33, 228), + (33, 242), + (37, 149), + (41, 208), + (42, 34), + (54, 178), + (59, 45), + (65, 232), + (72, 234), + (76, 80), + (12, 210), + (13, 225), + (20, 255), + (21, 70), + (22, 153), + (23, 137), + (27, 55), + (27, 156), + (31, 125), + (32, 6), + (34, 172), + (38, 118), + (44, 44), + (44, 63), + (44, 122), + (46, 17), + (47, 164), + (48, 178), + (48, 219), + (51, 55), + (51, 163), + (52, 76), + (53, 20), + (55, 94), + (61, 53), + (64, 26), + (64, 168), + (68, 234), + (69, 227), + (72, 94), + (72, 140), + (73, 163), + (74, 164), + (75, 83), + (76, 142), + (77, 43), + (12, 229), + (13, 167), + (13, 208), + (17, 175), + (18, 6), + (19, 89), + (23, 188), + (27, 53), + (30, 167), + (33, 165), + (33, 237), + (35, 109), + (35, 177), + (35, 189), + (46, 156), + (47, 0), + (47, 255), + (49, 5), + (49, 94), + (49, 118), + (52, 136), + (53, 203), + (54, 216), + (56, 123), + (59, 20), + (62, 71), + (63, 254), + (64, 193), + (71, 27), + (73, 26), + (76, 17), + (76, 171), + (77, 159), + (16, 49), + (20, 49), + (23, 251), + (25, 81), + (25, 175), + (30, 180), + (33, 243), + (35, 4), + (36, 220), + (40, 233), + (41, 163), + (42, 131), + (42, 181), + (45, 108), + (46, 175), + (47, 30), + (61, 94), + (62, 108), + (63, 139), + (67, 23), + (67, 162), + (68, 51), + (71, 15), + (71, 147), + (73, 113), + (10, 24), + (11, 23), + (15, 238), + (19, 28), + (19, 100), + (19, 132), + (21, 78), + (22, 245), + (24, 198), + (26, 91), + (27, 98), + (31, 21), + (31, 143), + (34, 18), + (34, 211), + (34, 215), + (34, 239), + (35, 43), + (36, 195), + (39, 210), + (40, 150), + (41, 71), + (41, 92), + (41, 113), + (44, 49), + (45, 46), + (48, 87), + (49, 59), + (53, 239), + (54, 74), + (55, 15), + (60, 135), + (61, 141), + (63, 10), + (67, 172), + (70, 111), + (74, 4), + (75, 203), + (10, 192), + (14, 89), + (15, 124), + (15, 155), + (16, 235), + (17, 57), + (17, 98), + (17, 204), + (21, 216), + (22, 221), + (26, 157), + (26, 206), + (29, 41), + (29, 117), + (33, 9), + (33, 116), + (34, 73), + (38, 78), + (39, 179), + (41, 138), + (41, 150), + (41, 223), + (42, 233), + (44, 13), + (45, 120), + (46, 200), + (48, 206), + (56, 182), + (57, 209), + (59, 152), + (61, 151), + (62, 83), + (66, 118), + (66, 154), + (66, 229), + (67, 63), + (67, 247), + (68, 64), + (68, 219), + (69, 158), + (72, 132), + (72, 157), + (73, 30), + (75, 84), + (5, 76), + (6, 90), + (8, 201), + (11, 254), + (13, 217), + (16, 212), + (17, 181), + (19, 105), + (21, 45), + (26, 16), + (26, 163), + (27, 16), + (28, 120), + (31, 17), + (31, 72), + (31, 214), + (31, 234), + (32, 9), + (33, 245), + (34, 100), + (35, 119), + (36, 53), + (37, 127), + (37, 220), + (37, 247), + (38, 19), + (40, 130), + (40, 164), + (41, 87), + (43, 207), + (44, 51), + (45, 136), + (47, 183), + (48, 230), + (49, 231), + (51, 199), + (53, 117), + (54, 65), + (55, 33), + (55, 184), + (57, 34), + (57, 150), + (58, 38), + (59, 0), + (61, 241), + (62, 39), + (63, 76), + (65, 44), + (66, 126), + (68, 103), + (68, 218), + (68, 231), + (69, 67), + (75, 18), + (5, 3), + (10, 181), + (11, 145), + (17, 126), + (17, 160), + (19, 221), + (22, 3), + (23, 36), + (24, 175), + (24, 178), + (27, 138), + (27, 176), + (29, 148), + (32, 128), + (33, 79), + (33, 148), + (33, 177), + (34, 93), + (35, 83), + (36, 38), + (36, 132), + (36, 212), + (37, 111), + (38, 40), + (38, 94), + (38, 181), + (38, 237), + (39, 16), + (41, 183), + (42, 33), + (43, 74), + (43, 79), + (43, 237), + (44, 71), + (44, 210), + (45, 187), + (47, 85), + (47, 99), + (49, 60), + (50, 71), + (50, 74), + (50, 94), + (50, 125), + (52, 63), + (53, 65), + (53, 163), + (57, 25), + (58, 44), + (58, 209), + (60, 29), + (60, 169), + (60, 226), + (61, 107), + (64, 58), + (65, 255), + (66, 153), + (71, 208), + (72, 111), + (74, 82), + (76, 94), + (77, 177), + (77, 234), + (3, 191), + (6, 153), + (13, 85), + (14, 150), + (14, 250), + (15, 176), + (15, 177), + (16, 169), + (20, 198), + (23, 14), + (28, 210), + (28, 211), + (29, 64), + (29, 233), + (31, 1), + (31, 128), + (31, 129), + (32, 157), + (32, 191), + (33, 88), + (33, 161), + (33, 218), + (34, 33), + (34, 39), + (36, 30), + (36, 42), + (37, 142), + (37, 218), + (39, 32), + (40, 111), + (41, 45), + (41, 110), + (42, 46), + (42, 59), + (42, 70), + (42, 116), + (43, 77), + (43, 98), + (43, 242), + (44, 116), + (44, 131), + (44, 150), + (46, 33), + (46, 109), + (47, 64), + (52, 154), + (52, 171), + (52, 185), + (53, 73), + (53, 150), + (54, 32), + (56, 77), + (57, 65), + (58, 169), + (61, 26), + (61, 76), + (61, 160), + (61, 179), + (62, 116), + (62, 170), + (63, 189), + (64, 156), + (64, 205), + (65, 120), + (66, 47), + (66, 79), + (66, 165), + (66, 191), + (67, 118), + (68, 206), + (69, 69), + (69, 79), + (69, 238), + (70, 143), + (70, 146), + (70, 180), + (70, 236), + (71, 214), + (71, 232), + (72, 133), + (73, 132), + (5, 48), + (7, 224), + (8, 90), + (10, 216), + (11, 10), + (11, 15), + (11, 62), + (11, 227), + (12, 67), + (12, 116), + (12, 220), + (13, 187), + (14, 11), + (15, 115), + (15, 142), + (16, 56), + (17, 151), + (17, 216), + (19, 24), + (20, 25), + (24, 81), + (24, 101), + (28, 81), + (29, 191), + (30, 207), + (31, 179), + (32, 216), + (32, 246), + (33, 47), + (34, 158), + (35, 15), + (35, 193), + (35, 252), + (41, 8), + (41, 139), + (41, 186), + (41, 234), + (42, 124), + (42, 134), + (42, 210), + (43, 26), + (44, 126), + (45, 18), + (45, 221), + (46, 118), + (47, 52), + (47, 138), + (48, 36), + (48, 181), + (48, 195), + (50, 244), + (51, 166), + (51, 194), + (51, 229), + (53, 6), + (54, 215), + (55, 183), + (55, 200), + (56, 28), + (56, 47), + (56, 169), + (56, 248), + (57, 54), + (57, 90), + (57, 168), + (58, 90), + (58, 195), + (59, 137), + (60, 51), + (60, 195), + (60, 237), + (61, 18), + (61, 81), + (61, 134), + (62, 70), + (64, 181), + (66, 123), + (67, 211), + (67, 234), + (68, 126), + (68, 164), + (69, 28), + (69, 151), + (69, 194), + (70, 38), + (70, 57), + (71, 229), + (72, 44), + (73, 236), + (74, 216), + (75, 165), + (76, 169), + (77, 28), + (77, 62), + (77, 149), + (3, 222), + (4, 119), + (6, 109), + (6, 117), + (6, 144), + (7, 77), + (10, 128), + (11, 157), + (12, 236), + (13, 212), + (14, 23), + (15, 29), + (15, 164), + (16, 63), + (16, 162), + (18, 145), + (18, 157), + (19, 151), + (19, 246), + (21, 219), + (23, 98), + (23, 113), + (23, 134), + (23, 160), + (23, 206), + (23, 216), + (24, 107), + (25, 29), + (25, 51), + (25, 85), + (25, 226), + (28, 34), + (28, 114), + (29, 15), + (29, 47), + (29, 73), + (29, 196), + (30, 33), + (30, 64), + (30, 191), + (31, 25), + (31, 161), + (33, 184), + (34, 26), + (36, 46), + (37, 164), + (37, 186), + (38, 134), + (38, 180), + (39, 200), + (40, 151), + (41, 210), + (41, 241), + (43, 222), + (44, 64), + (44, 66), + (47, 11), + (47, 195), + (48, 49), + (49, 64), + (49, 145), + (49, 232), + (51, 216), + (54, 245), + (55, 104), + (55, 199), + (55, 221), + (55, 239), + (56, 13), + (57, 18), + (57, 208), + (58, 162), + (58, 211), + (58, 226), + (59, 113), + (61, 17), + (62, 37), + (62, 124), + (62, 151), + (63, 119), + (63, 229), + (65, 13), + (65, 68), + (66, 43), + (66, 45), + (66, 150), + (66, 172), + (66, 174), + (67, 154), + (68, 185), + (69, 119), + (72, 96), + (73, 3), + (73, 18), + (73, 192), + (73, 241), + (74, 102), + (74, 227), + (75, 77), + (76, 82), + (76, 87), + (77, 34), + (77, 68), + (77, 128), + (77, 176), + (4, 111), + (5, 105), + (6, 172), + (8, 5), + (8, 8), + (8, 129), + (8, 136), + (8, 169), + (9, 50), + (9, 202), + (13, 25), + (13, 178), + (13, 230), + (14, 3), + (14, 53), + (14, 135), + (14, 170), + (15, 80), + (15, 139), + (15, 163), + (15, 210), + (18, 126), + (22, 66), + (22, 119), + (22, 152), + (23, 44), + (23, 84), + (23, 155), + (23, 229), + (26, 114), + (26, 159), + (26, 209), + (27, 11), + (27, 163), + (27, 189), + (29, 147), + (31, 134), + (31, 173), + (31, 175), + (32, 62), + (32, 84), + (32, 89), + (32, 194), + (33, 67), + (33, 204), + (34, 83), + (34, 227), + (35, 29), + (35, 35), + (36, 1), + (36, 64), + (36, 169), + (37, 6), + (37, 153), + (37, 156), + (38, 93), + (38, 145), + (38, 156), + (38, 161), + (39, 15), + (39, 133), + (39, 135), + (39, 161), + (39, 175), + (39, 182), + (40, 127), + (40, 131), + (41, 116), + (41, 235), + (42, 52), + (42, 56), + (42, 96), + (42, 141), + (43, 89), + (43, 117), + (43, 176), + (44, 81), + (44, 112), + (44, 218), + (44, 234), + (46, 16), + (46, 132), + (46, 166), + (46, 227), + (47, 83), + (47, 251), + (48, 187), + (49, 144), + (49, 229), + (50, 221), + (51, 85), + (51, 188), + (51, 245), + (53, 102), + (54, 9), + (55, 86), + (55, 191), + (55, 240), + (56, 197), + (56, 210), + (57, 234), + (58, 252), + (60, 75), + (62, 82), + (62, 232), + (63, 71), + (63, 115), + (64, 217), + (65, 2), + (65, 140), + (65, 145), + (65, 147), + (66, 147), + (66, 213), + (70, 119), + (70, 255), + (71, 2), + (71, 59), + (72, 103), + (72, 181), + (74, 5), + (74, 22), + (75, 29), + (75, 40), + (75, 187), + (76, 58), + (76, 168), + (76, 199), + (77, 54), + (77, 64), + (4, 107), + (6, 27), + (6, 180), + (7, 65), + (8, 7), + (8, 97), + (8, 218), + (9, 48), + (9, 89), + (9, 142), + (9, 150), + (10, 160), + (10, 205), + (10, 220), + (11, 99), + (11, 152), + (12, 193), + (14, 102), + (14, 192), + (14, 201), + (14, 216), + (15, 108), + (15, 123), + (16, 47), + (16, 70), + (17, 220), + (17, 239), + (19, 126), + (19, 139), + (20, 50), + (20, 120), + (22, 109), + (23, 171), + (24, 31), + (24, 34), + (24, 169), + (24, 218), + (25, 17), + (25, 138), + (25, 144), + (25, 154), + (25, 249), + (25, 250), + (26, 21), + (26, 55), + (26, 128), + (27, 21), + (27, 90), + (27, 211), + (27, 251), + (28, 84), + (29, 45), + (29, 158), + (30, 22), + (30, 110), + (30, 234), + (31, 76), + (32, 0), + (32, 214), + (32, 225), + (33, 98), + (33, 139), + (33, 140), + (33, 150), + (33, 152), + (35, 110), + (36, 85), + (36, 137), + (36, 217), + (37, 26), + (37, 206), + (37, 223), + (38, 4), + (38, 24), + (38, 28), + (39, 5), + (39, 76), + (39, 215), + (40, 244), + (41, 152), + (41, 158), + (41, 254), + (42, 242), + (42, 247), + (43, 10), + (43, 48), + (44, 24), + (44, 55), + (44, 120), + (44, 181), + (44, 187), + (45, 71), + (45, 177), + (45, 252), + (47, 3), + (47, 20), + (47, 104), + (47, 144), + (48, 41), + (48, 173), + (49, 13), + (49, 17), + (51, 125), + (51, 191), + (52, 6), + (52, 116), + (53, 23), + (53, 28), + (53, 85), + (53, 94), + (54, 135), + (54, 157), + (54, 168), + (55, 160), + (55, 252), + (56, 158), + (56, 191), + (57, 55), + (57, 171), + (58, 101), + (58, 103), + (58, 168), + (58, 210), + (58, 243), + (59, 222), + (60, 32), + (60, 45), + (60, 54), + (61, 45), + (62, 196), + (63, 109), + (64, 135), + (64, 186), + (64, 231), + (65, 47), + (65, 69), + (65, 246), + (66, 34), + (66, 77), + (68, 38), + (69, 197), + (70, 225), + (71, 196), + (72, 9), + (73, 34), + (73, 144), + (74, 160), + (74, 205), + (75, 244), + (76, 116), + (76, 176), + (77, 112), + (77, 192), + (3, 83), + (4, 7), + (4, 73), + (5, 35), + (5, 86), + (5, 112), + (5, 215), + (6, 165), + (7, 235), + (8, 10), + (8, 60), + (8, 189), + (8, 209), + (9, 33), + (9, 72), + (9, 81), + (10, 20), + (10, 69), + (11, 56), + (11, 134), + (11, 161), + (12, 8), + (12, 192), + (13, 41), + (13, 82), + (13, 155), + (13, 180), + (13, 188), + (14, 41), + (14, 133), + (14, 230), + (15, 65), + (15, 89), + (15, 203), + (16, 144), + (17, 10), + (17, 69), + (18, 30), + (19, 44), + (19, 152), + (19, 193), + (19, 203), + (21, 249), + (22, 123), + (22, 128), + (23, 68), + (23, 170), + (25, 204), + (26, 1), + (27, 130), + (28, 107), + (28, 160), + (29, 94), + (29, 98), + (29, 221), + (29, 245), + (30, 120), + (31, 9), + (31, 145), + (31, 146), + (32, 153), + (32, 158), + (33, 34), + (33, 75), + (34, 49), + (35, 80), + (35, 137), + (37, 191), + (37, 197), + (37, 203), + (39, 13), + (39, 122), + (40, 43), + (40, 75), + (40, 107), + (42, 9), + (42, 61), + (42, 152), + (42, 164), + (42, 204), + (43, 58), + (43, 70), + (43, 128), + (45, 58), + (45, 67), + (45, 127), + (45, 150), + (45, 201), + (46, 6), + (46, 26), + (46, 61), + (46, 101), + (48, 105), + (48, 146), + (48, 177), + (49, 46), + (50, 36), + (50, 54), + (50, 229), + (51, 16), + (51, 19), + (51, 207), + (53, 34), + (53, 48), + (53, 126), + (53, 220), + (53, 224), + (54, 20), + (54, 144), + (54, 174), + (55, 24), + (55, 56), + (55, 158), + (55, 163), + (55, 168), + (56, 79), + (56, 100), + (56, 167), + (57, 46), + (57, 110), + (59, 193), + (59, 202), + (59, 220), + (60, 248), + (61, 30), + (61, 104), + (61, 108), + (61, 115), + (61, 120), + (61, 171), + (62, 202), + (63, 35), + (63, 165), + (63, 240), + (64, 85), + (64, 249), + (65, 5), + (65, 48), + (65, 96), + (66, 44), + (66, 101), + (66, 131), + (68, 7), + (68, 184), + (68, 236), + (69, 33), + (69, 64), + (69, 123), + (69, 228), + (70, 222), + (71, 144), + (71, 219), + (71, 247), + (73, 96), + (73, 247), + (74, 158), + (75, 123), + (75, 160), + (76, 77), + (76, 79), + (76, 170), + (76, 236), + (77, 0), + (77, 27), + (77, 105), + (3, 72), + (3, 130), + (3, 148), + (4, 2), + (4, 130), + (4, 134), + (4, 206), + (5, 25), + (5, 36), + (6, 50), + (6, 76), + (6, 175), + (6, 213), + (6, 245), + (7, 1), + (7, 35), + (7, 121), + (7, 186), + (7, 209), + (8, 51), + (8, 255), + (9, 5), + (9, 14), + (9, 86), + (9, 116), + (9, 143), + (9, 255), + (10, 140), + (10, 248), + (10, 253), + (11, 6), + (12, 22), + (12, 69), + (12, 140), + (12, 180), + (12, 196), + (13, 64), + (13, 112), + (13, 215), + (13, 238), + (14, 79), + (14, 152), + (14, 165), + (14, 215), + (15, 97), + (15, 161), + (16, 90), + (17, 193), + (17, 194), + (18, 118), + (18, 236), + (19, 228), + (20, 231), + (21, 10), + (21, 14), + (21, 225), + (22, 98), + (22, 102), + (22, 183), + (22, 242), + (23, 175), + (23, 233), + (24, 48), + (24, 74), + (24, 145), + (24, 153), + (24, 173), + (25, 101), + (25, 149), + (26, 2), + (26, 71), + (27, 14), + (27, 127), + (27, 186), + (28, 38), + (28, 52), + (28, 110), + (29, 130), + (29, 139), + (29, 238), + (30, 30), + (31, 44), + (31, 211), + (31, 232), + (34, 84), + (34, 241), + (35, 31), + (35, 66), + (35, 215), + (36, 33), + (36, 55), + (36, 104), + (36, 189), + (37, 96), + (37, 105), + (37, 112), + (37, 190), + (39, 46), + (39, 184), + (40, 4), + (40, 22), + (40, 133), + (40, 148), + (40, 166), + (40, 204), + (40, 206), + (41, 40), + (41, 84), + (41, 165), + (42, 12), + (42, 44), + (42, 148), + (43, 8), + (43, 212), + (44, 36), + (44, 82), + (44, 217), + (45, 109), + (46, 36), + (46, 41), + (46, 76), + (46, 151), + (46, 163), + (47, 34), + (47, 125), + (47, 148), + (47, 188), + (47, 254), + (48, 60), + (48, 220), + (48, 221), + (48, 243), + (49, 98), + (49, 188), + (50, 79), + (50, 91), + (50, 103), + (50, 216), + (51, 36), + (51, 48), + (51, 75), + (51, 80), + (51, 164), + (52, 221), + (52, 231), + (53, 123), + (53, 136), + (54, 62), + (54, 108), + (54, 165), + (54, 166), + (55, 80), + (56, 3), + (56, 58), + (56, 110), + (56, 129), + (56, 154), + (56, 233), + (56, 243), + (57, 21), + (57, 186), + (58, 3), + (58, 45), + (58, 49), + (58, 56), + (58, 72), + (58, 132), + (58, 187), + (59, 221), + (60, 217), + (61, 137), + (61, 246), + (62, 69), + (62, 96), + (62, 112), + (62, 183), + (62, 184), + (62, 192), + (63, 1), + (63, 22), + (63, 149), + (63, 187), + (64, 97), + (64, 235), + (65, 4), + (65, 32), + (65, 93), + (65, 118), + (65, 134), + (65, 172), + (65, 239), + (65, 242), + (66, 64), + (67, 48), + (67, 253), + (68, 1), + (68, 16), + (68, 54), + (68, 102), + (68, 186), + (68, 204), + (68, 242), + (69, 198), + (70, 72), + (70, 88), + (70, 122), + (70, 127), + (70, 229), + (71, 148), + (73, 148), + (73, 245), + (74, 147), + (75, 28), + (75, 33), + (75, 155), + (75, 181), + (75, 233), + (76, 6), + (76, 24), + (76, 190), + (76, 225), + (77, 17), + (77, 65), + (77, 71), + (77, 83), + (77, 146), + (77, 205), + (77, 238), + (3, 14), + (3, 18), + (3, 26), + (3, 78), + (3, 93), + (3, 209), + (4, 4), + (4, 86), + (4, 175), + (4, 183), + (4, 214), + (5, 52), + (5, 90), + (6, 25), + (6, 72), + (6, 121), + (6, 149), + (7, 20), + (7, 82), + (7, 219), + (7, 250), + (8, 27), + (8, 66), + (8, 76), + (8, 118), + (8, 240), + (9, 34), + (9, 83), + (9, 123), + (9, 160), + (9, 224), + (10, 1), + (10, 11), + (10, 22), + (10, 25), + (10, 131), + (10, 202), + (10, 246), + (11, 66), + (11, 73), + (11, 79), + (11, 186), + (11, 229), + (12, 51), + (12, 78), + (12, 122), + (12, 139), + (12, 161), + (13, 4), + (13, 79), + (13, 182), + (13, 206), + (13, 241), + (14, 34), + (14, 65), + (14, 71), + (14, 78), + (14, 183), + (15, 151), + (15, 172), + (15, 255), + (16, 3), + (16, 15), + (16, 62), + (16, 170), + (16, 175), + (17, 30), + (17, 59), + (18, 94), + (19, 120), + (19, 173), + (19, 206), + (20, 35), + (20, 149), + (21, 183), + (21, 227), + (22, 31), + (22, 192), + (23, 57), + (23, 161), + (23, 245), + (24, 51), + (24, 100), + (24, 238), + (25, 64), + (25, 140), + (25, 155), + (25, 188), + (25, 209), + (25, 248), + (26, 50), + (26, 185), + (26, 234), + (27, 9), + (27, 24), + (27, 75), + (27, 85), + (27, 104), + (28, 2), + (28, 174), + (28, 216), + (28, 226), + (28, 231), + (28, 251), + (29, 67), + (29, 79), + (29, 118), + (29, 138), + (29, 157), + (29, 209), + (29, 226), + (29, 251), + (30, 76), + (30, 93), + (30, 129), + (30, 149), + (30, 178), + (30, 194), + (31, 97), + (31, 116), + (31, 248), + (32, 76), + (32, 116), + (32, 180), + (32, 255), + (33, 50), + (33, 59), + (33, 131), + (33, 187), + (34, 32), + (34, 55), + (34, 74), + (34, 151), + (34, 195), + (34, 207), + (35, 55), + (35, 93), + (35, 94), + (35, 154), + (35, 171), + (35, 195), + (36, 146), + (36, 206), + (36, 245), + (37, 45), + (37, 155), + (37, 189), + (37, 204), + (37, 232), + (38, 37), + (38, 45), + (38, 46), + (38, 115), + (38, 245), + (39, 33), + (39, 98), + (39, 126), + (39, 185), + (39, 223), + (39, 232), + (39, 239), + (40, 15), + (40, 183), + (41, 24), + (41, 59), + (42, 51), + (42, 123), + (42, 145), + (42, 192), + (42, 203), + (43, 217), + (43, 235), + (44, 56), + (44, 92), + (44, 114), + (44, 242), + (45, 53), + (45, 133), + (45, 172), + (46, 74), + (47, 92), + (47, 123), + (47, 126), + (47, 145), + (47, 229), + (48, 30), + (48, 59), + (48, 72), + (48, 136), + (48, 170), + (48, 171), + (48, 179), + (48, 225), + (49, 8), + (49, 70), + (49, 222), + (50, 52), + (50, 131), + (50, 142), + (50, 210), + (50, 246), + (50, 252), + (51, 25), + (51, 70), + (51, 72), + (51, 101), + (51, 109), + (51, 221), + (52, 46), + (52, 72), + (52, 73), + (53, 1), + (53, 35), + (53, 159), + (53, 233), + (54, 5), + (54, 16), + (54, 24), + (54, 40), + (54, 58), + (54, 67), + (54, 118), + (54, 132), + (54, 164), + (54, 193), + (54, 207), + (55, 9), + (55, 110), + (55, 253), + (56, 16), + (56, 199), + (56, 218), + (57, 63), + (58, 70), + (58, 122), + (58, 214), + (59, 13), + (59, 22), + (59, 72), + (59, 87), + (59, 128), + (59, 184), + (60, 59), + (62, 44), + (62, 50), + (62, 113), + (62, 128), + (63, 9), + (63, 207), + (64, 54), + (64, 82), + (64, 112), + (64, 154), + (65, 59), + (65, 91), + (65, 119), + (65, 165), + (66, 65), + (66, 141), + (67, 57), + (67, 182), + (68, 11), + (68, 84), + (68, 95), + (68, 107), + (68, 134), + (68, 251), + (69, 42), + (69, 132), + (69, 174), + (70, 163), + (70, 178), + (70, 224), + (71, 143), + (71, 241), + (72, 32), + (72, 148), + (73, 35), + (73, 48), + (73, 164), + (74, 90), + (74, 134), + (75, 65), + (75, 174), + (75, 201), + (76, 83), + (76, 186), + (77, 8), + (77, 197), + (3, 48), + (3, 199), + (3, 230), + (3, 238), + (4, 27), + (4, 34), + (4, 51), + (4, 159), + (4, 195), + (5, 46), + (5, 156), + (5, 207), + (5, 242), + (6, 24), + (6, 87), + (6, 152), + (7, 44), + (7, 129), + (7, 161), + (8, 56), + (8, 163), + (8, 192), + (8, 198), + (8, 200), + (9, 26), + (9, 59), + (9, 169), + (9, 182), + (9, 214), + (9, 240), + (10, 57), + (10, 109), + (10, 235), + (11, 35), + (11, 55), + (11, 118), + (11, 137), + (11, 142), + (11, 171), + (11, 216), + (11, 224), + (11, 252), + (12, 1), + (12, 94), + (12, 194), + (13, 1), + (13, 22), + (13, 49), + (13, 53), + (13, 73), + (13, 80), + (13, 184), + (14, 146), + (14, 157), + (14, 209), + (15, 54), + (15, 84), + (15, 102), + (15, 218), + (16, 161), + (17, 115), + (17, 154), + (17, 235), + (17, 247), + (18, 161), + (18, 211), + (19, 1), + (19, 49), + (19, 51), + (19, 247), + (20, 211), + (21, 160), + (21, 198), + (22, 0), + (22, 21), + (22, 35), + (22, 42), + (23, 148), + (23, 209), + (23, 215), + (24, 20), + (24, 58), + (24, 64), + (24, 109), + (24, 252), + (25, 8), + (25, 45), + (25, 88), + (25, 173), + (25, 201), + (25, 221), + (25, 254), + (26, 37), + (26, 46), + (26, 112), + (26, 125), + (26, 192), + (27, 95), + (27, 115), + (28, 22), + (28, 78), + (28, 155), + (29, 172), + (29, 192), + (30, 223), + (31, 149), + (31, 243), + (32, 65), + (32, 236), + (33, 19), + (33, 76), + (33, 217), + (34, 28), + (34, 81), + (34, 119), + (34, 176), + (35, 64), + (35, 181), + (35, 223), + (36, 80), + (36, 135), + (36, 185), + (37, 123), + (37, 125), + (37, 199), + (37, 233), + (38, 9), + (38, 18), + (39, 31), + (39, 57), + (39, 78), + (39, 212), + (39, 229), + (40, 55), + (40, 180), + (41, 5), + (42, 180), + (42, 238), + (43, 71), + (43, 183), + (44, 98), + (44, 171), + (44, 192), + (45, 87), + (45, 91), + (45, 104), + (45, 147), + (45, 193), + (45, 255), + (46, 19), + (46, 93), + (46, 232), + (47, 49), + (47, 71), + (47, 187), + (47, 246), + (48, 64), + (48, 90), + (48, 235), + (50, 245), + (51, 11), + (51, 79), + (51, 92), + (51, 110), + (51, 153), + (51, 231), + (52, 39), + (52, 79), + (52, 92), + (52, 146), + (52, 230), + (53, 125), + (53, 164), + (53, 251), + (54, 15), + (54, 219), + (55, 0), + (55, 27), + (55, 211), + (55, 232), + (55, 238), + (56, 11), + (56, 38), + (56, 70), + (56, 87), + (56, 111), + (56, 119), + (56, 125), + (56, 156), + (56, 173), + (56, 177), + (57, 147), + (57, 205), + (57, 247), + (58, 84), + (58, 144), + (59, 43), + (59, 165), + (59, 187), + (60, 7), + (60, 25), + (60, 126), + (60, 134), + (60, 233), + (60, 235), + (61, 66), + (61, 255), + (62, 152), + (62, 231), + (63, 27), + (63, 80), + (63, 230), + (64, 63), + (64, 94), + (64, 114), + (64, 120), + (64, 171), + (65, 11), + (65, 78), + (65, 148), + (65, 173), + (65, 181), + (66, 177), + (67, 4), + (67, 167), + (67, 199), + (68, 173), + (69, 34), + (69, 127), + (69, 202), + (70, 125), + (70, 164), + (70, 192), + (71, 105), + (71, 113), + (71, 190), + (72, 71), + (72, 134), + (73, 103), + (73, 151), + (74, 68), + (74, 95), + (74, 184), + (75, 96), + (75, 103), + (75, 189), + (75, 225), + (77, 35), + (77, 75), + (77, 194), + (77, 224), + (77, 247), + (3, 111), + (3, 144), + (3, 205), + (3, 248), + (4, 14), + (4, 33), + (4, 54), + (4, 160), + (4, 198), + (4, 199), + (5, 106), + (5, 158), + (5, 175), + (5, 203), + (5, 228), + (5, 240), + (6, 5), + (6, 38), + (6, 55), + (6, 112), + (6, 137), + (6, 222), + (6, 252), + (7, 12), + (7, 28), + (7, 29), + (7, 130), + (7, 203), + (7, 226), + (8, 31), + (8, 55), + (8, 63), + (8, 120), + (8, 130), + (8, 133), + (8, 148), + (8, 180), + (8, 185), + (8, 186), + (8, 202), + (8, 248), + (9, 25), + (9, 52), + (9, 77), + (9, 126), + (9, 146), + (9, 185), + (10, 19), + (10, 244), + (11, 72), + (11, 75), + (11, 103), + (11, 108), + (11, 110), + (11, 113), + (11, 146), + (11, 197), + (12, 43), + (12, 66), + (12, 123), + (12, 174), + (12, 216), + (13, 0), + (13, 63), + (13, 107), + (14, 15), + (14, 17), + (14, 29), + (14, 35), + (14, 99), + (14, 174), + (14, 179), + (14, 203), + (15, 23), + (15, 58), + (15, 119), + (15, 149), + (15, 194), + (16, 58), + (16, 153), + (16, 187), + (16, 201), + (16, 242), + (16, 251), + (17, 17), + (17, 73), + (17, 74), + (17, 190), + (17, 245), + (18, 49), + (18, 173), + (18, 242), + (19, 52), + (19, 170), + (19, 182), + (20, 60), + (20, 107), + (20, 172), + (21, 230), + (22, 65), + (22, 172), + (23, 22), + (23, 58), + (23, 132), + (23, 203), + (23, 210), + (23, 227), + (23, 243), + (24, 122), + (24, 124), + (24, 138), + (24, 160), + (24, 231), + (24, 236), + (25, 55), + (25, 70), + (25, 72), + (25, 99), + (25, 124), + (25, 145), + (25, 196), + (25, 216), + (26, 86), + (26, 129), + (26, 134), + (26, 140), + (27, 6), + (27, 71), + (27, 173), + (28, 0), + (28, 46), + (28, 63), + (28, 89), + (29, 24), + (29, 50), + (29, 134), + (30, 85), + (30, 204), + (31, 126), + (31, 150), + (31, 201), + (32, 25), + (32, 26), + (32, 107), + (33, 44), + (33, 73), + (34, 213), + (34, 237), + (34, 242), + (35, 185), + (35, 202), + (36, 48), + (36, 224), + (37, 58), + (37, 116), + (37, 137), + (37, 154), + (37, 229), + (38, 13), + (38, 39), + (38, 106), + (38, 187), + (38, 190), + (38, 199), + (38, 234), + (38, 239), + (39, 86), + (39, 152), + (39, 207), + (40, 2), + (40, 54), + (40, 81), + (40, 100), + (40, 154), + (41, 6), + (41, 32), + (41, 86), + (41, 249), + (42, 75), + (42, 86), + (42, 103), + (42, 106), + (42, 195), + (42, 196), + (42, 198), + (42, 240), + (42, 252), + (43, 37), + (43, 105), + (43, 229), + (44, 99), + (44, 145), + (44, 166), + (45, 4), + (45, 7), + (45, 10), + (45, 24), + (45, 25), + (45, 135), + (46, 1), + (46, 42), + (46, 96), + (47, 74), + (47, 91), + (47, 112), + (47, 130), + (47, 135), + (47, 204), + (47, 249), + (48, 69), + (48, 168), + (49, 149), + (49, 159), + (49, 252), + (49, 253), + (50, 12), + (50, 80), + (50, 89), + (50, 214), + (51, 91), + (51, 217), + (52, 66), + (52, 71), + (52, 144), + (52, 148), + (52, 189), + (53, 3), + (53, 4), + (53, 5), + (53, 45), + (53, 101), + (53, 120), + (53, 147), + (53, 180), + (54, 14), + (54, 61), + (55, 114), + (55, 123), + (55, 210), + (56, 184), + (56, 195), + (56, 196), + (56, 232), + (57, 44), + (57, 78), + (57, 100), + (57, 118), + (57, 119), + (57, 164), + (57, 233), + (57, 241), + (58, 188), + (58, 212), + (58, 227), + (59, 37), + (59, 236), + (60, 42), + (60, 102), + (60, 152), + (60, 192), + (61, 42), + (61, 50), + (61, 52), + (61, 87), + (61, 118), + (61, 232), + (62, 134), + (62, 181), + (62, 235), + (63, 96), + (63, 151), + (63, 152), + (64, 7), + (64, 190), + (64, 223), + (65, 9), + (65, 50), + (65, 115), + (65, 215), + (66, 185), + (67, 21), + (67, 40), + (67, 116), + (67, 132), + (67, 208), + (67, 226), + (67, 250), + (68, 148), + (68, 149), + (69, 5), + (69, 37), + (69, 74), + (69, 233), + (70, 17), + (70, 80), + (70, 138), + (70, 161), + (70, 181), + (70, 216), + (71, 63), + (71, 120), + (71, 162), + (71, 169), + (71, 188), + (72, 38), + (72, 65), + (72, 102), + (72, 193), + (72, 220), + (72, 245), + (73, 27), + (73, 64), + (73, 79), + (73, 94), + (73, 130), + (73, 169), + (73, 201), + (73, 218), + (74, 39), + (74, 62), + (74, 86), + (74, 88), + (75, 61), + (75, 179), + (75, 180), + (75, 245), + (75, 252), + (76, 72), + (76, 103), + (76, 120), + (76, 151), + (3, 70), + (3, 71), + (3, 75), + (3, 80), + (3, 112), + (3, 171), + (3, 211), + (3, 242), + (3, 252), + (4, 10), + (4, 16), + (4, 32), + (4, 60), + (4, 61), + (4, 94), + (4, 109), + (4, 122), + (4, 132), + (4, 215), + (4, 228), + (5, 1), + (5, 51), + (5, 55), + (5, 88), + (5, 96), + (5, 122), + (5, 129), + (5, 192), + (5, 195), + (5, 199), + (5, 253), + (6, 32), + (6, 62), + (6, 70), + (6, 78), + (6, 89), + (6, 94), + (6, 102), + (6, 118), + (6, 140), + (6, 145), + (6, 163), + (6, 209), + (6, 233), + (6, 235), + (7, 3), + (7, 7), + (7, 22), + (7, 120), + (7, 138), + (7, 153), + (7, 154), + (7, 155), + (7, 176), + (7, 177), + (7, 202), + (7, 234), + (8, 29), + (8, 41), + (8, 98), + (8, 107), + (8, 110), + (8, 170), + (9, 6), + (9, 10), + (9, 64), + (9, 79), + (9, 87), + (9, 92), + (9, 96), + (9, 113), + (9, 139), + (9, 156), + (9, 162), + (9, 167), + (9, 189), + (9, 200), + (9, 217), + (10, 26), + (10, 54), + (10, 103), + (10, 107), + (10, 148), + (10, 166), + (10, 183), + (10, 247), + (10, 255), + (11, 2), + (11, 144), + (11, 159), + (11, 177), + (11, 199), + (11, 236), + (11, 253), + (11, 255), + (12, 72), + (12, 145), + (12, 198), + (12, 217), + (12, 235), + (13, 35), + (13, 39), + (13, 56), + (13, 69), + (13, 86), + (13, 136), + (13, 175), + (13, 237), + (13, 246), + (13, 248), + (14, 14), + (14, 25), + (14, 30), + (14, 61), + (14, 134), + (14, 139), + (14, 148), + (14, 173), + (14, 198), + (14, 241), + (15, 4), + (15, 13), + (15, 19), + (15, 52), + (15, 70), + (15, 76), + (15, 140), + (15, 147), + (15, 152), + (15, 209), + (15, 228), + (16, 13), + (16, 87), + (16, 130), + (16, 135), + (16, 165), + (16, 180), + (16, 230), + (17, 104), + (17, 206), + (17, 217), + (17, 243), + (18, 141), + (19, 156), + (20, 47), + (20, 56), + (20, 57), + (20, 66), + (20, 125), + (20, 142), + (20, 227), + (20, 230), + (21, 36), + (21, 84), + (21, 177), + (21, 201), + (21, 207), + (21, 237), + (22, 79), + (22, 106), + (22, 136), + (22, 179), + (22, 234), + (23, 88), + (23, 91), + (23, 99), + (23, 109), + (23, 126), + (23, 165), + (23, 174), + (23, 201), + (23, 238), + (24, 27), + (24, 69), + (24, 112), + (24, 118), + (24, 144), + (24, 147), + (24, 152), + (24, 172), + (24, 184), + (24, 189), + (24, 254), + (25, 9), + (25, 10), + (25, 32), + (25, 33), + (25, 50), + (25, 63), + (25, 71), + (25, 93), + (25, 177), + (25, 180), + (25, 183), + (25, 190), + (25, 224), + (25, 241), + (26, 8), + (26, 136), + (26, 195), + (26, 228), + (27, 39), + (27, 97), + (27, 107), + (27, 181), + (27, 220), + (27, 244), + (28, 173), + (28, 194), + (28, 214), + (29, 17), + (29, 164), + (29, 232), + (30, 77), + (30, 132), + (30, 176), + (30, 193), + (30, 208), + (30, 209), + (30, 216), + (31, 16), + (31, 47), + (31, 152), + (31, 219), + (31, 237), + (31, 253), + (32, 14), + (32, 100), + (32, 125), + (32, 235), + (33, 45), + (33, 63), + (33, 122), + (33, 130), + (33, 151), + (33, 199), + (34, 21), + (34, 113), + (34, 146), + (35, 25), + (35, 49), + (35, 74), + (35, 99), + (35, 121), + (35, 203), + (35, 240), + (36, 51), + (36, 54), + (36, 57), + (36, 123), + (36, 136), + (36, 139), + (37, 12), + (37, 117), + (37, 131), + (37, 160), + (37, 187), + (38, 50), + (38, 59), + (38, 98), + (38, 149), + (38, 216), + (38, 241), + (39, 27), + (39, 64), + (39, 105), + (40, 26), + (40, 34), + (40, 62), + (40, 104), + (41, 27), + (41, 81), + (41, 155), + (41, 230), + (41, 240), + (42, 78), + (42, 97), + (42, 117), + (42, 194), + (42, 202), + (42, 218), + (42, 219), + (42, 246), + (42, 250), + (42, 253), + (43, 40), + (43, 43), + (43, 60), + (43, 81), + (43, 131), + (43, 182), + (44, 97), + (44, 175), + (44, 189), + (44, 197), + (44, 202), + (44, 203), + (44, 205), + (44, 238), + (44, 239), + (45, 13), + (45, 20), + (45, 29), + (45, 79), + (45, 101), + (45, 161), + (45, 220), + (45, 241), + (46, 15), + (46, 205), + (46, 218), + (46, 219), + (46, 228), + (47, 167), + (47, 215), + (47, 217), + (47, 244), + (48, 24), + (48, 43), + (48, 78), + (48, 93), + (48, 102), + (48, 103), + (48, 143), + (48, 167), + (48, 197), + (48, 198), + (48, 204), + (48, 245), + (48, 255), + (49, 19), + (49, 34), + (49, 73), + (49, 115), + (49, 235), + (49, 247), + (50, 40), + (50, 63), + (50, 104), + (50, 198), + (50, 231), + (51, 30), + (51, 47), + (51, 84), + (51, 141), + (51, 145), + (51, 213), + (52, 26), + (52, 58), + (52, 84), + (52, 151), + (52, 193), + (52, 196), + (52, 209), + (52, 236), + (52, 241), + (52, 247), + (53, 77), + (53, 132), + (53, 148), + (53, 226), + (54, 17), + (54, 41), + (54, 47), + (54, 93), + (54, 188), + (54, 192), + (54, 251), + (54, 254), + (54, 255), + (55, 115), + (55, 213), + (56, 21), + (56, 59), + (56, 60), + (56, 81), + (56, 118), + (57, 106), + (57, 117), + (57, 120), + (57, 139), + (57, 212), + (58, 25), + (58, 28), + (58, 128), + (59, 129), + (59, 201), + (59, 248), + (60, 95), + (60, 202), + (61, 173), + (61, 218), + (61, 245), + (62, 88), + (62, 142), + (62, 210), + (63, 0), + (63, 39), + (63, 174), + (63, 232), + (64, 0), + (64, 37), + (64, 72), + (64, 101), + (64, 133), + (64, 141), + (64, 212), + (64, 229), + (64, 230), + (65, 17), + (65, 55), + (65, 253), + (66, 8), + (66, 9), + (66, 35), + (66, 106), + (66, 127), + (66, 181), + (66, 184), + (66, 221), + (66, 228), + (66, 237), + (67, 61), + (67, 127), + (67, 151), + (67, 197), + (68, 0), + (68, 14), + (68, 114), + (68, 131), + (68, 153), + (68, 170), + (68, 177), + (68, 197), + (68, 226), + (68, 229), + (68, 244), + (69, 234), + (69, 255), + (70, 52), + (70, 60), + (70, 81), + (70, 151), + (70, 211), + (70, 226), + (71, 17), + (71, 20), + (71, 154), + (71, 160), + (72, 70), + (72, 131), + (72, 144), + (72, 156), + (72, 248), + (72, 251), + (73, 5), + (73, 32), + (73, 72), + (73, 102), + (73, 152), + (73, 170), + (73, 178), + (73, 181), + (73, 190), + (73, 251), + (74, 16), + (74, 46), + (74, 85), + (74, 101), + (74, 126), + (74, 203), + (74, 212), + (75, 13), + (75, 24), + (75, 43), + (75, 60), + (75, 75), + (75, 186), + (75, 197), + (75, 207), + (76, 34), + (76, 88), + (76, 137), + (76, 183), + (76, 193), + (76, 201), + (76, 212), + (76, 247), + (76, 253), + (77, 2), + (77, 46), + (77, 57), + (77, 70), + (77, 123), + (77, 173), + (77, 221), + (77, 233), + (77, 246), + (3, 17), + (3, 35), + (3, 42), + (3, 82), + (3, 104), + (3, 154), + (3, 157), + (3, 169), + (3, 235), + (4, 9), + (4, 23), + (4, 25), + (4, 30), + (4, 39), + (4, 49), + (4, 83), + (4, 98), + (4, 103), + (4, 105), + (4, 110), + (4, 124), + (4, 125), + (4, 157), + (4, 168), + (4, 192), + (4, 204), + (5, 32), + (5, 37), + (5, 38), + (5, 39), + (5, 45), + (5, 87), + (5, 115), + (5, 135), + (5, 222), + (5, 223), + (6, 15), + (6, 18), + (6, 21), + (6, 45), + (6, 74), + (6, 122), + (6, 141), + (6, 168), + (6, 201), + (6, 240), + (6, 244), + (7, 8), + (7, 15), + (7, 36), + (7, 37), + (7, 62), + (7, 83), + (7, 88), + (7, 107), + (7, 113), + (7, 118), + (7, 124), + (7, 127), + (7, 139), + (7, 141), + (7, 160), + (7, 181), + (7, 205), + (7, 231), + (7, 237), + (7, 244), + (8, 3), + (8, 14), + (8, 23), + (8, 48), + (8, 83), + (8, 91), + (8, 111), + (8, 123), + (8, 187), + (8, 223), + (9, 0), + (9, 40), + (9, 61), + (9, 66), + (9, 73), + (9, 75), + (9, 80), + (9, 91), + (9, 125), + (9, 175), + (9, 190), + (9, 239), + (9, 243), + (10, 2), + (10, 21), + (10, 46), + (10, 78), + (10, 117), + (10, 135), + (10, 174), + (10, 185), + (10, 189), + (10, 195), + (10, 250), + (11, 9), + (11, 17), + (11, 65), + (11, 76), + (11, 78), + (11, 86), + (11, 93), + (11, 94), + (11, 95), + (11, 100), + (11, 107), + (11, 153), + (11, 226), + (12, 80), + (12, 157), + (12, 221), + (12, 232), + (12, 245), + (13, 10), + (13, 44), + (13, 45), + (13, 47), + (13, 61), + (13, 68), + (13, 78), + (13, 173), + (13, 179), + (13, 186), + (13, 209), + (13, 218), + (14, 113), + (14, 153), + (14, 172), + (14, 207), + (14, 210), + (14, 223), + (14, 224), + (15, 1), + (15, 38), + (15, 45), + (15, 48), + (15, 171), + (15, 221), + (15, 230), + (15, 233), + (16, 65), + (16, 76), + (16, 78), + (16, 93), + (16, 109), + (16, 115), + (16, 140), + (16, 151), + (16, 239), + (16, 253), + (17, 25), + (17, 67), + (17, 172), + (17, 232), + (18, 28), + (18, 194), + (20, 6), + (20, 7), + (21, 33), + (21, 102), + (21, 120), + (21, 169), + (21, 223), + (21, 239), + (21, 240), + (22, 44), + (22, 59), + (22, 81), + (22, 89), + (22, 111), + (22, 160), + (22, 184), + (22, 228), + (23, 19), + (23, 20), + (23, 21), + (23, 32), + (23, 35), + (23, 64), + (23, 107), + (23, 108), + (23, 145), + (23, 180), + (23, 204), + (24, 0), + (24, 4), + (24, 10), + (24, 39), + (24, 62), + (24, 72), + (24, 76), + (24, 87), + (24, 96), + (24, 99), + (24, 163), + (24, 171), + (24, 191), + (24, 219), + (24, 240), + (25, 21), + (25, 181), + (25, 236), + (25, 251), + (26, 105), + (26, 230), + (26, 248), + (27, 87), + (27, 152), + (27, 157), + (27, 182), + (27, 195), + (27, 209), + (27, 218), + (27, 254), + (28, 50), + (28, 157), + (28, 164), + (28, 188), + (28, 195), + (28, 198), + (28, 236), + (29, 28), + (29, 38), + (29, 42), + (29, 60), + (29, 104), + (29, 106), + (29, 121), + (29, 126), + (29, 128), + (29, 149), + (29, 185), + (29, 200), + (29, 211), + (30, 31), + (30, 71), + (30, 172), + (30, 224), + (30, 229), + (31, 48), + (31, 102), + (31, 155), + (31, 195), + (31, 206), + (31, 227), + (31, 250), + (32, 127), + (32, 218), + (32, 238), + (32, 247), + (32, 253), + (33, 20), + (33, 22), + (33, 48), + (33, 170), + (33, 188), + (34, 44), + (34, 105), + (34, 131), + (34, 150), + (34, 235), + (35, 44), + (35, 130), + (36, 28), + (36, 36), + (36, 124), + (36, 222), + (36, 232), + (37, 13), + (37, 16), + (37, 72), + (37, 84), + (37, 106), + (37, 150), + (37, 231), + (37, 242), + (38, 5), + (38, 11), + (38, 132), + (39, 8), + (39, 10), + (39, 39), + (39, 54), + (39, 63), + (39, 77), + (39, 121), + (39, 176), + (39, 237), + (40, 6), + (40, 42), + (40, 116), + (40, 193), + (40, 214), + (40, 234), + (41, 47), + (41, 69), + (41, 89), + (41, 99), + (41, 102), + (41, 117), + (41, 119), + (41, 180), + (41, 191), + (41, 226), + (41, 229), + (42, 118), + (42, 143), + (42, 161), + (42, 188), + (42, 220), + (42, 223), + (43, 49), + (43, 92), + (43, 149), + (43, 160), + (43, 231), + (44, 3), + (44, 6), + (44, 73), + (44, 80), + (44, 105), + (44, 125), + (44, 178), + (44, 237), + (45, 30), + (45, 56), + (45, 62), + (45, 107), + (45, 126), + (45, 195), + (45, 211), + (45, 248), + (46, 4), + (46, 9), + (46, 45), + (46, 71), + (46, 79), + (46, 184), + (46, 208), + (46, 216), + (46, 229), + (47, 63), + (47, 70), + (47, 170), + (47, 212), + (47, 228), + (47, 247), + (48, 57), + (48, 82), + (48, 94), + (48, 124), + (48, 174), + (48, 217), + (48, 226), + (48, 244), + (49, 6), + (49, 120), + (49, 167), + (50, 0), + (50, 37), + (50, 68), + (50, 121), + (50, 182), + (51, 53), + (51, 67), + (51, 100), + (51, 172), + (51, 237), + (51, 247), + (51, 252), + (51, 253), + (52, 0), + (52, 17), + (52, 44), + (52, 168), + (52, 183), + (52, 198), + (53, 64), + (53, 115), + (53, 151), + (53, 152), + (53, 157), + (53, 165), + (53, 189), + (53, 202), + (54, 3), + (54, 84), + (54, 142), + (54, 156), + (54, 161), + (54, 200), + (54, 206), + (54, 225), + (55, 14), + (55, 29), + (55, 83), + (55, 93), + (55, 153), + (55, 215), + (55, 235), + (56, 36), + (56, 40), + (56, 140), + (56, 145), + (56, 160), + (56, 185), + (56, 224), + (56, 231), + (57, 1), + (57, 35), + (57, 128), + (57, 165), + (57, 192), + (57, 214), + (57, 221), + (58, 16), + (58, 37), + (58, 65), + (58, 77), + (58, 85), + (58, 119), + (58, 183), + (58, 234), + (58, 242), + (59, 59), + (59, 250), + (60, 11), + (60, 66), + (60, 73), + (60, 111), + (60, 112), + (60, 115), + (61, 60), + (61, 80), + (61, 130), + (61, 149), + (61, 162), + (61, 194), + (61, 211), + (61, 212), + (61, 219), + (61, 254), + (62, 73), + (62, 126), + (62, 127), + (62, 219), + (63, 3), + (63, 16), + (63, 20), + (63, 21), + (63, 25), + (63, 72), + (63, 130), + (63, 215), + (63, 228), + (63, 231), + (63, 237), + (64, 17), + (64, 18), + (64, 162), + (64, 245), + (65, 53), + (65, 112), + (65, 125), + (65, 184), + (65, 189), + (65, 190), + (65, 254), + (66, 15), + (66, 24), + (66, 48), + (66, 53), + (66, 162), + (66, 180), + (66, 238), + (68, 3), + (68, 12), + (68, 37), + (68, 167), + (68, 207), + (68, 243), + (69, 0), + (69, 11), + (69, 15), + (69, 99), + (69, 134), + (69, 153), + (69, 223), + (70, 33), + (70, 40), + (70, 45), + (70, 73), + (70, 90), + (70, 109), + (70, 176), + (71, 18), + (71, 46), + (71, 176), + (71, 179), + (71, 215), + (71, 249), + (72, 4), + (72, 142), + (72, 195), + (72, 201), + (72, 205), + (73, 29), + (73, 37), + (73, 74), + (73, 123), + (73, 162), + (73, 196), + (74, 1), + (74, 37), + (74, 110), + (75, 8), + (75, 50), + (75, 78), + (75, 107), + (75, 136), + (75, 153), + (75, 158), + (75, 177), + (75, 221), + (75, 234), + (75, 242), + (76, 1), + (76, 4), + (76, 30), + (76, 32), + (76, 140), + (76, 196), + (76, 211), + (76, 230), + (76, 239), + (76, 243), + (76, 245), + (77, 7), + (77, 16), + (77, 38), + (77, 85), + (77, 88), + (77, 126), + (77, 207), + (77, 210), + (77, 237), + (3, 19), + (3, 61), + (3, 62), + (3, 68), + (3, 98), + (3, 118), + (3, 137), + (3, 153), + (3, 166), + (3, 167), + (3, 179), + (3, 180), + (3, 184), + (3, 186), + (3, 192), + (3, 204), + (3, 206), + (3, 237), + (3, 241), + (4, 11), + (4, 21), + (4, 56), + (4, 67), + (4, 95), + (4, 129), + (4, 150), + (4, 156), + (4, 173), + (4, 184), + (4, 191), + (4, 203), + (4, 221), + (4, 237), + (5, 17), + (5, 20), + (5, 33), + (5, 34), + (5, 41), + (5, 68), + (5, 71), + (5, 74), + (5, 79), + (5, 83), + (5, 91), + (5, 98), + (5, 110), + (5, 119), + (5, 123), + (5, 127), + (5, 133), + (5, 149), + (5, 166), + (5, 171), + (5, 181), + (5, 214), + (5, 225), + (5, 229), + (5, 237), + (6, 4), + (6, 14), + (6, 42), + (6, 60), + (6, 66), + (6, 68), + (6, 98), + (6, 101), + (6, 107), + (6, 111), + (6, 114), + (6, 142), + (6, 200), + (6, 216), + (6, 220), + (6, 232), + (6, 241), + (6, 253), + (7, 2), + (7, 39), + (7, 51), + (7, 54), + (7, 64), + (7, 66), + (7, 74), + (7, 98), + (7, 122), + (7, 142), + (7, 167), + (7, 189), + (7, 192), + (7, 204), + (7, 211), + (7, 216), + (7, 227), + (7, 238), + (7, 246), + (8, 44), + (8, 68), + (8, 69), + (8, 74), + (8, 94), + (8, 121), + (8, 137), + (8, 167), + (8, 175), + (8, 179), + (8, 194), + (8, 204), + (8, 212), + (9, 18), + (9, 35), + (9, 56), + (9, 67), + (9, 85), + (9, 104), + (9, 144), + (9, 163), + (9, 180), + (9, 184), + (9, 192), + (9, 194), + (9, 220), + (9, 225), + (9, 241), + (10, 5), + (10, 9), + (10, 23), + (10, 44), + (10, 48), + (10, 59), + (10, 81), + (10, 85), + (10, 96), + (10, 100), + (10, 147), + (10, 201), + (10, 237), + (10, 238), + (10, 242), + (11, 1), + (11, 36), + (11, 50), + (11, 52), + (11, 81), + (11, 121), + (11, 122), + (11, 125), + (11, 149), + (11, 151), + (11, 194), + (11, 203), + (11, 217), + (12, 7), + (12, 75), + (12, 77), + (12, 110), + (12, 149), + (12, 158), + (12, 160), + (12, 167), + (12, 182), + (12, 195), + (12, 253), + (13, 15), + (13, 38), + (13, 55), + (13, 110), + (13, 113), + (13, 123), + (13, 133), + (13, 137), + (13, 159), + (13, 164), + (13, 177), + (13, 202), + (13, 222), + (14, 91), + (14, 108), + (14, 132), + (14, 143), + (14, 160), + (14, 164), + (14, 184), + (14, 197), + (14, 202), + (14, 214), + (14, 232), + (14, 239), + (15, 77), + (15, 95), + (15, 150), + (15, 178), + (16, 5), + (16, 22), + (16, 68), + (16, 95), + (16, 110), + (16, 184), + (16, 192), + (16, 208), + (17, 38), + (17, 108), + (17, 213), + (17, 237), + (18, 57), + (18, 67), + (18, 115), + (18, 174), + (18, 202), + (19, 76), + (19, 244), + (20, 41), + (20, 207), + (20, 233), + (21, 18), + (21, 31), + (21, 48), + (21, 92), + (21, 134), + (21, 143), + (21, 148), + (21, 162), + (21, 187), + (22, 47), + (22, 60), + (22, 93), + (22, 114), + (22, 150), + (22, 189), + (23, 38), + (23, 50), + (23, 92), + (23, 131), + (23, 153), + (23, 177), + (23, 192), + (23, 199), + (23, 218), + (23, 221), + (23, 237), + (24, 9), + (24, 29), + (24, 86), + (24, 108), + (24, 137), + (24, 156), + (24, 188), + (25, 34), + (25, 47), + (25, 65), + (25, 75), + (25, 78), + (25, 109), + (25, 142), + (25, 157), + (25, 215), + (25, 229), + (26, 11), + (26, 18), + (26, 27), + (26, 73), + (26, 83), + (26, 93), + (26, 103), + (26, 109), + (26, 111), + (26, 113), + (26, 115), + (26, 184), + (26, 211), + (26, 231), + (27, 8), + (27, 28), + (27, 32), + (27, 50), + (27, 63), + (27, 94), + (27, 99), + (27, 137), + (27, 144), + (27, 158), + (27, 159), + (27, 170), + (27, 191), + (27, 225), + (27, 237), + (27, 241), + (28, 31), + (28, 48), + (28, 49), + (28, 51), + (28, 75), + (28, 146), + (28, 158), + (28, 162), + (28, 169), + (28, 184), + (28, 224), + (28, 241), + (29, 37), + (29, 53), + (29, 91), + (29, 103), + (29, 116), + (29, 151), + (29, 206), + (29, 240), + (29, 246), + (30, 133), + (30, 147), + (30, 200), + (30, 219), + (30, 221), + (31, 20), + (31, 69), + (31, 96), + (31, 135), + (31, 220), + (32, 2), + (32, 20), + (32, 52), + (32, 205), + (32, 219), + (33, 92), + (33, 235), + (33, 252), + (33, 254), + (34, 0), + (34, 69), + (34, 137), + (34, 193), + (34, 210), + (35, 2), + (35, 8), + (35, 88), + (35, 91), + (35, 113), + (35, 127), + (35, 147), + (35, 180), + (35, 209), + (35, 210), + (35, 246), + (36, 16), + (36, 58), + (36, 130), + (36, 153), + (36, 183), + (36, 194), + (36, 249), + (37, 34), + (37, 56), + (37, 177), + (37, 185), + (37, 208), + (38, 170), + (38, 200), + (38, 235), + (38, 247), + (39, 11), + (39, 12), + (39, 61), + (39, 102), + (39, 104), + (39, 117), + (39, 198), + (39, 216), + (39, 236), + (40, 14), + (40, 20), + (40, 48), + (40, 69), + (40, 95), + (40, 98), + (40, 141), + (40, 156), + (40, 194), + (40, 209), + (41, 13), + (41, 20), + (41, 26), + (41, 33), + (41, 120), + (41, 209), + (41, 231), + (41, 236), + (42, 13), + (42, 29), + (42, 32), + (42, 69), + (42, 99), + (42, 156), + (42, 160), + (42, 193), + (42, 254), + (43, 30), + (43, 32), + (43, 132), + (43, 179), + (43, 184), + (43, 203), + (43, 204), + (43, 226), + (44, 16), + (44, 52), + (44, 65), + (44, 79), + (44, 118), + (44, 185), + (44, 201), + (44, 207), + (44, 213), + (44, 240), + (44, 244), + (45, 11), + (45, 22), + (45, 37), + (45, 45), + (45, 96), + (45, 106), + (45, 110), + (45, 166), + (45, 174), + (45, 175), + (45, 232), + (46, 46), + (46, 65), + (46, 73), + (46, 104), + (46, 142), + (46, 169), + (46, 192), + (46, 203), + (46, 241), + (46, 252), + (47, 68), + (47, 97), + (47, 132), + (47, 163), + (47, 173), + (47, 185), + (47, 193), + (47, 211), + (47, 253), + (48, 70), + (48, 116), + (48, 214), + (48, 224), + (48, 234), + (49, 11), + (49, 29), + (49, 54), + (49, 63), + (49, 80), + (49, 108), + (49, 131), + (49, 132), + (49, 136), + (49, 138), + (49, 156), + (49, 187), + (49, 240), + (50, 6), + (50, 92), + (50, 98), + (50, 136), + (50, 191), + (51, 15), + (51, 41), + (51, 42), + (51, 78), + (51, 89), + (51, 93), + (51, 140), + (51, 146), + (51, 220), + (51, 241), + (52, 11), + (52, 123), + (52, 173), + (52, 176), + (52, 192), + (52, 194), + (52, 199), + (52, 201), + (52, 255), + (53, 9), + (53, 10), + (68, 124), + (51, 108), + (17, 22), + (38, 196), + (56, 74), + (56, 150), + (25, 84), + (42, 197), + (52, 224), + (51, 202), + (67, 223), + (68, 215), + (45, 212), + (51, 44), + (29, 167), + (30, 67), + (34, 251), + (64, 45), + (65, 211), + (72, 11), + (47, 200), + (48, 145), + (24, 216), + (50, 194), + (77, 202), + (10, 47), + (29, 65), + (46, 23), + (66, 205), + (10, 55), + (54, 48), + (55, 61), + (61, 20), + (75, 126), + (16, 156), + (28, 94), + (48, 107), + (55, 98), + (67, 168), + (68, 250), + (70, 171), + (71, 225), + (26, 81), + (31, 154), + (49, 111), + (56, 89), + (58, 61), + (64, 125), + (68, 29), + (26, 242), + (39, 60), + (50, 30), + (56, 134), + (57, 28), + (62, 76), + (62, 98), + (71, 95), + (14, 13), + (16, 8), + (29, 62), + (31, 246), + (33, 247), + (37, 50), + (39, 62), + (41, 160), + (42, 22), + (42, 235), + (43, 54), + (43, 116), + (45, 158), + (48, 218), + (55, 101), + (69, 83), + (74, 33), + (21, 66), + (30, 6), + (36, 177), + (36, 248), + (37, 145), + (39, 7), + (40, 80), + (40, 105), + (44, 69), + (49, 213), + (49, 228), + (58, 225), + (59, 18), + (64, 169), + (9, 176), + (12, 4), + (16, 174), + (19, 25), + (19, 95), + (26, 212), + (28, 26), + (34, 12), + (39, 103), + (43, 172), + (47, 66), + (47, 146), + (49, 119), + (51, 31), + (52, 206), + (52, 235), + (53, 191), + (59, 106), + (61, 13), + (63, 201), + (65, 160), + (65, 170), + (76, 35), + (14, 80), + (14, 138), + (14, 212), + (15, 162), + (16, 84), + (16, 117), + (16, 164), + (26, 236), + (28, 200), + (29, 89), + (30, 164), + (30, 227), + (37, 214), + (37, 244), + (38, 91), + (40, 120), + (50, 87), + (56, 42), + (57, 189), + (62, 16), + (66, 163), + (69, 98), + (74, 56), + (8, 162), + (11, 179), + (18, 84), + (18, 191), + (18, 238), + (20, 251), + (22, 161), + (25, 59), + (28, 133), + (29, 194), + (38, 49), + (40, 169), + (43, 4), + (46, 125), + (55, 31), + (55, 57), + (55, 247), + (59, 96), + (61, 105), + (62, 31), + (63, 156), + (70, 124), + (74, 107), + (77, 225), + (8, 158), + (17, 29), + (21, 203), + (22, 129), + (31, 24), + (32, 112), + (34, 164), + (35, 117), + (44, 247), + (49, 117), + (54, 129), + (54, 203), + (57, 170), + (59, 146), + (61, 145), + (62, 64), + (64, 117), + (66, 97), + (67, 17), + (70, 18), + (70, 152), + (75, 249), + (5, 155), + (8, 102), + (10, 7), + (17, 249), + (18, 97), + (20, 220), + (21, 103), + (21, 145), + (22, 206), + (25, 116), + (26, 56), + (28, 250), + (31, 186), + (32, 102), + (36, 223), + (38, 243), + (46, 48), + (48, 0), + (53, 87), + (54, 211), + (55, 13), + (57, 193), + (58, 179), + (60, 191), + (62, 255), + (64, 107), + (65, 14), + (65, 183), + (68, 160), + (69, 9), + (72, 164), + (74, 186), + (75, 240), + (76, 124), + (76, 166), + (76, 204), + (77, 93), + (7, 140), + (15, 24), + (20, 249), + (22, 57), + (23, 62), + (24, 197), + (25, 36), + (25, 129), + (26, 168), + (31, 59), + (34, 183), + (37, 205), + (39, 95), + (40, 88), + (41, 145), + (43, 146), + (47, 6), + (47, 86), + (49, 96), + (52, 147), + (54, 100), + (57, 71), + (60, 103), + (60, 220), + (63, 65), + (63, 186), + (66, 121), + (67, 110), + (68, 163), + (70, 135), + (70, 188), + (71, 83), + (71, 161), + (73, 134), + (5, 61), + (7, 136), + (8, 191), + (10, 215), + (11, 104), + (15, 69), + (16, 181), + (16, 249), + (18, 213), + (18, 226), + (19, 4), + (19, 121), + (19, 179), + (20, 11), + (21, 7), + (23, 106), + (26, 89), + (30, 246), + (33, 206), + (34, 97), + (38, 175), + (41, 105), + (41, 164), + (41, 212), + (43, 114), + (44, 37), + (44, 206), + (50, 39), + (50, 247), + (51, 130), + (51, 185), + (56, 2), + (57, 49), + (57, 183), + (58, 163), + (59, 105), + (59, 254), + (63, 221), + (65, 249), + (66, 46), + (66, 58), + (66, 244), + (67, 27), + (69, 23), + (69, 41), + (69, 76), + (70, 4), + (71, 223), + (72, 137), + (73, 221), + (74, 42), + (74, 182), + (75, 238), + (76, 246), + (77, 182), + (3, 7), + (4, 65), + (4, 187), + (8, 141), + (9, 99), + (15, 254), + (17, 71), + (19, 112), + (22, 39), + (22, 97), + (23, 124), + (23, 182), + (24, 88), + (26, 120), + (26, 124), + (28, 191), + (29, 14), + (29, 25), + (29, 227), + (31, 196), + (36, 84), + (39, 65), + (39, 191), + (40, 175), + (42, 1), + (46, 52), + (51, 138), + (51, 195), + (52, 90), + (52, 238), + (53, 31), + (56, 90), + (56, 241), + (57, 73), + (57, 107), + (58, 52), + (58, 200), + (59, 10), + (60, 4), + (60, 12), + (60, 53), + (60, 255), + (62, 193), + (63, 63), + (64, 61), + (64, 98), + (65, 60), + (67, 25), + (67, 59), + (70, 179), + (71, 178), + (72, 177), + (73, 124), + (73, 157), + (73, 195), + (74, 27), + (75, 71), + (75, 114), + (76, 255), + (77, 21), + (77, 151), + (77, 196), + (77, 222), + (3, 34), + (3, 95), + (4, 222), + (5, 126), + (5, 176), + (5, 220), + (7, 0), + (10, 14), + (11, 131), + (12, 181), + (13, 128), + (13, 247), + (16, 82), + (17, 43), + (17, 122), + (19, 208), + (19, 220), + (20, 174), + (21, 55), + (22, 187), + (23, 94), + (25, 52), + (27, 248), + (28, 93), + (29, 30), + (30, 89), + (31, 2), + (32, 109), + (33, 118), + (34, 140), + (35, 187), + (38, 15), + (41, 14), + (41, 245), + (42, 177), + (44, 153), + (46, 56), + (47, 241), + (48, 139), + (48, 205), + (50, 177), + (54, 90), + (55, 119), + (59, 140), + (59, 141), + (60, 174), + (61, 166), + (63, 102), + (63, 209), + (64, 47), + (65, 20), + (65, 187), + (66, 5), + (67, 163), + (70, 237), + (71, 73), + (71, 114), + (71, 127), + (71, 167), + (71, 192), + (72, 17), + (72, 219), + (74, 99), + (74, 141), + (75, 38), + (76, 43), + (77, 50), + (77, 168), + (3, 3), + (4, 66), + (4, 99), + (4, 142), + (5, 47), + (5, 69), + (5, 148), + (7, 24), + (8, 214), + (10, 152), + (11, 124), + (11, 210), + (11, 214), + (11, 234), + (12, 105), + (12, 251), + (13, 145), + (13, 244), + (14, 236), + (15, 17), + (15, 18), + (15, 101), + (16, 92), + (18, 251), + (19, 58), + (19, 196), + (20, 19), + (20, 236), + (21, 111), + (24, 177), + (25, 167), + (26, 85), + (27, 12), + (27, 142), + (28, 215), + (29, 223), + (30, 190), + (31, 26), + (31, 58), + (31, 148), + (31, 151), + (35, 105), + (35, 184), + (37, 130), + (38, 75), + (38, 229), + (42, 191), + (43, 97), + (43, 193), + (44, 117), + (45, 44), + (46, 31), + (46, 113), + (46, 133), + (46, 189), + (47, 15), + (47, 220), + (48, 16), + (48, 161), + (52, 83), + (54, 83), + (55, 193), + (55, 203), + (56, 194), + (58, 247), + (59, 28), + (59, 70), + (59, 206), + (60, 89), + (61, 235), + (62, 8), + (62, 51), + (62, 94), + (63, 28), + (63, 99), + (66, 104), + (66, 119), + (66, 190), + (66, 232), + (67, 158), + (67, 204), + (70, 248), + (71, 153), + (71, 248), + (73, 75), + (73, 217), + (75, 209), + (76, 28), + (77, 14), + (77, 231), + (3, 45), + (3, 133), + (4, 52), + (4, 63), + (4, 93), + (5, 8), + (6, 219), + (7, 49), + (8, 80), + (8, 101), + (8, 142), + (9, 70), + (9, 248), + (10, 51), + (10, 169), + (11, 12), + (11, 102), + (11, 191), + (12, 36), + (12, 49), + (13, 51), + (13, 183), + (14, 144), + (14, 166), + (14, 252), + (15, 118), + (18, 24), + (18, 185), + (20, 44), + (21, 107), + (22, 133), + (23, 33), + (24, 66), + (24, 228), + (24, 233), + (25, 148), + (26, 64), + (26, 176), + (27, 22), + (27, 40), + (27, 129), + (28, 148), + (29, 210), + (30, 218), + (31, 18), + (31, 190), + (32, 223), + (33, 141), + (34, 175), + (38, 35), + (39, 231), + (40, 25), + (40, 144), + (41, 94), + (41, 199), + (42, 87), + (42, 93), + (42, 127), + (43, 199), + (44, 156), + (44, 232), + (45, 74), + (46, 63), + (48, 26), + (48, 233), + (49, 26), + (49, 71), + (50, 69), + (50, 138), + (51, 26), + (51, 212), + (52, 49), + (53, 183), + (53, 188), + (54, 163), + (54, 229), + (56, 52), + (56, 124), + (56, 216), + (57, 88), + (57, 103), + (58, 92), + (58, 130), + (58, 151), + (58, 240), + (59, 178), + (60, 144), + (61, 157), + (62, 150), + (62, 165), + (62, 174), + (63, 238), + (64, 175), + (64, 200), + (65, 18), + (65, 31), + (66, 155), + (66, 182), + (67, 1), + (67, 216), + (67, 235), + (69, 10), + (69, 85), + (69, 196), + (70, 56), + (70, 183), + (70, 227), + (70, 241), + (72, 232), + (72, 249), + (73, 66), + (73, 70), + (73, 89), + (73, 197), + (74, 25), + (74, 30), + (74, 161), + (75, 3), + (75, 62), + (75, 85), + (75, 124), + (77, 63), + (77, 188), + (3, 27), + (3, 254), + (4, 79), + (4, 91), + (4, 123), + (4, 186), + (5, 7), + (5, 168), + (6, 23), + (6, 40), + (6, 246), + (6, 251), + (7, 163), + (7, 175), + (8, 87), + (8, 132), + (8, 253), + (9, 12), + (10, 56), + (10, 61), + (10, 184), + (10, 243), + (11, 61), + (11, 165), + (11, 209), + (12, 25), + (12, 62), + (12, 100), + (13, 17), + (13, 89), + (13, 121), + (13, 135), + (13, 143), + (13, 205), + (15, 11), + (16, 23), + (16, 123), + (16, 185), + (16, 190), + (17, 16), + (17, 20), + (17, 41), + (17, 198), + (18, 31), + (18, 210), + (19, 189), + (20, 65), + (21, 5), + (22, 125), + (23, 83), + (23, 120), + (24, 115), + (24, 185), + (25, 242), + (25, 255), + (26, 36), + (26, 247), + (27, 33), + (27, 109), + (27, 215), + (29, 43), + (29, 87), + (29, 217), + (30, 26), + (30, 201), + (31, 165), + (31, 181), + (33, 108), + (33, 244), + (34, 145), + (35, 16), + (35, 248), + (36, 68), + (36, 90), + (36, 134), + (39, 67), + (41, 10), + (41, 149), + (42, 168), + (43, 83), + (45, 215), + (46, 62), + (46, 105), + (46, 121), + (46, 160), + (46, 170), + (47, 159), + (47, 239), + (47, 248), + (48, 54), + (48, 189), + (48, 228), + (49, 24), + (49, 83), + (50, 23), + (50, 76), + (50, 101), + (51, 65), + (51, 160), + (52, 213), + (52, 253), + (53, 124), + (54, 155), + (55, 26), + (55, 32), + (55, 122), + (56, 17), + (57, 17), + (57, 72), + (57, 79), + (57, 176), + (57, 184), + (58, 140), + (58, 193), + (58, 207), + (59, 40), + (59, 176), + (60, 157), + (60, 201), + (60, 223), + (61, 55), + (61, 125), + (62, 104), + (63, 73), + (63, 176), + (64, 158), + (66, 54), + (66, 109), + (66, 255), + (67, 238), + (68, 85), + (68, 213), + (68, 238), + (69, 136), + (69, 142), + (69, 207), + (70, 20), + (70, 147), + (70, 230), + (71, 21), + (72, 7), + (73, 93), + (73, 212), + (75, 14), + (75, 229), + (75, 251), + (76, 114), + (76, 185), + (77, 49), + (77, 52), + (77, 127), + (77, 190), + (77, 219), + (77, 244), + (3, 0), + (3, 11), + (3, 101), + (3, 168), + (3, 189), + (3, 201), + (3, 212), + (3, 219), + (4, 81), + (4, 227), + (5, 50), + (5, 99), + (5, 205), + (5, 236), + (6, 10), + (6, 12), + (6, 48), + (6, 57), + (6, 75), + (6, 82), + (6, 83), + (6, 110), + (6, 181), + (7, 84), + (8, 11), + (8, 70), + (9, 137), + (9, 207), + (9, 229), + (10, 42), + (10, 230), + (11, 30), + (11, 44), + (11, 47), + (11, 115), + (11, 117), + (11, 119), + (12, 74), + (12, 132), + (13, 26), + (13, 99), + (13, 170), + (13, 228), + (13, 254), + (14, 60), + (14, 126), + (14, 171), + (15, 103), + (16, 112), + (17, 11), + (18, 199), + (19, 64), + (20, 8), + (20, 176), + (20, 186), + (21, 149), + (21, 184), + (21, 186), + (23, 81), + (23, 100), + (24, 43), + (24, 154), + (24, 206), + (25, 132), + (25, 158), + (26, 95), + (26, 117), + (26, 155), + (26, 167), + (27, 177), + (27, 198), + (27, 228), + (28, 39), + (29, 4), + (29, 71), + (31, 121), + (31, 131), + (31, 132), + (31, 228), + (32, 233), + (33, 97), + (33, 198), + (33, 253), + (34, 72), + (34, 189), + (35, 32), + (35, 65), + (35, 126), + (35, 179), + (36, 237), + (36, 239), + (36, 255), + (37, 200), + (38, 85), + (38, 250), + (39, 29), + (39, 154), + (41, 50), + (41, 141), + (41, 202), + (41, 211), + (41, 227), + (43, 224), + (44, 127), + (44, 233), + (45, 144), + (45, 165), + (45, 233), + (46, 172), + (46, 245), + (48, 63), + (48, 77), + (49, 35), + (49, 126), + (50, 21), + (51, 198), + (51, 223), + (52, 120), + (53, 24), + (53, 128), + (53, 231), + (53, 245), + (54, 97), + (54, 217), + (55, 159), + (55, 164), + (55, 236), + (56, 202), + (56, 249), + (57, 226), + (57, 231), + (57, 243), + (58, 213), + (59, 126), + (60, 36), + (60, 148), + (61, 63), + (61, 135), + (61, 253), + (62, 147), + (63, 226), + (64, 68), + (64, 102), + (64, 151), + (64, 195), + (64, 210), + (65, 106), + (65, 176), + (66, 168), + (67, 50), + (68, 76), + (68, 110), + (68, 247), + (69, 96), + (69, 146), + (69, 184), + (69, 229), + (70, 121), + (70, 134), + (72, 39), + (72, 88), + (72, 254), + (73, 28), + (73, 87), + (73, 122), + (74, 2), + (74, 23), + (74, 206), + (75, 132), + (76, 15), + (76, 78), + (76, 131), + (76, 158), + (76, 180), + (77, 79), + (77, 186), + (3, 29), + (3, 81), + (3, 85), + (3, 120), + (3, 131), + (3, 140), + (3, 163), + (4, 8), + (4, 133), + (4, 153), + (4, 169), + (4, 243), + (5, 29), + (5, 56), + (5, 85), + (5, 111), + (5, 183), + (5, 221), + (5, 243), + (6, 13), + (6, 20), + (6, 71), + (6, 119), + (6, 124), + (6, 136), + (6, 167), + (7, 5), + (7, 87), + (7, 89), + (7, 111), + (7, 133), + (7, 168), + (7, 243), + (7, 247), + (8, 0), + (8, 30), + (8, 35), + (8, 108), + (8, 242), + (9, 55), + (9, 129), + (9, 173), + (9, 195), + (9, 216), + (9, 237), + (9, 244), + (11, 8), + (11, 39), + (11, 46), + (11, 85), + (11, 133), + (11, 184), + (11, 204), + (11, 220), + (12, 71), + (13, 92), + (13, 156), + (13, 199), + (13, 203), + (14, 4), + (14, 5), + (14, 20), + (14, 92), + (15, 174), + (15, 183), + (16, 35), + (16, 66), + (17, 210), + (18, 14), + (18, 71), + (18, 77), + (18, 92), + (18, 114), + (19, 187), + (19, 188), + (19, 239), + (20, 84), + (20, 87), + (20, 127), + (20, 180), + (20, 240), + (22, 28), + (22, 78), + (22, 164), + (22, 180), + (23, 29), + (23, 140), + (23, 173), + (23, 181), + (23, 186), + (23, 193), + (23, 207), + (23, 211), + (23, 222), + (24, 18), + (24, 93), + (24, 94), + (24, 202), + (24, 213), + (24, 241), + (25, 16), + (25, 54), + (25, 67), + (25, 156), + (25, 160), + (25, 252), + (26, 30), + (26, 53), + (26, 137), + (27, 139), + (28, 10), + (30, 165), + (30, 182), + (30, 202), + (31, 11), + (31, 245), + (32, 66), + (32, 231), + (32, 249), + (35, 86), + (35, 174), + (36, 141), + (38, 64), + (39, 30), + (39, 37), + (40, 31), + (40, 37), + (40, 58), + (40, 74), + (40, 84), + (40, 124), + (40, 128), + (40, 135), + (40, 226), + (41, 7), + (41, 143), + (42, 77), + (43, 29), + (43, 41), + (43, 61), + (43, 202), + (44, 96), + (45, 69), + (45, 88), + (45, 132), + (45, 190), + (45, 229), + (45, 242), + (46, 80), + (46, 106), + (46, 230), + (46, 247), + (47, 12), + (47, 67), + (48, 35), + (48, 79), + (49, 143), + (49, 221), + (50, 20), + (50, 144), + (50, 153), + (50, 180), + (50, 242), + (51, 49), + (51, 250), + (52, 80), + (53, 142), + (56, 5), + (56, 226), + (57, 158), + (57, 175), + (57, 253), + (58, 78), + (58, 156), + (58, 235), + (59, 39), + (59, 83), + (59, 85), + (59, 186), + (59, 204), + (60, 132), + (60, 229), + (61, 75), + (62, 18), + (62, 35), + (62, 36), + (62, 45), + (62, 125), + (62, 187), + (63, 148), + (64, 9), + (65, 150), + (65, 168), + (65, 251), + (66, 25), + (66, 26), + (66, 92), + (66, 207), + (66, 209), + (67, 166), + (67, 254), + (68, 133), + (68, 198), + (69, 89), + (69, 139), + (69, 221), + (69, 246), + (70, 98), + (70, 235), + (70, 249), + (71, 10), + (71, 205), + (71, 217), + (72, 18), + (72, 86), + (72, 174), + (72, 222), + (73, 99), + (73, 253), + (75, 82), + (75, 140), + (75, 141), + (75, 212), + (75, 217), + (76, 42), + (76, 71), + (76, 117), + (76, 164), + (76, 178), + (77, 1), + (77, 135), + (77, 136), + (3, 15), + (3, 31), + (3, 41), + (3, 79), + (3, 170), + (3, 188), + (3, 203), + (3, 227), + (3, 229), + (3, 253), + (4, 50), + (4, 108), + (4, 120), + (4, 165), + (4, 233), + (5, 27), + (5, 57), + (5, 95), + (5, 101), + (5, 103), + (5, 136), + (5, 140), + (5, 159), + (5, 180), + (5, 193), + (5, 194), + (5, 198), + (5, 254), + (6, 26), + (6, 47), + (6, 54), + (6, 126), + (6, 204), + (6, 230), + (6, 236), + (6, 242), + (7, 95), + (7, 125), + (7, 148), + (7, 165), + (7, 220), + (7, 228), + (7, 241), + (8, 4), + (8, 52), + (8, 81), + (8, 88), + (8, 113), + (8, 144), + (9, 29), + (9, 45), + (9, 100), + (9, 101), + (9, 120), + (9, 168), + (9, 230), + (10, 63), + (10, 68), + (10, 155), + (10, 175), + (10, 180), + (10, 196), + (10, 231), + (11, 59), + (11, 128), + (11, 150), + (12, 28), + (12, 34), + (12, 83), + (12, 143), + (13, 58), + (13, 111), + (13, 124), + (13, 126), + (13, 148), + (14, 1), + (14, 7), + (14, 105), + (14, 112), + (14, 162), + (14, 193), + (14, 194), + (14, 221), + (14, 255), + (15, 71), + (15, 198), + (15, 247), + (16, 33), + (16, 40), + (16, 146), + (16, 233), + (17, 78), + (17, 101), + (17, 119), + (18, 83), + (18, 93), + (18, 160), + (18, 208), + (19, 50), + (19, 224), + (19, 254), + (20, 0), + (20, 18), + (20, 99), + (20, 164), + (20, 190), + (21, 4), + (21, 128), + (21, 133), + (22, 162), + (22, 252), + (23, 16), + (23, 66), + (23, 135), + (23, 187), + (23, 198), + (23, 214), + (24, 17), + (24, 38), + (24, 133), + (24, 181), + (24, 255), + (25, 7), + (25, 58), + (25, 97), + (25, 117), + (25, 170), + (25, 219), + (25, 247), + (26, 92), + (26, 94), + (26, 217), + (27, 30), + (27, 49), + (27, 60), + (27, 101), + (27, 172), + (27, 242), + (28, 21), + (28, 208), + (29, 61), + (29, 111), + (29, 160), + (29, 241), + (30, 90), + (30, 123), + (30, 169), + (31, 164), + (32, 123), + (33, 219), + (34, 7), + (34, 65), + (34, 117), + (34, 205), + (35, 14), + (35, 40), + (35, 54), + (35, 175), + (36, 7), + (36, 149), + (36, 158), + (36, 192), + (37, 107), + (37, 118), + (37, 121), + (37, 176), + (37, 221), + (38, 152), + (38, 184), + (38, 232), + (39, 99), + (39, 107), + (39, 145), + (39, 164), + (39, 183), + (40, 44), + (40, 57), + (40, 63), + (40, 207), + (41, 72), + (41, 101), + (42, 7), + (42, 109), + (43, 112), + (43, 159), + (43, 167), + (43, 233), + (44, 21), + (44, 34), + (44, 221), + (44, 251), + (46, 18), + (46, 140), + (47, 51), + (47, 139), + (47, 153), + (47, 197), + (47, 243), + (48, 67), + (48, 109), + (48, 129), + (48, 131), + (49, 57), + (49, 68), + (49, 101), + (50, 22), + (50, 60), + (50, 100), + (50, 116), + (50, 122), + (50, 237), + (51, 137), + (51, 159), + (51, 225), + (52, 29), + (52, 31), + (52, 103), + (52, 124), + (52, 127), + (52, 159), + (52, 164), + (53, 131), + (54, 27), + (54, 52), + (54, 98), + (54, 136), + (54, 238), + (56, 0), + (56, 33), + (56, 53), + (56, 187), + (56, 200), + (56, 227), + (57, 39), + (57, 157), + (58, 98), + (58, 111), + (58, 136), + (58, 197), + (59, 230), + (60, 206), + (60, 214), + (61, 12), + (61, 46), + (61, 71), + (61, 78), + (61, 82), + (61, 90), + (61, 189), + (61, 197), + (61, 229), + (62, 4), + (62, 65), + (62, 84), + (62, 212), + (63, 17), + (63, 37), + (63, 59), + (63, 90), + (63, 108), + (63, 110), + (63, 128), + (63, 161), + (65, 22), + (65, 85), + (65, 116), + (65, 192), + (65, 224), + (66, 149), + (66, 241), + (66, 250), + (67, 44), + (67, 136), + (67, 180), + (68, 22), + (68, 74), + (68, 116), + (68, 208), + (68, 216), + (69, 65), + (69, 135), + (69, 186), + (70, 1), + (70, 44), + (70, 83), + (71, 24), + (71, 92), + (72, 106), + (72, 136), + (72, 162), + (72, 171), + (73, 13), + (73, 97), + (73, 109), + (73, 193), + (73, 213), + (74, 60), + (74, 64), + (74, 66), + (74, 75), + (74, 156), + (74, 166), + (74, 167), + (74, 175), + (74, 235), + (75, 53), + (75, 118), + (75, 120), + (75, 157), + (75, 216), + (75, 218), + (75, 220), + (76, 126), + (76, 152), + (76, 200), + (76, 233), + (77, 45), + (77, 110), + (77, 157), + (77, 170), + (3, 8), + (3, 40), + (3, 107), + (3, 129), + (3, 139), + (3, 149), + (3, 155), + (4, 31), + (4, 53), + (4, 55), + (4, 72), + (4, 74), + (4, 78), + (4, 148), + (4, 244), + (4, 249), + (5, 12), + (5, 59), + (5, 60), + (5, 70), + (5, 100), + (5, 190), + (5, 232), + (6, 1), + (6, 30), + (6, 52), + (6, 67), + (6, 84), + (6, 148), + (6, 151), + (6, 214), + (7, 6), + (7, 14), + (7, 32), + (7, 38), + (7, 47), + (7, 80), + (7, 96), + (7, 108), + (7, 119), + (7, 151), + (7, 157), + (7, 198), + (7, 229), + (8, 9), + (8, 17), + (8, 156), + (8, 157), + (8, 177), + (8, 193), + (8, 219), + (8, 228), + (8, 235), + (9, 7), + (9, 20), + (9, 44), + (9, 210), + (9, 211), + (10, 33), + (10, 89), + (10, 90), + (10, 91), + (10, 158), + (10, 162), + (10, 204), + (10, 206), + (10, 240), + (11, 92), + (11, 148), + (11, 154), + (11, 175), + (11, 176), + (11, 208), + (11, 240), + (11, 242), + (12, 32), + (12, 56), + (12, 119), + (12, 135), + (12, 227), + (12, 234), + (12, 238), + (13, 52), + (13, 165), + (13, 189), + (13, 227), + (14, 36), + (14, 175), + (15, 30), + (15, 94), + (16, 7), + (16, 31), + (16, 73), + (16, 132), + (16, 150), + (16, 241), + (16, 254), + (17, 140), + (17, 252), + (18, 60), + (18, 72), + (18, 143), + (18, 149), + (18, 193), + (18, 225), + (18, 240), + (18, 252), + (19, 29), + (19, 181), + (20, 105), + (20, 121), + (20, 166), + (21, 13), + (22, 74), + (22, 131), + (23, 67), + (23, 117), + (23, 127), + (23, 255), + (24, 22), + (24, 37), + (24, 41), + (24, 125), + (24, 149), + (24, 230), + (25, 2), + (25, 13), + (25, 23), + (25, 92), + (25, 107), + (26, 41), + (26, 54), + (26, 70), + (26, 219), + (26, 223), + (26, 235), + (26, 254), + (27, 37), + (27, 76), + (27, 185), + (27, 192), + (27, 222), + (28, 15), + (28, 58), + (28, 73), + (28, 132), + (28, 196), + (29, 11), + (29, 69), + (29, 180), + (29, 201), + (29, 204), + (29, 215), + (30, 116), + (30, 121), + (30, 160), + (30, 249), + (31, 3), + (31, 27), + (31, 46), + (31, 223), + (32, 60), + (32, 129), + (32, 140), + (32, 199), + (33, 135), + (34, 2), + (34, 27), + (34, 96), + (34, 179), + (35, 1), + (35, 38), + (35, 57), + (35, 95), + (35, 125), + (35, 162), + (35, 241), + (36, 26), + (36, 49), + (36, 127), + (36, 144), + (36, 154), + (36, 214), + (37, 77), + (37, 209), + (37, 210), + (38, 3), + (38, 230), + (39, 28), + (39, 151), + (39, 153), + (39, 178), + (40, 179), + (40, 255), + (41, 61), + (41, 95), + (41, 97), + (41, 137), + (41, 174), + (42, 15), + (42, 53), + (42, 119), + (42, 249), + (43, 51), + (43, 99), + (43, 139), + (43, 169), + (43, 240), + (44, 31), + (44, 167), + (44, 252), + (45, 36), + (45, 97), + (45, 254), + (46, 85), + (46, 209), + (46, 240), + (47, 9), + (47, 18), + (47, 190), + (47, 224), + (49, 139), + (49, 195), + (49, 196), + (50, 26), + (50, 61), + (50, 173), + (51, 21), + (51, 117), + (51, 120), + (52, 70), + (52, 94), + (52, 140), + (52, 210), + (53, 95), + (54, 69), + (54, 158), + (54, 160), + (55, 73), + (55, 77), + (55, 142), + (56, 9), + (56, 30), + (56, 121), + (56, 152), + (56, 172), + (56, 193), + (56, 254), + (57, 66), + (57, 203), + (58, 88), + (58, 96), + (58, 110), + (58, 219), + (58, 239), + (59, 8), + (59, 21), + (59, 46), + (59, 55), + (59, 109), + (59, 169), + (59, 233), + (60, 27), + (60, 87), + (60, 246), + (61, 195), + (61, 228), + (62, 27), + (62, 38), + (62, 67), + (62, 81), + (62, 121), + (62, 158), + (62, 180), + (62, 186), + (62, 204), + (62, 213), + (62, 217), + (62, 218), + (62, 230), + (63, 97), + (63, 181), + (64, 65), + (64, 119), + (65, 73), + (65, 102), + (65, 203), + (65, 205), + (65, 219), + (66, 218), + (67, 37), + (67, 109), + (67, 145), + (67, 213), + (67, 252), + (68, 27), + (68, 66), + (68, 147), + (68, 222), + (68, 240), + (69, 160), + (69, 195), + (69, 232), + (69, 252), + (70, 26), + (70, 104), + (70, 154), + (70, 172), + (71, 171), + (72, 13), + (72, 77), + (72, 120), + (72, 168), + (72, 172), + (72, 182), + (72, 184), + (72, 207), + (72, 210), + (72, 217), + (73, 10), + (73, 57), + (73, 110), + (73, 118), + (73, 133), + (73, 167), + (73, 187), + (74, 3), + (74, 84), + (74, 129), + (74, 146), + (74, 194), + (74, 201), + (74, 249), + (75, 17), + (75, 45), + (75, 99), + (75, 163), + (76, 47), + (76, 64), + (76, 202), + (77, 19), + (77, 25), + (77, 29), + (77, 94), + (77, 140), + (77, 187), + (3, 2), + (3, 49), + (3, 63), + (3, 64), + (3, 69), + (3, 99), + (3, 117), + (3, 123), + (3, 124), + (3, 135), + (3, 142), + (3, 164), + (3, 194), + (3, 208), + (3, 210), + (3, 217), + (3, 221), + (3, 232), + (4, 19), + (4, 40), + (4, 46), + (4, 58), + (4, 100), + (4, 145), + (4, 154), + (4, 170), + (4, 213), + (4, 255), + (5, 14), + (5, 89), + (5, 113), + (5, 117), + (5, 118), + (5, 132), + (5, 177), + (5, 182), + (5, 208), + (6, 16), + (6, 41), + (6, 69), + (6, 80), + (6, 93), + (6, 99), + (6, 123), + (6, 131), + (6, 156), + (6, 173), + (6, 225), + (6, 243), + (6, 248), + (7, 13), + (7, 33), + (7, 34), + (7, 60), + (7, 150), + (7, 164), + (7, 166), + (7, 188), + (7, 230), + (8, 26), + (8, 71), + (8, 89), + (8, 100), + (8, 124), + (8, 140), + (8, 152), + (8, 173), + (8, 206), + (8, 236), + (8, 246), + (9, 22), + (9, 53), + (9, 118), + (9, 127), + (9, 172), + (9, 178), + (9, 208), + (9, 221), + (10, 6), + (10, 58), + (10, 62), + (10, 110), + (10, 126), + (10, 141), + (10, 170), + (11, 24), + (11, 68), + (11, 140), + (11, 141), + (11, 164), + (11, 195), + (12, 48), + (12, 85), + (12, 88), + (12, 90), + (12, 112), + (12, 171), + (12, 237), + (13, 9), + (13, 19), + (13, 24), + (13, 28), + (13, 94), + (13, 134), + (13, 214), + (13, 216), + (13, 224), + (14, 50), + (14, 90), + (14, 103), + (14, 114), + (14, 123), + (14, 225), + (15, 42), + (15, 57), + (15, 92), + (15, 130), + (15, 200), + (16, 203), + (16, 204), + (16, 240), + (17, 63), + (17, 81), + (17, 156), + (17, 230), + (18, 23), + (19, 97), + (19, 110), + (19, 119), + (20, 53), + (20, 119), + (20, 243), + (21, 0), + (21, 11), + (21, 50), + (21, 232), + (22, 8), + (22, 68), + (22, 80), + (22, 176), + (22, 253), + (23, 76), + (23, 112), + (23, 189), + (23, 253), + (24, 6), + (24, 23), + (24, 50), + (24, 56), + (24, 60), + (24, 158), + (24, 211), + (25, 4), + (25, 56), + (25, 113), + (25, 174), + (25, 187), + (25, 217), + (26, 33), + (26, 110), + (26, 177), + (26, 190), + (26, 194), + (26, 225), + (26, 253), + (27, 0), + (27, 3), + (27, 123), + (27, 243), + (28, 27), + (28, 35), + (28, 43), + (28, 83), + (28, 100), + (28, 136), + (28, 138), + (28, 185), + (28, 230), + (28, 234), + (28, 246), + (29, 63), + (29, 124), + (29, 125), + (29, 214), + (30, 91), + (30, 108), + (30, 140), + (30, 222), + (31, 31), + (31, 93), + (32, 19), + (32, 21), + (32, 222), + (33, 11), + (33, 15), + (33, 193), + (34, 3), + (34, 36), + (34, 41), + (34, 92), + (34, 94), + (34, 224), + (34, 229), + (35, 160), + (35, 178), + (36, 138), + (36, 201), + (36, 234), + (37, 4), + (37, 201), + (38, 122), + (38, 228), + (39, 115), + (39, 146), + (39, 171), + (39, 206), + (39, 245), + (40, 5), + (40, 115), + (40, 125), + (40, 212), + (40, 230), + (40, 247), + (41, 21), + (41, 67), + (41, 73), + (41, 80), + (41, 123), + (41, 189), + (41, 224), + (42, 88), + (42, 100), + (42, 227), + (42, 234), + (43, 7), + (43, 120), + (43, 194), + (43, 252), + (44, 15), + (44, 23), + (45, 54), + (45, 66), + (45, 90), + (45, 156), + (45, 239), + (46, 38), + (46, 120), + (46, 127), + (46, 153), + (46, 165), + (46, 177), + (46, 179), + (46, 182), + (47, 27), + (47, 39), + (47, 119), + (47, 133), + (47, 201), + (48, 8), + (48, 104), + (48, 133), + (48, 151), + (48, 254), + (49, 88), + (49, 157), + (49, 161), + (49, 183), + (49, 211), + (50, 67), + (50, 84), + (50, 190), + (50, 248), + (51, 18), + (51, 90), + (51, 136), + (51, 168), + (51, 236), + (52, 12), + (52, 52), + (52, 97), + (52, 102), + (52, 117), + (52, 184), + (52, 186), + (53, 33), + (53, 56), + (53, 58), + (53, 71), + (53, 99), + (53, 179), + (53, 206), + (54, 79), + (54, 111), + (54, 198), + (54, 208), + (55, 60), + (55, 75), + (55, 81), + (55, 154), + (55, 194), + (55, 234), + (56, 1), + (56, 85), + (56, 91), + (56, 128), + (56, 131), + (56, 149), + (57, 41), + (57, 77), + (57, 141), + (57, 145), + (57, 156), + (57, 159), + (58, 9), + (58, 86), + (58, 123), + (58, 124), + (58, 172), + (58, 174), + (58, 203), + (59, 49), + (59, 53), + (59, 75), + (59, 198), + (59, 208), + (59, 216), + (60, 44), + (60, 69), + (60, 122), + (60, 153), + (61, 31), + (61, 124), + (61, 164), + (61, 177), + (61, 191), + (62, 21), + (62, 161), + (62, 162), + (63, 34), + (63, 42), + (63, 177), + (63, 185), + (64, 155), + (65, 6), + (65, 21), + (65, 39), + (65, 57), + (65, 61), + (65, 108), + (65, 231), + (65, 236), + (66, 7), + (66, 167), + (67, 131), + (67, 147), + (67, 171), + (67, 243), + (68, 104), + (68, 115), + (68, 245), + (69, 6), + (69, 87), + (69, 117), + (69, 126), + (69, 162), + (69, 166), + (69, 180), + (69, 251), + (70, 61), + (70, 94), + (70, 128), + (70, 167), + (70, 193), + (71, 174), + (71, 204), + (72, 23), + (72, 93), + (72, 101), + (72, 153), + (72, 166), + (72, 213), + (73, 12), + (73, 31), + (73, 68), + (73, 104), + (73, 127), + (73, 228), + (74, 13), + (74, 71), + (74, 100), + (74, 108), + (74, 115), + (74, 157), + (74, 163), + (74, 165), + (74, 187), + (75, 23), + (75, 44), + (75, 67), + (75, 87), + (75, 102), + (75, 108), + (75, 119), + (75, 255), + (76, 36), + (76, 109), + (76, 161), + (76, 177), + (76, 220), + (76, 222), + (77, 72), + (77, 106), + (77, 118), + (77, 138), + (77, 141), + (77, 161), + (77, 215), + (3, 67), + (3, 84), + (3, 86), + (3, 90), + (3, 97), + (3, 103), + (3, 145), + (3, 146), + (3, 158), + (3, 174), + (3, 213), + (3, 215), + (3, 218), + (3, 247), + (3, 249), + (4, 36), + (4, 70), + (4, 77), + (4, 85), + (4, 137), + (4, 202), + (4, 212), + (4, 223), + (5, 0), + (5, 5), + (5, 54), + (5, 65), + (5, 75), + (5, 81), + (5, 84), + (5, 94), + (5, 108), + (5, 144), + (5, 145), + (5, 154), + (5, 163), + (5, 178), + (5, 189), + (5, 213), + (6, 11), + (6, 22), + (6, 92), + (6, 116), + (6, 160), + (6, 177), + (6, 190), + (6, 194), + (7, 4), + (7, 21), + (7, 31), + (7, 46), + (7, 48), + (7, 55), + (7, 56), + (7, 57), + (7, 61), + (7, 100), + (7, 104), + (7, 131), + (7, 132), + (7, 135), + (7, 147), + (7, 184), + (7, 193), + (7, 200), + (7, 233), + (7, 236), + (8, 34), + (8, 45), + (8, 54), + (8, 73), + (8, 115), + (8, 176), + (8, 190), + (8, 208), + (8, 211), + (8, 224), + (9, 1), + (9, 9), + (9, 28), + (9, 68), + (9, 76), + (9, 78), + (9, 82), + (9, 93), + (9, 97), + (9, 106), + (9, 115), + (9, 149), + (9, 183), + (9, 193), + (9, 198), + (9, 205), + (9, 231), + (9, 251), + (10, 4), + (10, 80), + (10, 99), + (10, 112), + (10, 124), + (10, 133), + (10, 145), + (10, 232), + (11, 48), + (11, 64), + (11, 101), + (11, 213), + (11, 218), + (12, 0), + (12, 18), +]; diff --git a/src/engine/metal/profile.rs b/src/engine/metal/profile.rs new file mode 100644 index 0000000..619dd27 --- /dev/null +++ b/src/engine/metal/profile.rs @@ -0,0 +1,307 @@ +use super::gpu::Buffer; +use crate::model::ModelChoice; +use serde_json::{Value, json}; +use std::cmp::Ordering; +use std::fs::{self, File}; +use std::path::PathBuf; + +const CACHE_CAPS: [usize; 10] = [1, 2, 4, 8, 16, 32, 64, 128, 256, 384]; + +#[derive(Default)] +struct LayerProfile { + records: u64, + counts: Vec, + weights: Vec, + caches: Vec>, + cache_hits: Vec, + cache_weight_hits: Vec, + previous: Option<(u32, Vec)>, + adjacent_pairs: u64, + adjacent_overlap: f64, + adjacent_jaccard: f64, + hash_router: bool, +} + +pub(super) struct ExpertProfile { + path: PathBuf, + model: ModelChoice, + experts: usize, + used: usize, + caps: Vec, + layers: Vec, +} + +impl ExpertProfile { + pub(super) fn new( + path: Option<&str>, + model: ModelChoice, + layers: u32, + experts: u64, + used: u64, + ) -> Result, String> { + let Some(path) = path else { return Ok(None) }; + if path.trim().is_empty() { + return Err("Expert profile path cannot be empty".into()); + } + let experts = usize::try_from(experts).map_err(|_| "expert count is too large")?; + let used = usize::try_from(used).map_err(|_| "selected expert count is too large")?; + let caps = CACHE_CAPS + .into_iter() + .filter(|cap| *cap <= experts) + .collect::>(); + let layers = (0..layers) + .map(|_| LayerProfile { + counts: vec![0; experts], + weights: vec![0.0; experts], + caches: vec![Vec::new(); caps.len()], + cache_hits: vec![0; caps.len()], + cache_weight_hits: vec![0.0; caps.len()], + ..LayerProfile::default() + }) + .collect(); + Ok(Some(Self { + path: path.into(), + model, + experts, + used, + caps, + layers, + })) + } + + pub(super) fn record( + &mut self, + layer: usize, + pos: u32, + selected: &Buffer, + weights: &Buffer, + rows: u32, + hash_router: bool, + ) -> Result<(), String> { + let count = self + .used + .checked_mul(rows as usize) + .ok_or("expert profile row count overflow")?; + let mut ids = vec![0; count]; + let mut route_weights = vec![0.0; count]; + super::call( + unsafe { super::gpu::ds4_gpu_end_commands() }, + "ending Metal commands for expert profiling", + )?; + let read = selected + .read_i32(&mut ids) + .and_then(|()| weights.read_f32(&mut route_weights)); + let resumed = super::call( + unsafe { super::gpu::ds4_gpu_begin_commands() }, + "resuming Metal commands after expert profiling", + ); + read.and(resumed)?; + for (row, (ids, weights)) in ids + .chunks_exact(self.used) + .zip(route_weights.chunks_exact(self.used)) + .enumerate() + { + self.record_row(layer, pos + row as u32, ids, weights, hash_router)?; + } + Ok(()) + } + + fn record_row( + &mut self, + layer: usize, + pos: u32, + ids: &[i32], + weights: &[f32], + hash_router: bool, + ) -> Result<(), String> { + let profile = self + .layers + .get_mut(layer) + .ok_or("expert profile layer is outside the model")?; + profile.records += 1; + profile.hash_router |= hash_router; + if let Some((previous_pos, previous)) = &profile.previous + && previous_pos.checked_add(1) == Some(pos) + { + let intersection = previous.iter().filter(|id| ids.contains(id)).count(); + profile.adjacent_pairs += 1; + profile.adjacent_overlap += intersection as f64 / self.used as f64; + profile.adjacent_jaccard += intersection as f64 / (2 * self.used - intersection) as f64; + } + profile.previous = Some((pos, ids.to_vec())); + for (&id, &weight) in ids.iter().zip(weights) { + let expert = usize::try_from(id) + .ok() + .filter(|expert| *expert < self.experts) + .ok_or_else(|| format!("router selected invalid expert {id} at layer {layer}"))?; + profile.counts[expert] += 1; + profile.weights[expert] += f64::from(weight); + for (index, (&cap, cache)) in self.caps.iter().zip(&mut profile.caches).enumerate() { + if let Some(found) = cache.iter().position(|cached| *cached == id) { + profile.cache_hits[index] += 1; + profile.cache_weight_hits[index] += f64::from(weight); + cache.remove(found); + } else if cache.len() == cap { + cache.pop(); + } + cache.insert(0, id); + } + } + Ok(()) + } + + pub(super) fn write(&self) -> Result<(), String> { + let selections: u64 = self + .layers + .iter() + .map(|layer| layer.counts.iter().sum::()) + .sum(); + let weight_total = self + .layers + .iter() + .flat_map(|layer| &layer.weights) + .sum::(); + let cache_summary = self + .caps + .iter() + .enumerate() + .map(|(index, cap)| { + let hits = self + .layers + .iter() + .map(|layer| layer.cache_hits[index]) + .sum::(); + let weighted = self + .layers + .iter() + .map(|layer| layer.cache_weight_hits[index]) + .sum::(); + json!({ + "n": cap, + "hits": hits, + "selections": selections, + "hit_rate": fraction(hits as f64, selections as f64), + "weighted_hit_rate": fraction(weighted, weight_total), + }) + }) + .collect::>(); + let layers = self + .layers + .iter() + .enumerate() + .map(|(index, layer)| self.layer_json(index, layer)) + .collect::>(); + let value = json!({ + "source": "ds4 Metal expert locality profile", + "model": self.model.id(), + "layers": self.layers.len(), + "experts": self.experts, + "expert_used": self.used, + "layer_records": self.layers.iter().map(|layer| layer.records).sum::(), + "selections": selections, + "cache_ns": self.caps, + "cache_summary": cache_summary, + "layers_detail": layers, + }); + if let Some(parent) = self + .path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + let temporary = self.path.with_extension("tmp"); + let file = File::create(&temporary).map_err(|error| error.to_string())?; + serde_json::to_writer_pretty(&file, &value).map_err(|error| error.to_string())?; + file.sync_all().map_err(|error| error.to_string())?; + fs::rename(temporary, &self.path).map_err(|error| error.to_string()) + } + + fn layer_json(&self, index: usize, layer: &LayerProfile) -> Value { + let selections = layer.counts.iter().sum::(); + let total_weight = layer.weights.iter().sum::(); + let mut experts = (0..self.experts) + .filter(|expert| layer.counts[*expert] != 0) + .collect::>(); + experts.sort_by(|a, b| { + layer.counts[*b] + .cmp(&layer.counts[*a]) + .then_with(|| { + layer.weights[*b] + .partial_cmp(&layer.weights[*a]) + .unwrap_or(Ordering::Equal) + }) + .then_with(|| a.cmp(b)) + }); + let top = experts + .into_iter() + .take(16) + .map(|expert| { + json!({ + "id": expert, + "count": layer.counts[expert], + "pct": 100.0 * fraction(layer.counts[expert] as f64, selections as f64), + "weight": layer.weights[expert], + "weight_pct": 100.0 * fraction(layer.weights[expert], total_weight), + }) + }) + .collect::>(); + let cache = self + .caps + .iter() + .enumerate() + .map(|(cap_index, cap)| { + json!({ + "n": cap, + "hits": layer.cache_hits[cap_index], + "hit_rate": fraction(layer.cache_hits[cap_index] as f64, selections as f64), + "weighted_hit_rate": fraction(layer.cache_weight_hits[cap_index], total_weight), + }) + }) + .collect::>(); + json!({ + "layer": index, + "hash_router": layer.hash_router, + "records": layer.records, + "selections": selections, + "unique_experts": layer.counts.iter().filter(|count| **count != 0).count(), + "avg_adjacent_overlap": fraction(layer.adjacent_overlap, layer.adjacent_pairs as f64), + "avg_adjacent_jaccard": fraction(layer.adjacent_jaccard, layer.adjacent_pairs as f64), + "top_experts": top, + "cache": cache, + }) + } +} + +fn fraction(value: f64, total: f64) -> f64 { + if total > 0.0 { value / total } else { 0.0 } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn profile_reports_locality_and_lru_hits() { + let path = std::env::temp_dir().join(format!( + "ds4-profile-{}-{}.json", + std::process::id(), + std::thread::current().name().unwrap_or("test") + )); + let mut profile = ExpertProfile::new(path.to_str(), ModelChoice::DeepSeekV4Flash, 1, 8, 2) + .unwrap() + .unwrap(); + profile + .record_row(0, 10, &[1, 2], &[0.6, 0.4], false) + .unwrap(); + profile + .record_row(0, 11, &[1, 3], &[0.7, 0.3], false) + .unwrap(); + profile.write().unwrap(); + let value: Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap(); + assert_eq!(value["selections"], 4); + assert_eq!(value["layers_detail"][0]["avg_adjacent_overlap"], 0.5); + assert_eq!(value["cache_summary"][1]["hits"], 1); + let _ = fs::remove_file(path); + } +} diff --git a/src/metrics.rs b/src/metrics.rs index 4dc95bc..b6a968d 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -96,7 +96,18 @@ pub(crate) struct MetricsSnapshot { pub(crate) ssd_resident_bytes: u64, pub(crate) ssd_cache_bytes: u64, pub(crate) ssd_cache_experts: u64, + pub(crate) ssd_cache_entries: u64, pub(crate) ssd_preloaded_experts: u64, + pub(crate) ssd_cache_hits: u64, + pub(crate) ssd_cache_misses: u64, + pub(crate) ssd_cache_evictions: u64, + pub(crate) ssd_cache_wraps: u64, + pub(crate) ssd_buffer_allocs: u64, + pub(crate) ssd_buffer_reuses: u64, + pub(crate) ssd_pread_bytes: u64, + pub(crate) ssd_pread_ms: u64, + pub(crate) ssd_evict_advise_bytes: u64, + pub(crate) ssd_willneed_advise_bytes: u64, pub(crate) ssd_selected_requests: u64, pub(crate) ssd_requested_bytes: u64, pub(crate) ssd_wait_ms: u64, @@ -178,7 +189,18 @@ pub(crate) struct Metrics { ssd_resident_bytes: AtomicU64, ssd_cache_bytes: AtomicU64, ssd_cache_experts: AtomicU64, + ssd_cache_entries: AtomicU64, ssd_preloaded_experts: AtomicU64, + ssd_cache_hits: AtomicU64, + ssd_cache_misses: AtomicU64, + ssd_cache_evictions: AtomicU64, + ssd_cache_wraps: AtomicU64, + ssd_buffer_allocs: AtomicU64, + ssd_buffer_reuses: AtomicU64, + ssd_pread_bytes: AtomicU64, + ssd_pread_ms: AtomicU64, + ssd_evict_advise_bytes: AtomicU64, + ssd_willneed_advise_bytes: AtomicU64, ssd_selected_requests: AtomicU64, ssd_requested_bytes: AtomicU64, ssd_wait_ms: AtomicU64, @@ -265,7 +287,18 @@ impl Metrics { ssd_resident_bytes: AtomicU64::new(0), ssd_cache_bytes: AtomicU64::new(0), ssd_cache_experts: AtomicU64::new(0), + ssd_cache_entries: AtomicU64::new(0), ssd_preloaded_experts: AtomicU64::new(0), + ssd_cache_hits: AtomicU64::new(0), + ssd_cache_misses: AtomicU64::new(0), + ssd_cache_evictions: AtomicU64::new(0), + ssd_cache_wraps: AtomicU64::new(0), + ssd_buffer_allocs: AtomicU64::new(0), + ssd_buffer_reuses: AtomicU64::new(0), + ssd_pread_bytes: AtomicU64::new(0), + ssd_pread_ms: AtomicU64::new(0), + ssd_evict_advise_bytes: AtomicU64::new(0), + ssd_willneed_advise_bytes: AtomicU64::new(0), ssd_selected_requests: AtomicU64::new(0), ssd_requested_bytes: AtomicU64::new(0), ssd_wait_ms: AtomicU64::new(0), @@ -442,7 +475,18 @@ impl Metrics { resident_bytes: u64, cache_bytes: u64, cache_experts: u64, + cache_entries: u64, preloaded_experts: u64, + cache_hits: u64, + cache_misses: u64, + cache_evictions: u64, + cache_wraps: u64, + buffer_allocs: u64, + buffer_reuses: u64, + pread_bytes: u64, + pread_ms: u64, + evict_advise_bytes: u64, + willneed_advise_bytes: u64, selected_requests: u64, requested_bytes: u64, wait_ms: u64, @@ -453,8 +497,25 @@ impl Metrics { self.ssd_cache_bytes.store(cache_bytes, Ordering::Relaxed); self.ssd_cache_experts .store(cache_experts, Ordering::Relaxed); + self.ssd_cache_entries + .store(cache_entries, Ordering::Relaxed); self.ssd_preloaded_experts .store(preloaded_experts, Ordering::Relaxed); + self.ssd_cache_hits.store(cache_hits, Ordering::Relaxed); + self.ssd_cache_misses.store(cache_misses, Ordering::Relaxed); + self.ssd_cache_evictions + .store(cache_evictions, Ordering::Relaxed); + self.ssd_cache_wraps.store(cache_wraps, Ordering::Relaxed); + self.ssd_buffer_allocs + .store(buffer_allocs, Ordering::Relaxed); + self.ssd_buffer_reuses + .store(buffer_reuses, Ordering::Relaxed); + self.ssd_pread_bytes.store(pread_bytes, Ordering::Relaxed); + self.ssd_pread_ms.store(pread_ms, Ordering::Relaxed); + self.ssd_evict_advise_bytes + .store(evict_advise_bytes, Ordering::Relaxed); + self.ssd_willneed_advise_bytes + .store(willneed_advise_bytes, Ordering::Relaxed); self.ssd_selected_requests .store(selected_requests, Ordering::Relaxed); self.ssd_requested_bytes @@ -474,7 +535,7 @@ impl Metrics { self.prefill_tps.store(0, Ordering::Relaxed); self.prefill_sample.store(0, Ordering::Relaxed); self.speculative_stats(0, 0, 0, 0, 0, 0); - self.ssd_stats(false, 0, 0, 0, 0, 0, 0, 0); + self.ssd_stats(false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); self.model_unloads.fetch_add(1, Ordering::Relaxed); } @@ -634,7 +695,18 @@ impl Metrics { ssd_resident_bytes: self.ssd_resident_bytes.load(Ordering::Relaxed), ssd_cache_bytes: self.ssd_cache_bytes.load(Ordering::Relaxed), ssd_cache_experts: self.ssd_cache_experts.load(Ordering::Relaxed), + ssd_cache_entries: self.ssd_cache_entries.load(Ordering::Relaxed), ssd_preloaded_experts: self.ssd_preloaded_experts.load(Ordering::Relaxed), + ssd_cache_hits: self.ssd_cache_hits.load(Ordering::Relaxed), + ssd_cache_misses: self.ssd_cache_misses.load(Ordering::Relaxed), + ssd_cache_evictions: self.ssd_cache_evictions.load(Ordering::Relaxed), + ssd_cache_wraps: self.ssd_cache_wraps.load(Ordering::Relaxed), + ssd_buffer_allocs: self.ssd_buffer_allocs.load(Ordering::Relaxed), + ssd_buffer_reuses: self.ssd_buffer_reuses.load(Ordering::Relaxed), + ssd_pread_bytes: self.ssd_pread_bytes.load(Ordering::Relaxed), + ssd_pread_ms: self.ssd_pread_ms.load(Ordering::Relaxed), + ssd_evict_advise_bytes: self.ssd_evict_advise_bytes.load(Ordering::Relaxed), + ssd_willneed_advise_bytes: self.ssd_willneed_advise_bytes.load(Ordering::Relaxed), ssd_selected_requests: self.ssd_selected_requests.load(Ordering::Relaxed), ssd_requested_bytes: self.ssd_requested_bytes.load(Ordering::Relaxed), ssd_wait_ms: self.ssd_wait_ms.load(Ordering::Relaxed), @@ -970,7 +1042,10 @@ mod tests { metrics.kv_write_finished(Duration::from_millis(20), false); assert_eq!(metrics.take_kv_io_sample(), (2_048, 4_096)); assert_eq!(metrics.take_kv_io_sample(), (0, 0)); - metrics.ssd_stats(true, 1_024, 2_048, 16, 4, 12, 8_192, 30); + metrics.ssd_stats( + true, 1_024, 2_048, 16, 10, 4, 90, 10, 3, 2, 8, 12, 4_096, 25, 1_024, 2_048, 12, 8_192, + 30, + ); metrics.request_finished( WorkSource::LocalChat, Duration::from_millis(250), diff --git a/src/settings.rs b/src/settings.rs index ea5c25e..93ae91a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -351,8 +351,12 @@ impl DiagnosticPreferences { if let Some(gib) = self.simulated_used_memory_gib { validate_gib("Simulated used memory", gib)?; } - if self.expert_profile_path.is_some() { - return Err("Expert profiling is not available in the Rust Metal executor yet.".into()); + if self + .expert_profile_path + .as_deref() + .is_some_and(|path| path.trim().is_empty()) + { + return Err("Expert profile path cannot be empty.".into()); } Ok(()) }