Хранить только genome_json+скаляры на прогон, детали строить на лету
decoded_summary_json раньше писал полную build_detail() (геометрия, URL, электрика по каждой ступени) на каждый из миллионов прогонов — это и душило CPU при GPU-эволюции (пул процессов нужен был только чтобы успевать за GPU), и разогнало базу до 70ГБ на 12.9М строк, пока диск не забился под завязку. genome_json уже достаточен для полного воспроизведения — теперь пишем дешёвую decoded_summary(), а run_detail() на веб-морде пересчитывает полную физику по клику (как уже делает render.py для графиков/анимации).
This commit is contained in:
@@ -314,29 +314,31 @@ def evaluate_genomes_gpu(
|
|||||||
|
|
||||||
|
|
||||||
def gpu_record_worker(payload) -> RunRecord:
|
def gpu_record_worker(payload) -> RunRecord:
|
||||||
"""Собирает RunRecord (decode + build_detail + стоимость + JSON) в процессе
|
"""Собирает RunRecord (decode + стоимость + JSON) в процессе пула —
|
||||||
пула — параллельно с GPU-оценкой следующих батчей. Тяжёлая часть записи
|
параллельно с GPU-оценкой следующих батчей.
|
||||||
(детализация + сериализация) не должна душить GPU одним потоком Python.
|
|
||||||
|
detail НЕ строится (build_detail дорогая — полная физика по каждой
|
||||||
|
ступени — и не нужна для хранения): genome_json уже достаточен, чтобы
|
||||||
|
воспроизвести всё через decode()+build_detail() на лету при просмотре
|
||||||
|
конкретного прогона (см. web/stats.run_detail). Пишем дешёвую
|
||||||
|
decoded_summary() — иначе на миллионах прогонов это дорогая работа,
|
||||||
|
съедающая и CPU (душит GPU), и диск (десятки ГБ на пустом месте).
|
||||||
|
|
||||||
Работает в воркере ProcessPoolExecutor с worker_context.init_worker
|
Работает в воркере ProcessPoolExecutor с worker_context.init_worker
|
||||||
(база компонентов уже загружена в процессе).
|
(база компонентов уже загружена в процессе).
|
||||||
"""
|
"""
|
||||||
from gausse.optim import worker_context
|
from gausse.optim import worker_context
|
||||||
from gausse.optim.objective import MODEL_VERSION, compute_cost_rub
|
from gausse.optim.objective import MODEL_VERSION, compute_cost_rub, decoded_summary
|
||||||
|
|
||||||
genome, outcome, search_mode = payload
|
genome, outcome, search_mode = payload
|
||||||
db, bounds = worker_context.db, worker_context.bounds
|
db, bounds = worker_context.db, worker_context.bounds
|
||||||
config, ix, iv = decode(genome, db, bounds)
|
config, _ix, _iv = decode(genome, db, bounds)
|
||||||
detail = build_detail(
|
|
||||||
config, _FEASIBLE if outcome["feasible"] else None, db, ix, iv,
|
|
||||||
genome.tube_inner_d_m, genome.tube_wall_m,
|
|
||||||
)
|
|
||||||
return RunRecord(
|
return RunRecord(
|
||||||
run_id=str(uuid.uuid4()),
|
run_id=str(uuid.uuid4()),
|
||||||
timestamp=datetime.now(timezone.utc).isoformat(),
|
timestamp=datetime.now(timezone.utc).isoformat(),
|
||||||
search_mode=search_mode,
|
search_mode=search_mode,
|
||||||
genome_json=json.dumps(genome_to_dict(genome)),
|
genome_json=json.dumps(genome_to_dict(genome)),
|
||||||
decoded_summary_json=json.dumps(detail, ensure_ascii=False),
|
decoded_summary_json=json.dumps(decoded_summary(genome, db), ensure_ascii=False),
|
||||||
feasible=outcome["feasible"],
|
feasible=outcome["feasible"],
|
||||||
model_version=MODEL_VERSION,
|
model_version=MODEL_VERSION,
|
||||||
infeasible_reason=outcome["reason"],
|
infeasible_reason=outcome["reason"],
|
||||||
@@ -396,18 +398,15 @@ def run_gpu_sweep(
|
|||||||
|
|
||||||
|
|
||||||
def _record(st: _State, db, bounds: SearchBounds, backend, efficiency, exit_v):
|
def _record(st: _State, db, bounds: SearchBounds, backend, efficiency, exit_v):
|
||||||
|
from gausse.optim.objective import decoded_summary
|
||||||
|
|
||||||
feasible = efficiency is not None
|
feasible = efficiency is not None
|
||||||
cfg, _, _ = decode(st.genome, db, bounds)
|
|
||||||
detail = build_detail(
|
|
||||||
cfg, _FEASIBLE if feasible else None, db, st.pre.initial_x_m, st.pre.initial_v_mps,
|
|
||||||
st.genome.tube_inner_d_m, st.genome.tube_wall_m,
|
|
||||||
)
|
|
||||||
return RunRecord(
|
return RunRecord(
|
||||||
run_id=str(uuid.uuid4()),
|
run_id=str(uuid.uuid4()),
|
||||||
timestamp=datetime.now(timezone.utc).isoformat(),
|
timestamp=datetime.now(timezone.utc).isoformat(),
|
||||||
search_mode=f"gpu-sweep-{backend}",
|
search_mode=f"gpu-sweep-{backend}",
|
||||||
genome_json=json.dumps(genome_to_dict(st.genome)),
|
genome_json=json.dumps(genome_to_dict(st.genome)),
|
||||||
decoded_summary_json=json.dumps(detail, ensure_ascii=False),
|
decoded_summary_json=json.dumps(decoded_summary(st.genome, db), ensure_ascii=False),
|
||||||
feasible=feasible,
|
feasible=feasible,
|
||||||
model_version=MODEL_VERSION,
|
model_version=MODEL_VERSION,
|
||||||
infeasible_reason=st.reason,
|
infeasible_reason=st.reason,
|
||||||
|
|||||||
@@ -61,10 +61,11 @@ def _wire_resistance_ohm(resistivity_ohm_m: float, gauge_mm: float, wire_length_
|
|||||||
|
|
||||||
|
|
||||||
def decoded_summary(genome: Genome, db: ComponentDatabase) -> dict:
|
def decoded_summary(genome: Genome, db: ComponentDatabase) -> dict:
|
||||||
"""Краткая сводка только по геному (без результатов симуляции).
|
"""Краткая сводка только по геному (без результатов симуляции) — это и
|
||||||
|
пишется в decoded_summary_json для каждого прогона: genome_json уже
|
||||||
Оставлена для обратной совместимости; подробную запись со всей физикой
|
достаточен для полного воспроизведения через build_detail() на лету,
|
||||||
и результатами по каждой ступени строит `build_detail` (её и пишем в БД).
|
так что хранить дорогую подробную запись для каждого из миллионов
|
||||||
|
прогонов незачем (см. web/stats.run_detail для просмотра одного прогона).
|
||||||
"""
|
"""
|
||||||
material = db.projectile_materials[genome.projectile.material_idx % len(db.projectile_materials)]
|
material = db.projectile_materials[genome.projectile.material_idx % len(db.projectile_materials)]
|
||||||
stages_summary = []
|
stages_summary = []
|
||||||
@@ -259,10 +260,15 @@ def build_detail(config, coilgun_result: CoilgunResult, db: ComponentDatabase, i
|
|||||||
|
|
||||||
|
|
||||||
def evaluate(genome: Genome, db: ComponentDatabase, bounds: SearchBounds) -> EvaluationResult:
|
def evaluate(genome: Genome, db: ComponentDatabase, bounds: SearchBounds) -> EvaluationResult:
|
||||||
|
"""detail НЕ строится здесь: build_detail — дорогая (полная физика по
|
||||||
|
каждой ступени) и не нужна ни фитнесу, ни хранению — geneome_json уже
|
||||||
|
достаточен, чтобы воспроизвести всё через decode()+build_detail() на
|
||||||
|
лету (см. web/stats.run_detail). build_run_record() кладёт в БД дешёвую
|
||||||
|
decoded_summary() вместо этого.
|
||||||
|
"""
|
||||||
config, initial_x_m, initial_v_mps = decode(genome, db, bounds)
|
config, initial_x_m, initial_v_mps = decode(genome, db, bounds)
|
||||||
cost_rub = compute_cost_rub(config, db)
|
cost_rub = compute_cost_rub(config, db)
|
||||||
result = run_coilgun(config)
|
result = run_coilgun(config)
|
||||||
detail = build_detail(config, result, db, initial_x_m, initial_v_mps, genome.tube_inner_d_m, genome.tube_wall_m)
|
|
||||||
|
|
||||||
if not result.feasible:
|
if not result.feasible:
|
||||||
n_stages = len(config.stages)
|
n_stages = len(config.stages)
|
||||||
@@ -275,7 +281,6 @@ def evaluate(genome: Genome, db: ComponentDatabase, bounds: SearchBounds) -> Eva
|
|||||||
reason=result.reason,
|
reason=result.reason,
|
||||||
failed_stage_index=result.failed_stage_index,
|
failed_stage_index=result.failed_stage_index,
|
||||||
energy_breakdown={},
|
energy_breakdown={},
|
||||||
detail=detail,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
energy_breakdown = {
|
energy_breakdown = {
|
||||||
@@ -291,7 +296,6 @@ def evaluate(genome: Genome, db: ComponentDatabase, bounds: SearchBounds) -> Eva
|
|||||||
efficiency=result.efficiency,
|
efficiency=result.efficiency,
|
||||||
exit_velocity_mps=result.exit_v_mps,
|
exit_velocity_mps=result.exit_v_mps,
|
||||||
energy_breakdown=energy_breakdown,
|
energy_breakdown=energy_breakdown,
|
||||||
detail=detail,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ CREATE TABLE IF NOT EXISTS runs (
|
|||||||
timestamp TEXT NOT NULL,
|
timestamp TEXT NOT NULL,
|
||||||
search_mode TEXT NOT NULL,
|
search_mode TEXT NOT NULL,
|
||||||
genome_json TEXT NOT NULL,
|
genome_json TEXT NOT NULL,
|
||||||
|
-- ДЕШЁВАЯ сводка (objective.decoded_summary) — НЕ полная физика по
|
||||||
|
-- ступеням. genome_json сам по себе достаточен, чтобы воспроизвести
|
||||||
|
-- всё через decode()+build_detail() на лету (см. web/stats.run_detail);
|
||||||
|
-- хранить дорогую детализацию для каждого из миллионов прогонов
|
||||||
|
-- бессмысленно — так база росла на пустом месте (70ГБ на 12.9М строк).
|
||||||
decoded_summary_json TEXT NOT NULL,
|
decoded_summary_json TEXT NOT NULL,
|
||||||
feasible INTEGER NOT NULL,
|
feasible INTEGER NOT NULL,
|
||||||
infeasible_reason TEXT,
|
infeasible_reason TEXT,
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ def overview(db_path: Path, log_path: Path | None = None, top_n: int = 15) -> di
|
|||||||
def _summarize(rows):
|
def _summarize(rows):
|
||||||
out = []
|
out = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
detail = json.loads(r.decoded_summary_json) if r.decoded_summary_json else {}
|
summary = json.loads(r.decoded_summary_json) if r.decoded_summary_json else {}
|
||||||
out.append({
|
out.append({
|
||||||
"run_id": r.run_id, "search_mode": r.search_mode, "efficiency": r.efficiency,
|
"run_id": r.run_id, "search_mode": r.search_mode, "efficiency": r.efficiency,
|
||||||
"exit_velocity_mps": r.exit_velocity_mps, "cost_rub": r.cost_rub,
|
"exit_velocity_mps": r.exit_velocity_mps, "cost_rub": r.cost_rub,
|
||||||
"n_stages": detail.get("n_stages"), "timestamp": r.timestamp,
|
"n_stages": len(summary.get("stages", [])), "timestamp": r.timestamp,
|
||||||
})
|
})
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -115,11 +115,41 @@ def _last_cycle_best(log_lines: list[str]) -> float | None:
|
|||||||
|
|
||||||
|
|
||||||
def run_detail(db_path: Path, run_id: str) -> dict | None:
|
def run_detail(db_path: Path, run_id: str) -> dict | None:
|
||||||
|
"""Подробности одного прогона — НЕ читаются из БД (там только дешёвая
|
||||||
|
decoded_summary), а пересчитываются на лету из genome_json тем же
|
||||||
|
decode()+build_detail(), что и остальной рендер веб-морды (см.
|
||||||
|
web/render.py). Это единичный запрос по клику, не миллионы прогонов —
|
||||||
|
пересимулировать дешевле, чем хранить полную физику для каждого."""
|
||||||
|
from gausse.components.database import ComponentDatabase
|
||||||
|
from gausse.optim.objective import build_detail
|
||||||
|
from gausse.optim.search_space import SearchBounds, decode, genome_from_dict
|
||||||
|
from gausse.sim.coilgun import run_coilgun
|
||||||
|
|
||||||
conn = open_connection(db_path)
|
conn = open_connection(db_path)
|
||||||
try:
|
try:
|
||||||
row = fetch_run_by_id(conn, run_id)
|
row = fetch_run_by_id(conn, run_id)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
genome = genome_from_dict(json.loads(row.genome_json))
|
||||||
|
db = ComponentDatabase.load()
|
||||||
|
bounds = SearchBounds()
|
||||||
|
config, initial_x_m, initial_v_mps = decode(genome, db, bounds)
|
||||||
|
result = run_coilgun(config)
|
||||||
|
detail = build_detail(config, result, db, initial_x_m, initial_v_mps, genome.tube_inner_d_m, genome.tube_wall_m)
|
||||||
|
energy_breakdown = (
|
||||||
|
{
|
||||||
|
"total_energy_in_j": result.total_energy_in_j,
|
||||||
|
"total_energy_dissipated_j": result.total_energy_dissipated_j,
|
||||||
|
"total_kinetic_energy_delta_j": result.total_kinetic_energy_delta_j,
|
||||||
|
"exit_kinetic_energy_j": result.exit_kinetic_energy_j,
|
||||||
|
}
|
||||||
|
if result.feasible
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"run_id": row.run_id,
|
"run_id": row.run_id,
|
||||||
"search_mode": row.search_mode,
|
"search_mode": row.search_mode,
|
||||||
@@ -132,8 +162,6 @@ def run_detail(db_path: Path, run_id: str) -> dict | None:
|
|||||||
"exit_velocity_mps": row.exit_velocity_mps,
|
"exit_velocity_mps": row.exit_velocity_mps,
|
||||||
"cost_rub": row.cost_rub,
|
"cost_rub": row.cost_rub,
|
||||||
"timestamp": row.timestamp,
|
"timestamp": row.timestamp,
|
||||||
"detail": json.loads(row.decoded_summary_json) if row.decoded_summary_json else {},
|
"detail": detail,
|
||||||
"energy_breakdown": json.loads(row.energy_breakdown_json) if row.energy_breakdown_json else {},
|
"energy_breakdown": energy_breakdown,
|
||||||
}
|
}
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user