"""Phase32B full frozen Unicode content with exact raw4/capacity CPU fallback. Taxonomy support remains the same ASCII-literal subset as A. The standalone CUDA file is read for build/source identity; runtime loads an AOT cubin. """ from __future__ import annotations import hashlib from pathlib import Path from typing import Callable import numpy as np from . import phase32a_gpu from .phase32a_table_builder import read_frozen, SOURCE_SHA256 _SOURCE_SHA256_AT_IMPORT = hashlib.sha256(Path(__file__).read_bytes()).hexdigest() _BASE_SOURCE_SHA256_AT_IMPORT = hashlib.sha256(Path(phase32a_gpu.__file__).read_bytes()).hexdigest() KERNEL_SOURCE_PATH = Path(__file__).with_name("phase32b_kernel.cu") CUDA_SOURCE = KERNEL_SOURCE_PATH.read_text(encoding="utf-8") _KERNEL_FILE_SHA256_AT_IMPORT = hashlib.sha256(KERNEL_SOURCE_PATH.read_bytes()).hexdigest() MAX_CANDIDATES = phase32a_gpu.MAX_CANDIDATES NORMALIZATION_CAPACITY_MULTIPLIER = 2 PHASE32B_SOURCE_SHA256 = SOURCE_SHA256 def _load_frozen_tables(directory): assets, meta = read_frozen(directory) arrays = { "unicode_index": np.frombuffer(assets["unicode_index.bin"], dtype=" 64 * 1024 * 1024 or batch_rows > 65536 or max_row_bytes > 65536: raise ValueError("Phase32B initial caps are 64 MiB / 65,536 rows / 65,536 row bytes") if max_row_bytes > batch_bytes: raise ValueError("max_row_bytes must not exceed batch_bytes") if not callable(masked_cpu): raise TypeError("masked_cpu must be the current native masked scanner callable") if not isinstance(overlap_cpu, bool): raise TypeError("overlap_cpu must be a boolean") if not isinstance(diagnostics, bool): raise TypeError("diagnostics must be a boolean") self.cpu_engine, self.masked_cpu = cpu_engine, masked_cpu self.cubin_path = Path(cubin_path).resolve() self.batch_bytes, self.batch_rows, self.max_row_bytes = batch_bytes, batch_rows, max_row_bytes self.overlap_cpu = overlap_cpu self.diagnostics = diagnostics self._unicode_arrays, self._unicode_identity = _load_frozen_tables(unicode_tables) self.unsupported_reason = None self._arrays, self._conjunction_count = {}, 0 try: self._arrays, self._conjunction_count = phase32a_gpu._pack_specs(_build_native_specs(cpu_engine._screening_indicator)) except phase32a_gpu.UnsupportedGpuTaxonomy as exc: self.unsupported_reason = str(exc) if not self.unsupported_reason: self._arrays.update(self._unicode_arrays) self.cp = self._kernel = None self._device_config, self._workspace = {}, {} self._capacity_bytes = self._capacity_rows = 0 self._cubin_sha256 = None self._cuda_source_sha256 = hashlib.sha256(CUDA_SOURCE.encode()).hexdigest() self._kernel_attributes = None self.last_profile = {} @property def backend_report(self): return {**super().backend_report, "backend": "cpu" if self.unsupported_reason else "experimental_frozen_unicode_cuda", "unicode_fallback_definition": "original raw4; code6 separately records whole-row 2x capacity rejection", "taxonomy_scope": "unchanged A ASCII pattern, context and exact-literal support", "normalization_capacity_multiplier": 2, "phase32b_kernel_file_sha256": _KERNEL_FILE_SHA256_AT_IMPORT} def _initialize(self): if self.cp is not None: return if not self.cubin_path.is_file(): raise FileNotFoundError("Build the experimental cubin explicitly before running CUDA: " + str(self.cubin_path)) import cupy as cp module = cp.RawModule(path=str(self.cubin_path)) kernel = module.get_function("range_frozen_unicode_classify") # Keep load-time metadata: later reports must not describe a replacement # file at the same path or rehash source text inside screening timings. loaded_cubin_sha256 = hashlib.sha256(self.cubin_path.read_bytes()).hexdigest() attributes = kernel.attributes kernel_attributes = { name: int(attributes[name]) if attributes.get(name) is not None else None for name in ("num_regs", "local_size_bytes", "shared_size_bytes") } config = {name: cp.asarray(value if value.size else np.zeros(1, dtype=value.dtype)) for name, value in self._arrays.items()} cp.cuda.Stream.null.synchronize() self.cp, self._module, self._kernel, self._device_config = cp, module, kernel, config self._cubin_sha256 = loaded_cubin_sha256 self._kernel_attributes = kernel_attributes def _reserve(self, byte_count, row_count): cp = self.cp if byte_count > self._capacity_bytes: self._capacity_bytes = max(byte_count, self._capacity_bytes) self._workspace.update(data=cp.empty(self._capacity_bytes, dtype=cp.uint8), normalized=cp.empty(2 * self._capacity_bytes, dtype=cp.uint8), source_offsets=cp.empty(2 * self._capacity_bytes, dtype=cp.uint32)) if row_count > self._capacity_rows: self._capacity_rows = max(row_count, self._capacity_rows) self._workspace.update(offsets=cp.empty(self._capacity_rows + 1, dtype=cp.int64), timestamps=cp.empty(self._capacity_rows, dtype=cp.int64), candidates=cp.empty(self._capacity_rows * MAX_CANDIDATES * 4, dtype=cp.int32), codes=cp.empty(self._capacity_rows, dtype=cp.uint8), facts=cp.empty((self._capacity_rows, 8), dtype=cp.uint32)) def _gpu_stage(self, batch, *, text_column, timestamp_column, diagnostic=None): stage = super()._gpu_stage(batch, text_column=text_column, timestamp_column=timestamp_column, diagnostic=diagnostic) # Frozen payload hashes make code7 impossible in valid operation. Refuse # to continue or export a corrupted-table result to the native scanner. if np.any(stage[0] == 7): raise RuntimeError("Phase32B frozen mapping guard failed") return stage def diagnostic_unicode_shadow(self, data, *, enabled=False, text_column="text"): raise NotImplementedError("Phase32B needs a separate bounded full-repertoire shadow; do not use A's subset model")