Download src/decode.py from dayomtechnologies/shilluk_mt: direct link, hf CLI and curl.
- Browser
- Download file 4.23 kB
-
https://huggingface.co/dayomtechnologies/shilluk_mt/resolve/main/src/decode.py
- Command line
-
hf download hf://dayomtechnologies/shilluk_mt/src/decode.py
-
curl -L -o decode.py https://huggingface.co/dayomtechnologies/shilluk_mt/resolve/main/src/decode.py
4.23 kB
| """Greedy and beam-search decoding (no KV cache: simple and correct; fast enough for small models).""" | |
| import math | |
| import torch | |
| def _encode_source(model, tok, text, device): | |
| ids = tok.encode(text)[: model.max_seq_len - 2] | |
| src = torch.tensor([[tok.BOS_ID] + ids + [tok.EOS_ID]], dtype=torch.long, device=device) | |
| src_mask = model.make_src_mask(src) | |
| return src, src_mask, model.encode(src, src_mask) | |
| def _max_steps(model, src): | |
| return max(1, min(model.max_seq_len - 1, int(src.size(1) * 2) + 10)) | |
| def greedy_decode(model, tok, text, device, max_len=None): | |
| model.eval() | |
| if not text.strip(): | |
| return "" | |
| src, src_mask, memory = _encode_source(model, tok, text, device) | |
| steps = min(max_len or 10 ** 9, _max_steps(model, src)) | |
| ys = torch.tensor([[tok.BOS_ID]], dtype=torch.long, device=device) | |
| for _ in range(steps): | |
| logits = model.decode(ys, memory, src_mask)[:, -1, :].float() | |
| logits[:, tok.PAD_ID] = float("-inf") | |
| logits[:, tok.BOS_ID] = float("-inf") | |
| nxt = int(logits.argmax(-1)) | |
| if nxt == tok.EOS_ID: | |
| break | |
| ys = torch.cat([ys, torch.tensor([[nxt]], device=device)], dim=1) | |
| return tok.decode(ys[0, 1:].tolist()) | |
| def _length_penalty(length, alpha): | |
| return ((5.0 + length) / 6.0) ** alpha | |
| def beam_search_decode(model, tok, text, device, beam_size=5, max_len=None, alpha=0.6): | |
| model.eval() | |
| if not text.strip(): | |
| return "" | |
| src, src_mask, memory = _encode_source(model, tok, text, device) | |
| steps = min(max_len or 10 ** 9, _max_steps(model, src)) | |
| seqs = torch.tensor([[tok.BOS_ID]], dtype=torch.long, device=device) # [k, t] | |
| scores = torch.zeros(1, device=device) # [k] | |
| finished = [] # (normalised_score, ids) | |
| for step in range(steps): | |
| k = seqs.size(0) | |
| logits = model.decode(seqs, memory.expand(k, -1, -1), src_mask.expand(k, -1, -1, -1))[:, -1, :] | |
| logp = torch.log_softmax(logits.float(), dim=-1) | |
| logp[:, tok.PAD_ID] = float("-inf") | |
| logp[:, tok.BOS_ID] = float("-inf") | |
| V = logp.size(-1) | |
| flat = (scores.unsqueeze(1) + logp).view(-1) | |
| top_scores, top_idx = flat.topk(min(2 * beam_size, flat.numel())) | |
| new_seqs, new_scores = [], [] | |
| for s, idx in zip(top_scores.tolist(), top_idx.tolist()): | |
| if not math.isfinite(s): | |
| continue | |
| b, t = divmod(idx, V) | |
| if t == tok.EOS_ID: | |
| finished.append((s / _length_penalty(step + 1, alpha), seqs[b, 1:].tolist())) | |
| else: | |
| new_seqs.append(torch.cat([seqs[b], torch.tensor([t], device=device)])) | |
| new_scores.append(s) | |
| if len(new_seqs) == beam_size: | |
| break | |
| if not new_seqs: | |
| break | |
| seqs = torch.stack(new_seqs) | |
| scores = torch.tensor(new_scores, device=device) | |
| if len(finished) >= beam_size: | |
| # Stop only when even the WORST of the best `beam_size` finished hypotheses already beats | |
| # the best hypothesis still being extended. (Stopping as soon as `beam_size` hypotheses | |
| # have finished would let early, low-quality endings crowd out the real best one.) | |
| finished.sort(key=lambda x: x[0], reverse=True) | |
| finished = finished[:beam_size] | |
| best_alive = scores.max().item() / _length_penalty(step + 1, alpha) | |
| if finished[-1][0] >= best_alive: | |
| break | |
| if not finished: # ran out of steps: fall back to the best unfinished hypotheses | |
| for s, seq in zip(scores.tolist(), seqs): | |
| finished.append((s / _length_penalty(seqs.size(1) - 1, alpha), seq[1:].tolist())) | |
| best = max(finished, key=lambda x: x[0])[1] | |
| return tok.decode(best) | |
| def translate(model, tok, text, device, beam_size=5, max_len=None): | |
| """beam_size <= 1 -> greedy.""" | |
| if beam_size and beam_size > 1: | |
| return beam_search_decode(model, tok, text, device, beam_size=beam_size, max_len=max_len) | |
| return greedy_decode(model, tok, text, device, max_len=max_len) | |