File size: 10,772 Bytes
8947dc2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""Installed CUDA parity and fixed full-corpus qualification; no tuning loop."""
import argparse,gc,hashlib,json,statistics,struct,sys,time
from datetime import datetime,timezone
from pathlib import Path
FULL_FACT_COLUMNS = (
    ("row_index", "uint64"),
    ("article_status", "uint8"),
    ("sp500", "uint8"),
    ("nasdaq100", "uint8"),
    ("russell2000", "uint8"),
    ("market_wide", "uint8"),
    ("observation_count", "uint32"),
    ("native_candidate", "bool"),
    ("exact_candidate", "bool"),
)

FULL_FACT_DIGEST_FORMAT = "range.full-fact-columns.v1"

def all_fact_sha256(result, row_count: int) -> str:
    """Hash all nine native columns, independently of Arrow chunking/padding.



    Encoding: NUL-terminated format ID, two little-endian uint64 row counts

    (input, facts), then each NUL-terminated name/type pair and its column bytes.

    Booleans use one byte per value (0 or 1), not Arrow's padded bit buffers.

    """
    import numpy as np
    import pyarrow as pa

    if (
        isinstance(row_count, bool)
        or not isinstance(row_count, int)
        or not 0 <= row_count < 2**64
    ):
        raise ValueError("row_count must fit an unsigned 64-bit integer")
    if hasattr(result, "to_pyarrow"):
        result = result.to_pyarrow()
    if not isinstance(result, (pa.Table, pa.RecordBatch)):
        raise TypeError("full-fact hashing requires an Arrow Table or RecordBatch")
    if tuple(result.schema.names) != tuple(name for name, _ in FULL_FACT_COLUMNS):
        raise ValueError("full-fact hashing requires the nine native columns in order")
    if result.num_rows > row_count:
        raise ValueError("fact row count exceeds the input row count")

    digest = hashlib.sha256(FULL_FACT_DIGEST_FORMAT.encode("ascii") + b"\0")
    digest.update(struct.pack("<QQ", row_count, result.num_rows))
    byte_types = {"uint64": "<u8", "uint32": "<u4", "uint8": "u1", "bool": "u1"}
    for index, (name, type_name) in enumerate(FULL_FACT_COLUMNS):
        column = result.column(index)
        if column.type != pa.type_for_alias(type_name) or column.null_count:
            raise ValueError(f"{name} must contain non-null Arrow {type_name} values")
        values = column.to_numpy(zero_copy_only=False)
        if index == 0 and len(values):
            if int(values[-1]) >= row_count or np.any(values[1:] <= values[:-1]):
                raise ValueError("fact row indices must be unique, sorted, and in range")
        digest.update(name.encode("ascii") + b"\0" + type_name.encode("ascii") + b"\0")
        digest.update(values.astype(np.dtype(byte_types[type_name]), copy=False).tobytes())
    return digest.hexdigest()

def custom_taxonomies():
    """Maintain every bundled rule; add only deterministic gate fixtures."""
    from dataclasses import replace
    from types import MappingProxyType
    from range_curate.examples.general_economy import DefaultTaxonomy
    from range_curate.customization import Taxonomy, TaxonomyBuilder
    from range_curate._core.config import ContextRule

    default = DefaultTaxonomy()
    yield "default", default
    contexts = dict(default.config.context_rules)
    contexts.update({f"phase32-context-{width}": ContextRule(width, (("ok",),), ("no",))
                     for width in (2, 3)})
    conjunctions = dict(default.config.market_wide_conjunctions)
    template = next(iter(conjunctions.values()))
    conjunctions["phase32-conjunction"] = replace(template,
        actor_phrases=("phaseactor",), actor_identifiers=("PACTOR",),
        action_phrases=("phaseacts",), object_phrases=("phaseobject",), scope_phrases=("phasescope",))
    base = Taxonomy(config=replace(default.config, context_rules=MappingProxyType(contexts),
                    market_wide_conjunctions=MappingProxyType(conjunctions)),
                    catalogs=default.catalogs)
    builder = TaxonomyBuilder(base)
    builder.add_entity(canonical_id="phase32-fixture", canonical_name="Phase32 Fixture",
                       entity_concept="fund", entity_kind="fund", exposure_family="EXPOSURE:SP500",
                       valid_from="2020-01-01", valid_to="2021-01-01")
    for index, (surface, mode) in enumerate([
        ("phasealpha", "normalized_phrase"), ("phasealpha beta", "normalized_phrase"),
        ("ffi", "normalized_phrase"), ("QXZ", "exact_case_sensitive"),
        ("iQs", "exact_casefold"),
    ]):
        builder.add_alias(alias_id=f"phase32-alias-{index}", canonical_id="phase32-fixture",
                          surface=surface, match_mode=mode)
    for width in (2, 3):
        builder.add_alias(alias_id=f"phase32-context-alias-{width}", canonical_id="phase32-fixture",
                          surface=f"CTX{width}", match_mode="exact_case_sensitive", requires_context=True,
                          context_rule_id=f"phase32-context-{width}")
    builder.add_lexical_rule(rule_id="phase32-lexical", entity_concept="macroeconomic_indicator",
                             tag_type="macroeconomic", phrases=("phaseevent",), context_window_chars=3,
                             context_any=("ok",), target_id="MACRO:PHASE32_CONTEXT")
    custom = builder.build()
    yield "custom-ascii", custom
    unicode_builder = TaxonomyBuilder(custom)
    unicode_builder.add_alias(alias_id="phase32-raw-unicode", canonical_id="phase32-fixture",
                              surface="ÉQ", match_mode="exact_case_sensitive")
    yield "custom-raw-unicode-fallback", unicode_builder.build()

def main():
    p=argparse.ArgumentParser(); p.add_argument('--target',type=Path,required=True); p.add_argument('--output',type=Path,required=True); p.add_argument('--stage',type=Path); p.add_argument('--mode',choices=['smoke','benchmark'],default='smoke'); args=p.parse_args()
    assert not args.output.exists(), 'Use a new evidence output; do not overwrite outcomes'
    sys.path.insert(0,str(args.target.resolve()))
    import range_curate
    print("Installed import:", range_curate.__file__, "target:", args.target.resolve(), flush=True)
    from range_curate.examples.general_economy import RelevanceEngine
    import range_curate_cuda
    assert 'cupy' not in sys.modules, 'Public imports must not initialize CUDA'
    import pyarrow as pa
    record={'mode':args.mode,'target':str(args.target.resolve()),'status':'running','checks':[],'calls':[]}
    def save(): args.output.write_text(json.dumps(record,indent=2,default=str)+'\n',encoding='utf-8')
    save()
    try:
        if args.mode=='smoke':
            texts=['QQQ','İ QQQ','é SPY','ffi phasealpha','phasealpha '*33,'SPY '*33,'x '*33000+'QQQ','ÉQ','','QXZ phaseevent ok']
            for name,taxonomy in custom_taxonomies():
                cpu=RelevanceEngine(taxonomy,workers=10); gpu=RelevanceEngine(taxonomy,workers=10,backend='cuda')
                prior=None
                for large in (False,True):
                    array=pa.array(['prefix',*texts,'suffix'],type=pa.large_string() if large else pa.string()).slice(1,len(texts))
                    table=pa.table({'text':array,'timestamp':pa.array([datetime(2020,7,1,tzinfo=timezone.utc)]*len(texts),type=pa.timestamp('us',tz='UTC'))})
                    expected=all_fact_sha256(cpu.screen_arrow(table),len(texts)); result=gpu.screen_arrow(table); observed=all_fact_sha256(result,len(texts)); assert expected==observed
                    if prior: assert all_fact_sha256(prior[0],len(texts))==prior[1]
                    prior=(result,observed)
                    runtime=gpu._backend._engine
                    record['checks'].append({'taxonomy':name,'large_string':large,'digest':observed,'profile':runtime.last_profile})
                    if name=='custom-raw-unicode-fallback': assert runtime.runtime.unsupported_reason
                    else:
                        assert runtime.runtime.unsupported_reason is None
                        assert runtime.last_profile['global_oversized_rows']>=1
                        assert runtime.last_profile['overflow_rows']>=1
                    save()
                record['binary_identity']=runtime.backend_report
                del cpu,gpu,runtime,prior,result; gc.collect()
            import cupy as cp
            record['gpu']=cp.cuda.runtime.getDeviceProperties(0)['name'].decode(); record['cupy']=cp.__version__
        else:
            assert args.stage and args.stage.is_file()
            h=hashlib.sha256()
            with args.stage.open('rb') as f:
                for b in iter(lambda:f.read(8*1024**2),b''): h.update(b)
            assert h.hexdigest()=='769d7de4a8295f4ce3f3d7508774e57ebdeaf2de9b4f4d7e31d1bb5a84d2865f'
            record['stage_sha256']=h.hexdigest()
            mapped=pa.memory_map(str(args.stage.resolve()),'r'); table=pa.ipc.open_file(mapped).read_all(); assert table.num_rows==10000000
            cpu=RelevanceEngine(workers=10); oracle=cpu.screen_arrow(table); expected=all_fact_sha256(oracle,table.num_rows); del oracle,cpu; gc.collect()
            gpu=RelevanceEngine(workers=10,backend='cuda'); warm=gpu.screen_arrow(table); assert all_fact_sha256(warm,table.num_rows)==expected; del warm; gc.collect()
            record['full_fact_sha256']=expected; record['backend']=gpu._backend._engine.backend_report; save()
            for i in range(8):
                started=time.perf_counter(); result=gpu.screen_arrow(table); seconds=time.perf_counter()-started
                assert all_fact_sha256(result,table.num_rows)==expected
                record['calls'].append({'index':i,'seconds':seconds,'articles_per_second':table.num_rows/seconds,'exact':True}); save(); print('call',i+1,round(table.num_rows/seconds),'articles/s',flush=True); del result; gc.collect()
            times=[c['seconds'] for c in record['calls']]; median=statistics.median(times); drift=abs(statistics.median(times[:4])-statistics.median(times[4:]))/min(statistics.median(times[:4]),statistics.median(times[4:])); record['summary']={'median_articles_per_second':table.num_rows/median,'median_seconds':median,'drift':drift,'stable':drift<=.05,'historical_peak_reproduced':table.num_rows/median>=1800000}
            import cupy,numpy
            record['environment']={'gpu':cupy.cuda.runtime.getDeviceProperties(0)['name'].decode(),'cupy':cupy.__version__,'numpy':numpy.__version__,'pyarrow':pa.__version__,'python':sys.version}
        record['status']='passed'; save(); print(json.dumps({'status':record['status'],'summary':record.get('summary'),'checks':len(record['checks'])}))
    except BaseException as e:
        record.update(status='failed',error=repr(e)); save(); raise

if __name__=='__main__': main()